firebase log level

This commit is contained in:
oscarz
2024-08-29 18:25:13 +08:00
parent 8500300d18
commit 27c160beaf
1165 changed files with 122916 additions and 1 deletions

View File

@ -0,0 +1,56 @@
// Copyright 2019 Google
//
// 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 <Foundation/Foundation.h>
OBJC_EXTERN const NSUInteger FIRCLSNetworkMaximumRetryCount;
NS_ASSUME_NONNULL_BEGIN
typedef void (^FIRCLSNetworkDataTaskCompletionHandlerBlock)(NSData *__nullable data,
NSURLResponse *__nullable response,
NSError *__nullable error);
typedef void (^FIRCLSNetworkDownloadTaskCompletionHandlerBlock)(NSURL *__nullable location,
NSURLResponse *__nullable response,
NSError *__nullable error);
@interface FIRCLSFABNetworkClient : NSObject
- (instancetype)init;
- (instancetype)initWithQueue:(nullable NSOperationQueue *)operationQueue;
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)config
queue:(nullable NSOperationQueue *)operationQueue
NS_DESIGNATED_INITIALIZER;
- (void)startDataTaskWithRequest:(NSURLRequest *)request
retryLimit:(NSUInteger)retryLimit
completionHandler:(FIRCLSNetworkDataTaskCompletionHandlerBlock)completionHandler;
- (void)startDownloadTaskWithRequest:(NSURLRequest *)request
retryLimit:(NSUInteger)retryLimit
completionHandler:
(FIRCLSNetworkDownloadTaskCompletionHandlerBlock)completionHandler;
- (void)invalidateAndCancel;
// Backwards compatibility (we cannot change an interface in Fabric Base that other kits rely on,
// since we have no control of versioning dependencies)
- (void)startDataTaskWithRequest:(NSURLRequest *)request
completionHandler:(FIRCLSNetworkDataTaskCompletionHandlerBlock)completionHandler;
- (void)startDownloadTaskWithRequest:(NSURLRequest *)request
completionHandler:
(FIRCLSNetworkDownloadTaskCompletionHandlerBlock)completionHandler;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,267 @@
// Copyright 2019 Google
//
// 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 "Crashlytics/Shared/FIRCLSNetworking/FIRCLSFABNetworkClient.h"
#import "Crashlytics/Shared/FIRCLSNetworking/FIRCLSNetworkResponseHandler.h"
static const float FIRCLSNetworkMinimumRetryJitter = 0.90f;
static const float FIRCLSNetworkMaximumRetryJitter = 1.10f;
const NSUInteger FIRCLSNetworkMaximumRetryCount = 10;
@interface FIRCLSFABNetworkClient () <NSURLSessionDelegate, NSURLSessionTaskDelegate>
@property(nonatomic, strong, readonly) NSURLSession *session;
@end
@implementation FIRCLSFABNetworkClient
- (instancetype)init {
return [self initWithQueue:nil];
}
- (instancetype)initWithQueue:(nullable NSOperationQueue *)operationQueue {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
return [self initWithSessionConfiguration:config queue:operationQueue];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)config
queue:(nullable NSOperationQueue *)operationQueue {
self = [super init];
if (!self) {
return nil;
}
_session = [NSURLSession sessionWithConfiguration:config
delegate:self
delegateQueue:operationQueue];
if (!_session) {
return nil;
}
return self;
}
- (void)dealloc {
[_session finishTasksAndInvalidate];
}
#pragma mark - Delay Handling
- (double)randomDoubleWithMin:(double)min max:(double)max {
return min + ((max - min) * drand48());
}
- (double)generateRandomJitter {
return [self randomDoubleWithMin:FIRCLSNetworkMinimumRetryJitter
max:FIRCLSNetworkMaximumRetryJitter];
}
- (NSTimeInterval)computeDelayForResponse:(NSURLResponse *)response
withRetryCount:(NSUInteger)count {
NSTimeInterval initialValue = [FIRCLSNetworkResponseHandler retryValueForResponse:response];
// make sure count is > 0
count = MAX(count, 1);
// make sure initialValue is >2 for exponential backoff to work reasonably with low count numbers
initialValue = MAX(initialValue, 2.0);
const double jitter = [self generateRandomJitter];
return pow(initialValue, count) * jitter; // exponential backoff
}
- (void)runAfterRetryValueFromResponse:(NSURLResponse *)response
attempts:(NSUInteger)count
onQueue:(dispatch_queue_t)queue
block:(void (^)(void))block {
const NSTimeInterval delay = [self computeDelayForResponse:response withRetryCount:count];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (uint64_t)(delay * NSEC_PER_SEC)), queue, block);
}
- (void)runAfterRetryValueFromResponse:(NSURLResponse *)response
attempts:(NSUInteger)count
block:(void (^)(void))block {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
[self runAfterRetryValueFromResponse:response attempts:count onQueue:queue block:block];
}
#pragma mark - Tasks
- (void)startDataTaskWithRequest:(NSURLRequest *)request
retryLimit:(NSUInteger)retryLimit
tries:(NSUInteger)tries
completionHandler:(FIRCLSNetworkDataTaskCompletionHandlerBlock)completionHandler {
NSURLSessionTask *task = [self.session
dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *taskError) {
[FIRCLSNetworkResponseHandler
handleCompletedResponse:response
forOriginalRequest:request
error:taskError
block:^(BOOL retry, NSError *error) {
if (!retry) {
completionHandler(data, response, error);
return;
}
if (tries >= retryLimit) {
NSDictionary *userInfo = @{
@"retryLimit" : @(retryLimit),
NSURLErrorFailingURLStringErrorKey : request.URL
};
completionHandler(
nil, nil,
[NSError
errorWithDomain:FIRCLSNetworkErrorDomain
code:FIRCLSNetworkErrorMaximumAttemptsReached
userInfo:userInfo]);
return;
}
[self
runAfterRetryValueFromResponse:response
attempts:tries
block:^{
[self
startDataTaskWithRequest:
request
retryLimit:
retryLimit
tries:
(tries +
1)
completionHandler:
completionHandler];
}];
}];
}];
[task resume];
if (!task) {
completionHandler(nil, nil,
[NSError errorWithDomain:FIRCLSNetworkErrorDomain
code:FIRCLSNetworkErrorFailedToStartOperation
userInfo:nil]);
}
}
- (void)startDataTaskWithRequest:(NSURLRequest *)request
retryLimit:(NSUInteger)retryLimit
completionHandler:(FIRCLSNetworkDataTaskCompletionHandlerBlock)completionHandler {
[self startDataTaskWithRequest:request
retryLimit:retryLimit
tries:0
completionHandler:completionHandler];
}
- (void)startDataTaskWithRequest:(NSURLRequest *)request
completionHandler:(FIRCLSNetworkDataTaskCompletionHandlerBlock)completionHandler {
[self startDataTaskWithRequest:request
retryLimit:FIRCLSNetworkMaximumRetryCount
completionHandler:completionHandler];
}
- (void)startDownloadTaskWithRequest:(NSURLRequest *)request
retryLimit:(NSUInteger)retryLimit
tries:(NSUInteger)tries
completionHandler:
(FIRCLSNetworkDownloadTaskCompletionHandlerBlock)completionHandler {
NSURLSessionTask *task = [self.session
downloadTaskWithRequest:request
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *taskError) {
[FIRCLSNetworkResponseHandler
handleCompletedResponse:response
forOriginalRequest:request
error:taskError
block:^(BOOL retry, NSError *error) {
if (!retry) {
completionHandler(location, response, error);
return;
}
if (tries >= retryLimit) {
NSDictionary *userInfo = @{
@"retryLimit" : @(retryLimit),
NSURLErrorFailingURLStringErrorKey : request.URL
};
completionHandler(
nil, nil,
[NSError
errorWithDomain:FIRCLSNetworkErrorDomain
code:
FIRCLSNetworkErrorMaximumAttemptsReached
userInfo:userInfo]);
return;
}
[self
runAfterRetryValueFromResponse:response
attempts:tries
block:^{
[self
startDownloadTaskWithRequest:
request
retryLimit:
retryLimit
tries:
(tries +
1)
completionHandler:
completionHandler];
}];
}];
}];
[task resume];
if (!task) {
completionHandler(nil, nil,
[NSError errorWithDomain:FIRCLSNetworkErrorDomain
code:FIRCLSNetworkErrorFailedToStartOperation
userInfo:nil]);
}
}
- (void)startDownloadTaskWithRequest:(NSURLRequest *)request
retryLimit:(NSUInteger)retryLimit
completionHandler:
(FIRCLSNetworkDownloadTaskCompletionHandlerBlock)completionHandler {
[self startDownloadTaskWithRequest:request
retryLimit:retryLimit
tries:0
completionHandler:completionHandler];
}
- (void)startDownloadTaskWithRequest:(NSURLRequest *)request
completionHandler:
(FIRCLSNetworkDownloadTaskCompletionHandlerBlock)completionHandler {
[self startDownloadTaskWithRequest:request
retryLimit:FIRCLSNetworkMaximumRetryCount
completionHandler:completionHandler];
}
- (void)invalidateAndCancel {
[self.session invalidateAndCancel];
}
#pragma mark - NSURLSession Delegate
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
}
@end

