added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / RLMNetworkClient.mm
1 ////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2016 Realm Inc.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 ////////////////////////////////////////////////////////////////////////////
18
19 #import "RLMNetworkClient.h"
20
21 #import "RLMRealmConfiguration.h"
22 #import "RLMJSONModels.h"
23 #import "RLMSyncUtil_Private.hpp"
24 #import "RLMUtil.hpp"
25
26 typedef void(^RLMServerURLSessionCompletionBlock)(NSData *, NSURLResponse *, NSError *);
27
28 static NSUInteger const kHTTPCodeRange = 100;
29
30 typedef enum : NSUInteger {
31     Informational       = 1, // 1XX
32     Success             = 2, // 2XX
33     Redirection         = 3, // 3XX
34     ClientError         = 4, // 4XX
35     ServerError         = 5, // 5XX
36 } RLMServerHTTPErrorCodeType;
37
38 static NSRange RLM_rangeForErrorType(RLMServerHTTPErrorCodeType type) {
39     return NSMakeRange(type*100, kHTTPCodeRange);
40 }
41
42 @interface RLMSyncServerEndpoint ()
43 - (instancetype)initPrivate NS_DESIGNATED_INITIALIZER;
44
45 /// The HTTP method the endpoint expects. Defaults to POST.
46 - (NSString *)httpMethod;
47
48 /// The URL to which the request should be made. Must be implemented.
49 - (NSURL *)urlForAuthServer:(NSURL *)authServerURL payload:(NSDictionary *)json;
50
51 /// The body for the request, if any.
52 - (NSData *)httpBodyForPayload:(NSDictionary *)json error:(NSError **)error;
53
54 /// The HTTP headers to be added to the request, if any.
55 - (NSDictionary<NSString *, NSString *> *)httpHeadersForPayload:(NSDictionary *)json;
56 @end
57
58 @implementation RLMSyncServerEndpoint
59
60 - (instancetype)initPrivate {
61     return (self = [super init]);
62 }
63
64 - (NSString *)httpMethod {
65     return @"POST";
66 }
67
68 - (NSURL *)urlForAuthServer:(__unused NSURL *)authServerURL payload:(__unused NSDictionary *)json {
69     NSAssert(NO, @"This method must be overriden by concrete subclasses.");
70     return nil;
71 }
72
73 - (NSData *)httpBodyForPayload:(NSDictionary *)json error:(NSError **)error {
74     NSError *localError = nil;
75     NSData *jsonData = [NSJSONSerialization dataWithJSONObject:json
76                                                        options:(NSJSONWritingOptions)0
77                                                          error:&localError];
78     if (jsonData && !localError) {
79         return jsonData;
80     }
81     NSAssert(localError, @"If there isn't a converted data object there must be an error.");
82     if (error) {
83         *error = localError;
84     }
85     return nil;
86 }
87
88 - (NSDictionary<NSString *, NSString *> *)httpHeadersForPayload:(__unused NSDictionary *)json {
89     return @{@"Content-Type":   @"application/json;charset=utf-8",
90              @"Accept":         @"application/json"};
91 }
92
93 @end
94
95 @implementation RLMSyncAuthEndpoint
96
97 + (instancetype)endpoint {
98     return [[RLMSyncAuthEndpoint alloc] initPrivate];
99 }
100
101 - (NSURL *)urlForAuthServer:(NSURL *)authServerURL payload:(__unused NSDictionary *)json {
102     return [authServerURL URLByAppendingPathComponent:@"auth"];
103 }
104
105 @end
106
107 @implementation RLMSyncChangePasswordEndpoint
108
109 + (instancetype)endpoint {
110     return [[RLMSyncChangePasswordEndpoint alloc] initPrivate];
111 }
112
113 - (NSString *)httpMethod {
114     return @"PUT";
115 }
116
117 - (NSURL *)urlForAuthServer:(NSURL *)authServerURL payload:(__unused NSDictionary *)json {
118     return [authServerURL URLByAppendingPathComponent:@"auth/password"];
119 }
120
121 - (NSDictionary *)httpHeadersForPayload:(NSDictionary *)json {
122     NSString *authToken = [json objectForKey:kRLMSyncTokenKey];
123     if (!authToken) {
124         @throw RLMException(@"Malformed request; this indicates an internal error.");
125     }
126     NSMutableDictionary *headers = [[super httpHeadersForPayload:json] mutableCopy];
127     [headers setObject:authToken forKey:@"Authorization"];
128     return [headers copy];
129 }
130
131 @end
132
133 @implementation RLMSyncGetUserInfoEndpoint
134
135 + (instancetype)endpoint {
136     return [[RLMSyncGetUserInfoEndpoint alloc] initPrivate];
137 }
138
139 - (NSString *)httpMethod {
140     return @"GET";
141 }
142
143 - (NSURL *)urlForAuthServer:(NSURL *)authServerURL payload:(NSDictionary *)json {
144     NSString *provider = json[kRLMSyncProviderKey];
145     NSString *providerID = json[kRLMSyncProviderIDKey];
146     NSAssert([provider isKindOfClass:[NSString class]] && [providerID isKindOfClass:[NSString class]],
147              @"malformed request; this indicates a logic error in the binding.");
148     NSCharacterSet *allowed = [NSCharacterSet URLQueryAllowedCharacterSet];
149     NSString *pathComponent = [NSString stringWithFormat:@"auth/users/%@/%@",
150                                [provider stringByAddingPercentEncodingWithAllowedCharacters:allowed],
151                                [providerID stringByAddingPercentEncodingWithAllowedCharacters:allowed]];
152     return [authServerURL URLByAppendingPathComponent:pathComponent];
153 }
154
155 - (NSData *)httpBodyForPayload:(__unused NSDictionary *)json error:(__unused NSError **)error {
156     return nil;
157 }
158
159 - (NSDictionary<NSString *, NSString *> *)httpHeadersForPayload:(NSDictionary *)json {
160     NSString *authToken = [json objectForKey:kRLMSyncTokenKey];
161     if (!authToken) {
162         @throw RLMException(@"Malformed request; this indicates an internal error.");
163     }
164     return @{@"Authorization": authToken};
165 }
166
167 @end
168
169
170 @implementation RLMNetworkClient
171
172 + (NSURLSession *)session {
173     return [NSURLSession sharedSession];
174 }
175
176 + (void)sendRequestToEndpoint:(RLMSyncServerEndpoint *)endpoint
177                        server:(NSURL *)serverURL
178                          JSON:(NSDictionary *)jsonDictionary
179                    completion:(RLMSyncCompletionBlock)completionBlock {
180     static NSTimeInterval const defaultTimeout = 60;
181     [self sendRequestToEndpoint:endpoint
182                          server:serverURL
183                            JSON:jsonDictionary
184                         timeout:defaultTimeout
185                      completion:completionBlock];
186 }
187
188 + (void)sendRequestToEndpoint:(RLMSyncServerEndpoint *)endpoint
189                        server:(NSURL *)serverURL
190                          JSON:(NSDictionary *)jsonDictionary
191                       timeout:(NSTimeInterval)timeout
192                    completion:(RLMSyncCompletionBlock)completionBlock {
193     // Create the request
194     NSError *localError = nil;
195     NSURL *requestURL = [endpoint urlForAuthServer:serverURL payload:jsonDictionary];
196     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestURL];
197     request.HTTPBody = [endpoint httpBodyForPayload:jsonDictionary error:&localError];
198     if (localError) {
199         completionBlock(localError, nil);
200         return;
201     }
202     request.HTTPMethod = [endpoint httpMethod];
203     request.timeoutInterval = MAX(timeout, 10);
204     NSDictionary<NSString *, NSString *> *headers = [endpoint httpHeadersForPayload:jsonDictionary];
205     for (NSString *key in headers) {
206         [request addValue:headers[key] forHTTPHeaderField:key];
207     }
208     RLMServerURLSessionCompletionBlock handler = ^(NSData *data,
209                                                    NSURLResponse *response,
210                                                    NSError *error) {
211         if (error != nil) {
212             // Network error
213             completionBlock(error, nil);
214             return;
215         }
216
217         NSError *localError = nil;
218
219         if (![self validateResponse:response data:data error:&localError]) {
220             // Response error
221             completionBlock(localError, nil);
222             return;
223         }
224
225         // Parse out the JSON
226         id json = [NSJSONSerialization JSONObjectWithData:data
227                                                   options:(NSJSONReadingOptions)0
228                                                     error:&localError];
229         if (!json || localError) {
230             // JSON parsing error
231             completionBlock(localError, nil);
232         } else if (![json isKindOfClass:[NSDictionary class]]) {
233             // JSON response malformed
234             localError = make_auth_error_bad_response(json);
235             completionBlock(localError, nil);
236         } else {
237             // JSON parsed successfully
238             completionBlock(nil, (NSDictionary *)json);
239         }
240     };
241
242     // Add the request to a task and start it
243     NSURLSessionTask *task = [self.session dataTaskWithRequest:request
244                                              completionHandler:handler];
245     [task resume];
246 }
247
248 + (BOOL)validateResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError * __autoreleasing *)error {
249     __autoreleasing NSError *localError = nil;
250     if (!error) {
251         error = &localError;
252     }
253
254     if (![response isKindOfClass:[NSHTTPURLResponse class]]) {
255         // FIXME: Provide error message
256         *error = make_auth_error_bad_response();
257         return NO;
258     }
259
260     NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
261     BOOL badResponse = (NSLocationInRange(httpResponse.statusCode, RLM_rangeForErrorType(ClientError))
262                         || NSLocationInRange(httpResponse.statusCode, RLM_rangeForErrorType(ServerError)));
263     if (badResponse) {
264         if (RLMSyncErrorResponseModel *responseModel = [self responseModelFromData:data]) {
265             switch (responseModel.code) {
266                 case RLMSyncAuthErrorInvalidCredential:
267                 case RLMSyncAuthErrorUserDoesNotExist:
268                 case RLMSyncAuthErrorUserAlreadyExists:
269                 case RLMSyncAuthErrorAccessDeniedOrInvalidPath:
270                 case RLMSyncAuthErrorInvalidAccessToken:
271                 case RLMSyncAuthErrorExpiredPermissionOffer:
272                 case RLMSyncAuthErrorAmbiguousPermissionOffer:
273                 case RLMSyncAuthErrorFileCannotBeShared:
274                     *error = make_auth_error(responseModel);
275                     break;
276                 default:
277                     // Right now we assume that any codes not described
278                     // above are generic HTTP error codes.
279                     *error = make_auth_error_http_status(responseModel.status);
280                     break;
281             }
282         } else {
283             *error = make_auth_error_http_status(httpResponse.statusCode);
284         }
285
286         return NO;
287     }
288
289     if (!data) {
290         // FIXME: provide error message
291         *error = make_auth_error_bad_response();
292         return NO;
293     }
294
295     return YES;
296 }
297
298 + (RLMSyncErrorResponseModel *)responseModelFromData:(NSData *)data {
299     if (data.length == 0) {
300         return nil;
301     }
302     id json = [NSJSONSerialization JSONObjectWithData:data
303                                               options:(NSJSONReadingOptions)0
304                                                 error:nil];
305     if (!json || ![json isKindOfClass:[NSDictionary class]]) {
306         return nil;
307     }
308     return [[RLMSyncErrorResponseModel alloc] initWithDictionary:json];
309 }
310
311 @end