root / asi-http-request-with-pithos / Classes / Pithos / ASIPithosRequest.m @ 0be02d23
History | View | Annotate | Download (15.2 kB)
1 |
// ASIPithosRequest.m |
---|---|
2 |
// Based on ASICloudFilesRequest.m |
3 |
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest |
4 |
// |
5 |
// Copyright 2011-2012 GRNET S.A. All rights reserved. |
6 |
// |
7 |
// Redistribution and use in source and binary forms, with or |
8 |
// without modification, are permitted provided that the following |
9 |
// conditions are met: |
10 |
// |
11 |
// 1. Redistributions of source code must retain the above |
12 |
// copyright notice, this list of conditions and the following |
13 |
// disclaimer. |
14 |
// |
15 |
// 2. Redistributions in binary form must reproduce the above |
16 |
// copyright notice, this list of conditions and the following |
17 |
// disclaimer in the documentation and/or other materials |
18 |
// provided with the distribution. |
19 |
// |
20 |
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
21 |
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
22 |
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
23 |
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
24 |
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
25 |
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
26 |
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
27 |
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
28 |
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
29 |
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
30 |
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
31 |
// POSSIBILITY OF SUCH DAMAGE. |
32 |
// |
33 |
// The views and conclusions contained in the software and |
34 |
// documentation are those of the authors and should not be |
35 |
// interpreted as representing official policies, either expressed |
36 |
// or implied, of GRNET S.A. |
37 |
|
38 |
#import "ASIPithosRequest.h" |
39 |
#import "ASIPithos.h" |
40 |
#import "ASIPithosAccount.h" |
41 |
#import "SBJson.h" |
42 |
|
43 |
@implementation ASIPithosRequest |
44 |
@synthesize currentElement, currentContent, currentAccount; |
45 |
|
46 |
#pragma mark - |
47 |
#pragma mark Constructors |
48 |
|
49 |
+ (id)authRequestWithMethod:(NSString *)method pithos:(ASIPithos *)pithos queryString:(NSString *)queryString useAuthToken:(BOOL)useAuthToken { |
50 |
NSString *urlString = [NSString stringWithString:pithos.authURL]; |
51 |
if (queryString) |
52 |
urlString = [urlString stringByAppendingString:queryString]; |
53 |
|
54 |
ASIPithosRequest *request = [self requestWithURL:[NSURL URLWithString:urlString]]; |
55 |
[request setRequestMethod:method]; |
56 |
if (useAuthToken) |
57 |
[request addRequestHeader:@"X-Auth-Token" value:pithos.authToken]; |
58 |
return request; |
59 |
} |
60 |
|
61 |
+ (id)authRequestWithMethod:(NSString *)method pithos:(ASIPithos *)pithos useAuthToken:(BOOL)useAuthToken { |
62 |
return [self authRequestWithMethod:method pithos:pithos queryString:nil useAuthToken:useAuthToken]; |
63 |
} |
64 |
|
65 |
#pragma mark - |
66 |
#pragma mark Memory Management |
67 |
|
68 |
- (void)dealloc { |
69 |
[currentAccount release]; |
70 |
[currentContent release]; |
71 |
[currentElement release]; |
72 |
[sharingAccounts release]; |
73 |
[super dealloc]; |
74 |
} |
75 |
|
76 |
#pragma mark - |
77 |
#pragma mark Authentication |
78 |
|
79 |
+ (id)authenticationRequestWithPithos:(ASIPithos *)pithos { |
80 |
ASIPithosRequest *request = [self authRequestWithMethod:@"GET" pithos:pithos useAuthToken:NO]; |
81 |
[request addRequestHeader:@"X-Auth-User" value:pithos.authUser]; |
82 |
[request addRequestHeader:@"X-Auth-Key" value:pithos.authKey]; |
83 |
return request; |
84 |
} |
85 |
|
86 |
+ (ASIPithos *)authenticateWithPithos:(ASIPithos *)pithos error:(NSError **)error { |
87 |
ASIPithosRequest *request = [self authenticationRequestWithPithos:pithos]; |
88 |
[request startSynchronous]; |
89 |
// XXX start asynchronous with polling loop? |
90 |
|
91 |
if (error != NULL) { |
92 |
*error = [request error]; |
93 |
if (!*error) { |
94 |
NSDictionary *responseHeaders = [request responseHeaders]; |
95 |
pithos.authToken = [responseHeaders objectForKey:@"X-Auth-Token"]; |
96 |
pithos.storageURL = [responseHeaders objectForKey:@"X-Storage-Url"]; |
97 |
return pithos; |
98 |
} |
99 |
} |
100 |
return nil; |
101 |
} |
102 |
|
103 |
#pragma mark - |
104 |
#pragma mark User Catalog |
105 |
|
106 |
// POST userCatalogURL |
107 |
+ (id)userCatalogRequestWithPithos:(ASIPithos *)pithos displaynames:(NSArray *)displaynames UUIDs:(NSArray *)UUIDs { |
108 |
NSString *urlString = [NSString stringWithString:pithos.userCatalogURL]; |
109 |
|
110 |
ASIPithosRequest *request = [self requestWithURL:[NSURL URLWithString:urlString]]; |
111 |
[request setRequestMethod:@"POST"]; |
112 |
[request addRequestHeader:@"X-Auth-Token" value:pithos.authToken]; |
113 |
[request addRequestHeader:@"Content-Type" value:@"application/json"]; |
114 |
|
115 |
NSMutableString *dataString = [NSMutableString stringWithString:@"{\"displaynames\":["]; |
116 |
if (displaynames) { |
117 |
for (NSUInteger index = 0 ; index < displaynames.count ; index++) { |
118 |
[dataString appendFormat:@"\"%@\"%@", |
119 |
[[[displaynames objectAtIndex:index] stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"] |
120 |
stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""], |
121 |
((index == displaynames.count - 1) ? @"" : @",")]; |
122 |
} |
123 |
} |
124 |
[dataString appendFormat:@"],\"uuids\":["]; |
125 |
if (UUIDs) { |
126 |
for (NSUInteger index = 0 ; index < UUIDs.count ; index++) { |
127 |
[dataString appendFormat:@"\"%@\"%@", [UUIDs objectAtIndex:index], ((index == UUIDs.count - 1) ? @"" : @",")]; |
128 |
} |
129 |
} |
130 |
[dataString appendFormat:@"]}"]; |
131 |
[request appendPostData:[dataString dataUsingEncoding:NSUTF8StringEncoding]]; |
132 |
|
133 |
return request; |
134 |
} |
135 |
|
136 |
- (NSDictionary *)catalogs { |
137 |
SBJsonParser *parser = [[SBJsonParser alloc] init]; |
138 |
NSDictionary *catalogs = [parser objectWithString:[self responseString]]; |
139 |
return catalogs; |
140 |
} |
141 |
|
142 |
- (NSDictionary *)displaynameCatalog { |
143 |
return [[self catalogs] objectForKey:@"displayname_catalog"]; |
144 |
} |
145 |
|
146 |
- (NSDictionary *)UUIDCatalog { |
147 |
return [[self catalogs] objectForKey:@"uuid_catalog"]; |
148 |
} |
149 |
|
150 |
#pragma mark - |
151 |
#pragma mark GET |
152 |
|
153 |
// GET authURL |
154 |
+ (id)listSharingAccountsRequestWithPithos:(ASIPithos *)pithos { |
155 |
return [self authRequestWithMethod:@"GET" pithos:pithos queryString:@"?format=xml" useAuthToken:YES]; |
156 |
} |
157 |
|
158 |
// GET authURL?[limit=limit]&[marker=marker] |
159 |
+ (id)listSharingAccountsRequestWithPithos:(ASIPithos *)pithos limit:(NSUInteger)limit marker:(NSString *)marker { |
160 |
NSString *queryString = @"?format=xml"; |
161 |
if (limit && (limit > 0)) |
162 |
queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&limit=%lu", limit]]; |
163 |
if (marker) |
164 |
queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&marker=%@", [self encodeToPercentEscape:marker]]]; |
165 |
|
166 |
return [self authRequestWithMethod:@"GET" pithos:pithos queryString:queryString useAuthToken:YES]; |
167 |
} |
168 |
|
169 |
- (NSArray *)sharingAccounts { |
170 |
if (sharingAccounts == nil) { |
171 |
sharingAccounts = [[NSMutableArray alloc] init]; |
172 |
|
173 |
NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:[self responseData]] autorelease]; |
174 |
[parser setDelegate:self]; |
175 |
[parser setShouldProcessNamespaces:NO]; |
176 |
[parser setShouldReportNamespacePrefixes:NO]; |
177 |
[parser setShouldResolveExternalEntities:NO]; |
178 |
[parser parse]; |
179 |
} |
180 |
return sharingAccounts; |
181 |
} |
182 |
|
183 |
#pragma mark - |
184 |
#pragma mark NSXMLParserDelegate |
185 |
|
186 |
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { |
187 |
self.currentElement = elementName; |
188 |
if ([elementName isEqualToString:@"account"]) { |
189 |
self.currentAccount = [ASIPithosAccount account]; |
190 |
} |
191 |
|
192 |
self.currentContent = @""; |
193 |
} |
194 |
|
195 |
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { |
196 |
if ([elementName isEqualToString:@"name"]) { |
197 |
currentAccount.name = currentContent; |
198 |
} else if ([elementName isEqualToString:@"count"]) { |
199 |
currentAccount.containerCount = [currentContent integerValue]; |
200 |
} else if ([elementName isEqualToString:@"last_modified"]) { |
201 |
currentAccount.lastModified = [[self dateFormatterWithFormatId:0] dateFromString: |
202 |
[currentContent stringByReplacingOccurrencesOfString:@":" |
203 |
withString:@"" |
204 |
options:NSBackwardsSearch |
205 |
range:NSMakeRange(([currentContent length] - 3), 1)]]; |
206 |
} else if ([elementName isEqualToString:@"bytes"]) { |
207 |
currentAccount.bytesUsed = [currentContent integerValue]; |
208 |
} else if ([elementName isEqualToString:@"account"]) { |
209 |
[sharingAccounts addObject:currentAccount]; |
210 |
self.currentAccount = nil; |
211 |
} |
212 |
} |
213 |
|
214 |
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { |
215 |
self.currentContent = [currentContent stringByAppendingString:string]; |
216 |
} |
217 |
|
218 |
#pragma mark - |
219 |
#pragma mark Date Formatters |
220 |
|
221 |
// We store our date formatters in the calling thread's dictionary |
222 |
// NSDateFormatter is not thread-safe, this approach ensures each formatter is only used on a single thread |
223 |
// Each formatter can be reused many times in parsing a single response, so it would be expensive to keep creating new date formatters |
224 |
|
225 |
-(NSDateFormatter *)dateFormatterWithFormatId:(NSUInteger)formatId { |
226 |
NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary]; |
227 |
NSString *dateFormatterKey = [NSString stringWithFormat:@"ASIPithosDateFormatter%lu", formatId]; |
228 |
NSDateFormatter *dateFormatter = [threadDict objectForKey:dateFormatterKey]; |
229 |
if (dateFormatter == nil) { |
230 |
switch (formatId) { |
231 |
case 0: |
232 |
// date format: 2009-11-04T19:46:20.123456+0000 |
233 |
// Needed for last_modified |
234 |
dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; |
235 |
[dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]]; |
236 |
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSSSSZ"]; |
237 |
[threadDict setObject:dateFormatter forKey:dateFormatterKey]; |
238 |
break; |
239 |
case 1: |
240 |
// date to format (RFC 1123): Sun, 06 Nov 1994 08:49:37 GMT |
241 |
// Needed for reading X-Account-Until-Timestamp, Last-Modified |
242 |
// and writing If-Modified-Since, If-Unmodified-Since in the request headers |
243 |
dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; |
244 |
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; |
245 |
[dateFormatter setDateFormat:@"eee, dd MMM yyyy HH:mm:ss 'GMT'"]; |
246 |
[threadDict setObject:dateFormatter forKey:dateFormatterKey]; |
247 |
break; |
248 |
default: |
249 |
break; |
250 |
} |
251 |
} |
252 |
return dateFormatter; |
253 |
} |
254 |
|
255 |
#pragma mark - |
256 |
#pragma mark Extra Headers |
257 |
|
258 |
- (void)addRequestIfModifiedSinceHeader:(NSDate *)sinceTimestamp { |
259 |
if (sinceTimestamp) |
260 |
[self addRequestHeader:@"If-Modified-Since" value:[[self dateFormatterWithFormatId:1] stringFromDate:sinceTimestamp]]; |
261 |
} |
262 |
|
263 |
- (void)addRequestIfUnmodifiedSinceHeader:(NSDate *)sinceTimestamp { |
264 |
if (sinceTimestamp) |
265 |
[self addRequestHeader:@"If-Unmodified-Since" value:[[self dateFormatterWithFormatId:1] stringFromDate:sinceTimestamp]]; |
266 |
} |
267 |
|
268 |
- (void)addRequestIfMatchHeader:(NSString *)matchETag { |
269 |
if (matchETag) |
270 |
[self addRequestHeader:@"If-Match" value:matchETag]; |
271 |
} |
272 |
|
273 |
- (void)addRequestIfNoneMatchHeader:(NSString *)matchETag { |
274 |
if (matchETag) |
275 |
[self addRequestHeader:@"If-None-Match" value:matchETag]; |
276 |
} |
277 |
|
278 |
- (void)addRequestRangeHeader:(NSString *)rangeString { |
279 |
if (rangeString) |
280 |
[self addRequestHeader:@"Range" value:rangeString]; |
281 |
} |
282 |
|
283 |
- (void)addRequestRangeHeader:(NSString *)rangeString ifRangeETag:(NSString *)rangeETag { |
284 |
if (rangeString) { |
285 |
[self addRequestHeader:@"Range" value:rangeString]; |
286 |
if (rangeETag) |
287 |
[self addRequestHeader:@"If-Range" value:rangeETag]; |
288 |
} |
289 |
} |
290 |
|
291 |
- (void)addRequestRangeHeader:(NSString *)rangeString ifRangeTimestamp:(NSDate *)rangeTimestamp { |
292 |
if (rangeString) { |
293 |
[self addRequestHeader:@"Range" value:rangeString]; |
294 |
if (rangeTimestamp) |
295 |
[self addRequestHeader:@"If-Range" value:[[self dateFormatterWithFormatId:1] stringFromDate:rangeTimestamp]]; |
296 |
} |
297 |
} |
298 |
|
299 |
#pragma mark - |
300 |
#pragma mark Request User |
301 |
|
302 |
- (void)setRequestUserFromDefaultTo:(NSString *)newAuthUser withPithos:(ASIPithos *)pithos { |
303 |
NSString *urlString = [self.url description]; |
304 |
NSRange storageURLRange = [urlString rangeOfString:pithos.storageURL]; |
305 |
if (storageURLRange.length && (storageURLRange.location == 0)) |
306 |
self.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", |
307 |
[pithos storageURLWithAuthUser:newAuthUser], |
308 |
[urlString substringFromIndex:storageURLRange.length]]]; |
309 |
} |
310 |
|
311 |
- (void)changeRequestUserFrom:(NSString *)oldAuthUser To:(NSString *)newAuthUser withPithos:(ASIPithos *)pithos { |
312 |
NSString *urlString = [self.url description]; |
313 |
NSRange storageURLRange = [urlString rangeOfString:[pithos storageURLWithAuthUser:oldAuthUser]]; |
314 |
if (storageURLRange.length && (storageURLRange.location == 0)) |
315 |
self.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", |
316 |
[pithos storageURLWithAuthUser:newAuthUser], |
317 |
[urlString substringFromIndex:storageURLRange.length]]]; |
318 |
} |
319 |
|
320 |
#pragma mark - |
321 |
#pragma mark Internal |
322 |
|
323 |
- (NSMutableDictionary *)getHeadersDictionaryForPrefix:(NSString *)prefix { |
324 |
NSMutableDictionary *headersDictionary = [NSMutableDictionary dictionary]; |
325 |
for (NSString *key in [self responseHeaders]) { |
326 |
NSRange prefixRange = [key rangeOfString:prefix]; |
327 |
if (prefixRange.location == 0) { |
328 |
[headersDictionary setObject:[self decodeFromPercentEscape:[[self responseHeaders] objectForKey:key]] |
329 |
forKey:[[[self decodeFromPercentEscape:[key substringFromIndex:prefix.length]] |
330 |
stringByReplacingOccurrencesOfString:@"-" withString:@"_"] |
331 |
lowercaseString]]; |
332 |
} |
333 |
} |
334 |
return headersDictionary; |
335 |
} |
336 |
|
337 |
// http://cybersam.com/programming/proper-url-percent-encoding-in-ios |
338 |
+ (NSString *)encodeToPercentEscape:(NSString *)string { |
339 |
return [((NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, |
340 |
(CFStringRef)string, |
341 |
NULL, |
342 |
(CFStringRef)@"!*'();:@&=+$,/?%#[]", |
343 |
kCFStringEncodingUTF8)) autorelease]; |
344 |
} |
345 |
|
346 |
+ (NSString *)decodeFromPercentEscape:(NSString *)string { |
347 |
return [((NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(NULL, |
348 |
(CFStringRef)string, |
349 |
(CFStringRef)@"", |
350 |
kCFStringEncodingUTF8)) autorelease]; |
351 |
} |
352 |
|
353 |
- (NSString *)encodeToPercentEscape:(NSString *)string { |
354 |
return [ASIPithosRequest encodeToPercentEscape:string]; |
355 |
} |
356 |
|
357 |
- (NSString *)decodeFromPercentEscape:(NSString *)string { |
358 |
return [ASIPithosRequest decodeFromPercentEscape:string]; |
359 |
} |
360 |
|
361 |
@end |