]> err.no Git - sope/blob - sope-core/NGExtensions/FdExt.subproj/NSString+URLEscaping.m
minor fixes
[sope] / sope-core / NGExtensions / FdExt.subproj / NSString+URLEscaping.m
1 /*
2   Copyright (C) 2000-2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with OGo; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21 // $Id: NSString+URLEscaping.m 1 2004-08-20 10:08:27Z znek $
22
23 #include "NSString+misc.h"
24 #include "common.h"
25
26 /*
27   TODO: support new Panther API?:
28 - (NSString *)stringByAddingPercentEscapesUsingEncoding:(NSStringEncoding)e
29 - (NSString *)stringByReplacingPercentEscapesUsingEncoding:(NSStringEncoding)e
30 */
31
32 @implementation NSString(URLEscaping)
33
34 static int useUTF8Encoding = -1;
35
36 static inline BOOL doUseUTF8Encoding(void) {
37   if (useUTF8Encoding == -1) {
38     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
39     
40     useUTF8Encoding = [ud boolForKey:@"NGUseUTF8AsURLEncoding"] ? 1 : 0;
41     if (useUTF8Encoding)
42       NSLog(@"Note: Using UTF-8 as URL encoding in NGExtensions.");
43   }
44   return useUTF8Encoding ? YES : NO;
45 }
46
47 static inline BOOL isUrlAlpha(unsigned char _c) {
48   return
49     (((_c >= 'a') && (_c <= 'z')) ||
50      ((_c >= 'A') && (_c <= 'Z')))
51     ? YES : NO;
52 }
53 static inline BOOL isUrlDigit(unsigned char _c) {
54   return ((_c >= '0') && (_c <= '9')) ? YES : NO;
55 }
56 static inline BOOL isUrlSafeChar(unsigned char _c) {
57   switch (_c) {
58     case '$': case '-': case '_': case '@':
59     case '.': case '&': case '+':
60       return YES;
61
62     default:
63       return NO;
64   }
65 }
66 static inline BOOL isUrlExtraChar(unsigned char _c) {
67   switch (_c) {
68     case '!': case '*': case '"': case '\'':
69     case '|': case ',':
70       return YES;
71   }
72   return NO;
73 }
74 static inline BOOL isUrlEscapeChar(unsigned char _c) {
75   return (_c == '%') ? YES : NO;
76 }
77 static inline BOOL isUrlReservedChar(unsigned char _c) {
78   switch (_c) {
79     case '=': case ';': case '/':
80     case '#': case '?': case ':':
81     case ' ':
82       return YES;
83   }
84   return NO;
85 }
86
87 static inline BOOL isUrlXalpha(unsigned char _c) {
88   if (isUrlAlpha(_c))      return YES;
89   if (isUrlDigit(_c))      return YES;
90   if (isUrlSafeChar(_c))   return YES;
91   if (isUrlExtraChar(_c))  return YES;
92   if (isUrlEscapeChar(_c)) return YES;
93   return NO;
94 }
95
96 static inline BOOL isUrlHexChar(unsigned char _c) {
97   if (isUrlDigit(_c))
98     return YES;
99   if ((_c >= 'a') && (_c <= 'f'))
100     return YES;
101   if ((_c >= 'A') && (_c <= 'F'))
102     return YES;
103   return NO;
104 }
105
106 static inline BOOL isUrlAlphaNum(unsigned char _c) {
107   return (isUrlAlpha(_c) || isUrlDigit(_c)) ? YES : NO;
108 }
109
110 static inline BOOL isToBeEscaped(unsigned char _c) {
111   return (isUrlAlphaNum(_c) || (_c == '_') || isUrlSafeChar(_c)) ? NO : YES;
112 }
113
114 static void
115 NGEscapeUrlBuffer(const unsigned char *_source, unsigned char *_dest,
116                   unsigned srclen)
117 {
118   register const unsigned char *src = (void*)_source;
119   register unsigned i;
120   for (i = 0; i < srclen; i++, src++) {
121 #if 0 // explain!
122     if (*src == ' ') { // a ' ' becomes a '+'
123       *_dest = '+'; _dest++;
124     }
125 #endif
126     if (!isToBeEscaped(*src)) {
127       *_dest = *src;
128       _dest++;
129     } 
130     else { // any other char is escaped ..
131       *_dest = '%'; _dest++;
132       sprintf(_dest, "%02X", (unsigned)*src);
133       _dest += 2;
134     }
135   }
136   *_dest = '\0';
137 }
138
139 static inline int _valueOfHexChar(register unichar _c) {
140   switch (_c) {
141     case '0': case '1': case '2': case '3': case '4':
142     case '5': case '6': case '7': case '8': case '9':
143       return (_c - 48); // 0-9 (ascii-char)'0' - 48 => (int)0
144       
145     case 'A': case 'B': case 'C':
146     case 'D': case 'E': case 'F':
147       return (_c - 55); // A-F, A=10..F=15, 'A'=65..'F'=70
148       
149     case 'a': case 'b': case 'c':
150     case 'd': case 'e': case 'f':
151       return (_c - 87); // a-f, a=10..F=15, 'a'=97..'f'=102
152
153     default:
154       return -1;
155   }
156 }
157 static inline BOOL _isHexDigit(register unichar _c) {
158   switch (_c) {
159     case '0': case '1': case '2': case '3': case '4':
160     case '5': case '6': case '7': case '8': case '9':
161     case 'A': case 'B': case 'C':
162     case 'D': case 'E': case 'F':
163     case 'a': case 'b': case 'c':
164     case 'd': case 'e': case 'f':
165       return YES;
166
167     default:
168       return NO;
169   }
170 }
171
172 static void
173 NGUnescapeUrlBuffer(const unsigned char *_source, unsigned char *_dest)
174 {
175   BOOL done = NO;
176
177   while (!done && (*_source != '\0')) {
178     char c = *_source;
179
180     //if (c == '+') // '+' stands for a space
181     //  *_dest = ' ';
182     if (c == '%') {
183       _source++; c = *_source;
184       
185       if (c == '\0') {
186         *_dest = '%';
187         done = YES;
188       }
189       else if (_isHexDigit(c)) { // hex-escaped char, like '%F3'
190         int decChar = _valueOfHexChar(c);
191         _source++;
192         c = *_source;
193         decChar = decChar * 16 + _valueOfHexChar(c);
194         *_dest = (unsigned char)decChar;
195       }
196       else // escaped char, like '%%' -> '%'
197         *_dest = c;
198     }
199     else // char passed through
200       *_dest = c;
201
202     _dest++;
203     _source++;
204   }
205   *_dest = '\0';
206 }
207
208 - (BOOL)containsURLEscapeCharacters {
209   register unsigned i, len;
210   register unichar (*charAtIdx)(id,SEL,unsigned);
211   
212   if ((len = [self length]) == 0) return NO;
213   
214   charAtIdx = (void*)[self methodForSelector:@selector(characterAtIndex:)];
215   for (i = 0; i < len; i++) {
216     if (charAtIdx(self, @selector(characterAtIndex:), i) == '%')
217       return YES;
218   }
219   return NO;
220 }
221 - (BOOL)containsURLInvalidCharacters {
222   register unsigned i, len;
223   register unichar (*charAtIdx)(id,SEL,unsigned);
224   
225   if ((len = [self length]) == 0) return NO;
226   
227   charAtIdx = (void*)[self methodForSelector:@selector(characterAtIndex:)];
228   for (i = 0; i < len; i++) {
229     if (isToBeEscaped(charAtIdx(self, @selector(characterAtIndex:), i)))
230       return YES;
231   }
232   return NO;
233 }
234
235 - (NSString *)stringByUnescapingURL {
236   /* 
237      input is a URL string - per definition ASCII(?!), like "hello%98%88.txt"
238      output is a unicode string (never longer than the input)
239      
240      Note that the input itself is in some encoding! That is, the input is
241      turned into a buffer eg containing UTF-8 and needs to be converted into
242      a unicode string.
243   */
244   unsigned len;
245   char     *cstr;
246   char     *buffer = NULL;
247   NSString *s;
248   
249   if (![self containsURLEscapeCharacters]) /* scan for '%' */
250     return [[self copy] autorelease];
251   
252   if ((len = [self cStringLength]) == 0) return @"";
253   
254   cstr = malloc(len + 10);
255   [self getCString:cstr]; /* this is OK, a URL is always in ASCII! */
256   cstr[len] = '\0';
257   
258   buffer = malloc(len + 4);
259   NGUnescapeUrlBuffer(cstr, buffer);
260   
261   if (doUseUTF8Encoding()) {
262     /* OK, the input is considered UTF-8 encoded in a string */
263     s = [[NSString alloc] initWithUTF8String:buffer];
264     if (buffer) free(buffer);
265   }
266   else {
267     s = [[NSString alloc]
268           initWithCStringNoCopy:buffer
269           length:strlen(buffer)
270           freeWhenDone:YES];
271   }
272   if (cstr) free(cstr);
273   return [s autorelease];
274 }
275
276 - (NSString *)stringByEscapingURL {
277   unsigned len;
278   NSString *s;
279   char     *buffer = NULL;
280   
281   if ((len = [self length]) == 0) return @"";
282   
283   if (![self containsURLInvalidCharacters]) // needs to be escaped ?
284     return [[self copy] autorelease];
285   
286   if (doUseUTF8Encoding()) {
287     // steps:
288     // a) encode into a data buffer! (eg UTF8 or ISO)
289     // b) encode that buffer into URL encoding
290     // c) create an ASCII string from that
291     NSData *data;
292     
293     if ((data = [self dataUsingEncoding:NSUTF8StringEncoding]) == nil)
294       return nil;
295     if ((len = [data length]) == 0)
296       return @"";
297     
298     buffer = malloc(len * 3 + 2);
299     NGEscapeUrlBuffer([data bytes], buffer, len);
300   }
301   else {
302     unsigned char *cstr;
303     
304     len  = [self cStringLength];
305     cstr = malloc(len + 4);
306     [self getCString:cstr]; // Unicode!
307     cstr[len] = '\0';
308     
309     buffer = malloc(len * 3 + 2);
310     NGEscapeUrlBuffer(cstr, buffer, len);
311     if (cstr) free(cstr);
312   
313   }
314   /* the following assumes that the default-encoding is ASCII compatible */
315   s = [[NSString alloc]
316                  initWithCStringNoCopy:buffer
317                  length:strlen(buffer)
318                  freeWhenDone:YES];
319   return [s autorelease];
320 }
321
322 @end /* NSString(URLEscaping) */