]> git.sur5r.net Git - openldap/blob - libraries/liblunicode/ucstr.c
474837ae6b4d83b994047064b13cc68bcadd66c2
[openldap] / libraries / liblunicode / ucstr.c
1 /*
2  * Copyright 2000-2003 The OpenLDAP Foundation
3  * COPYING RESTRICTIONS APPLY.  See COPYRIGHT File in top level directory
4  * of this package for details.
5  */
6
7 #include "portable.h"
8
9 #include <ac/ctype.h>
10 #include <ac/string.h>
11 #include <ac/stdlib.h>
12
13 #include <lber_pvt.h>
14
15 #include <ldap_utf8.h>
16 #include <ldap_pvt_uc.h>
17
18 #define malloc(x)       ber_memalloc_x(x,ctx)
19 #define realloc(x,y)    ber_memrealloc_x(x,y,ctx)
20 #define free(x)         ber_memfree_x(x,ctx)
21
22 int ucstrncmp(
23         const ldap_unicode_t *u1,
24         const ldap_unicode_t *u2,
25         ber_len_t n )
26 {
27         for(; 0 < n; ++u1, ++u2, --n ) {
28                 if( *u1 != *u2 ) {
29                         return *u1 < *u2 ? -1 : +1;
30                 }
31                 if ( *u1 == 0 ) {
32                         return 0;
33                 }
34         }
35         return 0;
36 }
37
38 int ucstrncasecmp(
39         const ldap_unicode_t *u1,
40         const ldap_unicode_t *u2,
41         ber_len_t n )
42 {
43         for(; 0 < n; ++u1, ++u2, --n ) {
44                 ldap_unicode_t uu1 = uctolower( *u1 );
45                 ldap_unicode_t uu2 = uctolower( *u2 );
46
47                 if( uu1 != uu2 ) {
48                         return uu1 < uu2 ? -1 : +1;
49                 }
50                 if ( uu1 == 0 ) {
51                         return 0;
52                 }
53         }
54         return 0;
55 }
56
57 ldap_unicode_t * ucstrnchr(
58         const ldap_unicode_t *u,
59         ber_len_t n,
60         ldap_unicode_t c )
61 {
62         for(; 0 < n; ++u, --n ) {
63                 if( *u == c ) {
64                         return (ldap_unicode_t *) u;
65                 }
66         }
67
68         return NULL;
69 }
70
71 ldap_unicode_t * ucstrncasechr(
72         const ldap_unicode_t *u,
73         ber_len_t n,
74         ldap_unicode_t c )
75 {
76         c = uctolower( c );
77         for(; 0 < n; ++u, --n ) {
78                 if( uctolower( *u ) == c ) {
79                         return (ldap_unicode_t *) u;
80                 }
81         }
82
83         return NULL;
84 }
85
86 void ucstr2upper(
87         ldap_unicode_t *u,
88         ber_len_t n )
89 {
90         for(; 0 < n; ++u, --n ) {
91                 *u = uctoupper( *u );
92         }
93 }
94
95 struct berval * UTF8bvnormalize(
96         struct berval *bv,
97         struct berval *newbv,
98         unsigned flags,
99         void *ctx )
100 {
101         int i, j, len, clen, outpos, ucsoutlen, outsize, last;
102         char *out, *outtmp, *s;
103         unsigned long *ucs, *p, *ucsout;
104
105         static unsigned char mask[] = {
106                 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 };
107
108         unsigned casefold = flags & LDAP_UTF8_CASEFOLD;
109         unsigned approx = flags & LDAP_UTF8_APPROX;
110
111         if ( bv == NULL ) {
112                 return NULL;
113         }
114
115         s = bv->bv_val;
116         len = bv->bv_len;
117
118         if ( len == 0 ) {
119                 return ber_dupbv_x( newbv, bv, ctx );
120         }
121         
122         /* Should first check to see if string is already in proper
123          * normalized form. This is almost as time consuming as
124          * the normalization though.
125          */
126
127         /* finish off everything up to character before first non-ascii */
128         if ( LDAP_UTF8_ISASCII( s ) ) {
129                 if ( casefold ) {
130                         outsize = len + 7;
131                         out = (char *) malloc( outsize );
132                         if ( out == NULL ) {
133                                 return NULL;
134                         }
135                         outpos = 0;
136
137                         for ( i = 1; (i < len) && LDAP_UTF8_ISASCII(s + i); i++ ) {
138                                 out[outpos++] = TOLOWER( s[i-1] );
139                         }
140                         if ( i == len ) {
141                                 out[outpos++] = TOLOWER( s[len-1] );
142                                 out[outpos] = '\0';
143                                 return ber_str2bv( out, outpos, 0, newbv);
144                         }
145                 } else {
146                         for ( i = 1; (i < len) && LDAP_UTF8_ISASCII(s + i); i++ ) {
147                                 /* empty */
148                         }
149
150                         if ( i == len ) {
151                                 return ber_str2bv_x( s, len, 1, newbv, ctx );
152                         }
153                                 
154                         outsize = len + 7;
155                         out = (char *) malloc( outsize );
156                         if ( out == NULL ) {
157                                 return NULL;
158                         }
159                         outpos = i - 1;
160                         memcpy(out, s, outpos);
161                 }
162         } else {
163                 outsize = len + 7;
164                 out = (char *) malloc( outsize );
165                 if ( out == NULL ) {
166                         return NULL;
167                 }
168                 outpos = 0;
169                 i = 0;
170         }
171
172         p = ucs = malloc( len * sizeof(*ucs) );
173         if ( ucs == NULL ) {
174                 free(out);
175                 return NULL;
176         }
177
178         /* convert character before first non-ascii to ucs-4 */
179         if ( i > 0 ) {
180                 *p = casefold ? TOLOWER( s[i-1] ) : s[i-1];
181                 p++;
182         }
183
184         /* s[i] is now first non-ascii character */
185         for (;;) {
186                 /* s[i] is non-ascii */
187                 /* convert everything up to next ascii to ucs-4 */
188                 while ( i < len ) {
189                         clen = LDAP_UTF8_CHARLEN2( s + i, clen );
190                         if ( clen == 0 ) {
191                                 free( ucs );
192                                 free( out );
193                                 return NULL;
194                         }
195                         if ( clen == 1 ) {
196                                 /* ascii */
197                                 break;
198                         }
199                         *p = s[i] & mask[clen];
200                         i++;
201                         for( j = 1; j < clen; j++ ) {
202                                 if ( (s[i] & 0xc0) != 0x80 ) {
203                                         free( ucs );
204                                         free( out );
205                                         return NULL;
206                                 }
207                                 *p <<= 6;
208                                 *p |= s[i] & 0x3f;
209                                 i++;
210                         }
211                         if ( casefold ) {
212                                 *p = uctolower( *p );
213                         }
214                         p++;
215                 }
216                 /* normalize ucs of length p - ucs */
217                 uccompatdecomp( ucs, p - ucs, &ucsout, &ucsoutlen, ctx );
218                 if ( approx ) {
219                         for ( j = 0; j < ucsoutlen; j++ ) {
220                                 if ( ucsout[j] < 0x80 ) {
221                                         out[outpos++] = ucsout[j];
222                                 }
223                         }
224                 } else {
225                         ucsoutlen = uccanoncomp( ucsout, ucsoutlen );
226                         /* convert ucs to utf-8 and store in out */
227                         for ( j = 0; j < ucsoutlen; j++ ) {
228                                 /* allocate more space if not enough room for
229                                    6 bytes and terminator */
230                                 if ( outsize - outpos < 7 ) {
231                                         outsize = ucsoutlen - j + outpos + 6;
232                                         outtmp = (char *) realloc( out, outsize );
233                                         if ( outtmp == NULL ) {
234                                                 free( out );
235                                                 free( ucs );
236                                                 free( ucsout );
237                                                 return NULL;
238                                         }
239                                         out = outtmp;
240                                 }
241                                 outpos += ldap_x_ucs4_to_utf8( ucsout[j], &out[outpos] );
242                         }
243                 }
244
245                 free( ucsout );
246                 ucsout = NULL;
247                 
248                 if ( i == len ) {
249                         break;
250                 }
251
252                 last = i;
253
254                 /* Allocate more space in out if necessary */
255                 if (len - i >= outsize - outpos) {
256                         outsize += 1 + ((len - i) - (outsize - outpos));
257                         outtmp = (char *) realloc(out, outsize);
258                         if (outtmp == NULL) {
259                                 free(out);
260                                 free(ucs);
261                                 return NULL;
262                         }
263                         out = outtmp;
264                 }
265
266                 /* s[i] is ascii */
267                 /* finish off everything up to char before next non-ascii */
268                 for ( i++; (i < len) && LDAP_UTF8_ISASCII(s + i); i++ ) {
269                         out[outpos++] = casefold ? TOLOWER( s[i-1] ) : s[i-1];
270                 }
271                 if ( i == len ) {
272                         out[outpos++] = casefold ? TOLOWER( s[len-1] ) : s[len-1];
273                         break;
274                 }
275
276                 /* convert character before next non-ascii to ucs-4 */
277                 *ucs = casefold ? TOLOWER( s[i-1] ) : s[i-1];
278                 p = ucs + 1;
279         }
280
281         free( ucs );
282         out[outpos] = '\0';
283         return ber_str2bv( out, outpos, 0, newbv );
284 }
285
286 /* compare UTF8-strings, optionally ignore casing */
287 /* slow, should be optimized */
288 int UTF8bvnormcmp(
289         struct berval *bv1,
290         struct berval *bv2,
291         unsigned flags,
292         void *ctx )
293 {
294         int i, l1, l2, len, ulen, res = 0;
295         char *s1, *s2, *done;
296         unsigned long *ucs, *ucsout1, *ucsout2;
297
298         unsigned casefold = flags & LDAP_UTF8_CASEFOLD;
299         unsigned norm1 = flags & LDAP_UTF8_ARG1NFC;
300         unsigned norm2 = flags & LDAP_UTF8_ARG2NFC;
301
302         if (bv1 == NULL) {
303                 return bv2 == NULL ? 0 : -1;
304
305         } else if (bv2 == NULL) {
306                 return 1;
307         }
308
309         l1 = bv1->bv_len;
310         l2 = bv2->bv_len;
311
312         len = (l1 < l2) ? l1 : l2;
313         if (len == 0) {
314                 return l1 == 0 ? (l2 == 0 ? 0 : -1) : 1;
315         }
316
317         s1 = bv1->bv_val;
318         s2 = bv2->bv_val;
319         done = s1 + len;
320
321         while ( (s1 < done) && LDAP_UTF8_ISASCII(s1) && LDAP_UTF8_ISASCII(s2) ) {
322                 if (casefold) {
323                         char c1 = TOLOWER(*s1);
324                         char c2 = TOLOWER(*s2);
325                         res = c1 - c2;
326                 } else {
327                         res = *s1 - *s2;
328                 }                       
329                 s1++;
330                 s2++;
331                 if (res) {
332                         /* done unless next character in s1 or s2 is non-ascii */
333                         if (s1 < done) {
334                                 if (!LDAP_UTF8_ISASCII(s1) || !LDAP_UTF8_ISASCII(s2)) {
335                                         break;
336                                 }
337                         } else if (((len < l1) && !LDAP_UTF8_ISASCII(s1)) ||
338                                 ((len < l2) && !LDAP_UTF8_ISASCII(s2)))
339                         {
340                                 break;
341                         }
342                         return res;
343                 }
344         }
345
346         /* We have encountered non-ascii or strings equal up to len */
347
348         /* set i to number of iterations */
349         i = s1 - done + len;
350         /* passed through loop at least once? */
351         if (i > 0) {
352                 if (!res && (s1 == done) &&
353                     ((len == l1) || LDAP_UTF8_ISASCII(s1)) &&
354                     ((len == l2) || LDAP_UTF8_ISASCII(s2))) {
355                         /* all ascii and equal up to len */
356                         return l1 - l2;
357                 }
358
359                 /* rewind one char, and do normalized compare from there */
360                 s1--;
361                 s2--;
362                 l1 -= i - 1;
363                 l2 -= i - 1;
364         }
365                         
366         /* Should first check to see if strings are already in
367          * proper normalized form.
368          */
369         ucs = malloc( ( ( norm1 || l1 > l2 ) ? l1 : l2 ) * sizeof(*ucs) );
370         if ( ucs == NULL ) {
371                 return l1 > l2 ? 1 : -1; /* what to do??? */
372         }
373         
374         /*
375          * XXYYZ: we convert to ucs4 even though -llunicode
376          * expects ucs2 in an unsigned long
377          */
378         
379         /* convert and normalize 1st string */
380         for ( i = 0, ulen = 0; i < l1; i += len, ulen++ ) {
381                 ucs[ulen] = ldap_x_utf8_to_ucs4( s1 + i );
382                 if ( ucs[ulen] == LDAP_UCS4_INVALID ) {
383                         free( ucs );
384                         return -1; /* what to do??? */
385                 }
386                 len = LDAP_UTF8_CHARLEN( s1 + i );
387         }
388
389         if ( norm1 ) {
390                 ucsout1 = ucs;
391                 l1 = ulen;
392                 ucs = malloc( l2 * sizeof(*ucs) );
393                 if ( ucs == NULL ) {
394                         return l1 > l2 ? 1 : -1; /* what to do??? */
395                 }
396         } else {
397                 uccompatdecomp( ucs, ulen, &ucsout1, &l1, ctx );
398                 l1 = uccanoncomp( ucsout1, l1 );
399         }
400
401         /* convert and normalize 2nd string */
402         for ( i = 0, ulen = 0; i < l2; i += len, ulen++ ) {
403                 ucs[ulen] = ldap_x_utf8_to_ucs4( s2 + i );
404                 if ( ucs[ulen] == LDAP_UCS4_INVALID ) {
405                         free( ucsout1 );
406                         free( ucs );
407                         return 1; /* what to do??? */
408                 }
409                 len = LDAP_UTF8_CHARLEN( s2 + i );
410         }
411
412         if ( norm2 ) {
413                 ucsout2 = ucs;
414                 l2 = ulen;
415         } else {
416                 uccompatdecomp( ucs, ulen, &ucsout2, &l2, ctx );
417                 l2 = uccanoncomp( ucsout2, l2 );
418                 free( ucs );
419         }
420         
421         res = casefold
422                 ? ucstrncasecmp( ucsout1, ucsout2, l1 < l2 ? l1 : l2 )
423                 : ucstrncmp( ucsout1, ucsout2, l1 < l2 ? l1 : l2 );
424         free( ucsout1 );
425         free( ucsout2 );
426
427         if ( res != 0 ) {
428                 return res;
429         }
430         if ( l1 == l2 ) {
431                 return 0;
432         }
433         return l1 > l2 ? 1 : -1;
434 }