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 <utility>
8 
9 #include "base/logging.h"
10 #include "base/strings/string_piece.h"
11 #include "base/values.h"
12 #include "net/base/net_errors.h"
13 #include "net/base/parse_number.h"
14 #include "net/log/net_log_event_type.h"
15 #include "net/log/net_log_values.h"
16 
17 namespace net {
18 
19 // static
20 const int FtpCtrlResponse::kInvalidStatusCode = -1;
21 
FtpCtrlResponse()22 FtpCtrlResponse::FtpCtrlResponse() : status_code(kInvalidStatusCode) {}
23 
24 FtpCtrlResponse::FtpCtrlResponse(const FtpCtrlResponse& other) = default;
25 
26 FtpCtrlResponse::~FtpCtrlResponse() = default;
27 
FtpCtrlResponseBuffer(const NetLogWithSource & net_log)28 FtpCtrlResponseBuffer::FtpCtrlResponseBuffer(const NetLogWithSource& net_log)
29     : multiline_(false), net_log_(net_log) {}
30 
31 FtpCtrlResponseBuffer::~FtpCtrlResponseBuffer() = default;
32 
ConsumeData(const char * data,int data_length)33 int FtpCtrlResponseBuffer::ConsumeData(const char* data, int data_length) {
34   buffer_.append(data, data_length);
35   ExtractFullLinesFromBuffer();
36 
37   while (!lines_.empty()) {
38     ParsedLine line = lines_.front();
39     lines_.pop();
40 
41     if (multiline_) {
42       if (!line.is_complete || line.status_code != response_buf_.status_code) {
43         line_buf_.append(line.raw_text);
44         continue;
45       }
46 
47       response_buf_.lines.push_back(line_buf_);
48 
49       line_buf_ = line.status_text;
50       DCHECK_EQ(line.status_code, response_buf_.status_code);
51 
52       if (!line.is_multiline) {
53         response_buf_.lines.push_back(line_buf_);
54         responses_.push(response_buf_);
55 
56         // Prepare to handle following lines.
57         response_buf_ = FtpCtrlResponse();
58         line_buf_.clear();
59         multiline_ = false;
60       }
61     } else {
62       if (!line.is_complete)
63         return ERR_INVALID_RESPONSE;
64 
65       response_buf_.status_code = line.status_code;
66       if (line.is_multiline) {
67         line_buf_ = line.status_text;
68         multiline_ = true;
69       } else {
70         response_buf_.lines.push_back(line.status_text);
71         responses_.push(response_buf_);
72 
73         // Prepare to handle following lines.
74         response_buf_ = FtpCtrlResponse();
75         line_buf_.clear();
76       }
77     }
78   }
79 
80   return OK;
81 }
82 
83 namespace {
84 
NetLogFtpCtrlResponseParams(const FtpCtrlResponse * response)85 base::Value NetLogFtpCtrlResponseParams(const FtpCtrlResponse* response) {
86   base::ListValue lines;
87   for (const auto& line : response->lines)
88     lines.Append(NetLogStringValue(line));
89 
90   base::DictionaryValue dict;
91   dict.SetInteger("status_code", response->status_code);
92   dict.SetKey("lines", std::move(lines));
93   return std::move(dict);
94 }
95 
96 }  // namespace
97 
PopResponse()98 FtpCtrlResponse FtpCtrlResponseBuffer::PopResponse() {
99   FtpCtrlResponse result = responses_.front();
100   responses_.pop();
101 
102   net_log_.AddEvent(NetLogEventType::FTP_CONTROL_RESPONSE,
103                     [&] { return NetLogFtpCtrlResponseParams(&result); });
104 
105   return result;
106 }
107 
ParsedLine()108 FtpCtrlResponseBuffer::ParsedLine::ParsedLine()
109     : has_status_code(false),
110       is_multiline(false),
111       is_complete(false),
112       status_code(FtpCtrlResponse::kInvalidStatusCode) {
113 }
114 
115 FtpCtrlResponseBuffer::ParsedLine::ParsedLine(const ParsedLine& other) =
116     default;
117 
118 // static
ParseLine(const std::string & line)119 FtpCtrlResponseBuffer::ParsedLine FtpCtrlResponseBuffer::ParseLine(
120     const std::string& line) {
121   ParsedLine result;
122 
123   if (line.length() >= 3) {
124     if (ParseInt32(base::StringPiece(line.begin(), line.begin() + 3),
125                    ParseIntFormat::NON_NEGATIVE, &result.status_code)) {
126       result.has_status_code =
127           (100 <= result.status_code && result.status_code <= 599);
128     }
129     if (result.has_status_code && line.length() >= 4 && line[3] == ' ') {
130       result.is_complete = true;
131     } else if (result.has_status_code && line.length() >= 4 && line[3] == '-') {
132       result.is_complete = true;
133       result.is_multiline = true;
134     }
135   }
136 
137   if (result.is_complete) {
138     result.status_text = line.substr(4);
139   } else {
140     result.status_text = line;
141   }
142 
143   result.raw_text = line;
144 
145   return result;
146 }
147 
ExtractFullLinesFromBuffer()148 void FtpCtrlResponseBuffer::ExtractFullLinesFromBuffer() {
149   int cut_pos = 0;
150   for (size_t i = 0; i < buffer_.length(); i++) {
151     if (i >= 1 && buffer_[i - 1] == '\r' && buffer_[i] == '\n') {
152       lines_.push(ParseLine(buffer_.substr(cut_pos, i - cut_pos - 1)));
153       cut_pos = i + 1;
154     }
155   }
156   buffer_.erase(0, cut_pos);
157 }
158 
159 }  // namespace net
160