1 // Copyright 2015-present 650 Industries. All rights reserved.
2 
3 import EXStructuredHeaders
4 
5 /**
6  * Data from the update response headers.
7  * For non-multipart responses, this is the data from the headers in the response.
8  * For multipart responses, this is the data from the headers in the outer response (as opposed to the headers in a part).
9  */
10 public final class ResponseHeaderData {
11   /**
12    * expo-protocol-version header. Indicates which version of the expo-updates protocol the response is.
13    */
14   private let protocolVersionRaw: String?
15 
16   /**
17    * expo-server-defined-headers header.  It defines headers that this library must store until overwritten by a newer dictionary.
18    * They must be included in every subsequent update request.
19    */
20   private let serverDefinedHeadersRaw: String?
21 
22   /**
23    * expo-manifest-filters header. It is used to filter updates stored by the client library by the
24    * `metadata` attribute found in the manifest. If a field is mentioned in the filter, the corresponding
25    * field in the metadata must either be missing or equal for the update to be included.
26    * The client library must store the manifest filters until it is overwritten by a newer response.
27    */
28   private let manifestFiltersRaw: String?
29 
30   /**
31    * Classic updates Expo Go manifest signature
32    */
33   let manifestSignature: String?
34 
35   public required init(protocolVersionRaw: String?, serverDefinedHeadersRaw: String?, manifestFiltersRaw: String?, manifestSignature: String?) {
36     self.protocolVersionRaw = protocolVersionRaw
37     self.serverDefinedHeadersRaw = serverDefinedHeadersRaw
38     self.manifestFiltersRaw = manifestFiltersRaw
39     self.manifestSignature = manifestSignature
40   }
41 
42   lazy var protocolVersion: Int? = {
43     self.protocolVersionRaw.let { it in
44       Int(it)
45     }
46   }()
47 
48   lazy var serverDefinedHeaders: [String: Any]? = {
49     self.serverDefinedHeadersRaw.let { it in
50       ResponseHeaderData.dictionaryWithStructuredHeader(it)
51     }
52   }()
53 
54   public lazy var manifestFilters: [String: Any]? = {
55     self.manifestFiltersRaw.let { it in
56       ResponseHeaderData.dictionaryWithStructuredHeader(it)
57     }
58   }()
59 
60   static func dictionaryWithStructuredHeader(_ headerString: String) -> [String: Any]? {
61     let parser = EXStructuredHeadersParser(
62       rawInput: headerString,
63       fieldType: EXStructuredHeadersParserFieldType.dictionary,
64       ignoringParameters: true
65     )
66     let parserOutput: Any
67     do {
68       parserOutput = try parser.parseStructuredFields()
69     } catch let error as NSError {
70       NSLog("Error parsing header value: %@", error.localizedDescription)
71       return nil
72     }
73 
74     guard let parserOutputDictionary = parserOutput as? [String: Any] else {
75       NSLog("Error parsing header value: %@", "Header was not a structured fields dictionary")
76       return nil
77     }
78 
79     // ignore any dictionary entries whose type is not string, number, or boolean
80     // since this will be re-serialized to JSON
81     // The only way I can figure out how to detect numbers is to do a is NSNumber (is any Numeric didn't work)
82     // swiftlint:disable:next legacy_objc_type
83     return parserOutputDictionary.filter { $0.value is String || $0.value is NSNumber || $0.value is Bool }
84   }
85 }
86 
87 /**
88  * Data from the update response part headers.
89  * For non-multipart responses, this is the data from the headers in the response.
90  * For multipart responses, this is the data from the headers in the part.
91  */
92 internal final class ResponsePartHeaderData: NSObject {
93   /**
94    * Code signing signature for response part.
95    */
96   let signature: String?
97 
98   required init(signature: String?) {
99     self.signature = signature
100   }
101 }
102 
103 /**
104  * Full info about a update response part.
105  * For non-multipart responses, this is the info about the full response.
106  * For multipart responses, this is the info about a single part (but includes the outer headers for processing).
107  */
108 internal final class ResponsePartInfo {
109   let responseHeaderData: ResponseHeaderData
110   let responsePartHeaderData: ResponsePartHeaderData
111   let body: Data
112 
113   required init(responseHeaderData: ResponseHeaderData, responsePartHeaderData: ResponsePartHeaderData, body: Data) {
114     self.responseHeaderData = responseHeaderData
115     self.responsePartHeaderData = responsePartHeaderData
116     self.body = body
117   }
118 }
119