chromium/chromeos/printing/ppd_line_reader.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chromeos/printing/ppd_line_reader.h"

#include <memory>
#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "base/memory/raw_ref.h"
#include "base/strings/string_util.h"
#include "net/base/completion_once_callback.h"
#include "net/base/io_buffer.h"
#include "net/filter/gzip_header.h"
#include "net/filter/gzip_source_stream.h"
#include "net/filter/source_stream.h"

namespace chromeos {
namespace {

constexpr char kPPDMagicNumberString[] = "*PPD-Adobe:";

// Return true if contents has a valid Gzip header.
bool IsGZipped(const std::string& contents) {
  const char* unused;
  return net::GZipHeader().ReadMore(contents.data(), contents.size(),
                                    &unused) ==
         net::GZipHeader::COMPLETE_HEADER;
}

// Return true if c is a newline in the ppd sense, that is, either newline or
// carriage return.
bool IsNewline(char c) {
  return c == '\n' || c == '\r';
}

// Source stream that reads from a string.  A reference is taken to the string
// used by StringSourceStream; it must not be modified while the
// StringSourceStream exists.
class StringSourceStream : public net::SourceStream {
 public:
  explicit StringSourceStream(const std::string& src)
      : SourceStream(TYPE_UNKNOWN), src_(src) {}

  // This source always reads sychronously, so never uses the callback.
  int Read(net::IOBuffer* dest_buffer,
           int buffer_size,
           net::CompletionOnceCallback) override {
    if (buffer_size < 0)
      return net::ERR_INVALID_ARGUMENT;
    if (!MayHaveMoreBytes())
      return net::OK;
    const size_t read_size =
        std::min(src_->size() - read_ofs_, static_cast<size_t>(buffer_size));
    memcpy(dest_buffer->data(), src_->data() + read_ofs_, read_size);
    read_ofs_ += read_size;
    return read_size;
  }
  std::string Description() const override { return ""; }
  bool MayHaveMoreBytes() const override { return read_ofs_ < src_->size(); }

 private:
  size_t read_ofs_ = 0;
  const raw_ref<const std::string> src_;
};

class PpdLineReaderImpl : public PpdLineReader {
 public:
  PpdLineReaderImpl(const std::string& ppd_contents, size_t max_line_length)
      : max_line_length_(max_line_length),
        read_buf_(
            base::MakeRefCounted<net::IOBufferWithSize>(kReadBufCapacity)) {
    input_ = std::make_unique<StringSourceStream>(ppd_contents);
    if (IsGZipped(ppd_contents)) {
      input_ = net::GzipSourceStream::Create(std::move(input_),
                                             net::SourceStream::TYPE_GZIP);
    }
  }
  ~PpdLineReaderImpl() override = default;

  bool NextLine(std::string* line) override {
    line->reserve(max_line_length_);

    // Outer loop controls retries; if we fail to read a line, we'll try again
    // after the next newline.
    while (true) {
      line->clear();
      while (line->size() <= max_line_length_) {
        char c = NextChar();
        if (Eof()) {
          return !line->empty();
        } else if (IsNewline(c)) {
          return true;
        }
        line->push_back(c);
      }

      // Exceeded max line length, skip the rest of this line, try for another
      // one.
      if (!SkipToNextLine()) {
        return false;
      }
    }
  }

  std::string RemainingContent() override {
    std::string content(read_buf_->data() + read_ofs_,
                        read_buf_->data() + read_buf_size_);
    for (ReadNextChunk(); read_buf_size_ > 0; ReadNextChunk()) {
      content.append(read_buf_->data(), read_buf_size_);
    }
    return content;
  }

  bool Error() const override { return error_; }

 private:
  // Chunk size of reads to the underlying source stream.
  static constexpr int kReadBufCapacity = 1024;

  // Skip input until we hit a newline (which is discarded).  If
  // we encounter eof before a newline, false is returned.
  bool SkipToNextLine() {
    while (true) {
      char c = NextChar();
      if (Eof()) {
        return false;
      }
      if (IsNewline(c)) {
        return true;
      }
    }
  }

  void ReadNextChunk() {
    // Just ignore if we already reach EOF.
    if (eof_) {
      return;
    }
    read_ofs_ = 0;

    // Since StringSourceStream never uses the callback, and filter streams
    // are only supposed to use the callback if the underlying source stream
    // uses it, we should never see the callback used.
    int result = input_->Read(
        read_buf_.get(), kReadBufCapacity,
        base::BindOnce([](int) { LOG(FATAL) << "Unexpected async read"; }));
    if (result <= 0) {
      eof_ = true;
      error_ = (result < 0);
      read_buf_size_ = 0;
    } else {
      read_buf_size_ = result;
    }
  }

  // Consume and return the next char from the source stream.  If there is no
  // more data to be had, set eof.  Eof() should be checked before the returned
  // value is used.
  char NextChar() {
    if (read_ofs_ == read_buf_size_) {
      // Grab more data from the underlying stream.
      ReadNextChunk();
      if (read_buf_size_ == 0) {
        return '\0';
      }
    }
    return read_buf_->data()[read_ofs_++];
  }

  bool Eof() const { return eof_; }

  // Maximum allowable line length from the source.  Any lines longer than this
  // will be silently discarded.
  size_t max_line_length_;

  // Buffer for reading from the source stream.
  scoped_refptr<net::IOBuffer> read_buf_;
  // Number of bytes actually in the buffer.
  int read_buf_size_ = 0;
  // Offset into read_buf for the next char.
  int read_ofs_ = 0;

  // Have we hit the end of the source stream?
  bool eof_ = false;

  // Did we encounter an error while reading?
  bool error_ = false;

  // The input stream we're reading bytes from.  This may be a gzip source
  // stream or string source stream depending on the source data.
  std::unique_ptr<net::SourceStream> input_;
};

constexpr int PpdLineReaderImpl::kReadBufCapacity;

}  // namespace

// static
std::unique_ptr<PpdLineReader> PpdLineReader::Create(
    const std::string& contents,
    size_t max_line_length) {
  return std::make_unique<PpdLineReaderImpl>(contents, max_line_length);
}

// static
bool PpdLineReader::ContainsMagicNumber(const std::string& contents,
                                        size_t max_line_length) {
  auto line_reader = PpdLineReader::Create(contents, max_line_length);
  std::string line;
  return line_reader->NextLine(&line) &&
         base::StartsWith(line, kPPDMagicNumberString,
                          base::CompareCase::SENSITIVE);
}

}  // namespace chromeos