Implementation of HTTPTransport via raw socket

Partial implementation: Currently only handles http (i.e. no TLS), only
POST, and only certain response types (only when Content-Length is
specified, and not chunked). Used for Linux and Fuchsia lacking anything
better (that's shippable). Removes libcurl HTTPTransport, since it isn't
available in the Chromium sysroot anyway.

This is an intermediate step until BoringSSL is available in the Fuchsia
SDK. Once that's available, it should be "relatively straightfoward" to
make http_transport_socket.cc secure its socket using BoringSSL or
OpenSSL depending on the platform.

Bug: crashpad:196, crashpad:227, crashpad:30
Change-Id: If33a0d3f11b9000cbc3f52f96cd024ef274a922f
Reviewed-on: https://chromium-review.googlesource.com/1022717
Commit-Queue: Scott Graham <scottmg@chromium.org>
Reviewed-by: Joshua Peraza <jperaza@chromium.org>
This commit is contained in:
Scott Graham 2018-04-26 17:21:45 -07:00 committed by Commit Bot
parent 2ddfb4cd3c
commit 60ae9eeadb
6 changed files with 423 additions and 442 deletions

View File

@ -422,7 +422,7 @@ def _RunOnFuchsiaTarget(binary_dir, test, device_name, extra_command_line):
done_message = 'TERMINATED: ' + unique_id
namespace_command = [
'namespace', '/pkg=' + staging_root, '/tmp=' + tmp_root,
'namespace', '/pkg=' + staging_root, '/tmp=' + tmp_root, '/svc=/svc',
'--replace-child-argv0=/pkg/bin/' + test, '--',
staging_root + '/bin/' + test] + extra_command_line
netruncmd(namespace_command, ['echo', done_message])

View File

@ -14,13 +14,6 @@
import("../build/crashpad_buildconfig.gni")
declare_args() {
if (crashpad_is_linux) {
# Whether the libcurl-based HTTPTransport implementation should be built.
crashpad_enable_http_transport_libcurl = !crashpad_is_in_chromium
}
}
if (crashpad_is_mac) {
if (crashpad_is_in_chromium) {
import("//build/config/sysroot.gni")
@ -251,12 +244,10 @@ static_library("util") {
sources += get_target_outputs(":mig")
}
if (crashpad_is_linux || crashpad_is_android) {
if (crashpad_is_linux && crashpad_enable_http_transport_libcurl) {
sources += [ "net/http_transport_libcurl.cc" ]
} else {
sources += [ "net/http_transport_none.cc" ]
}
if (crashpad_is_linux || crashpad_is_fuchsia) {
sources += [ "net/http_transport_socket.cc" ]
} else if (crashpad_is_android) {
sources += [ "net/http_transport_none.cc" ]
}
if (crashpad_is_linux || crashpad_is_android) {
@ -394,7 +385,6 @@ static_library("util") {
"fuchsia/scoped_task_suspend.h",
"misc/capture_context_fuchsia.S",
"misc/paths_fuchsia.cc",
"net/http_transport_none.cc",
"process/process_memory_fuchsia.cc",
"process/process_memory_fuchsia.h",
]
@ -425,10 +415,6 @@ static_library("util") {
include_dirs += [ "$root_build_dir/gen" ]
}
if (crashpad_is_linux && crashpad_enable_http_transport_libcurl) {
libs = [ "curl" ]
}
if (crashpad_is_win) {
libs = [
"user32.lib",
@ -519,19 +505,8 @@ source_set("util_test") {
"thread/worker_thread_test.cc",
]
if (!crashpad_is_android && !crashpad_is_fuchsia &&
(!crashpad_is_linux || crashpad_enable_http_transport_libcurl)) {
# Android and Fuchsia will each require an HTTPTransport implementation
# (libcurl isnt in eithers SDK) and a solution to
# http_transport_test_server.py, because Python isnt available on either.
# The latter could be ported to non-Python, or the test server could run on
# the build host with a method to forward requests from the device to the
# host.
#
# Linux optionally compiles in a libcurl-based HTTPTransport, but since curl
# isn't in a base Debian sysroot (which is what Chromium builds against),
# maintain an option to exclude that, for now.
# https://crashpad.chromium.org/bug/220.
if (!crashpad_is_android) {
# Android requires an HTTPTransport implementation.
sources += [ "net/http_transport_test.cc" ]
}

View File

@ -1,402 +0,0 @@
// 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 "util/net/http_transport.h"
#include <curl/curl.h>
#include <string.h>
#include <sys/utsname.h>
#include <algorithm>
#include <limits>
#include "base/logging.h"
#include "base/numerics/safe_math.h"
#include "base/scoped_generic.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "package.h"
#include "util/net/http_body.h"
#include "util/numeric/safe_assignment.h"
namespace crashpad {
namespace {
std::string UserAgent() {
std::string user_agent = base::StringPrintf(
"%s/%s %s", PACKAGE_NAME, PACKAGE_VERSION, curl_version());
utsname os;
if (uname(&os) != 0) {
PLOG(WARNING) << "uname";
} else {
// Match the architecture name that would be used by the kernel, so that the
// strcmp() below can omit the kernels architecture name if its the same
// as the user process architecture. On Linux, these names are normally
// defined in each architectures Makefile as UTS_MACHINE, but can be
// overridden in architecture-specific configuration as COMPAT_UTS_MACHINE.
// See linux-4.9.17/arch/*/Makefile and
// linux-4.9.17/arch/*/include/asm/compat.h. In turn, on some systems, these
// names are further overridden or refined in early kernel startup code by
// modifying the string returned by linux-4.9.17/include/linux/utsname.h
// init_utsname() as noted.
#if defined(ARCH_CPU_X86)
// linux-4.9.17/arch/x86/kernel/cpu/bugs.c check_bugs() sets the first digit
// to 4, 5, or 6, but no higher.
#if defined(__i686__)
static constexpr char arch[] = "i686";
#elif defined(__i586__)
static constexpr char arch[] = "i586";
#elif defined(__i486__)
static constexpr char arch[] = "i486";
#else
static constexpr char arch[] = "i386";
#endif
#elif defined(ARCH_CPU_X86_64)
static constexpr char arch[] = "x86_64";
#elif defined(ARCH_CPU_ARMEL)
// linux-4.9.17/arch/arm/kernel/setup.c setup_processor() bases the string
// on the ARM processor name and a character identifying little- or
// big-endian. The processor name comes from a definition in
// arch/arm/mm/proc-*.S.
#if defined(__ARM_ARCH_4T__)
static constexpr char arch[] = "armv4t"
#elif defined(__ARM_ARCH_5TEJ__)
static constexpr char arch[] = "armv5tej"
#elif defined(__ARM_ARCH_5TE__)
static constexpr char arch[] = "armv5te"
#elif defined(__ARM_ARCH_5T__)
static constexpr char arch[] = "armv5t"
#elif defined(__ARM_ARCH_7M__)
static constexpr char arch[] = "armv7m"
#else
// Most ARM architectures fall into here, including all profile variants of
// armv6, armv7, armv8, with one exception, armv7m, handled above.
// xstr(__ARM_ARCH) will be the architecture revision number, such as 6, 7,
// or 8.
#define xstr(s) str(s)
#define str(s) #s
static constexpr char arch[] = "armv" xstr(__ARM_ARCH)
#undef str
#undef xstr
#endif
#if defined(ARCH_CPU_LITTLE_ENDIAN)
"l";
#elif defined(ARCH_CPU_BIG_ENDIAN)
"b";
#endif
#elif defined(ARCH_CPU_ARM64)
// ARM64 uses aarch64 or aarch64_be as directed by ELF_PLATFORM. See
// linux-4.9.17/arch/arm64/kernel/setup.c setup_arch().
#if defined(ARCH_CPU_LITTLE_ENDIAN)
static constexpr char arch[] = "aarch64";
#elif defined(ARCH_CPU_BIG_ENDIAN)
static constexpr char arch[] = "aarch64_be";
#endif
#elif defined(ARCH_CPU_MIPSEL)
static constexpr char arch[] = "mips";
#elif defined(ARCH_CPU_MIPS64EL)
static constexpr char arch[] = "mips64";
#else
#error Port
#endif
user_agent.append(
base::StringPrintf(" %s/%s (%s", os.sysname, os.release, arch));
if (strcmp(arch, os.machine) != 0) {
user_agent.append(base::StringPrintf("; %s", os.machine));
}
user_agent.append(1, ')');
}
return user_agent;
}
std::string CurlErrorMessage(CURLcode curl_err, const std::string& base) {
return base::StringPrintf(
"%s: %s (%d)", base.c_str(), curl_easy_strerror(curl_err), curl_err);
}
struct ScopedCURLTraits {
static CURL* InvalidValue() { return nullptr; }
static void Free(CURL* curl) {
if (curl) {
curl_easy_cleanup(curl);
}
}
};
using ScopedCURL = base::ScopedGeneric<CURL*, ScopedCURLTraits>;
class CurlSList {
public:
CurlSList() : list_(nullptr) {}
~CurlSList() {
if (list_) {
curl_slist_free_all(list_);
}
}
curl_slist* get() const { return list_; }
bool Append(const char* data) {
curl_slist* list = curl_slist_append(list_, data);
if (!list_) {
list_ = list;
}
return list != nullptr;
}
private:
curl_slist* list_;
DISALLOW_COPY_AND_ASSIGN(CurlSList);
};
class ScopedClearString {
public:
explicit ScopedClearString(std::string* string) : string_(string) {}
~ScopedClearString() {
if (string_) {
string_->clear();
}
}
void Disarm() { string_ = nullptr; }
private:
std::string* string_;
DISALLOW_COPY_AND_ASSIGN(ScopedClearString);
};
class HTTPTransportLibcurl final : public HTTPTransport {
public:
HTTPTransportLibcurl();
~HTTPTransportLibcurl() override;
// HTTPTransport:
bool ExecuteSynchronously(std::string* response_body) override;
private:
static size_t ReadRequestBody(char* buffer,
size_t size,
size_t nitems,
void* userdata);
static size_t WriteResponseBody(char* buffer,
size_t size,
size_t nitems,
void* userdata);
DISALLOW_COPY_AND_ASSIGN(HTTPTransportLibcurl);
};
HTTPTransportLibcurl::HTTPTransportLibcurl() : HTTPTransport() {}
HTTPTransportLibcurl::~HTTPTransportLibcurl() {}
bool HTTPTransportLibcurl::ExecuteSynchronously(std::string* response_body) {
DCHECK(body_stream());
response_body->clear();
// curl_easy_init() will do this on the first call if it hasnt been done yet,
// but not in a thread-safe way as is done here.
static CURLcode curl_global_init_err = []() {
return curl_global_init(CURL_GLOBAL_DEFAULT);
}();
if (curl_global_init_err != CURLE_OK) {
LOG(ERROR) << CurlErrorMessage(curl_global_init_err, "curl_global_init");
return false;
}
CurlSList curl_headers;
ScopedCURL curl(curl_easy_init());
if (!curl.get()) {
LOG(ERROR) << "curl_easy_init";
return false;
}
// These macros wrap the repetitive “try something, log an error and return
// false on failure” pattern. Macros are convenient because the log messages
// will point to the correct line number, which can help pinpoint a problem when
// there are as many calls to these functions as there are here.
#define TRY_CURL_EASY_SETOPT(curl, option, parameter) \
do { \
CURLcode curl_err = curl_easy_setopt((curl), (option), (parameter)); \
if (curl_err != CURLE_OK) { \
LOG(ERROR) << CurlErrorMessage(curl_err, "curl_easy_setopt"); \
return false; \
} \
} while (false)
#define TRY_CURL_SLIST_APPEND(slist, data) \
do { \
if (!(slist).Append(data)) { \
LOG(ERROR) << "curl_slist_append"; \
return false; \
} \
} while (false)
TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_USERAGENT, UserAgent().c_str());
// Accept and automatically decode any encoding that libcurl understands.
TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_ACCEPT_ENCODING, "");
TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_URL, url().c_str());
constexpr int kMillisecondsPerSecond = 1E3;
TRY_CURL_EASY_SETOPT(curl.get(),
CURLOPT_TIMEOUT_MS,
static_cast<long>(timeout() * kMillisecondsPerSecond));
// If the request body size is known ahead of time, a Content-Length header
// field will be present. Store that to use as CURLOPT_POSTFIELDSIZE_LARGE,
// which will both set the Content-Length field in the request header and
// inform libcurl of the request body size. Otherwise, use Transfer-Encoding:
// chunked, which does not require advance knowledge of the request body size.
bool chunked = true;
size_t content_length;
for (const auto& pair : headers()) {
if (pair.first == kContentLength) {
chunked = !base::StringToSizeT(pair.second, &content_length);
DCHECK(!chunked);
} else {
TRY_CURL_SLIST_APPEND(curl_headers,
(pair.first + ": " + pair.second).c_str());
}
}
if (method() == "POST") {
TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_POST, 1l);
// By default when sending a POST request, libcurl includes an “Expect:
// 100-continue” header field. Althogh this header is specified in HTTP/1.1
// (RFC 2616 §8.2.3, RFC 7231 §5.1.1), even collection servers that claim to
// speak HTTP/1.1 may not respond to it. When sending this header field,
// libcurl will wait for one second for the server to respond with a “100
// Continue” status before continuing to transmit the request body. This
// delay is avoided by telling libcurl not to send this header field at all.
// The drawback is that certain HTTP error statuses may not be received
// until after substantial amounts of data have been sent to the server.
TRY_CURL_SLIST_APPEND(curl_headers, "Expect:");
if (chunked) {
TRY_CURL_SLIST_APPEND(curl_headers, "Transfer-Encoding: chunked");
} else {
curl_off_t content_length_curl;
if (!AssignIfInRange(&content_length_curl, content_length)) {
LOG(ERROR) << base::StringPrintf("Content-Length %zu too large",
content_length);
return false;
}
TRY_CURL_EASY_SETOPT(
curl.get(), CURLOPT_POSTFIELDSIZE_LARGE, content_length_curl);
}
} else if (method() != "GET") {
// Untested.
TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_CUSTOMREQUEST, method().c_str());
}
TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_HTTPHEADER, curl_headers.get());
TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_READFUNCTION, ReadRequestBody);
TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_READDATA, this);
TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_WRITEFUNCTION, WriteResponseBody);
TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_WRITEDATA, response_body);
#undef TRY_CURL_EASY_SETOPT
#undef TRY_CURL_SLIST_APPEND
// If a partial response body is received and then a failure occurs, ensure
// that response_body is cleared.
ScopedClearString clear_response_body(response_body);
// Do it.
CURLcode curl_err = curl_easy_perform(curl.get());
if (curl_err != CURLE_OK) {
LOG(ERROR) << CurlErrorMessage(curl_err, "curl_easy_perform");
return false;
}
long status;
curl_err = curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &status);
if (curl_err != CURLE_OK) {
LOG(ERROR) << CurlErrorMessage(curl_err, "curl_easy_getinfo");
return false;
}
if (status != 200) {
LOG(ERROR) << base::StringPrintf("HTTP status %ld", status);
return false;
}
// The response body is complete. Dont clear it.
clear_response_body.Disarm();
return true;
}
// static
size_t HTTPTransportLibcurl::ReadRequestBody(char* buffer,
size_t size,
size_t nitems,
void* userdata) {
HTTPTransportLibcurl* self =
reinterpret_cast<HTTPTransportLibcurl*>(userdata);
// This libcurl callback mimics the silly stdio-style fread() interface: size
// and nitems have been separated and must be multiplied.
base::CheckedNumeric<size_t> checked_len = base::CheckMul(size, nitems);
size_t len = checked_len.ValueOrDefault(std::numeric_limits<size_t>::max());
// Limit the read to what can be expressed in a FileOperationResult.
len = std::min(
len,
static_cast<size_t>(std::numeric_limits<FileOperationResult>::max()));
FileOperationResult bytes_read = self->body_stream()->GetBytesBuffer(
reinterpret_cast<uint8_t*>(buffer), len);
if (bytes_read < 0) {
return CURL_READFUNC_ABORT;
}
return bytes_read;
}
// static
size_t HTTPTransportLibcurl::WriteResponseBody(char* buffer,
size_t size,
size_t nitems,
void* userdata) {
std::string* response_body = reinterpret_cast<std::string*>(userdata);
// This libcurl callback mimics the silly stdio-style fread() interface: size
// and nitems have been separated and must be multiplied.
base::CheckedNumeric<size_t> checked_len = base::CheckMul(size, nitems);
size_t len = checked_len.ValueOrDefault(std::numeric_limits<size_t>::max());
response_body->append(buffer, len);
return len;
}
} // namespace
// static
std::unique_ptr<HTTPTransport> HTTPTransport::Create() {
return std::unique_ptr<HTTPTransport>(new HTTPTransportLibcurl());
}
} // namespace crashpad

