Repair to Code Final Commit

This commit is contained in:
Alexander Davis
2017-05-06 02:17:51 +01:00
parent 219378c12f
commit d2046d4c7c
752 changed files with 65519 additions and 222 deletions

202
Code Burner/Pods/GTMSessionFetcher/LICENSE generated Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@@ -0,0 +1,23 @@
# Google Toolbox for Mac - Session Fetcher #
**Project site** <https://github.com/google/gtm-session-fetcher><br>
**Discussion group** <http://groups.google.com/group/google-toolbox-for-mac>
[![Build Status](https://travis-ci.org/google/gtm-session-fetcher.svg?branch=master)](https://travis-ci.org/google/gtm-session-fetcher)
`GTMSessionFetcher` makes it easy for Cocoa applications to perform http
operations. The fetcher is implemented as a wrapper on `NSURLSession`, so its
behavior is asynchronous and uses operating-system settings on iOS and Mac OS X.
Features include:
- Simple to build; only one source/header file pair is required
- Simple to use: takes just two lines of code to fetch a request
- Supports upload and download sessions
- Flexible cookie storage
- Automatic retry on errors, with exponential backoff
- Support for generating multipart MIME upload streams
- Easy, convenient logging of http requests and responses
- Supports plug-in authentication such as with gtm-oauth2
- Easily testable; self-mocking
- Automatic rate limiting when created by the `GTMSessionFetcherService` factory class
- Fully independent of other projects

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,107 @@
/* Copyright 2014 Google Inc. 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.
*/
#import "GTMSessionFetcher.h"
// GTM HTTP Logging
//
// All traffic using GTMSessionFetcher can be easily logged. Call
//
// [GTMSessionFetcher setLoggingEnabled:YES];
//
// to begin generating log files.
//
// Log files are put into a folder on the desktop called "GTMHTTPDebugLogs"
// unless another directory is specified with +setLoggingDirectory.
//
// In the iPhone simulator, the default logs location is the user's home
// directory in ~/Library/Application Support. On the iPhone device, the
// default logs location is the application's documents directory on the device.
//
// Tip: use the Finder's "Sort By Date" to find the most recent logs.
//
// Each run of an application gets a separate set of log files. An html
// file is generated to simplify browsing the run's http transactions.
// The html file includes javascript links for inline viewing of uploaded
// and downloaded data.
//
// A symlink is created in the logs folder to simplify finding the html file
// for the latest run of the application; the symlink is called
//
// AppName_http_log_newest.html
//
// For better viewing of XML logs, use Camino or Firefox rather than Safari.
//
// Each fetcher may be given a comment to be inserted as a label in the logs,
// such as
// [fetcher setCommentWithFormat:@"retrieve item %@", itemName];
//
// Projects may define STRIP_GTM_FETCH_LOGGING to remove logging code.
#if !STRIP_GTM_FETCH_LOGGING
@interface GTMSessionFetcher (GTMSessionFetcherLogging)
// Note: the default logs directory is ~/Desktop/GTMHTTPDebugLogs; it will be
// created as needed. If a custom directory is set, the directory should
// already exist.
+ (void)setLoggingDirectory:(NSString *)path;
+ (NSString *)loggingDirectory;
// client apps can turn logging on and off
+ (void)setLoggingEnabled:(BOOL)isLoggingEnabled;
+ (BOOL)isLoggingEnabled;
// client apps can turn off logging to a file if they want to only check
// the fetcher's log property
+ (void)setLoggingToFileEnabled:(BOOL)isLoggingToFileEnabled;
+ (BOOL)isLoggingToFileEnabled;
// client apps can optionally specify process name and date string used in
// log file names
+ (void)setLoggingProcessName:(NSString *)processName;
+ (NSString *)loggingProcessName;
+ (void)setLoggingDateStamp:(NSString *)dateStamp;
+ (NSString *)loggingDateStamp;
// client apps can specify the directory for the log for this specific run,
// typically to match the directory used by another fetcher class, like:
//
// [GTMSessionFetcher setLogDirectoryForCurrentRun:[GTMHTTPFetcher logDirectoryForCurrentRun]];
//
// Setting this overrides the logging directory, process name, and date stamp when writing
// the log file.
+ (void)setLogDirectoryForCurrentRun:(NSString *)logDirectoryForCurrentRun;
+ (NSString *)logDirectoryForCurrentRun;
// Prunes old log directories that have not been modified since the provided date.
// This will not delete the current run's log directory.
+ (void)deleteLogDirectoriesOlderThanDate:(NSDate *)date;
// internal; called by fetcher
- (void)logFetchWithError:(NSError *)error;
- (NSInputStream *)loggedInputStreamForInputStream:(NSInputStream *)inputStream;
- (GTMSessionFetcherBodyStreamProvider)loggedStreamProviderForStreamProvider:
(GTMSessionFetcherBodyStreamProvider)streamProvider;
// internal; accessors useful for viewing logs
+ (NSString *)processNameLogPrefix;
+ (NSString *)symlinkNameSuffix;
+ (NSString *)htmlFileName;
@end
#endif // !STRIP_GTM_FETCH_LOGGING

View File

@@ -0,0 +1,976 @@
/* Copyright 2014 Google Inc. 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.
*/
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#include <sys/stat.h>
#include <unistd.h>
#import "GTMSessionFetcherLogging.h"
#ifndef STRIP_GTM_FETCH_LOGGING
#error GTMSessionFetcher headers should have defaulted this if it wasn't already defined.
#endif
#if !STRIP_GTM_FETCH_LOGGING
// Sensitive credential strings are replaced in logs with _snip_
//
// Apps that must see the contents of sensitive tokens can set this to 1
#ifndef SKIP_GTM_FETCH_LOGGING_SNIPPING
#define SKIP_GTM_FETCH_LOGGING_SNIPPING 0
#endif
// If GTMReadMonitorInputStream is available, it can be used for
// capturing uploaded streams of data
//
// We locally declare methods of GTMReadMonitorInputStream so we
// do not need to import the header, as some projects may not have it available
#if !GTMSESSION_BUILD_COMBINED_SOURCES
@interface GTMReadMonitorInputStream : NSInputStream
+ (instancetype)inputStreamWithStream:(NSInputStream *)input;
@property (assign) id readDelegate;
@property (assign) SEL readSelector;
@end
#else
@class GTMReadMonitorInputStream;
#endif // !GTMSESSION_BUILD_COMBINED_SOURCES
@interface GTMSessionFetcher (GTMHTTPFetcherLoggingUtilities)
+ (NSString *)headersStringForDictionary:(NSDictionary *)dict;
+ (NSString *)snipSubstringOfString:(NSString *)originalStr
betweenStartString:(NSString *)startStr
endString:(NSString *)endStr;
- (void)inputStream:(GTMReadMonitorInputStream *)stream
readIntoBuffer:(void *)buffer
length:(int64_t)length;
@end
@implementation GTMSessionFetcher (GTMSessionFetcherLogging)
// fetchers come and fetchers go, but statics are forever
static BOOL gIsLoggingEnabled = NO;
static BOOL gIsLoggingToFile = YES;
static NSString *gLoggingDirectoryPath = nil;
static NSString *gLogDirectoryForCurrentRun = nil;
static NSString *gLoggingDateStamp = nil;
static NSString *gLoggingProcessName = nil;
+ (void)setLoggingDirectory:(NSString *)path {
gLoggingDirectoryPath = [path copy];
}
+ (NSString *)loggingDirectory {
if (!gLoggingDirectoryPath) {
NSArray *paths = nil;
#if TARGET_IPHONE_SIMULATOR
// default to a directory called GTMHTTPDebugLogs into a sandbox-safe
// directory that a developer can find easily, the application home
paths = @[ NSHomeDirectory() ];
#elif TARGET_OS_IPHONE
// Neither ~/Desktop nor ~/Home is writable on an actual iPhone device.
// Put it in ~/Documents.
paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
#else
// default to a directory called GTMHTTPDebugLogs in the desktop folder
paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES);
#endif
NSString *desktopPath = paths.firstObject;
if (desktopPath) {
NSString *const kGTMLogFolderName = @"GTMHTTPDebugLogs";
NSString *logsFolderPath = [desktopPath stringByAppendingPathComponent:kGTMLogFolderName];
NSFileManager *fileMgr = [NSFileManager defaultManager];
BOOL isDir;
BOOL doesFolderExist = [fileMgr fileExistsAtPath:logsFolderPath isDirectory:&isDir];
if (!doesFolderExist) {
// make the directory
doesFolderExist = [fileMgr createDirectoryAtPath:logsFolderPath
withIntermediateDirectories:YES
attributes:nil
error:NULL];
}
if (doesFolderExist) {
// it's there; store it in the global
gLoggingDirectoryPath = [logsFolderPath copy];
}
}
}
return gLoggingDirectoryPath;
}
+ (void)setLogDirectoryForCurrentRun:(NSString *)logDirectoryForCurrentRun {
// Set the path for this run's logs.
gLogDirectoryForCurrentRun = [logDirectoryForCurrentRun copy];
}
+ (NSString *)logDirectoryForCurrentRun {
// make a directory for this run's logs, like SyncProto_logs_10-16_01-56-58PM
if (gLogDirectoryForCurrentRun) return gLogDirectoryForCurrentRun;
NSString *parentDir = [self loggingDirectory];
NSString *logNamePrefix = [self processNameLogPrefix];
NSString *dateStamp = [self loggingDateStamp];
NSString *dirName = [NSString stringWithFormat:@"%@%@", logNamePrefix, dateStamp];
NSString *logDirectory = [parentDir stringByAppendingPathComponent:dirName];
if (gIsLoggingToFile) {
NSFileManager *fileMgr = [NSFileManager defaultManager];
// Be sure that the first time this app runs, it's not writing to a preexisting folder
static BOOL gShouldReuseFolder = NO;
if (!gShouldReuseFolder) {
gShouldReuseFolder = YES;
NSString *origLogDir = logDirectory;
for (int ctr = 2; ctr < 20; ++ctr) {
if (![fileMgr fileExistsAtPath:logDirectory]) break;
// append a digit
logDirectory = [origLogDir stringByAppendingFormat:@"_%d", ctr];
}
}
if (![fileMgr createDirectoryAtPath:logDirectory
withIntermediateDirectories:YES
attributes:nil
error:NULL]) return nil;
}
gLogDirectoryForCurrentRun = logDirectory;
return gLogDirectoryForCurrentRun;
}
+ (void)setLoggingEnabled:(BOOL)isLoggingEnabled {
gIsLoggingEnabled = isLoggingEnabled;
}
+ (BOOL)isLoggingEnabled {
return gIsLoggingEnabled;
}
+ (void)setLoggingToFileEnabled:(BOOL)isLoggingToFileEnabled {
gIsLoggingToFile = isLoggingToFileEnabled;
}
+ (BOOL)isLoggingToFileEnabled {
return gIsLoggingToFile;
}
+ (void)setLoggingProcessName:(NSString *)processName {
gLoggingProcessName = [processName copy];
}
+ (NSString *)loggingProcessName {
// get the process name (once per run) replacing spaces with underscores
if (!gLoggingProcessName) {
NSString *procName = [[NSProcessInfo processInfo] processName];
gLoggingProcessName = [procName stringByReplacingOccurrencesOfString:@" " withString:@"_"];
}
return gLoggingProcessName;
}
+ (void)setLoggingDateStamp:(NSString *)dateStamp {
gLoggingDateStamp = [dateStamp copy];
}
+ (NSString *)loggingDateStamp {
// We'll pick one date stamp per run, so a run that starts at a later second
// will get a unique results html file
if (!gLoggingDateStamp) {
// produce a string like 08-21_01-41-23PM
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setFormatterBehavior:NSDateFormatterBehavior10_4];
[formatter setDateFormat:@"M-dd_hh-mm-ssa"];
gLoggingDateStamp = [formatter stringFromDate:[NSDate date]];
}
return gLoggingDateStamp;
}
+ (NSString *)processNameLogPrefix {
static NSString *gPrefix = nil;
if (!gPrefix) {
NSString *processName = [self loggingProcessName];
gPrefix = [[NSString alloc] initWithFormat:@"%@_log_", processName];
}
return gPrefix;
}
+ (NSString *)symlinkNameSuffix {
return @"_log_newest.html";
}
+ (NSString *)htmlFileName {
return @"aperçu_http_log.html";
}
+ (void)deleteLogDirectoriesOlderThanDate:(NSDate *)cutoffDate {
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSURL *parentDir = [NSURL fileURLWithPath:[[self class] loggingDirectory]];
NSURL *logDirectoryForCurrentRun =
[NSURL fileURLWithPath:[[self class] logDirectoryForCurrentRun]];
NSError *error;
NSArray *contents = [fileMgr contentsOfDirectoryAtURL:parentDir
includingPropertiesForKeys:@[ NSURLContentModificationDateKey ]
options:0
error:&error];
for (NSURL *itemURL in contents) {
if ([itemURL isEqual:logDirectoryForCurrentRun]) continue;
NSDate *modDate;
if ([itemURL getResourceValue:&modDate
forKey:NSURLContentModificationDateKey
error:&error]) {
if ([modDate compare:cutoffDate] == NSOrderedAscending) {
if (![fileMgr removeItemAtURL:itemURL error:&error]) {
NSLog(@"deleteLogDirectoriesOlderThanDate failed to delete %@: %@",
itemURL.path, error);
}
}
} else {
NSLog(@"deleteLogDirectoriesOlderThanDate failed to get mod date of %@: %@",
itemURL.path, error);
}
}
}
// formattedStringFromData returns a prettyprinted string for XML or JSON input,
// and a plain string for other input data
- (NSString *)formattedStringFromData:(NSData *)inputData
contentType:(NSString *)contentType
JSON:(NSDictionary **)outJSON {
if (!inputData) return nil;
// if the content type is JSON and we have the parsing class available, use that
if ([contentType hasPrefix:@"application/json"] && inputData.length > 5) {
// convert from JSON string to NSObjects and back to a formatted string
NSMutableDictionary *obj = [NSJSONSerialization JSONObjectWithData:inputData
options:NSJSONReadingMutableContainers
error:NULL];
if (obj) {
if (outJSON) *outJSON = obj;
if ([obj isKindOfClass:[NSMutableDictionary class]]) {
// for security and privacy, omit OAuth 2 response access and refresh tokens
if ([obj valueForKey:@"refresh_token"] != nil) {
[obj setObject:@"_snip_" forKey:@"refresh_token"];
}
if ([obj valueForKey:@"access_token"] != nil) {
[obj setObject:@"_snip_" forKey:@"access_token"];
}
}
NSData *data = [NSJSONSerialization dataWithJSONObject:obj
options:NSJSONWritingPrettyPrinted
error:NULL];
if (data) {
NSString *jsonStr = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
return jsonStr;
}
}
}
#if !TARGET_OS_IPHONE && !GTM_SKIP_LOG_XMLFORMAT
// verify that this data starts with the bytes indicating XML
NSString *const kXMLLintPath = @"/usr/bin/xmllint";
static BOOL gHasCheckedAvailability = NO;
static BOOL gIsXMLLintAvailable = NO;
if (!gHasCheckedAvailability) {
gIsXMLLintAvailable = [[NSFileManager defaultManager] fileExistsAtPath:kXMLLintPath];
gHasCheckedAvailability = YES;
}
if (gIsXMLLintAvailable
&& inputData.length > 5
&& strncmp(inputData.bytes, "<?xml", 5) == 0) {
// call xmllint to format the data
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:kXMLLintPath];
// use the dash argument to specify stdin as the source file
[task setArguments:@[ @"--format", @"-" ]];
[task setEnvironment:@{}];
NSPipe *inputPipe = [NSPipe pipe];
NSPipe *outputPipe = [NSPipe pipe];
[task setStandardInput:inputPipe];
[task setStandardOutput:outputPipe];
[task launch];
[[inputPipe fileHandleForWriting] writeData:inputData];
[[inputPipe fileHandleForWriting] closeFile];
// drain the stdout before waiting for the task to exit
NSData *formattedData = [[outputPipe fileHandleForReading] readDataToEndOfFile];
[task waitUntilExit];
int status = [task terminationStatus];
if (status == 0 && formattedData.length > 0) {
// success
inputData = formattedData;
}
}
#else
// we can't call external tasks on the iPhone; leave the XML unformatted
#endif
NSString *dataStr = [[NSString alloc] initWithData:inputData
encoding:NSUTF8StringEncoding];
return dataStr;
}
// stringFromStreamData creates a string given the supplied data
//
// If NSString can create a UTF-8 string from the data, then that is returned.
//
// Otherwise, this routine tries to find a MIME boundary at the beginning of the data block, and
// uses that to break up the data into parts. Each part will be used to try to make a UTF-8 string.
// For parts that fail, a replacement string showing the part header and <<n bytes>> is supplied
// in place of the binary data.
- (NSString *)stringFromStreamData:(NSData *)data
contentType:(NSString *)contentType {
if (!data) return nil;
// optimistically, see if the whole data block is UTF-8
NSString *streamDataStr = [self formattedStringFromData:data
contentType:contentType
JSON:NULL];
if (streamDataStr) return streamDataStr;
// Munge a buffer by replacing non-ASCII bytes with underscores, and turn that munged buffer an
// NSString. That gives us a string we can use with NSScanner.
NSMutableData *mutableData = [NSMutableData dataWithData:data];
unsigned char *bytes = (unsigned char *)mutableData.mutableBytes;
for (unsigned int idx = 0; idx < mutableData.length; ++idx) {
if (bytes[idx] > 0x7F || bytes[idx] == 0) {
bytes[idx] = '_';
}
}
NSString *mungedStr = [[NSString alloc] initWithData:mutableData
encoding:NSUTF8StringEncoding];
if (mungedStr) {
// scan for the boundary string
NSString *boundary = nil;
NSScanner *scanner = [NSScanner scannerWithString:mungedStr];
if ([scanner scanUpToString:@"\r\n" intoString:&boundary]
&& [boundary hasPrefix:@"--"]) {
// we found a boundary string; use it to divide the string into parts
NSArray *mungedParts = [mungedStr componentsSeparatedByString:boundary];
// look at each munged part in the original string, and try to convert those into UTF-8
NSMutableArray *origParts = [NSMutableArray array];
NSUInteger offset = 0;
for (NSString *mungedPart in mungedParts) {
NSUInteger partSize = mungedPart.length;
NSData *origPartData = [data subdataWithRange:NSMakeRange(offset, partSize)];
NSString *origPartStr = [[NSString alloc] initWithData:origPartData
encoding:NSUTF8StringEncoding];
if (origPartStr) {
// we could make this original part into UTF-8; use the string
[origParts addObject:origPartStr];
} else {
// this part can't be made into UTF-8; scan the header, if we can
NSString *header = nil;
NSScanner *headerScanner = [NSScanner scannerWithString:mungedPart];
if (![headerScanner scanUpToString:@"\r\n\r\n" intoString:&header]) {
// we couldn't find a header
header = @"";
}
// make a part string with the header and <<n bytes>>
NSString *binStr = [NSString stringWithFormat:@"\r%@\r<<%lu bytes>>\r",
header, (long)(partSize - header.length)];
[origParts addObject:binStr];
}
offset += partSize + boundary.length;
}
// rejoin the original parts
streamDataStr = [origParts componentsJoinedByString:boundary];
}
}
if (!streamDataStr) {
// give up; just make a string showing the uploaded bytes
streamDataStr = [NSString stringWithFormat:@"<<%u bytes>>", (unsigned int)data.length];
}
return streamDataStr;
}
// logFetchWithError is called following a successful or failed fetch attempt
//
// This method does all the work for appending to and creating log files
- (void)logFetchWithError:(NSError *)error {
if (![[self class] isLoggingEnabled]) return;
NSString *logDirectory = [[self class] logDirectoryForCurrentRun];
if (!logDirectory) return;
NSString *processName = [[self class] loggingProcessName];
// TODO: add Javascript to display response data formatted in hex
// each response's NSData goes into its own xml or txt file, though all responses for this run of
// the app share a main html file. This counter tracks all fetch responses for this app run.
//
// we'll use a local variable since this routine may be reentered while waiting for XML formatting
// to be completed by an external task
static int gResponseCounter = 0;
int responseCounter = ++gResponseCounter;
NSURLResponse *response = [self response];
NSDictionary *responseHeaders = [self responseHeaders];
NSString *responseDataStr = nil;
NSDictionary *responseJSON = nil;
// if there's response data, decide what kind of file to put it in based on the first bytes of the
// file or on the mime type supplied by the server
NSString *responseMIMEType = [response MIMEType];
BOOL isResponseImage = NO;
// file name for an image data file
NSString *responseDataFileName = nil;
int64_t responseDataLength = self.downloadedLength;
if (responseDataLength > 0) {
NSData *downloadedData = self.downloadedData;
if (downloadedData == nil
&& responseDataLength > 0
&& responseDataLength < 20000
&& self.destinationFileURL) {
// There's a download file that's not too big, so get the data to display from the downloaded
// file.
NSURL *destinationURL = self.destinationFileURL;
downloadedData = [NSData dataWithContentsOfURL:destinationURL];
}
NSString *responseType = [responseHeaders valueForKey:@"Content-Type"];
responseDataStr = [self formattedStringFromData:downloadedData
contentType:responseType
JSON:&responseJSON];
NSString *responseDataExtn = nil;
NSData *dataToWrite = nil;
if (responseDataStr) {
// we were able to make a UTF-8 string from the response data
if ([responseMIMEType isEqual:@"application/atom+xml"]
|| [responseMIMEType hasSuffix:@"/xml"]) {
responseDataExtn = @"xml";
dataToWrite = [responseDataStr dataUsingEncoding:NSUTF8StringEncoding];
}
} else if ([responseMIMEType isEqual:@"image/jpeg"]) {
responseDataExtn = @"jpg";
dataToWrite = downloadedData;
isResponseImage = YES;
} else if ([responseMIMEType isEqual:@"image/gif"]) {
responseDataExtn = @"gif";
dataToWrite = downloadedData;
isResponseImage = YES;
} else if ([responseMIMEType isEqual:@"image/png"]) {
responseDataExtn = @"png";
dataToWrite = downloadedData;
isResponseImage = YES;
} else {
// add more non-text types here
}
// if we have an extension, save the raw data in a file with that extension
if (responseDataExtn && dataToWrite) {
// generate a response file base name like
NSString *responseBaseName = [NSString stringWithFormat:@"fetch_%d_response", responseCounter];
responseDataFileName = [responseBaseName stringByAppendingPathExtension:responseDataExtn];
NSString *responseDataFilePath = [logDirectory stringByAppendingPathComponent:responseDataFileName];
NSError *downloadedError = nil;
if (gIsLoggingToFile && ![dataToWrite writeToFile:responseDataFilePath
options:0
error:&downloadedError]) {
NSLog(@"%@ logging write error:%@ (%@)", [self class], downloadedError, responseDataFileName);
}
}
}
// we'll have one main html file per run of the app
NSString *htmlName = [[self class] htmlFileName];
NSString *htmlPath =[logDirectory stringByAppendingPathComponent:htmlName];
// if the html file exists (from logging previous fetches) we don't need
// to re-write the header or the scripts
NSFileManager *fileMgr = [NSFileManager defaultManager];
BOOL didFileExist = [fileMgr fileExistsAtPath:htmlPath];
NSMutableString* outputHTML = [NSMutableString string];
// we need a header to say we'll have UTF-8 text
if (!didFileExist) {
[outputHTML appendFormat:@"<html><head><meta http-equiv=\"content-type\" "
"content=\"text/html; charset=UTF-8\"><title>%@ HTTP fetch log %@</title>",
processName, [[self class] loggingDateStamp]];
}
// now write the visible html elements
NSString *copyableFileName = [NSString stringWithFormat:@"fetch_%d.txt", responseCounter];
NSDate *now = [NSDate date];
// write the date & time, the comment, and the link to the plain-text (copyable) log
[outputHTML appendFormat:@"<b>%@ &nbsp;&nbsp;&nbsp;&nbsp; ", now];
NSString *comment = [self comment];
if (comment.length > 0) {
[outputHTML appendFormat:@"%@ &nbsp;&nbsp;&nbsp;&nbsp; ", comment];
}
[outputHTML appendFormat:@"</b><a href='%@'><i>request/response log</i></a><br>", copyableFileName];
NSTimeInterval elapsed = -self.initialBeginFetchDate.timeIntervalSinceNow;
[outputHTML appendFormat:@"elapsed: %5.3fsec<br>", elapsed];
// write the request URL
NSURLRequest *request = self.request;
NSString *requestMethod = request.HTTPMethod;
NSURL *requestURL = request.URL;
// Save the request URL for next time in case this redirects.
NSString *redirectedFromURLString = [self.redirectedFromURL absoluteString];
self.redirectedFromURL = [requestURL copy];
if (redirectedFromURLString) {
[outputHTML appendFormat:@"<FONT COLOR='#990066'><i>redirected from %@</i></FONT><br>",
redirectedFromURLString];
}
[outputHTML appendFormat:@"<b>request:</b> %@ <code>%@</code><br>\n", requestMethod, requestURL];
// write the request headers
NSDictionary *requestHeaders = request.allHTTPHeaderFields;
NSUInteger numberOfRequestHeaders = requestHeaders.count;
if (numberOfRequestHeaders > 0) {
// Indicate if the request is authorized; warn if the request is authorized but non-SSL
NSString *auth = [requestHeaders objectForKey:@"Authorization"];
NSString *headerDetails = @"";
if (auth) {
BOOL isInsecure = [[requestURL scheme] isEqual:@"http"];
if (isInsecure) {
// 26A0 =
headerDetails =
@"&nbsp;&nbsp;&nbsp;<i>authorized, non-SSL</i><FONT COLOR='#FF00FF'> &#x26A0;</FONT> ";
} else {
headerDetails = @"&nbsp;&nbsp;&nbsp;<i>authorized</i>";
}
}
NSString *cookiesHdr = [requestHeaders objectForKey:@"Cookie"];
if (cookiesHdr) {
headerDetails = [headerDetails stringByAppendingString:@"&nbsp;&nbsp;&nbsp;<i>cookies</i>"];
}
NSString *matchHdr = [requestHeaders objectForKey:@"If-Match"];
if (matchHdr) {
headerDetails = [headerDetails stringByAppendingString:@"&nbsp;&nbsp;&nbsp;<i>if-match</i>"];
}
matchHdr = [requestHeaders objectForKey:@"If-None-Match"];
if (matchHdr) {
headerDetails = [headerDetails stringByAppendingString:@"&nbsp;&nbsp;&nbsp;<i>if-none-match</i>"];
}
[outputHTML appendFormat:@"&nbsp;&nbsp; headers: %d %@<br>",
(int)numberOfRequestHeaders, headerDetails];
} else {
[outputHTML appendFormat:@"&nbsp;&nbsp; headers: none<br>"];
}
// write the request post data
NSData *bodyData = nil;
NSData *loggedStreamData = self.loggedStreamData;
if (loggedStreamData) {
bodyData = loggedStreamData;
} else {
bodyData = self.bodyData;
if (bodyData == nil) {
bodyData = self.request.HTTPBody;
}
}
uint64_t bodyDataLength = bodyData.length;
if (bodyData.length == 0) {
// If the data is in a body upload file URL, read that in if it's not huge.
NSURL *bodyFileURL = self.bodyFileURL;
if (bodyFileURL) {
NSNumber *fileSizeNum = nil;
NSError *fileSizeError = nil;
if ([bodyFileURL getResourceValue:&fileSizeNum
forKey:NSURLFileSizeKey
error:&fileSizeError]) {
bodyDataLength = [fileSizeNum unsignedLongLongValue];
if (bodyDataLength > 0 && bodyDataLength < 50000) {
bodyData = [NSData dataWithContentsOfURL:bodyFileURL
options:NSDataReadingUncached
error:&fileSizeError];
}
}
}
}
NSString *bodyDataStr = nil;
NSString *postType = [requestHeaders valueForKey:@"Content-Type"];
if (bodyDataLength > 0) {
[outputHTML appendFormat:@"&nbsp;&nbsp; data: %llu bytes, <code>%@</code><br>\n",
bodyDataLength, postType ? postType : @"(no type)"];
NSString *logRequestBody = self.logRequestBody;
if (logRequestBody) {
bodyDataStr = [logRequestBody copy];
self.logRequestBody = nil;
} else {
bodyDataStr = [self stringFromStreamData:bodyData
contentType:postType];
if (bodyDataStr) {
// remove OAuth 2 client secret and refresh token
bodyDataStr = [[self class] snipSubstringOfString:bodyDataStr
betweenStartString:@"client_secret="
endString:@"&"];
bodyDataStr = [[self class] snipSubstringOfString:bodyDataStr
betweenStartString:@"refresh_token="
endString:@"&"];
// remove ClientLogin password
bodyDataStr = [[self class] snipSubstringOfString:bodyDataStr
betweenStartString:@"&Passwd="
endString:@"&"];
}
}
} else {
// no post data
}
// write the response status, MIME type, URL
NSInteger status = [self statusCode];
if (response) {
NSString *statusString = @"";
if (status != 0) {
if (status == 200 || status == 201) {
statusString = [NSString stringWithFormat:@"%ld", (long)status];
// report any JSON-RPC error
if ([responseJSON isKindOfClass:[NSDictionary class]]) {
NSDictionary *jsonError = [responseJSON objectForKey:@"error"];
if ([jsonError isKindOfClass:[NSDictionary class]]) {
NSString *jsonCode = [[jsonError valueForKey:@"code"] description];
NSString *jsonMessage = [jsonError valueForKey:@"message"];
if (jsonCode || jsonMessage) {
// 2691 =
NSString *const jsonErrFmt =
@"&nbsp;&nbsp;&nbsp;<i>JSON error:</i> <FONT COLOR='#FF00FF'>%@ %@ &nbsp;&#x2691;</FONT>";
statusString = [statusString stringByAppendingFormat:jsonErrFmt,
jsonCode ? jsonCode : @"",
jsonMessage ? jsonMessage : @""];
}
}
}
} else {
// purple for anything other than 200 or 201
NSString *flag = status >= 400 ? @"&nbsp;&#x2691;" : @""; // 2691 =
NSString *explanation = [NSHTTPURLResponse localizedStringForStatusCode:status];
NSString *const statusFormat = @"<FONT COLOR='#FF00FF'>%ld %@ %@</FONT>";
statusString = [NSString stringWithFormat:statusFormat, (long)status, explanation, flag];
}
}
// show the response URL only if it's different from the request URL
NSString *responseURLStr = @"";
NSURL *responseURL = response.URL;
if (responseURL && ![responseURL isEqual:request.URL]) {
NSString *const responseURLFormat =
@"<FONT COLOR='#FF00FF'>response URL:</FONT> <code>%@</code><br>\n";
responseURLStr = [NSString stringWithFormat:responseURLFormat, [responseURL absoluteString]];
}
[outputHTML appendFormat:@"<b>response:</b>&nbsp;&nbsp;status %@<br>\n%@",
statusString, responseURLStr];
// Write the response headers
NSUInteger numberOfResponseHeaders = responseHeaders.count;
if (numberOfResponseHeaders > 0) {
// Indicate if the server is setting cookies
NSString *cookiesSet = [responseHeaders valueForKey:@"Set-Cookie"];
NSString *cookiesStr =
cookiesSet ? @"&nbsp;&nbsp;<FONT COLOR='#990066'><i>sets cookies</i></FONT>" : @"";
// Indicate if the server is redirecting
NSString *location = [responseHeaders valueForKey:@"Location"];
BOOL isRedirect = status >= 300 && status <= 399 && location != nil;
NSString *redirectsStr =
isRedirect ? @"&nbsp;&nbsp;<FONT COLOR='#990066'><i>redirects</i></FONT>" : @"";
[outputHTML appendFormat:@"&nbsp;&nbsp; headers: %d %@ %@<br>\n",
(int)numberOfResponseHeaders, cookiesStr, redirectsStr];
} else {
[outputHTML appendString:@"&nbsp;&nbsp; headers: none<br>\n"];
}
}
// error
if (error) {
[outputHTML appendFormat:@"<b>Error:</b> %@ <br>\n", error.description];
}
// Write the response data
if (responseDataFileName) {
if (isResponseImage) {
// Make a small inline image that links to the full image file
[outputHTML appendFormat:@"&nbsp;&nbsp; data: %lld bytes, <code>%@</code><br>",
responseDataLength, responseMIMEType];
NSString *const fmt =
@"<a href=\"%@\"><img src='%@' alt='image' style='border:solid thin;max-height:32'></a>\n";
[outputHTML appendFormat:fmt, responseDataFileName, responseDataFileName];
} else {
// The response data was XML; link to the xml file
NSString *const fmt =
@"&nbsp;&nbsp; data: %lld bytes, <code>%@</code>&nbsp;&nbsp;&nbsp;<i><a href=\"%@\">%@</a></i>\n";
[outputHTML appendFormat:fmt, responseDataLength, responseMIMEType,
responseDataFileName, [responseDataFileName pathExtension]];
}
} else {
// The response data was not an image; just show the length and MIME type
[outputHTML appendFormat:@"&nbsp;&nbsp; data: %lld bytes, <code>%@</code>\n",
responseDataLength, responseMIMEType ? responseMIMEType : @"(no response type)"];
}
// Make a single string of the request and response, suitable for copying
// to the clipboard and pasting into a bug report
NSMutableString *copyable = [NSMutableString string];
if (comment) {
[copyable appendFormat:@"%@\n\n", comment];
}
[copyable appendFormat:@"%@ elapsed: %5.3fsec\n", now, elapsed];
if (redirectedFromURLString) {
[copyable appendFormat:@"Redirected from %@\n", redirectedFromURLString];
}
[copyable appendFormat:@"Request: %@ %@\n", requestMethod, requestURL];
if (requestHeaders.count > 0) {
[copyable appendFormat:@"Request headers:\n%@\n",
[[self class] headersStringForDictionary:requestHeaders]];
}
if (bodyDataLength > 0) {
[copyable appendFormat:@"Request body: (%llu bytes)\n", bodyDataLength];
if (bodyDataStr) {
[copyable appendFormat:@"%@\n", bodyDataStr];
}
[copyable appendString:@"\n"];
}
if (response) {
[copyable appendFormat:@"Response: status %d\n", (int) status];
[copyable appendFormat:@"Response headers:\n%@\n",
[[self class] headersStringForDictionary:responseHeaders]];
[copyable appendFormat:@"Response body: (%lld bytes)\n", responseDataLength];
if (responseDataLength > 0) {
NSString *logResponseBody = self.logResponseBody;
if (logResponseBody) {
// The user has provided the response body text.
responseDataStr = [logResponseBody copy];
self.logResponseBody = nil;
}
if (responseDataStr != nil) {
[copyable appendFormat:@"%@\n", responseDataStr];
} else {
// Even though it's redundant, we'll put in text to indicate that all the bytes are binary.
if (self.destinationFileURL) {
[copyable appendFormat:@"<<%lld bytes>> to file %@\n",
responseDataLength, self.destinationFileURL.path];
} else {
[copyable appendFormat:@"<<%lld bytes>>\n", responseDataLength];
}
}
}
}
if (error) {
[copyable appendFormat:@"Error: %@\n", error];
}
// Save to log property before adding the separator
self.log = copyable;
[copyable appendString:@"-----------------------------------------------------------\n"];
// Write the copyable version to another file (linked to at the top of the html file, above)
//
// Ideally, something to just copy this to the clipboard like
// <span onCopy='window.event.clipboardData.setData(\"Text\",
// \"copyable stuff\");return false;'>Copy here.</span>"
// would work everywhere, but it only works in Safari as of 8/2010
if (gIsLoggingToFile) {
NSString *parentDir = [[self class] loggingDirectory];
NSString *copyablePath = [logDirectory stringByAppendingPathComponent:copyableFileName];
NSError *copyableError = nil;
if (![copyable writeToFile:copyablePath
atomically:NO
encoding:NSUTF8StringEncoding
error:&copyableError]) {
// Error writing to file
NSLog(@"%@ logging write error:%@ (%@)", [self class], copyableError, copyablePath);
}
[outputHTML appendString:@"<br><hr><p>"];
// Append the HTML to the main output file
const char* htmlBytes = outputHTML.UTF8String;
NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:htmlPath
append:YES];
[stream open];
[stream write:(const uint8_t *) htmlBytes maxLength:strlen(htmlBytes)];
[stream close];
// Make a symlink to the latest html
NSString *const symlinkNameSuffix = [[self class] symlinkNameSuffix];
NSString *symlinkName = [processName stringByAppendingString:symlinkNameSuffix];
NSString *symlinkPath = [parentDir stringByAppendingPathComponent:symlinkName];
[fileMgr removeItemAtPath:symlinkPath error:NULL];
[fileMgr createSymbolicLinkAtPath:symlinkPath
withDestinationPath:htmlPath
error:NULL];
#if TARGET_OS_IPHONE
static BOOL gReportedLoggingPath = NO;
if (!gReportedLoggingPath) {
gReportedLoggingPath = YES;
NSLog(@"GTMSessionFetcher logging to \"%@\"", parentDir);
}
#endif
}
}
- (NSInputStream *)loggedInputStreamForInputStream:(NSInputStream *)inputStream {
if (!inputStream) return nil;
if (![GTMSessionFetcher isLoggingEnabled]) return inputStream;
[self clearLoggedStreamData]; // Clear any previous data.
Class monitorClass = NSClassFromString(@"GTMReadMonitorInputStream");
if (!monitorClass) {
NSString const *str = @"<<Uploaded stream log unavailable without GTMReadMonitorInputStream>>";
NSData *stringData = [str dataUsingEncoding:NSUTF8StringEncoding];
[self appendLoggedStreamData:stringData];
return inputStream;
}
inputStream = [monitorClass inputStreamWithStream:inputStream];
GTMReadMonitorInputStream *readMonitorInputStream = (GTMReadMonitorInputStream *)inputStream;
[readMonitorInputStream setReadDelegate:self];
SEL readSel = @selector(inputStream:readIntoBuffer:length:);
[readMonitorInputStream setReadSelector:readSel];
return inputStream;
}
- (GTMSessionFetcherBodyStreamProvider)loggedStreamProviderForStreamProvider:
(GTMSessionFetcherBodyStreamProvider)streamProvider {
if (!streamProvider) return nil;
if (![GTMSessionFetcher isLoggingEnabled]) return streamProvider;
[self clearLoggedStreamData]; // Clear any previous data.
Class monitorClass = NSClassFromString(@"GTMReadMonitorInputStream");
if (!monitorClass) {
NSString const *str = @"<<Uploaded stream log unavailable without GTMReadMonitorInputStream>>";
NSData *stringData = [str dataUsingEncoding:NSUTF8StringEncoding];
[self appendLoggedStreamData:stringData];
return streamProvider;
}
GTMSessionFetcherBodyStreamProvider loggedStreamProvider =
^(GTMSessionFetcherBodyStreamProviderResponse response) {
streamProvider(^(NSInputStream *bodyStream) {
bodyStream = [self loggedInputStreamForInputStream:bodyStream];
response(bodyStream);
});
};
return loggedStreamProvider;
}
@end
@implementation GTMSessionFetcher (GTMSessionFetcherLoggingUtilities)
- (void)inputStream:(GTMReadMonitorInputStream *)stream
readIntoBuffer:(void *)buffer
length:(int64_t)length {
// append the captured data
NSData *data = [NSData dataWithBytesNoCopy:buffer
length:(NSUInteger)length
freeWhenDone:NO];
[self appendLoggedStreamData:data];
}
#pragma mark Fomatting Utilities
+ (NSString *)snipSubstringOfString:(NSString *)originalStr
betweenStartString:(NSString *)startStr
endString:(NSString *)endStr {
#if SKIP_GTM_FETCH_LOGGING_SNIPPING
return originalStr;
#else
if (!originalStr) return nil;
// Find the start string, and replace everything between it
// and the end string (or the end of the original string) with "_snip_"
NSRange startRange = [originalStr rangeOfString:startStr];
if (startRange.location == NSNotFound) return originalStr;
// We found the start string
NSUInteger originalLength = originalStr.length;
NSUInteger startOfTarget = NSMaxRange(startRange);
NSRange targetAndRest = NSMakeRange(startOfTarget, originalLength - startOfTarget);
NSRange endRange = [originalStr rangeOfString:endStr
options:0
range:targetAndRest];
NSRange replaceRange;
if (endRange.location == NSNotFound) {
// Found no end marker so replace to end of string
replaceRange = targetAndRest;
} else {
// Replace up to the endStr
replaceRange = NSMakeRange(startOfTarget, endRange.location - startOfTarget);
}
NSString *result = [originalStr stringByReplacingCharactersInRange:replaceRange
withString:@"_snip_"];
return result;
#endif // SKIP_GTM_FETCH_LOGGING_SNIPPING
}
+ (NSString *)headersStringForDictionary:(NSDictionary *)dict {
// Format the dictionary in http header style, like
// Accept: application/json
// Cache-Control: no-cache
// Content-Type: application/json; charset=utf-8
//
// Pad the key names, but not beyond 16 chars, since long custom header
// keys just create too much whitespace
NSArray *keys = [dict.allKeys sortedArrayUsingSelector:@selector(compare:)];
NSMutableString *str = [NSMutableString string];
for (NSString *key in keys) {
NSString *value = [dict valueForKey:key];
if ([key isEqual:@"Authorization"]) {
// Remove OAuth 1 token
value = [[self class] snipSubstringOfString:value
betweenStartString:@"oauth_token=\""
endString:@"\""];
// Remove OAuth 2 bearer token (draft 16, and older form)
value = [[self class] snipSubstringOfString:value
betweenStartString:@"Bearer "
endString:@"\n"];
value = [[self class] snipSubstringOfString:value
betweenStartString:@"OAuth "
endString:@"\n"];
// Remove Google ClientLogin
value = [[self class] snipSubstringOfString:value
betweenStartString:@"GoogleLogin auth="
endString:@"\n"];
}
[str appendFormat:@" %@: %@\n", key, value];
}
return str;
}
@end
#endif // !STRIP_GTM_FETCH_LOGGING

