FIRStoragePath.m 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. // Copyright 2017 Google
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "FIRStoragePath.h"
  15. #import "FIRStorageConstants_Private.h"
  16. @implementation FIRStoragePath
  17. #pragma mark - Class methods
  18. + (nullable FIRStoragePath *)pathFromString:(NSString *)string {
  19. if ([string hasPrefix:@"gs://"]) {
  20. // "gs://bucket/path/to/object"
  21. return [FIRStoragePath pathFromGSURI:string];
  22. } else if ([string hasPrefix:@"http://"] || [string hasPrefix:@"https://"]) {
  23. // "http[s]://firebasestorage.googleapis.com/bucket/path/to/object?signed_url_params"
  24. return [FIRStoragePath pathFromHTTPURL:string];
  25. } else {
  26. // Invalid scheme, raise an exception!
  27. [NSException raise:NSInternalInconsistencyException
  28. format:@"URL scheme must be one of gs://, http://, or https:// "];
  29. return nil;
  30. }
  31. }
  32. + (nullable FIRStoragePath *)pathFromGSURI:(NSString *)aURIString {
  33. NSString *bucketName;
  34. NSString *objectName;
  35. NSScanner *scanner = [NSScanner scannerWithString:aURIString];
  36. BOOL isGSURI = [scanner scanString:@"gs://" intoString:NULL];
  37. BOOL hasBucket = [scanner scanUpToString:@"/" intoString:&bucketName];
  38. [scanner scanString:@"/" intoString:NULL];
  39. [scanner scanUpToString:@"\n" intoString:&objectName];
  40. if (!isGSURI || !hasBucket) {
  41. [NSException raise:NSInternalInconsistencyException
  42. format:@"URI must be in the form of gs://<bucket>/<path/to/object>"];
  43. return nil;
  44. }
  45. return [[self alloc] initWithBucket:bucketName object:objectName];
  46. }
  47. + (nullable FIRStoragePath *)pathFromHTTPURL:(NSString *)aURLString {
  48. NSString *bucketName;
  49. NSString *objectName;
  50. NSURL *httpsURL = [NSURL URLWithString:aURLString];
  51. NSArray *pathComponents = httpsURL.pathComponents; // [/, v0, b, <bucket>, o, <objects/...>]
  52. if ([httpsURL.host isEqual:kFIRStorageHost]) {
  53. // Have a bucket name
  54. if ([pathComponents count] > 3) {
  55. bucketName = pathComponents[3];
  56. }
  57. // Have an object name
  58. if ([pathComponents count] > 5) {
  59. NSRange objectRange = NSMakeRange(5, [pathComponents count] - 5);
  60. objectName = [[pathComponents subarrayWithRange:objectRange] componentsJoinedByString:@"/"];
  61. }
  62. }
  63. if (bucketName.length == 0) {
  64. [NSException raise:NSInternalInconsistencyException
  65. format:@"URL must be in the form of "
  66. @"http[s]://firebasestorage.googleapis.com/v0/b/<bucket>/o/<path/to/"
  67. @"object>[?token=signed_url_params]"];
  68. return nil;
  69. }
  70. if (objectName.length == 0) {
  71. objectName = nil;
  72. }
  73. return [[self alloc] initWithBucket:bucketName object:objectName];
  74. }
  75. #pragma mark - Initializers
  76. - (instancetype)initWithBucket:(NSString *)bucket object:(nullable NSString *)object {
  77. self = [super init];
  78. if (self) {
  79. _bucket = [bucket copy];
  80. _object = [self standardizedPathForString:[object copy]];
  81. }
  82. return self;
  83. }
  84. #pragma mark - NSObject overrides
  85. - (instancetype)copyWithZone:(NSZone *)zone {
  86. return [[[self class] allocWithZone:zone] initWithBucket:_bucket object:_object];
  87. }
  88. - (BOOL)isEqual:(id)object {
  89. if (self == object) {
  90. return YES;
  91. }
  92. if (![object isKindOfClass:[FIRStoragePath class]]) {
  93. return NO;
  94. }
  95. BOOL isObjectEqual = [self isEqualToFIRStoragePath:(FIRStoragePath *)object];
  96. return isObjectEqual;
  97. }
  98. - (BOOL)isEqualToFIRStoragePath:(FIRStoragePath *)path {
  99. BOOL isBucketEqual = _bucket == nil && path->_bucket == nil;
  100. BOOL isObjectEqual = _object == nil && path->_object == nil;
  101. if (_bucket && path->_bucket) {
  102. isBucketEqual = [_bucket isEqual:path->_bucket];
  103. }
  104. if (_object && path.object) {
  105. isObjectEqual = [_object isEqual:path->_object];
  106. }
  107. BOOL isEqual = isBucketEqual && isObjectEqual;
  108. return isEqual;
  109. }
  110. - (NSUInteger)hash {
  111. // "...because in those days, you could XOR anything with anything and get something useful..."
  112. // https://www.usenix.org/system/files/1309_14-17_mickens.pdf
  113. NSUInteger hash = [_bucket hash] ^ [_object hash];
  114. return hash;
  115. }
  116. - (NSString *)description {
  117. return [NSString stringWithFormat:@"%@ %p: %@", [self class], self, [self stringValue]];
  118. }
  119. - (NSString *)stringValue {
  120. return [NSString stringWithFormat:@"gs://%@/%@", _bucket, _object ?: @""];
  121. }
  122. #pragma mark - Public methods
  123. - (FIRStoragePath *)child:(NSString *)path {
  124. if (path.length == 0) {
  125. return [self copy]; // Return a copy of the same path, nothing happened
  126. }
  127. NSString *childObject;
  128. if (_object == nil) {
  129. childObject = path;
  130. } else {
  131. childObject = [_object stringByAppendingPathComponent:path];
  132. }
  133. FIRStoragePath *childPath = [[FIRStoragePath alloc] initWithBucket:_bucket object:childObject];
  134. return childPath;
  135. }
  136. - (nullable FIRStoragePath *)parent {
  137. if (_object.length == 0) {
  138. return nil;
  139. }
  140. NSString *parentObject = [_object stringByDeletingLastPathComponent];
  141. FIRStoragePath *parentPath = [[FIRStoragePath alloc] initWithBucket:_bucket object:parentObject];
  142. return parentPath;
  143. }
  144. - (FIRStoragePath *)root {
  145. FIRStoragePath *rootPath = [[FIRStoragePath alloc] initWithBucket:_bucket object:nil];
  146. return rootPath;
  147. }
  148. #pragma mark - Private methods
  149. // Removes leading and trailing slashes, and compresses multiple slashes
  150. // to create a canonical representation.
  151. // Example: /foo//bar///baz//// -> foo/bar/baz
  152. - (NSString *)standardizedPathForString:(NSString *)string {
  153. NSMutableArray *components = [[string componentsSeparatedByString:@"/"] mutableCopy];
  154. NSIndexSet *removedPaths =
  155. [components indexesOfObjectsPassingTest:^BOOL(NSString *string, NSUInteger idx, BOOL *stop) {
  156. return (string.length == 0);
  157. }];
  158. [components removeObjectsAtIndexes:removedPaths];
  159. return [components componentsJoinedByString:@"/"];
  160. }
  161. @end