text: // 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/ipp_parser/ipp_parser.h"
  
  #include <cups/ipp.h>
  #include <memory>
  #include <string>
  #include <utility>
  #include <vector>
  
  #include "base/containers/flat_map.h"
  #include "base/optional.h"
  #include "chrome/services/cups_proxy/public/cpp/type_conversions.h"
  #include "chrome/services/ipp_parser/public/cpp/ipp_converter.h"
  #include "net/http/http_util.h"
  
  namespace ipp_parser {
  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(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 = LocateEndOfRequestLine(request);
    if (idx < 0) {
      return -1;
    }
  
    // Advance to first header and check it exists
    idx += strlen(kCarriage);
    return idx < static_cast<int>(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 int end_of_headers_delimiter_size = 2 * strlen(kCarriage);
    return idx - end_of_headers_delimiter_size;
  }
  
  // Returns the starting index of the IPP metadata, -1 on failure.
  int LocateStartOfIppMetadata(base::span<const uint8_t> request) {
    std::vector<char> char_buffer = ipp_converter::ConvertToCharBuffer(request);
    return net::HttpUtil::LocateEndOfHeaders(char_buffer.data(),
                                             char_buffer.size());
  }
  
  bool SplitRequestMetadata(base::span<const uint8_t> request,
                            std::string* http_metadata,
                            base::span<const uint8_t>* ipp_metadata) {
    size_t start_of_ipp_metadata = LocateStartOfIppMetadata(request);
    if (start_of_ipp_metadata < 0) {
      return false;
    }
  
    *http_metadata =
        ipp_converter::ConvertToString(request.first(start_of_ipp_metadata));
    *ipp_metadata = request.subspan(start_of_ipp_metadata);
    return true;
  }
  
  base::Optional<std::vector<std::string>> ExtractHttpRequestLine(
      base::StringPiece request) {
    size_t 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>> ExtractHttpHeaders(
      base::StringPiece request) {
    size_t start_of_headers = LocateStartOfHeaders(request);
    if (start_of_headers < 0) {
      return base::nullopt;
    }
  
    size_t 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);
  }
  
  // Parses |ipp_metadata| and sets |ipp_message| and |ipp_data| accordingly.
  // Returns false and leaves the outputs unchanged on failure.
  bool ExtractIppMetadata(base::span<const uint8_t> ipp_metadata,
                          mojom::IppMessagePtr* ipp_message,
                          std::vector<uint8_t>* ipp_data) {
    printing::ScopedIppPtr ipp = ipp_converter::ParseIppMessage(ipp_metadata);
    if (!ipp) {
      return false;
    }
  
    mojom::IppMessagePtr message = ipp_converter::ConvertIppToMojo(ipp.get());
    if (!message) {
      return false;
    }
  
    size_t ipp_message_length = ippLength(ipp.get());
    ipp_metadata = ipp_metadata.subspan(ipp_message_length);
    *ipp_data = std::vector<uint8_t>(ipp_metadata.begin(), ipp_metadata.end());
  
    *ipp_message = std::move(message);
    return true;
  }
  
  }  // namespace
  
  IppParser::IppParser(mojo::PendingReceiver<mojom::IppParser> receiver)
      : receiver_(this, std::move(receiver)) {}
  
  IppParser::~IppParser() = default;
  
  void IppParser::ParseIpp(const std::vector<uint8_t>& to_parse,
                           ParseIppCallback callback) {
    // Separate |to_parse| into http metadata (interpreted as ASCII chars), and
    // ipp metadata (interpreted as arbitrary bytes).
    std::string http_metadata;
    base::span<const uint8_t> ipp_metadata;
    if (!SplitRequestMetadata(to_parse, &http_metadata, &ipp_metadata)) {
      return Fail("Failed to split HTTP and IPP metadata", std::move(callback));
    }
  
    // Parse Request line.
    auto request_line = ExtractHttpRequestLine(http_metadata);
    if (!request_line) {
      return Fail("Failed to parse request line", std::move(callback));
    }
  
    // Parse Headers.
    auto headers = ExtractHttpHeaders(http_metadata);
    if (!headers) {
      return Fail("Failed to parse headers", std::move(callback));
    }
  
    // Parse IPP message and IPP data.
    mojom::IppMessagePtr ipp_message;
    std::vector<uint8_t> ipp_data;
    if (!ExtractIppMetadata(ipp_metadata, &ipp_message, &ipp_data)) {
      return Fail("Failed to parse IPP metadata", std::move(callback));
    }
  
    // Marshall response.
    mojom::IppRequestPtr parsed_request = mojom::IppRequest::New();
  
    std::vector<std::string> request_line_terms = *request_line;
    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 =
        base::flat_map<std::string, std::string>(std::move(*headers));
    parsed_request->ipp = std::move(ipp_message);
    parsed_request->data = std::move(ipp_data);
  
    DVLOG(1) << "Finished parsing IPP request.";
    std::move(callback).Run(std::move(parsed_request));
  }
  
  }  // namespace ipp_parser
  