StorageUtils.swift 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. // Copyright 2022 Google LLC
  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 Foundation
  15. #if os(iOS) || os(tvOS)
  16. import MobileCoreServices
  17. #elseif os(macOS) || os(watchOS)
  18. import CoreServices
  19. #endif
  20. class StorageUtils {
  21. internal class func defaultRequestForReference(reference: StorageReference,
  22. queryParams: [String: String]? = nil)
  23. -> URLRequest {
  24. var components = URLComponents()
  25. components.scheme = reference.storage.scheme
  26. components.host = reference.storage.host
  27. components.port = reference.storage.port
  28. if let queryParams = queryParams {
  29. var queryItems = [URLQueryItem]()
  30. for (key, value) in queryParams {
  31. queryItems.append(URLQueryItem(name: key, value: value))
  32. }
  33. components.queryItems = queryItems
  34. // NSURLComponents does not encode "+" as "%2B". This is however required by our backend, as
  35. // it treats "+" as a shorthand encoding for spaces. See also
  36. // https://stackoverflow.com/questions/31577188/how-to-encode-into-2b-with-nsurlcomponents
  37. components.percentEncodedQuery = components.percentEncodedQuery?.replacingOccurrences(
  38. of: "+",
  39. with: "%2B"
  40. )
  41. }
  42. let encodedPath = encodedURL(for: reference.path)
  43. components.percentEncodedPath = encodedPath
  44. guard let url = components.url else {
  45. fatalError("FirebaseStorage Internal Error: Failed to create url for \(reference.bucket)")
  46. }
  47. return URLRequest(url: url)
  48. }
  49. internal class func encodedURL(for path: StoragePath) -> String {
  50. let bucketString = "/b/\(GCSEscapedString(path.bucket))"
  51. var objectString: String
  52. if let objectName = path.object {
  53. objectString = "/o/\(GCSEscapedString(objectName))"
  54. } else {
  55. objectString = "/o"
  56. }
  57. return "/v0\(bucketString)\(objectString)"
  58. }
  59. internal class func GCSEscapedString(_ string: String) -> String {
  60. // This is the list at https://cloud.google.com/storage/docs/json_api/ without &, ; and +.
  61. let allowedSet =
  62. CharacterSet(
  63. charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$'()*,=:@"
  64. )
  65. return string.addingPercentEncoding(withAllowedCharacters: allowedSet)!
  66. }
  67. internal class func MIMETypeForExtension(_ fileExtension: String?) -> String {
  68. guard let fileExtension = fileExtension else {
  69. return "application/octet-stream"
  70. }
  71. if let type = UTTypeCreatePreferredIdentifierForTag(
  72. kUTTagClassFilenameExtension,
  73. fileExtension as NSString,
  74. nil
  75. )?.takeRetainedValue() {
  76. if let mimeType = UTTypeCopyPreferredTagWithClass(type, kUTTagClassMIMEType)?
  77. .takeRetainedValue() {
  78. return mimeType as String
  79. }
  80. }
  81. return "application/octet-stream"
  82. }
  83. }