1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "net/ftp/ftp_ctrl_response_buffer.h"
6
7#include "base/bind.h"
8#include "base/logging.h"
9#include "base/strings/string_number_conversions.h"
10#include "base/strings/string_piece.h"
11#include "base/values.h"
12#include "net/base/net_errors.h"
13
14namespace net {
15
16// static
17const int FtpCtrlResponse::kInvalidStatusCode = -1;
18
19FtpCtrlResponse::FtpCtrlResponse() : status_code(kInvalidStatusCode) {}
20
21FtpCtrlResponse::~FtpCtrlResponse() {}
22
23FtpCtrlResponseBuffer::FtpCtrlResponseBuffer(const BoundNetLog& net_log)
24    : multiline_(false),
25      net_log_(net_log) {
26}
27
28FtpCtrlResponseBuffer::~FtpCtrlResponseBuffer() {}
29
30int FtpCtrlResponseBuffer::ConsumeData(const char* data, int data_length) {
31  buffer_.append(data, data_length);
32  ExtractFullLinesFromBuffer();
33
34  while (!lines_.empty()) {
35    ParsedLine line = lines_.front();
36    lines_.pop();
37
38    if (multiline_) {
39      if (!line.is_complete || line.status_code != response_buf_.status_code) {
40        line_buf_.append(line.raw_text);
41        continue;
42      }
43
44      response_buf_.lines.push_back(line_buf_);
45
46      line_buf_ = line.status_text;
47      DCHECK_EQ(line.status_code, response_buf_.status_code);
48
49      if (!line.is_multiline) {
50        response_buf_.lines.push_back(line_buf_);
51        responses_.push(response_buf_);
52
53        // Prepare to handle following lines.
54        response_buf_ = FtpCtrlResponse();
55        line_buf_.clear();
56        multiline_ = false;
57      }
58    } else {
59      if (!line.is_complete)
60        return ERR_INVALID_RESPONSE;
61
62      response_buf_.status_code = line.status_code;
63      if (line.is_multiline) {
64        line_buf_ = line.status_text;
65        multiline_ = true;
66      } else {
67        response_buf_.lines.push_back(line.status_text);
68        responses_.push(response_buf_);
69
70        // Prepare to handle following lines.
71        response_buf_ = FtpCtrlResponse();
72        line_buf_.clear();
73      }
74    }
75  }
76
77  return OK;
78}
79
80namespace {
81
82base::Value* NetLogFtpCtrlResponseCallback(const FtpCtrlResponse* response,
83                                           NetLog::LogLevel log_level) {
84  base::ListValue* lines = new base::ListValue();
85  lines->AppendStrings(response->lines);
86
87  base::DictionaryValue* dict = new base::DictionaryValue();
88  dict->SetInteger("status_code", response->status_code);
89  dict->Set("lines", lines);
90  return dict;
91}
92
93}  // namespace
94
95FtpCtrlResponse FtpCtrlResponseBuffer::PopResponse() {
96  FtpCtrlResponse result = responses_.front();
97  responses_.pop();
98
99  net_log_.AddEvent(NetLog::TYPE_FTP_CONTROL_RESPONSE,
100                    base::Bind(&NetLogFtpCtrlResponseCallback, &result));
101
102  return result;
103}
104
105FtpCtrlResponseBuffer::ParsedLine::ParsedLine()
106    : has_status_code(false),
107      is_multiline(false),
108      is_complete(false),
109      status_code(FtpCtrlResponse::kInvalidStatusCode) {
110}
111
112// static
113FtpCtrlResponseBuffer::ParsedLine FtpCtrlResponseBuffer::ParseLine(
114    const std::string& line) {
115  ParsedLine result;
116
117  if (line.length() >= 3) {
118    if (base::StringToInt(base::StringPiece(line.begin(), line.begin() + 3),
119                          &result.status_code))
120      result.has_status_code = (100 <= result.status_code &&
121                                result.status_code <= 599);
122    if (result.has_status_code && line.length() >= 4 && line[3] == ' ') {
123      result.is_complete = true;
124    } else if (result.has_status_code && line.length() >= 4 && line[3] == '-') {
125      result.is_complete = true;
126      result.is_multiline = true;
127    }
128  }
129
130  if (result.is_complete) {
131    result.status_text = line.substr(4);
132  } else {
133    result.status_text = line;
134  }
135
136  result.raw_text = line;
137
138  return result;
139}
140
141void FtpCtrlResponseBuffer::ExtractFullLinesFromBuffer() {
142  int cut_pos = 0;
143  for (size_t i = 0; i < buffer_.length(); i++) {
144    if (i >= 1 && buffer_[i - 1] == '\r' && buffer_[i] == '\n') {
145      lines_.push(ParseLine(buffer_.substr(cut_pos, i - cut_pos - 1)));
146      cut_pos = i + 1;
147    }
148  }
149  buffer_.erase(0, cut_pos);
150}
151
152}  // namespace net
153