Use a FileReaderInterface for file attachments instead of a FilePath

This is a step towards a database which gives out FileReaders in Report
objects instead of FilePaths.

Change-Id: I59704da65fc5521e5d47019416bf962c215d13bc
Reviewed-on: https://chromium-review.googlesource.com/721978
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Joshua Peraza 2017-10-17 08:50:58 -07:00 committed by Commit Bot
parent 4d7a07f684
commit 68a0e736c6
8 changed files with 114 additions and 124 deletions

View File

@ -70,7 +70,7 @@ void InsertOrReplaceMapEntry(std::map<std::string, std::string>* map,
//
// In the event of an error reading the minidump file, a message will be logged.
std::map<std::string, std::string> BreakpadHTTPFormParametersFromMinidump(
FileReader* minidump_file_reader) {
FileReaderInterface* minidump_file_reader) {
ProcessSnapshotMinidump minidump_process_snapshot;
if (!minidump_process_snapshot.Initialize(minidump_file_reader)) {
return std::map<std::string, std::string>();
@ -344,18 +344,25 @@ CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport(
std::string* response_body) {
std::map<std::string, std::string> parameters;
{
FileReader minidump_file_reader;
if (!minidump_file_reader.Open(report->file_path)) {
// If the minidump file cant be opened, all hope is lost.
return UploadResult::kPermanentFailure;
}
FileReader minidump_file_reader;
if (!minidump_file_reader.Open(report->file_path)) {
// If the minidump file cant be opened, all hope is lost.
return UploadResult::kPermanentFailure;
}
// If the minidump file could be opened, ignore any errors that might occur
// when attempting to interpret it. This may result in its being uploaded
// with few or no parameters, but as long as theres a dump file, the server
// can decide what to do with it.
parameters = BreakpadHTTPFormParametersFromMinidump(&minidump_file_reader);
FileOffset start_offset = minidump_file_reader.SeekGet();
if (start_offset < 0) {
return UploadResult::kPermanentFailure;
}
// Ignore any errors that might occur when attempting to interpret the
// minidump file. This may result in its being uploaded with few or no
// parameters, but as long as theres a dump file, the server can decide what
// to do with it.
parameters = BreakpadHTTPFormParametersFromMinidump(&minidump_file_reader);
if (!minidump_file_reader.SeekSet(start_offset)) {
return UploadResult::kPermanentFailure;
}
HTTPMultipartBuilder http_multipart_builder;
@ -379,7 +386,7 @@ CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport(
#else
report->file_path.BaseName().value(),
#endif
report->file_path,
&minidump_file_reader,
"application/octet-stream");
std::unique_ptr<HTTPTransport> http_transport(HTTPTransport::Create());

View File

@ -19,9 +19,11 @@
#include <memory>
#include <string>
#include <vector>
#include "base/files/file_path.h"
#include "tools/tool_support.h"
#include "util/file/file_reader.h"
#include "util/file/file_writer.h"
#include "util/net/http_body.h"
#include "util/net/http_multipart_builder.h"
@ -85,6 +87,7 @@ int HTTPUploadMain(int argc, char* argv[]) {
{nullptr, 0, nullptr, 0},
};
std::vector<std::unique_ptr<FileReader>> readers;
HTTPMultipartBuilder http_multipart_builder;
int opt;
@ -102,8 +105,14 @@ int HTTPUploadMain(int argc, char* argv[]) {
ToolSupport::CommandLineArgumentToFilePathStringType(path));
std::string file_name(
ToolSupport::FilePathToCommandLineArgument(file_path.BaseName()));
readers.push_back(std::make_unique<FileReader>());
FileReader* upload_file_reader = readers.back().get();
if (!upload_file_reader->Open(file_path)) {
return EXIT_FAILURE;
}
http_multipart_builder.SetFileAttachment(
key, file_name, file_path, "application/octet-stream");
key, file_name, upload_file_reader, "application/octet-stream");
break;
}
case kOptionNoUploadGzip: {

View File

@ -46,38 +46,22 @@ FileOperationResult StringHTTPBodyStream::GetBytesBuffer(uint8_t* buffer,
return num_bytes_returned;
}
FileHTTPBodyStream::FileHTTPBodyStream(const base::FilePath& path)
: HTTPBodyStream(), path_(path), file_(), file_state_(kUnopenedFile) {
FileReaderHTTPBodyStream::FileReaderHTTPBodyStream(FileReaderInterface* reader)
: HTTPBodyStream(), reader_(reader), reached_eof_(false) {
DCHECK(reader_);
}
FileHTTPBodyStream::~FileHTTPBodyStream() {
}
FileReaderHTTPBodyStream::~FileReaderHTTPBodyStream() {}
FileOperationResult FileHTTPBodyStream::GetBytesBuffer(uint8_t* buffer,
size_t max_len) {
switch (file_state_) {
case kUnopenedFile:
file_.reset(LoggingOpenFileForRead(path_));
if (!file_.is_valid()) {
file_state_ = kFileOpenError;
return -1;
}
file_state_ = kReading;
break;
case kFileOpenError:
return -1;
case kClosedAtEOF:
return 0;
case kReading:
break;
FileOperationResult FileReaderHTTPBodyStream::GetBytesBuffer(uint8_t* buffer,
size_t max_len) {
if (reached_eof_) {
return 0;
}
FileOperationResult rv = ReadFile(file_.get(), buffer, max_len);
FileOperationResult rv = reader_->Read(buffer, max_len);
if (rv == 0) {
file_.reset();
file_state_ = kClosedAtEOF;
} else if (rv < 0) {
PLOG(ERROR) << "read";
reached_eof_ = true;
}
return rv;
}

View File

@ -24,6 +24,7 @@
#include "base/files/file_path.h"
#include "base/macros.h"
#include "util/file/file_io.h"
#include "util/file/file_reader.h"
namespace crashpad {
@ -70,33 +71,26 @@ class StringHTTPBodyStream : public HTTPBodyStream {
DISALLOW_COPY_AND_ASSIGN(StringHTTPBodyStream);
};
//! \brief An implementation of HTTPBodyStream that reads from the specified
//! file and provides its contents for an HTTP body.
class FileHTTPBodyStream : public HTTPBodyStream {
//! \brief An implementation of HTTPBodyStream that reads from a
//! FileReaderInterface and provides its contents for an HTTP body.
class FileReaderHTTPBodyStream : public HTTPBodyStream {
public:
//! \brief Creates a stream for reading the file at the specified \a path.
//! \brief Creates a stream for reading from a FileReaderInterface.
//!
//! \param[in] path The file from which this HTTPBodyStream will read.
explicit FileHTTPBodyStream(const base::FilePath& path);
//! \param[in] reader A FileReaderInterface from which this HTTPBodyStream
//! will read.
explicit FileReaderHTTPBodyStream(FileReaderInterface* reader);
~FileHTTPBodyStream() override;
~FileReaderHTTPBodyStream() override;
// HTTPBodyStream:
FileOperationResult GetBytesBuffer(uint8_t* buffer, size_t max_len) override;
private:
enum FileState {
kUnopenedFile,
kFileOpenError,
kClosedAtEOF,
kReading,
};
FileReaderInterface* reader_; // weak
bool reached_eof_;
base::FilePath path_;
ScopedFileHandle file_;
FileState file_state_;
DISALLOW_COPY_AND_ASSIGN(FileHTTPBodyStream);
DISALLOW_COPY_AND_ASSIGN(FileReaderHTTPBodyStream);
};
//! \brief An implementation of HTTPBodyStream that combines an array of

View File

@ -96,10 +96,13 @@ TEST(StringHTTPBodyStream, MultipleReads) {
}
}
TEST(FileHTTPBodyStream, ReadASCIIFile) {
TEST(FileReaderHTTPBodyStream, ReadASCIIFile) {
base::FilePath path = TestPaths::TestDataRoot().Append(
FILE_PATH_LITERAL("util/net/testdata/ascii_http_body.txt"));
FileHTTPBodyStream stream(path);
FileReader reader;
ASSERT_TRUE(reader.Open(path));
FileReaderHTTPBodyStream stream(&reader);
std::string contents = ReadStreamToString(&stream, 32);
EXPECT_EQ(contents, "This is a test.\n");
@ -113,14 +116,16 @@ TEST(FileHTTPBodyStream, ReadASCIIFile) {
ExpectBufferSet(buf, '!', sizeof(buf));
}
TEST(FileHTTPBodyStream, ReadBinaryFile) {
TEST(FileReaderHTTPBodyStream, ReadBinaryFile) {
// HEX contents of file: |FEEDFACE A11A15|.
base::FilePath path = TestPaths::TestDataRoot().Append(
FILE_PATH_LITERAL("util/net/testdata/binary_http_body.dat"));
// This buffer size was chosen so that reading the file takes multiple reads.
uint8_t buf[4];
FileHTTPBodyStream stream(path);
FileReader reader;
ASSERT_TRUE(reader.Open(path));
FileReaderHTTPBodyStream stream(&reader);
memset(buf, '!', sizeof(buf));
EXPECT_EQ(stream.GetBytesBuffer(buf, sizeof(buf)), 4);
@ -143,18 +148,6 @@ TEST(FileHTTPBodyStream, ReadBinaryFile) {
ExpectBufferSet(buf, '!', sizeof(buf));
}
TEST(FileHTTPBodyStream, NonExistentFile) {
base::FilePath path = base::FilePath(
FILE_PATH_LITERAL("/var/empty/crashpad/util/net/http_body/null"));
FileHTTPBodyStream stream(path);
uint8_t buf = 0xff;
EXPECT_LT(stream.GetBytesBuffer(&buf, 1), 0);
EXPECT_EQ(buf, 0xff);
EXPECT_LT(stream.GetBytesBuffer(&buf, 1), 0);
EXPECT_EQ(buf, 0xff);
}
TEST(CompositeHTTPBodyStream, TwoEmptyStrings) {
std::vector<HTTPBodyStream*> parts;
parts.push_back(new StringHTTPBodyStream(std::string()));
@ -201,7 +194,10 @@ TEST_P(CompositeHTTPBodyStreamBufferSize, StringsAndFile) {
parts.push_back(new StringHTTPBodyStream(string1));
base::FilePath path = TestPaths::TestDataRoot().Append(
FILE_PATH_LITERAL("util/net/testdata/ascii_http_body.txt"));
parts.push_back(new FileHTTPBodyStream(path));
FileReader reader;
ASSERT_TRUE(reader.Open(path));
parts.push_back(new FileReaderHTTPBodyStream(&reader));
parts.push_back(new StringHTTPBodyStream(string2));
CompositeHTTPBodyStream stream(parts);

View File

@ -133,13 +133,13 @@ void HTTPMultipartBuilder::SetFormData(const std::string& key,
void HTTPMultipartBuilder::SetFileAttachment(
const std::string& key,
const std::string& upload_file_name,
const base::FilePath& path,
FileReaderInterface* reader,
const std::string& content_type) {
EraseKey(upload_file_name);
FileAttachment attachment;
attachment.filename = EncodeMIMEField(upload_file_name);
attachment.path = path;
attachment.reader = reader;
if (content_type.empty()) {
attachment.content_type = "application/octet-stream";
@ -174,7 +174,7 @@ std::unique_ptr<HTTPBodyStream> HTTPMultipartBuilder::GetBodyStream() {
attachment.content_type.c_str(), kBoundaryCRLF);
streams.push_back(new StringHTTPBodyStream(header));
streams.push_back(new FileHTTPBodyStream(attachment.path));
streams.push_back(new FileReaderHTTPBodyStream(attachment.reader));
streams.push_back(new StringHTTPBodyStream(kCRLF));
}

View File

@ -19,8 +19,8 @@
#include <memory>
#include <string>
#include "base/files/file_path.h"
#include "base/macros.h"
#include "util/file/file_reader.h"
#include "util/net/http_headers.h"
namespace crashpad {
@ -51,7 +51,7 @@ class HTTPMultipartBuilder {
//! \param[in] value The value to set at the \a key.
void SetFormData(const std::string& key, const std::string& value);
//! \brief Specifies the file at \a path to have its contents uploaded as
//! \brief Specifies the contents read from \a reader to be uploaded as
//! multipart data, available at `name` of \a upload_file_name.
//!
//! \param[in] key The key of the form data, specified as the `name` in the
@ -59,12 +59,13 @@ class HTTPMultipartBuilder {
//! key will be overwritten.
//! \param[in] upload_file_name The `filename` to specify for this multipart
//! data attachment.
//! \param[in] path The path of the file whose contents will be uploaded.
//! \param[in] reader A FileReaderInterface from which to read the content to
//! upload.
//! \param[in] content_type The `Content-Type` to specify for the attachment.
//! If this is empty, `"application/octet-stream"` will be used.
void SetFileAttachment(const std::string& key,
const std::string& upload_file_name,
const base::FilePath& path,
FileReaderInterface* reader,
const std::string& content_type);
//! \brief Generates the HTTPBodyStream for the data currently supplied to
@ -83,7 +84,7 @@ class HTTPMultipartBuilder {
struct FileAttachment {
std::string filename;
std::string content_type;
base::FilePath path;
FileReaderInterface* reader;
};
// Removes elements from both data maps at the specified |key|, to ensure

View File

@ -102,18 +102,19 @@ TEST(HTTPMultipartBuilder, ThreeFileAttachments) {
HTTPMultipartBuilder builder;
base::FilePath ascii_http_body_path = TestPaths::TestDataRoot().Append(
FILE_PATH_LITERAL("util/net/testdata/ascii_http_body.txt"));
builder.SetFileAttachment("first",
"minidump.dmp",
ascii_http_body_path,
"");
builder.SetFileAttachment("second",
"minidump.dmp",
ascii_http_body_path,
"text/plain");
builder.SetFileAttachment("\"third 50% silly\"",
"test%foo.txt",
ascii_http_body_path,
"text/plain");
FileReader reader1;
ASSERT_TRUE(reader1.Open(ascii_http_body_path));
builder.SetFileAttachment("first", "minidump.dmp", &reader1, "");
FileReader reader2;
ASSERT_TRUE(reader2.Open(ascii_http_body_path));
builder.SetFileAttachment("second", "minidump.dmp", &reader2, "text/plain");
FileReader reader3;
ASSERT_TRUE(reader3.Open(ascii_http_body_path));
builder.SetFileAttachment(
"\"third 50% silly\"", "test%foo.txt", &reader3, "text/plain");
static constexpr char kFileContents[] = "This is a test.\n";
@ -187,21 +188,22 @@ TEST(HTTPMultipartBuilder, OverwriteFileAttachment) {
builder.SetFormData("a key", kValue);
base::FilePath testdata_path =
TestPaths::TestDataRoot().Append(FILE_PATH_LITERAL("util/net/testdata"));
builder.SetFileAttachment("minidump",
"minidump.dmp",
testdata_path.Append(FILE_PATH_LITERAL(
"binary_http_body.dat")),
"");
builder.SetFileAttachment("minidump2",
"minidump.dmp",
testdata_path.Append(FILE_PATH_LITERAL(
"binary_http_body.dat")),
"");
builder.SetFileAttachment("minidump",
"minidump.dmp",
testdata_path.Append(FILE_PATH_LITERAL(
"ascii_http_body.txt")),
"text/plain");
FileReader reader1;
ASSERT_TRUE(reader1.Open(
testdata_path.Append(FILE_PATH_LITERAL("binary_http_body.dat"))));
builder.SetFileAttachment("minidump", "minidump.dmp", &reader1, "");
FileReader reader2;
ASSERT_TRUE(reader2.Open(
testdata_path.Append(FILE_PATH_LITERAL("binary_http_body.dat"))));
builder.SetFileAttachment("minidump2", "minidump.dmp", &reader2, "");
FileReader reader3;
ASSERT_TRUE(reader3.Open(
testdata_path.Append(FILE_PATH_LITERAL("ascii_http_body.txt"))));
builder.SetFileAttachment("minidump", "minidump.dmp", &reader3, "text/plain");
std::unique_ptr<HTTPBodyStream> body(builder.GetBodyStream());
ASSERT_TRUE(body.get());
std::string contents = ReadStreamToString(body.get());
@ -244,10 +246,10 @@ TEST(HTTPMultipartBuilder, SharedFormDataAndAttachmentKeyNamespace) {
builder.SetFormData("one", kValue1);
base::FilePath ascii_http_body_path = TestPaths::TestDataRoot().Append(
FILE_PATH_LITERAL("util/net/testdata/ascii_http_body.txt"));
builder.SetFileAttachment("minidump",
"minidump.dmp",
ascii_http_body_path,
"");
FileReader reader;
ASSERT_TRUE(reader.Open(ascii_http_body_path));
builder.SetFileAttachment("minidump", "minidump.dmp", &reader, "");
static constexpr char kValue2[] = "this is not a file";
builder.SetFormData("minidump", kValue2);
@ -278,19 +280,16 @@ TEST(HTTPMultipartBuilder, SharedFormDataAndAttachmentKeyNamespace) {
TEST(HTTPMultipartBuilderDeathTest, AssertUnsafeMIMEType) {
HTTPMultipartBuilder builder;
FileReader reader;
// Invalid and potentially dangerous:
ASSERT_DEATH_CHECK(
builder.SetFileAttachment("", "", base::FilePath(), "\r\n"), "");
ASSERT_DEATH_CHECK(
builder.SetFileAttachment("", "", base::FilePath(), "\""), "");
ASSERT_DEATH_CHECK(
builder.SetFileAttachment("", "", base::FilePath(), "\x12"), "");
ASSERT_DEATH_CHECK(
builder.SetFileAttachment("", "", base::FilePath(), "<>"), "");
ASSERT_DEATH_CHECK(builder.SetFileAttachment("", "", &reader, "\r\n"), "");
ASSERT_DEATH_CHECK(builder.SetFileAttachment("", "", &reader, "\""), "");
ASSERT_DEATH_CHECK(builder.SetFileAttachment("", "", &reader, "\x12"), "");
ASSERT_DEATH_CHECK(builder.SetFileAttachment("", "", &reader, "<>"), "");
// Invalid but safe:
builder.SetFileAttachment("", "", base::FilePath(), "0/totally/-invalid.pdf");
builder.SetFileAttachment("", "", &reader, "0/totally/-invalid.pdf");
// Valid and safe:
builder.SetFileAttachment("", "", base::FilePath(), "application/xml+xhtml");
builder.SetFileAttachment("", "", &reader, "application/xml+xhtml");
}
} // namespace