root / asi-http-request-with-pithos / Classes / Pithos / ASIPithosRequest.m @ 7c60da08
History | View | Annotate | Download (13.4 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 |
|
42 |
@implementation ASIPithosRequest |
43 |
@synthesize currentElement, currentContent, currentAccount; |
44 |
|
45 |
#pragma mark - |
46 |
#pragma mark Constructors |
47 |
|
48 |
+ (id)authRequestWithMethod:(NSString *)method pithos:(ASIPithos *)pithos queryString:(NSString *)queryString useAuthToken:(BOOL)useAuthToken { |
49 |
NSString *urlString = [NSString stringWithString:pithos.authURL]; |
50 |
if (queryString) |
51 |
urlString = [urlString stringByAppendingString:queryString]; |
52 |
|
53 |
ASIPithosRequest *request = [self requestWithURL:[NSURL URLWithString:urlString]]; |
54 |
[request setRequestMethod:method]; |
55 |
if (useAuthToken) |
56 |
[request addRequestHeader:@"X-Auth-Token" value:pithos.authToken]; |
57 |
return request; |
58 |
} |
59 |
|
60 |
+ (id)authRequestWithMethod:(NSString *)method pithos:(ASIPithos *)pithos useAuthToken:(BOOL)useAuthToken { |
61 |
return [self authRequestWithMethod:method pithos:pithos queryString:nil useAuthToken:useAuthToken]; |
62 |
} |
63 |
|
64 |
#pragma mark - |
65 |
#pragma mark Memory Management |
66 |
|
67 |
- (void)dealloc { |
68 |
[currentAccount release]; |
69 |
[currentContent release]; |
70 |
[currentElement release]; |
71 |
[sharingAccounts release]; |
72 |
[super dealloc]; |
73 |
} |
74 |
|
75 |
#pragma mark - |
76 |
#pragma mark Authentication |
77 |
|
78 |
+ (id)authenticationRequestWithPithos:(ASIPithos *)pithos { |
79 |
ASIPithosRequest *request = [self authRequestWithMethod:@"GET" pithos:pithos useAuthToken:NO]; |
80 |
[request addRequestHeader:@"X-Auth-User" value:pithos.authUser]; |
81 |
[request addRequestHeader:@"X-Auth-Key" value:pithos.authKey]; |
82 |
return request; |
83 |
} |
84 |
|
85 |
+ (ASIPithos *)authenticateWithPithos:(ASIPithos *)pithos error:(NSError **)error { |
86 |
ASIPithosRequest *request = [self authenticationRequestWithPithos:pithos]; |
87 |
[request startSynchronous]; |
88 |
// XXX start asynchronous with polling loop? |
89 |
|
90 |
if (error != NULL) { |
91 |
*error = [request error]; |
92 |
if (!*error) { |
93 |
NSDictionary *responseHeaders = [request responseHeaders]; |
94 |
pithos.authToken = [responseHeaders objectForKey:@"X-Auth-Token"]; |
95 |
pithos.storageURL = [responseHeaders objectForKey:@"X-Storage-Url"]; |
96 |
return pithos; |
97 |
} |
98 |
} |
99 |
return nil; |
100 |
} |
101 |
|
102 |
#pragma mark - |
103 |
#pragma mark GET |
104 |
|
105 |
// GET authURL |
106 |
+ (id)listSharingAccountsRequestWithPithos:(ASIPithos *)pithos { |
107 |
return [self authRequestWithMethod:@"GET" pithos:pithos queryString:@"?format=xml" useAuthToken:YES]; |
108 |
} |
109 |
|
110 |
// GET authURL?[limit=limit]&[marker=marker] |
111 |
+ (id)listSharingAccountsRequestWithPithos:(ASIPithos *)pithos limit:(NSUInteger)limit marker:(NSString *)marker { |
112 |
NSString *queryString = @"?format=xml"; |
113 |
if (limit && (limit > 0)) |
114 |
queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&limit=%lu", limit]]; |
115 |
if (marker) |
116 |
queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&marker=%@", [self encodeToPercentEscape:marker]]]; |
117 |
|
118 |
return [self authRequestWithMethod:@"GET" pithos:pithos queryString:queryString useAuthToken:YES]; |
119 |
} |
120 |
|
121 |
- (NSArray *)sharingAccounts { |
122 |
if (sharingAccounts == nil) { |
123 |
sharingAccounts = [[NSMutableArray alloc] init]; |
124 |
|
125 |
NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:[self responseData]] autorelease]; |
126 |
[parser setDelegate:self]; |
127 |
[parser setShouldProcessNamespaces:NO]; |
128 |
[parser setShouldReportNamespacePrefixes:NO]; |
129 |
[parser setShouldResolveExternalEntities:NO]; |
130 |
[parser parse]; |
131 |
} |
132 |
return sharingAccounts; |
133 |
} |
134 |
|
135 |
#pragma mark - |
136 |
#pragma mark NSXMLParserDelegate |
137 |
|
138 |
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { |
139 |
self.currentElement = elementName; |
140 |
if ([elementName isEqualToString:@"account"]) { |
141 |
self.currentAccount = [ASIPithosAccount account]; |
142 |
} |
143 |
|
144 |
self.currentContent = @""; |
145 |
} |
146 |
|
147 |
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { |
148 |
if ([elementName isEqualToString:@"name"]) { |
149 |
currentAccount.name = currentContent; |
150 |
} else if ([elementName isEqualToString:@"count"]) { |
151 |
currentAccount.containerCount = [currentContent integerValue]; |
152 |
} else if ([elementName isEqualToString:@"last_modified"]) { |
153 |
currentAccount.lastModified = [[self dateFormatterWithFormatId:0] dateFromString: |
154 |
[currentContent stringByReplacingOccurrencesOfString:@":" |
155 |
withString:@"" |
156 |
options:NSBackwardsSearch |
157 |
range:NSMakeRange(([currentContent length] - 3), 1)]]; |
158 |
} else if ([elementName isEqualToString:@"bytes"]) { |
159 |
currentAccount.bytesUsed = [currentContent integerValue]; |
160 |
} else if ([elementName isEqualToString:@"account"]) { |
161 |
[sharingAccounts addObject:currentAccount]; |
162 |
self.currentAccount = nil; |
163 |
} |
164 |
} |
165 |
|
166 |
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { |
167 |
self.currentContent = [currentContent stringByAppendingString:string]; |
168 |
} |
169 |
|
170 |
#pragma mark - |
171 |
#pragma mark Date Formatters |
172 |
|
173 |
// We store our date formatters in the calling thread's dictionary |
174 |
// NSDateFormatter is not thread-safe, this approach ensures each formatter is only used on a single thread |
175 |
// Each formatter can be reused many times in parsing a single response, so it would be expensive to keep creating new date formatters |
176 |
|
177 |
-(NSDateFormatter *)dateFormatterWithFormatId:(NSUInteger)formatId { |
178 |
NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary]; |
179 |
NSString *dateFormatterKey = [NSString stringWithFormat:@"ASIPithosDateFormatter%lu", formatId]; |
180 |
NSDateFormatter *dateFormatter = [threadDict objectForKey:dateFormatterKey]; |
181 |
if (dateFormatter == nil) { |
182 |
switch (formatId) { |
183 |
case 0: |
184 |
// date format: 2009-11-04T19:46:20.123456+0000 |
185 |
// Needed for last_modified |
186 |
dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; |
187 |
[dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]]; |
188 |
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSSSSZ"]; |
189 |
[threadDict setObject:dateFormatter forKey:dateFormatterKey]; |
190 |
break; |
191 |
case 1: |
192 |
// date to format (RFC 1123): Sun, 06 Nov 1994 08:49:37 GMT |
193 |
// Needed for reading X-Account-Until-Timestamp, Last-Modified |
194 |
// and writing If-Modified-Since, If-Unmodified-Since in the request headers |
195 |
dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; |
196 |
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; |
197 |
[dateFormatter setDateFormat:@"eee, dd MMM yyyy HH:mm:ss 'GMT'"]; |
198 |
[threadDict setObject:dateFormatter forKey:dateFormatterKey]; |
199 |
break; |
200 |
default: |
201 |
break; |
202 |
} |
203 |
} |
204 |
return dateFormatter; |
205 |
} |
206 |
|
207 |
#pragma mark - |
208 |
#pragma mark Extra Headers |
209 |
|
210 |
- (void)addRequestIfModifiedSinceHeader:(NSDate *)sinceTimestamp { |
211 |
if (sinceTimestamp) |
212 |
[self addRequestHeader:@"If-Modified-Since" value:[[self dateFormatterWithFormatId:1] stringFromDate:sinceTimestamp]]; |
213 |
} |
214 |
|
215 |
- (void)addRequestIfUnmodifiedSinceHeader:(NSDate *)sinceTimestamp { |
216 |
if (sinceTimestamp) |
217 |
[self addRequestHeader:@"If-Unmodified-Since" value:[[self dateFormatterWithFormatId:1] stringFromDate:sinceTimestamp]]; |
218 |
} |
219 |
|
220 |
- (void)addRequestIfMatchHeader:(NSString *)matchETag { |
221 |
if (matchETag) |
222 |
[self addRequestHeader:@"If-Match" value:matchETag]; |
223 |
} |
224 |
|
225 |
- (void)addRequestIfNoneMatchHeader:(NSString *)matchETag { |
226 |
if (matchETag) |
227 |
[self addRequestHeader:@"If-None-Match" value:matchETag]; |
228 |
} |
229 |
|
230 |
- (void)addRequestRangeHeader:(NSString *)rangeString { |
231 |
if (rangeString) |
232 |
[self addRequestHeader:@"Range" value:rangeString]; |
233 |
} |
234 |
|
235 |
- (void)addRequestRangeHeader:(NSString *)rangeString ifRangeETag:(NSString *)rangeETag { |
236 |
if (rangeString) { |
237 |
[self addRequestHeader:@"Range" value:rangeString]; |
238 |
if (rangeETag) |
239 |
[self addRequestHeader:@"If-Range" value:rangeETag]; |
240 |
} |
241 |
} |
242 |
|
243 |
- (void)addRequestRangeHeader:(NSString *)rangeString ifRangeTimestamp:(NSDate *)rangeTimestamp { |
244 |
if (rangeString) { |
245 |
[self addRequestHeader:@"Range" value:rangeString]; |
246 |
if (rangeTimestamp) |
247 |
[self addRequestHeader:@"If-Range" value:[[self dateFormatterWithFormatId:1] stringFromDate:rangeTimestamp]]; |
248 |
} |
249 |
} |
250 |
|
251 |
#pragma mark - |
252 |
#pragma mark Request User |
253 |
|
254 |
- (void)setRequestUserFromDefaultTo:(NSString *)newAuthUser withPithos:(ASIPithos *)pithos { |
255 |
NSString *urlString = [self.url description]; |
256 |
NSRange storageURLRange = [urlString rangeOfString:pithos.storageURL]; |
257 |
if (storageURLRange.length && (storageURLRange.location == 0)) |
258 |
self.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", |
259 |
[pithos storageURLWithAuthUser:newAuthUser], |
260 |
[urlString substringFromIndex:storageURLRange.length]]]; |
261 |
} |
262 |
|
263 |
- (void)changeRequestUserFrom:(NSString *)oldAuthUser To:(NSString *)newAuthUser withPithos:(ASIPithos *)pithos { |
264 |
NSString *urlString = [self.url description]; |
265 |
NSRange storageURLRange = [urlString rangeOfString:[pithos storageURLWithAuthUser:oldAuthUser]]; |
266 |
if (storageURLRange.length && (storageURLRange.location == 0)) |
267 |
self.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", |
268 |
[pithos storageURLWithAuthUser:newAuthUser], |
269 |
[urlString substringFromIndex:storageURLRange.length]]]; |
270 |
} |
271 |
|
272 |
#pragma mark - |
273 |
#pragma mark Internal |
274 |
|
275 |
- (NSMutableDictionary *)getHeadersDictionaryForPrefix:(NSString *)prefix { |
276 |
NSMutableDictionary *headersDictionary = [NSMutableDictionary dictionary]; |
277 |
for (NSString *key in [[self responseHeaders] keyEnumerator]) { |
278 |
NSRange prefixRange = [key rangeOfString:prefix]; |
279 |
if (prefixRange.location == 0) { |
280 |
[headersDictionary setObject:[self decodeFromPercentEscape:[[self responseHeaders] objectForKey:key]] |
281 |
forKey:[[[self decodeFromPercentEscape:[key substringFromIndex:prefix.length]] |
282 |
stringByReplacingOccurrencesOfString:@"-" withString:@"_"] |
283 |
lowercaseString]]; |
284 |
} |
285 |
} |
286 |
return headersDictionary; |
287 |
} |
288 |
|
289 |
// http://cybersam.com/programming/proper-url-percent-encoding-in-ios |
290 |
+ (NSString *)encodeToPercentEscape:(NSString *)string { |
291 |
return [((NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, |
292 |
(CFStringRef)string, |
293 |
NULL, |
294 |
(CFStringRef)@"!*'();:@&=+$,/?%#[]", |
295 |
kCFStringEncodingUTF8)) autorelease]; |
296 |
} |
297 |
|
298 |
+ (NSString *)decodeFromPercentEscape:(NSString *)string { |
299 |
return [((NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(NULL, |
300 |
(CFStringRef)string, |
301 |
(CFStringRef)@"", |
302 |
kCFStringEncodingUTF8)) autorelease]; |
303 |
} |
304 |
|
305 |
- (NSString *)encodeToPercentEscape:(NSString *)string { |
306 |
return [ASIPithosRequest encodeToPercentEscape:string]; |
307 |
} |
308 |
|
309 |
- (NSString *)decodeFromPercentEscape:(NSString *)string { |
310 |
return [ASIPithosRequest decodeFromPercentEscape:string]; |
311 |
} |
312 |
|
313 |
@end |