// Copyright 2018 The Chromium Authors
// 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/public/cpp/ipp_converter.h"
#include <algorithm>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/containers/contains.h"
#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
#include "base/values.h"
#include "net/http/http_util.h"
namespace ipp_converter {
namespace {
using IppAttributeValue = ipp_parser::mojom::IppAttributeValue;
const char kStatusDelimiter[] = " ";
const char kHeaderDelimiter[] = ": ";
const size_t kIppDateSize = 11;
// Callback used with ippReadIO (libCUPS API),
// Repeatedly used to copy IPP request buffer -> ipp_t.
ssize_t IppRead(base::span<const uint8_t>* src,
ipp_uchar_t* dst,
size_t bytes) {
// Note: Cast here is safe since ipp_uchar_t == uint8_t and this will build
// error if that ever changes.
base::span<const ipp_uchar_t> safe_src(*src);
size_t num_to_write = std::min(safe_src.size(), bytes);
std::copy(safe_src.begin(), safe_src.begin() + num_to_write, dst);
// Note: Modifying src here, not safe_src
*src = src->subspan(num_to_write);
return num_to_write;
}
// Callback used with ippWriteIO (libCUPS API),
// Repeatedly used to copy IPP ipp_t -> request buffer.
ssize_t IppWrite(base::span<uint8_t>* dst, ipp_uchar_t* source, size_t bytes) {
// Note: Cast here is safe since ipp_uchar_t == uint8_t and this will build
// error if that ever changes.
uint8_t* src = static_cast<uint8_t*>(source);
size_t num_to_write = std::min(dst->size(), bytes);
std::copy(src, src + num_to_write, dst->begin());
*dst = dst->subspan(num_to_write);
return num_to_write;
}
// Returns a parsed HttpHeader on success, empty Optional on failure.
std::optional<HttpHeader> ParseHeader(std::string_view header) {
if (base::Contains(header, kCarriage)) {
return std::nullopt;
}
// Parse key
const size_t key_end_index = header.find(":");
if (key_end_index == std::string::npos || key_end_index == 0) {
return std::nullopt;
}
const std::string_view key = header.substr(0, key_end_index);
// Parse value
const size_t value_begin_index = key_end_index + 1;
if (value_begin_index == header.size()) {
// Empty header value is valid
return HttpHeader{std::string(key), ""};
}
std::string_view value = header.substr(value_begin_index);
value = net::HttpUtil::TrimLWS(value);
return HttpHeader{std::string(key), std::string(value)};
}
// Converts |value_tag| to corresponding mojom type for marshalling.
std::optional<IppAttributeValue::Tag> ValueTagToType(const int value_tag) {
switch (value_tag) {
case IPP_TAG_BOOLEAN:
return IppAttributeValue::Tag::kBools;
case IPP_TAG_DATE:
return IppAttributeValue::Tag::kDate;
case IPP_TAG_INTEGER:
case IPP_TAG_ENUM:
return IppAttributeValue::Tag::kInts;
// Below string cases take from libCUPS ippAttributeString API
case IPP_TAG_TEXT:
case IPP_TAG_NAME:
case IPP_TAG_KEYWORD:
case IPP_TAG_CHARSET:
case IPP_TAG_URI:
case IPP_TAG_URISCHEME:
case IPP_TAG_MIMETYPE:
case IPP_TAG_LANGUAGE:
case IPP_TAG_TEXTLANG:
case IPP_TAG_NAMELANG:
return IppAttributeValue::Tag::kStrings;
// Octet (binary) string
case IPP_TAG_STRING:
return IppAttributeValue::Tag::kOctets;
case IPP_TAG_RESOLUTION:
return IppAttributeValue::Tag::kResolutions;
default:
break;
}
// Fail to convert any unrecognized types.
DVLOG(1) << "Failed to convert CUPS value tag, type " << value_tag;
return std::nullopt;
}
std::vector<bool> IppGetBools(ipp_attribute_t* attr) {
const size_t count = ippGetCount(attr);
std::vector<bool> ret;
ret.reserve(count);
for (size_t i = 0; i < count; ++i) {
// No decipherable failure condition for this libCUPS method.
ret.push_back(ippGetBoolean(attr, i));
}
return ret;
}
std::optional<std::vector<int>> IppGetInts(ipp_attribute_t* attr) {
const size_t count = ippGetCount(attr);
std::vector<int> ret;
ret.reserve(count);
for (size_t i = 0; i < count; ++i) {
int v = ippGetInteger(attr, i);
if (!v) {
return std::nullopt;
}
ret.push_back(v);
}
return ret;
}
std::optional<std::vector<std::string>> IppGetStrings(ipp_attribute_t* attr) {
const size_t count = ippGetCount(attr);
std::vector<std::string> ret;
ret.reserve(count);
for (size_t i = 0; i < count; ++i) {
const char* v = ippGetString(
attr, i, nullptr /* TODO(crbug.com/945409): figure out language */);
if (!v) {
return std::nullopt;
}
ret.emplace_back(v);
}
return ret;
}
std::optional<std::vector<std::vector<uint8_t>>> IppGetOctets(
ipp_attribute_t* attr) {
const size_t count = ippGetCount(attr);
std::vector<std::vector<uint8_t>> ret;
ret.reserve(count);
for (size_t i = 0; i < count; ++i) {
int len = 0;
const uint8_t* v =
static_cast<const uint8_t*>(ippGetOctetString(attr, i, &len));
if (!v || len <= 0) {
return std::nullopt;
}
ret.emplace_back(v, v + len);
}
return ret;
}
std::optional<std::vector<ipp_parser::mojom::ResolutionPtr>> IppGetResolutions(
ipp_attribute_t* attr) {
const size_t count = ippGetCount(attr);
std::vector<ipp_parser::mojom::ResolutionPtr> ret;
ret.reserve(count);
for (size_t i = 0; i < count; ++i) {
int xres = 0;
int yres = 0;
ipp_res_t units{};
xres = ippGetResolution(attr, i, &yres, &units);
if (xres <= 0 || yres <= 0 || units != IPP_RES_PER_INCH) {
LOG(ERROR) << "bad resolution: " << xres << ", " << yres << ", "
<< int(units);
return std::nullopt;
}
ret.push_back(ipp_parser::mojom::Resolution(xres, yres).Clone());
}
return ret;
}
} // namespace
std::optional<std::vector<std::string>> ParseRequestLine(
std::string_view status_line) {
// Split |status_slice| into triple method-endpoint-httpversion
std::vector<std::string> terms =
base::SplitString(status_line, kStatusDelimiter, base::KEEP_WHITESPACE,
base::SPLIT_WANT_ALL);
if (terms.size() != 3) {
return std::nullopt;
}
return terms;
}
// Implicit conversion is safe since the conversion preserves memory layout.
std::optional<std::vector<uint8_t>> BuildRequestLine(
std::string_view method,
std::string_view endpoint,
std::string_view http_version) {
std::string status_line =
base::StrCat({method, kStatusDelimiter, endpoint, kStatusDelimiter,
http_version, kCarriage});
return std::vector<uint8_t>(status_line.begin(), status_line.end());
}
std::optional<std::vector<HttpHeader>> ParseHeaders(
std::string_view headers_slice) {
auto raw_headers = base::SplitStringPieceUsingSubstr(
headers_slice, kCarriage, base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
std::vector<HttpHeader> ret;
ret.reserve(raw_headers.size());
for (auto raw_header : raw_headers) {
auto header = ParseHeader(raw_header);
if (!header) {
return std::nullopt;
}
ret.push_back(header.value());
}
return ret;
}
std::optional<std::vector<uint8_t>> BuildHeaders(
std::vector<HttpHeader> terms) {
std::string headers;
for (auto term : terms) {
base::StrAppend(&headers,
{term.first, kHeaderDelimiter, term.second, kCarriage});
}
// End-of-headers sentinel is a double carriage return; add the second one.
headers += kCarriage;
return std::vector<uint8_t>(headers.begin(), headers.end());
}
// Synchronously reads/parses |ipp_slice| and returns the resulting ipp_t
// wrapper.
printing::ScopedIppPtr ParseIppMessage(base::span<const uint8_t> ipp_slice) {
printing::ScopedIppPtr ipp = printing::WrapIpp(ippNew());
// Casting IppRead callback to correct internal CUPS type
// Note: This is safe since we essentially only cast the first argument from
// base::span<const uint8_t> to void* and back, only accessing it from the
// former.
auto ret = ippReadIO(&ipp_slice, reinterpret_cast<ipp_iocb_t>(IppRead), 1,
nullptr, ipp.get());
if (ret == IPP_STATE_ERROR) {
// Read failed, clear and return nullptr
ipp.reset();
}
return ipp;
}
std::optional<std::vector<uint8_t>> BuildIppMessage(ipp_t* ipp) {
std::vector<uint8_t> request(ippLength(ipp));
// Need to start in idle state for reading/writing.
if (!ippSetState(ipp, IPP_STATE_IDLE)) {
return std::nullopt;
}
// Casting IppWrite callback to correct internal CUPS type
// Note: This is safe since we essentially only cast the first argument from
// base::span<uint8_t> to void* and back, only accessing it from the former.
base::span<uint8_t> request_view(request);
auto ret = ippWriteIO(&request_view, reinterpret_cast<ipp_iocb_t>(IppWrite),
1, nullptr, ipp);
if (ret == IPP_STATE_ERROR) {
// Write failed
return std::nullopt;
}
return request;
}
std::optional<std::vector<uint8_t>> BuildIppRequest(
std::string_view method,
std::string_view endpoint,
std::string_view http_version,
std::vector<HttpHeader> terms,
ipp_t* ipp,
std::vector<uint8_t> ipp_data) {
// Build each subpart
auto request_line_buffer = BuildRequestLine(method, endpoint, http_version);
if (!request_line_buffer) {
return std::nullopt;
}
auto headers_buffer = BuildHeaders(std::move(terms));
if (!headers_buffer) {
return std::nullopt;
}
auto ipp_message_buffer = BuildIppMessage(ipp);
if (!ipp_message_buffer) {
return std::nullopt;
}
// Marshall request
std::vector<uint8_t> ret;
const size_t request_size = request_line_buffer->size() +
headers_buffer->size() +
ipp_message_buffer->size() + ipp_data.size();
ret.reserve(request_size);
ret.insert(ret.end(), request_line_buffer->begin(),
request_line_buffer->end());
ret.insert(ret.end(), headers_buffer->begin(), headers_buffer->end());
ret.insert(ret.end(), ipp_message_buffer->begin(), ipp_message_buffer->end());
ret.insert(ret.end(), ipp_data.begin(), ipp_data.end());
return ret;
}
// If no |ipp_data| is passed in, default to empty data portion.
std::optional<std::vector<uint8_t>> BuildIppRequest(
std::string_view method,
std::string_view endpoint,
std::string_view http_version,
std::vector<HttpHeader> terms,
ipp_t* ipp) {
return BuildIppRequest(method, endpoint, http_version, std::move(terms), ipp,
std::vector<uint8_t>());
}
// Parses and converts |ipp| to corresponding mojom type for marshalling.
// Returns nullptr on failure.
ipp_parser::mojom::IppMessagePtr ConvertIppToMojo(ipp_t* ipp) {
ipp_parser::mojom::IppMessagePtr ret = ipp_parser::mojom::IppMessage::New();
// Parse version numbers
int major, minor;
major = ippGetVersion(ipp, &minor);
ret->major_version = major;
ret->minor_version = minor;
// IPP request opcode ids are specified by the RFC, so casting to ints is
// safe.
ret->operation_id = static_cast<int>(ippGetOperation(ipp));
if (!ret->operation_id) {
return nullptr;
}
// Parse request id
ret->request_id = ippGetRequestId(ipp);
if (!ret->request_id) {
return nullptr;
}
std::vector<ipp_parser::mojom::IppAttributePtr> attributes;
for (ipp_attribute_t* attr = ippFirstAttribute(ipp); attr != nullptr;
attr = ippNextAttribute(ipp)) {
ipp_parser::mojom::IppAttributePtr attrptr =
ipp_parser::mojom::IppAttribute::New();
auto* name = ippGetName(attr);
if (!name) {
return nullptr;
}
attrptr->name = name;
attrptr->group_tag = ippGetGroupTag(attr);
if (attrptr->group_tag == IPP_TAG_ZERO) {
return nullptr;
}
attrptr->value_tag = ippGetValueTag(attr);
if (attrptr->value_tag == IPP_TAG_ZERO) {
return nullptr;
}
auto type = ValueTagToType(attrptr->value_tag);
if (!type) {
return nullptr;
}
switch (*type) {
case IppAttributeValue::Tag::kBools: {
attrptr->value =
ipp_parser::mojom::IppAttributeValue::NewBools(IppGetBools(attr));
break;
}
case IppAttributeValue::Tag::kDate: {
// Note: We expect date-attributes to be single-valued.
const uint8_t* v =
reinterpret_cast<const uint8_t*>(ippGetDate(attr, 0));
if (!v) {
return nullptr;
}
attrptr->value = ipp_parser::mojom::IppAttributeValue::NewDate(
std::vector<uint8_t>(v, v + kIppDateSize));
break;
}
case IppAttributeValue::Tag::kInts: {
auto vals = IppGetInts(attr);
if (!vals.has_value()) {
return nullptr;
}
attrptr->value = ipp_parser::mojom::IppAttributeValue::NewInts(*vals);
break;
}
case IppAttributeValue::Tag::kStrings: {
auto vals = IppGetStrings(attr);
if (!vals.has_value()) {
return nullptr;
}
attrptr->value =
ipp_parser::mojom::IppAttributeValue::NewStrings(*vals);
break;
}
case IppAttributeValue::Tag::kOctets: {
auto vals = IppGetOctets(attr);
if (!vals.has_value()) {
return nullptr;
}
attrptr->value = ipp_parser::mojom::IppAttributeValue::NewOctets(*vals);
break;
}
case IppAttributeValue::Tag::kResolutions: {
auto vals = IppGetResolutions(attr);
if (!vals.has_value()) {
return nullptr;
}
attrptr->value = ipp_parser::mojom::IppAttributeValue::NewResolutions(
std::move(*vals));
break;
}
}
if (!attrptr->value)
NOTREACHED_IN_MIGRATION();
attributes.push_back(std::move(attrptr));
}
ret->attributes = std::move(attributes);
return ret;
}
} // namespace ipp_converter