View File

@ -0,0 +1,87 @@
// Copyright 2019 Google
//
// 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 <Foundation/Foundation.h>
/**
* Type to indicate response status
*/
typedef NS_ENUM(NSInteger, FIRCLSNetworkClientResponseType) {
FIRCLSNetworkClientResponseSuccess,
FIRCLSNetworkClientResponseInvalid,
FIRCLSNetworkClientResponseFailure,
FIRCLSNetworkClientResponseRetry,
FIRCLSNetworkClientResponseBackOff
};
typedef NS_ENUM(NSInteger, FIRCLSNetworkErrorType) {
FIRCLSNetworkErrorUnknown = -1,
FIRCLSNetworkErrorFailedToStartOperation = -3,
FIRCLSNetworkErrorResponseInvalid = -4,
FIRCLSNetworkErrorRequestFailed = -5,
FIRCLSNetworkErrorMaximumAttemptsReached = -6,
};
extern NSInteger const FIRCLSNetworkErrorUnknownURLCancelReason;
/**
* This block is an input parameter to handleCompletedResponse: and handleCompletedTask: methods of
* this class.
* @param retryMightSucceed is YES if the request should be retried.
* @param error is the error received back in response.
*/
typedef void (^FIRCLSNetworkResponseCompletionHandlerBlock)(BOOL retryMightSucceed, NSError *error);
/**
* Error domain for Crashlytics network errors
*/
extern NSString *const FIRCLSNetworkErrorDomain;
/**
* This class handles network responses.
*/
@interface FIRCLSNetworkResponseHandler : NSObject
/**
* Returns the header in the given NSURLResponse with name as key
*/
+ (NSString *)headerForResponse:(NSURLResponse *)response withKey:(NSString *)key;
/**
* Returns Retry-After header value in response, and if absent returns a default retry value
*/
+ (NSTimeInterval)retryValueForResponse:(NSURLResponse *)response;
/**
* Checks if the content type for response matches the request
*/
+ (BOOL)contentTypeForResponse:(NSURLResponse *)response matchesRequest:(NSURLRequest *)request;
+ (NSInteger)cancelReasonFromURLError:(NSError *)error;
+ (BOOL)retryableURLError:(NSError *)error;
/**
* Convenience method that calls back the input block with FIRCLSNetworkClientResponseType after
* checking the response code in response
*/
+ (void)clientResponseType:(NSURLResponse *)response
handler:(void (^)(FIRCLSNetworkClientResponseType type,
NSInteger statusCode))responseTypeAndStatusCodeHandlerBlock;
/**
* Handles a completed response for request and calls back input block. Populates error even if
* error was nil, but response code indicated an error.
*/
+ (void)handleCompletedResponse:(NSURLResponse *)response
forOriginalRequest:(NSURLRequest *)originalRequest
error:(NSError *)error
block:(FIRCLSNetworkResponseCompletionHandlerBlock)completionHandlerBlock;
@end

