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