| // Copyright 2018 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 "chrome/services/cups_ipp_parser/ipp_parser.h" |
| |
| #include <cups/cups.h> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/optional.h" |
| #include "chrome/services/cups_ipp_parser/public/cpp/ipp_converter.h" |
| #include "mojo/public/cpp/bindings/strong_binding.h" |
| #include "net/http/http_util.h" |
| |
| namespace chrome { |
| namespace { |
| |
| using ipp_converter::HttpHeader; |
| using ipp_converter::kCarriage; |
| using ipp_converter::kIppSentinel; |
| |
| // Log debugging error and send empty response, signalling error. |
| void Fail(const std::string& error_log, IppParser::ParseIppCallback cb) { |
| DVLOG(1) << "IPP Parser Error: " << error_log; |
| std::move(cb).Run(mojom::IppParserResult::FAILURE, nullptr); |
| return; |
| } |
| |
| // Returns the starting index of the request-line-delimiter, -1 on failure. |
| int LocateEndOfRequestLine(base::StringPiece request) { |
| auto end_of_request_line = request.find(kCarriage); |
| if (end_of_request_line == std::string::npos) { |
| return -1; |
| } |
| |
| return end_of_request_line; |
| } |
| |
| // Returns the starting index of the first HTTP header, -1 on failure. |
| int LocateStartOfHeaders(base::StringPiece request) { |
| auto idx = request.find(kCarriage); |
| if (idx == base::StringPiece::npos) { |
| return -1; |
| } |
| |
| // Advance to first header and check it exists |
| idx += strlen(kCarriage); |
| return idx < request.size() ? idx : -1; |
| } |
| |
| // Returns the starting index of the end-of-headers-delimiter, -1 on failure. |
| int LocateEndOfHeaders(base::StringPiece request) { |
| auto idx = net::HttpUtil::LocateEndOfHeaders(request.data(), request.size()); |
| if (idx < 0) { |
| return -1; |
| } |
| |
| // Back up to the start of the delimiter. |
| // Note: The end-of-http-headers delimiter is 2 back-to-back carriage returns. |
| const size_t end_of_headers_delimiter_size = 2 * strlen(kCarriage); |
| return idx - end_of_headers_delimiter_size; |
| } |
| |
| // Returns the starting index of the IPP message, -1 on failure. |
| int LocateStartOfIppMessage(base::StringPiece request) { |
| return net::HttpUtil::LocateEndOfHeaders(request.data(), request.size()); |
| } |
| |
| // Return the starting index of the IPP data/payload(pdf), |
| // Returns |request|.size() on empty IppData and -1 on failure. |
| int LocateStartOfIppData(base::StringPiece request) { |
| int end_of_headers = LocateEndOfHeaders(request); |
| if (end_of_headers < 0) { |
| return -1; |
| } |
| |
| auto idx = request.find(kIppSentinel, end_of_headers); |
| if (idx == base::StringPiece::npos) { |
| return -1; |
| } |
| |
| // Advance to start and check existence or end of request. |
| idx += strlen(kIppSentinel); |
| return idx <= request.size() ? idx : -1; |
| } |
| |
| base::Optional<std::vector<std::string>> ExtractRequestLine( |
| base::StringPiece request) { |
| int end_of_request_line = LocateEndOfRequestLine(request); |
| if (end_of_request_line < 0) { |
| return base::nullopt; |
| } |
| |
| const base::StringPiece request_line_slice = |
| request.substr(0, end_of_request_line); |
| return ipp_converter::ParseRequestLine(request_line_slice); |
| } |
| |
| base::Optional<std::vector<HttpHeader>> ExtractHeaders( |
| base::StringPiece request) { |
| int start_of_headers = LocateStartOfHeaders(request); |
| if (start_of_headers < 0) { |
| return base::nullopt; |
| } |
| |
| int end_of_headers = LocateEndOfHeaders(request); |
| if (end_of_headers < 0) { |
| return base::nullopt; |
| } |
| |
| const base::StringPiece headers_slice = |
| request.substr(start_of_headers, end_of_headers - start_of_headers); |
| return ipp_converter::ParseHeaders(headers_slice); |
| } |
| |
| mojom::IppMessagePtr ExtractIppMessage(base::StringPiece request) { |
| int start_of_ipp_message = LocateStartOfIppMessage(request); |
| if (start_of_ipp_message < 0) { |
| return nullptr; |
| } |
| |
| std::vector<uint8_t> ipp_slice = |
| ipp_converter::ConvertToByteBuffer(request.substr(start_of_ipp_message)); |
| printing::ScopedIppPtr ipp = ipp_converter::ParseIppMessage(ipp_slice); |
| if (!ipp) { |
| return nullptr; |
| } |
| |
| return ipp_converter::ConvertIppToMojo(ipp.get()); |
| } |
| |
| // Parse IPP request's |ipp_data| |
| base::Optional<std::vector<uint8_t>> ExtractIppData(base::StringPiece request) { |
| size_t start_of_ipp_data = LocateStartOfIppData(request); |
| if (start_of_ipp_data < 0) { |
| return base::nullopt; |
| } |
| |
| // Subtlety: Correctly generates empty buffers for requests without ipp_data. |
| const base::StringPiece ipp_data_slice = request.substr(start_of_ipp_data); |
| return ipp_converter::ConvertToByteBuffer(ipp_data_slice); |
| } |
| |
| } // namespace |
| |
| IppParser::IppParser( |
| std::unique_ptr<service_manager::ServiceContextRef> service_ref) |
| : service_ref_(std::move(service_ref)) {} |
| |
| IppParser::~IppParser() = default; |
| |
| // Checks that |to_parse| is a correctly formatted IPP request, per RFC2910. |
| // Calls |callback| with a fully parsed IPP request on success, empty on |
| // failure. |
| void IppParser::ParseIpp(const std::string& to_parse, |
| ParseIppCallback callback) { |
| // Parse Request line |
| auto request_line = ExtractRequestLine(to_parse); |
| if (!request_line) { |
| return Fail("Failed to parse request line", std::move(callback)); |
| } |
| |
| // Parse Headers |
| auto headers = ExtractHeaders(to_parse); |
| if (!headers) { |
| return Fail("Failed to parse headers", std::move(callback)); |
| } |
| |
| // Parse IPP message |
| auto ipp_message = ExtractIppMessage(to_parse); |
| if (!ipp_message) { |
| return Fail("Failed to parse IPP message", std::move(callback)); |
| } |
| |
| // Parse IPP data |
| auto ipp_data = ExtractIppData(to_parse); |
| if (!ipp_data) { |
| return Fail("Failed to parse IPP data", std::move(callback)); |
| } |
| |
| // Marshall response |
| mojom::IppRequestPtr parsed_request = mojom::IppRequest::New(); |
| |
| std::vector<std::string> request_line_terms = request_line.value(); |
| parsed_request->method = request_line_terms[0]; |
| parsed_request->endpoint = request_line_terms[1]; |
| parsed_request->http_version = request_line_terms[2]; |
| |
| parsed_request->headers = std::move(headers.value()); |
| parsed_request->ipp = std::move(ipp_message); |
| parsed_request->data = std::move(ipp_data.value()); |
| |
| DVLOG(1) << "Finished parsing IPP request."; |
| std::move(callback).Run(mojom::IppParserResult::SUCCESS, |
| std::move(parsed_request)); |
| } |
| |
| } // namespace chrome |