aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firebase/Storage
diff options
context:
space:
mode:
authorGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
committerGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
commit98ba64449a632518bd2b86fe8d927f4a960d3ddc (patch)
tree131d9c4272fa6179fcda6c5a33fcb3b1bd57ad2e /Firebase/Storage
parent32461366c9e204a527ca05e6e9b9404a2454ac51 (diff)
Initial
Diffstat (limited to 'Firebase/Storage')
-rw-r--r--Firebase/Storage/FIRStorage.h130
-rw-r--r--Firebase/Storage/FIRStorage.m233
-rw-r--r--Firebase/Storage/FIRStorageConstants.h173
-rw-r--r--Firebase/Storage/FIRStorageConstants.m83
-rw-r--r--Firebase/Storage/FIRStorageDeleteTask.m54
-rw-r--r--Firebase/Storage/FIRStorageDownloadTask.h39
-rw-r--r--Firebase/Storage/FIRStorageDownloadTask.m162
-rw-r--r--Firebase/Storage/FIRStorageErrors.m172
-rw-r--r--Firebase/Storage/FIRStorageGetMetadataTask.m84
-rw-r--r--Firebase/Storage/FIRStorageMetadata.h149
-rw-r--r--Firebase/Storage/FIRStorageMetadata.m227
-rw-r--r--Firebase/Storage/FIRStorageObservableTask.h63
-rw-r--r--Firebase/Storage/FIRStorageObservableTask.m216
-rw-r--r--Firebase/Storage/FIRStoragePath.m199
-rw-r--r--Firebase/Storage/FIRStorageReference.h244
-rw-r--r--Firebase/Storage/FIRStorageReference.m364
-rw-r--r--Firebase/Storage/FIRStorageSwiftNameSupport.h29
-rw-r--r--Firebase/Storage/FIRStorageTask.h76
-rw-r--r--Firebase/Storage/FIRStorageTask.m65
-rw-r--r--Firebase/Storage/FIRStorageTaskSnapshot.h68
-rw-r--r--Firebase/Storage/FIRStorageTaskSnapshot.m88
-rw-r--r--Firebase/Storage/FIRStorageTokenAuthorizer.m131
-rw-r--r--Firebase/Storage/FIRStorageUpdateMetadataTask.m91
-rw-r--r--Firebase/Storage/FIRStorageUploadTask.h39
-rw-r--r--Firebase/Storage/FIRStorageUploadTask.m199
-rw-r--r--Firebase/Storage/FIRStorageUtils.m121
-rw-r--r--Firebase/Storage/FirebaseStorage.h25
-rw-r--r--Firebase/Storage/FirebaseStorage.podspec44
-rw-r--r--Firebase/Storage/Private/FIRStorageConstants_Private.h145
-rw-r--r--Firebase/Storage/Private/FIRStorageDeleteTask.h34
-rw-r--r--Firebase/Storage/Private/FIRStorageDownloadTask_Private.h52
-rw-r--r--Firebase/Storage/Private/FIRStorageErrors.h54
-rw-r--r--Firebase/Storage/Private/FIRStorageGetMetadataTask.h34
-rw-r--r--Firebase/Storage/Private/FIRStorageMetadata_Private.h52
-rw-r--r--Firebase/Storage/Private/FIRStorageObservableTask_Private.h45
-rw-r--r--Firebase/Storage/Private/FIRStoragePath.h106
-rw-r--r--Firebase/Storage/Private/FIRStorageReference_Private.h37
-rw-r--r--Firebase/Storage/Private/FIRStorageTaskSnapshot_Private.h56
-rw-r--r--Firebase/Storage/Private/FIRStorageTask_Private.h77
-rw-r--r--Firebase/Storage/Private/FIRStorageTokenAuthorizer.h44
-rw-r--r--Firebase/Storage/Private/FIRStorageUpdateMetadataTask.h35
-rw-r--r--Firebase/Storage/Private/FIRStorageUploadTask_Private.h69
-rw-r--r--Firebase/Storage/Private/FIRStorageUtils.h93
-rw-r--r--Firebase/Storage/Private/FIRStorage_Private.h38
44 files changed, 4539 insertions, 0 deletions
diff --git a/Firebase/Storage/FIRStorage.h b/Firebase/Storage/FIRStorage.h
new file mode 100644
index 0000000..3b37a0e
--- /dev/null
+++ b/Firebase/Storage/FIRStorage.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2017 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>
+
+#import "FIRStorageConstants.h"
+#import "FIRStorageSwiftNameSupport.h"
+
+@class FIRApp;
+@class FIRStorageReference;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** Project version string for FirebaseStorage. */
+FOUNDATION_EXPORT const unsigned char *const FIRStorageVersionString;
+
+/**
+ * FirebaseStorage is a service that supports uploading and downloading binary objects,
+ * such as images, videos, and other files to Google Cloud Storage.
+ *
+ * If you call [FIRStorage storage], the instance will initialize with the default FIRApp,
+ * [FIRApp defaultApp], and the storage location will come from the provided
+ * GoogleService-Info.plist.
+ *
+ * If you call [FIRStorage storageForApp:] and provide a custom instance of FIRApp,
+ * the storage location will be specified via the FIROptions#storageBucket property.
+ */
+FIR_SWIFT_NAME(Storage)
+@interface FIRStorage : NSObject
+
+/**
+ * Creates an instance of FIRStorage, configured with the default FIRApp.
+ * @return the FIRStorage instance, initialized with the default FIRApp.
+ */
++ (instancetype)storage FIR_SWIFT_NAME(storage());
+
+/**
+ * Creates an instance of FIRStorage, configured with the custom FIRApp @a app.
+ * @param app The custom FIRApp used for initialization.
+ * @return the FIRStorage instance, initialized with the custom FIRApp.
+ */
++ (instancetype)storageForApp:(FIRApp *)app FIR_SWIFT_NAME(storage(app:));
+
+/**
+ * Creates an instance of FIRStorage, configured with a custom storage bucket @a url.
+ * @param url The gs:// url to your Firebase Storage Bucket.
+ * @return the FIRStorage instance, initialized with the custom FIRApp.
+ */
++ (instancetype)storageWithURL:(NSString *)url FIR_SWIFT_NAME(storage(url:));
+
+/**
+ * Creates an instance of FIRStorage, configured with a custom FIRApp @a app and a custom storage
+ * bucket @a url.
+ * @param app The custom FIRApp used for initialization.
+ * @param url The gs:// url to your Firebase Storage Bucket.
+ * @return the FIRStorage instance, initialized with the custom FIRApp.
+ */
++ (instancetype)storageForApp:(FIRApp *)app
+ URL:(NSString *)url FIR_SWIFT_NAME(storage(app:url:));
+
+/**
+ * The Firebase App associated with this Firebase Storage instance.
+ */
+@property(strong, nonatomic, readonly) FIRApp *app;
+
+/**
+ * Maximum time in seconds to retry an upload if a failure occurs.
+ * Defaults to 10 minutes (600 seconds).
+ */
+@property NSTimeInterval maxUploadRetryTime;
+
+/**
+ * Maximum time in seconds to retry a download if a failure occurs.
+ * Defaults to 10 minutes (600 seconds).
+ */
+@property NSTimeInterval maxDownloadRetryTime;
+
+/**
+ * Maximum time in seconds to retry operations other than upload and download if a failure occurs.
+ * Defaults to 2 minutes (120 seconds).
+ */
+@property NSTimeInterval maxOperationRetryTime;
+
+/**
+ * Queue that all developer callbacks are fired on. Defaults to the main queue.
+ */
+@property(strong, nonatomic) dispatch_queue_t callbackQueue;
+
+/**
+ * Creates a FIRStorageReference initialized at the root Firebase Storage location.
+ * @return An instance of FIRStorageReference initialized at the root.
+ */
+- (FIRStorageReference *)reference;
+
+/**
+ * Creates a FIRStorageReference given a gs:// or https:// URL pointing to a Firebase Storage
+ * location. For example, you can pass in an https:// download URL retrieved from
+ * [FIRStorageReference downloadURLWithCompletion] or the gs:// URI from
+ * [FIRStorageReference description].
+ * @param string A gs:// or https:// URL to initialize the reference with.
+ * @return An instance of FIRStorageReference at the given child path.
+ * @throws Throws an exception if passed in URL is not associated with the FIRApp used to initialize
+ * this FIRStorage.
+ */
+- (FIRStorageReference *)referenceForURL:(NSString *)string;
+
+/**
+ * Creates a FIRStorageReference initialized at a child Firebase Storage location.
+ * @param string A relative path from the root to initialize the reference with,
+ * for instance @"path/to/object".
+ * @return An instance of FIRStorageReference at the given child path.
+ */
+- (FIRStorageReference *)referenceWithPath:(NSString *)string;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/FIRStorage.m b/Firebase/Storage/FIRStorage.m
new file mode 100644
index 0000000..dd11391
--- /dev/null
+++ b/Firebase/Storage/FIRStorage.m
@@ -0,0 +1,233 @@
+// Copyright 2017 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 "FIRStorage.h"
+
+#import "FIRStorageConstants_Private.h"
+#import "FIRStoragePath.h"
+#import "FIRStorageReference.h"
+#import "FIRStorageReference_Private.h"
+#import "FIRStorageTokenAuthorizer.h"
+#import "FIRStorageUtils.h"
+#import "FIRStorage_Private.h"
+
+#import "FIRApp.h"
+#import "FIROptions.h"
+
+#import <GTMSessionFetcher/GTMSessionFetcher.h>
+#import <GTMSessionFetcher/GTMSessionFetcherLogging.h>
+
+static NSMutableDictionary<
+ NSString * /* app name */,
+ NSMutableDictionary<NSString * /* bucket */, GTMSessionFetcherService *> *> *_fetcherServiceMap;
+static GTMSessionFetcherRetryBlock _retryWhenOffline;
+
+@implementation FIRStorage
+
++ (void)initialize {
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ _retryWhenOffline = ^(BOOL suggestedWillRetry,
+ NSError * GTM_NULLABLE_TYPE error,
+ GTMSessionFetcherRetryResponse response) {
+ bool shouldRetry = suggestedWillRetry;
+ // GTMSessionFetcher does not consider being offline a retryable error, but we do, so we
+ // special-case it here.
+ if (!shouldRetry && error) {
+ shouldRetry = error.code == NSURLErrorNotConnectedToInternet;
+ }
+ response(shouldRetry);
+ };
+ _fetcherServiceMap = [[NSMutableDictionary alloc] init];
+ });
+}
+
++ (GTMSessionFetcherService *)fetcherServiceForApp:(FIRApp *)app bucket:(NSString *)bucket {
+ @synchronized(_fetcherServiceMap) {
+ NSMutableDictionary *bucketMap = _fetcherServiceMap[app.name];
+ if (!bucketMap) {
+ bucketMap = [[NSMutableDictionary alloc] init];
+ _fetcherServiceMap[app.name] = bucketMap;
+ }
+
+ GTMSessionFetcherService *fetcherService = bucketMap[bucket];
+ if (!fetcherService) {
+ fetcherService = [[GTMSessionFetcherService alloc] init];
+ [fetcherService setRetryEnabled:YES];
+ [fetcherService setRetryBlock:_retryWhenOffline];
+ FIRStorageTokenAuthorizer *authorizer =
+ [[FIRStorageTokenAuthorizer alloc] initWithApp:app fetcherService:fetcherService];
+ [fetcherService setAuthorizer:authorizer];
+ bucketMap[bucket] = fetcherService;
+ }
+ return fetcherService;
+ }
+}
+
++ (void)setGTMSessionFetcherLoggingEnabled:(BOOL)isLoggingEnabled {
+ [GTMSessionFetcher setLoggingEnabled:isLoggingEnabled];
+}
+
++ (instancetype)storage {
+ return [self storageForApp:[FIRApp defaultApp]];
+}
+
++ (instancetype)storageForApp:(FIRApp *)app {
+ NSString* url;
+
+ if (app.options.storageBucket) {
+ url = [app.options.storageBucket isEqualToString:@""] ? @""
+ : [@"gs://" stringByAppendingString:app.options.storageBucket];
+ }
+
+ return [self storageForApp:app URL:url];
+}
+
++ (instancetype)storageWithURL:(NSString *)url {
+ return [self storageForApp:[FIRApp defaultApp] URL:url];
+}
+
++ (instancetype)storageForApp:(FIRApp *)app URL:(NSString *)url {
+ if (!url) {
+ NSString *const kAppNotConfiguredMessage =
+ @"No default Storage bucket found. Did you configure Firebase Storage properly?";
+ [NSException raise:NSInvalidArgumentException format:kAppNotConfiguredMessage];
+ }
+
+ NSString *bucket;
+ if ([url isEqualToString:@""]) {
+ bucket = @"";
+ } else {
+ FIRStoragePath *path;
+
+ @try {
+ path = [FIRStoragePath pathFromGSURI:url];
+ } @catch (NSException *e) {
+ [NSException raise:NSInternalInconsistencyException
+ format:@"URI must be in the form of gs://<bucket>/"];
+ }
+
+ if (path.object != nil && ![path.object isEqualToString:@""]) {
+ [NSException raise:NSInternalInconsistencyException
+ format:@"Storage bucket cannot be initialized with a path"];
+ }
+
+ bucket = path.bucket;
+ }
+
+ return [[self alloc] initWithApp:app bucket:bucket];
+}
+
+- (instancetype)initWithApp:(FIRApp *)app bucket:(NSString *)bucket {
+ self = [super init];
+ if (self) {
+ _app = app;
+ _storageBucket = bucket;
+ _fetcherServiceForApp = [FIRStorage fetcherServiceForApp:_app bucket:bucket];
+ _maxDownloadRetryTime = 600.0;
+ _maxOperationRetryTime = 120.0;
+ _maxUploadRetryTime = 600.0;
+ }
+ return self;
+}
+
+#pragma mark - NSObject overrides
+
+- (instancetype)copyWithZone:(NSZone *)zone {
+ FIRStorage *storage = [[[self class] allocWithZone:zone] initWithApp:_app bucket:_storageBucket];
+ storage.callbackQueue = _callbackQueue;
+ return storage;
+}
+
+// Two FIRStorage objects are equal if they use the same app
+- (BOOL)isEqual:(id)object {
+ if (self == object) {
+ return YES;
+ }
+
+ if (![object isKindOfClass:[FIRStorage class]]) {
+ return NO;
+ }
+
+ BOOL isEqualObject = [self isEqualToFIRStorage:(FIRStorage *)object];
+ return isEqualObject;
+}
+
+- (BOOL)isEqualToFIRStorage:(FIRStorage *)storage {
+ BOOL isEqual = [_app isEqual:storage->_app];
+ return isEqual;
+}
+
+- (NSUInteger)hash {
+ NSUInteger hash = [_app hash] ^ [_callbackQueue hash];
+ return hash;
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"%@ %p: %@", [self class], self, _app];
+}
+
+#pragma mark - Public methods
+
+- (FIRStorageReference *)reference {
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:_storageBucket object:nil];
+ return [[FIRStorageReference alloc] initWithStorage:self path:path];
+}
+
+- (FIRStorageReference *)referenceForURL:(NSString *)string {
+ FIRStoragePath *path = [FIRStoragePath pathFromString:string];
+
+ // If no default bucket exists (empty string), accept anything.
+ if ([_storageBucket isEqual:@""]) {
+ FIRStorageReference *reference = [[FIRStorageReference alloc] initWithStorage:self path:path];
+ return reference;
+ }
+
+ // If there exists a default bucket, throw if provided a different bucket.
+ if (![path.bucket isEqual:_storageBucket]) {
+ NSString *const kInvalidBucketFormat =
+ @"Provided bucket: %@ does not match the Storage bucket of the current instance: %@";
+ [NSException raise:NSInvalidArgumentException
+ format:kInvalidBucketFormat, path.bucket, _storageBucket];
+ }
+
+ FIRStorageReference *reference = [[FIRStorageReference alloc] initWithStorage:self path:path];
+ return reference;
+}
+
+- (FIRStorageReference *)referenceWithPath:(NSString *)string {
+ FIRStorageReference *reference = [[self reference] child:string];
+ return reference;
+}
+
+- (void)setCallbackQueue:(dispatch_queue_t)callbackQueue {
+ _fetcherServiceForApp.callbackQueue = callbackQueue;
+}
+
+#pragma mark - Background tasks
+
++ (void)enableBackgroundTasks:(BOOL)isEnabled {
+ [NSException raise:NSGenericException format:@"enableBackgroundTasks not implemented"];
+}
+
+- (NSArray<FIRStorageUploadTask *> *)uploadTasks {
+ [NSException raise:NSGenericException format:@"getUploadTasks not implemented"];
+ return nil;
+}
+
+- (NSArray<FIRStorageDownloadTask *> *)downloadTasks {
+ [NSException raise:NSGenericException format:@"getDownloadTasks not implemented"];
+ return nil;
+}
+@end
diff --git a/Firebase/Storage/FIRStorageConstants.h b/Firebase/Storage/FIRStorageConstants.h
new file mode 100644
index 0000000..cf6c3b8
--- /dev/null
+++ b/Firebase/Storage/FIRStorageConstants.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2017 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>
+
+#import "FIRStorageSwiftNameSupport.h"
+
+@class FIRStorageDownloadTask;
+@class FIRStorageMetadata;
+@class FIRStorageTaskSnapshot;
+@class FIRStorageUploadTask;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * NSString typedef representing a task listener handle.
+ */
+typedef NSString *FIRStorageHandle FIR_SWIFT_NAME(StorageHandle);
+
+/**
+ * Block typedef typically used when downloading data.
+ * @param data The data returned by the download, or nil if no data available or download failed.
+ * @param error The error describing failure, if one occurred.
+ */
+typedef void (^FIRStorageVoidDataError)(NSData *_Nullable data, NSError *_Nullable error)
+ FIR_SWIFT_NAME(StorageVoidDataError);
+
+/**
+ * Block typedef typically used when performing "binary" async operations such as delete,
+ * where the operation either succeeds without an error or fails with an error.
+ * @param error The error describing failure, if one occurred.
+ */
+typedef void (^FIRStorageVoidError)(NSError *_Nullable error) FIR_SWIFT_NAME(StorageVoidError);
+
+/**
+ * Block typedef typically used when retrieving metadata.
+ * @param metadata The metadata returned by the operation, if metadata exists.
+ */
+typedef void (^FIRStorageVoidMetadata)(FIRStorageMetadata *_Nullable metadata)
+ FIR_SWIFT_NAME(StorageVoidMetadata);
+
+/**
+ * Block typedef typically used when retrieving metadata with the possibility of an error.
+ * @param metadata The metadata returned by the operation, if metadata exists.
+ * @param error The error describing failure, if one occurred.
+ */
+typedef void (^FIRStorageVoidMetadataError)(FIRStorageMetadata *_Nullable metadata,
+ NSError *_Nullable error)
+ FIR_SWIFT_NAME(StorageVoidMetadataError);
+
+/**
+ * Block typedef typically used to asynchronously return a storage task snapshot.
+ * @param snapshot The returned task snapshot.
+ */
+typedef void (^FIRStorageVoidSnapshot)(FIRStorageTaskSnapshot *snapshot)
+ FIR_SWIFT_NAME(StorageVoidSnapshot);
+
+/**
+ * Block typedef typically used when retrieving a download URL.
+ * @param URL The download URL associated with the operation.
+ * @param error The error describing failure, if one occurred.
+ */
+typedef void (^FIRStorageVoidURLError)(NSURL *_Nullable URL, NSError *_Nullable error)
+ FIR_SWIFT_NAME(StorageVoidURLError);
+
+/**
+ * Enum representing the upload and download task status.
+ */
+typedef NS_ENUM(NSInteger, FIRStorageTaskStatus) {
+ /**
+ * Unknown task status.
+ */
+ FIRStorageTaskStatusUnknown,
+
+ /**
+ * Task is being resumed.
+ */
+ FIRStorageTaskStatusResume,
+
+ /**
+ * Task reported a progress event.
+ */
+ FIRStorageTaskStatusProgress,
+
+ /**
+ * Task is paused.
+ */
+ FIRStorageTaskStatusPause,
+
+ /**
+ * Task has completed successfully.
+ */
+ FIRStorageTaskStatusSuccess,
+
+ /**
+ * Task has failed and is unrecoverable.
+ */
+ FIRStorageTaskStatusFailure
+} FIR_SWIFT_NAME(StorageTaskStatus);
+
+/**
+ * Firebase Storage error domain.
+ */
+FOUNDATION_EXPORT NSString *const FIRStorageErrorDomain FIR_SWIFT_NAME(StorageErrorDomain);
+
+/**
+ * Enum representing the errors raised by Firebase Storage.
+ */
+typedef NS_ENUM(NSInteger, FIRStorageErrorCode) {
+ /** An unknown error occurred. */
+ FIRStorageErrorCodeUnknown = -13000,
+
+ /** No object exists at the desired reference. */
+ FIRStorageErrorCodeObjectNotFound = -13010,
+
+ /** No bucket is configured for Firebase Storage. */
+ FIRStorageErrorCodeBucketNotFound = -13011,
+
+ /** No project is configured for Firebase Storage. */
+ FIRStorageErrorCodeProjectNotFound = -13012,
+
+ /**
+ * Quota on your Firebase Storage bucket has been exceeded.
+ * If you're on the free tier, upgrade to a paid plan.
+ * If you're on a paid plan, reach out to Firebase support.
+ */
+ FIRStorageErrorCodeQuotaExceeded = -13013,
+
+ /** User is unauthenticated. Authenticate and try again. */
+ FIRStorageErrorCodeUnauthenticated = -13020,
+
+ /**
+ * User is not authorized to perform the desired action.
+ * Check your rules to ensure they are correct.
+ */
+ FIRStorageErrorCodeUnauthorized = -13021,
+
+ /**
+ * The maximum time limit on an operation (upload, download, delete, etc.) has been exceeded.
+ * Try uploading again.
+ */
+ FIRStorageErrorCodeRetryLimitExceeded = -13030,
+
+ /**
+ * File on the client does not match the checksum of the file received by the server.
+ * Try uploading again.
+ */
+ FIRStorageErrorCodeNonMatchingChecksum = -13031,
+
+ /**
+ * Size of the downloaded file exceeds the amount of memory allocated for the download.
+ * Increase memory cap and try downloading again.
+ */
+ FIRStorageErrorCodeDownloadSizeExceeded = -13032,
+
+ /** User cancelled the operation. */
+ FIRStorageErrorCodeCancelled = -13040
+} FIR_SWIFT_NAME(StorageErrorCode);
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/FIRStorageConstants.m b/Firebase/Storage/FIRStorageConstants.m
new file mode 100644
index 0000000..aa3da1b
--- /dev/null
+++ b/Firebase/Storage/FIRStorageConstants.m
@@ -0,0 +1,83 @@
+// Copyright 2017 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 "FIRStorageConstants.h"
+
+#import "FIRStorageConstants_Private.h"
+
+NSString *const kGCSScheme = @"https";
+NSString *const kGCSHost = @"www.googleapis.com";
+NSString *const kGCSUploadPath = @"upload";
+NSString *const kGCSStorageVersionPath = @"storage/v1";
+NSString *const kGCSBucketPathFormat = @"b/%@";
+NSString *const kGCSObjectPathFormat = @"o/%@";
+
+NSString *const kFIRStorageScheme = @"https";
+NSString *const kFIRStorageHost = @"firebasestorage.googleapis.com";
+NSString *const kFIRStorageVersionPath = @"v0";
+NSString *const kFIRStorageBucketPathFormat = @"b/%@";
+NSString *const kFIRStorageObjectPathFormat = @"o/%@";
+NSString *const kFIRStorageFullPathFormat = @"/v0/b/%@/o/%@";
+
+NSString *const kFIRStorageAuthTokenFormat = @"Firebase %@";
+NSString *const kFIRStorageDefaultBucketFormat = @"gs://%@";
+
+NSString *const kFIRStorageResponseErrorDomain = @"ResponseErrorDomain";
+NSString *const kFIRStorageResponseErrorCode = @"ResponseErrorCode";
+NSString *const kFIRStorageResponseBody = @"ResponseBody";
+
+NSString *const FIRStorageErrorDomain = @"FIRStorageErrorDomain";
+
+NSString *const kFIRStorageInvalidDataFormat = @"Invalid data returned from the server: %@";
+NSString *const kFIRStorageInvalidObserverStatus = @"Invalid observer status requested, use one "
+ @"of: FIRStorageTaskStatusPause, Resume, Progress, " @"Complete, or Failure";
+
+/**
+ * String constants mapping GCS Object#resource mappings to metadata fields.
+ */
+NSString *const kFIRStorageMetadataBucket = @"bucket";
+NSString *const kFIRStorageMetadataCacheControl = @"cacheControl";
+NSString *const kFIRStorageMetadataContentDisposition = @"contentDisposition";
+NSString *const kFIRStorageMetadataContentEncoding = @"contentEncoding";
+NSString *const kFIRStorageMetadataContentLanguage = @"contentLanguage";
+NSString *const kFIRStorageMetadataContentType = @"contentType";
+NSString *const kFIRStorageMetadataCustomMetadata = @"metadata";
+NSString *const kFIRStorageMetadataSize = @"size";
+NSString *const kFIRStorageMetadataDownloadURLs = @"downloadURLs";
+NSString *const kFIRStorageMetadataGeneration = @"generation";
+NSString *const kFIRStorageMetadataMetageneration = @"metageneration";
+NSString *const kFIRStorageMetadataTimeCreated = @"timeCreated";
+NSString *const kFIRStorageMetadataUpdated = @"updated";
+NSString *const kFIRStorageMetadataName = @"name";
+NSString *const kFIRStorageMetadataDownloadTokens = @"downloadTokens";
+
+// TODO: add notification support
+NSString *const kFIRStorageTaskStatusResumeNotification =
+ @"kFIRStorageTaskStatusResumeNotification";
+NSString *const kFIRStorageTaskStatusPauseNotification = @"kFIRStorageTaskStatusResumeNotification";
+NSString *const kFIRStorageTaskStatusProgressNotification =
+ @"kFIRStorageTaskStatusResumeNotification";
+NSString *const kFIRStorageTaskStatusCompleteNotification =
+ @"kFIRStorageTaskStatusResumeNotification";
+NSString *const kFIRStorageTaskStatusFailureNotification =
+ @"kFIRStorageTaskStatusResumeNotification";
+
+NSString *const kFIRStorageBundleIdentifier = @"com.google.firebase.storage";
+
+// The STR and STR_EXPAND macro allow a numeric version passed to he compiler driver
+// with a -D to be treated as a string instead of an invalid floating point value.
+#define STR(x) STR_EXPAND(x)
+#define STR_EXPAND(x) #x
+const unsigned char *const FIRStorageVersionString =
+ (const unsigned char *const) STR(FIRStorage_VERSION);
diff --git a/Firebase/Storage/FIRStorageDeleteTask.m b/Firebase/Storage/FIRStorageDeleteTask.m
new file mode 100644
index 0000000..4f3f1cc
--- /dev/null
+++ b/Firebase/Storage/FIRStorageDeleteTask.m
@@ -0,0 +1,54 @@
+// Copyright 2017 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 "FIRStorageDeleteTask.h"
+
+#import "FIRStorageTask_Private.h"
+
+@implementation FIRStorageDeleteTask {
+ @private
+ FIRStorageVoidError _completion;
+}
+
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service
+ completion:(FIRStorageVoidError)completion {
+ self = [super initWithReference:reference fetcherService:service];
+ if (self) {
+ _completion = [completion copy];
+ }
+ return self;
+}
+
+- (void)enqueue {
+ NSMutableURLRequest *request = [self.baseRequest mutableCopy];
+ request.HTTPMethod = @"DELETE";
+ request.timeoutInterval = self.reference.storage.maxOperationRetryTime;
+
+ FIRStorageVoidError callback = _completion;
+ _completion = nil;
+
+ GTMSessionFetcher *fetcher = [self.fetcherService fetcherWithRequest:request];
+ fetcher.comment = @"DeleteTask";
+ [fetcher beginFetchWithCompletionHandler:^(NSData *_Nullable data, NSError *_Nullable error) {
+ if (!self.error) {
+ self.error = [FIRStorageErrors errorWithServerError:error reference:self.reference];
+ }
+ if (callback) {
+ callback(self.error);
+ }
+ }];
+}
+
+@end
diff --git a/Firebase/Storage/FIRStorageDownloadTask.h b/Firebase/Storage/FIRStorageDownloadTask.h
new file mode 100644
index 0000000..252b910
--- /dev/null
+++ b/Firebase/Storage/FIRStorageDownloadTask.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017 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>
+
+#import "FIRStorageObservableTask.h"
+#import "FIRStorageSwiftNameSupport.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * FIRStorageDownloadTask implements resumable downloads from an object in Firebase Storage.
+ * Downloads can be returned on completion with a completion handler, and can be monitored
+ * by attaching observers, or controlled by calling FIRStorageTask#pause, FIRStorageTask#resume,
+ * or FIRStorageTask#cancel.
+ * Downloads can currently be returned as NSData in memory, or as an NSURL to a file on disk.
+ * Downloads are performed on a background queue, and callbacks are raised on the developer
+ * specified callbackQueue in FIRStorage, or the main queue if left unspecified.
+ * Currently all uploads must be initiated and managed on the main queue.
+ */
+FIR_SWIFT_NAME(StorageDownloadTask)
+@interface FIRStorageDownloadTask : FIRStorageObservableTask<FIRStorageTaskManagement>
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/FIRStorageDownloadTask.m b/Firebase/Storage/FIRStorageDownloadTask.m
new file mode 100644
index 0000000..0d71e52
--- /dev/null
+++ b/Firebase/Storage/FIRStorageDownloadTask.m
@@ -0,0 +1,162 @@
+// Copyright 2017 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 "FIRStorageDownloadTask.h"
+
+#import "FIRStorageConstants_Private.h"
+#import "FIRStorageDownloadTask_Private.h"
+#import "FIRStorageObservableTask_Private.h"
+#import "FIRStorageTask_Private.h"
+
+@implementation FIRStorageDownloadTask
+
+@synthesize progress = _progress;
+@synthesize fetcher = _fetcher;
+
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service
+ file:(nullable NSURL *)fileURL {
+ self = [super initWithReference:reference fetcherService:service];
+ if (self) {
+ _fileURL = [fileURL copy];
+ _progress = [NSProgress progressWithTotalUnitCount:0];
+ }
+ return self;
+}
+
+- (void)enqueue {
+ [self enqueueWithData:nil];
+}
+
+- (void)enqueueWithData:(nullable NSData *)resumeData {
+ NSAssert([NSThread isMainThread], @"Download attempting to execute on non main queue! Please "
+ @"only execute this method on the main queue.");
+ self.state = FIRStorageTaskStateQueueing;
+ NSMutableURLRequest *request = [self.baseRequest mutableCopy];
+ request.HTTPMethod = @"GET";
+ request.timeoutInterval = self.reference.storage.maxDownloadRetryTime;
+ NSURLComponents *components =
+ [NSURLComponents componentsWithURL:request.URL resolvingAgainstBaseURL:NO];
+ [components setQuery:@"alt=media"];
+ request.URL = components.URL;
+
+ GTMSessionFetcher *fetcher;
+ if (resumeData) {
+ fetcher = [GTMSessionFetcher fetcherWithDownloadResumeData:resumeData];
+ fetcher.comment = @"Resuming DownloadTask";
+ } else {
+ fetcher = [self.fetcherService fetcherWithRequest:request];
+ fetcher.comment = @"Starting DownloadTask";
+ }
+
+ [fetcher setResumeDataBlock:^(NSData *data) {
+ if (data) {
+ _downloadData = data;
+ }
+ }];
+
+ fetcher.maxRetryInterval = self.reference.storage.maxDownloadRetryTime;
+
+ if (_fileURL) {
+ // Handle file downloads
+ [fetcher setDestinationFileURL:_fileURL];
+ [fetcher setDownloadProgressBlock:^(int64_t bytesWritten, int64_t totalBytesWritten,
+ int64_t totalBytesExpectedToWrite) {
+ self.state = FIRStorageTaskStateProgress;
+ self.progress.completedUnitCount = totalBytesWritten;
+ self.progress.totalUnitCount = totalBytesExpectedToWrite;
+ FIRStorageTaskSnapshot *snapshot = self.snapshot;
+ [self fireHandlersForStatus:FIRStorageTaskStatusProgress snapshot:snapshot];
+ self.state = FIRStorageTaskStateRunning;
+ }];
+ } else {
+ // Handle data downloads
+ [fetcher setReceivedProgressBlock:^(int64_t bytesWritten, int64_t totalBytesWritten) {
+ self.state = FIRStorageTaskStateProgress;
+ self.progress.completedUnitCount = totalBytesWritten;
+ int64_t totalLength = [[self.fetcher response] expectedContentLength];
+ self.progress.totalUnitCount = totalLength;
+ FIRStorageTaskSnapshot *snapshot = self.snapshot;
+ [self fireHandlersForStatus:FIRStorageTaskStatusProgress snapshot:snapshot];
+ self.state = FIRStorageTaskStateRunning;
+ }];
+ }
+
+ _fetcher = fetcher;
+
+ self.state = FIRStorageTaskStateRunning;
+ [self.fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
+ // Fire last progress updates
+ [self fireHandlersForStatus:FIRStorageTaskStatusProgress snapshot:self.snapshot];
+
+ // Handle potential issues with download
+ if (error) {
+ self.state = FIRStorageTaskStateFailed;
+ self.error = [FIRStorageErrors errorWithServerError:error reference:self.reference];
+ [self fireHandlersForStatus:FIRStorageTaskStatusFailure snapshot:self.snapshot];
+ [self removeAllObservers];
+ return;
+ }
+
+ // Download completed successfully, fire completion callbacks
+ self.state = FIRStorageTaskStateSuccess;
+
+ if (data) {
+ _downloadData = data;
+ }
+
+ [self fireHandlersForStatus:FIRStorageTaskStatusSuccess snapshot:self.snapshot];
+ [self removeAllObservers];
+ }];
+}
+
+#pragma mark - Download Management
+
+- (void)cancel {
+ NSError *error = [FIRStorageErrors errorWithCode:FIRStorageErrorCodeCancelled];
+ [self cancelWithError:error];
+}
+
+- (void)cancelWithError:(NSError *)error {
+ NSAssert([NSThread isMainThread], @"Cancel attempting to execute on non main queue! Please only "
+ @"execute this method on the main queue.");
+ self.state = FIRStorageTaskStateCancelled;
+ [self.fetcher stopFetching];
+ self.error = error;
+ [self fireHandlersForStatus:FIRStorageTaskStatusFailure snapshot:self.snapshot];
+}
+
+- (void)pause {
+ NSAssert([NSThread isMainThread], @"Pause attempting to execute on non main queue! Please only "
+ @"execute this method on the main queue.");
+ self.state = FIRStorageTaskStatePausing;
+ [self.fetcher stopFetching];
+ // Give the resume callback a chance to run (if scheduled)
+ [self.fetcher waitForCompletionWithTimeout:0.001];
+ self.state = FIRStorageTaskStatePaused;
+ FIRStorageTaskSnapshot *snapshot = self.snapshot;
+ [self fireHandlersForStatus:FIRStorageTaskStatusPause snapshot:snapshot];
+}
+
+- (void)resume {
+ NSAssert([NSThread isMainThread], @"Resume attempting to execute on non main queue! Please only "
+ @"execute this method on the main queue.");
+ self.state = FIRStorageTaskStateResuming;
+ FIRStorageTaskSnapshot *snapshot = self.snapshot;
+ [self fireHandlersForStatus:FIRStorageTaskStatusResume snapshot:snapshot];
+ self.state = FIRStorageTaskStateRunning;
+ [self enqueueWithData:_downloadData];
+}
+
+@end
diff --git a/Firebase/Storage/FIRStorageErrors.m b/Firebase/Storage/FIRStorageErrors.m
new file mode 100644
index 0000000..49a5ffa
--- /dev/null
+++ b/Firebase/Storage/FIRStorageErrors.m
@@ -0,0 +1,172 @@
+// Copyright 2017 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 "FIRStorageErrors.h"
+
+#import "FIRStorageConstants_Private.h"
+#import "FIRStorageReference.h"
+#import "FIRStorageReference_Private.h"
+
+@implementation FIRStorageErrors
+
++ (NSError *)errorWithCode:(FIRStorageErrorCode)code {
+ return [FIRStorageErrors errorWithCode:code infoDictionary:nil];
+}
+
++ (NSError *)errorWithCode:(FIRStorageErrorCode)code
+ infoDictionary:(nullable NSDictionary *)dictionary {
+ NSMutableDictionary *errorDictionary;
+ if (dictionary) {
+ errorDictionary = [dictionary mutableCopy];
+ } else {
+ errorDictionary = [[NSMutableDictionary alloc] init];
+ }
+
+ NSString *errorMessage;
+ switch (code) {
+ case FIRStorageErrorCodeObjectNotFound:
+ errorMessage =
+ [NSString stringWithFormat:@"Object %@ does not exist.", errorDictionary[@"object"]];
+ break;
+
+ case FIRStorageErrorCodeBucketNotFound:
+ errorMessage =
+ [NSString stringWithFormat:@"Bucket %@ does not exist.", errorDictionary[@"bucket"]];
+ break;
+
+ case FIRStorageErrorCodeProjectNotFound:
+ errorMessage =
+ [NSString stringWithFormat:@"Project %@ does not exist.", errorDictionary[@"project"]];
+ break;
+
+ case FIRStorageErrorCodeQuotaExceeded: {
+ NSString *const kQuotaExceededFormat =
+ @"Quota for bucket %@ exceeded, please view quota on firebase.google.com.";
+ errorMessage = [NSString stringWithFormat:kQuotaExceededFormat, errorDictionary[@"bucket"]];
+ break;
+ }
+
+ case FIRStorageErrorCodeDownloadSizeExceeded: {
+ int64_t total = [errorDictionary[@"totalSize"] longLongValue];
+ int64_t size = [errorDictionary[@"maxAllowedSize"] longLongValue];
+ NSString *totalString = total ? @(total).stringValue : @"unknown";
+ NSString *sizeString = total ? @(size).stringValue : @"unknown";
+ NSString *const kSizeExceededErrorFormat =
+ @"Attempeted to download object with size of %@ bytes, "
+ @"which exceeds the maximum size of %@ bytes. "
+ @"Consider raising the maximum download size, or using "
+ @"[FIRStorageReference writeToFile:]";
+ errorMessage = [NSString stringWithFormat:kSizeExceededErrorFormat, totalString, sizeString];
+ break;
+ }
+
+ case FIRStorageErrorCodeUnauthenticated:
+ errorMessage = @"User is not authenticated, please authenticate using Firebase "
+ @"Authentication and try again.";
+ break;
+
+ case FIRStorageErrorCodeUnauthorized: {
+ NSString *bucket = errorDictionary[@"bucket"];
+ NSString *object = errorDictionary[@"object"];
+ NSString *const kUnauthorizedFormat = @"User does not have permission to access gs://%@/%@.";
+ errorMessage = [NSString stringWithFormat:kUnauthorizedFormat, bucket, object];
+ break;
+ }
+
+ case FIRStorageErrorCodeRetryLimitExceeded:
+ errorMessage = @"Max retry time for operation exceeded, please try again.";
+ break;
+
+ case FIRStorageErrorCodeNonMatchingChecksum: {
+ // TODO: replace with actual checksum strings when we choose to implement.
+ NSString *const kChecksumFailedErrorFormat =
+ @"Uploaded/downloaded object %@ has checksum: %@ "
+ @"which does not match server checksum: %@. Please retry the upload/download.";
+ errorMessage = [NSString stringWithFormat:kChecksumFailedErrorFormat, @"object",
+ @"client checksum", @"server checksum"];
+ break;
+ }
+
+ case FIRStorageErrorCodeCancelled:
+ errorMessage = @"User cancelled the upload/download.";
+ break;
+
+ case FIRStorageErrorCodeUnknown:
+ /* Fall through to default case for unknown errors */
+
+ default:
+ errorMessage = @"An unknown error occurred, please check the server response.";
+ break;
+ }
+
+ errorDictionary[NSLocalizedDescriptionKey] = errorMessage;
+
+ NSError *err = [NSError errorWithDomain:FIRStorageErrorDomain code:code userInfo:errorDictionary];
+ return err;
+}
+
++ (nullable NSError *)errorWithServerError:(nullable NSError *)error
+ reference:(nullable FIRStorageReference *)reference {
+ if (error == nil) {
+ return nil;
+ }
+
+ FIRStorageErrorCode errorCode;
+ switch (error.code) {
+ case 400:
+ errorCode = FIRStorageErrorCodeUnknown;
+ break;
+
+ case 401:
+ errorCode = FIRStorageErrorCodeUnauthenticated;
+ break;
+
+ case 402:
+ errorCode = FIRStorageErrorCodeQuotaExceeded;
+ break;
+
+ case 403:
+ errorCode = FIRStorageErrorCodeUnauthorized;
+ break;
+
+ case 404:
+ errorCode = FIRStorageErrorCodeObjectNotFound;
+ break;
+
+ default:
+ errorCode = FIRStorageErrorCodeUnknown;
+ break;
+ }
+
+ NSMutableDictionary *errorDictionary =
+ [[[NSDictionary alloc] initWithDictionary:error.userInfo] mutableCopy];
+ errorDictionary[kFIRStorageResponseErrorDomain] = error.domain;
+ errorDictionary[kFIRStorageResponseErrorCode] = @(error.code);
+
+ // Turn raw response into a string
+ NSData *responseData = errorDictionary[@"data"];
+ if (responseData) {
+ NSString *errorString =
+ [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
+ errorDictionary[kFIRStorageResponseBody] = errorString ?: @"No Response from Server.";
+ }
+
+ errorDictionary[@"bucket"] = reference.path.bucket;
+ errorDictionary[@"object"] = reference.path.object;
+
+ NSError *clientError = [FIRStorageErrors errorWithCode:errorCode infoDictionary:errorDictionary];
+ return clientError;
+}
+
+@end
diff --git a/Firebase/Storage/FIRStorageGetMetadataTask.m b/Firebase/Storage/FIRStorageGetMetadataTask.m
new file mode 100644
index 0000000..d0e8981
--- /dev/null
+++ b/Firebase/Storage/FIRStorageGetMetadataTask.m
@@ -0,0 +1,84 @@
+// Copyright 2017 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 "FIRStorageGetMetadataTask.h"
+
+#import "FIRStorageConstants.h"
+#import "FIRStorageMetadata_Private.h"
+#import "FIRStorageTask_Private.h"
+#import "FIRStorageUtils.h"
+
+#import "FirebaseStorage.h"
+
+@implementation FIRStorageGetMetadataTask {
+ @private
+ FIRStorageVoidMetadataError _completion;
+}
+
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service
+ completion:(FIRStorageVoidMetadataError)completion {
+ self = [super initWithReference:reference fetcherService:service];
+ if (self) {
+ _completion = [completion copy];
+ }
+ return self;
+}
+
+- (void)enqueue {
+ NSMutableURLRequest *request = [self.baseRequest mutableCopy];
+ request.HTTPMethod = @"GET";
+ request.timeoutInterval = self.reference.storage.maxDownloadRetryTime;
+
+ FIRStorageVoidMetadataError callback = _completion;
+ _completion = nil;
+
+ GTMSessionFetcher *fetcher = [self.fetcherService fetcherWithRequest:request];
+ fetcher.comment = @"GetMetadataTask";
+ [fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
+ if (error) {
+ if (!self.error) {
+ self.error = [FIRStorageErrors errorWithServerError:error reference:self.reference];
+ }
+ if (callback) {
+ callback(nil, self.error);
+ }
+ return;
+ }
+
+ NSDictionary *responseDictionary = [NSDictionary frs_dictionaryFromJSONData:data];
+ if (responseDictionary != nil) {
+ FIRStorageMetadata *metadata =
+ [[FIRStorageMetadata alloc] initWithDictionary:responseDictionary];
+ [metadata setType:FIRStorageMetadataTypeFile];
+ if (callback) {
+ callback(metadata, nil);
+ }
+ } else {
+ NSString *returnedData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ NSString *invalidDataString =
+ [NSString stringWithFormat:kFIRStorageInvalidDataFormat, returnedData];
+ NSDictionary *dict;
+ if (invalidDataString.length > 0) {
+ dict = @{NSLocalizedFailureReasonErrorKey : invalidDataString};
+ }
+ self.error = [FIRStorageErrors errorWithCode:FIRStorageErrorCodeUnknown infoDictionary:dict];
+ if (callback) {
+ callback(nil, self.error);
+ }
+ }
+ }];
+}
+
+@end
diff --git a/Firebase/Storage/FIRStorageMetadata.h b/Firebase/Storage/FIRStorageMetadata.h
new file mode 100644
index 0000000..8d844f7
--- /dev/null
+++ b/Firebase/Storage/FIRStorageMetadata.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2017 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>
+
+#import "FIRStorageSwiftNameSupport.h"
+
+@class FIRStorageReference;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Class which represents the metadata on an object in Firebase Storage. This metadata is
+ * returned on successful operations, and can be used to retrieve download URLs, content types,
+ * and a FIRStorage reference to the object in question. Full documentation can be found at the GCS
+ * Objects#resource docs.
+ * @see https://cloud.google.com/storage/docs/json_api/v1/objects#resource
+ */
+FIR_SWIFT_NAME(StorageMetadata)
+@interface FIRStorageMetadata : NSObject<NSCopying>
+
+/**
+ * The name of the bucket containing this object.
+ */
+@property(copy, nonatomic, readonly) NSString *bucket;
+
+/**
+ * Cache-Control directive for the object data.
+ */
+@property(copy, nonatomic, nullable) NSString *cacheControl;
+
+/**
+ * Content-Disposition of the object data.
+ */
+@property(copy, nonatomic, nullable) NSString *contentDisposition;
+
+/**
+ * Content-Encoding of the object data.
+ */
+@property(copy, nonatomic, nullable) NSString *contentEncoding;
+
+/**
+ * Content-Language of the object data.
+ */
+@property(copy, nonatomic, nullable) NSString *contentLanguage;
+
+/**
+ * Content-Type of the object data.
+ */
+@property(copy, nonatomic, nullable) NSString *contentType;
+
+/**
+ * The content generation of this object. Used for object versioning.
+ */
+@property(readonly) int64_t generation;
+
+/**
+ * User-provided metadata, in key/value pairs.
+ */
+@property(copy, nonatomic, nullable) NSDictionary<NSString *, NSString *> *customMetadata;
+
+/**
+ * The version of the metadata for this object at this generation. Used
+ * for preconditions and for detecting changes in metadata. A metageneration number is only
+ * meaningful in the context of a particular generation of a particular object.
+ */
+@property(readonly) int64_t metageneration;
+
+/**
+ * The name of this object, in gs://bucket/path/to/object.txt, this is object.txt.
+ */
+@property(copy, nonatomic, readonly, nullable) NSString *name;
+
+/**
+ * The full path of this object, in gs://bucket/path/to/object.txt, this is path/to/object.txt.
+ */
+@property(copy, nonatomic, readonly, nullable) NSString *path;
+
+/**
+ * Content-Length of the data in bytes.
+ */
+@property(readonly) int64_t size;
+
+/**
+ * The creation time of the object in RFC 3339 format.
+ */
+@property(copy, nonatomic, readonly, nullable) NSDate *timeCreated;
+
+/**
+ * The modification time of the object metadata in RFC 3339 format.
+ */
+@property(copy, nonatomic, readonly, nullable) NSDate *updated;
+
+/**
+ * A reference to the object in Firebase Storage.
+ */
+@property(strong, nonatomic, readonly, nullable) FIRStorageReference *storageReference;
+
+/**
+ * An array containing all download URLs available for the object.
+ */
+@property(strong, nonatomic, readonly, nullable) NSArray<NSURL *> *downloadURLs;
+
+/**
+ * Creates an instanece of FIRStorageMetadata from the contents of a dictionary.
+ * @return An instance of FIRStorageMetadata that represents the contents of a dictionary.
+ */
+- (nullable instancetype)initWithDictionary:(NSDictionary <NSString *, id>*)dictionary
+ NS_DESIGNATED_INITIALIZER;
+
+/**
+ * Creates an NSDictionary from the contents of the metadata.
+ * @return An NSDictionary that represents the contents of the metadata.
+ */
+- (NSDictionary <NSString *, id>*)dictionaryRepresentation;
+
+/**
+ * Determines if the current metadata represents a "file".
+ */
+@property(readonly, getter=isFile) BOOL file;
+
+/**
+ * Determines if the current metadata represents a "folder".
+ */
+@property(readonly, getter=isFolder) BOOL folder;
+
+/**
+ * Retrieves a download URL for the given object, or nil if none exist.
+ * Note that if there are many valid download tokens, this will always return the first
+ * valid token created.
+ */
+- (nullable NSURL *)downloadURL;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/FIRStorageMetadata.m b/Firebase/Storage/FIRStorageMetadata.m
new file mode 100644
index 0000000..6c85bbf
--- /dev/null
+++ b/Firebase/Storage/FIRStorageMetadata.m
@@ -0,0 +1,227 @@
+// Copyright 2017 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 "FIRStorageMetadata.h"
+
+#import "FIRStorageConstants.h"
+#import "FIRStorageConstants_Private.h"
+#import "FIRStorageMetadata_Private.h"
+#import "FIRStorageUtils.h"
+
+// TODO: consider rewriting this using GTLR (GTLRStorageObjects.h)
+@implementation FIRStorageMetadata
+
+#pragma mark - Initializers
+
+- (instancetype)init {
+ return [self initWithDictionary:[NSDictionary dictionary]];
+}
+
+- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
+ self = [super init];
+ if (self) {
+ _bucket = dictionary[kFIRStorageMetadataBucket];
+ _cacheControl = dictionary[kFIRStorageMetadataCacheControl];
+ _contentDisposition = dictionary[kFIRStorageMetadataContentDisposition];
+ _contentEncoding = dictionary[kFIRStorageMetadataContentEncoding];
+ _contentLanguage = dictionary[kFIRStorageMetadataContentLanguage];
+ _contentType = dictionary[kFIRStorageMetadataContentType];
+ _customMetadata = dictionary[kFIRStorageMetadataCustomMetadata];
+ _size = [dictionary[kFIRStorageMetadataSize] longLongValue];
+ _downloadURLs = dictionary[kFIRStorageMetadataDownloadURLs];
+ _generation = [dictionary[kFIRStorageMetadataGeneration] longLongValue];
+ _metageneration = [dictionary[kFIRStorageMetadataMetageneration] longLongValue];
+ _timeCreated = [self dateFromRFC3339String:dictionary[kFIRStorageMetadataTimeCreated]];
+ _updated = [self dateFromRFC3339String:dictionary[kFIRStorageMetadataUpdated]];
+ // GCS "name" is our path, our "name" is just the last path component of the path
+ _path = dictionary[kFIRStorageMetadataName];
+ _name = [_path lastPathComponent];
+ NSString *downloadTokens = dictionary[kFIRStorageMetadataDownloadTokens];
+ if (downloadTokens) {
+ NSArray<NSString *> *downloadStringArray = [downloadTokens componentsSeparatedByString:@","];
+ NSMutableArray<NSURL *> *downloadURLArray =
+ [[NSMutableArray alloc] initWithCapacity:[downloadStringArray count]];
+ [downloadStringArray enumerateObjectsUsingBlock:^(NSString *_Nonnull token, NSUInteger idx,
+ BOOL *_Nonnull stop) {
+ NSURLComponents *components = [[NSURLComponents alloc] init];
+ components.scheme = kFIRStorageScheme;
+ components.host = kFIRStorageHost;
+ NSString *path = [FIRStorageUtils GCSEscapedString:_path];
+ NSString *fullPath = [NSString stringWithFormat:kFIRStorageFullPathFormat, _bucket, path];
+ components.percentEncodedPath = fullPath;
+ components.query = [NSString stringWithFormat:@"alt=media&token=%@", token];
+
+ [downloadURLArray insertObject:[components URL] atIndex:idx];
+ }];
+ _downloadURLs = downloadURLArray;
+ }
+ }
+ return self;
+}
+
+#pragma mark - NSObject overrides
+
+- (instancetype)copyWithZone:(NSZone *)zone {
+ return [[[self class] allocWithZone:zone] initWithDictionary:[self dictionaryRepresentation]];
+}
+
+- (BOOL)isEqual:(id)object {
+ if (self == object) {
+ return YES;
+ }
+
+ if (![object isKindOfClass:[FIRStorageMetadata class]]) {
+ return NO;
+ }
+
+ BOOL isEqualObject = [self isEqualToFIRStorageMetadata:(FIRStorageMetadata *)object];
+ return isEqualObject;
+}
+
+- (BOOL)isEqualToFIRStorageMetadata:(FIRStorageMetadata *)metadata {
+ return [[self dictionaryRepresentation] isEqualToDictionary:[metadata dictionaryRepresentation]];
+}
+
+- (NSUInteger)hash {
+ NSUInteger hash = [[self dictionaryRepresentation] hash];
+ return hash;
+}
+
+- (NSString *)description {
+ NSDictionary *metadataDictionary = [self dictionaryRepresentation];
+ return [NSString stringWithFormat:@"%@ %p: %@", [self class], self, metadataDictionary];
+}
+
+#pragma mark - Public methods
+
+- (NSDictionary *)dictionaryRepresentation {
+ NSMutableDictionary *metadataDictionary = [[NSMutableDictionary alloc] initWithCapacity:13];
+
+ if (_bucket) {
+ metadataDictionary[kFIRStorageMetadataBucket] = _bucket;
+ }
+
+ if (_cacheControl) {
+ metadataDictionary[kFIRStorageMetadataCacheControl] = _cacheControl;
+ }
+
+ if (_contentDisposition) {
+ metadataDictionary[kFIRStorageMetadataContentDisposition] = _contentDisposition;
+ }
+
+ if (_contentEncoding) {
+ metadataDictionary[kFIRStorageMetadataContentEncoding] = _contentEncoding;
+ }
+
+ if (_contentLanguage) {
+ metadataDictionary[kFIRStorageMetadataContentLanguage] = _contentLanguage;
+ }
+
+ if (_contentType) {
+ metadataDictionary[kFIRStorageMetadataContentType] = _contentType;
+ }
+
+ if (_customMetadata) {
+ metadataDictionary[kFIRStorageMetadataCustomMetadata] = _customMetadata;
+ }
+
+ if (_downloadURLs) {
+ NSMutableArray *downloadTokens = [[NSMutableArray alloc] init];
+ [_downloadURLs
+ enumerateObjectsUsingBlock:^(NSURL *_Nonnull URL, NSUInteger idx, BOOL *_Nonnull stop) {
+ NSArray *queryItems = [URL.query componentsSeparatedByString:@"&"];
+ [queryItems enumerateObjectsUsingBlock:^(NSString *queryString, NSUInteger idx,
+ BOOL *_Nonnull stop) {
+ NSString *key;
+ NSString *value;
+ NSScanner *scanner = [NSScanner scannerWithString:queryString];
+ [scanner scanUpToString:@"=" intoString:&key];
+ [scanner scanString:@"=" intoString:NULL];
+ [scanner scanUpToString:@"\n" intoString:&value];
+ if ([key isEqual:@"token"]) {
+ [downloadTokens addObject:value];
+ *stop = YES;
+ }
+ }];
+ }];
+ NSString *downloadTokenString = [downloadTokens componentsJoinedByString:@","];
+ metadataDictionary[kFIRStorageMetadataDownloadTokens] = downloadTokenString;
+ }
+
+ if (_generation) {
+ NSString *generationString = [NSString stringWithFormat:@"%lld", _generation];
+ metadataDictionary[kFIRStorageMetadataGeneration] = generationString;
+ }
+
+ if (_metageneration) {
+ NSString *metagenerationString = [NSString stringWithFormat:@"%lld", _metageneration];
+ metadataDictionary[kFIRStorageMetadataMetageneration] = metagenerationString;
+ }
+
+ if (_timeCreated) {
+ metadataDictionary[kFIRStorageMetadataTimeCreated] = [self RFC3339StringFromDate:_timeCreated];
+ }
+
+ if (_updated) {
+ metadataDictionary[kFIRStorageMetadataUpdated] = [self RFC3339StringFromDate:_updated];
+ }
+
+ if (_path) {
+ metadataDictionary[kFIRStorageMetadataName] = _path;
+ }
+
+ return [metadataDictionary copy];
+}
+
+- (BOOL)isFile {
+ return _type == FIRStorageMetadataTypeFile;
+}
+
+- (BOOL)isFolder {
+ return _type == FIRStorageMetadataTypeFolder;
+}
+
+- (nullable NSURL *)downloadURL {
+ return [_downloadURLs firstObject];
+}
+
+#pragma mark - RFC 3339 conversions
+
+static NSDateFormatter *sRFC3339DateFormatter;
+
+static void setupDateFormatterOnce(void) {
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ sRFC3339DateFormatter = [[NSDateFormatter alloc] init];
+ NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
+
+ [sRFC3339DateFormatter setLocale:enUSPOSIXLocale];
+ [sRFC3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSZZZZZ"];
+ [sRFC3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
+ });
+}
+
+- (nullable NSDate *)dateFromRFC3339String:(NSString *)dateString {
+ setupDateFormatterOnce();
+ NSDate *rfc3339Date = [sRFC3339DateFormatter dateFromString:dateString];
+ return rfc3339Date;
+}
+
+- (nullable NSString *)RFC3339StringFromDate:(NSDate *)date {
+ setupDateFormatterOnce();
+ NSString *rfc3339String = [sRFC3339DateFormatter stringFromDate:date];
+ return rfc3339String;
+}
+
+@end
diff --git a/Firebase/Storage/FIRStorageObservableTask.h b/Firebase/Storage/FIRStorageObservableTask.h
new file mode 100644
index 0000000..502aba5
--- /dev/null
+++ b/Firebase/Storage/FIRStorageObservableTask.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2017 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 "FIRStorageSwiftNameSupport.h"
+#import "FIRStorageTask.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class FIRStorageReference;
+@class FIRStorageTaskSnapshot;
+
+/**
+ * Extends FIRStorageTask to provide observable semantics such as adding and removing observers.
+ * Observers produce a FIRStorageHandle, which is used to keep track of and remove specific
+ * observers at a later date.
+ * This class is currently not thread safe and can only be called on the main thread.
+ */
+FIR_SWIFT_NAME(StorageObservableTask)
+@interface FIRStorageObservableTask : FIRStorageTask
+
+/**
+ * Observes changes in the upload status: Resume, Pause, Progress, Success, and Failure.
+ * @param status The FIRStorageTaskStatus change to observe.
+ * @param handler A callback that fires every time the status event occurs,
+ * returns a FIRStorageTaskSnapshot containing the state of the task.
+ * @return A task handle that can be used to remove the observer at a later date.
+ */
+- (FIRStorageHandle)observeStatus:(FIRStorageTaskStatus)status
+ handler:(void (^)(FIRStorageTaskSnapshot *snapshot))handler;
+
+/**
+ * Removes the single observer with the provided handle.
+ * @param handle The handle of the task to remove.
+ */
+- (void)removeObserverWithHandle:(FIRStorageHandle)handle;
+
+/**
+ * Removes all observers for a single status.
+ * @param status A FIRStorageTaskStatus to remove listeners for.
+ */
+- (void)removeAllObserversForStatus:(FIRStorageTaskStatus)status;
+
+/**
+ * Removes all observers.
+ */
+- (void)removeAllObservers;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/FIRStorageObservableTask.m b/Firebase/Storage/FIRStorageObservableTask.m
new file mode 100644
index 0000000..bac5924
--- /dev/null
+++ b/Firebase/Storage/FIRStorageObservableTask.m
@@ -0,0 +1,216 @@
+// Copyright 2017 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 "FIRStorageObservableTask.h"
+#import "FIRStorageObservableTask_Private.h"
+#import "FIRStorageTask_Private.h"
+
+@implementation FIRStorageObservableTask {
+ @private
+ // Handlers for pause, resume, progress, success, and failure callbacks
+ NSMutableDictionary<NSString *, FIRStorageVoidSnapshot> *_resumeHandlers;
+ NSMutableDictionary<NSString *, FIRStorageVoidSnapshot> *_pauseHandlers;
+ NSMutableDictionary<NSString *, FIRStorageVoidSnapshot> *_progressHandlers;
+ NSMutableDictionary<NSString *, FIRStorageVoidSnapshot> *_successHandlers;
+ NSMutableDictionary<NSString *, FIRStorageVoidSnapshot> *_failureHandlers;
+ // Reverse map of fetcher handles to status types
+ NSMutableDictionary<NSString *, NSNumber *> *_handleToStatusMap;
+}
+
+@synthesize state = _state;
+
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service {
+ self = [super initWithReference:reference fetcherService:service];
+ if (self) {
+ _pauseHandlers = [[NSMutableDictionary alloc] init];
+ _resumeHandlers = [[NSMutableDictionary alloc] init];
+ _progressHandlers = [[NSMutableDictionary alloc] init];
+ _successHandlers = [[NSMutableDictionary alloc] init];
+ _failureHandlers = [[NSMutableDictionary alloc] init];
+ _handleToStatusMap = [[NSMutableDictionary alloc] init];
+ }
+ return self;
+}
+
+#pragma mark - Observers
+
+- (FIRStorageHandle)observeStatus:(FIRStorageTaskStatus)status
+ handler:(FIRStorageVoidSnapshot)handler {
+ FIRStorageVoidSnapshot callback = handler;
+ handler = nil;
+
+ // Note: self.snapshot is synchronized
+ FIRStorageTaskSnapshot *snapshot = self.snapshot;
+ // TODO: use an increasing counter instead of a random UUID
+ NSString *UUIDString = [[NSUUID UUID] UUIDString];
+ switch (status) {
+ case FIRStorageTaskStatusPause:
+ @synchronized(self) {
+ [_pauseHandlers setValue:callback forKey:UUIDString];
+ } // @synchronized(self)
+ if (_state == FIRStorageTaskStatePausing || _state == FIRStorageTaskStatePaused) {
+ [self fireHandlers:_pauseHandlers snapshot:snapshot];
+ }
+ break;
+
+ case FIRStorageTaskStatusResume:
+ @synchronized(self) {
+ [_resumeHandlers setValue:callback forKey:UUIDString];
+ } // @synchronized(self)
+ if (_state == FIRStorageTaskStateResuming || _state == FIRStorageTaskStateRunning) {
+ [self fireHandlers:_resumeHandlers snapshot:snapshot];
+ }
+ break;
+
+ case FIRStorageTaskStatusProgress:
+ @synchronized(self) {
+ [_progressHandlers setValue:callback forKey:UUIDString];
+ } // @synchronized(self)
+ if (_state == FIRStorageTaskStateRunning || _state == FIRStorageTaskStateProgress) {
+ [self fireHandlers:_progressHandlers snapshot:snapshot];
+ }
+ break;
+
+ case FIRStorageTaskStatusSuccess:
+ @synchronized(self) {
+ [_successHandlers setValue:callback forKey:UUIDString];
+ } // @synchronized(self)
+ if (_state == FIRStorageTaskStateSuccess) {
+ [self fireHandlers:_successHandlers snapshot:snapshot];
+ }
+ break;
+
+ case FIRStorageTaskStatusFailure:
+ @synchronized(self) {
+ [_failureHandlers setValue:callback forKey:UUIDString];
+ } // @synchronized(self)
+ if (_state == FIRStorageTaskStateFailing || _state == FIRStorageTaskStateFailed) {
+ [self fireHandlers:_failureHandlers snapshot:snapshot];
+ }
+ break;
+
+ case FIRStorageTaskStatusUnknown:
+ // Fall through to exception case if an unknown status is passed
+
+ default:
+ [NSException raise:NSInternalInconsistencyException
+ format:kFIRStorageInvalidObserverStatus, nil];
+ break;
+ }
+
+ @synchronized(self) {
+ _handleToStatusMap[UUIDString] = @(status);
+ } // @synchronized(self)
+
+ return UUIDString;
+}
+
+- (void)removeObserverWithHandle:(FIRStorageHandle)handle {
+ FIRStorageTaskStatus status = [_handleToStatusMap[handle] intValue];
+ NSMutableDictionary<NSString *, FIRStorageVoidSnapshot> *observerDictionary =
+ [self handlerDictionaryForStatus:status];
+
+ @synchronized(self) {
+ [observerDictionary removeObjectForKey:handle];
+ [_handleToStatusMap removeObjectForKey:handle];
+ } // @synchronized(self)
+}
+
+- (void)removeAllObserversForStatus:(FIRStorageTaskStatus)status {
+ NSMutableDictionary<NSString *, FIRStorageVoidSnapshot> *observerDictionary =
+ [self handlerDictionaryForStatus:status];
+ [self removeHandlersFromStatusMapForDictionary:observerDictionary];
+
+ @synchronized(self) {
+ [observerDictionary removeAllObjects];
+ } // @synchronized(self)
+}
+
+- (void)removeAllObservers {
+ @synchronized(self) {
+ [_pauseHandlers removeAllObjects];
+ [_resumeHandlers removeAllObjects];
+ [_progressHandlers removeAllObjects];
+ [_successHandlers removeAllObjects];
+ [_failureHandlers removeAllObjects];
+ [_handleToStatusMap removeAllObjects];
+ } // @synchronized(self)
+}
+
+- (NSMutableDictionary<NSString *, FIRStorageVoidSnapshot> *)handlerDictionaryForStatus:
+ (FIRStorageTaskStatus)status {
+ switch (status) {
+ case FIRStorageTaskStatusPause:
+ return _pauseHandlers;
+
+ case FIRStorageTaskStatusResume:
+ return _resumeHandlers;
+
+ case FIRStorageTaskStatusProgress:
+ return _progressHandlers;
+
+ case FIRStorageTaskStatusSuccess:
+ return _successHandlers;
+
+ case FIRStorageTaskStatusFailure:
+ return _failureHandlers;
+
+ case FIRStorageTaskStatusUnknown:
+ return [NSMutableDictionary dictionary];
+
+ default:
+ [NSException raise:NSInternalInconsistencyException
+ format:kFIRStorageInvalidObserverStatus, nil];
+ return nil;
+ }
+}
+
+- (void)removeHandlersFromStatusMapForDictionary:
+ (NSMutableDictionary<NSString *, FIRStorageVoidSnapshot> *)dict {
+ @synchronized(self) {
+ [_handleToStatusMap removeObjectsForKeys:dict.allKeys];
+ } // @synchronized(self)
+}
+
+- (void)fireHandlersForStatus:(FIRStorageTaskStatus)status
+ snapshot:(FIRStorageTaskSnapshot *)snapshot {
+ NSMutableDictionary<NSString *, FIRStorageVoidSnapshot> *observerDictionary =
+ [self handlerDictionaryForStatus:status];
+ [self fireHandlers:observerDictionary snapshot:snapshot];
+}
+
+- (void)fireHandlers:(NSMutableDictionary<NSString *, FIRStorageVoidSnapshot> *)handlers
+ snapshot:(FIRStorageTaskSnapshot *)snapshot {
+ dispatch_queue_t callbackQueue = self.fetcherService.callbackQueue;
+ if (!callbackQueue) {
+ callbackQueue = dispatch_get_main_queue();
+ }
+
+ // TODO: iterate over this list in a consistent order
+ NSMutableDictionary<NSString *, FIRStorageVoidSnapshot> *handlersCopy;
+ @synchronized(self) {
+ handlersCopy = [handlers copy];
+ } // @synchronized(self)
+ [handlersCopy enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key,
+ FIRStorageVoidSnapshot _Nonnull handler,
+ BOOL *_Nonnull stop) {
+
+ dispatch_async(callbackQueue, ^{
+ handler(snapshot);
+ });
+ }];
+}
+
+@end
diff --git a/Firebase/Storage/FIRStoragePath.m b/Firebase/Storage/FIRStoragePath.m
new file mode 100644
index 0000000..7188ab6
--- /dev/null
+++ b/Firebase/Storage/FIRStoragePath.m
@@ -0,0 +1,199 @@
+// Copyright 2017 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 "FIRStoragePath.h"
+
+#import "FIRStorageConstants_Private.h"
+
+@implementation FIRStoragePath
+
+#pragma mark - Class methods
+
++ (nullable FIRStoragePath *)pathFromString:(NSString *)string {
+ if ([string hasPrefix:@"gs://"]) {
+ // "gs://bucket/path/to/object"
+ return [FIRStoragePath pathFromGSURI:string];
+ } else if ([string hasPrefix:@"http://"] || [string hasPrefix:@"https://"]) {
+ // "http[s]://firebasestorage.googleapis.com/bucket/path/to/object?signed_url_params"
+ return [FIRStoragePath pathFromHTTPURL:string];
+ } else {
+ // Invalid scheme, raise an exception!
+ [NSException raise:NSInternalInconsistencyException
+ format:@"URL scheme must be one of gs://, http://, or https:// "];
+ return nil;
+ }
+}
+
++ (nullable FIRStoragePath *)pathFromGSURI:(NSString *)aURIString {
+ NSString *bucketName;
+ NSString *objectName;
+ NSScanner *scanner = [NSScanner scannerWithString:aURIString];
+ BOOL isGSURI = [scanner scanString:@"gs://" intoString:NULL];
+ BOOL hasBucket = [scanner scanUpToString:@"/" intoString:&bucketName];
+ [scanner scanString:@"/" intoString:NULL];
+ [scanner scanUpToString:@"\n" intoString:&objectName];
+
+ if (!isGSURI || !hasBucket) {
+ [NSException raise:NSInternalInconsistencyException
+ format:@"URI must be in the form of gs://<bucket>/<path/to/object>"];
+ return nil;
+ }
+
+ return [[self alloc] initWithBucket:bucketName object:objectName];
+}
+
++ (nullable FIRStoragePath *)pathFromHTTPURL:(NSString *)aURLString {
+ NSString *bucketName;
+ NSString *objectName;
+ NSURL *httpsURL = [NSURL URLWithString:aURLString];
+ NSArray *pathComponents = httpsURL.pathComponents; // [/, v0, b, <bucket>, o, <objects/...>]
+
+ if ([httpsURL.host isEqual:kFIRStorageHost]) {
+ // Have a bucket name
+ if ([pathComponents count] > 3) {
+ bucketName = pathComponents[3];
+ }
+
+ // Have an object name
+ if ([pathComponents count] > 5) {
+ NSRange objectRange = NSMakeRange(5, [pathComponents count] - 5);
+ objectName = [[pathComponents subarrayWithRange:objectRange] componentsJoinedByString:@"/"];
+ }
+ }
+
+ if (bucketName.length == 0) {
+ [NSException raise:NSInternalInconsistencyException
+ format:@"URL must be in the form of "
+ @"http[s]://firebasestorage.googleapis.com/v0/b/<bucket>/o/<path/to/"
+ @"object>[?token=signed_url_params]"];
+ return nil;
+ }
+
+ if (objectName.length == 0) {
+ objectName = nil;
+ }
+
+ return [[self alloc] initWithBucket:bucketName object:objectName];
+}
+
+#pragma mark - Initializers
+
+- (instancetype)initWithBucket:(NSString *)bucket object:(nullable NSString *)object {
+ self = [super init];
+ if (self) {
+ _bucket = [bucket copy];
+ _object = [self standardizedPathForString:[object copy]];
+ }
+ return self;
+}
+
+#pragma mark - NSObject overrides
+
+- (instancetype)copyWithZone:(NSZone *)zone {
+ return [[[self class] allocWithZone:zone] initWithBucket:_bucket object:_object];
+}
+
+- (BOOL)isEqual:(id)object {
+ if (self == object) {
+ return YES;
+ }
+
+ if (![object isKindOfClass:[FIRStoragePath class]]) {
+ return NO;
+ }
+
+ BOOL isObjectEqual = [self isEqualToFIRStoragePath:(FIRStoragePath *)object];
+ return isObjectEqual;
+}
+
+- (BOOL)isEqualToFIRStoragePath:(FIRStoragePath *)path {
+ BOOL isBucketEqual = _bucket == nil && path->_bucket == nil;
+ BOOL isObjectEqual = _object == nil && path->_object == nil;
+
+ if (_bucket && path->_bucket) {
+ isBucketEqual = [_bucket isEqual:path->_bucket];
+ }
+
+ if (_object && path.object) {
+ isObjectEqual = [_object isEqual:path->_object];
+ }
+
+ BOOL isEqual = isBucketEqual && isObjectEqual;
+ return isEqual;
+}
+
+- (NSUInteger)hash {
+ // "...because in those days, you could XOR anything with anything and get something useful..."
+ // https://www.usenix.org/system/files/1309_14-17_mickens.pdf
+ NSUInteger hash = [_bucket hash] ^ [_object hash];
+ return hash;
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"%@ %p: %@", [self class], self, [self stringValue]];
+}
+
+- (NSString *)stringValue {
+ return [NSString stringWithFormat:@"gs://%@/%@", _bucket, _object ?: @""];
+}
+
+#pragma mark - Public methods
+
+- (FIRStoragePath *)child:(NSString *)path {
+ if (path.length == 0) {
+ return [self copy]; // Return a copy of the same path, nothing happened
+ }
+
+ NSString *childObject;
+ if (_object == nil) {
+ childObject = path;
+ } else {
+ childObject = [_object stringByAppendingPathComponent:path];
+ }
+
+ FIRStoragePath *childPath = [[FIRStoragePath alloc] initWithBucket:_bucket object:childObject];
+ return childPath;
+}
+
+- (nullable FIRStoragePath *)parent {
+ if (_object.length == 0) {
+ return nil;
+ }
+
+ NSString *parentObject = [_object stringByDeletingLastPathComponent];
+ FIRStoragePath *parentPath = [[FIRStoragePath alloc] initWithBucket:_bucket object:parentObject];
+ return parentPath;
+}
+
+- (FIRStoragePath *)root {
+ FIRStoragePath *rootPath = [[FIRStoragePath alloc] initWithBucket:_bucket object:nil];
+ return rootPath;
+}
+
+#pragma mark - Private methods
+
+// Removes leading and trailing slashes, and compresses multiple slashes
+// to create a canonical representation.
+// Example: /foo//bar///baz//// -> foo/bar/baz
+- (NSString *)standardizedPathForString:(NSString *)string {
+ NSMutableArray *components = [[string componentsSeparatedByString:@"/"] mutableCopy];
+ NSIndexSet *removedPaths =
+ [components indexesOfObjectsPassingTest:^BOOL(NSString *string, NSUInteger idx, BOOL *stop) {
+ return (string.length == 0);
+ }];
+ [components removeObjectsAtIndexes:removedPaths];
+ return [components componentsJoinedByString:@"/"];
+}
+
+@end
diff --git a/Firebase/Storage/FIRStorageReference.h b/Firebase/Storage/FIRStorageReference.h
new file mode 100644
index 0000000..0b85267
--- /dev/null
+++ b/Firebase/Storage/FIRStorageReference.h
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2017 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>
+
+#import "FIRStorage.h"
+#import "FIRStorageConstants.h"
+#import "FIRStorageDownloadTask.h"
+#import "FIRStorageMetadata.h"
+#import "FIRStorageSwiftNameSupport.h"
+#import "FIRStorageTask.h"
+#import "FIRStorageUploadTask.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * FIRStorageReference represents a reference to a Google Cloud Storage object. Developers can
+ * upload and download objects, as well as get/set object metadata, and delete an object at the
+ * path.
+ * @see https://cloud.google.com/storage/
+ */
+FIR_SWIFT_NAME(StorageReference)
+@interface FIRStorageReference : NSObject
+
+/**
+ * The FIRStorage service object which created this reference.
+ */
+@property(nonatomic, readonly) FIRStorage *storage;
+
+/**
+ * The name of the Google Cloud Storage bucket associated with this reference,
+ * in gs://bucket/path/to/object.txt, the bucket would be: 'bucket'
+ */
+@property(nonatomic, readonly) NSString *bucket;
+
+/**
+ * The full path to this object, not including the Google Cloud Storage bucket.
+ * In gs://bucket/path/to/object.txt, the full path would be: 'path/to/object.txt'
+ */
+@property(nonatomic, readonly) NSString *fullPath;
+
+/**
+ * The short name of the object associated with this reference,
+ * in gs://bucket/path/to/object.txt, the name of the object would be: 'object.txt'
+ */
+@property(nonatomic, readonly) NSString *name;
+
+#pragma mark - Path Operations
+
+/**
+ * Creates a new FIRStorageReference pointing to the root object.
+ * @return A new FIRStorageReference pointing to the root object.
+ */
+- (FIRStorageReference *)root;
+
+/**
+ * Creates a new FIRStorageReference pointing to the parent of the current reference
+ * or nil if this instance references the root location.
+ * For example:
+ * path = foo/bar/baz parent = foo/bar
+ * path = foo parent = (root)
+ * path = (root) parent = nil
+ * @return A new FIRStorageReference pointing to the parent of the current reference.
+ */
+- (nullable FIRStorageReference *)parent;
+
+/**
+ * Creates a new FIRStorageReference pointing to a child object of the current reference.
+ * path = foo child = bar newPath = foo/bar
+ * path = foo/bar child = baz newPath = foo/bar/baz
+ * All leading and trailing slashes will be removed, and consecutive slashes will be
+ * compressed to single slashes. For example:
+ * child = /foo/bar newPath = foo/bar
+ * child = foo/bar/ newPath = foo/bar
+ * child = foo///bar newPath = foo/bar
+ * @param path Path to append to the current path.
+ * @return A new FIRStorageReference pointing to a child location of the current reference.
+ */
+- (FIRStorageReference *)child:(NSString *)path;
+
+#pragma mark - Uploads
+
+/**
+ * Asynchronously uploads data to the currently specified FIRStorageReference,
+ * without additional metadata.
+ * This is not recommended for large files, and one should instead upload a file from disk.
+ * @param uploadData The NSData to upload.
+ * @return An instance of FIRStorageUploadTask, which can be used to monitor or manage the upload.
+ */
+- (FIRStorageUploadTask *)putData:(NSData *)uploadData FIR_SWIFT_NAME(putData(_:));
+
+/**
+ * Asynchronously uploads data to the currently specified FIRStorageReference.
+ * This is not recommended for large files, and one should instead upload a file from disk.
+ * @param uploadData The NSData to upload.
+ * @param metadata FIRStorageMetadata containing additional information (MIME type, etc.)
+ * about the object being uploaded.
+ * @return An instance of FIRStorageUploadTask, which can be used to monitor or manage the upload.
+ */
+- (FIRStorageUploadTask *)putData:(NSData *)uploadData
+ metadata:(nullable FIRStorageMetadata *)metadata
+ FIR_SWIFT_NAME(putData(_:metadata:));
+
+/**
+ * Asynchronously uploads data to the currently specified FIRStorageReference.
+ * This is not recommended for large files, and one should instead upload a file from disk.
+ * @param uploadData The NSData to upload.
+ * @param metadata FIRStorageMetadata containing additional information (MIME type, etc.)
+ * about the object being uploaded.
+ * @param completion A completion block that either returns the object metadata on success,
+ * or an error on failure.
+ * @return An instance of FIRStorageUploadTask, which can be used to monitor or manage the upload.
+ */
+- (FIRStorageUploadTask *)putData:(NSData *)uploadData
+ metadata:(nullable FIRStorageMetadata *)metadata
+ completion:(nullable void (^)(FIRStorageMetadata *_Nullable metadata,
+ NSError *_Nullable error))completion
+ FIR_SWIFT_NAME(putData(_:metadata:completion:));
+
+/**
+ * Asynchronously uploads a file to the currently specified FIRStorageReference,
+ * without additional metadata.
+ * @param fileURL A URL representing the system file path of the object to be uploaded.
+ * @return An instance of FIRStorageUploadTask, which can be used to monitor or manage the upload.
+ */
+- (FIRStorageUploadTask *)putFile:(NSURL *)fileURL FIR_SWIFT_NAME(putFile(from:));
+
+/**
+ * Asynchronously uploads a file to the currently specified FIRStorageReference.
+ * @param fileURL A URL representing the system file path of the object to be uploaded.
+ * @param metadata FIRStorageMetadata containing additional information (MIME type, etc.)
+ * about the object being uploaded.
+ * @return An instance of FIRStorageUploadTask, which can be used to monitor or manage the upload.
+ */
+- (FIRStorageUploadTask *)putFile:(NSURL *)fileURL metadata:(nullable FIRStorageMetadata *)metadata
+ FIR_SWIFT_NAME(putFile(from:metadata:));
+
+/**
+ * Asynchronously uploads a file to the currently specified FIRStorageReference.
+ * @param fileURL A URL representing the system file path of the object to be uploaded.
+ * @param metadata FIRStorageMetadata containing additional information (MIME type, etc.)
+ * about the object being uploaded.
+ * @param completion A completion block that either returns the object metadata on success,
+ * or an error on failure.
+ * @return An instance of FIRStorageUploadTask, which can be used to monitor or manage the upload.
+ */
+- (FIRStorageUploadTask *)putFile:(NSURL *)fileURL
+ metadata:(nullable FIRStorageMetadata *)metadata
+ completion:(nullable void (^)(FIRStorageMetadata *_Nullable metadata,
+ NSError *_Nullable error))completion
+ FIR_SWIFT_NAME(putFile(from:metadata:completion:));
+
+#pragma mark - Downloads
+
+/**
+ * Asynchronously downloads the object at the FIRStorageReference to an NSData object in memory.
+ * An NSData of the provided max size will be allocated, so ensure that the device has enough free
+ * memory to complete the download. For downloading large files, writeToFile may be a better option.
+ * @param size The maximum size in bytes to download. If the download exceeds this size
+ * the task will be cancelled and an error will be returned.
+ * @param completion A completion block that either returns the object data on success,
+ * or an error on failure.
+ * @return An FIRStorageDownloadTask that can be used to monitor or manage the download.
+ */
+- (FIRStorageDownloadTask *)dataWithMaxSize:(int64_t)size
+ completion:(void (^)(NSData *_Nullable data,
+ NSError *_Nullable error))completion
+ FIR_SWIFT_NAME(getData(maxSize:completion:));
+
+/**
+ * Asynchronously retrieves a long lived download URL with a revokable token.
+ * This can be used to share the file with others, but can be revoked by a developer
+ * in the Firebase Console if desired.
+ * @param completion A completion block that either returns the URL on success,
+ * or an error on failure.
+ */
+- (void)downloadURLWithCompletion:(void (^)(NSURL *_Nullable URL,
+ NSError *_Nullable error))completion;
+
+/**
+ * Asynchronously downloads the object at the current path to a specified system filepath.
+ * @param fileURL A file system URL representing the path the object should be downloaded to.
+ * @return An FIRStorageDownloadTask that can be used to monitor or manage the download.
+ */
+- (FIRStorageDownloadTask *)writeToFile:(NSURL *)fileURL;
+
+/**
+ * Asynchronously downloads the object at the current path to a specified system filepath.
+ * @param fileURL A file system URL representing the path the object should be downloaded to.
+ * @param completion A completion block that fires when the file download completes.
+ * Returns an NSURL pointing to the file path of the downloaded file on success,
+ * or an error on failure.
+ * @return An FIRStorageDownloadTask that can be used to monitor or manage the download.
+ */
+- (FIRStorageDownloadTask *)writeToFile:(NSURL *)fileURL
+ completion:(nullable void (^)(NSURL *_Nullable URL,
+ NSError *_Nullable error))completion;
+
+#pragma mark - Metadata Operations
+
+/**
+ * Retrieves metadata associated with an object at the current path.
+ * @param completion A completion block which returns the object metadata on success,
+ * or an error on failure.
+ */
+- (void)metadataWithCompletion:(void (^)(FIRStorageMetadata *_Nullable metadata,
+ NSError *_Nullable error))completion
+ FIR_SWIFT_NAME(getMetadata(completion:));
+
+/**
+ * Updates the metadata associated with an object at the current path.
+ * @param metadata An FIRStorageMetadata object with the metadata to update.
+ * @param completion A completion block which returns the FIRStorageMetadata on success,
+ * or an error on failure.
+ */
+- (void)updateMetadata:(FIRStorageMetadata *)metadata
+ completion:(nullable void (^)(FIRStorageMetadata *_Nullable metadata,
+ NSError *_Nullable error))completion
+ FIR_SWIFT_NAME(updateMetadata(_:completion:));
+
+#pragma mark - Delete
+
+/**
+ * Deletes the object at the current path.
+ * @param completion A completion block which returns nil on success, or an error on failure.
+ */
+- (void)deleteWithCompletion:(nullable void (^)(NSError *_Nullable error))completion;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/FIRStorageReference.m b/Firebase/Storage/FIRStorageReference.m
new file mode 100644
index 0000000..6e8105c
--- /dev/null
+++ b/Firebase/Storage/FIRStorageReference.m
@@ -0,0 +1,364 @@
+// Copyright 2017 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 "FIRStorageReference.h"
+
+#import "FIRStorageConstants_Private.h"
+#import "FIRStorageDeleteTask.h"
+#import "FIRStorageDownloadTask_Private.h"
+#import "FIRStorageGetMetadataTask.h"
+#import "FIRStorageMetadata_Private.h"
+#import "FIRStorageReference_Private.h"
+#import "FIRStorageTaskSnapshot.h"
+#import "FIRStorageTaskSnapshot_Private.h"
+#import "FIRStorageTask_Private.h"
+#import "FIRStorageUpdateMetadataTask.h"
+#import "FIRStorageUploadTask_Private.h"
+#import "FIRStorageUtils.h"
+#import "FIRStorage_Private.h"
+
+#import "FIRApp.h"
+#import "FIROptions.h"
+
+#import <GTMSessionFetcher/GTMSessionFetcher.h>
+#import "GTMSessionFetcherService.h"
+
+@implementation FIRStorageReference
+
+- (instancetype)init {
+ FIRStorage *storage = [FIRStorage storage];
+ NSString *storageBucket = storage.app.options.storageBucket;
+ FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:storageBucket object:nil];
+ FIRStorageReference *reference = [self initWithStorage:storage path:path];
+ return reference;
+}
+
+- (instancetype)initWithStorage:(FIRStorage *)storage path:(FIRStoragePath *)path {
+ self = [super init];
+ if (self) {
+ _storage = storage;
+ _path = path;
+ }
+ return self;
+}
+
+#pragma mark - NSObject overrides
+
+- (instancetype)copyWithZone:(NSZone *)zone {
+ FIRStorageReference *copiedReference =
+ [[[self class] allocWithZone:zone] initWithStorage:_storage path:_path];
+ return copiedReference;
+}
+
+- (BOOL)isEqual:(id)object {
+ if (self == object) {
+ return YES;
+ }
+
+ if (![object isKindOfClass:[FIRStorageReference class]]) {
+ return NO;
+ }
+
+ BOOL isObjectEqual = [self isEqualToFIRStorageReference:(FIRStorageReference *)object];
+ return isObjectEqual;
+}
+
+- (BOOL)isEqualToFIRStorageReference:(FIRStorageReference *)reference {
+ BOOL isEqual = [_storage isEqual:reference.storage] && [_path isEqual:reference.path];
+ return isEqual;
+}
+
+- (NSUInteger)hash {
+ NSUInteger hash = [_storage hash] ^ [_path hash];
+ return hash;
+}
+
+- (NSString *)description {
+ return [self stringValue];
+}
+
+- (NSString *)stringValue {
+ NSString *value = [NSString stringWithFormat:@"gs://%@/%@", _path.bucket, _path.object ?: @""];
+ return value;
+}
+
+#pragma mark - Property Getters
+
+- (NSString *)bucket {
+ NSString *bucket = _path.bucket;
+ return bucket;
+}
+
+- (NSString *)fullPath {
+ NSString *path = _path.object;
+ if (!path) {
+ path = @"";
+ }
+ return path;
+}
+
+- (NSString *)name {
+ NSString *name = [_path.object lastPathComponent];
+ if (!name) {
+ name = @"";
+ }
+ return name;
+}
+
+#pragma mark - Path Operations
+
+- (FIRStorageReference *)root {
+ FIRStoragePath *rootPath = [_path root];
+ FIRStorageReference *rootReference =
+ [[FIRStorageReference alloc] initWithStorage:_storage path:rootPath];
+ return rootReference;
+}
+
+- (nullable FIRStorageReference *)parent {
+ FIRStoragePath *parentPath = [_path parent];
+ if (!parentPath) {
+ return nil;
+ }
+
+ FIRStorageReference *parentReference =
+ [[FIRStorageReference alloc] initWithStorage:_storage path:parentPath];
+ return parentReference;
+}
+
+- (FIRStorageReference *)child:(NSString *)path {
+ FIRStoragePath *childPath = [_path child:path];
+ FIRStorageReference *childReference =
+ [[FIRStorageReference alloc] initWithStorage:_storage path:childPath];
+ return childReference;
+}
+
+#pragma mark - Uploads
+
+- (FIRStorageUploadTask *)putData:(NSData *)uploadData {
+ return [self putData:uploadData metadata:nil completion:nil];
+}
+
+- (FIRStorageUploadTask *)putData:(NSData *)uploadData
+ metadata:(nullable FIRStorageMetadata *)metadata {
+ return [self putData:uploadData metadata:metadata completion:nil];
+}
+
+- (FIRStorageUploadTask *)putData:(NSData *)uploadData
+ metadata:(nullable FIRStorageMetadata *)metadata
+ completion:(nullable FIRStorageVoidMetadataError)completion {
+ if (!metadata) {
+ metadata = [[FIRStorageMetadata alloc] init];
+ }
+
+ metadata.path = _path.object;
+ metadata.name = [_path.object lastPathComponent];
+ FIRStorageUploadTask *task =
+ [[FIRStorageUploadTask alloc] initWithReference:self
+ fetcherService:_storage.fetcherServiceForApp
+ data:uploadData
+ metadata:metadata];
+
+ if (completion) {
+ dispatch_queue_t callbackQueue = _storage.fetcherServiceForApp.callbackQueue;
+ if (!callbackQueue) {
+ callbackQueue = dispatch_get_main_queue();
+ }
+
+ [task observeStatus:FIRStorageTaskStatusSuccess
+ handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
+ dispatch_async(callbackQueue, ^{
+ completion(snapshot.metadata, nil);
+ });
+ }];
+ [task observeStatus:FIRStorageTaskStatusFailure
+ handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
+ dispatch_async(callbackQueue, ^{
+ completion(nil, snapshot.error);
+ });
+ }];
+ }
+ [task enqueue];
+ return task;
+}
+
+- (FIRStorageUploadTask *)putFile:(NSURL *)fileURL {
+ return [self putFile:fileURL metadata:nil completion:nil];
+}
+
+- (FIRStorageUploadTask *)putFile:(NSURL *)fileURL
+ metadata:(nullable FIRStorageMetadata *)metadata {
+ return [self putFile:fileURL metadata:metadata completion:nil];
+}
+
+- (FIRStorageUploadTask *)putFile:(NSURL *)fileURL
+ metadata:(nullable FIRStorageMetadata *)metadata
+ completion:(nullable FIRStorageVoidMetadataError)completion {
+ if (!metadata) {
+ metadata = [[FIRStorageMetadata alloc] init];
+ }
+
+ metadata.path = _path.object;
+ metadata.name = [_path.object lastPathComponent];
+ FIRStorageUploadTask *task =
+ [[FIRStorageUploadTask alloc] initWithReference:self
+ fetcherService:_storage.fetcherServiceForApp
+ file:fileURL
+ metadata:metadata];
+
+ if (completion) {
+ dispatch_queue_t callbackQueue = _storage.fetcherServiceForApp.callbackQueue;
+ if (!callbackQueue) {
+ callbackQueue = dispatch_get_main_queue();
+ }
+
+ [task observeStatus:FIRStorageTaskStatusSuccess
+ handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
+ dispatch_async(callbackQueue, ^{
+ completion(snapshot.metadata, nil);
+ });
+ }];
+ [task observeStatus:FIRStorageTaskStatusFailure
+ handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
+ dispatch_async(callbackQueue, ^{
+ completion(nil, snapshot.error);
+ });
+ }];
+ }
+ [task enqueue];
+ return task;
+}
+
+#pragma mark - Downloads
+
+- (FIRStorageDownloadTask *)dataWithMaxSize:(int64_t)size
+ completion:(FIRStorageVoidDataError)completion {
+ FIRStorageDownloadTask *task =
+ [[FIRStorageDownloadTask alloc] initWithReference:self
+ fetcherService:_storage.fetcherServiceForApp
+ file:nil];
+
+ dispatch_queue_t callbackQueue = _storage.fetcherServiceForApp.callbackQueue;
+ if (!callbackQueue) {
+ callbackQueue = dispatch_get_main_queue();
+ }
+
+ [task observeStatus:FIRStorageTaskStatusSuccess
+ handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
+ FIRStorageDownloadTask *task = snapshot.task;
+ dispatch_async(callbackQueue, ^{
+ completion(task.downloadData, nil);
+ });
+ }];
+ [task observeStatus:FIRStorageTaskStatusFailure
+ handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
+ dispatch_async(callbackQueue, ^{
+ completion(nil, snapshot.error);
+ });
+ }];
+ [task observeStatus:FIRStorageTaskStatusProgress
+ handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
+ FIRStorageDownloadTask *task = snapshot.task;
+ if (task.progress.totalUnitCount > size ||
+ task.progress.completedUnitCount > size) {
+ NSDictionary *infoDictionary = @{
+ @"totalSize" : @(task.progress.totalUnitCount),
+ @"maxAllowedSize" : @(size)
+ };
+ NSError *error =
+ [FIRStorageErrors errorWithCode:FIRStorageErrorCodeDownloadSizeExceeded
+ infoDictionary:infoDictionary];
+ [task cancelWithError:error];
+ }
+ }];
+ [task enqueue];
+ return task;
+}
+
+- (FIRStorageDownloadTask *)writeToFile:(NSURL *)fileURL {
+ return [self writeToFile:fileURL completion:nil];
+}
+
+- (FIRStorageDownloadTask *)writeToFile:(NSURL *)fileURL
+ completion:(FIRStorageVoidURLError)completion {
+ FIRStorageDownloadTask *task =
+ [[FIRStorageDownloadTask alloc] initWithReference:self
+ fetcherService:_storage.fetcherServiceForApp
+ file:fileURL];
+ if (completion) {
+ dispatch_queue_t callbackQueue = _storage.fetcherServiceForApp.callbackQueue;
+ if (!callbackQueue) {
+ callbackQueue = dispatch_get_main_queue();
+ }
+
+ [task observeStatus:FIRStorageTaskStatusSuccess
+ handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
+ dispatch_async(callbackQueue, ^{
+ completion(fileURL, nil);
+ });
+ }];
+ [task observeStatus:FIRStorageTaskStatusFailure
+ handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
+ dispatch_async(callbackQueue, ^{
+ completion(nil, snapshot.error);
+ });
+ }];
+ }
+ [task enqueue];
+ return task;
+}
+
+- (void)downloadURLWithCompletion:(FIRStorageVoidURLError)completion {
+ dispatch_queue_t callbackQueue = _storage.fetcherServiceForApp.callbackQueue;
+ if (!callbackQueue) {
+ callbackQueue = dispatch_get_main_queue();
+ }
+
+ return [self metadataWithCompletion:^(FIRStorageMetadata *metadata, NSError *error) {
+ dispatch_async(callbackQueue, ^{
+ completion(metadata.downloadURL, error);
+ });
+ }];
+}
+
+#pragma mark - Metadata Operations
+
+- (void)metadataWithCompletion:(FIRStorageVoidMetadataError)completion {
+ FIRStorageGetMetadataTask *task =
+ [[FIRStorageGetMetadataTask alloc] initWithReference:self
+ fetcherService:_storage.fetcherServiceForApp
+ completion:completion];
+ [task enqueue];
+}
+
+- (void)updateMetadata:(FIRStorageMetadata *)metadata
+ completion:(nullable FIRStorageVoidMetadataError)completion {
+ FIRStorageUpdateMetadataTask *task =
+ [[FIRStorageUpdateMetadataTask alloc] initWithReference:self
+ fetcherService:_storage.fetcherServiceForApp
+ metadata:metadata
+ completion:completion];
+ [task enqueue];
+}
+
+#pragma mark - Delete
+
+- (void)deleteWithCompletion:(nullable FIRStorageVoidError)completion {
+ FIRStorageDeleteTask *task =
+ [[FIRStorageDeleteTask alloc] initWithReference:self
+ fetcherService:_storage.fetcherServiceForApp
+ completion:completion];
+ [task enqueue];
+}
+
+@end
diff --git a/Firebase/Storage/FIRStorageSwiftNameSupport.h b/Firebase/Storage/FIRStorageSwiftNameSupport.h
new file mode 100644
index 0000000..529adf4
--- /dev/null
+++ b/Firebase/Storage/FIRStorageSwiftNameSupport.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2017 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.
+ */
+
+#ifndef FIR_SWIFT_NAME
+
+#import <Foundation/Foundation.h>
+
+// NS_SWIFT_NAME can only translate factory methods before the iOS 9.3 SDK.
+// // Wrap it in our own macro if it's a non-compatible SDK.
+#ifdef __IPHONE_9_3
+#define FIR_SWIFT_NAME(X) NS_SWIFT_NAME(X)
+#else
+#define FIR_SWIFT_NAME(X) // Intentionally blank.
+#endif // #ifdef __IPHONE_9_3
+
+#endif // FIR_SWIFT_NAME \ No newline at end of file
diff --git a/Firebase/Storage/FIRStorageTask.h b/Firebase/Storage/FIRStorageTask.h
new file mode 100644
index 0000000..0428220
--- /dev/null
+++ b/Firebase/Storage/FIRStorageTask.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017 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>
+
+#import "FIRStorageConstants.h"
+#import "FIRStorageMetadata.h"
+#import "FIRStorageSwiftNameSupport.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * A superclass to all FIRStorage*Tasks, including FIRStorageUploadTask
+ * and FIRStorageDownloadTask, to provide state transitions, event raising, and common storage
+ * or metadata and errors.
+ * Callbacks are always fired on the developer specified callback queue.
+ * If no queue is specified by the developer, it defaults to the main queue.
+ * Currently not thread safe, so only call methods on the main thread.
+ */
+FIR_SWIFT_NAME(StorageTask)
+@interface FIRStorageTask : NSObject
+
+/**
+ * An immutable view of the task and associated metadata, progress, error, etc.
+ */
+@property(strong, readonly, nonatomic, nonnull) FIRStorageTaskSnapshot *snapshot;
+
+@end
+
+/**
+ * Defines task operations such as pause, resume, cancel, and enqueue for all tasks.
+ * All tasks are required to implement enqueue, which begins the task, and may optionally
+ * implement pause, resume, and cancel, which operate on the task to pause, resume, and cancel
+ * operations.
+ */
+FIR_SWIFT_NAME(StorageTaskManagement)
+@protocol FIRStorageTaskManagement<NSObject>
+
+@required
+/**
+ * Prepares a task and begins execution.
+ */
+- (void)enqueue;
+
+@optional
+/**
+ * Pauses a task currently in progress.
+ */
+- (void)pause;
+
+/**
+ * Cancels a task currently in progress.
+ */
+- (void)cancel;
+
+/**
+ * Resumes a task that is paused.
+ */
+- (void)resume;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/FIRStorageTask.m b/Firebase/Storage/FIRStorageTask.m
new file mode 100644
index 0000000..3c1cf6f
--- /dev/null
+++ b/Firebase/Storage/FIRStorageTask.m
@@ -0,0 +1,65 @@
+// Copyright 2017 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 "FIRStorageTask.h"
+
+#import "FIRStorage.h"
+#import "FIRStorage_Private.h"
+#import "FIRStorageReference.h"
+#import "FIRStorageReference_Private.h"
+#import "FIRStorageTaskSnapshot.h"
+#import "FIRStorageTaskSnapshot_Private.h"
+#import "FIRStorageTask_Private.h"
+
+#import "GTMSessionFetcherService.h"
+
+@implementation FIRStorageTask
+
+- (instancetype)init {
+ FIRStorage *storage = [FIRStorage storage];
+ FIRStorageReference *reference = [storage reference];
+ FIRStorageTask *task =
+ [self initWithReference:reference fetcherService:storage.fetcherServiceForApp];
+ return task;
+}
+
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service {
+ self = [super init];
+ if (self) {
+ _reference = reference;
+ _baseRequest = [FIRStorageUtils defaultRequestForPath:reference.path];
+ _fetcherService = service;
+ _fetcherService.maxRetryInterval = _reference.storage.maxOperationRetryTime;
+ }
+ return self;
+}
+
+- (FIRStorageTaskSnapshot *)snapshot {
+ @synchronized(self) {
+ NSProgress *progress =
+ [NSProgress progressWithTotalUnitCount:self.progress.totalUnitCount];
+ progress.completedUnitCount = self.progress.completedUnitCount;
+ FIRStorageTaskSnapshot *snapshot =
+ [[FIRStorageTaskSnapshot alloc] initWithTask:self
+ state:self.state
+ metadata:self.metadata
+ reference:self.reference
+ progress:progress
+ error:[self.error copy]];
+ return snapshot;
+ }
+}
+
+@end
diff --git a/Firebase/Storage/FIRStorageTaskSnapshot.h b/Firebase/Storage/FIRStorageTaskSnapshot.h
new file mode 100644
index 0000000..b654c09
--- /dev/null
+++ b/Firebase/Storage/FIRStorageTaskSnapshot.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 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>
+
+#import "FIRStorageConstants.h"
+#import "FIRStorageSwiftNameSupport.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class FIRStorageMetadata;
+@class FIRStorageReference;
+@class FIRStorageTask;
+
+/**
+ * FIRStorageTaskSnapshot represents an immutable view of a task.
+ * A Snapshot contains a task, storage reference, metadata (if it exists),
+ * progress, and an error (if one occurred).
+ */
+FIR_SWIFT_NAME(StorageTaskSnapshot)
+@interface FIRStorageTaskSnapshot : NSObject
+
+/**
+ * Subclass of FIRStorageTask this snapshot represents.
+ */
+@property(readonly, copy, nonatomic) __kindof FIRStorageTask *task;
+
+/**
+ * Metadata returned by the task, or nil if no metadata returned.
+ */
+@property(readonly, copy, nonatomic, nullable) FIRStorageMetadata *metadata;
+
+/**
+ * FIRStorageReference this task is operates on.
+ */
+@property(readonly, copy, nonatomic) FIRStorageReference *reference;
+
+/**
+ * NSProgress object which tracks the progess of an upload or download.
+ */
+@property(readonly, strong, nonatomic, nullable) NSProgress *progress;
+
+/**
+ * Error during task execution, or nil if no error occurred.
+ */
+@property(readonly, copy, nonatomic, nullable) NSError *error;
+
+/**
+ * Status of the task.
+ */
+@property(readonly, nonatomic) FIRStorageTaskStatus status;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/FIRStorageTaskSnapshot.m b/Firebase/Storage/FIRStorageTaskSnapshot.m
new file mode 100644
index 0000000..050d05c
--- /dev/null
+++ b/Firebase/Storage/FIRStorageTaskSnapshot.m
@@ -0,0 +1,88 @@
+// Copyright 2017 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 "FIRStorageTaskSnapshot.h"
+#import "FIRStorageTaskSnapshot_Private.h"
+
+#import "FIRStorageTask_Private.h"
+
+@implementation FIRStorageTaskSnapshot
+
+- (instancetype)initWithTask:(__kindof FIRStorageTask *)task
+ state:(FIRStorageTaskState)state
+ metadata:(nullable FIRStorageMetadata *)metadata
+ reference:(FIRStorageReference *)reference
+ progress:(nullable NSProgress *)progress
+ error:(nullable NSError *)error {
+ self = [super init];
+ if (self) {
+ _task = task;
+ _metadata = metadata;
+ _reference = reference;
+ _progress = progress;
+ _error = error;
+
+ switch (state) {
+ case FIRStorageTaskStateQueueing:
+ case FIRStorageTaskStateRunning:
+ case FIRStorageTaskStateResuming:
+ _status = FIRStorageTaskStatusResume;
+ break;
+
+ case FIRStorageTaskStateProgress:
+ _status = FIRStorageTaskStatusProgress;
+ break;
+
+ case FIRStorageTaskStatePaused:
+ case FIRStorageTaskStatePausing:
+ _status = FIRStorageTaskStatusPause;
+ break;
+
+ case FIRStorageTaskStateSuccess:
+ case FIRStorageTaskStateCompleting:
+ _status = FIRStorageTaskStatusSuccess;
+ break;
+
+ case FIRStorageTaskStateCancelled:
+ case FIRStorageTaskStateFailing:
+ case FIRStorageTaskStateFailed:
+ _status = FIRStorageTaskStatusFailure;
+ break;
+
+ default:
+ _status = FIRStorageTaskStatusUnknown;
+ }
+ }
+ return self;
+}
+
+
+-(NSString *)description {
+ switch (_status) {
+ case FIRStorageTaskStatusResume:
+ return @"<State: Resume>";
+ case FIRStorageTaskStatusProgress:
+ return [NSString stringWithFormat:@"<State: Progress, Progress: %@>", _progress];
+ case FIRStorageTaskStatusPause:
+ return @"<State: Paused>";
+ case FIRStorageTaskStatusSuccess:
+ return @"<State: Success>";
+ case FIRStorageTaskStatusFailure:
+ return [NSString stringWithFormat:@"<State: Failed, Error: %@>", _error];
+ default:
+ return @"<State: Unknown>";
+ };
+}
+
+@end
diff --git a/Firebase/Storage/FIRStorageTokenAuthorizer.m b/Firebase/Storage/FIRStorageTokenAuthorizer.m
new file mode 100644
index 0000000..36b94a9
--- /dev/null
+++ b/Firebase/Storage/FIRStorageTokenAuthorizer.m
@@ -0,0 +1,131 @@
+// Copyright 2017 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 "../../Firebase/Core/Private/FIRAppInternal.h"
+
+#import "FIRStorageTokenAuthorizer.h"
+
+#import "FIRStorageConstants.h"
+#import "FIRStorageConstants_Private.h"
+#import "FIRStorageErrors.h"
+
+#import "FirebaseStorage.h"
+
+#import "FIRApp.h"
+#import "FIROptions.h"
+
+@implementation FIRStorageTokenAuthorizer {
+ @private
+ // Firebase App which vends tokens
+ FIRApp *_app;
+}
+
+@synthesize fetcherService = _fetcherService;
+
+- (instancetype)initWithApp:(FIRApp *)app fetcherService:(GTMSessionFetcherService *)service {
+ self = [super init];
+ if (self) {
+ _app = app;
+ _fetcherService = service;
+ }
+ return self;
+}
+
+#pragma mark - GTMFetcherAuthorizationProtocol methods
+
+- (void)authorizeRequest:(NSMutableURLRequest *)request
+ delegate:(id)delegate
+ didFinishSelector:(SEL)sel {
+ // Set version header on each request
+ NSString *versionString = [NSString stringWithFormat:@"ios/%s", FIRStorageVersionString];
+ [request setValue:versionString forHTTPHeaderField:@"x-firebase-storage-version"];
+
+ // Set GMP ID on each request
+ NSString *GMPAppId = _app.options.googleAppID;
+ [request setValue:GMPAppId forHTTPHeaderField:@"x-firebase-gmpid"];
+
+ if (delegate && sel) {
+ id selfParam = self;
+ NSMethodSignature *sig = [delegate methodSignatureForSelector:sel];
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
+ [invocation setSelector:sel];
+ [invocation setTarget:delegate];
+ [invocation setArgument:&selfParam atIndex:2];
+ [invocation setArgument:&request atIndex:3];
+
+ dispatch_queue_t callbackQueue = self.fetcherService.callbackQueue;
+ if (!callbackQueue) {
+ callbackQueue = dispatch_get_main_queue();
+ }
+
+ [invocation retainArguments];
+ if (_app.getTokenImplementation) {
+ [_app getTokenForcingRefresh:NO
+ withCallback:^(NSString *_Nullable token, NSError *_Nullable error) {
+ if (error) {
+ NSMutableDictionary *errorDictionary =
+ [NSMutableDictionary dictionaryWithDictionary:error.userInfo];
+ errorDictionary[kFIRStorageResponseErrorDomain] = error.domain;
+ errorDictionary[kFIRStorageResponseErrorCode] = @(error.code);
+
+ NSError *tokenError =
+ [FIRStorageErrors errorWithCode:FIRStorageErrorCodeUnauthenticated
+ infoDictionary:errorDictionary];
+ [invocation setArgument:&tokenError atIndex:4];
+ } else if (token) {
+ NSString *firebaseToken =
+ [NSString stringWithFormat:kFIRStorageAuthTokenFormat, token];
+ [request setValue:firebaseToken forHTTPHeaderField:@"Authorization"];
+ }
+ dispatch_async(callbackQueue, ^{
+ [invocation invoke];
+ });
+ }];
+ } else {
+ dispatch_async(callbackQueue, ^{
+ [invocation invoke];
+ });
+ }
+ }
+}
+
+// Note that stopAuthorization, isAuthorizingRequest, and userEmail
+// aren't relevant with the Firebase App/Auth implementation of tokens,
+// and thus aren't implemented. Token refresh is handled transparently
+// for us, and we don't allow the auth request to be stopped.
+// Auth is also not required so the world doesn't stop.
+- (void)stopAuthorization {
+ // Noop
+}
+
+- (void)stopAuthorizationForRequest:(NSURLRequest *)request {
+ // Noop
+}
+
+- (BOOL)isAuthorizingRequest:(NSURLRequest *)request {
+ return NO;
+}
+
+- (BOOL)isAuthorizedRequest:(NSURLRequest *)request {
+ NSString *authHeader = request.allHTTPHeaderFields[@"Authorization"];
+ BOOL isFirebaseToken = [authHeader hasPrefix:@"Firebase"];
+ return isFirebaseToken;
+}
+
+- (NSString *)userEmail {
+ // Noop
+ return nil;
+}
+
+@end
diff --git a/Firebase/Storage/FIRStorageUpdateMetadataTask.m b/Firebase/Storage/FIRStorageUpdateMetadataTask.m
new file mode 100644
index 0000000..dbd276b
--- /dev/null
+++ b/Firebase/Storage/FIRStorageUpdateMetadataTask.m
@@ -0,0 +1,91 @@
+// Copyright 2017 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 "FIRStorageUpdateMetadataTask.h"
+
+#import "FIRStorageMetadata_Private.h"
+#import "FIRStorageTask_Private.h"
+
+@implementation FIRStorageUpdateMetadataTask {
+ @private
+ FIRStorageVoidMetadataError _completion;
+ // Metadata used in the update request
+ FIRStorageMetadata *_updateMetadata;
+}
+
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service
+ metadata:(FIRStorageMetadata *)metadata
+ completion:(FIRStorageVoidMetadataError)completion {
+ self = [super initWithReference:reference fetcherService:service];
+ if (self) {
+ _updateMetadata = [metadata copy];
+ _completion = [completion copy];
+ }
+ return self;
+}
+
+- (void)enqueue {
+ NSMutableURLRequest *request = [self.baseRequest mutableCopy];
+ NSDictionary *updateDictionary = [_updateMetadata dictionaryRepresentation];
+ NSData *updateData = [NSData frs_dataFromJSONDictionary:updateDictionary];
+ request.HTTPMethod = @"PATCH";
+ request.timeoutInterval = self.reference.storage.maxUploadRetryTime;
+ request.HTTPBody = updateData;
+ NSString *typeString = @"application/json; charset=UTF-8";
+ [request setValue:typeString forHTTPHeaderField:@"Content-Type"];
+ NSString *lengthString = [NSString stringWithFormat:@"%zu", (unsigned long)[updateData length]];
+ [request setValue:lengthString forHTTPHeaderField:@"Content-Length"];
+
+ FIRStorageVoidMetadataError callback = _completion;
+ _completion = nil;
+
+ GTMSessionFetcher *fetcher = [self.fetcherService fetcherWithRequest:request];
+ fetcher.comment = @"UpdateMetadataTask";
+ [fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
+ if (error) {
+ if (!self.error) {
+ self.error = [FIRStorageErrors errorWithServerError:error reference:self.reference];
+ }
+ if (callback) {
+ callback(nil, self.error);
+ }
+ return;
+ }
+
+ NSDictionary *responseDictionary = [NSDictionary frs_dictionaryFromJSONData:data];
+ if (responseDictionary) {
+ FIRStorageMetadata *metadata =
+ [[FIRStorageMetadata alloc] initWithDictionary:responseDictionary];
+ [metadata setType:FIRStorageMetadataTypeFile];
+ if (callback){
+ callback(metadata, nil);
+ }
+ } else {
+ NSString *returnedData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ NSString *invalidDataString =
+ [NSString stringWithFormat:kFIRStorageInvalidDataFormat, returnedData];
+ NSDictionary *dict;
+ if (invalidDataString.length > 0) {
+ dict = @{NSLocalizedFailureReasonErrorKey : invalidDataString};
+ }
+ self.error = [FIRStorageErrors errorWithCode:FIRStorageErrorCodeUnknown infoDictionary:dict];
+ if (callback) {
+ callback(nil, self.error);
+ }
+ }
+ }];
+}
+
+@end
diff --git a/Firebase/Storage/FIRStorageUploadTask.h b/Firebase/Storage/FIRStorageUploadTask.h
new file mode 100644
index 0000000..cdf1d29
--- /dev/null
+++ b/Firebase/Storage/FIRStorageUploadTask.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017 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>
+
+#import "FIRStorageObservableTask.h"
+#import "FIRStorageSwiftNameSupport.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * FIRStorageUploadTask implements resumable uploads to a file in Firebase Storage.
+ * Uploads can be returned on completion with a completion callback, and can be monitored
+ * by attaching observers, or controlled by calling FIRStorageTask#pause, FIRStorageTask#resume,
+ * or FIRStorageTask#cancel.
+ * Uploads can take NSData in memory, or an NSURL to a file on disk.
+ * Uploads are performed on a background queue, and callbacks are raised on the developer
+ * specified callbackQueue in FIRStorage, or the main queue if left unspecified.
+ * Currently all uploads must be initiated and managed on the main queue.
+ */
+FIR_SWIFT_NAME(StorageUploadTask)
+@interface FIRStorageUploadTask : FIRStorageObservableTask<FIRStorageTaskManagement>
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/FIRStorageUploadTask.m b/Firebase/Storage/FIRStorageUploadTask.m
new file mode 100644
index 0000000..74741b0
--- /dev/null
+++ b/Firebase/Storage/FIRStorageUploadTask.m
@@ -0,0 +1,199 @@
+// Copyright 2017 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 "FIRStorageUploadTask.h"
+
+#import "FIRStorageConstants_Private.h"
+#import "FIRStorageMetadata_Private.h"
+#import "FIRStorageObservableTask_Private.h"
+#import "FIRStorageTask_Private.h"
+#import "FIRStorageUploadTask_Private.h"
+
+#import "GTMSessionUploadFetcher.h"
+
+@implementation FIRStorageUploadTask
+
+@synthesize progress = _progress;
+
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service
+ data:(NSData *)uploadData
+ metadata:(FIRStorageMetadata *)metadata {
+ self = [super initWithReference:reference fetcherService:service];
+ if (self) {
+ _uploadMetadata = [metadata copy];
+ _uploadData = [uploadData copy];
+ _progress = [NSProgress progressWithTotalUnitCount:[_uploadData length]];
+
+ if (!_uploadMetadata.contentType) {
+ _uploadMetadata.contentType = @"application/octet-stream";
+ }
+ }
+ return self;
+}
+
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service
+ file:(NSURL *)fileURL
+ metadata:(FIRStorageMetadata *)metadata {
+ self = [super initWithReference:reference fetcherService:service];
+ if (self) {
+ _uploadMetadata = [metadata copy];
+ _fileURL = [fileURL copy];
+ _progress = [NSProgress progressWithTotalUnitCount:0];
+
+ NSString *mimeType = [FIRStorageUtils MIMETypeForExtension:[_fileURL pathExtension]];
+
+ if (!_uploadMetadata.contentType) {
+ _uploadMetadata.contentType = mimeType ?: @"application/octet-stream";
+ }
+ }
+ return self;
+}
+
+- (void)enqueue {
+ NSAssert([NSThread isMainThread], @"Upload attempting to execute on non main queue! Please only "
+ @"execute this method on the main queue.");
+ self.state = FIRStorageTaskStateQueueing;
+
+ NSMutableURLRequest *request = [self.baseRequest mutableCopy];
+ request.HTTPMethod = @"POST";
+ request.timeoutInterval = self.reference.storage.maxUploadRetryTime;
+ NSData *bodyData = [NSData frs_dataFromJSONDictionary:[_uploadMetadata dictionaryRepresentation]];
+ request.HTTPBody = bodyData;
+ [request setValue:@"application/json; charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
+ NSString *contentLengthString = [NSString stringWithFormat:@"%zu",
+ (unsigned long)[bodyData length]];
+ [request setValue:contentLengthString forHTTPHeaderField:@"Content-Length"];
+
+ NSURLComponents *components =
+ [NSURLComponents componentsWithURL:request.URL resolvingAgainstBaseURL:NO];
+
+ if ([components.host isEqual:kGCSHost]) {
+ [components setPercentEncodedPath:[@"/upload" stringByAppendingString:components.path]];
+ }
+
+ NSDictionary *queryParams = @{ @"uploadType" : @"resumable", @"name" : self.uploadMetadata.path };
+ [components setPercentEncodedQuery:[FIRStorageUtils queryStringForDictionary:queryParams]];
+ request.URL = components.URL;
+
+ GTMSessionUploadFetcher *uploadFetcher =
+ [GTMSessionUploadFetcher uploadFetcherWithRequest:request
+ uploadMIMEType:_uploadMetadata.contentType
+ chunkSize:kGTMSessionUploadFetcherStandardChunkSize
+ fetcherService:self.fetcherService];
+
+ if (_uploadData) {
+ [uploadFetcher setUploadData:_uploadData];
+ uploadFetcher.comment = @"Data UploadTask";
+ } else if (_fileURL) {
+ [uploadFetcher setUploadFileURL:_fileURL];
+ uploadFetcher.comment = @"File UploadTask";
+ }
+
+ uploadFetcher.maxRetryInterval = self.reference.storage.maxUploadRetryTime;
+
+ [uploadFetcher setSendProgressBlock:^(int64_t bytesSent, int64_t totalBytesSent,
+ int64_t totalBytesExpectedToSend) {
+ self.state = FIRStorageTaskStateProgress;
+ self.progress.completedUnitCount = totalBytesSent;
+ self.progress.totalUnitCount = totalBytesExpectedToSend;
+ self.metadata = _uploadMetadata;
+ [self fireHandlersForStatus:FIRStorageTaskStatusProgress snapshot:self.snapshot];
+ self.state = FIRStorageTaskStateRunning;
+ }];
+
+ _uploadFetcher = uploadFetcher;
+
+ // Process fetches
+ self.state = FIRStorageTaskStateRunning;
+ [_uploadFetcher beginFetchWithCompletionHandler:^(NSData *_Nullable data,
+ NSError *_Nullable error) {
+ // Fire last progress updates
+ [self fireHandlersForStatus:FIRStorageTaskStatusProgress snapshot:self.snapshot];
+
+ // Handle potential issues with upload
+ if (error) {
+ self.state = FIRStorageTaskStateFailed;
+ self.error = [FIRStorageErrors errorWithServerError:error reference:self.reference];
+ self.metadata = _uploadMetadata;
+ [self fireHandlersForStatus:FIRStorageTaskStatusFailure snapshot:self.snapshot];
+ [self removeAllObservers];
+ return;
+ }
+
+ // Upload completed successfully, fire completion callbacks
+ self.state = FIRStorageTaskStateSuccess;
+
+ NSDictionary *responseDictionary = [NSDictionary frs_dictionaryFromJSONData:data];
+ if (responseDictionary) {
+ FIRStorageMetadata *metadata =
+ [[FIRStorageMetadata alloc] initWithDictionary:responseDictionary];
+ [metadata setType:FIRStorageMetadataTypeFile];
+ self.metadata = metadata;
+ } else {
+ NSString *returnedData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ NSString *invalidDataString =
+ [NSString stringWithFormat:kFIRStorageInvalidDataFormat, returnedData];
+ NSDictionary *dict;
+ if (invalidDataString.length > 0) {
+ dict = @{NSLocalizedFailureReasonErrorKey : invalidDataString};
+ }
+ self.error =
+ [FIRStorageErrors errorWithCode:FIRStorageErrorCodeUnknown infoDictionary:dict];
+ }
+
+ [self fireHandlersForStatus:FIRStorageTaskStatusSuccess snapshot:self.snapshot];
+ [self removeAllObservers];
+ }];
+}
+
+#pragma mark - Upload Management
+
+- (void)cancel {
+ NSAssert([NSThread isMainThread], @"Cancel attempting to execute on non main queue! Please only "
+ @"execute this method on the main queue.");
+ self.state = FIRStorageTaskStateCancelled;
+ [_uploadFetcher stopFetching];
+ if (self.state != FIRStorageTaskStateSuccess) {
+ self.metadata = _uploadMetadata;
+ }
+ self.error = [FIRStorageErrors errorWithCode:FIRStorageErrorCodeCancelled];
+ [self fireHandlersForStatus:FIRStorageTaskStatusFailure snapshot:self.snapshot];
+}
+
+- (void)pause {
+ NSAssert([NSThread isMainThread], @"Pause attempting to execute on non main queue! Please only "
+ @"execute this method on the main queue.");
+ self.state = FIRStorageTaskStatePaused;
+ [_uploadFetcher pauseFetching];
+ if (self.state != FIRStorageTaskStateSuccess) {
+ self.metadata = _uploadMetadata;
+ }
+ [self fireHandlersForStatus:FIRStorageTaskStatusPause snapshot:self.snapshot];
+}
+
+- (void)resume {
+ NSAssert([NSThread isMainThread], @"Resume attempting to execute on non main queue! Please only "
+ @"execute this method on the main queue.");
+ self.state = FIRStorageTaskStateResuming;
+ [_uploadFetcher resumeFetching];
+ if (self.state != FIRStorageTaskStateSuccess) {
+ self.metadata = _uploadMetadata;
+ }
+ [self fireHandlersForStatus:FIRStorageTaskStatusResume snapshot:self.snapshot];
+ self.state = FIRStorageTaskStateRunning;
+}
+
+@end
diff --git a/Firebase/Storage/FIRStorageUtils.m b/Firebase/Storage/FIRStorageUtils.m
new file mode 100644
index 0000000..e0abe0a
--- /dev/null
+++ b/Firebase/Storage/FIRStorageUtils.m
@@ -0,0 +1,121 @@
+// Copyright 2017 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 <MobileCoreServices/MobileCoreServices.h>
+
+#import "FIRStorageUtils.h"
+
+#import "FIRStorageConstants_Private.h"
+#import "FIRStoragePath.h"
+
+#import "FirebaseStorage.h"
+
+#import "GTMSessionFetcher.h"
+
+// This is the list at https://cloud.google.com/storage/docs/json_api/ without & and +.
+NSString *const kGCSObjectAllowedCharacterSet =
+ @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$'()*,;=:@";
+
+@implementation FIRStorageUtils
+
++ (nullable NSString *)GCSEscapedString:(NSString *)string {
+ NSCharacterSet *allowedCharacters =
+ [NSCharacterSet characterSetWithCharactersInString:kGCSObjectAllowedCharacterSet];
+
+ return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters];
+}
+
++ (nullable NSString *)MIMETypeForExtension:(NSString *)extension {
+ if (extension == nil) {
+ return nil;
+ }
+
+ CFStringRef pathExtension = (__bridge_retained CFStringRef)extension;
+ CFStringRef type =
+ UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension, NULL);
+ NSString *mimeType =
+ (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass(type, kUTTagClassMIMEType);
+ CFRelease(pathExtension);
+ if (type != NULL) {
+ CFRelease(type);
+ }
+
+ return mimeType;
+}
+
++ (NSString *)queryStringForDictionary:(nullable NSDictionary *)dictionary {
+ if (!dictionary) {
+ return @"";
+ }
+
+ __block NSMutableArray *queryItems = [[NSMutableArray alloc] initWithCapacity:[dictionary count]];
+ [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull name, NSString *_Nonnull value,
+ BOOL *_Nonnull stop) {
+ NSString *item =
+ [FIRStorageUtils GCSEscapedString:[NSString stringWithFormat:@"%@=%@", name, value]];
+ [queryItems addObject:item];
+ }];
+ return [queryItems componentsJoinedByString:@"&"];
+}
+
++ (NSURLRequest *)defaultRequestForPath:(FIRStoragePath *)path {
+ NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
+ NSURLComponents *components = [[NSURLComponents alloc] init];
+ [components setScheme:kFIRStorageScheme];
+ [components setHost:kFIRStorageHost];
+ NSString *encodedPath = [self encodedURLForPath:path];
+ [components setPercentEncodedPath:encodedPath];
+ [request setURL:components.URL];
+ return request;
+}
+
++ (NSString *)encodedURLForPath:(FIRStoragePath *)path {
+ NSString *bucketName = [FIRStorageUtils GCSEscapedString:path.bucket];
+ NSString *objectName = [FIRStorageUtils GCSEscapedString:path.object];
+ NSString *bucketFormat = [NSString stringWithFormat:kFIRStorageBucketPathFormat, bucketName];
+ NSString *urlPath = [@"/" stringByAppendingPathComponent:bucketFormat];
+ if (objectName) {
+ NSString *objectFormat = [NSString stringWithFormat:kFIRStorageObjectPathFormat, objectName];
+ urlPath = [urlPath stringByAppendingFormat:@"/%@", objectFormat];
+ } else {
+ urlPath = [urlPath stringByAppendingString:@"/o"];
+ }
+ return [@"/" stringByAppendingString:[kFIRStorageVersionPath stringByAppendingString:urlPath]];
+}
+
+@end
+
+@implementation NSDictionary (FIRStorageNSDictionaryJSONHelpers)
+
++ (nullable instancetype)frs_dictionaryFromJSONData:(nullable NSData *)data {
+ if (!data) {
+ return nil;
+ }
+ return [NSJSONSerialization JSONObjectWithData:data
+ options:NSJSONReadingMutableContainers
+ error:nil];
+}
+
+@end
+
+@implementation NSData (FIRStorageNSDataJSONHelpers)
+
++ (nullable instancetype)frs_dataFromJSONDictionary:(nullable NSDictionary *)dictionary {
+ if (!dictionary) {
+ return nil;
+ }
+ return [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:nil];
+}
+
+@end \ No newline at end of file
diff --git a/Firebase/Storage/FirebaseStorage.h b/Firebase/Storage/FirebaseStorage.h
new file mode 100644
index 0000000..1e5dfa4
--- /dev/null
+++ b/Firebase/Storage/FirebaseStorage.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017 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 "FIRStorage.h"
+#import "FIRStorageConstants.h"
+#import "FIRStorageDownloadTask.h"
+#import "FIRStorageMetadata.h"
+#import "FIRStorageObservableTask.h"
+#import "FIRStorageReference.h"
+#import "FIRStorageTask.h"
+#import "FIRStorageTaskSnapshot.h"
+#import "FIRStorageUploadTask.h"
diff --git a/Firebase/Storage/FirebaseStorage.podspec b/Firebase/Storage/FirebaseStorage.podspec
new file mode 100644
index 0000000..69c6ddc
--- /dev/null
+++ b/Firebase/Storage/FirebaseStorage.podspec
@@ -0,0 +1,44 @@
+# This podspec is not intended to be deployed. It is solely for the static
+# library framework build process at
+# https://github.com/firebase/firebase-ios-sdk/tree/master/BuildFrameworks
+
+Pod::Spec.new do |s|
+ s.name = 'FirebaseStorage'
+ s.version = '2.0.0'
+ s.summary = 'Firebase Open Source Libraries for iOS.'
+
+ s.description = <<-DESC
+Simplify your iOS development, grow your user base, and monetize more effectively with Firebase.
+ DESC
+
+ s.homepage = 'https://firebase.google.com'
+ s.license = { :type => 'Apache', :file => '../../LICENSE' }
+ s.authors = 'Google, Inc.'
+
+ # NOTE that the FirebaseDev pod is neither publicly deployed nor yet interchangeable with the
+ # Firebase pod
+ s.source = { :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => s.version.to_s }
+ s.social_media_url = 'https://twitter.com/Firebase'
+ s.ios.deployment_target = '7.0'
+
+ s.source_files = '**/*.[mh]'
+ s.public_header_files =
+ 'FirebaseStorage.h',
+ 'FIRStorage.h',
+ 'FIRStorageConstants.h',
+ 'FIRStorageDownloadTask.h',
+ 'FIRStorageMetadata.h',
+ 'FIRStorageObservableTask.h',
+ 'FIRStorageReference.h',
+ 'FIRStorageSwiftNameSupport.h',
+ 'FIRStorageTask.h',
+ 'FIRStorageTaskSnapshot.h',
+ 'FIRStorageUploadTask.h'
+
+ s.framework = 'MobileCoreServices'
+# s.dependency 'FirebaseDev/Core'
+ s.dependency 'GTMSessionFetcher/Core', '~> 1.1'
+ s.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' =>
+ '$(inherited) ' +
+ 'FIRStorage_VERSION=' + s.version.to_s }
+end
diff --git a/Firebase/Storage/Private/FIRStorageConstants_Private.h b/Firebase/Storage/Private/FIRStorageConstants_Private.h
new file mode 100644
index 0000000..50addb1
--- /dev/null
+++ b/Firebase/Storage/Private/FIRStorageConstants_Private.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2017 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>
+
+@class FIRStorageMetadata;
+
+NS_ASSUME_NONNULL_BEGIN
+
+FOUNDATION_EXPORT NSString *const kGCSScheme;
+FOUNDATION_EXPORT NSString *const kGCSHost;
+FOUNDATION_EXPORT NSString *const kGCSUploadPath;
+FOUNDATION_EXPORT NSString *const kGCSStorageVersionPath;
+FOUNDATION_EXPORT NSString *const kGCSBucketPathFormat;
+FOUNDATION_EXPORT NSString *const kGCSObjectPathFormat;
+
+FOUNDATION_EXPORT NSString *const kFIRStorageScheme;
+FOUNDATION_EXPORT NSString *const kFIRStorageHost;
+FOUNDATION_EXPORT NSString *const kFIRStorageVersionPath;
+FOUNDATION_EXPORT NSString *const kFIRStorageBucketPathFormat;
+FOUNDATION_EXPORT NSString *const kFIRStorageObjectPathFormat;
+FOUNDATION_EXPORT NSString *const kFIRStorageFullPathFormat;
+
+FOUNDATION_EXPORT NSString *const kFIRStorageAuthTokenFormat;
+FOUNDATION_EXPORT NSString *const kFIRStorageDefaultBucketFormat;
+
+FOUNDATION_EXPORT NSString *const kFIRStorageResponseErrorDomain;
+FOUNDATION_EXPORT NSString *const kFIRStorageResponseErrorCode;
+FOUNDATION_EXPORT NSString *const kFIRStorageResponseBody;
+
+FOUNDATION_EXPORT NSString *const kFIRStorageTaskStatusResumeNotification;
+FOUNDATION_EXPORT NSString *const kFIRStorageTaskStatusPauseNotification;
+FOUNDATION_EXPORT NSString *const kFIRStorageTaskStatusProgressNotification;
+FOUNDATION_EXPORT NSString *const kFIRStorageTaskStatusCompleteNotification;
+FOUNDATION_EXPORT NSString *const kFIRStorageTaskStatusFailureNotification;
+
+FOUNDATION_EXPORT NSString *const kFIRStorageMetadataBucket;
+FOUNDATION_EXPORT NSString *const kFIRStorageMetadataCacheControl;
+FOUNDATION_EXPORT NSString *const kFIRStorageMetadataContentDisposition;
+FOUNDATION_EXPORT NSString *const kFIRStorageMetadataContentEncoding;
+FOUNDATION_EXPORT NSString *const kFIRStorageMetadataContentLanguage;
+FOUNDATION_EXPORT NSString *const kFIRStorageMetadataContentType;
+FOUNDATION_EXPORT NSString *const kFIRStorageMetadataCustomMetadata;
+FOUNDATION_EXPORT NSString *const kFIRStorageMetadataSize;
+FOUNDATION_EXPORT NSString *const kFIRStorageMetadataDownloadURLs;
+FOUNDATION_EXPORT NSString *const kFIRStorageMetadataGeneration;
+FOUNDATION_EXPORT NSString *const kFIRStorageMetadataMetageneration;
+FOUNDATION_EXPORT NSString *const kFIRStorageMetadataTimeCreated;
+FOUNDATION_EXPORT NSString *const kFIRStorageMetadataUpdated;
+FOUNDATION_EXPORT NSString *const kFIRStorageMetadataName;
+FOUNDATION_EXPORT NSString *const kFIRStorageMetadataDownloadTokens;
+
+FOUNDATION_EXPORT NSString *const kFIRStorageInvalidDataFormat;
+FOUNDATION_EXPORT NSString *const kFIRStorageInvalidObserverStatus;
+
+FOUNDATION_EXPORT NSString *const kFIRStorageBundleIdentifier;
+
+/**
+ * Enum representing the internal state of an upload or download task.
+ */
+typedef NS_ENUM(NSInteger, FIRStorageTaskState) {
+ /**
+ * Unknown task state
+ */
+ FIRStorageTaskStateUnknown,
+
+ /**
+ * Task is being queued is ready to run
+ */
+ FIRStorageTaskStateQueueing,
+
+ /**
+ * Task is resuming from a paused state
+ */
+ FIRStorageTaskStateResuming,
+
+ /**
+ * Task is currently running
+ */
+ FIRStorageTaskStateRunning,
+
+ /**
+ * Task reporting a progress event
+ */
+ FIRStorageTaskStateProgress,
+
+ /**
+ * Task is pausing
+ */
+ FIRStorageTaskStatePausing,
+
+ /**
+ * Task is completing successfully
+ */
+ FIRStorageTaskStateCompleting,
+
+ /**
+ * Task is failing unrecoverably
+ */
+ FIRStorageTaskStateFailing,
+
+ /**
+ * Task paused successfully
+ */
+ FIRStorageTaskStatePaused,
+
+ /**
+ * Task cancelled successfully
+ */
+ FIRStorageTaskStateCancelled,
+
+ /**
+ * Task completed successfully
+ */
+ FIRStorageTaskStateSuccess,
+
+ /**
+ * Task failed unrecoverably
+ */
+ FIRStorageTaskStateFailed
+};
+
+/**
+ * Represents the various types of metadata: Files or Folders.
+ */
+typedef NS_ENUM(NSUInteger, FIRStorageMetadataType) {
+ FIRStorageMetadataTypeUnknown,
+ FIRStorageMetadataTypeFile,
+ FIRStorageMetadataTypeFolder,
+};
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/Private/FIRStorageDeleteTask.h b/Firebase/Storage/Private/FIRStorageDeleteTask.h
new file mode 100644
index 0000000..c97fd27
--- /dev/null
+++ b/Firebase/Storage/Private/FIRStorageDeleteTask.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017 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 "FIRStorageTask.h"
+
+@class GTMSessionFetcherService;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Task which provides the ability to delete an object in Firebase Storage.
+ */
+@interface FIRStorageDeleteTask : FIRStorageTask<FIRStorageTaskManagement>
+
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service
+ completion:(FIRStorageVoidError)completion;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/Private/FIRStorageDownloadTask_Private.h b/Firebase/Storage/Private/FIRStorageDownloadTask_Private.h
new file mode 100644
index 0000000..293d1d5
--- /dev/null
+++ b/Firebase/Storage/Private/FIRStorageDownloadTask_Private.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017 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.
+ */
+
+@class GTMSessionFetcherService;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRStorageDownloadTask ()
+
+/**
+ * Bytes which have been downloaded so far.
+ */
+@property(readonly, nonatomic) NSData *downloadData;
+
+/**
+ * The file on disk to write to.
+ */
+@property(copy, nonatomic) NSURL *fileURL;
+
+/**
+ * Initializes a download task with a base FIRStorageReference and GTMSessionFetcherService.
+ * @param reference The base FIRStorageReference which fetchers use for configuration.
+ * @param service The GTMSessionFetcherService which will create fetchers.
+ * @param fileURL The system URL to download to. If nil, download in memory as bytes.
+ * @return Returns an instance of FIRStorageDownloadTask
+ */
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service
+ file:(nullable NSURL *)fileURL;
+
+/**
+ * Cancels the download task and passes an appropriate error to the developer.
+ * @param error NSError to propegate to the developer.
+ */
+- (void)cancelWithError:(NSError *)error;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/Private/FIRStorageErrors.h b/Firebase/Storage/Private/FIRStorageErrors.h
new file mode 100644
index 0000000..7c236d9
--- /dev/null
+++ b/Firebase/Storage/Private/FIRStorageErrors.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 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 "FIRStorageConstants.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class FIRStorageReference;
+
+/**
+ * Adds wrappers for common Firebase Storage errors (including creating errors from GCS errors).
+ * For more information on unwrapping GCS errors, see the GCS errors docs:
+ * https://cloud.google.com/storage/docs/json_api/v1/status-codes
+ * This is never publicly exposed to end developers (as they will simply see an NSError).
+ */
+@interface FIRStorageErrors : NSObject
+
+/**
+ * Creates a Firebase Storage error from a specific FIRStorageErrorCode.
+ */
++ (NSError *)errorWithCode:(FIRStorageErrorCode)code;
+
+/**
+ * Creates a Firebase Storage error from a specific FIRStorageErrorCode while adding
+ * custom info from an optionally provided info dictionary.
+ */
++ (NSError *)errorWithCode:(FIRStorageErrorCode)code
+ infoDictionary:(nullable NSDictionary *)dictionary;
+
+/**
+ * Creates a Firebase Storage error from a specific GCS error and FIRStorageReference.
+ * @param error Server error to wrap and return as a Firebase Storage error.
+ * @param reference FIRStorageReference which provides context about the request being made.
+ * @return Returns an Firebase Storage error, or nil if no error is provided.
+ */
++ (nullable NSError *)errorWithServerError:(nullable NSError *)error
+ reference:(nullable FIRStorageReference *)reference;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/Private/FIRStorageGetMetadataTask.h b/Firebase/Storage/Private/FIRStorageGetMetadataTask.h
new file mode 100644
index 0000000..5f1dc8f
--- /dev/null
+++ b/Firebase/Storage/Private/FIRStorageGetMetadataTask.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017 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 "FIRStorageTask.h"
+
+@class GTMSessionFetcherService;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Task which provides the ability to get metadata on an object in Firebase Storage.
+ */
+@interface FIRStorageGetMetadataTask : FIRStorageTask<FIRStorageTaskManagement>
+
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service
+ completion:(FIRStorageVoidMetadataError)completion;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/Private/FIRStorageMetadata_Private.h b/Firebase/Storage/Private/FIRStorageMetadata_Private.h
new file mode 100644
index 0000000..629c935
--- /dev/null
+++ b/Firebase/Storage/Private/FIRStorageMetadata_Private.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017 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 "FIRStorageConstants_Private.h"
+
+@class FIRStorageReference;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRStorageMetadata ()
+
+@property(readwrite, nonatomic) NSString *name;
+
+@property(readwrite, nonatomic) NSString *path;
+
+@property(readwrite, nonatomic) FIRStorageReference *reference;
+
+/**
+ * The type of the object, either a "File" or a "Folder".
+ */
+@property(readwrite) FIRStorageMetadataType type;
+
+/**
+ * Returns an RFC3339 formatted date from a string.
+ * @param dateString An NSString of the form: yyyy-MM-ddTHH:mm:ss.SSSZ.
+ * @return An NSDate populated from the string or nil if conversion isn't possible.
+ */
+- (nullable NSDate *)dateFromRFC3339String:(NSString *)dateString;
+
+/**
+ * Returns an RFC3339 formatted string from an NSDate object.
+ * @param date The NSDate object to be converted to a string.
+ * @return An NSString of the form: yyyy-MM-ddTHH:mm:ss.SSSZ or nil if conversion isn't possible.
+ */
+- (nullable NSString *)RFC3339StringFromDate:(NSDate *)date;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/Private/FIRStorageObservableTask_Private.h b/Firebase/Storage/Private/FIRStorageObservableTask_Private.h
new file mode 100644
index 0000000..e37b63f
--- /dev/null
+++ b/Firebase/Storage/Private/FIRStorageObservableTask_Private.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017 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.
+ */
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class FIRStorageTaskSnapshot;
+
+@class GTMSessionFetcherService;
+
+@interface FIRStorageObservableTask ()
+
+/**
+ * Creates a new FIRStorageTask initialized with a FIRStorageReference and GTMSessionFetcherService.
+ * @param reference A FIRStorageReference the task will be performed on.
+ * @param service A GTMSessionFetcherService which provides the fetchers and configuration for
+ * requests.
+ * @return A new FIRStorageTask representing the current task.
+ */
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service;
+
+/**
+ * Raise events for a given task status by passing along a snapshot of existing task state.
+ * @param status A FIRStorageTaskStatus to raise events for.
+ * @param snapshot A FIRStorageTaskSnapshot snapshot of task state to pass through the handler.
+ */
+- (void)fireHandlersForStatus:(FIRStorageTaskStatus)status
+ snapshot:(FIRStorageTaskSnapshot *)snapshot;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/Private/FIRStoragePath.h b/Firebase/Storage/Private/FIRStoragePath.h
new file mode 100644
index 0000000..53ff7ef
--- /dev/null
+++ b/Firebase/Storage/Private/FIRStoragePath.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2017 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>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Represents a path in GCS, which can be represented as: gs://bucket/path/to/object
+ * or http[s]://firebasestorage.googleapis.com/v0/b/bucket/o/path/to/object?token=<12345>
+ * This class also includes helper methods to parse those URI/Ls, as well as to
+ * add and remove path segments.
+ */
+@interface FIRStoragePath : NSObject
+
+/**
+ * The GCS bucket in the path.
+ */
+@property(copy, nonatomic) NSString *bucket;
+
+/**
+ * The GCS object in the path.
+ */
+@property(copy, nonatomic, nullable) NSString *object;
+
+/**
+ * Parses a generic string (representing some URI or URL) and returns the appropriate path.
+ * @param string String which is parsed into a path.
+ * @return Returns an instance of FIRStoragePath or nil if one can't be created.
+ * @throws Throws an exception if the string is not a valid gs:// URI or http[s]:// URL.
+ */
++ (nullable FIRStoragePath *)pathFromString:(NSString *)string;
+
+/**
+ * Parses a gs://bucket/path/to/object URI into a GCS path.
+ * @param aURIString gs:// URI which is parsed into a path.
+ * @return Returns an instance of FIRStoragePath or nil if one can't be created.
+ * @throws Throws an exception if the string is not a valid gs:// URI.
+ */
++ (nullable FIRStoragePath *)pathFromGSURI:(NSString *)aURIString;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+/**
+ * Constructs an FIRStoragePath object that represents the given bucket and object.
+ * @param bucket The name of the bucket.
+ * @param object The name of the object.
+ * @return An instance of FIRStoragePath representing the @a bucket and @a object.
+ */
+- (instancetype)initWithBucket:(NSString *)bucket
+ object:(nullable NSString *)object NS_DESIGNATED_INITIALIZER;
+
+/**
+ * Parses a http[s]://firebasestorage.googleapis.com/v0/b/bucket/o/path/to/object...?token=<12345>
+ * URL into a GCS path.
+ * @param aURLString http[s]:// URL which is parsed into a path.
+ * string which is parsed into a path.
+ * @return Returns an instance of FIRStoragePath or nil if one can't be created.
+ * @throws Throws an exception if the string is not a valid http[s]:// URL.
+ */
++ (nullable FIRStoragePath *)pathFromHTTPURL:(NSString *)aURLString;
+
+/**
+ * Creates a new path based off of the current path and a string appended to it.
+ * Note that all slashes are compressed to a single slash, and leading and trailing slashes
+ * are removed.
+ * @param path String to append to the current path.
+ * @return Returns a new instance of FIRStoragePath with the new path appended.
+ */
+- (FIRStoragePath *)child:(NSString *)path;
+
+/**
+ * Creates a new path based off of the current path with the last path segment removed.
+ * @return Returns a new instance of FIRStoragePath pointing to the parent path,
+ * or nil if the current path points to the root.
+ */
+- (nullable FIRStoragePath *)parent;
+
+/**
+ * Creates a new path based off of the root of the bucket.
+ * @return Returns a new instance of FIRStoragePath pointing to the root of the bucket.
+ */
+- (FIRStoragePath *)root;
+
+/**
+ * Returns a GS URI representing the current path.
+ * @return Returns a gs://bucket/path/to/object URI representing the current path.
+ */
+- (NSString *)stringValue;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/Private/FIRStorageReference_Private.h b/Firebase/Storage/Private/FIRStorageReference_Private.h
new file mode 100644
index 0000000..825964d
--- /dev/null
+++ b/Firebase/Storage/Private/FIRStorageReference_Private.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017 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 "FIRStoragePath.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRStorageReference ()
+
+@property(nonatomic, readwrite) FIRStorage *storage;
+
+/**
+ * The current path which points to an object in the Google Cloud Storage bucket.
+ */
+@property(strong, nonatomic) FIRStoragePath *path;
+
+- (instancetype)initWithStorage:(FIRStorage *)storage
+ path:(FIRStoragePath *)path NS_DESIGNATED_INITIALIZER;
+
+- (NSString *)stringValue;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/Private/FIRStorageTaskSnapshot_Private.h b/Firebase/Storage/Private/FIRStorageTaskSnapshot_Private.h
new file mode 100644
index 0000000..1762a61
--- /dev/null
+++ b/Firebase/Storage/Private/FIRStorageTaskSnapshot_Private.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017 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 "FIRStorageConstants_Private.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class FIRStorageMetadata;
+@class FIRStorageReference;
+@class FIRStorageTask;
+
+@interface FIRStorageTaskSnapshot ()
+
+@property(readwrite, copy, nonatomic) FIRStorageTask *task;
+@property(readwrite, copy, nonatomic) FIRStorageMetadata *metadata;
+@property(readwrite, copy, nonatomic) FIRStorageReference *reference;
+@property(readwrite, strong, nonatomic) NSProgress *progress;
+@property(readwrite, copy, nonatomic) NSError *error;
+
+/**
+ * Creates a new task snapshot from the given properties.
+ * @param task The task being represented in this snapshot.
+ * @param state The current state of the parent task.
+ * @param metadata The FIRStorageMetadata of a task. Before upload/update, contains the metadata
+ * to be updated; after, contains the returned metadata. May be nil if no metadata is provided
+ * or returned.
+ * @param reference The FIRStorageReference that spawned the task this snapshot is based on.
+ * @param progress An NSProgress object containing progress of the task this snapshot is based on,
+ * or nil if the task doesn't report progress.
+ * @param error An NSError object containing an error that occurred during the task,
+ * if one occurred.
+ * @return Returns the constructed snapshot.
+ */
+- (instancetype)initWithTask:(__kindof FIRStorageTask *)task
+ state:(FIRStorageTaskState)state
+ metadata:(nullable FIRStorageMetadata *)metadata
+ reference:(FIRStorageReference *)reference
+ progress:(nullable NSProgress *)progress
+ error:(nullable NSError *)error;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/Private/FIRStorageTask_Private.h b/Firebase/Storage/Private/FIRStorageTask_Private.h
new file mode 100644
index 0000000..598006b
--- /dev/null
+++ b/Firebase/Storage/Private/FIRStorageTask_Private.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017 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 "FIRStorageConstants_Private.h"
+#import "FIRStorageErrors.h"
+#import "FIRStorageReference.h"
+#import "FIRStorageReference_Private.h"
+#import "FIRStorageTaskSnapshot.h"
+#import "FIRStorageTaskSnapshot_Private.h"
+#import "FIRStorageUtils.h"
+
+#import <GTMSessionFetcher/GTMSessionFetcher.h>
+#import <GTMSessionFetcher/GTMSessionFetcherService.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRStorageTask ()
+
+/**
+ * State for the current task in progress.
+ */
+@property(atomic) FIRStorageTaskState state;
+
+/**
+ * FIRStorageMetadata for the task in progress, or nil if none present.
+ */
+@property(strong, nonatomic, nullable) FIRStorageMetadata *metadata;
+
+/**
+ * Error which occurred during task execution, or nil if no error occurred.
+ */
+@property(strong, nonatomic, nullable) NSError *error;
+
+/**
+ * NSProgress object which tracks the progess of an observable task.
+ */
+@property(strong, nonatomic) NSProgress *progress;
+
+/**
+ * Reference pointing to the location the task is being performed against.
+ */
+@property(strong, nonatomic) FIRStorageReference *reference;
+
+@property(strong, readwrite, nonatomic, nonnull) FIRStorageTaskSnapshot *snapshot;
+
+@property(readonly, copy, nonatomic) NSURLRequest *baseRequest;
+
+@property(strong, atomic) GTMSessionFetcher *fetcher;
+
+@property(readonly, nonatomic) GTMSessionFetcherService *fetcherService;
+
+/**
+ * Creates a new FIRStorageTask initialized with a FIRStorageReference and GTMSessionFetcherService.
+ * @param reference A FIRStorageReference the task will be performed on.
+ * @param service A GTMSessionFetcherService which provides the fetchers and configuration for
+ * requests.
+ * @return A new FIRStorageTask representing the current task.
+ */
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service NS_DESIGNATED_INITIALIZER;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/Private/FIRStorageTokenAuthorizer.h b/Firebase/Storage/Private/FIRStorageTokenAuthorizer.h
new file mode 100644
index 0000000..78a8218
--- /dev/null
+++ b/Firebase/Storage/Private/FIRStorageTokenAuthorizer.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017 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 <GTMSessionFetcher/GTMSessionFetcherService.h>
+
+@class FIRApp;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Wrapper class for FIRApp that implements the GTMFetcherAuthorizationProtocol,
+ * so as to easily provide GTMSessionFetcher fetches a Firebase Authentication JWT
+ * for the current logged in user. Handles token expiration and other failure cases.
+ * If no authentication provider exists or no token is found, no token is added
+ * and the request is passed.
+ */
+@interface FIRStorageTokenAuthorizer : NSObject<GTMFetcherAuthorizationProtocol>
+
+/**
+ * Initializes the token authorizer with an instance of FIRApp.
+ * @param app An instance of FIRApp which provides auth tokens.
+ * @return Returns an instance of FIRStorageTokenAuthorizer which adds the appropriate
+ * "Authorization" header to all outbound requests. Note that a token may not be added
+ * if a getTokenImplementation doesn't exist on FIRApp. This allows for unauthenticated
+ * access, if Firebase Storage rules allow for it.
+ */
+- (instancetype)initWithApp:(FIRApp *)app fetcherService:(GTMSessionFetcherService *)service;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/Private/FIRStorageUpdateMetadataTask.h b/Firebase/Storage/Private/FIRStorageUpdateMetadataTask.h
new file mode 100644
index 0000000..2fcefdd
--- /dev/null
+++ b/Firebase/Storage/Private/FIRStorageUpdateMetadataTask.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 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 "FIRStorageTask.h"
+
+@class GTMSessionFetcherService;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Task which provides the ability update the metadata on an object in Firebase Storage.
+ */
+@interface FIRStorageUpdateMetadataTask : FIRStorageTask<FIRStorageTaskManagement>
+
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service
+ metadata:(FIRStorageMetadata *)metadata
+ completion:(FIRStorageVoidMetadataError)completion;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/Private/FIRStorageUploadTask_Private.h b/Firebase/Storage/Private/FIRStorageUploadTask_Private.h
new file mode 100644
index 0000000..468d9d3
--- /dev/null
+++ b/Firebase/Storage/Private/FIRStorageUploadTask_Private.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017 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.
+ */
+
+@class GTMSessionUploadFetcher;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRStorageUploadTask ()
+
+/**
+ * The data to be uploaded (if uploading bytes).
+ */
+@property(readonly, copy, nonatomic, nullable) NSData *uploadData;
+
+/**
+ * The name of a file on disk to be uploaded (if uploading from a file).
+ */
+@property(readonly, copy, nonatomic, nullable) NSURL *fileURL;
+
+/**
+ * The FIRStorageMetadata about the object being uploaded.
+ */
+@property(readonly, copy, nonatomic) FIRStorageMetadata *uploadMetadata;
+
+/**
+ * GTMSessionUploadFetcher used by all uploads.
+ */
+@property(strong, atomic) GTMSessionUploadFetcher *uploadFetcher;
+
+/**
+ * Initializes an upload task with a base FIRStorageReference and GTMSessionFetcherService.
+ * @param reference The base FIRStorageReference which fetchers use for configuration.
+ * @param service The GTMSessionFetcherService which will create fetchers.
+ * @param uploadData The NSData object to be uploaded.
+ * @return Returns an instance of FIRStorageUploadTask.
+ */
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service
+ data:(NSData *)uploadData
+ metadata:(FIRStorageMetadata *)metadata;
+
+/**
+ * Initializes an upload task with a base FIRStorageReference and GTMSessionFetcherService.
+ * @param reference The base FIRStorageReference which fetchers use for configuration.
+ * @param service The GTMSessionFetcherService which will create fetchers.
+ * @param fileURL The system file URL to upload from.
+ * @return Returns an instance of FIRStorageUploadTask.
+ */
+- (instancetype)initWithReference:(FIRStorageReference *)reference
+ fetcherService:(GTMSessionFetcherService *)service
+ file:(NSURL *)fileURL
+ metadata:(FIRStorageMetadata *)metadata;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/Private/FIRStorageUtils.h b/Firebase/Storage/Private/FIRStorageUtils.h
new file mode 100644
index 0000000..e687c82
--- /dev/null
+++ b/Firebase/Storage/Private/FIRStorageUtils.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2017 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>
+
+@class FIRStoragePath;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * FIRStorageUtils provides a number of helper methods for commonly used operations
+ * in Firebase Storage, such as JSON parsing, escaping, and file extensions.
+ */
+@interface FIRStorageUtils : NSObject
+
+/**
+ * Returns a percent encoded string appropriate for GCS.
+ * See https://cloud.google.com/storage/docs/naming for more details.
+ * @param string A path to escape characters according to the GCS
+ * @return A percent encoded string appropriate for GCS operations or nil if string is nil
+ * or can't be escaped.
+ */
++ (nullable NSString *)GCSEscapedString:(NSString *)string;
+
+/**
+ * Returns the MIME type for a file extension.
+ * Example of how to get MIME type here: http://ddeville.me/2011/12/mime-to-UTI-cocoa/
+ * @param extension A file extension such as "txt", "png", etc.
+ * @return The MIME type for the input extension such as "text/plain", "image/png", etc.
+ * or nil if no type is found.
+ */
++ (nullable NSString *)MIMETypeForExtension:(NSString *)extension;
+
+/**
+ * Returns a properly escaped query string from a given dictionary of query items to values.
+ * @param dictionary A dictionary containing query items and associated values.
+ * @return A properly escaped query string or the empty string for a nil or empty dictionary.
+ */
++ (NSString *)queryStringForDictionary:(nullable NSDictionary *)dictionary;
+
+/**
+ * Returns a base NSURLRequest used by all tasks.
+ * @param path The FIRStoragePath to create a request for.
+ * @return Returns a properly formatted NSURLRequest of the form:
+ * scheme://host/version/b/<bucket>/o[/path/to/object]
+ */
++ (NSURLRequest *)defaultRequestForPath:(FIRStoragePath *)path;
+
+/**
+ * Creates the appropriate GCS percent escaped path for a given FIRStoragePath.
+ * @param path The FIRStoragePath to encode.
+ * @return Returns the GCS encoded URL for a given FIRStoragePath.
+ */
++ (NSString *)encodedURLForPath:(FIRStoragePath *)path;
+
+@end
+
+@interface NSDictionary (FIRStorageNSDictionaryJSONHelpers)
+
+/**
+ * Returns a dictionary representation of the data in @a data.
+ * @param data NSData containing JSON data.
+ * @return An NSDictionary representation of the JSON, or nil if serialization failed.
+ */
++ (nullable instancetype)frs_dictionaryFromJSONData:(nullable NSData *)data;
+
+@end
+
+@interface NSData (FIRStorageNSDataJSONHelpers)
+
+/**
+ * Returns an NSData instance containing JSON serialized from @a dictionary.
+ * @param dictionary An NSDictionary containing only types serializable to JSON.
+ * @return An NSData object representing the binary JSON, or nil if serialization failed.
+ */
++ (nullable instancetype)frs_dataFromJSONDictionary:(nullable NSDictionary *)dictionary;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Storage/Private/FIRStorage_Private.h b/Firebase/Storage/Private/FIRStorage_Private.h
new file mode 100644
index 0000000..aefe808
--- /dev/null
+++ b/Firebase/Storage/Private/FIRStorage_Private.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017 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.
+ */
+
+@class FIRApp;
+@class GTMSessionFetcherService;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRStorage ()
+
+@property(strong, nonatomic, readwrite) FIRApp *app;
+
+@property(strong, nonatomic) GTMSessionFetcherService *fetcherServiceForApp;
+
+@property(strong, nonatomic) NSString *storageBucket;
+
+/**
+ * Enables/disables GTMSessionFetcher HTTP logging
+ * @param isLoggingEnabled Boolean passed through to enable/disable GTMSessionFetcher logging
+ */
++ (void)setGTMSessionFetcherLoggingEnabled:(BOOL)isLoggingEnabled;
+
+@end
+
+NS_ASSUME_NONNULL_END