// 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 #import #include #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 bundle’s Info.plist. NSString* UserAgentString() { NSString* user_agent = [NSString string]; // CFNetwork would use the main bundle’s CFBundleName, or the main // executable’s filename if none. user_agent = AppendEscapedFormat(user_agent, @"%@", @PACKAGE_NAME); // CFNetwork would use the main bundle’s CFBundleVersion, or the string // “(unknown version)” if none. user_agent = AppendEscapedFormat(user_agent, @"/%@", @PACKAGE_VERSION); // Expected to be CFNetwork. NSBundle* nsurl_bundle = [NSBundle bundleForClass:[NSURLRequest class]]; NSString* bundle_name = base::mac::ObjCCast([nsurl_bundle objectForInfoDictionaryKey:base::mac::CFToNSCast(kCFBundleNameKey)]); if (bundle_name) { user_agent = AppendEscapedFormat(user_agent, @" %@", bundle_name); NSString* bundle_version = base::mac::ObjCCast([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, @" %@", @(os.sysname)); user_agent = AppendEscapedFormat(user_agent, @"/%@", @(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"; #elif defined(ARCH_CPU_ARM64) NSString* arch = @"arm64"; #else #error Port #endif user_agent = AppendEscapedFormat(user_agent, @" (%@", arch); NSString* machine = @(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 }; constexpr 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(&callbacks), &context); return base::mac::CFToNSCast(read_stream); } private: static HTTPBodyStream* GetStream(void* info) { return static_cast(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 bundle’s 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 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(response); if (!http_response) { LOG(ERROR) << "no http_response"; return false; } NSInteger http_status = [http_response statusCode]; if (http_status < 200 || http_status > 203) { LOG(ERROR) << base::StringPrintf("HTTP status %ld", implicit_cast(http_status)); return false; } if (response_body) { response_body->assign(static_cast([body bytes]), [body length]); } return true; } } } // namespace // static std::unique_ptr HTTPTransport::Create() { return std::unique_ptr(new HTTPTransportMac()); } } // namespace crashpad