View File

@ -0,0 +1,409 @@
// Copyright 2018 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 "util/net/http_transport.h"
#include <fcntl.h>
#include <netdb.h>
#include <poll.h>
#include <sys/socket.h>
#include "base/logging.h"
#include "base/macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/posix/eintr_wrapper.h"
#include "base/scoped_generic.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "util/file/file_io.h"
#include "util/net/http_body.h"
#include "util/net/url.h"
#include "util/stdlib/string_number_conversion.h"
#include "util/string/split_string.h"
namespace crashpad {
namespace {
constexpr const char kCRLFTerminator[] = "\r\n";
class HTTPTransportSocket final : public HTTPTransport {
public:
HTTPTransportSocket() = default;
~HTTPTransportSocket() override = default;
bool ExecuteSynchronously(std::string* response_body) override;
private:
DISALLOW_COPY_AND_ASSIGN(HTTPTransportSocket);
};
struct ScopedAddrinfoTraits {
static addrinfo* InvalidValue() { return nullptr; }
static void Free(addrinfo* ai) { freeaddrinfo(ai); }
};
using ScopedAddrinfo =
base::ScopedGeneric<addrinfo*, ScopedAddrinfoTraits>;
bool WaitUntilSocketIsReady(int sock) {
pollfd pollfds;
pollfds.fd = sock;
pollfds.events = POLLIN | POLLPRI | POLLOUT;
constexpr int kTimeoutMS = 1000;
int ret = HANDLE_EINTR(poll(&pollfds, 1, kTimeoutMS));
if (ret < 0) {
PLOG(ERROR) << "poll";
return false;
} else if (ret == 1) {
if (pollfds.revents & POLLERR) {
int err;
socklen_t err_len = sizeof(err);
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &err_len) != 0) {
PLOG(ERROR) << "getsockopt";
} else {
errno = err;
PLOG(ERROR) << "POLLERR";
}
return false;
}
if (pollfds.revents & POLLHUP) {
return false;
}
return (pollfds.revents & POLLIN) != 0 || (pollfds.revents & POLLOUT) != 0;
}
// Timeout.
return false;
}
class ScopedSetNonblocking {
public:
explicit ScopedSetNonblocking(int sock) : sock_(sock) {
int flags = fcntl(sock, F_GETFL, 0);
if (flags < 0) {
PLOG(ERROR) << "fcntl";
sock_ = -1;
return;
}
if (fcntl(sock_, F_SETFL, flags | O_NONBLOCK) < 0) {
PLOG(ERROR) << "fcntl";
sock_ = -1;
}
}
~ScopedSetNonblocking() {
if (sock_ >= 0) {
int flags = fcntl(sock_, F_GETFL, 0);
if (flags < 0) {
PLOG(ERROR) << "fcntl";
return;
}
if (fcntl(sock_, F_SETFL, flags & (~O_NONBLOCK)) < 0) {
PLOG(ERROR) << "fcntl";
}
}
}
private:
int sock_;
DISALLOW_COPY_AND_ASSIGN(ScopedSetNonblocking);
};
base::ScopedFD CreateSocket(const std::string& hostname,
const std::string& port) {
addrinfo hints = {};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
hints.ai_flags = 0;
addrinfo* addrinfo_raw;
if (getaddrinfo(hostname.c_str(), port.c_str(), &hints, &addrinfo_raw) < 0) {
PLOG(ERROR) << "getaddrinfo";
return base::ScopedFD();
}
ScopedAddrinfo addrinfo(addrinfo_raw);
for (const auto* ap = addrinfo.get(); ap; ap = ap->ai_next) {
base::ScopedFD result(
socket(ap->ai_family, ap->ai_socktype, ap->ai_protocol));
if (!result.is_valid()) {
continue;
}
{
// Set socket to non-blocking to avoid hanging for a long time if the
// network is down.
ScopedSetNonblocking nonblocking(result.get());
if (HANDLE_EINTR(connect(result.get(), ap->ai_addr, ap->ai_addrlen)) <
0) {
if (errno != EINPROGRESS) {
PLOG(ERROR) << "connect";
} else if (WaitUntilSocketIsReady(result.get())) {
return result;
}
return base::ScopedFD();
}
return result;
}
}
return base::ScopedFD();
}
bool WriteRequest(int sock,
const std::string& method,
const std::string& resource,
const HTTPHeaders& headers,
HTTPBodyStream* body_stream) {
std::string request_line = base::StringPrintf(
"%s %s HTTP/1.0\r\n", method.c_str(), resource.c_str());
if (!LoggingWriteFile(sock, request_line.data(), request_line.size()))
return false;
// Write headers, and determine if Content-Length has been specified.
bool chunked = true;
size_t content_length = 0;
for (const auto& header : headers) {
std::string header_str = base::StringPrintf(
"%s: %s\r\n", header.first.c_str(), header.second.c_str());
if (header.first == kContentLength) {
chunked = !base::StringToSizeT(header.second, &content_length);
DCHECK(!chunked);
}
if (!LoggingWriteFile(sock, header_str.data(), header_str.size()))
return false;
}
// If no Content-Length, then encode as chunked, so add that header too.
if (chunked) {
static constexpr const char kTransferEncodingChunked[] =
"Transfer-Encoding: chunked\r\n";
if (!LoggingWriteFile(
sock, kTransferEncodingChunked, strlen(kTransferEncodingChunked))) {
return false;
}
}
if (!LoggingWriteFile(sock, kCRLFTerminator, strlen(kCRLFTerminator))) {
return false;
}
FileOperationResult data_bytes;
do {
constexpr size_t kCRLFSize = arraysize(kCRLFTerminator) - 1;
struct __attribute__((packed)) {
char size[8];
char crlf[2];
uint8_t data[32 * 1024 + kCRLFSize];
} buf;
static_assert(
sizeof(buf) == sizeof(buf.size) + sizeof(buf.crlf) + sizeof(buf.data),
"buf should not have padding");
// Read a block of data.
data_bytes =
body_stream->GetBytesBuffer(buf.data, sizeof(buf.data) - kCRLFSize);
if (data_bytes == -1) {
return false;
}
DCHECK_GE(data_bytes, 0);
DCHECK_LE(static_cast<size_t>(data_bytes), sizeof(buf.data) - kCRLFSize);
void* write_start;
size_t write_size;
if (chunked) {
// Chunked encoding uses the entirety of buf. buf.size is presented in
// hexadecimal without any leading "0x". The terminating CR and LF will be
// placed immediately following the used portion of buf.data, even if
// buf.data is not full.
// Not snprintf because non-null terminated is desired.
int rv = sprintf(
buf.size, "%08x", base::checked_cast<unsigned int>(data_bytes));
DCHECK_GE(rv, 0);
DCHECK_EQ(static_cast<size_t>(rv), sizeof(buf.size));
DCHECK_NE(buf.size[sizeof(buf.size) - 1], '\0');
memcpy(&buf.crlf[0], kCRLFTerminator, kCRLFSize);
memcpy(&buf.data[data_bytes], kCRLFTerminator, kCRLFSize);
// Skip leading zeroes in the chunk size.
size_t size_len;
for (size_len = sizeof(buf.size); size_len > 1; --size_len) {
if (buf.size[sizeof(buf.size) - size_len] != '0') {
break;
}
}
write_start = buf.crlf - size_len;
write_size = size_len + sizeof(buf.crlf) + data_bytes + kCRLFSize;
} else {
// When not using chunked encoding, only use buf.data.
write_start = buf.data;
write_size = data_bytes;
}
// write_size will be 0 at EOF in non-chunked mode. Skip the write in that
// case. In contrast, at EOF in chunked mode, a zero-length chunk must be
// sent to signal EOF. This will happen when processing the EOF indicated by
// a 0 return from body_stream()->GetBytesBuffer() above.
if (write_size != 0) {
if (!LoggingWriteFile(sock, write_start, write_size))
return false;
}
} while (data_bytes > 0);
return true;
}
bool ReadLine(int sock, std::string* line) {
line->clear();
for (;;) {
char byte;
if (!LoggingReadFileExactly(sock, &byte, 1)) {
return false;
}
line->append(&byte, 1);
if (byte == '\n')
return true;
}
}
bool StartsWith(const std::string& str, const char* with, size_t len) {
return str.compare(0, len, with) == 0;
}
bool ReadResponseLine(int sock) {
std::string response_line;
if (!ReadLine(sock, &response_line)) {
LOG(ERROR) << "ReadLine";
return false;
}
static constexpr const char kHttp10[] = "HTTP/1.0 200 ";
static constexpr const char kHttp11[] = "HTTP/1.1 200 ";
return StartsWith(response_line, kHttp10, strlen(kHttp10)) ||
StartsWith(response_line, kHttp11, strlen(kHttp11));
}
bool ReadResponseHeaders(int sock, HTTPHeaders* headers) {
for (;;) {
std::string line;
if (!ReadLine(sock, &line)) {
return false;
}
if (line == kCRLFTerminator) {
return true;
}
std::string left, right;
if (!SplitStringFirst(line, ':', &left, &right)) {
LOG(ERROR) << "SplitStringFirst";
return false;
}
DCHECK_EQ(right[right.size() - 1], '\n');
DCHECK_EQ(right[right.size() - 2], '\r');
DCHECK_EQ(right[0], ' ');
DCHECK_NE(right[1], ' ');
right = right.substr(1, right.size() - 3);
(*headers)[left] = right;
}
}
bool ReadContentChunked(int sock, std::string* body) {
// TODO(scottmg): https://crashpad.chromium.org/bug/196.
LOG(ERROR) << "TODO(scottmg): chunked response read";
return false;
}
bool ReadResponse(int sock, std::string* response_body) {
response_body->clear();
if (!ReadResponseLine(sock)) {
return false;
}
HTTPHeaders response_headers;
if (!ReadResponseHeaders(sock, &response_headers)) {
return false;
}
auto it = response_headers.find("Content-Length");
size_t len = 0;
if (it != response_headers.end()) {
if (!base::StringToSizeT(it->second, &len)) {
LOG(ERROR) << "invalid Content-Length";
return false;
}
}
if (len) {
response_body->resize(len, 0);
return ReadFileExactly(sock, &(*response_body)[0], len);
}
it = response_headers.find("Transfer-Encoding");
bool chunked = false;
if (it != response_headers.end() && it->second == "chunked") {
chunked = true;
}
if (!chunked) {
// TODO(scottmg): https://crashpad.chromium.org/bug/196. Doesn't happen
// in practice, but is possible.
LOG(ERROR) << "unimplemented non-chunked without Content-Length";
return false;
}
return ReadContentChunked(sock, response_body);
}
bool HTTPTransportSocket::ExecuteSynchronously(std::string* response_body) {
std::string scheme, hostname, port, resource;
if (!CrackURL(url(), &scheme, &hostname, &port, &resource)) {
return false;
}
base::ScopedFD sock(CreateSocket(hostname, port));
if (!sock.is_valid()) {
return false;
}
if (!WriteRequest(sock.get(), method(), resource, headers(), body_stream())) {
return false;
}
if (!ReadResponse(sock.get(), response_body)) {
return false;
}
return true;
}
} // namespace
// static
std::unique_ptr<HTTPTransport> HTTPTransport::Create() {
return std::unique_ptr<HTTPTransportSocket>(new HTTPTransportSocket);
}
} // namespace crashpad

