blob: 337237c548aae7eb86fbc09fbb69262aef008c80 [file] [log] [blame]
// 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/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 = 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