blob: 95042b619cffd682588fec5d6dec5f519d15bc2f [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/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