View File

@ -0,0 +1,290 @@
// Copyright 2019 Google
//
// 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 "Crashlytics/Shared/FIRCLSNetworking/FIRCLSNetworkResponseHandler.h"
@implementation FIRCLSNetworkResponseHandler
static const NSTimeInterval kFIRCLSNetworkResponseHandlerDefaultRetryInterval = 2.0;
static NSString *const kFIRCLSNetworkResponseHandlerContentType = @"Content-Type";
NSString *const FIRCLSNetworkErrorDomain = @"FIRCLSNetworkError";
NSInteger const FIRCLSNetworkErrorUnknownURLCancelReason = -1;
#pragma mark - Header Handling
+ (NSString *)headerForResponse:(NSURLResponse *)response withKey:(NSString *)key {
if (![response respondsToSelector:@selector(allHeaderFields)]) {
return nil;
}
return [((NSHTTPURLResponse *)response).allHeaderFields objectForKey:key];
}
+ (NSTimeInterval)retryValueForResponse:(NSURLResponse *)response {
NSString *retryValueString = [self headerForResponse:response withKey:@"Retry-After"];
if (!retryValueString) {
return kFIRCLSNetworkResponseHandlerDefaultRetryInterval;
}
NSTimeInterval value = retryValueString.doubleValue;
if (value < 0.0) {
return kFIRCLSNetworkResponseHandlerDefaultRetryInterval;
}
return value;
}
+ (NSString *)requestIdForResponse:(NSURLResponse *)response {
return [self headerForResponse:response withKey:@"X-Request-Id"];
}
+ (BOOL)contentTypeForResponse:(NSURLResponse *)response matchesRequest:(NSURLRequest *)request {
NSString *accept = [request.allHTTPHeaderFields objectForKey:@"Accept"];
if (!accept) {
// An omitted accept header is defined to match everything
return YES;
}
NSString *contentHeader = [self.class headerForResponse:response
withKey:kFIRCLSNetworkResponseHandlerContentType];
if (!contentHeader) {
// FIRCLSDeveloperLog("Network", @"Content-Type not present in response");
return NO;
}
NSString *acceptCharset = request.allHTTPHeaderFields[@"Accept-Charset"];
NSArray *parts = [contentHeader componentsSeparatedByString:@"; charset="];
if (!parts) {
parts = @[ contentHeader ];
}
if ([[parts objectAtIndex:0] caseInsensitiveCompare:accept] != NSOrderedSame) {
// FIRCLSDeveloperLog("Network", @"Content-Type does not match Accept");
return NO;
}
if (!acceptCharset) {
return YES;
}
if (parts.count < 2) {
return YES;
}
return [[parts objectAtIndex:1] caseInsensitiveCompare:acceptCharset] == NSOrderedSame;
}
+ (NSInteger)cancelReasonFromURLError:(NSError *)error {
if (![[error domain] isEqualToString:NSURLErrorDomain]) {
return FIRCLSNetworkErrorUnknownURLCancelReason;
}
if ([error code] != NSURLErrorCancelled) {
return FIRCLSNetworkErrorUnknownURLCancelReason;
}
NSNumber *reason = [[error userInfo] objectForKey:NSURLErrorBackgroundTaskCancelledReasonKey];
if (reason == nil) {
return FIRCLSNetworkErrorUnknownURLCancelReason;
}
return [reason integerValue];
}
+ (BOOL)retryableURLError:(NSError *)error {
// So far, the only task errors seen are NSURLErrorDomain. For others, we're not
// sure what to do.
if (![[error domain] isEqualToString:NSURLErrorDomain]) {
return NO;
}
// cases that we know are definitely not retryable
switch ([error code]) {
case NSURLErrorBadURL:
case NSURLErrorUnsupportedURL:
case NSURLErrorHTTPTooManyRedirects:
case NSURLErrorRedirectToNonExistentLocation:
case NSURLErrorUserCancelledAuthentication:
case NSURLErrorUserAuthenticationRequired:
case NSURLErrorAppTransportSecurityRequiresSecureConnection:
case NSURLErrorFileDoesNotExist:
case NSURLErrorFileIsDirectory:
case NSURLErrorDataLengthExceedsMaximum:
case NSURLErrorSecureConnectionFailed:
case NSURLErrorServerCertificateHasBadDate:
case NSURLErrorServerCertificateUntrusted:
case NSURLErrorServerCertificateHasUnknownRoot:
case NSURLErrorServerCertificateNotYetValid:
case NSURLErrorClientCertificateRejected:
case NSURLErrorClientCertificateRequired:
case NSURLErrorBackgroundSessionRequiresSharedContainer:
return NO;
}
// All other errors, as far as I can tell, are things that could clear up
// without action on the part of the client.
// NSURLErrorCancelled is a potential special-case. I believe there are
// situations where a cancelled request cannot be successfully restarted. But,
// until I can prove it, we'll retry. There are definitely many cases where
// a cancelled request definitely can be restarted and will work.
return YES;
}
#pragma mark - Error Creation
+ (NSError *)errorForCode:(NSInteger)code userInfo:(NSDictionary *)userInfo {
return [NSError errorWithDomain:FIRCLSNetworkErrorDomain code:code userInfo:userInfo];
}
+ (NSError *)errorForResponse:(NSURLResponse *)response
ofType:(FIRCLSNetworkClientResponseType)type
status:(NSInteger)status {
if (type == FIRCLSNetworkClientResponseSuccess) {
return nil;
}
NSString *requestId = [self requestIdForResponse:response];
NSString *contentType = [self headerForResponse:response
withKey:kFIRCLSNetworkResponseHandlerContentType];
// this could be nil, so be careful
requestId = requestId ? requestId : @"";
contentType = contentType ? contentType : @"";
NSDictionary *userInfo = @{
@"type" : @(type),
@"status_code" : @(status),
@"request_id" : requestId,
@"content_type" : contentType
};
// compute a reasonable error code type
NSInteger errorCode = FIRCLSNetworkErrorUnknown;
switch (type) {
case FIRCLSNetworkClientResponseFailure:
errorCode = FIRCLSNetworkErrorRequestFailed;
break;
case FIRCLSNetworkClientResponseInvalid:
errorCode = FIRCLSNetworkErrorResponseInvalid;
break;
default:
break;
}
return [self errorForCode:errorCode userInfo:userInfo];
}
+ (void)clientResponseType:(NSURLResponse *)response
handler:(void (^)(FIRCLSNetworkClientResponseType type,
NSInteger statusCode))responseTypeAndStatusCodeHandlerBlock {
if (![response respondsToSelector:@selector(statusCode)]) {
responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseInvalid, 0);
return;
}
NSInteger code = ((NSHTTPURLResponse *)response).statusCode;
switch (code) {
case 200:
case 201:
case 202:
case 204:
case 304:
responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseSuccess, code);
return;
case 420:
case 429:
responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseBackOff, code);
return;
case 408:
responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseRetry, code);
return;
case 400:
case 401:
case 403:
case 404:
case 406:
case 410:
case 411:
case 413:
case 419:
case 422:
case 431:
responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseFailure, code);
return;
}
// check for a 5xx
if (code >= 500 && code <= 599) {
responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseRetry, code);
return;
}
responseTypeAndStatusCodeHandlerBlock(FIRCLSNetworkClientResponseInvalid, code);
}
+ (void)handleCompletedResponse:(NSURLResponse *)response
forOriginalRequest:(NSURLRequest *)originalRequest
error:(NSError *)originalError
block:
(FIRCLSNetworkResponseCompletionHandlerBlock)completionHandlerBlock {
// if we have an error, we can just continue
if (originalError) {
BOOL retryable = [self retryableURLError:originalError];
completionHandlerBlock(retryable, originalError);
return;
}
[self.class clientResponseType:response
handler:^(FIRCLSNetworkClientResponseType type, NSInteger statusCode) {
NSError *error = nil;
switch (type) {
case FIRCLSNetworkClientResponseInvalid:
error = [self errorForResponse:response
ofType:type
status:statusCode];
break;
case FIRCLSNetworkClientResponseBackOff:
case FIRCLSNetworkClientResponseRetry:
error = [self errorForResponse:response
ofType:type
status:statusCode];
completionHandlerBlock(YES, error);
return;
case FIRCLSNetworkClientResponseFailure:
error = [self errorForResponse:response
ofType:type
status:statusCode];
break;
case FIRCLSNetworkClientResponseSuccess:
if (![self contentTypeForResponse:response
matchesRequest:originalRequest]) {
error = [self errorForResponse:response
ofType:FIRCLSNetworkClientResponseInvalid
status:statusCode];
break;
}
break;
}
completionHandlerBlock(NO, error);
}];
}
@end