View File

@ -23,6 +23,7 @@
// client, and write the entire request to stdout. It will then terminate.
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "tools/tool_support.h"
@ -67,6 +68,7 @@ int HttpTransportTestServerMain(int argc, char* argv[]) {
res.set_content("error", "text/plain");
}
to_stdout += "POST /upload HTTP/1.0\r\n";
for (const auto& h : req.headers) {
to_stdout += base::StringPrintf(
"%s: %s\r\n", h.first.c_str(), h.second.c_str());
@ -77,7 +79,8 @@ int HttpTransportTestServerMain(int argc, char* argv[]) {
svr.stop();
});
int port = svr.bind_to_any_port("127.0.0.1");
uint16_t port =
base::checked_cast<uint16_t>(svr.bind_to_any_port("127.0.0.1"));
CheckedWriteFile(
StdioFileHandle(StdioStream::kStandardOutput), &port, sizeof(port));

View File

@ -175,7 +175,6 @@
'net/http_multipart_builder.h',
'net/http_transport.cc',
'net/http_transport.h',
'net/http_transport_libcurl.cc',
'net/http_transport_mac.mm',
'net/http_transport_none.cc',
'net/http_transport_win.cc',
@ -383,15 +382,12 @@
],
}],
['OS=="linux"', {
'link_settings': {
'libraries': [
'-lcurl',
],
},
'sources': [
'net/http_transport_socket.cc',
],
}, { # else: OS!="linux"
'sources!': [
'misc/capture_context_linux.S',
'net/http_transport_libcurl.cc',
],
}],
['OS!="android"', {