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