update
This commit is contained in:
397
Pods/CocoaDebug/Sources/CustomHTTPProtocol/_CanonicalRequest.m
generated
Normal file
397
Pods/CocoaDebug/Sources/CustomHTTPProtocol/_CanonicalRequest.m
generated
Normal file
@@ -0,0 +1,397 @@
|
||||
//
|
||||
// Example
|
||||
// man
|
||||
//
|
||||
// Created by man 11/11/2018.
|
||||
// Copyright © 2020 man. All rights reserved.
|
||||
//
|
||||
|
||||
#import "_CanonicalRequest.h"
|
||||
|
||||
#include <xlocale.h>
|
||||
|
||||
#pragma mark * URL canonicalization steps
|
||||
|
||||
/*! A step in the canonicalisation process.
|
||||
* \details The canonicalisation process is made up of a sequence of steps, each of which is
|
||||
* implemented by a function that matches this function pointer. The function gets a URL
|
||||
* and a mutable buffer holding that URL as bytes. The function can mutate the buffer as it
|
||||
* sees fit. It typically does this by calling CFURLGetByteRangeForComponent to find the range
|
||||
* of interest in the buffer. In that case bytesInserted is the amount to adjust that range,
|
||||
* and the function should modify that to account for any bytes it inserts or deletes. If
|
||||
* the function modifies the buffer too much, it can return kCFNotFound to force the system
|
||||
* to re-create the URL from the buffer.
|
||||
* \param url The original URL to work on.
|
||||
* \param urlData The URL as a mutable buffer; the routine modifies this.
|
||||
* \param bytesInserted The number of bytes that have been inserted so far the mutable buffer.
|
||||
* \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed.
|
||||
*/
|
||||
|
||||
typedef CFIndex (*CanonicalRequestStepFunction)(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted);
|
||||
|
||||
/*! The post-scheme separate should be "://"; if that's not the case, fix it.
|
||||
* \param url The original URL to work on.
|
||||
* \param urlData The URL as a mutable buffer; the routine modifies this.
|
||||
* \param bytesInserted The number of bytes that have been inserted so far the mutable buffer.
|
||||
* \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed.
|
||||
*/
|
||||
|
||||
static CFIndex FixPostSchemeSeparator(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted)
|
||||
{
|
||||
CFRange range;
|
||||
uint8_t * urlDataBytes;
|
||||
NSUInteger urlDataLength;
|
||||
NSUInteger cursor;
|
||||
NSUInteger separatorLength;
|
||||
NSUInteger expectedSeparatorLength;
|
||||
|
||||
//assert(url != nil);
|
||||
//assert(urlData != nil);
|
||||
//assert(bytesInserted >= 0);
|
||||
|
||||
range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentScheme, NULL);
|
||||
if (range.location != kCFNotFound) {
|
||||
//assert(range.location >= 0);
|
||||
//assert(range.length >= 0);
|
||||
|
||||
urlDataBytes = [urlData mutableBytes];
|
||||
urlDataLength = [urlData length];
|
||||
|
||||
separatorLength = 0;
|
||||
cursor = (NSUInteger) range.location + (NSUInteger) bytesInserted + (NSUInteger) range.length;
|
||||
if ( (cursor < urlDataLength) && (urlDataBytes[cursor] == ':') ) {
|
||||
cursor += 1;
|
||||
separatorLength += 1;
|
||||
if ( (cursor < urlDataLength) && (urlDataBytes[cursor] == '/') ) {
|
||||
cursor += 1;
|
||||
separatorLength += 1;
|
||||
if ( (cursor < urlDataLength) && (urlDataBytes[cursor] == '/') ) {
|
||||
cursor += 1;
|
||||
separatorLength += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma unused(cursor) // quietens an analyser warning
|
||||
|
||||
expectedSeparatorLength = strlen("://");
|
||||
if (separatorLength != expectedSeparatorLength) {
|
||||
[urlData replaceBytesInRange:NSMakeRange((NSUInteger) range.location + (NSUInteger) bytesInserted + (NSUInteger) range.length, separatorLength) withBytes:"://" length:expectedSeparatorLength];
|
||||
bytesInserted = kCFNotFound; // have to build everything now
|
||||
}
|
||||
}
|
||||
|
||||
return bytesInserted;
|
||||
}
|
||||
|
||||
/*! The scheme should be lower case; if it's not, make it so.
|
||||
* \param url The original URL to work on.
|
||||
* \param urlData The URL as a mutable buffer; the routine modifies this.
|
||||
* \param bytesInserted The number of bytes that have been inserted so far the mutable buffer.
|
||||
* \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed.
|
||||
*/
|
||||
|
||||
static CFIndex LowercaseScheme(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted)
|
||||
{
|
||||
CFRange range;
|
||||
uint8_t * urlDataBytes;
|
||||
CFIndex i;
|
||||
|
||||
//assert(url != nil);
|
||||
//assert(urlData != nil);
|
||||
//assert(bytesInserted >= 0);
|
||||
|
||||
range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentScheme, NULL);
|
||||
if (range.location != kCFNotFound) {
|
||||
//assert(range.location >= 0);
|
||||
//assert(range.length >= 0);
|
||||
|
||||
urlDataBytes = [urlData mutableBytes];
|
||||
for (i = range.location + bytesInserted; i < (range.location + bytesInserted + range.length); i++) {
|
||||
urlDataBytes[i] = (uint8_t) tolower_l(urlDataBytes[i], NULL);
|
||||
}
|
||||
}
|
||||
return bytesInserted;
|
||||
}
|
||||
|
||||
/*! The host should be lower case; if it's not, make it so.
|
||||
* \param url The original URL to work on.
|
||||
* \param urlData The URL as a mutable buffer; the routine modifies this.
|
||||
* \param bytesInserted The number of bytes that have been inserted so far the mutable buffer.
|
||||
* \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed.
|
||||
*/
|
||||
|
||||
static CFIndex LowercaseHost(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted)
|
||||
// The host should be lower case; if it's not, make it so.
|
||||
{
|
||||
CFRange range;
|
||||
uint8_t * urlDataBytes;
|
||||
CFIndex i;
|
||||
|
||||
//assert(url != nil);
|
||||
//assert(urlData != nil);
|
||||
//assert(bytesInserted >= 0);
|
||||
|
||||
range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentHost, NULL);
|
||||
if (range.location != kCFNotFound) {
|
||||
//assert(range.location >= 0);
|
||||
//assert(range.length >= 0);
|
||||
|
||||
urlDataBytes = [urlData mutableBytes];
|
||||
for (i = range.location + bytesInserted; i < (range.location + bytesInserted + range.length); i++) {
|
||||
urlDataBytes[i] = (uint8_t) tolower_l(urlDataBytes[i], NULL);
|
||||
}
|
||||
}
|
||||
return bytesInserted;
|
||||
}
|
||||
|
||||
/*! An empty host should be treated as "localhost" case; if it's not, make it so.
|
||||
* \param url The original URL to work on.
|
||||
* \param urlData The URL as a mutable buffer; the routine modifies this.
|
||||
* \param bytesInserted The number of bytes that have been inserted so far the mutable buffer.
|
||||
* \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed.
|
||||
*/
|
||||
|
||||
static CFIndex FixEmptyHost(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted)
|
||||
{
|
||||
CFRange range;
|
||||
CFRange rangeWithSeparator;
|
||||
|
||||
//assert(url != nil);
|
||||
//assert(urlData != nil);
|
||||
//assert(bytesInserted >= 0);
|
||||
|
||||
range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentHost, &rangeWithSeparator);
|
||||
if (range.length == 0) {
|
||||
NSUInteger localhostLength;
|
||||
|
||||
//assert(range.location >= 0);
|
||||
//assert(range.length >= 0);
|
||||
|
||||
localhostLength = strlen("localhost");
|
||||
if (range.location != kCFNotFound) {
|
||||
[urlData replaceBytesInRange:NSMakeRange( (NSUInteger) range.location + (NSUInteger) bytesInserted, 0) withBytes:"localhost" length:localhostLength];
|
||||
bytesInserted += localhostLength;
|
||||
} else if ( (rangeWithSeparator.location != kCFNotFound) && (rangeWithSeparator.length == 0) ) {
|
||||
[urlData replaceBytesInRange:NSMakeRange((NSUInteger) rangeWithSeparator.location + (NSUInteger) bytesInserted, 0) withBytes:"localhost" length:localhostLength];
|
||||
bytesInserted += localhostLength;
|
||||
}
|
||||
}
|
||||
return bytesInserted;
|
||||
}
|
||||
|
||||
/*! Transform an empty URL path to "/". For example, "http://www.apple.com" becomes "http://www.apple.com/".
|
||||
* \param url The original URL to work on.
|
||||
* \param urlData The URL as a mutable buffer; the routine modifies this.
|
||||
* \param bytesInserted The number of bytes that have been inserted so far the mutable buffer.
|
||||
* \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed.
|
||||
*/
|
||||
|
||||
static CFIndex FixEmptyPath(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted)
|
||||
{
|
||||
CFRange range;
|
||||
CFRange rangeWithSeparator;
|
||||
|
||||
//assert(url != nil);
|
||||
//assert(urlData != nil);
|
||||
//assert(bytesInserted >= 0);
|
||||
|
||||
range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentPath, &rangeWithSeparator);
|
||||
// The following is not a typo. We use rangeWithSeparator to find where to insert the
|
||||
// "/" and the range length to decide whether we /need/ to insert the "/".
|
||||
if ( (rangeWithSeparator.location != kCFNotFound) && (range.length == 0) ) {
|
||||
//assert(range.location >= 0);
|
||||
//assert(range.length >= 0);
|
||||
//assert(rangeWithSeparator.location >= 0);
|
||||
//assert(rangeWithSeparator.length >= 0);
|
||||
|
||||
[urlData replaceBytesInRange:NSMakeRange( (NSUInteger) rangeWithSeparator.location + (NSUInteger) bytesInserted, 0) withBytes:"/" length:1];
|
||||
bytesInserted += 1;
|
||||
}
|
||||
return bytesInserted;
|
||||
}
|
||||
|
||||
/*! If the user specified the default port (80 for HTTP, 443 for HTTPS), remove it from the URL.
|
||||
* \details Actually this code is disabled because the equivalent code in the default protocol
|
||||
* handler has also been disabled; some setups depend on get the port number in the URL, even if it
|
||||
* is the default.
|
||||
* \param url The original URL to work on.
|
||||
* \param urlData The URL as a mutable buffer; the routine modifies this.
|
||||
* \param bytesInserted The number of bytes that have been inserted so far the mutable buffer.
|
||||
* \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed.
|
||||
*/
|
||||
|
||||
__attribute__((unused)) static CFIndex DeleteDefaultPort(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted)
|
||||
{
|
||||
NSString * scheme;
|
||||
BOOL isHTTP;
|
||||
BOOL isHTTPS;
|
||||
CFRange range;
|
||||
uint8_t * urlDataBytes;
|
||||
NSString * portNumberStr;
|
||||
int portNumber;
|
||||
|
||||
//assert(url != nil);
|
||||
//assert(urlData != nil);
|
||||
//assert(bytesInserted >= 0);
|
||||
|
||||
scheme = [[url scheme] lowercaseString];
|
||||
//assert(scheme != nil);
|
||||
|
||||
isHTTP = [scheme isEqual:@"http" ];
|
||||
isHTTPS = [scheme isEqual:@"https"];
|
||||
|
||||
range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentPort, NULL);
|
||||
if (range.location != kCFNotFound) {
|
||||
//assert(range.location >= 0);
|
||||
//assert(range.length >= 0);
|
||||
|
||||
urlDataBytes = [urlData mutableBytes];
|
||||
|
||||
portNumberStr = [[NSString alloc] initWithBytes:&urlDataBytes[range.location + bytesInserted] length:(NSUInteger) range.length encoding:NSUTF8StringEncoding];
|
||||
if (portNumberStr != nil) {
|
||||
portNumber = [portNumberStr intValue];
|
||||
if ( (isHTTP && (portNumber == 80)) || (isHTTPS && (portNumber == 443)) ) {
|
||||
// -1 and +1 to account for the leading ":"
|
||||
[urlData replaceBytesInRange:NSMakeRange((NSUInteger) range.location + (NSUInteger) bytesInserted - 1, (NSUInteger) range.length + 1) withBytes:NULL length:0];
|
||||
bytesInserted -= (range.length + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return bytesInserted;
|
||||
}
|
||||
|
||||
#pragma mark * Other request canonicalization
|
||||
|
||||
/*! Canonicalise the request headers.
|
||||
* \param request The request to canonicalise.
|
||||
*/
|
||||
|
||||
static void CanonicaliseHeaders(NSMutableURLRequest * request)
|
||||
{
|
||||
// If there's no content type and the request is a POST with a body, add a default
|
||||
// content type of "application/x-www-form-urlencoded".
|
||||
|
||||
if ( ([request valueForHTTPHeaderField:@"Content-Type"] == nil)
|
||||
&& ([[request HTTPMethod] caseInsensitiveCompare:@"POST"] == NSOrderedSame)
|
||||
&& (([request HTTPBody] != nil) || ([request HTTPBodyStream] != nil)) ) {
|
||||
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
|
||||
}
|
||||
|
||||
// If there's no "Accept" header, add a default.
|
||||
|
||||
if ([request valueForHTTPHeaderField:@"Accept"] == nil) {
|
||||
[request setValue:@"*/*" forHTTPHeaderField:@"Accept"];
|
||||
}
|
||||
|
||||
// If there's not "Accept-Encoding" header, add a default.
|
||||
|
||||
if ([request valueForHTTPHeaderField:@"Accept-Encoding"] == nil) {
|
||||
[request setValue:@"gzip, deflate" forHTTPHeaderField:@"Accept-Encoding"];
|
||||
}
|
||||
|
||||
// If there's not an "Accept-Language" headre, add a default. This is quite bogus; ideally we
|
||||
// should derive the correct "Accept-Language" value from the langauge that the app is running
|
||||
// in. However, that's quite difficult to get right, so rather than show some general purpose
|
||||
// code that might fail in some circumstances, I've decided to just hardwire US English.
|
||||
// If you use this code in your own app you can customise it as you see fit. One option might be
|
||||
// to base this value on -[NSBundle preferredLocalizations], so that the web page comes back in
|
||||
// the language that the app is running in.
|
||||
|
||||
if ([request valueForHTTPHeaderField:@"Accept-Language"] == nil) {
|
||||
[request setValue:@"en-us" forHTTPHeaderField:@"Accept-Language"];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark * API
|
||||
|
||||
extern NSMutableURLRequest * CanonicalRequestForRequest(NSURLRequest *request)
|
||||
{
|
||||
NSMutableURLRequest * result;
|
||||
NSString * scheme;
|
||||
|
||||
//assert(request != nil);
|
||||
|
||||
// Make a mutable copy of the request.
|
||||
|
||||
result = [request mutableCopy];
|
||||
|
||||
// First up check that we're dealing with HTTP or HTTPS. If not, do nothing (why were we
|
||||
// we even called?).
|
||||
|
||||
scheme = [[[request URL] scheme] lowercaseString];
|
||||
//assert(scheme != nil);
|
||||
|
||||
if ( ! [scheme isEqual:@"http" ] && ! [scheme isEqual:@"https"]) {
|
||||
//assert(NO);
|
||||
} else {
|
||||
CFIndex bytesInserted;
|
||||
NSURL * requestURL;
|
||||
NSMutableData * urlData;
|
||||
static const CanonicalRequestStepFunction kStepFunctions[] = {
|
||||
FixPostSchemeSeparator,
|
||||
LowercaseScheme,
|
||||
LowercaseHost,
|
||||
FixEmptyHost,
|
||||
// DeleteDefaultPort, -- The built-in canonicalizer has stopped doing this, so we don't do it either.
|
||||
FixEmptyPath
|
||||
};
|
||||
size_t stepIndex;
|
||||
size_t stepCount;
|
||||
|
||||
// Canonicalise the URL by executing each of our step functions.
|
||||
|
||||
bytesInserted = kCFNotFound;
|
||||
urlData = nil;
|
||||
requestURL = [request URL];
|
||||
//assert(requestURL != nil);
|
||||
|
||||
stepCount = sizeof(kStepFunctions) / sizeof(*kStepFunctions);
|
||||
for (stepIndex = 0; stepIndex < stepCount; stepIndex++) {
|
||||
|
||||
// If we don't have valid URL data, create it from the URL.
|
||||
|
||||
//assert(requestURL != nil);
|
||||
if (bytesInserted == kCFNotFound) {
|
||||
NSData * urlDataImmutable;
|
||||
|
||||
urlDataImmutable = CFBridgingRelease( CFURLCreateData(NULL, (CFURLRef) requestURL, kCFStringEncodingUTF8, true) );
|
||||
//assert(urlDataImmutable != nil);
|
||||
|
||||
urlData = [urlDataImmutable mutableCopy];
|
||||
//assert(urlData != nil);
|
||||
|
||||
bytesInserted = 0;
|
||||
}
|
||||
//assert(urlData != nil);
|
||||
|
||||
// Run the step.
|
||||
|
||||
bytesInserted = kStepFunctions[stepIndex](requestURL, urlData, bytesInserted);
|
||||
|
||||
// Note: The following logging is useful when debugging this code. Change the
|
||||
// if expression to YES to enable it.
|
||||
|
||||
if (/* DISABLES CODE */ (NO)) {
|
||||
// fprintf(stderr, " [%zu] %.*s\n", stepIndex, (int) [urlData length], (const char *) [urlData bytes]);
|
||||
}
|
||||
|
||||
// If the step invalidated our URL (or we're on the last step, whereupon we'll need
|
||||
// the URL outside of the loop), recreate the URL from the URL data.
|
||||
|
||||
if ( (bytesInserted == kCFNotFound) || ((stepIndex + 1) == stepCount) ) {
|
||||
requestURL = CFBridgingRelease( CFURLCreateWithBytes(NULL, [urlData bytes], (CFIndex) [urlData length], kCFStringEncodingUTF8, NULL) );
|
||||
//assert(requestURL != nil);
|
||||
|
||||
urlData = nil;
|
||||
}
|
||||
}
|
||||
|
||||
[result setURL:requestURL];
|
||||
|
||||
// Canonicalise the headers.
|
||||
|
||||
CanonicaliseHeaders(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user