FIRStoragePath.m 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  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:
  66. @"URL must be in the form of "
  67. @"http[s]://firebasestorage.googleapis.com/v0/b/<bucket>/o/<path/to/"
  68. @"object>[?token=signed_url_params]"];
  69. return nil;
  70. }
  71. if (objectName.length == 0) {
  72. objectName = nil;
  73. }
  74. return [[self alloc] initWithBucket:bucketName object:objectName];
  75. }
  76. #pragma mark - Initializers
  77. - (instancetype)initWithBucket:(NSString *)bucket object:(nullable NSString *)object {
  78. self = [super init];
  79. if (self) {
  80. _bucket = [bucket copy];
  81. _object = [self standardizedPathForString:[object copy]];
  82. }
  83. return self;
  84. }
  85. #pragma mark - NSObject overrides
  86. - (instancetype)copyWithZone:(NSZone *)zone {
  87. return [[[self class] allocWithZone:zone] initWithBucket:_bucket object:_object];
  88. }
  89. - (BOOL)isEqual:(id)object {
  90. if (self == object) {
  91. return YES;
  92. }
  93. if (![object isKindOfClass:[FIRStoragePath class]]) {
  94. return NO;
  95. }
  96. BOOL isObjectEqual = [self isEqualToFIRStoragePath:(FIRStoragePath *)object];
  97. return isObjectEqual;
  98. }
  99. - (BOOL)isEqualToFIRStoragePath:(FIRStoragePath *)path {
  100. BOOL isBucketEqual = _bucket == nil && path->_bucket == nil;
  101. BOOL isObjectEqual = _object == nil && path->_object == nil;
  102. if (_bucket && path->_bucket) {
  103. isBucketEqual = [_bucket isEqual:path->_bucket];
  104. }
  105. if (_object && path.object) {
  106. isObjectEqual = [_object isEqual:path->_object];
  107. }
  108. BOOL isEqual = isBucketEqual && isObjectEqual;
  109. return isEqual;
  110. }
  111. - (NSUInteger)hash {
  112. // "...because in those days, you could XOR anything with anything and get something useful..."
  113. // https://www.usenix.org/system/files/1309_14-17_mickens.pdf
  114. NSUInteger hash = [_bucket hash] ^ [_object hash];
  115. return hash;
  116. }
  117. - (NSString *)description {
  118. return [NSString stringWithFormat:@"%@ %p: %@", [self class], self, [self stringValue]];
  119. }
  120. - (NSString *)stringValue {
  121. return [NSString stringWithFormat:@"gs://%@/%@", _bucket, _object ?: @""];
  122. }
  123. #pragma mark - Public methods
  124. - (FIRStoragePath *)child:(NSString *)path {
  125. if (path.length == 0) {
  126. return [self copy]; // Return a copy of the same path, nothing happened
  127. }
  128. NSString *childObject;
  129. if (_object == nil) {
  130. childObject = path;
  131. } else {
  132. childObject = [_object stringByAppendingPathComponent:path];
  133. }
  134. FIRStoragePath *childPath = [[FIRStoragePath alloc] initWithBucket:_bucket object:childObject];
  135. return childPath;
  136. }
  137. - (nullable FIRStoragePath *)parent {
  138. if (_object.length == 0) {
  139. return nil;
  140. }
  141. NSString *parentObject = [_object stringByDeletingLastPathComponent];
  142. FIRStoragePath *parentPath = [[FIRStoragePath alloc] initWithBucket:_bucket object:parentObject];
  143. return parentPath;
  144. }
  145. - (FIRStoragePath *)root {
  146. FIRStoragePath *rootPath = [[FIRStoragePath alloc] initWithBucket:_bucket object:nil];
  147. return rootPath;
  148. }
  149. #pragma mark - Private methods
  150. // Removes leading and trailing slashes, and compresses multiple slashes
  151. // to create a canonical representation.
  152. // Example: /foo//bar///baz//// -> foo/bar/baz
  153. - (NSString *)standardizedPathForString:(NSString *)string {
  154. NSMutableArray *components = [[string componentsSeparatedByString:@"/"] mutableCopy];
  155. NSIndexSet *removedPaths =
  156. [components indexesOfObjectsPassingTest:^BOOL(NSString *string, NSUInteger idx, BOOL *stop) {
  157. return (string.length == 0);
  158. }];
  159. [components removeObjectsAtIndexes:removedPaths];
  160. return [components componentsJoinedByString:@"/"];
  161. }
  162. @end