FIRStorage.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  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 "FirebaseStorageInternal/Sources/Public/FirebaseStorageInternal/FIRStorage.h"
  15. #import "FirebaseStorageInternal/Sources/Public/FirebaseStorageInternal/FIRStorageReference.h"
  16. #import "FirebaseStorageInternal/Sources/FIRStorageConstants_Private.h"
  17. #import "FirebaseStorageInternal/Sources/FIRStoragePath.h"
  18. #import "FirebaseStorageInternal/Sources/FIRStorageReference_Private.h"
  19. #import "FirebaseStorageInternal/Sources/FIRStorageTokenAuthorizer.h"
  20. #import "FirebaseStorageInternal/Sources/FIRStorageUtils.h"
  21. #import "FirebaseStorageInternal/Sources/FIRStorage_Private.h"
  22. #import "FirebaseAppCheck/Interop/FIRAppCheckInterop.h"
  23. #import "FirebaseAuth/Interop/FIRAuthInterop.h"
  24. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  25. #if SWIFT_PACKAGE
  26. @import GTMSessionFetcherCore;
  27. #else
  28. #import <GTMSessionFetcher/GTMSessionFetcher.h>
  29. #import <GTMSessionFetcher/GTMSessionFetcherLogging.h>
  30. #endif
  31. static NSMutableDictionary<
  32. NSString * /* app name */,
  33. NSMutableDictionary<NSString * /* bucket */, GTMSessionFetcherService *> *> *_fetcherServiceMap;
  34. static GTMSessionFetcherRetryBlock _retryWhenOffline;
  35. @interface FIRIMPLStorage () {
  36. /// Stored Auth reference, if it exists. This needs to be stored for `copyWithZone:`.
  37. id<FIRAuthInterop> _Nullable _auth;
  38. id<FIRAppCheckInterop> _Nullable _appCheck;
  39. BOOL _usesEmulator;
  40. NSTimeInterval _maxUploadRetryTime;
  41. NSTimeInterval _maxDownloadRetryTime;
  42. NSTimeInterval _maxOperationRetryTime;
  43. }
  44. @end
  45. @implementation FIRIMPLStorage
  46. + (void)initialize {
  47. static dispatch_once_t onceToken;
  48. dispatch_once(&onceToken, ^{
  49. _retryWhenOffline = ^(BOOL suggestedWillRetry, NSError *__nullable error,
  50. GTMSessionFetcherRetryResponse response) {
  51. bool shouldRetry = suggestedWillRetry;
  52. // GTMSessionFetcher does not consider being offline a retryable error, but we do, so we
  53. // special-case it here.
  54. if (!shouldRetry && error) {
  55. shouldRetry = error.code == NSURLErrorNotConnectedToInternet;
  56. }
  57. response(shouldRetry);
  58. };
  59. _fetcherServiceMap = [[NSMutableDictionary alloc] init];
  60. });
  61. }
  62. + (GTMSessionFetcherService *)fetcherServiceForApp:(FIRApp *)app
  63. bucket:(NSString *)bucket
  64. auth:(nullable id<FIRAuthInterop>)auth
  65. appCheck:(nullable id<FIRAppCheckInterop>)appCheck {
  66. @synchronized(_fetcherServiceMap) {
  67. NSMutableDictionary *bucketMap = _fetcherServiceMap[app.name];
  68. if (!bucketMap) {
  69. bucketMap = [[NSMutableDictionary alloc] init];
  70. _fetcherServiceMap[app.name] = bucketMap;
  71. }
  72. GTMSessionFetcherService *fetcherService = bucketMap[bucket];
  73. if (!fetcherService) {
  74. fetcherService = [[GTMSessionFetcherService alloc] init];
  75. [fetcherService setRetryEnabled:YES];
  76. [fetcherService setRetryBlock:_retryWhenOffline];
  77. [fetcherService setAllowLocalhostRequest:YES];
  78. FIRStorageTokenAuthorizer *authorizer =
  79. [[FIRStorageTokenAuthorizer alloc] initWithGoogleAppID:app.options.googleAppID
  80. fetcherService:fetcherService
  81. authProvider:auth
  82. appCheck:appCheck];
  83. [fetcherService setAuthorizer:authorizer];
  84. bucketMap[bucket] = fetcherService;
  85. }
  86. return fetcherService;
  87. }
  88. }
  89. + (void)setGTMSessionFetcherLoggingEnabled:(BOOL)isLoggingEnabled {
  90. [GTMSessionFetcher setLoggingEnabled:isLoggingEnabled];
  91. }
  92. + (NSString *)bucketForApp:(FIRApp *)app {
  93. if (app.options.storageBucket) {
  94. NSString *url = [app.options.storageBucket isEqualToString:@""]
  95. ? @""
  96. : [@"gs://" stringByAppendingString:app.options.storageBucket];
  97. return [self bucketForApp:app URL:url];
  98. } else {
  99. NSString *const kAppNotConfiguredMessage =
  100. @"No default Storage bucket found. Did you configure Firebase Storage properly?";
  101. [NSException raise:NSInvalidArgumentException format:kAppNotConfiguredMessage];
  102. return nil;
  103. }
  104. }
  105. + (NSString *)bucketForApp:(FIRApp *)app URL:(NSString *)url {
  106. NSString *bucket;
  107. if ([url isEqualToString:@""]) {
  108. bucket = @"";
  109. } else {
  110. FIRStoragePath *path;
  111. @try {
  112. path = [FIRStoragePath pathFromGSURI:url];
  113. } @catch (NSException *e) {
  114. [NSException raise:NSInternalInconsistencyException
  115. format:@"URI must be in the form of gs://<bucket>/"];
  116. }
  117. if (path.object != nil && ![path.object isEqualToString:@""]) {
  118. [NSException raise:NSInternalInconsistencyException
  119. format:@"Storage bucket cannot be initialized with a path"];
  120. }
  121. bucket = path.bucket;
  122. }
  123. return bucket;
  124. }
  125. - (instancetype)initWithApp:(FIRApp *)app
  126. bucket:(NSString *)bucket
  127. auth:(nullable id<FIRAuthInterop>)auth
  128. appCheck:(nullable id<FIRAppCheckInterop>)appCheck {
  129. self = [super init];
  130. if (self) {
  131. _app = app;
  132. _auth = auth;
  133. _appCheck = appCheck;
  134. _storageBucket = bucket;
  135. _host = kFIRStorageHost;
  136. _scheme = kFIRStorageScheme;
  137. _port = @(kFIRStoragePort);
  138. _fetcherServiceForApp = nil; // Configured in `ensureConfigured()`
  139. // Must be a serial queue.
  140. _dispatchQueue = dispatch_queue_create("com.google.firebase.storage", DISPATCH_QUEUE_SERIAL);
  141. _maxDownloadRetryTime = 600.0;
  142. _maxDownloadRetryInterval =
  143. [FIRStorageUtils computeRetryIntervalFromRetryTime:_maxDownloadRetryTime];
  144. _maxOperationRetryTime = 120.0;
  145. _maxOperationRetryInterval =
  146. [FIRStorageUtils computeRetryIntervalFromRetryTime:_maxOperationRetryTime];
  147. _maxUploadRetryTime = 600.0;
  148. _maxUploadRetryInterval =
  149. [FIRStorageUtils computeRetryIntervalFromRetryTime:_maxUploadRetryTime];
  150. }
  151. return self;
  152. }
  153. - (instancetype)init {
  154. NSAssert(false, @"Storage cannot be directly instantiated, use "
  155. "Storage.storage() or Storage.storage(app:) instead");
  156. return nil;
  157. }
  158. #pragma mark - NSObject overrides
  159. - (instancetype)copyWithZone:(NSZone *)zone {
  160. FIRIMPLStorage *storage = [[[self class] allocWithZone:zone] initWithApp:_app
  161. bucket:_storageBucket
  162. auth:_auth
  163. appCheck:_appCheck];
  164. storage.callbackQueue = self.callbackQueue;
  165. return storage;
  166. }
  167. // Two FIRStorage objects are equal if they use the same app
  168. - (BOOL)isEqual:(id)object {
  169. if (self == object) {
  170. return YES;
  171. }
  172. if (![object isKindOfClass:[FIRIMPLStorage class]]) {
  173. return NO;
  174. }
  175. BOOL isEqualObject = [self isEqualToFIRStorage:(FIRIMPLStorage *)object];
  176. return isEqualObject;
  177. }
  178. - (BOOL)isEqualToFIRStorage:(FIRIMPLStorage *)storage {
  179. BOOL isEqual =
  180. [_app isEqual:storage.app] && [_storageBucket isEqualToString:storage.storageBucket];
  181. return isEqual;
  182. }
  183. - (NSUInteger)hash {
  184. NSUInteger hash = [_app hash] ^ [self.callbackQueue hash];
  185. return hash;
  186. }
  187. - (NSString *)description {
  188. return [NSString stringWithFormat:@"%@ %p: %@", [self class], self, _app];
  189. }
  190. #pragma mark - Retry time intervals
  191. - (void)setMaxUploadRetryTime:(NSTimeInterval)maxUploadRetryTime {
  192. @synchronized(self) {
  193. _maxUploadRetryTime = maxUploadRetryTime;
  194. _maxUploadRetryInterval =
  195. [FIRStorageUtils computeRetryIntervalFromRetryTime:maxUploadRetryTime];
  196. }
  197. }
  198. - (NSTimeInterval)maxDownloadRetryTime {
  199. @synchronized(self) {
  200. return _maxDownloadRetryTime;
  201. }
  202. }
  203. - (void)setMaxDownloadRetryTime:(NSTimeInterval)maxDownloadRetryTime {
  204. @synchronized(self) {
  205. _maxDownloadRetryTime = maxDownloadRetryTime;
  206. _maxDownloadRetryInterval =
  207. [FIRStorageUtils computeRetryIntervalFromRetryTime:maxDownloadRetryTime];
  208. }
  209. }
  210. - (NSTimeInterval)maxUploadRetryTime {
  211. @synchronized(self) {
  212. return _maxUploadRetryTime;
  213. }
  214. }
  215. - (void)setMaxOperationRetryTime:(NSTimeInterval)maxOperationRetryTime {
  216. @synchronized(self) {
  217. _maxOperationRetryTime = maxOperationRetryTime;
  218. _maxOperationRetryInterval =
  219. [FIRStorageUtils computeRetryIntervalFromRetryTime:maxOperationRetryTime];
  220. }
  221. }
  222. - (NSTimeInterval)maxOperationRetryTime {
  223. @synchronized(self) {
  224. return _maxOperationRetryTime;
  225. }
  226. }
  227. #pragma mark - Public methods
  228. - (FIRIMPLStorageReference *)reference {
  229. [self ensureConfigured];
  230. FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:_storageBucket object:nil];
  231. return [[FIRIMPLStorageReference alloc] initWithStorage:self path:path];
  232. }
  233. - (FIRIMPLStorageReference *)referenceForURL:(NSString *)string {
  234. [self ensureConfigured];
  235. FIRStoragePath *path = [FIRStoragePath pathFromString:string];
  236. // If no default bucket exists (empty string), accept anything.
  237. if ([_storageBucket isEqual:@""]) {
  238. FIRIMPLStorageReference *reference = [[FIRIMPLStorageReference alloc] initWithStorage:self
  239. path:path];
  240. return reference;
  241. }
  242. // If there exists a default bucket, throw if provided a different bucket.
  243. if (![path.bucket isEqual:_storageBucket]) {
  244. NSString *const kInvalidBucketFormat =
  245. @"Provided bucket: %@ does not match the Storage bucket of the current instance: %@";
  246. [NSException raise:NSInvalidArgumentException
  247. format:kInvalidBucketFormat, path.bucket, _storageBucket];
  248. }
  249. FIRIMPLStorageReference *reference = [[FIRIMPLStorageReference alloc] initWithStorage:self
  250. path:path];
  251. return reference;
  252. }
  253. - (FIRIMPLStorageReference *)referenceWithPath:(NSString *)string {
  254. FIRIMPLStorageReference *reference = [[self reference] child:string];
  255. return reference;
  256. }
  257. - (dispatch_queue_t)callbackQueue {
  258. [self ensureConfigured];
  259. return _fetcherServiceForApp.callbackQueue;
  260. }
  261. - (void)setCallbackQueue:(dispatch_queue_t)callbackQueue {
  262. [self ensureConfigured];
  263. _fetcherServiceForApp.callbackQueue = callbackQueue;
  264. }
  265. - (void)useEmulatorWithHost:(NSString *)host port:(NSInteger)port {
  266. if (host.length == 0) {
  267. [NSException raise:NSInvalidArgumentException format:@"Cannot connect to nil or empty host."];
  268. }
  269. if (port < 0) {
  270. [NSException raise:NSInvalidArgumentException
  271. format:@"Port must be greater than or equal to zero."];
  272. }
  273. if (_fetcherServiceForApp != nil) {
  274. [NSException raise:NSInternalInconsistencyException
  275. format:@"Cannot connect to emulator after Storage SDK initialization. "
  276. @"Call useEmulator(host:port:) before creating a Storage "
  277. @"reference or trying to load data."];
  278. }
  279. _usesEmulator = YES;
  280. _scheme = @"http";
  281. _host = host;
  282. _port = @(port);
  283. }
  284. #pragma mark - Background tasks
  285. + (void)enableBackgroundTasks:(BOOL)isEnabled {
  286. [NSException raise:NSGenericException format:@"enableBackgroundTasks not implemented"];
  287. }
  288. - (NSArray<FIRIMPLStorageUploadTask *> *)uploadTasks {
  289. [NSException raise:NSGenericException format:@"getUploadTasks not implemented"];
  290. return nil;
  291. }
  292. - (NSArray<FIRIMPLStorageDownloadTask *> *)downloadTasks {
  293. [NSException raise:NSGenericException format:@"getDownloadTasks not implemented"];
  294. return nil;
  295. }
  296. - (void)ensureConfigured {
  297. if (!_fetcherServiceForApp) {
  298. _fetcherServiceForApp = [FIRIMPLStorage fetcherServiceForApp:_app
  299. bucket:_storageBucket
  300. auth:_auth
  301. appCheck:_appCheck];
  302. if (_usesEmulator) {
  303. _fetcherServiceForApp.allowLocalhostRequest = YES;
  304. _fetcherServiceForApp.allowedInsecureSchemes = @[ @"http" ];
  305. }
  306. }
  307. }
  308. @end