1 // Copyright 2018 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 "chrome/services/ipp_parser/ipp_parser.h"
6
7 #include <cups/ipp.h>
8 #include <memory>
9 #include <string>
10 #include <utility>
11 #include <vector>
12
13 #include "base/containers/flat_map.h"
14 #include "base/optional.h"
15 #include "chrome/services/cups_proxy/public/cpp/type_conversions.h"
16 #include "chrome/services/ipp_parser/public/cpp/ipp_converter.h"
17 #include "net/http/http_util.h"
18
19 namespace ipp_parser {
20 namespace {
21
22 using ipp_converter::HttpHeader;
23 using ipp_converter::kCarriage;
24 using ipp_converter::kIppSentinel;
25
26 // Log debugging error and send empty response, signalling error.
Fail(const std::string & error_log,IppParser::ParseIppCallback cb)27 void Fail(const std::string& error_log, IppParser::ParseIppCallback cb) {
28 DVLOG(1) << "IPP Parser Error: " << error_log;
29 std::move(cb).Run(nullptr);
30 return;
31 }
32
33 // Returns the starting index of the request-line-delimiter, -1 on failure.
LocateEndOfRequestLine(base::StringPiece request)34 int LocateEndOfRequestLine(base::StringPiece request) {
35 auto end_of_request_line = request.find(kCarriage);
36 if (end_of_request_line == std::string::npos) {
37 return -1;
38 }
39
40 return end_of_request_line;
41 }
42
43 // Returns the starting index of the first HTTP header, -1 on failure.
LocateStartOfHeaders(base::StringPiece request)44 int LocateStartOfHeaders(base::StringPiece request) {
45 auto idx = LocateEndOfRequestLine(request);
46 if (idx < 0) {
47 return -1;
48 }
49
50 // Advance to first header and check it exists
51 idx += strlen(kCarriage);
52 return idx < static_cast<int>(request.size()) ? idx : -1;
53 }
54
55 // Returns the starting index of the end-of-headers-delimiter, -1 on failure.
LocateEndOfHeaders(base::StringPiece request)56 int LocateEndOfHeaders(base::StringPiece request) {
57 auto idx = net::HttpUtil::LocateEndOfHeaders(request.data(), request.size());
58 if (idx < 0) {
59 return -1;
60 }
61
62 // Back up to the start of the delimiter.
63 // Note: The end-of-http-headers delimiter is 2 back-to-back carriage returns.
64 const int end_of_headers_delimiter_size = 2 * strlen(kCarriage);
65 return idx - end_of_headers_delimiter_size;
66 }
67
68 // Returns the starting index of the IPP metadata, -1 on failure.
LocateStartOfIppMetadata(base::span<const uint8_t> request)69 int LocateStartOfIppMetadata(base::span<const uint8_t> request) {
70 std::vector<char> char_buffer = ipp_converter::ConvertToCharBuffer(request);
71 return net::HttpUtil::LocateEndOfHeaders(char_buffer.data(),
72 char_buffer.size());
73 }
74
SplitRequestMetadata(base::span<const uint8_t> request,std::string * http_metadata,base::span<const uint8_t> * ipp_metadata)75 bool SplitRequestMetadata(base::span<const uint8_t> request,
76 std::string* http_metadata,
77 base::span<const uint8_t>* ipp_metadata) {
78 size_t start_of_ipp_metadata = LocateStartOfIppMetadata(request);
79 if (start_of_ipp_metadata < 0) {
80 return false;
81 }
82
83 *http_metadata =
84 ipp_converter::ConvertToString(request.first(start_of_ipp_metadata));
85 *ipp_metadata = request.subspan(start_of_ipp_metadata);
86 return true;
87 }
88
ExtractHttpRequestLine(base::StringPiece request)89 base::Optional<std::vector<std::string>> ExtractHttpRequestLine(
90 base::StringPiece request) {
91 size_t end_of_request_line = LocateEndOfRequestLine(request);
92 if (end_of_request_line < 0) {
93 return base::nullopt;
94 }
95
96 const base::StringPiece request_line_slice =
97 request.substr(0, end_of_request_line);
98 return ipp_converter::ParseRequestLine(request_line_slice);
99 }
100
ExtractHttpHeaders(base::StringPiece request)101 base::Optional<std::vector<HttpHeader>> ExtractHttpHeaders(
102 base::StringPiece request) {
103 size_t start_of_headers = LocateStartOfHeaders(request);
104 if (start_of_headers < 0) {
105 return base::nullopt;
106 }
107
108 size_t end_of_headers = LocateEndOfHeaders(request);
109 if (end_of_headers < 0) {
110 return base::nullopt;
111 }
112
113 const base::StringPiece headers_slice =
114 request.substr(start_of_headers, end_of_headers - start_of_headers);
115 return ipp_converter::ParseHeaders(headers_slice);
116 }
117
118 // Parses |ipp_metadata| and sets |ipp_message| and |ipp_data| accordingly.
119 // Returns false and leaves the outputs unchanged on failure.
ExtractIppMetadata(base::span<const uint8_t> ipp_metadata,mojom::IppMessagePtr * ipp_message,std::vector<uint8_t> * ipp_data)120 bool ExtractIppMetadata(base::span<const uint8_t> ipp_metadata,
121 mojom::IppMessagePtr* ipp_message,
122 std::vector<uint8_t>* ipp_data) {
123 printing::ScopedIppPtr ipp = ipp_converter::ParseIppMessage(ipp_metadata);
124 if (!ipp) {
125 return false;
126 }
127
128 mojom::IppMessagePtr message = ipp_converter::ConvertIppToMojo(ipp.get());
129 if (!message) {
130 return false;
131 }
132
133 size_t ipp_message_length = ippLength(ipp.get());
134 ipp_metadata = ipp_metadata.subspan(ipp_message_length);
135 *ipp_data = std::vector<uint8_t>(ipp_metadata.begin(), ipp_metadata.end());
136
137 *ipp_message = std::move(message);
138 return true;
139 }
140
141 } // namespace
142
IppParser(mojo::PendingReceiver<mojom::IppParser> receiver)143 IppParser::IppParser(mojo::PendingReceiver<mojom::IppParser> receiver)
144 : receiver_(this, std::move(receiver)) {}
145
146 IppParser::~IppParser() = default;
147
ParseIpp(const std::vector<uint8_t> & to_parse,ParseIppCallback callback)148 void IppParser::ParseIpp(const std::vector<uint8_t>& to_parse,
149 ParseIppCallback callback) {
150 // Separate |to_parse| into http metadata (interpreted as ASCII chars), and
151 // ipp metadata (interpreted as arbitrary bytes).
152 std::string http_metadata;
153 base::span<const uint8_t> ipp_metadata;
154 if (!SplitRequestMetadata(to_parse, &http_metadata, &ipp_metadata)) {
155 return Fail("Failed to split HTTP and IPP metadata", std::move(callback));
156 }
157
158 // Parse Request line.
159 auto request_line = ExtractHttpRequestLine(http_metadata);
160 if (!request_line) {
161 return Fail("Failed to parse request line", std::move(callback));
162 }
163
164 // Parse Headers.
165 auto headers = ExtractHttpHeaders(http_metadata);
166 if (!headers) {
167 return Fail("Failed to parse headers", std::move(callback));
168 }
169
170 // Parse IPP message and IPP data.
171 mojom::IppMessagePtr ipp_message;
172 std::vector<uint8_t> ipp_data;
173 if (!ExtractIppMetadata(ipp_metadata, &ipp_message, &ipp_data)) {
174 return Fail("Failed to parse IPP metadata", std::move(callback));
175 }
176
177 // Marshall response.
178 mojom::IppRequestPtr parsed_request = mojom::IppRequest::New();
179
180 std::vector<std::string> request_line_terms = *request_line;
181 parsed_request->method = request_line_terms[0];
182 parsed_request->endpoint = request_line_terms[1];
183 parsed_request->http_version = request_line_terms[2];
184
185 parsed_request->headers =
186 base::flat_map<std::string, std::string>(std::move(*headers));
187 parsed_request->ipp = std::move(ipp_message);
188 parsed_request->data = std::move(ipp_data);
189
190 DVLOG(1) << "Finished parsing IPP request.";
191 std::move(callback).Run(std::move(parsed_request));
192 }
193
194 } // namespace ipp_parser
195