View File

@ -0,0 +1,44 @@
// Copyright 2019 Google
//
// 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 <Foundation/Foundation.h>
/**
* This is a convenience class to ease constructing NSURLs.
*/
@interface FIRCLSURLBuilder : NSObject
/**
* Convenience method that returns a FIRCLSURLBuilder instance with the input base URL appended to
* it.
*/
+ (instancetype)URLWithBase:(NSString *)base;
/**
* Appends the component to the URL being built by FIRCLSURLBuilder instance
*/
- (void)appendComponent:(NSString *)component;
/**
* Escapes and appends the component to the URL being built by FIRCLSURLBuilder instance
*/
- (void)escapeAndAppendComponent:(NSString *)component;
/**
* Adds a query and value to the URL being built
*/
- (void)appendValue:(id)value forQueryParam:(NSString *)param;
/**
* Returns the built URL
*/
- (NSURL *)URL;
@end

View File

@ -0,0 +1,104 @@
// Copyright 2019 Google
//
// 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 "Crashlytics/Shared/FIRCLSNetworking/FIRCLSURLBuilder.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
@interface FIRCLSURLBuilder ()
@property(nonatomic) NSMutableString *URLString;
@property(nonatomic) NSUInteger queryParams;
- (NSString *)escapeString:(NSString *)string;
@end
@implementation FIRCLSURLBuilder
+ (instancetype)URLWithBase:(NSString *)base {
FIRCLSURLBuilder *url = [[FIRCLSURLBuilder alloc] init];
[url appendComponent:base];
return url;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
_URLString = [[NSMutableString alloc] init];
_queryParams = 0;
return self;
}
- (NSString *)escapeString:(NSString *)string {
#if TARGET_OS_WATCH
// TODO: Question - Why does watchOS use a different encoding from the other platforms and the
// Android SDK?
// This broken the unit test on watchOS: https://github.com/firebase/firebase-ios-sdk/pull/10511
return
[string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet
URLPathAllowedCharacterSet]];
#else
return
[string stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet
.URLQueryAllowedCharacterSet];
#endif
}
- (void)appendComponent:(NSString *)component {
if (component.length == 0) {
FIRCLSErrorLog(@"URLBuilder parameter component must not be empty");
return;
}
[self.URLString appendString:component];
}
- (void)escapeAndAppendComponent:(NSString *)component {
[self appendComponent:[self escapeString:component]];
}
- (void)appendValue:(id)value forQueryParam:(NSString *)param {
if (!value) {
return;
}
if (self.queryParams == 0) {
[self appendComponent:@"?"];
} else {
[self appendComponent:@"&"];
}
self.queryParams += 1;
[self appendComponent:param];
[self appendComponent:@"="];
if ([value isKindOfClass:NSString.class]) {
[self escapeAndAppendComponent:value];
} else {
[self escapeAndAppendComponent:[value description]];
}
}
- (NSURL *)URL {
return [NSURL URLWithString:self.URLString];
}
@end