crashpad/util/net/http_transport_mac.mm

319 строки
10 KiB
Plaintext
Исходник Постоянная ссылка Ответственный История

Этот файл содержит невидимые символы Юникода!

Этот файл содержит невидимые символы Юникода, которые могут быть отображены не так, как показано ниже. Если это намеренно, можете спокойно проигнорировать это предупреждение. Используйте кнопку Экранировать, чтобы показать скрытые символы.

Этот файл содержит неоднозначные символы Юникода, которые могут быть перепутаны с другими в текущей локали. Если это намеренно, можете спокойно проигнорировать это предупреждение. Используйте кнопку Экранировать, чтобы подсветить эти символы.

// 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 <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>
#include <sys/utsname.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 "build/build_config.h"
#include "package.h"
#include "third_party/apple_cf/CFStreamAbstract.h"
#include "util/file/file_io.h"
#include "util/misc/implicit_cast.h"
#include "util/net/http_body.h"
namespace crashpad {
namespace {
NSString* AppendEscapedFormat(NSString* base,
NSString* format,
NSString* data) {
return [base stringByAppendingFormat:
format,
[data stringByAddingPercentEncodingWithAllowedCharacters:
[[NSCharacterSet
characterSetWithCharactersInString:
@"()<>@,;:\\\"/[]?={} \t"] invertedSet]]];
}
// This builds the same User-Agent string that CFNetwork would build internally,
// but it uses PACKAGE_NAME and PACKAGE_VERSION in place of values obtained from
// the main bundles Info.plist.
NSString* UserAgentString() {
NSString* user_agent = [NSString string];
// CFNetwork would use the main bundles CFBundleName, or the main
// executables filename if none.
user_agent = AppendEscapedFormat(
user_agent, @"%@", [NSString stringWithUTF8String:PACKAGE_NAME]);
// CFNetwork would use the main bundles CFBundleVersion, or the string
// “(unknown version)” if none.
user_agent = AppendEscapedFormat(
user_agent, @"/%@", [NSString stringWithUTF8String:PACKAGE_VERSION]);
// Expected to be CFNetwork.
NSBundle* nsurl_bundle = [NSBundle bundleForClass:[NSURLRequest class]];
NSString* bundle_name = base::mac::ObjCCast<NSString>([nsurl_bundle
objectForInfoDictionaryKey:base::mac::CFToNSCast(kCFBundleNameKey)]);
if (bundle_name) {
user_agent = AppendEscapedFormat(user_agent, @" %@", bundle_name);
NSString* bundle_version = base::mac::ObjCCast<NSString>([nsurl_bundle
objectForInfoDictionaryKey:base::mac::CFToNSCast(kCFBundleVersionKey)]);
if (bundle_version) {
user_agent = AppendEscapedFormat(user_agent, @"/%@", bundle_version);
}
}
utsname os;
if (uname(&os) != 0) {
PLOG(WARNING) << "uname";
} else {
user_agent = AppendEscapedFormat(
user_agent, @" %@", [NSString stringWithUTF8String:os.sysname]);
user_agent = AppendEscapedFormat(
user_agent, @"/%@", [NSString stringWithUTF8String:os.release]);
// CFNetwork just uses the equivalent of os.machine to obtain the native
// (kernel) architecture. Here, give the process architecture as well as
// the native architecture. Use the same strings that the kernel would, so
// that they can be de-duplicated.
#if defined(ARCH_CPU_X86)
NSString* arch = @"i386";
#elif defined(ARCH_CPU_X86_64)
NSString* arch = @"x86_64";
#else
#error Port
#endif
user_agent = AppendEscapedFormat(user_agent, @" (%@", arch);
NSString* machine = [NSString stringWithUTF8String:os.machine];
if (![machine isEqualToString:arch]) {
user_agent = AppendEscapedFormat(user_agent, @"; %@", machine);
}
user_agent = [user_agent stringByAppendingString:@")"];
}
return user_agent;
}
// An implementation of CFReadStream. This implements the V0 callback
// scheme.
class HTTPBodyStreamCFReadStream {
public:
explicit HTTPBodyStreamCFReadStream(HTTPBodyStream* body_stream)
: body_stream_(body_stream) {
}
// Creates a new NSInputStream, which the caller owns.
NSInputStream* CreateInputStream() {
CFStreamClientContext context = {
.version = 0,
.info = this,
.retain = nullptr,
.release = nullptr,
.copyDescription = nullptr
};
const CFReadStreamCallBacksV0 callbacks = {
.version = 0,
.open = &Open,
.openCompleted = &OpenCompleted,
.read = &Read,
.getBuffer = &GetBuffer,
.canRead = &CanRead,
.close = &Close,
.copyProperty = &CopyProperty,
.schedule = &Schedule,
.unschedule = &Unschedule
};
CFReadStreamRef read_stream = CFReadStreamCreate(nullptr,
reinterpret_cast<const CFReadStreamCallBacks*>(&callbacks), &context);
return base::mac::CFToNSCast(read_stream);
}
private:
static HTTPBodyStream* GetStream(void* info) {
return static_cast<HTTPBodyStreamCFReadStream*>(info)->body_stream_;
}
static Boolean Open(CFReadStreamRef stream,
CFStreamError* error,
Boolean* open_complete,
void* info) {
*open_complete = TRUE;
return TRUE;
}
static Boolean OpenCompleted(CFReadStreamRef stream,
CFStreamError* error,
void* info) {
return TRUE;
}
static CFIndex Read(CFReadStreamRef stream,
UInt8* buffer,
CFIndex buffer_length,
CFStreamError* error,
Boolean* at_eof,
void* info) {
if (buffer_length == 0) {
*at_eof = FALSE;
return 0;
}
FileOperationResult bytes_read =
GetStream(info)->GetBytesBuffer(buffer, buffer_length);
if (bytes_read < 0) {
error->error = -1;
error->domain = kCFStreamErrorDomainCustom;
} else {
*at_eof = bytes_read == 0;
}
return bytes_read;
}
static const UInt8* GetBuffer(CFReadStreamRef stream,
CFIndex max_bytes_to_read,
CFIndex* num_bytes_read,
CFStreamError* error,
Boolean* at_eof,
void* info) {
return nullptr;
}
static Boolean CanRead(CFReadStreamRef stream, void* info) {
return TRUE;
}
static void Close(CFReadStreamRef stream, void* info) {}
static CFTypeRef CopyProperty(CFReadStreamRef stream,
CFStringRef property_name,
void* info) {
return nullptr;
}
static void Schedule(CFReadStreamRef stream,
CFRunLoopRef run_loop,
CFStringRef run_loop_mode,
void* info) {}
static void Unschedule(CFReadStreamRef stream,
CFRunLoopRef run_loop,
CFStringRef run_loop_mode,
void* info) {}
HTTPBodyStream* body_stream_; // weak
DISALLOW_COPY_AND_ASSIGN(HTTPBodyStreamCFReadStream);
};
class HTTPTransportMac final : public HTTPTransport {
public:
HTTPTransportMac();
~HTTPTransportMac() override;
bool ExecuteSynchronously(std::string* response_body) override;
private:
DISALLOW_COPY_AND_ASSIGN(HTTPTransportMac);
};
HTTPTransportMac::HTTPTransportMac() : HTTPTransport() {
}
HTTPTransportMac::~HTTPTransportMac() {
}
bool HTTPTransportMac::ExecuteSynchronously(std::string* response_body) {
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())];
// If left to its own devices, CFNetwork would build a user-agent string
// based on keys in the main bundles Info.plist, giving ugly results if
// there is no Info.plist. Provide a User-Agent string similar to the one
// that CFNetwork would use, but with appropriate values in place of the
// Info.plist-derived strings.
[request setValue:UserAgentString() forHTTPHeaderField:@"User-Agent"];
for (const auto& pair : headers()) {
[request setValue:base::SysUTF8ToNSString(pair.second)
forHTTPHeaderField:base::SysUTF8ToNSString(pair.first)];
}
HTTPBodyStreamCFReadStream body_stream_cf(body_stream());
base::scoped_nsobject<NSInputStream> input_stream(
body_stream_cf.CreateInputStream());
[request setHTTPBodyStream:input_stream.get()];
NSURLResponse* response = nil;
NSError* error = nil;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// Deprecated in OS X 10.11. The suggested replacement, NSURLSession, is
// only available on 10.9 and later, and this needs to run on earlier
// releases.
NSData* body = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
#pragma clang diagnostic pop
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 || http_status >= 300) {
LOG(ERROR) << base::StringPrintf("HTTP status %ld",
implicit_cast<long>(http_status));
return false;
}
if (response_body) {
response_body->assign(static_cast<const char*>([body bytes]),
[body length]);
}
return true;
}
}
} // namespace
// static
std::unique_ptr<HTTPTransport> HTTPTransport::Create() {
return std::unique_ptr<HTTPTransport>(new HTTPTransportMac());
}
} // namespace crashpad