FIRStorageUtils.m 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. // Copyright 2017 Google
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import <Foundation/Foundation.h>
  15. #if TARGET_OS_IOS || TARGET_OS_TV
  16. #import <MobileCoreServices/MobileCoreServices.h>
  17. #elif TARGET_OS_OSX || TARGET_OS_WATCH
  18. #import <CoreServices/CoreServices.h>
  19. #endif
  20. #import "FirebaseStorage/Sources/FIRStorageUtils.h"
  21. #import "FirebaseStorage/Sources/FIRStorageConstants_Private.h"
  22. #import "FirebaseStorage/Sources/FIRStorageErrors.h"
  23. #import "FirebaseStorage/Sources/FIRStoragePath.h"
  24. #import "FirebaseStorage/Sources/FIRStorageReference_Private.h"
  25. #import "FirebaseStorage/Sources/FIRStorage_Private.h"
  26. #if SWIFT_PACKAGE
  27. @import GTMSessionFetcherCore;
  28. #else
  29. #import <GTMSessionFetcher/GTMSessionFetcher.h>
  30. #endif
  31. // This is the list at https://cloud.google.com/storage/docs/json_api/ without &, ; and +.
  32. NSString *const kGCSObjectAllowedCharacterSet =
  33. @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$'()*,=:@";
  34. @implementation FIRStorageUtils
  35. + (nullable NSString *)GCSEscapedString:(NSString *)string {
  36. NSCharacterSet *allowedCharacters =
  37. [NSCharacterSet characterSetWithCharactersInString:kGCSObjectAllowedCharacterSet];
  38. return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters];
  39. }
  40. + (nullable NSString *)MIMETypeForExtension:(NSString *)extension {
  41. if (extension == nil) {
  42. return nil;
  43. }
  44. CFStringRef pathExtension = (__bridge_retained CFStringRef)extension;
  45. CFStringRef type =
  46. UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension, NULL);
  47. NSString *mimeType =
  48. (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass(type, kUTTagClassMIMEType);
  49. CFRelease(pathExtension);
  50. if (type != NULL) {
  51. CFRelease(type);
  52. }
  53. return mimeType;
  54. }
  55. + (NSString *)queryStringForDictionary:(nullable NSDictionary *)dictionary {
  56. if (!dictionary) {
  57. return @"";
  58. }
  59. __block NSMutableArray *queryItems = [[NSMutableArray alloc] initWithCapacity:[dictionary count]];
  60. [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull name, NSString *_Nonnull value,
  61. BOOL *_Nonnull stop) {
  62. NSString *item =
  63. [FIRStorageUtils GCSEscapedString:[NSString stringWithFormat:@"%@=%@", name, value]];
  64. [queryItems addObject:item];
  65. }];
  66. return [queryItems componentsJoinedByString:@"&"];
  67. }
  68. + (NSURLRequest *)defaultRequestForReference:(FIRStorageReference *)reference {
  69. NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
  70. NSURLComponents *components = [[NSURLComponents alloc] init];
  71. [components setScheme:reference.storage.scheme];
  72. [components setHost:reference.storage.host];
  73. [components setPort:reference.storage.port];
  74. NSString *encodedPath = [self encodedURLForPath:reference.path];
  75. [components setPercentEncodedPath:encodedPath];
  76. [request setURL:components.URL];
  77. return request;
  78. }
  79. + (NSURLRequest *)defaultRequestForReference:(FIRStorageReference *)reference
  80. queryParams:(NSDictionary<NSString *, NSString *> *)queryParams {
  81. NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
  82. NSURLComponents *components = [[NSURLComponents alloc] init];
  83. [components setScheme:reference.storage.scheme];
  84. [components setHost:reference.storage.host];
  85. [components setPort:reference.storage.port];
  86. NSMutableArray<NSURLQueryItem *> *queryItems = [NSMutableArray new];
  87. for (NSString *key in queryParams) {
  88. [queryItems addObject:[NSURLQueryItem queryItemWithName:key value:queryParams[key]]];
  89. }
  90. [components setQueryItems:queryItems];
  91. // NSURLComponents does not encode "+" as "%2B". This is however required by our backend, as
  92. // it treats "+" as a shorthand encoding for spaces. See also
  93. // https://stackoverflow.com/questions/31577188/how-to-encode-into-2b-with-nsurlcomponents
  94. [components setPercentEncodedQuery:[[components percentEncodedQuery]
  95. stringByReplacingOccurrencesOfString:@"+"
  96. withString:@"%2B"]];
  97. NSString *encodedPath = [self encodedURLForPath:reference.path];
  98. [components setPercentEncodedPath:encodedPath];
  99. [request setURL:components.URL];
  100. return request;
  101. }
  102. + (NSString *)encodedURLForPath:(FIRStoragePath *)path {
  103. NSString *bucketName = [FIRStorageUtils GCSEscapedString:path.bucket];
  104. NSString *objectName = [FIRStorageUtils GCSEscapedString:path.object];
  105. NSString *bucketFormat = [NSString stringWithFormat:kFIRStorageBucketPathFormat, bucketName];
  106. NSString *urlPath = [@"/" stringByAppendingPathComponent:bucketFormat];
  107. if (objectName) {
  108. NSString *objectFormat = [NSString stringWithFormat:kFIRStorageObjectPathFormat, objectName];
  109. urlPath = [urlPath stringByAppendingFormat:@"/%@", objectFormat];
  110. } else {
  111. urlPath = [urlPath stringByAppendingString:@"/o"];
  112. }
  113. return [@"/" stringByAppendingString:[kFIRStorageVersionPath stringByAppendingString:urlPath]];
  114. }
  115. + (NSError *)storageErrorWithDescription:(NSString *)description code:(NSInteger)code {
  116. return [NSError errorWithDomain:FIRStorageErrorDomain
  117. code:code
  118. userInfo:@{NSLocalizedDescriptionKey : description}];
  119. }
  120. + (NSTimeInterval)computeRetryIntervalFromRetryTime:(NSTimeInterval)retryTime {
  121. // GTMSessionFetcher's retry starts at 1 second and then doubles every time. We use this
  122. // information to compute a best-effort estimate of what to translate the user provided retry
  123. // time into.
  124. // Note that this is the same as 2 << (log2(retryTime) - 1), but deemed more readable.
  125. NSTimeInterval lastInterval = 1.0;
  126. NSTimeInterval sumOfAllIntervals = 1.0;
  127. while (sumOfAllIntervals < retryTime) {
  128. lastInterval *= 2;
  129. sumOfAllIntervals += lastInterval;
  130. }
  131. return lastInterval;
  132. }
  133. @end
  134. @implementation NSDictionary (FIRStorageNSDictionaryJSONHelpers)
  135. + (nullable instancetype)frs_dictionaryFromJSONData:(nullable NSData *)data {
  136. if (!data) {
  137. return nil;
  138. }
  139. return [NSJSONSerialization JSONObjectWithData:data
  140. options:NSJSONReadingMutableContainers
  141. error:nil];
  142. }
  143. @end
  144. @implementation NSData (FIRStorageNSDataJSONHelpers)
  145. + (nullable instancetype)frs_dataFromJSONDictionary:(nullable NSDictionary *)dictionary {
  146. if (!dictionary) {
  147. return nil;
  148. }
  149. return [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:nil];
  150. }
  151. @end