// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/ftp/ftp_ctrl_response_buffer.h"

#include <utility>

#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/strings/string_piece.h"
#include "base/values.h"
#include "net/base/net_errors.h"
#include "net/base/parse_number.h"
#include "net/log/net_log.h"
#include "net/log/net_log_event_type.h"

namespace net {

// static
const int FtpCtrlResponse::kInvalidStatusCode = -1;

FtpCtrlResponse::FtpCtrlResponse() : status_code(kInvalidStatusCode) {}

FtpCtrlResponse::FtpCtrlResponse(const FtpCtrlResponse& other) = default;

FtpCtrlResponse::~FtpCtrlResponse() = default;

FtpCtrlResponseBuffer::FtpCtrlResponseBuffer(const NetLogWithSource& net_log)
    : multiline_(false), net_log_(net_log) {}

FtpCtrlResponseBuffer::~FtpCtrlResponseBuffer() = default;

int FtpCtrlResponseBuffer::ConsumeData(const char* data, int data_length) {
  buffer_.append(data, data_length);
  ExtractFullLinesFromBuffer();

  while (!lines_.empty()) {
    ParsedLine line = lines_.front();
    lines_.pop();

    if (multiline_) {
      if (!line.is_complete || line.status_code != response_buf_.status_code) {
        line_buf_.append(line.raw_text);
        continue;
      }

      response_buf_.lines.push_back(line_buf_);

      line_buf_ = line.status_text;
      DCHECK_EQ(line.status_code, response_buf_.status_code);

      if (!line.is_multiline) {
        response_buf_.lines.push_back(line_buf_);
        responses_.push(response_buf_);

        // Prepare to handle following lines.
        response_buf_ = FtpCtrlResponse();
        line_buf_.clear();
        multiline_ = false;
      }
    } else {
      if (!line.is_complete)
        return ERR_INVALID_RESPONSE;

      response_buf_.status_code = line.status_code;
      if (line.is_multiline) {
        line_buf_ = line.status_text;
        multiline_ = true;
      } else {
        response_buf_.lines.push_back(line.status_text);
        responses_.push(response_buf_);

        // Prepare to handle following lines.
        response_buf_ = FtpCtrlResponse();
        line_buf_.clear();
      }
    }
  }

  return OK;
}

namespace {

std::unique_ptr<base::Value> NetLogFtpCtrlResponseCallback(
    const FtpCtrlResponse* response,
    NetLogCaptureMode capture_mode) {
  std::unique_ptr<base::ListValue> lines(new base::ListValue());
  for (const auto& line : response->lines)
    lines->GetList().push_back(NetLogStringValue(line));

  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
  dict->SetInteger("status_code", response->status_code);
  dict->Set("lines", std::move(lines));
  return std::move(dict);
}

}  // namespace

FtpCtrlResponse FtpCtrlResponseBuffer::PopResponse() {
  FtpCtrlResponse result = responses_.front();
  responses_.pop();

  net_log_.AddEvent(NetLogEventType::FTP_CONTROL_RESPONSE,
                    base::Bind(&NetLogFtpCtrlResponseCallback, &result));

  return result;
}

FtpCtrlResponseBuffer::ParsedLine::ParsedLine()
    : has_status_code(false),
      is_multiline(false),
      is_complete(false),
      status_code(FtpCtrlResponse::kInvalidStatusCode) {
}

FtpCtrlResponseBuffer::ParsedLine::ParsedLine(const ParsedLine& other) =
    default;

// static
FtpCtrlResponseBuffer::ParsedLine FtpCtrlResponseBuffer::ParseLine(
    const std::string& line) {
  ParsedLine result;

  if (line.length() >= 3) {
    if (ParseInt32(base::StringPiece(line.begin(), line.begin() + 3),
                   ParseIntFormat::NON_NEGATIVE, &result.status_code)) {
      result.has_status_code =
          (100 <= result.status_code && result.status_code <= 599);
    }
    if (result.has_status_code && line.length() >= 4 && line[3] == ' ') {
      result.is_complete = true;
    } else if (result.has_status_code && line.length() >= 4 && line[3] == '-') {
      result.is_complete = true;
      result.is_multiline = true;
    }
  }

  if (result.is_complete) {
    result.status_text = line.substr(4);
  } else {
    result.status_text = line;
  }

  result.raw_text = line;

  return result;
}

void FtpCtrlResponseBuffer::ExtractFullLinesFromBuffer() {
  int cut_pos = 0;
  for (size_t i = 0; i < buffer_.length(); i++) {
    if (i >= 1 && buffer_[i - 1] == '\r' && buffer_[i] == '\n') {
      lines_.push(ParseLine(buffer_.substr(cut_pos, i - cut_pos - 1)));
      cut_pos = i + 1;
    }
  }
  buffer_.erase(0, cut_pos);
}

}  // namespace net
