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 "util/net/http_multipart_builder.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
@ -152,7 +153,7 @@ scoped_ptr<HTTPBodyStream> HTTPMultipartBuilder::GetBodyStream() {
|
|||||||
std::vector<HTTPBodyStream*> streams;
|
std::vector<HTTPBodyStream*> streams;
|
||||||
|
|
||||||
for (const auto& pair : form_data_) {
|
for (const auto& pair : form_data_) {
|
||||||
std::string field = GetFormDataBoundary(boundary(), pair.first);
|
std::string field = GetFormDataBoundary(boundary_, pair.first);
|
||||||
field += kBoundaryCRLF;
|
field += kBoundaryCRLF;
|
||||||
field += pair.second;
|
field += pair.second;
|
||||||
field += kCRLF;
|
field += kCRLF;
|
||||||
@ -161,7 +162,7 @@ scoped_ptr<HTTPBodyStream> HTTPMultipartBuilder::GetBodyStream() {
|
|||||||
|
|
||||||
for (const auto& pair : file_attachments_) {
|
for (const auto& pair : file_attachments_) {
|
||||||
const FileAttachment& attachment = pair.second;
|
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",
|
header += base::StringPrintf("; filename=\"%s\"%s",
|
||||||
attachment.filename.c_str(), kCRLF);
|
attachment.filename.c_str(), kCRLF);
|
||||||
header += base::StringPrintf("Content-Type: %s%s",
|
header += base::StringPrintf("Content-Type: %s%s",
|
||||||
@ -173,11 +174,17 @@ scoped_ptr<HTTPBodyStream> HTTPMultipartBuilder::GetBodyStream() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
streams.push_back(
|
streams.push_back(
|
||||||
new StringHTTPBodyStream("--" + boundary() + "--" + kCRLF));
|
new StringHTTPBodyStream("--" + boundary_ + "--" + kCRLF));
|
||||||
|
|
||||||
return scoped_ptr<HTTPBodyStream>(new CompositeHTTPBodyStream(streams));
|
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) {
|
void HTTPMultipartBuilder::EraseKey(const std::string& key) {
|
||||||
auto data_it = form_data_.find(key);
|
auto data_it = form_data_.find(key);
|
||||||
if (data_it != form_data_.end())
|
if (data_it != form_data_.end())
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include "base/basictypes.h"
|
#include "base/basictypes.h"
|
||||||
#include "base/files/file_path.h"
|
#include "base/files/file_path.h"
|
||||||
#include "base/memory/scoped_ptr.h"
|
#include "base/memory/scoped_ptr.h"
|
||||||
|
#include "util/net/http_headers.h"
|
||||||
|
|
||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
|
|
||||||
@ -63,8 +64,8 @@ class HTTPMultipartBuilder {
|
|||||||
//! \return A caller-owned HTTPBodyStream object.
|
//! \return A caller-owned HTTPBodyStream object.
|
||||||
scoped_ptr<HTTPBodyStream> GetBodyStream();
|
scoped_ptr<HTTPBodyStream> GetBodyStream();
|
||||||
|
|
||||||
//! \brief Gets the boundary that will be used in GetBodyStream().
|
//! \brief Gets the header pair for `"Content-Type"`.
|
||||||
std::string boundary() const { return boundary_; }
|
HTTPHeaders::value_type GetContentType() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct FileAttachment {
|
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',
|
'misc/uuid.h',
|
||||||
'net/http_body.cc',
|
'net/http_body.cc',
|
||||||
'net/http_body.h',
|
'net/http_body.h',
|
||||||
|
'net/http_headers.cc',
|
||||||
|
'net/http_headers.h',
|
||||||
'net/http_multipart_builder.cc',
|
'net/http_multipart_builder.cc',
|
||||||
'net/http_multipart_builder.h',
|
'net/http_multipart_builder.h',
|
||||||
|
'net/http_transport.cc',
|
||||||
|
'net/http_transport.h',
|
||||||
|
'net/http_transport_mac.mm',
|
||||||
'numeric/checked_range.h',
|
'numeric/checked_range.h',
|
||||||
'numeric/in_range_cast.h',
|
'numeric/in_range_cast.h',
|
||||||
'numeric/int128.h',
|
'numeric/int128.h',
|
||||||
@ -220,6 +225,7 @@
|
|||||||
'net/http_body_test_util.cc',
|
'net/http_body_test_util.cc',
|
||||||
'net/http_body_test_util.h',
|
'net/http_body_test_util.h',
|
||||||
'net/http_multipart_builder_test.cc',
|
'net/http_multipart_builder_test.cc',
|
||||||
|
'net/http_transport_test.cc',
|
||||||
'numeric/checked_range_test.cc',
|
'numeric/checked_range_test.cc',
|
||||||
'numeric/in_range_cast_test.cc',
|
'numeric/in_range_cast_test.cc',
|
||||||
'numeric/int128_test.cc',
|
'numeric/int128_test.cc',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user