Introduce the crashpad_http_upload tool

crashpad_http_upload sends HTTP POST multipart/form-data requests and
receives responses in exactly the same manner that crashpad_handler does
for crash report uploads, but separates it out for more general testing
and debugging.

Change-Id: I5c5919f9b1dc1e6be1e43b15a35b31f51add8a46
This commit is contained in:
Mark Mentovai 2017-03-03 15:11:50 -05:00
parent 777e36014f
commit 6a5695967f
9 changed files with 486 additions and 10 deletions

View File

@ -0,0 +1,206 @@
// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <memory>
#include <string>
#include "base/files/file_path.h"
#include "tools/tool_support.h"
#include "util/file/file_writer.h"
#include "util/net/http_body.h"
#include "util/net/http_multipart_builder.h"
#include "util/net/http_transport.h"
#include "util/string/split_string.h"
namespace crashpad {
namespace {
void Usage(const base::FilePath& me) {
fprintf(stderr,
"Usage: %" PRFilePath " [OPTION]...\n"
"Send an HTTP POST request.\n"
" -f, --file=KEY=PATH upload the file at PATH for the HTTP KEY parameter\n"
" --no-upload-gzip don't use gzip compression when uploading\n"
" -o, --output=FILE write the response body to FILE instead of stdout\n"
" -s, --string=KEY=VALUE set the HTTP KEY parameter to VALUE\n"
" -u, --url=URL send the request to URL\n"
" --help display this help and exit\n"
" --version output version information and exit\n",
me.value().c_str());
ToolSupport::UsageTail(me);
}
int HTTPUploadMain(int argc, char* argv[]) {
const base::FilePath argv0(
ToolSupport::CommandLineArgumentToFilePathStringType(argv[0]));
const base::FilePath me(argv0.BaseName());
enum OptionFlags {
// “Short” (single-character) options.
kOptionFile = 'f',
kOptionOutput = 'o',
kOptionString = 's',
kOptionURL = 'u',
// Long options without short equivalents.
kOptionLastChar = 255,
kOptionNoUploadGzip,
// Standard options.
kOptionHelp = -2,
kOptionVersion = -3,
};
struct {
std::string url;
const char* output;
bool upload_gzip;
} options = {};
options.upload_gzip = true;
const option long_options[] = {
{"file", required_argument, nullptr, kOptionFile},
{"no-upload-gzip", no_argument, nullptr, kOptionNoUploadGzip},
{"output", required_argument, nullptr, kOptionOutput},
{"string", required_argument, nullptr, kOptionString},
{"url", required_argument, nullptr, kOptionURL},
{"help", no_argument, nullptr, kOptionHelp},
{"version", no_argument, nullptr, kOptionVersion},
{nullptr, 0, nullptr, 0},
};
HTTPMultipartBuilder http_multipart_builder;
int opt;
while ((opt = getopt_long(argc, argv, "f:o:s:u:", long_options, nullptr)) !=
-1) {
switch (opt) {
case kOptionFile: {
std::string key;
std::string path;
if (!SplitStringFirst(optarg, '=', &key, &path)) {
ToolSupport::UsageHint(me, "--file requires KEY=STRING");
return EXIT_FAILURE;
}
base::FilePath file_path(
ToolSupport::CommandLineArgumentToFilePathStringType(path));
std::string file_name(
ToolSupport::FilePathToCommandLineArgument(file_path.BaseName()));
http_multipart_builder.SetFileAttachment(
key, file_name, file_path, "application/octet-stream");
break;
}
case kOptionNoUploadGzip: {
options.upload_gzip = false;
break;
}
case kOptionOutput: {
options.output = optarg;
break;
}
case kOptionString: {
std::string key;
std::string value;
if (!SplitStringFirst(optarg, '=', &key, &value)) {
ToolSupport::UsageHint(me, "--string requires KEY=VALUE");
return EXIT_FAILURE;
}
http_multipart_builder.SetFormData(key, value);
break;
}
case kOptionURL:
options.url = optarg;
break;
case kOptionHelp:
Usage(me);
return EXIT_SUCCESS;
case kOptionVersion:
ToolSupport::Version(me);
return EXIT_SUCCESS;
default:
ToolSupport::UsageHint(me, nullptr);
return EXIT_FAILURE;
}
}
argc -= optind;
argv += optind;
if (options.url.empty()) {
ToolSupport::UsageHint(me, "--url is required");
return EXIT_FAILURE;
}
if (argc) {
ToolSupport::UsageHint(me, nullptr);
return EXIT_FAILURE;
}
std::unique_ptr<FileWriterInterface> file_writer;
if (options.output) {
FileWriter* file_writer_impl = new FileWriter();
file_writer.reset(file_writer_impl);
base::FilePath output_path(
ToolSupport::CommandLineArgumentToFilePathStringType(options.output));
if (!file_writer_impl->Open(output_path,
FileWriteMode::kTruncateOrCreate,
FilePermissions::kWorldReadable)) {
return EXIT_FAILURE;
}
} else {
file_writer.reset(new WeakStdioFileWriter(stdout));
}
http_multipart_builder.SetGzipEnabled(options.upload_gzip);
std::unique_ptr<HTTPTransport> http_transport(HTTPTransport::Create());
http_transport->SetURL(options.url);
HTTPHeaders content_headers;
http_multipart_builder.PopulateContentHeaders(&content_headers);
for (const auto& content_header : content_headers) {
http_transport->SetHeader(content_header.first, content_header.second);
}
http_transport->SetBodyStream(http_multipart_builder.GetBodyStream());
std::string response_body;
if (!http_transport->ExecuteSynchronously(&response_body)) {
return EXIT_FAILURE;
}
if (!response_body.empty() &&
!file_writer->Write(&response_body[0], response_body.size())) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
} // namespace
} // namespace crashpad
#if defined(OS_POSIX)
int main(int argc, char* argv[]) {
return crashpad::HTTPUploadMain(argc, argv);
}
#elif defined(OS_WIN)
int wmain(int argc, wchar_t* argv[]) {
return crashpad::ToolSupport::Wmain(argc, argv, crashpad::HTTPUploadMain);
}
#endif // OS_POSIX

View File

@ -0,0 +1,132 @@
<!--
Copyright 2017 The Crashpad Authors. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
# crashpad_http_upload(1)
## Name
crashpad_http_upload—Send an HTTP POST request
## Synopsis
**crashpad_http_uplaod** [_OPTION…_]
## Description
Performs an HTTP or HTTPS POST, building a `multipart/form-data` request from
key-value pairs and files in the manner of an HTML `<form>` with a POST action.
Provides the response.
Programs that use the Crashpad client library directly will not normally use
this tool. This tool is provided for debugging and testing as it isolates
Crashpads networking implementation normally used to upload crash reports to
a crash report collection server, making it available for more general use.
## Options
* **-f**, **--file**=_KEY_=_PATH_
Include _PATH_ in the request as a file upload, in the manner of an HTML
`<input type="file">` element. _KEY_ is used as the field name.
* **--no-upload-gzip**
Do not use `gzip` compression. Normally, the entire request body is
compressed into a `gzip` stream and transmitted with `Content-Encoding:
gzip`. This option disables compression, and is intended for use with servers
that dont accept uploads compressed in this way.
* **-o**, **--output**=_FILE_
The response body will be written to _FILE_ instead of standard output.
* **-s**, **--string**=_KEY_=_VALUE_
Include _KEY_ and _VALUE_ in the request as an ordinary form field, in the
manner of an HTML `<input type="text">` element. _KEY_ is used as the field
name, and _VALUE_ is used as its value.
* **-u**, **--url**=_URL_
Send the request to _URL_. This option is required.
* **--help**
Display help and exit.
* **--version**
Output version information and exit.
## Examples
Uploads a file to an HTTP server running on `localhost`.
```
$ crashpad_http-upload --url http://localhost/upload_test \
--string=when=now --file=what=1040.pdf
Thanks for the upload!
```
This example corresponds to the HTML form:
```
<form action="http://localhost/upload_test" method="post">
<input type="text" name="when" value="now" />
<input type="file" name="what" />
<input type="submit" />
</form>
```
## Exit Status
* **0**
Success.
* **1**
Failure, with a message printed to the standard error stream. HTTP error
statuses such as 404 (Not Found) are included in the definition of failure.
## See Also
[crashpad_handler(8)](../handler/crashpad_handler.md)
## Resources
Crashpad home page: https://crashpad.chromium.org/.
Report bugs at https://crashpad.chromium.org/bug/new.
## Copyright
Copyright 2017 [The Crashpad
Authors](https://chromium.googlesource.com/crashpad/crashpad/+/master/AUTHORS).
## License
Licensed under the Apache License, Version 2.0 (the “License”);
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
@ -48,12 +47,6 @@
namespace crashpad {
namespace {
struct Options {
std::string dump_path;
pid_t pid;
bool suspend;
};
void Usage(const base::FilePath& me) {
fprintf(stderr,
"Usage: %" PRFilePath " [OPTION]... PID\n"
@ -85,7 +78,11 @@ int GenerateDumpMain(int argc, char* argv[]) {
kOptionVersion = -3,
};
Options options = {};
struct {
std::string dump_path;
pid_t pid;
bool suspend;
} options = {};
options.suspend = true;
const option long_options[] = {

View File

@ -98,4 +98,14 @@ base::FilePath::StringType ToolSupport::CommandLineArgumentToFilePathStringType(
#endif // OS_POSIX
}
// static
std::string ToolSupport::FilePathToCommandLineArgument(
const base::FilePath& file_path) {
#if defined(OS_POSIX)
return file_path.value();
#elif defined(OS_WIN)
return base::UTF16ToUTF8(file_path.value());
#endif // OS_POSIX
}
} // namespace crashpad

View File

@ -72,9 +72,18 @@ class ToolSupport {
//! `char*`. This undoes that transformation.
//!
//! \sa Wmain()
//! \sa FilePathToCommandLineArgument()
static base::FilePath::StringType CommandLineArgumentToFilePathStringType(
const base::StringPiece& arg);
//! \brief Converts a base::FilePath to a command line argument.
//!
//! On POSIX, this is a no-op. On Windows, this undoes the transformation done
//! by CommandLineArgumentToFilePathStringType() in the same manner as
//! Wmain().
static std::string FilePathToCommandLineArgument(
const base::FilePath& file_path);
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(ToolSupport);
};

View File

@ -48,6 +48,22 @@
'crashpad_database_util.cc',
],
},
{
'target_name': 'crashpad_http_upload',
'type': 'executable',
'dependencies': [
'crashpad_tool_support',
'../compat/compat.gyp:crashpad_compat',
'../third_party/mini_chromium/mini_chromium.gyp:base',
'../util/util.gyp:crashpad_util',
],
'include_dirs': [
'..',
],
'sources': [
'crashpad_http_upload.cc',
],
},
{
'target_name': 'generate_dump',
'type': 'executable',
@ -77,7 +93,7 @@
},
}],
],
}
},
],
'conditions': [
['OS=="mac"', {

View File

@ -109,10 +109,14 @@ FileOperationResult WeakStdioFileReader::Read(void* data, size_t size) {
DCHECK(file_);
size_t rv = fread(data, 1, size, file_);
if (rv < size && ferror(file_)) {
if (rv != size && ferror(file_)) {
STDIO_PLOG(ERROR) << "fread";
return -1;
}
if (rv > size) {
LOG(ERROR) << "fread: expected " << size << ", observed " << rv;
return -1;
}
return rv;
}

View File

@ -56,6 +56,11 @@ bool WeakFileHandleFileWriter::Write(const void* data, size_t size) {
bool WeakFileHandleFileWriter::WriteIoVec(std::vector<WritableIoVec>* iovecs) {
DCHECK_NE(file_handle_, kInvalidFileHandle);
if (iovecs->empty()) {
LOG(ERROR) << "WriteIoVec(): no iovecs";
return false;
}
#if defined(OS_POSIX)
ssize_t size = 0;
@ -187,4 +192,66 @@ FileOffset FileWriter::Seek(FileOffset offset, int whence) {
return weak_file_handle_file_writer_.Seek(offset, whence);
}
WeakStdioFileWriter::WeakStdioFileWriter(FILE* file)
: file_(file) {
}
WeakStdioFileWriter::~WeakStdioFileWriter() {
}
bool WeakStdioFileWriter::Write(const void* data, size_t size) {
DCHECK(file_);
size_t rv = fwrite(data, 1, size, file_);
if (rv != size) {
if (ferror(file_)) {
STDIO_PLOG(ERROR) << "fwrite";
} else {
LOG(ERROR) << "fwrite: expected " << size << ", observed " << rv;
}
return false;
}
return true;
}
bool WeakStdioFileWriter::WriteIoVec(std::vector<WritableIoVec>* iovecs) {
DCHECK(file_);
if (iovecs->empty()) {
LOG(ERROR) << "WriteIoVec(): no iovecs";
return false;
}
for (const WritableIoVec& iov : *iovecs) {
if (!Write(iov.iov_base, iov.iov_len)) {
return false;
}
}
#ifndef NDEBUG
// The interface says that |iovecs| is not sacred, so scramble it to make sure
// that nobody depends on it.
memset(&(*iovecs)[0], 0xa5, sizeof((*iovecs)[0]) * iovecs->size());
#endif
return true;
}
FileOffset WeakStdioFileWriter::Seek(FileOffset offset, int whence) {
DCHECK(file_);
if (fseeko(file_, offset, whence) == -1) {
STDIO_PLOG(ERROR) << "fseeko";
return -1;
}
FileOffset new_offset = ftello(file_);
if (new_offset == -1) {
STDIO_PLOG(ERROR) << "ftello";
return -1;
}
return new_offset;
}
} // namespace crashpad

View File

@ -167,6 +167,41 @@ class FileWriter : public FileWriterInterface {
DISALLOW_COPY_AND_ASSIGN(FileWriter);
};
//! \brief A file writer backed by a standard input/output `FILE*`.
//!
//! This class accepts an already-open `FILE*`. It is not responsible for
//! opening or closing this `FILE*`. Users of this class must ensure that the
//! `FILE*` is closed appropriately elsewhere. Objects of this class may be used
//! to write to `FILE*` objects not associated with filesystem-based files,
//! although special attention should be paid to the Seek() method, which may
//! not function on `FILE*` objects that do not refer to disk-based files.
//!
//! This class is expected to be used when other code is responsible for
//! opening `FILE*` objects and already provides `FILE*` objects. A good use
//! would be a WeakStdioFileWriter for `stdout`.
class WeakStdioFileWriter : public FileWriterInterface {
public:
explicit WeakStdioFileWriter(FILE* file);
~WeakStdioFileWriter() override;
// FileWriterInterface:
bool Write(const void* data, size_t size) override;
bool WriteIoVec(std::vector<WritableIoVec>* iovecs) override;
// FileSeekerInterface:
//! \copydoc FileWriterInterface::Seek()
//!
//! \note This method is only guaranteed to function on `FILE*` objects
//! referring to disk-based files.
FileOffset Seek(FileOffset offset, int whence) override;
private:
FILE* file_; // weak
DISALLOW_COPY_AND_ASSIGN(WeakStdioFileWriter);
};
} // namespace crashpad
#endif // CRASHPAD_UTIL_FILE_FILE_WRITER_H_