FIRStoragePath.m 6.1 KB

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