View File

@@ -0,0 +1,190 @@
/* Copyright 2014 Google Inc. 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.
*/
// For best performance and convenient usage, fetchers should be generated by a common
// GTMSessionFetcherService instance, like
//
// _fetcherService = [[GTMSessionFetcherService alloc] init];
// GTMSessionFetcher* myFirstFetcher = [_fetcherService fetcherWithRequest:request1];
// GTMSessionFetcher* mySecondFetcher = [_fetcherService fetcherWithRequest:request2];
#import "GTMSessionFetcher.h"
GTM_ASSUME_NONNULL_BEGIN
// Notifications.
// This notification indicates a reusable session has become invalid. It is intended mainly for the
// service's unit tests.
//
// The notification object is the fetcher service.
// The invalid session is provided via the userInfo kGTMSessionFetcherServiceSessionKey key.
extern NSString *const kGTMSessionFetcherServiceSessionBecameInvalidNotification;
extern NSString *const kGTMSessionFetcherServiceSessionKey;
@interface GTMSessionFetcherService : NSObject<GTMSessionFetcherServiceProtocol>
// Queues of delayed and running fetchers. Each dictionary contains arrays
// of GTMSessionFetcher *fetchers, keyed by NSString *host
@property(atomic, strong, readonly, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSArray *) *delayedFetchersByHost;
@property(atomic, strong, readonly, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSArray *) *runningFetchersByHost;
// A max value of 0 means no fetchers should be delayed.
// The default limit is 10 simultaneous fetchers targeting each host.
// This does not apply to fetchers whose useBackgroundSession property is YES. Since services are
// not resurrected on an app relaunch, delayed fetchers would effectively be abandoned.
@property(atomic, assign) NSUInteger maxRunningFetchersPerHost;
// Properties to be applied to each fetcher; see GTMSessionFetcher.h for descriptions
@property(atomic, strong, GTM_NULLABLE) NSURLSessionConfiguration *configuration;
@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherConfigurationBlock configurationBlock;
@property(atomic, strong, GTM_NULLABLE) NSHTTPCookieStorage *cookieStorage;
@property(atomic, strong, GTM_NULL_RESETTABLE) dispatch_queue_t callbackQueue;
@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherChallengeBlock challengeBlock;
@property(atomic, strong, GTM_NULLABLE) NSURLCredential *credential;
@property(atomic, strong) NSURLCredential *proxyCredential;
@property(atomic, copy, GTM_NULLABLE) GTM_NSArrayOf(NSString *) *allowedInsecureSchemes;
@property(atomic, assign) BOOL allowLocalhostRequest;
@property(atomic, assign) BOOL allowInvalidServerCertificates;
@property(atomic, assign, getter=isRetryEnabled) BOOL retryEnabled;
@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherRetryBlock retryBlock;
@property(atomic, assign) NSTimeInterval maxRetryInterval;
@property(atomic, assign) NSTimeInterval minRetryInterval;
@property(atomic, copy, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, id) *properties;
#if GTM_BACKGROUND_TASK_FETCHING
@property(atomic, assign) BOOL skipBackgroundTask;
#endif
// A default useragent of GTMFetcherStandardUserAgentString(nil) will be given to each fetcher
// created by this service unless the request already has a user-agent header set.
// This default will be added starting with builds with the SDKs for OS X 10.11 and iOS 9.
//
// To use the configuration's default user agent, set this property to nil.
@property(atomic, copy, GTM_NULLABLE) NSString *userAgent;
// The authorizer to attach to the created fetchers. If a specific fetcher should
// not authorize its requests, the fetcher's authorizer property may be set to nil
// before the fetch begins.
@property(atomic, strong, GTM_NULLABLE) id<GTMFetcherAuthorizationProtocol> authorizer;
// Delegate queue used by the session when calling back to the fetcher. The default
// is the main queue. Changing this does not affect the queue used to call back to the
// application; that is specified by the callbackQueue property above.
@property(atomic, strong, GTM_NULL_RESETTABLE) NSOperationQueue *sessionDelegateQueue;
// When enabled, indicates the same session should be used by subsequent fetchers.
//
// This is enabled by default.
@property(atomic, assign) BOOL reuseSession;
// Sets the delay until an unused session is invalidated.
// The default interval is 60 seconds.
//
// If the interval is set to 0, then any reused session is not invalidated except by
// explicitly invoking -resetSession. Be aware that setting the interval to 0 thus
// causes the session's delegate to be retained until the session is explicitly reset.
@property(atomic, assign) NSTimeInterval unusedSessionTimeout;
// If shouldReuseSession is enabled, this will force creation of a new session when future
// fetchers begin.
- (void)resetSession;
// Create a fetcher
//
// These methods will return a fetcher. If successfully created, the connection
// will hold a strong reference to it for the life of the connection as well.
// So the caller doesn't have to hold onto the fetcher explicitly unless they
// want to be able to monitor or cancel it.
- (GTMSessionFetcher *)fetcherWithRequest:(NSURLRequest *)request;
- (GTMSessionFetcher *)fetcherWithURL:(NSURL *)requestURL;
- (GTMSessionFetcher *)fetcherWithURLString:(NSString *)requestURLString;
// Common method for fetcher creation.
//
// -fetcherWithRequest:fetcherClass: may be overridden to customize creation of
// fetchers. This is the ONLY method in the GTMSessionFetcher library intended to
// be overridden.
- (id)fetcherWithRequest:(NSURLRequest *)request
fetcherClass:(Class)fetcherClass;
- (BOOL)isDelayingFetcher:(GTMSessionFetcher *)fetcher;
- (NSUInteger)numberOfFetchers; // running + delayed fetchers
- (NSUInteger)numberOfRunningFetchers;
- (NSUInteger)numberOfDelayedFetchers;
// Return a list of all running or delayed fetchers. This includes fetchers created
// by the service which have been started and have not yet stopped.
//
// Returns an array of fetcher objects, or nil if none.
- (GTM_NULLABLE GTM_NSArrayOf(GTMSessionFetcher *) *)issuedFetchers;
// Search for running or delayed fetchers with the specified URL.
//
// Returns an array of fetcher objects found, or nil if none found.
- (GTM_NULLABLE GTM_NSArrayOf(GTMSessionFetcher *) *)issuedFetchersWithRequestURL:(NSURL *)requestURL;
- (void)stopAllFetchers;
// Methods for use by the fetcher class only.
- (GTM_NULLABLE NSURLSession *)session;
- (GTM_NULLABLE NSURLSession *)sessionForFetcherCreation;
- (GTM_NULLABLE id<NSURLSessionDelegate>)sessionDelegate;
- (GTM_NULLABLE NSDate *)stoppedAllFetchersDate;
// The testBlock can inspect its fetcher parameter's request property to
// determine which fetcher is being faked.
@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherTestBlock testBlock;
@end
@interface GTMSessionFetcherService (TestingSupport)
// Convenience method to create a fetcher service for testing.
//
// Fetchers generated by this mock fetcher service will not perform any
// network operation, but will invoke callbacks and provide the supplied data
// or error to the completion handler.
//
// You can make more customized mocks by setting the test block property of the service
// or fetcher; the test block can inspect the fetcher's request or other properties.
//
// See the description of the testBlock property below.
+ (instancetype)mockFetcherServiceWithFakedData:(GTM_NULLABLE NSData *)fakedDataOrNil
fakedError:(GTM_NULLABLE NSError *)fakedErrorOrNil;
// Spin the run loop and discard events (or, if not on the main thread, just sleep the thread)
// until all running and delayed fetchers have completed.
//
// This is only for use in testing or in tools without a user interface.
//
// Synchronous fetches should never be done by shipping apps; they are
// sufficient reason for rejection from the app store.
//
// Returns NO if timed out.
- (BOOL)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds;
@end
@interface GTMSessionFetcherService (BackwardsCompatibilityOnly)
// Clients using GTMSessionFetcher should set the cookie storage explicitly themselves.
// This method is just for compatibility with the old fetcher.
@property(atomic, assign) NSInteger cookieStorageMethod;
@end
GTM_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,128 @@
/* Copyright 2014 Google Inc. 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.
*/
// GTMSessionUploadFetcher implements Google's resumable upload protocol.
//
// This subclass of GTMSessionFetcher simulates the series of fetches
// needed for chunked upload as a single fetch operation.
//
// Protocol document: TBD
//
// To the client, the only fetcher that exists is this class; the subsidiary
// fetchers needed for uploading chunks are not visible (though the most recent
// chunk fetcher may be accessed via the -activeFetcher or -chunkFetcher methods, and
// -responseHeaders and -statusCode reflect results from the most recent chunk
// fetcher.)
//
// Chunk fetchers are discarded as soon as they have completed.
//
// Note: Unlike the fetcher superclass, the methods of GTMSessionUploadFetcher should
// only be used from the main thread until further work is done to make this subclass
// thread-safe.
#import "GTMSessionFetcher.h"
#import "GTMSessionFetcherService.h"
GTM_ASSUME_NONNULL_BEGIN
// Unless an application knows it needs a smaller chunk size, it should use the standard
// chunk size, which sends the entire file as a single chunk to minimize upload overhead.
extern int64_t const kGTMSessionUploadFetcherStandardChunkSize;
// When uploading requires data buffer allocations (such as uploading from an NSData or
// an NSFileHandle) this is the maximum buffer size that will be created by the fetcher.
extern int64_t const kGTMSessionUploadFetcherMaximumDemandBufferSize;
// Notification that the upload location URL was provided by the server.
extern NSString *const kGTMSessionFetcherUploadLocationObtainedNotification;
// Block to provide data during uploads.
//
// Response data may be allocated with dataWithBytesNoCopy:length:freeWhenDone: for efficiency,
// and released after the response block returns.
//
// Pass nil as the data (and optionally an NSError) for a failure.
typedef void (^GTMSessionUploadFetcherDataProviderResponse)(NSData * GTM_NULLABLE_TYPE data,
NSError * GTM_NULLABLE_TYPE error);
typedef void (^GTMSessionUploadFetcherDataProvider)(int64_t offset, int64_t length,
GTMSessionUploadFetcherDataProviderResponse response);
@interface GTMSessionUploadFetcher : GTMSessionFetcher
// Create an upload fetcher specifying either the request or the resume location URL,
// then set an upload data source using one of these:
//
// setUploadFileURL:
// setUploadDataLength:provider:
// setUploadFileHandle:
// setUploadData:
+ (instancetype)uploadFetcherWithRequest:(NSURLRequest *)request
uploadMIMEType:(NSString *)uploadMIMEType
chunkSize:(int64_t)chunkSize
fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil;
+ (instancetype)uploadFetcherWithLocation:(NSURL * GTM_NULLABLE_TYPE)uploadLocationURL
uploadMIMEType:(NSString *)uploadMIMEType
chunkSize:(int64_t)chunkSize
fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil;
- (void)setUploadDataLength:(int64_t)fullLength
provider:(GTM_NULLABLE GTMSessionUploadFetcherDataProvider)block;
+ (NSArray *)uploadFetchersForBackgroundSessions;
+ (GTM_NULLABLE instancetype)uploadFetcherForSessionIdentifier:(NSString *)sessionIdentifier;
- (void)pauseFetching;
- (void)resumeFetching;
- (BOOL)isPaused;
@property(atomic, strong, GTM_NULLABLE) NSURL *uploadLocationURL;
@property(atomic, strong, GTM_NULLABLE) NSData *uploadData;
@property(atomic, strong, GTM_NULLABLE) NSURL *uploadFileURL;
@property(atomic, strong, GTM_NULLABLE) NSFileHandle *uploadFileHandle;
@property(atomic, copy, readonly, GTM_NULLABLE) GTMSessionUploadFetcherDataProvider uploadDataProvider;
@property(atomic, copy) NSString *uploadMIMEType;
@property(atomic, assign) int64_t chunkSize;
@property(atomic, readonly, assign) int64_t currentOffset;
// The fetcher for the current data chunk, if any
@property(atomic, strong, GTM_NULLABLE) GTMSessionFetcher *chunkFetcher;
// The active fetcher is the current chunk fetcher, or the upload fetcher itself
// if no chunk fetcher has yet been created.
@property(atomic, readonly) GTMSessionFetcher *activeFetcher;
// The last request made by an active fetcher. Useful for testing.
@property(atomic, readonly, GTM_NULLABLE) NSURLRequest *lastChunkRequest;
// The status code from the most recently-completed fetch.
@property(atomic, assign) NSInteger statusCode;
// Exposed for testing only.
@property(atomic, readonly, GTM_NULLABLE) dispatch_queue_t delegateCallbackQueue;
@property(atomic, readonly, GTM_NULLABLE) GTMSessionFetcherCompletionHandler delegateCompletionHandler;
@end
@interface GTMSessionFetcher (GTMSessionUploadFetcherMethods)
@property(readonly, GTM_NULLABLE) GTMSessionUploadFetcher *parentUploadFetcher;
@end
GTM_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff