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