mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
Add HTTPTransport, a Mac implementation, and an end-to-end test.
BUG=https://crbug.com/415544 R=mark@chromium.org Review URL: https://codereview.chromium.org/692963002
This commit is contained in:
parent
45993a6959
commit
d88711adfa
23
util/net/http_headers.cc
Normal file
23
util/net/http_headers.cc
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright 2014 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_headers.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
const char kContentType[] = "Content-Type";
|
||||
|
||||
const char kContentLength[] = "Content-Length";
|
||||
|
||||
} // namespace crashpad
|
34
util/net/http_headers.h
Normal file
34
util/net/http_headers.h
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright 2014 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.
|
||||
|
||||
#ifndef CRASHPAD_UTIL_NET_HTTP_HEADERS_H_
|
||||
#define CRASHPAD_UTIL_NET_HTTP_HEADERS_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief A map of HTTP header fields to their values.
|
||||
using HTTPHeaders = std::map<std::string, std::string>;
|
||||
|
||||
//! \brief The header name `"Content-Type"`.
|
||||
extern const char kContentType[];
|
||||
|
||||
//! \brief The header name `"Content-Length"`.
|
||||
extern const char kContentLength[];
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_UTIL_NET_HTTP_HEADERS_H_
|
@ -14,6 +14,7 @@
|
||||
|
||||
#include "util/net/http_multipart_builder.h"
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/logging.h"
|
||||
@ -152,7 +153,7 @@ scoped_ptr<HTTPBodyStream> HTTPMultipartBuilder::GetBodyStream() {
|
||||
std::vector<HTTPBodyStream*> streams;
|
||||
|
||||
for (const auto& pair : form_data_) {
|
||||
std::string field = GetFormDataBoundary(boundary(), pair.first);
|
||||
std::string field = GetFormDataBoundary(boundary_, pair.first);
|
||||
field += kBoundaryCRLF;
|
||||
field += pair.second;
|
||||
field += kCRLF;
|
||||
@ -161,7 +162,7 @@ scoped_ptr<HTTPBodyStream> HTTPMultipartBuilder::GetBodyStream() {
|
||||
|
||||
for (const auto& pair : file_attachments_) {
|
||||
const FileAttachment& attachment = pair.second;
|
||||
std::string header = GetFormDataBoundary(boundary(), pair.first);
|
||||
std::string header = GetFormDataBoundary(boundary_, pair.first);
|
||||
header += base::StringPrintf("; filename=\"%s\"%s",
|
||||
attachment.filename.c_str(), kCRLF);
|
||||
header += base::StringPrintf("Content-Type: %s%s",
|
||||
@ -173,11 +174,17 @@ scoped_ptr<HTTPBodyStream> HTTPMultipartBuilder::GetBodyStream() {
|
||||
}
|
||||
|
||||
streams.push_back(
|
||||
new StringHTTPBodyStream("--" + boundary() + "--" + kCRLF));
|
||||
new StringHTTPBodyStream("--" + boundary_ + "--" + kCRLF));
|
||||
|
||||
return scoped_ptr<HTTPBodyStream>(new CompositeHTTPBodyStream(streams));
|
||||
}
|
||||
|
||||
HTTPHeaders::value_type HTTPMultipartBuilder::GetContentType() const {
|
||||
std::string content_type =
|
||||
base::StringPrintf("multipart/form-data; boundary=%s", boundary_.c_str());
|
||||
return std::make_pair(kContentType, content_type);
|
||||
}
|
||||
|
||||
void HTTPMultipartBuilder::EraseKey(const std::string& key) {
|
||||
auto data_it = form_data_.find(key);
|
||||
if (data_it != form_data_.end())
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "base/basictypes.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/memory/scoped_ptr.h"
|
||||
#include "util/net/http_headers.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
@ -63,8 +64,8 @@ class HTTPMultipartBuilder {
|
||||
//! \return A caller-owned HTTPBodyStream object.
|
||||
scoped_ptr<HTTPBodyStream> GetBodyStream();
|
||||
|
||||
//! \brief Gets the boundary that will be used in GetBodyStream().
|
||||
std::string boundary() const { return boundary_; }
|
||||
//! \brief Gets the header pair for `"Content-Type"`.
|
||||
HTTPHeaders::value_type GetContentType() const;
|
||||
|
||||
private:
|
||||
struct FileAttachment {
|
||||
|
53
util/net/http_transport.cc
Normal file
53
util/net/http_transport.cc
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2014 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 "util/net/http_body.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
HTTPTransport::HTTPTransport()
|
||||
: url_(),
|
||||
method_("POST"),
|
||||
headers_(),
|
||||
body_stream_(),
|
||||
timeout_(15.0) {
|
||||
}
|
||||
|
||||
HTTPTransport::~HTTPTransport() {
|
||||
}
|
||||
|
||||
void HTTPTransport::SetURL(const std::string& url) {
|
||||
url_ = url;
|
||||
}
|
||||
|
||||
void HTTPTransport::SetMethod(const std::string& method) {
|
||||
method_ = method;
|
||||
}
|
||||
|
||||
void HTTPTransport::SetHeader(const std::string& header,
|
||||
const std::string& value) {
|
||||
headers_[header] = value;
|
||||
}
|
||||
|
||||
void HTTPTransport::SetBodyStream(scoped_ptr<HTTPBodyStream> stream) {
|
||||
body_stream_ = stream.Pass();
|
||||
}
|
||||
|
||||
void HTTPTransport::SetTimeout(double timeout) {
|
||||
timeout_ = timeout;
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
102
util/net/http_transport.h
Normal file
102
util/net/http_transport.h
Normal file
@ -0,0 +1,102 @@
|
||||
// Copyright 2014 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.
|
||||
|
||||
#ifndef CRASHPAD_UTIL_NET_HTTP_TRANSPORT_H_
|
||||
#define CRASHPAD_UTIL_NET_HTTP_TRANSPORT_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/memory/scoped_ptr.h"
|
||||
#include "util/net/http_headers.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
class HTTPBodyStream;
|
||||
|
||||
//! \brief HTTPTransport executes a HTTP request using the specified URL, HTTP
|
||||
//! method, headers, and body. This class can only issue a synchronous
|
||||
//! HTTP request.
|
||||
//!
|
||||
//! This class cannot be instantiated directly. A concrete subclass must be
|
||||
//! instantiated instead, which provides an implementation to execute the
|
||||
//! request that is appropriate for the host operating system.
|
||||
class HTTPTransport {
|
||||
public:
|
||||
virtual ~HTTPTransport();
|
||||
|
||||
//! \brief Instantiates a concrete HTTPTransport class for the current
|
||||
//! operating system.
|
||||
//!
|
||||
//! \return A new caller-owned HTTPTransport object.
|
||||
static scoped_ptr<HTTPTransport> Create();
|
||||
|
||||
//! \brief Sets URL to which the request will be made.
|
||||
//!
|
||||
//! \param[in] url The request URL.
|
||||
void SetURL(const std::string& url);
|
||||
|
||||
//! \brief Sets the HTTP method to execute. E.g., GET, POST, etc. The default
|
||||
//! method is `"POST"`.
|
||||
//!
|
||||
//! \param[in] http_method The HTTP method.
|
||||
void SetMethod(const std::string& http_method);
|
||||
|
||||
//! \brief Sets a HTTP header-value pair.
|
||||
//!
|
||||
//! \param[in] header The HTTP header name. Any previous value set at this
|
||||
//! name will be overwritten.
|
||||
//! \param[in] value The value to set for the header.
|
||||
void SetHeader(const std::string& header, const std::string& value);
|
||||
|
||||
//! \brief Sets the stream object from which to generate the HTTP body.
|
||||
//!
|
||||
//! \param[in] stream A HTTPBodyStream, of which this class will take
|
||||
//! ownership.
|
||||
void SetBodyStream(scoped_ptr<HTTPBodyStream> stream);
|
||||
|
||||
//! \brief Sets the timeout for the HTTP request. The default is 15 seconds.
|
||||
//!
|
||||
//! \param[in] timeout The request timeout, in seconds.
|
||||
void SetTimeout(double timeout);
|
||||
|
||||
//! \brief Performs the HTTP request with the configured parameters and waits
|
||||
//! for the execution to complete.
|
||||
//!
|
||||
//! \return Whether or not the request was successful, defined as returning
|
||||
//! a HTTP status 200 (OK) code.
|
||||
virtual bool ExecuteSynchronously() = 0;
|
||||
|
||||
protected:
|
||||
HTTPTransport();
|
||||
|
||||
const std::string& url() const { return url_; }
|
||||
const std::string& method() const { return method_; }
|
||||
const HTTPHeaders& headers() const { return headers_; }
|
||||
HTTPBodyStream* body_stream() const { return body_stream_.get(); }
|
||||
double timeout() const { return timeout_; }
|
||||
|
||||
private:
|
||||
std::string url_;
|
||||
std::string method_;
|
||||
HTTPHeaders headers_;
|
||||
scoped_ptr<HTTPBodyStream> body_stream_;
|
||||
double timeout_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(HTTPTransport);
|
||||
};
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_UTIL_NET_HTTP_TRANSPORT_H_
|
192
util/net/http_transport_mac.mm
Normal file
192
util/net/http_transport_mac.mm
Normal file
@ -0,0 +1,192 @@
|
||||
// Copyright 2014 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"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "base/mac/foundation_util.h"
|
||||
#import "base/mac/scoped_nsobject.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "util/net/http_body.h"
|
||||
|
||||
@interface CrashpadHTTPBodyStreamTransport : NSInputStream {
|
||||
@private
|
||||
NSStreamStatus streamStatus_;
|
||||
id<NSStreamDelegate> delegate_;
|
||||
crashpad::HTTPBodyStream* bodyStream_; // weak
|
||||
}
|
||||
- (instancetype)initWithBodyStream:(crashpad::HTTPBodyStream*)bodyStream;
|
||||
@end
|
||||
|
||||
@implementation CrashpadHTTPBodyStreamTransport
|
||||
|
||||
- (instancetype)initWithBodyStream:(crashpad::HTTPBodyStream*)bodyStream {
|
||||
if ((self = [super init])) {
|
||||
streamStatus_ = NSStreamStatusNotOpen;
|
||||
bodyStream_ = bodyStream;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// NSInputStream:
|
||||
|
||||
- (BOOL)hasBytesAvailable {
|
||||
// Per Apple's documentation: "May also return YES if a read must be attempted
|
||||
// in order to determine the availability of bytes."
|
||||
switch (streamStatus_) {
|
||||
case NSStreamStatusAtEnd:
|
||||
case NSStreamStatusClosed:
|
||||
case NSStreamStatusError:
|
||||
return NO;
|
||||
default:
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)read:(uint8_t*)buffer maxLength:(NSUInteger)maxLen {
|
||||
streamStatus_ = NSStreamStatusReading;
|
||||
|
||||
NSInteger rv = bodyStream_->GetBytesBuffer(buffer, maxLen);
|
||||
|
||||
if (rv == 0)
|
||||
streamStatus_ = NSStreamStatusAtEnd;
|
||||
else if (rv < 0)
|
||||
streamStatus_ = NSStreamStatusError;
|
||||
else
|
||||
streamStatus_ = NSStreamStatusOpen;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
- (BOOL)getBuffer:(uint8_t**)buffer length:(NSUInteger*)length {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// NSStream:
|
||||
|
||||
- (void)scheduleInRunLoop:(NSRunLoop*)runLoop
|
||||
forMode:(NSString*)mode {
|
||||
}
|
||||
|
||||
- (void)removeFromRunLoop:(NSRunLoop*)runLoop
|
||||
forMode:(NSString*)mode {
|
||||
}
|
||||
|
||||
- (void)open {
|
||||
streamStatus_ = NSStreamStatusOpen;
|
||||
}
|
||||
|
||||
- (void)close {
|
||||
streamStatus_ = NSStreamStatusClosed;
|
||||
}
|
||||
|
||||
- (NSStreamStatus)streamStatus {
|
||||
return streamStatus_;
|
||||
}
|
||||
|
||||
- (id<NSStreamDelegate>)delegate {
|
||||
return delegate_;
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id)delegate {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace {
|
||||
|
||||
class HTTPTransportMac final : public HTTPTransport {
|
||||
public:
|
||||
HTTPTransportMac();
|
||||
~HTTPTransportMac() override;
|
||||
|
||||
bool ExecuteSynchronously() override;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(HTTPTransportMac);
|
||||
};
|
||||
|
||||
HTTPTransportMac::HTTPTransportMac() : HTTPTransport() {
|
||||
}
|
||||
|
||||
HTTPTransportMac::~HTTPTransportMac() {
|
||||
}
|
||||
|
||||
bool HTTPTransportMac::ExecuteSynchronously() {
|
||||
DCHECK(body_stream());
|
||||
|
||||
@autoreleasepool {
|
||||
NSString* url_ns_string = base::SysUTF8ToNSString(url());
|
||||
NSURL* url = [NSURL URLWithString:url_ns_string];
|
||||
NSMutableURLRequest* request =
|
||||
[NSMutableURLRequest requestWithURL:url
|
||||
cachePolicy:NSURLRequestUseProtocolCachePolicy
|
||||
timeoutInterval:timeout()];
|
||||
[request setHTTPMethod:base::SysUTF8ToNSString(method())];
|
||||
|
||||
for (const auto& pair : headers()) {
|
||||
[request setValue:base::SysUTF8ToNSString(pair.second)
|
||||
forHTTPHeaderField:base::SysUTF8ToNSString(pair.first)];
|
||||
}
|
||||
|
||||
base::scoped_nsobject<CrashpadHTTPBodyStreamTransport> transport(
|
||||
[[CrashpadHTTPBodyStreamTransport alloc] initWithBodyStream:
|
||||
body_stream()]);
|
||||
[request setHTTPBodyStream:transport.get()];
|
||||
|
||||
NSURLResponse* response = nil;
|
||||
NSError* error = nil;
|
||||
[NSURLConnection sendSynchronousRequest:request
|
||||
returningResponse:&response
|
||||
error:&error];
|
||||
|
||||
if (error) {
|
||||
LOG(ERROR) << [[error localizedDescription] UTF8String] << " ("
|
||||
<< [[error domain] UTF8String] << " " << [error code] << ")";
|
||||
return false;
|
||||
}
|
||||
if (!response) {
|
||||
LOG(ERROR) << "no response";
|
||||
return false;
|
||||
}
|
||||
NSHTTPURLResponse* http_response =
|
||||
base::mac::ObjCCast<NSHTTPURLResponse>(response);
|
||||
if (!http_response) {
|
||||
LOG(ERROR) << "no http_response";
|
||||
return false;
|
||||
}
|
||||
NSInteger http_status = [http_response statusCode];
|
||||
if (http_status != 200) {
|
||||
LOG(ERROR) << base::StringPrintf("HTTP status %ld",
|
||||
static_cast<long>(http_status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
scoped_ptr<HTTPTransport> HTTPTransport::Create() {
|
||||
return scoped_ptr<HTTPTransport>(new HTTPTransportMac());
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
242
util/net/http_transport_test.cc
Normal file
242
util/net/http_transport_test.cc
Normal file
@ -0,0 +1,242 @@
|
||||
// Copyright 2014 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 <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/memory/scoped_ptr.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "build/build_config.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "util/file/fd_io.h"
|
||||
#include "util/net/http_body.h"
|
||||
#include "util/net/http_headers.h"
|
||||
#include "util/net/http_multipart_builder.h"
|
||||
#include "util/test/multiprocess_exec.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
class HTTPTransportTestFixture : public MultiprocessExec {
|
||||
public:
|
||||
using RequestValidator =
|
||||
void(*)(HTTPTransportTestFixture*, const std::string&);
|
||||
|
||||
HTTPTransportTestFixture(const HTTPHeaders& headers,
|
||||
scoped_ptr<HTTPBodyStream> body_stream,
|
||||
uint16_t http_response_code,
|
||||
RequestValidator request_validator)
|
||||
: MultiprocessExec(),
|
||||
headers_(headers),
|
||||
body_stream_(body_stream.Pass()),
|
||||
response_code_(http_response_code),
|
||||
request_validator_(request_validator) {
|
||||
// TODO(rsesek): Use a more robust mechanism to locate testdata
|
||||
// <https://code.google.com/p/crashpad/issues/detail?id=4>.
|
||||
SetChildCommand("util/net/http_transport_test_server.py", nullptr);
|
||||
}
|
||||
|
||||
const HTTPHeaders& headers() { return headers_; }
|
||||
|
||||
private:
|
||||
void MultiprocessParent() override {
|
||||
// The child will write the HTTP server port number as a packed unsigned
|
||||
// short to stdout.
|
||||
uint16_t port;
|
||||
CheckedReadFD(ReadPipeFD(), &port, sizeof(port));
|
||||
|
||||
// Then the parent will tell the web server what response code to send
|
||||
// for the HTTP request.
|
||||
CheckedWriteFD(WritePipeFD(), &response_code_, sizeof(response_code_));
|
||||
|
||||
// Now execute the HTTP request.
|
||||
scoped_ptr<HTTPTransport> transport(HTTPTransport::Create());
|
||||
transport->SetMethod("POST");
|
||||
transport->SetURL(base::StringPrintf("http://127.0.0.1:%d/upload", port));
|
||||
for (const auto& pair : headers_) {
|
||||
transport->SetHeader(pair.first, pair.second);
|
||||
}
|
||||
transport->SetBodyStream(body_stream_.Pass());
|
||||
|
||||
EXPECT_EQ(transport->ExecuteSynchronously(), (response_code_ == 200));
|
||||
|
||||
// Read until the child's stdout closes.
|
||||
std::string request;
|
||||
char buf[32];
|
||||
ssize_t bytes_read;
|
||||
while ((bytes_read = ReadFD(ReadPipeFD(), buf, sizeof(buf))) != 0) {
|
||||
ASSERT_GE(bytes_read, 0);
|
||||
request.append(buf, bytes_read);
|
||||
}
|
||||
|
||||
if (request_validator_)
|
||||
request_validator_(this, request);
|
||||
}
|
||||
|
||||
HTTPHeaders headers_;
|
||||
scoped_ptr<HTTPBodyStream> body_stream_;
|
||||
uint16_t response_code_;
|
||||
RequestValidator request_validator_;
|
||||
};
|
||||
|
||||
const char kMultipartFormData[] = "multipart/form-data";
|
||||
|
||||
void GetHeaderField(const std::string& request,
|
||||
const std::string& header,
|
||||
std::string* value) {
|
||||
size_t index = request.find(header);
|
||||
ASSERT_NE(std::string::npos, index);
|
||||
// Since the header is never the first line of the request, it should always
|
||||
// be preceded by a CRLF.
|
||||
EXPECT_EQ('\n', request[index - 1]);
|
||||
EXPECT_EQ('\r', request[index - 2]);
|
||||
|
||||
index += header.length();
|
||||
EXPECT_EQ(':', request[index++]);
|
||||
// Per RFC 7230 §3.2, there can be one or more spaces or horizontal tabs.
|
||||
// For testing purposes, just assume one space.
|
||||
EXPECT_EQ(' ', request[index++]);
|
||||
|
||||
size_t header_end = request.find('\r', index);
|
||||
ASSERT_NE(std::string::npos, header_end);
|
||||
|
||||
*value = request.substr(index, header_end - index);
|
||||
}
|
||||
|
||||
void GetMultipartBoundary(const std::string& request,
|
||||
std::string* multipart_boundary) {
|
||||
std::string content_type;
|
||||
GetHeaderField(request, kContentType, &content_type);
|
||||
|
||||
ASSERT_GE(content_type.length(), strlen(kMultipartFormData));
|
||||
size_t index = strlen(kMultipartFormData);
|
||||
EXPECT_EQ(kMultipartFormData, content_type.substr(0, index));
|
||||
|
||||
EXPECT_EQ(';', content_type[index++]);
|
||||
|
||||
size_t boundary_begin = content_type.find('=', index);
|
||||
ASSERT_NE(std::string::npos, boundary_begin);
|
||||
EXPECT_EQ('=', content_type[boundary_begin++]);
|
||||
if (multipart_boundary) {
|
||||
*multipart_boundary = content_type.substr(boundary_begin);
|
||||
}
|
||||
}
|
||||
|
||||
const char kBoundaryEq[] = "boundary=";
|
||||
|
||||
void ValidFormData(HTTPTransportTestFixture* fixture,
|
||||
const std::string& request) {
|
||||
std::string actual_boundary;
|
||||
GetMultipartBoundary(request, &actual_boundary);
|
||||
|
||||
const auto& content_type = fixture->headers().find(kContentType);
|
||||
ASSERT_NE(fixture->headers().end(), content_type);
|
||||
|
||||
size_t boundary = content_type->second.find(kBoundaryEq);
|
||||
ASSERT_NE(std::string::npos, boundary);
|
||||
std::string expected_boundary =
|
||||
content_type->second.substr(boundary + strlen(kBoundaryEq));
|
||||
EXPECT_EQ(expected_boundary, actual_boundary);
|
||||
|
||||
size_t body_start = request.find("\r\n\r\n");
|
||||
ASSERT_NE(std::string::npos, body_start);
|
||||
body_start += 4;
|
||||
|
||||
std::string expected = "--" + expected_boundary + "\r\n";
|
||||
expected += "Content-Disposition: form-data; name=\"key1\"\r\n\r\n";
|
||||
expected += "test\r\n";
|
||||
ASSERT_LT(body_start + expected.length(), request.length());
|
||||
EXPECT_EQ(expected, request.substr(body_start, expected.length()));
|
||||
|
||||
body_start += expected.length();
|
||||
|
||||
expected = "--" + expected_boundary + "\r\n";
|
||||
expected += "Content-Disposition: form-data; name=\"key2\"\r\n\r\n";
|
||||
expected += "--abcdefg123\r\n";
|
||||
expected += "--" + expected_boundary + "--\r\n";
|
||||
ASSERT_EQ(body_start + expected.length(), request.length());
|
||||
EXPECT_EQ(expected, request.substr(body_start));
|
||||
}
|
||||
|
||||
TEST(HTTPTransport, ValidFormData) {
|
||||
HTTPMultipartBuilder builder;
|
||||
builder.SetFormData("key1", "test");
|
||||
builder.SetFormData("key2", "--abcdefg123");
|
||||
|
||||
HTTPHeaders headers;
|
||||
headers.insert(builder.GetContentType());
|
||||
|
||||
HTTPTransportTestFixture test(headers, builder.GetBodyStream(), 200,
|
||||
&ValidFormData);
|
||||
test.Run();
|
||||
}
|
||||
|
||||
const char kTextPlain[] = "text/plain";
|
||||
|
||||
void ErrorResponse(HTTPTransportTestFixture* fixture,
|
||||
const std::string& request) {
|
||||
std::string content_type;
|
||||
GetHeaderField(request, kContentType, &content_type);
|
||||
EXPECT_EQ(kTextPlain, content_type);
|
||||
}
|
||||
|
||||
TEST(HTTPTransport, ErrorResponse) {
|
||||
HTTPMultipartBuilder builder;
|
||||
HTTPHeaders headers;
|
||||
headers[kContentType] = kTextPlain;
|
||||
HTTPTransportTestFixture test(headers, builder.GetBodyStream(),
|
||||
404, &ErrorResponse);
|
||||
test.Run();
|
||||
}
|
||||
|
||||
const char kTextBody[] = "hello world";
|
||||
|
||||
void UnchunkedPlainText(HTTPTransportTestFixture* fixture,
|
||||
const std::string& request) {
|
||||
std::string header_value;
|
||||
GetHeaderField(request, kContentType, &header_value);
|
||||
EXPECT_EQ(kTextPlain, header_value);
|
||||
|
||||
GetHeaderField(request, kContentLength, &header_value);
|
||||
const auto& content_length = fixture->headers().find(kContentLength);
|
||||
ASSERT_NE(fixture->headers().end(), content_length);
|
||||
EXPECT_EQ(content_length->second, header_value);
|
||||
|
||||
size_t body_start = request.rfind("\r\n");
|
||||
ASSERT_NE(std::string::npos, body_start);
|
||||
|
||||
EXPECT_EQ(kTextBody, request.substr(body_start + 2));
|
||||
}
|
||||
|
||||
TEST(HTTPTransport, UnchunkedPlainText) {
|
||||
scoped_ptr<HTTPBodyStream> body_stream(new StringHTTPBodyStream(kTextBody));
|
||||
|
||||
HTTPHeaders headers;
|
||||
headers[kContentType] = kTextPlain;
|
||||
headers[kContentLength] = base::StringPrintf("%zu", strlen(kTextBody));
|
||||
|
||||
HTTPTransportTestFixture test(headers, body_stream.Pass(), 200,
|
||||
&UnchunkedPlainText);
|
||||
test.Run();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
137
util/net/http_transport_test_server.py
Executable file
137
util/net/http_transport_test_server.py
Executable file
@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# Copyright 2014 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.
|
||||
|
||||
"""A one-shot testing webserver.
|
||||
|
||||
When invoked, this server will write an integer to stdout, indiciating on which
|
||||
port the server is listening. It will then read one integer from stdin,
|
||||
indiciating the response code to set for a request. The server will process
|
||||
one HTTP request, writing it out entirely to stdout, and will then terminate.
|
||||
|
||||
This server is written in Python since it provides a simple HTTP stack, and
|
||||
because parsing Chunked encoding is safer and easier in a memory-safe language.
|
||||
This could easily have been written in C++ instead.
|
||||
"""
|
||||
|
||||
import BaseHTTPServer
|
||||
import struct
|
||||
import sys
|
||||
|
||||
class BufferedReadFile(object):
|
||||
"""A File-like object that stores all read contents into a buffer."""
|
||||
|
||||
def __init__(self, real_file):
|
||||
self.file = real_file
|
||||
self.buffer = ""
|
||||
|
||||
def read(self, size=-1):
|
||||
buf = self.file.read(size)
|
||||
self.buffer += buf
|
||||
return buf
|
||||
|
||||
def readline(self, size=-1):
|
||||
buf = self.file.readline(size)
|
||||
self.buffer += buf
|
||||
return buf
|
||||
|
||||
def flush(self):
|
||||
self.file.flush()
|
||||
|
||||
def close(self):
|
||||
self.file.close()
|
||||
|
||||
|
||||
class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
response_code = 500
|
||||
|
||||
def handle_one_request(self):
|
||||
# Wrap the rfile in the buffering file object so that the raw header block
|
||||
# can be written to stdout after it is parsed.
|
||||
self.rfile = BufferedReadFile(self.rfile)
|
||||
BaseHTTPServer.BaseHTTPRequestHandler.handle_one_request(self)
|
||||
|
||||
def do_POST(self):
|
||||
writer = sys.stdout
|
||||
|
||||
writer.write(self.rfile.buffer)
|
||||
self.rfile.buffer = ''
|
||||
|
||||
if self.headers.get('Transfer-Encoding', '') == 'Chunked':
|
||||
body = self.handle_chunked_encoding()
|
||||
else:
|
||||
length = int(self.headers.get('Content-Length', -1))
|
||||
body = self.rfile.read(length)
|
||||
|
||||
self.send_response(self.response_code)
|
||||
writer.write(body)
|
||||
writer.flush()
|
||||
|
||||
def handle_chunked_encoding(self):
|
||||
"""This parses a "Transfer-Encoding: Chunked" body in accordance with
|
||||
RFC 7230 §4.1. This returns the result as a string.
|
||||
"""
|
||||
body = ''
|
||||
chunk_size = self.read_chunk_size()
|
||||
while chunk_size > 0:
|
||||
# Read the body.
|
||||
data = self.rfile.read(chunk_size)
|
||||
chunk_size -= len(data)
|
||||
body += data
|
||||
|
||||
# Finished reading this chunk.
|
||||
if chunk_size == 0:
|
||||
# Read through any trailer fields.
|
||||
trailer_line = self.rfile.readline()
|
||||
while trailer_line.strip() != '':
|
||||
trailer_line = self.rfile.readline()
|
||||
|
||||
# Read the chunk size.
|
||||
chunk_size = self.read_chunk_size()
|
||||
return body
|
||||
|
||||
def read_chunk_size(self):
|
||||
# Read the whole line, including the \r\n.
|
||||
chunk_size_and_ext_line = self.rfile.readline()
|
||||
# Look for a chunk extension.
|
||||
chunk_size_end = chunk_size_and_ext_line.find(';')
|
||||
if chunk_size_end == -1:
|
||||
# No chunk extensions; just encounter the end of line.
|
||||
chunk_size_end = chunk_size_and_ext_line.find('\r')
|
||||
if chunk_size_end == -1:
|
||||
self.send_response(400) # Bad request.
|
||||
return -1
|
||||
return int(chunk_size_and_ext_line[:chunk_size_end], base=16)
|
||||
|
||||
|
||||
def Main():
|
||||
# Start the server.
|
||||
server = BaseHTTPServer.HTTPServer(('127.0.0.1', 0), RequestHandler)
|
||||
|
||||
# Write the port as an unsigned short to the parent process.
|
||||
sys.stdout.write(struct.pack('=H', server.server_address[1]))
|
||||
sys.stdout.flush()
|
||||
|
||||
# Read the desired test response code as an unsigned short from the parent
|
||||
# process.
|
||||
RequestHandler.response_code = \
|
||||
struct.unpack('=H', sys.stdin.read(struct.calcsize('=H')))[0]
|
||||
|
||||
# Handle the request.
|
||||
server.handle_request()
|
||||
|
||||
if __name__ == '__main__':
|
||||
Main()
|
@ -71,8 +71,13 @@
|
||||
'misc/uuid.h',
|
||||
'net/http_body.cc',
|
||||
'net/http_body.h',
|
||||
'net/http_headers.cc',
|
||||
'net/http_headers.h',
|
||||
'net/http_multipart_builder.cc',
|
||||
'net/http_multipart_builder.h',
|
||||
'net/http_transport.cc',
|
||||
'net/http_transport.h',
|
||||
'net/http_transport_mac.mm',
|
||||
'numeric/checked_range.h',
|
||||
'numeric/in_range_cast.h',
|
||||
'numeric/int128.h',
|
||||
@ -220,6 +225,7 @@
|
||||
'net/http_body_test_util.cc',
|
||||
'net/http_body_test_util.h',
|
||||
'net/http_multipart_builder_test.cc',
|
||||
'net/http_transport_test.cc',
|
||||
'numeric/checked_range_test.cc',
|
||||
'numeric/in_range_cast_test.cc',
|
||||
'numeric/int128_test.cc',
|
||||
|
Loading…
x
Reference in New Issue
Block a user