1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 5 |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | http://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Authors: Vadim Savchuk <vsavchuk@productengine.com> |
14 | Dmitry Lakhtyuk <dlakhtyuk@productengine.com> |
15 +----------------------------------------------------------------------+
16 */
17
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21
22 #include "php_intl.h"
23 #include "collator.h"
24 #include "collator_class.h"
25 #include "collator_sort.h"
26 #include "collator_convert.h"
27 #include "intl_convert.h"
28
29 #if !defined(HAVE_PTRDIFF_T) && !defined(_PTRDIFF_T_DEFINED)
30 typedef long ptrdiff_t;
31 #endif
32
33 /**
34 * Declare 'index' which will point to sort key in sort key
35 * buffer.
36 */
37 typedef struct _collator_sort_key_index {
38 char* key; /* pointer to sort key */
39 zval** zstr; /* pointer to original string(hash-item) */
40 } collator_sort_key_index_t;
41
42 ZEND_EXTERN_MODULE_GLOBALS( intl )
43
44 static const size_t DEF_SORT_KEYS_BUF_SIZE = 1048576;
45 static const size_t DEF_SORT_KEYS_BUF_INCREMENT = 1048576;
46
47 static const size_t DEF_SORT_KEYS_INDX_BUF_SIZE = 1048576;
48 static const size_t DEF_SORT_KEYS_INDX_BUF_INCREMENT = 1048576;
49
50 static const size_t DEF_UTF16_BUF_SIZE = 1024;
51
52 /* {{{ collator_regular_compare_function */
collator_regular_compare_function(zval * result,zval * op1,zval * op2 TSRMLS_DC)53 static int collator_regular_compare_function(zval *result, zval *op1, zval *op2 TSRMLS_DC)
54 {
55 Collator_object* co = NULL;
56
57 int rc = SUCCESS;
58
59 zval* str1 = collator_convert_object_to_string( op1 TSRMLS_CC );
60 zval* str2 = collator_convert_object_to_string( op2 TSRMLS_CC );
61
62 zval* num1 = NULL;
63 zval* num2 = NULL;
64 zval* norm1 = NULL;
65 zval* norm2 = NULL;
66
67 /* If both args are strings AND either of args is not numeric string
68 * then use ICU-compare. Otherwise PHP-compare. */
69 if( Z_TYPE_P(str1) == IS_STRING && Z_TYPE_P(str2) == IS_STRING &&
70 ( str1 == ( num1 = collator_convert_string_to_number_if_possible( str1 ) ) ||
71 str2 == ( num2 = collator_convert_string_to_number_if_possible( str2 ) ) ) )
72 {
73 /* Fetch collator object. */
74 co = (Collator_object *) zend_object_store_get_object( INTL_G(current_collator) TSRMLS_CC );
75
76 if (!co || !co->ucoll) {
77 intl_error_set_code( NULL, COLLATOR_ERROR_CODE( co ) TSRMLS_CC );
78 intl_errors_set_custom_msg( COLLATOR_ERROR_P( co ),
79 "Object not initialized", 0 TSRMLS_CC );
80 php_error_docref(NULL TSRMLS_CC, E_RECOVERABLE_ERROR, "Object not initialized");
81 }
82
83 /* Compare the strings using ICU. */
84 result->value.lval = ucol_strcoll(
85 co->ucoll,
86 INTL_Z_STRVAL_P(str1), INTL_Z_STRLEN_P(str1),
87 INTL_Z_STRVAL_P(str2), INTL_Z_STRLEN_P(str2) );
88 result->type = IS_LONG;
89 }
90 else
91 {
92 /* num1 is set if str1 and str2 are strings. */
93 if( num1 )
94 {
95 if( num1 == str1 )
96 {
97 /* str1 is string but not numeric string
98 * just convert it to utf8.
99 */
100 norm1 = collator_convert_zstr_utf16_to_utf8( str1 );
101
102 /* num2 is not set but str2 is string => do normalization. */
103 norm2 = collator_normalize_sort_argument( str2 );
104 }
105 else
106 {
107 /* str1 is numeric strings => passthru to PHP-compare. */
108 zval_add_ref( &num1 );
109 norm1 = num1;
110
111 /* str2 is numeric strings => passthru to PHP-compare. */
112 zval_add_ref( &num2 );
113 norm2 = num2;
114 }
115 }
116 else
117 {
118 /* num1 is not set if str1 or str2 is not a string => do normalization. */
119 norm1 = collator_normalize_sort_argument( str1 );
120
121 /* if num1 is not set then num2 is not set as well => do normalization. */
122 norm2 = collator_normalize_sort_argument( str2 );
123 }
124
125 rc = compare_function( result, norm1, norm2 TSRMLS_CC );
126
127 zval_ptr_dtor( &norm1 );
128 zval_ptr_dtor( &norm2 );
129 }
130
131 if( num1 )
132 zval_ptr_dtor( &num1 );
133
134 if( num2 )
135 zval_ptr_dtor( &num2 );
136
137 zval_ptr_dtor( &str1 );
138 zval_ptr_dtor( &str2 );
139
140 return rc;
141 }
142 /* }}} */
143
144 /* {{{ collator_numeric_compare_function
145 * Convert input args to double and compare it.
146 */
collator_numeric_compare_function(zval * result,zval * op1,zval * op2 TSRMLS_DC)147 static int collator_numeric_compare_function(zval *result, zval *op1, zval *op2 TSRMLS_DC)
148 {
149 int rc = SUCCESS;
150 zval* num1 = NULL;
151 zval* num2 = NULL;
152
153 if( Z_TYPE_P(op1) == IS_STRING )
154 {
155 num1 = collator_convert_string_to_double( op1 );
156 op1 = num1;
157 }
158
159 if( Z_TYPE_P(op2) == IS_STRING )
160 {
161 num2 = collator_convert_string_to_double( op2 );
162 op2 = num2;
163 }
164
165 rc = numeric_compare_function( result, op1, op2 TSRMLS_CC);
166
167 if( num1 )
168 zval_ptr_dtor( &num1 );
169 if( num2 )
170 zval_ptr_dtor( &num2 );
171
172 return rc;
173 }
174 /* }}} */
175
176 /* {{{ collator_icu_compare_function
177 * Direct use of ucol_strcoll.
178 */
collator_icu_compare_function(zval * result,zval * op1,zval * op2 TSRMLS_DC)179 static int collator_icu_compare_function(zval *result, zval *op1, zval *op2 TSRMLS_DC)
180 {
181 int rc = SUCCESS;
182 Collator_object* co = NULL;
183 zval* str1 = NULL;
184 zval* str2 = NULL;
185
186 str1 = collator_make_printable_zval( op1 );
187 str2 = collator_make_printable_zval( op2 );
188
189 /* Fetch collator object. */
190 co = (Collator_object *) zend_object_store_get_object( INTL_G(current_collator) TSRMLS_CC );
191
192 /* Compare the strings using ICU. */
193 result->value.lval = ucol_strcoll(
194 co->ucoll,
195 INTL_Z_STRVAL_P(str1), INTL_Z_STRLEN_P(str1),
196 INTL_Z_STRVAL_P(str2), INTL_Z_STRLEN_P(str2) );
197 result->type = IS_LONG;
198
199 zval_ptr_dtor( &str1 );
200 zval_ptr_dtor( &str2 );
201
202 return rc;
203 }
204 /* }}} */
205
206 /* {{{ collator_compare_func
207 * Taken from PHP5 source (array_data_compare).
208 */
collator_compare_func(const void * a,const void * b TSRMLS_DC)209 static int collator_compare_func( const void* a, const void* b TSRMLS_DC )
210 {
211 Bucket *f;
212 Bucket *s;
213 zval result;
214 zval *first;
215 zval *second;
216
217 f = *((Bucket **) a);
218 s = *((Bucket **) b);
219
220 first = *((zval **) f->pData);
221 second = *((zval **) s->pData);
222
223 if( INTL_G(compare_func)( &result, first, second TSRMLS_CC) == FAILURE )
224 return 0;
225
226 if( Z_TYPE(result) == IS_DOUBLE )
227 {
228 if( Z_DVAL(result) < 0 )
229 return -1;
230 else if( Z_DVAL(result) > 0 )
231 return 1;
232 else
233 return 0;
234 }
235
236 convert_to_long(&result);
237
238 if( Z_LVAL(result) < 0 )
239 return -1;
240 else if( Z_LVAL(result) > 0 )
241 return 1;
242
243 return 0;
244 }
245 /* }}} */
246
247 /* {{{ collator_cmp_sort_keys
248 * Compare sort keys
249 */
collator_cmp_sort_keys(const void * p1,const void * p2 TSRMLS_DC)250 static int collator_cmp_sort_keys( const void *p1, const void *p2 TSRMLS_DC )
251 {
252 char* key1 = ((collator_sort_key_index_t*)p1)->key;
253 char* key2 = ((collator_sort_key_index_t*)p2)->key;
254
255 return strcmp( key1, key2 );
256 }
257 /* }}} */
258
259 /* {{{ collator_get_compare_function
260 * Choose compare function according to sort flags.
261 */
collator_get_compare_function(const long sort_flags)262 static collator_compare_func_t collator_get_compare_function( const long sort_flags )
263 {
264 collator_compare_func_t func;
265
266 switch( sort_flags )
267 {
268 case COLLATOR_SORT_NUMERIC:
269 func = collator_numeric_compare_function;
270 break;
271
272 case COLLATOR_SORT_STRING:
273 func = collator_icu_compare_function;
274 break;
275
276 case COLLATOR_SORT_REGULAR:
277 default:
278 func = collator_regular_compare_function;
279 break;
280 }
281
282 return func;
283 }
284 /* }}} */
285
286 /* {{{ collator_sort_internal
287 * Common code shared by collator_sort() and collator_asort() API functions.
288 */
collator_sort_internal(int renumber,INTERNAL_FUNCTION_PARAMETERS)289 static void collator_sort_internal( int renumber, INTERNAL_FUNCTION_PARAMETERS )
290 {
291 zval* array = NULL;
292 HashTable* hash = NULL;
293 zval* saved_collator = NULL;
294 long sort_flags = COLLATOR_SORT_REGULAR;
295
296 COLLATOR_METHOD_INIT_VARS
297
298 /* Parse parameters. */
299 if( zend_parse_method_parameters( ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa|l",
300 &object, Collator_ce_ptr, &array, &sort_flags ) == FAILURE )
301 {
302 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
303 "collator_sort_internal: unable to parse input params", 0 TSRMLS_CC );
304
305 RETURN_FALSE;
306 }
307
308 /* Fetch the object. */
309 COLLATOR_METHOD_FETCH_OBJECT;
310
311 /* Set 'compare function' according to sort flags. */
312 INTL_G(compare_func) = collator_get_compare_function( sort_flags );
313
314 hash = HASH_OF( array );
315
316 /* Convert strings in the specified array from UTF-8 to UTF-16. */
317 collator_convert_hash_from_utf8_to_utf16( hash, COLLATOR_ERROR_CODE_P( co ) );
318 COLLATOR_CHECK_STATUS( co, "Error converting hash from UTF-8 to UTF-16" );
319
320 /* Save specified collator in the request-global (?) variable. */
321 saved_collator = INTL_G( current_collator );
322 INTL_G( current_collator ) = object;
323
324 /* Sort specified array. */
325 zend_hash_sort( hash, zend_qsort, collator_compare_func, renumber TSRMLS_CC );
326
327 /* Restore saved collator. */
328 INTL_G( current_collator ) = saved_collator;
329
330 /* Convert strings in the specified array back to UTF-8. */
331 collator_convert_hash_from_utf16_to_utf8( hash, COLLATOR_ERROR_CODE_P( co ) );
332 COLLATOR_CHECK_STATUS( co, "Error converting hash from UTF-16 to UTF-8" );
333
334 RETURN_TRUE;
335 }
336 /* }}} */
337
338 /* {{{ proto bool Collator::sort( Collator $coll, array(string) $arr [, int $sort_flags] )
339 * Sort array using specified collator. }}} */
340 /* {{{ proto bool collator_sort( Collator $coll, array(string) $arr [, int $sort_flags] )
341 * Sort array using specified collator.
342 */
PHP_FUNCTION(collator_sort)343 PHP_FUNCTION( collator_sort )
344 {
345 collator_sort_internal( TRUE, INTERNAL_FUNCTION_PARAM_PASSTHRU );
346 }
347 /* }}} */
348
349 /* {{{ proto bool Collator::sortWithSortKeys( Collator $coll, array(string) $arr )
350 * Equivalent to standard PHP sort using Collator.
351 * Uses ICU ucol_getSortKey for performance. }}} */
352 /* {{{ proto bool collator_sort_with_sort_keys( Collator $coll, array(string) $arr )
353 * Equivalent to standard PHP sort using Collator.
354 * Uses ICU ucol_getSortKey for performance.
355 */
PHP_FUNCTION(collator_sort_with_sort_keys)356 PHP_FUNCTION( collator_sort_with_sort_keys )
357 {
358 zval* array = NULL;
359 HashTable* hash = NULL;
360 zval** hashData = NULL; /* currently processed item of input hash */
361
362 char* sortKeyBuf = NULL; /* buffer to store sort keys */
363 uint32_t sortKeyBufSize = DEF_SORT_KEYS_BUF_SIZE; /* buffer size */
364 ptrdiff_t sortKeyBufOffset = 0; /* pos in buffer to store sort key */
365 int32_t sortKeyLen = 0; /* the length of currently processing key */
366 uint32_t bufLeft = 0;
367 uint32_t bufIncrement = 0;
368
369 collator_sort_key_index_t* sortKeyIndxBuf = NULL; /* buffer to store 'indexes' which will be passed to 'qsort' */
370 uint32_t sortKeyIndxBufSize = DEF_SORT_KEYS_INDX_BUF_SIZE;
371 uint32_t sortKeyIndxSize = sizeof( collator_sort_key_index_t );
372
373 uint32_t sortKeyCount = 0;
374 uint32_t j = 0;
375
376 UChar* utf16_buf = NULL; /* tmp buffer to hold current processing string in utf-16 */
377 int utf16_buf_size = DEF_UTF16_BUF_SIZE; /* the length of utf16_buf */
378 int utf16_len = 0; /* length of converted string */
379
380 HashTable* sortedHash = NULL;
381
382 COLLATOR_METHOD_INIT_VARS
383
384 /* Parse parameters. */
385 if( zend_parse_method_parameters( ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa",
386 &object, Collator_ce_ptr, &array ) == FAILURE )
387 {
388 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
389 "collator_sort_with_sort_keys: unable to parse input params", 0 TSRMLS_CC );
390
391 RETURN_FALSE;
392 }
393
394 /* Fetch the object. */
395 COLLATOR_METHOD_FETCH_OBJECT;
396
397 if (!co || !co->ucoll) {
398 intl_error_set_code( NULL, COLLATOR_ERROR_CODE( co ) TSRMLS_CC );
399 intl_errors_set_custom_msg( COLLATOR_ERROR_P( co ),
400 "Object not initialized", 0 TSRMLS_CC );
401 php_error_docref(NULL TSRMLS_CC, E_RECOVERABLE_ERROR, "Object not initialized");
402
403 RETURN_FALSE;
404 }
405
406 /*
407 * Sort specified array.
408 */
409 hash = HASH_OF( array );
410
411 if( !hash || zend_hash_num_elements( hash ) == 0 )
412 RETURN_TRUE;
413
414 /* Create bufers */
415 sortKeyBuf = ecalloc( sortKeyBufSize, sizeof( char ) );
416 sortKeyIndxBuf = ecalloc( sortKeyIndxBufSize, sizeof( uint8_t ) );
417 utf16_buf = eumalloc( utf16_buf_size );
418
419 /* Iterate through input hash and create a sort key for each value. */
420 zend_hash_internal_pointer_reset( hash );
421 while( zend_hash_get_current_data( hash, (void**) &hashData ) == SUCCESS )
422 {
423 /* Convert current hash item from UTF-8 to UTF-16LE and save the result to utf16_buf. */
424
425 utf16_len = utf16_buf_size;
426
427 /* Process string values only. */
428 if( Z_TYPE_PP( hashData ) == IS_STRING )
429 {
430 intl_convert_utf8_to_utf16( &utf16_buf, &utf16_len, Z_STRVAL_PP( hashData ), Z_STRLEN_PP( hashData ), COLLATOR_ERROR_CODE_P( co ) );
431
432 if( U_FAILURE( COLLATOR_ERROR_CODE( co ) ) )
433 {
434 intl_error_set_code( NULL, COLLATOR_ERROR_CODE( co ) TSRMLS_CC );
435 intl_errors_set_custom_msg( COLLATOR_ERROR_P( co ), "Sort with sort keys failed", 0 TSRMLS_CC );
436
437 if( utf16_buf )
438 efree( utf16_buf );
439
440 efree( sortKeyIndxBuf );
441 efree( sortKeyBuf );
442
443 RETURN_FALSE;
444 }
445 }
446 else
447 {
448 /* Set empty string */
449 utf16_len = 0;
450 utf16_buf[utf16_len] = 0;
451 }
452
453 if( (utf16_len + 1) > utf16_buf_size )
454 utf16_buf_size = utf16_len + 1;
455
456 /* Get sort key, reallocating the buffer if needed. */
457 bufLeft = sortKeyBufSize - sortKeyBufOffset;
458
459 sortKeyLen = ucol_getSortKey( co->ucoll,
460 utf16_buf,
461 utf16_len,
462 (uint8_t*)sortKeyBuf + sortKeyBufOffset,
463 bufLeft );
464
465 /* check for sortKeyBuf overflow, increasing its size of the buffer if needed */
466 if( sortKeyLen > bufLeft )
467 {
468 bufIncrement = ( sortKeyLen > DEF_SORT_KEYS_BUF_INCREMENT ) ? sortKeyLen : DEF_SORT_KEYS_BUF_INCREMENT;
469
470 sortKeyBufSize += bufIncrement;
471 bufLeft += bufIncrement;
472
473 sortKeyBuf = erealloc( sortKeyBuf, sortKeyBufSize );
474
475 sortKeyLen = ucol_getSortKey( co->ucoll, utf16_buf, utf16_len, (uint8_t*)sortKeyBuf + sortKeyBufOffset, bufLeft );
476 }
477
478 /* check sortKeyIndxBuf overflow, increasing its size of the buffer if needed */
479 if( ( sortKeyCount + 1 ) * sortKeyIndxSize > sortKeyIndxBufSize )
480 {
481 bufIncrement = ( sortKeyIndxSize > DEF_SORT_KEYS_INDX_BUF_INCREMENT ) ? sortKeyIndxSize : DEF_SORT_KEYS_INDX_BUF_INCREMENT;
482
483 sortKeyIndxBufSize += bufIncrement;
484
485 sortKeyIndxBuf = erealloc( sortKeyIndxBuf, sortKeyIndxBufSize );
486 }
487
488 sortKeyIndxBuf[sortKeyCount].key = (char*)sortKeyBufOffset; /* remeber just offset, cause address */
489 /* of 'sortKeyBuf' may be changed due to realloc. */
490 sortKeyIndxBuf[sortKeyCount].zstr = hashData;
491
492 sortKeyBufOffset += sortKeyLen;
493 ++sortKeyCount;
494
495 zend_hash_move_forward( hash );
496 }
497
498 /* update ptrs to point to valid keys. */
499 for( j = 0; j < sortKeyCount; j++ )
500 sortKeyIndxBuf[j].key = sortKeyBuf + (ptrdiff_t)sortKeyIndxBuf[j].key;
501
502 /* sort it */
503 zend_qsort( sortKeyIndxBuf, sortKeyCount, sortKeyIndxSize, collator_cmp_sort_keys TSRMLS_CC );
504
505 /* for resulting hash we'll assign new hash keys rather then reordering */
506 ALLOC_HASHTABLE( sortedHash );
507 zend_hash_init( sortedHash, 0, NULL, ZVAL_PTR_DTOR, 0 );
508
509 for( j = 0; j < sortKeyCount; j++ )
510 {
511 zval_add_ref( sortKeyIndxBuf[j].zstr );
512 zend_hash_next_index_insert( sortedHash, sortKeyIndxBuf[j].zstr, sizeof(zval **), NULL );
513 }
514
515 /* Save sorted hash into return variable. */
516 zval_dtor( array );
517 (array)->value.ht = sortedHash;
518 (array)->type = IS_ARRAY;
519
520 if( utf16_buf )
521 efree( utf16_buf );
522
523 efree( sortKeyIndxBuf );
524 efree( sortKeyBuf );
525
526 RETURN_TRUE;
527 }
528 /* }}} */
529
530 /* {{{ proto bool Collator::asort( Collator $coll, array(string) $arr )
531 * Sort array using specified collator, maintaining index association. }}} */
532 /* {{{ proto bool collator_asort( Collator $coll, array(string) $arr )
533 * Sort array using specified collator, maintaining index association.
534 */
PHP_FUNCTION(collator_asort)535 PHP_FUNCTION( collator_asort )
536 {
537 collator_sort_internal( FALSE, INTERNAL_FUNCTION_PARAM_PASSTHRU );
538 }
539 /* }}} */
540
541 /* {{{ proto bool Collator::getSortKey( Collator $coll, string $str )
542 * Get a sort key for a string from a Collator. }}} */
543 /* {{{ proto bool collator_get_sort_key( Collator $coll, string $str )
544 * Get a sort key for a string from a Collator. }}} */
PHP_FUNCTION(collator_get_sort_key)545 PHP_FUNCTION( collator_get_sort_key )
546 {
547 char* str = NULL;
548 int str_len = 0;
549 UChar* ustr = NULL;
550 int ustr_len = 0;
551 uint8_t* key = NULL;
552 int key_len = 0;
553
554 COLLATOR_METHOD_INIT_VARS
555
556 /* Parse parameters. */
557 if( zend_parse_method_parameters( ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os",
558 &object, Collator_ce_ptr, &str, &str_len ) == FAILURE )
559 {
560 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
561 "collator_get_sort_key: unable to parse input params", 0 TSRMLS_CC );
562
563 RETURN_FALSE;
564 }
565
566 /* Fetch the object. */
567 COLLATOR_METHOD_FETCH_OBJECT;
568
569 if (!co || !co->ucoll) {
570 intl_error_set_code( NULL, COLLATOR_ERROR_CODE( co ) TSRMLS_CC );
571 intl_errors_set_custom_msg( COLLATOR_ERROR_P( co ),
572 "Object not initialized", 0 TSRMLS_CC );
573 php_error_docref(NULL TSRMLS_CC, E_RECOVERABLE_ERROR, "Object not initialized");
574
575 RETURN_FALSE;
576 }
577
578 /*
579 * Compare given strings (converting them to UTF-16 first).
580 */
581
582 /* First convert the strings to UTF-16. */
583 intl_convert_utf8_to_utf16(
584 &ustr, &ustr_len, str, str_len, COLLATOR_ERROR_CODE_P( co ) );
585 if( U_FAILURE( COLLATOR_ERROR_CODE( co ) ) )
586 {
587 /* Set global error code. */
588 intl_error_set_code( NULL, COLLATOR_ERROR_CODE( co ) TSRMLS_CC );
589
590 /* Set error messages. */
591 intl_errors_set_custom_msg( COLLATOR_ERROR_P( co ),
592 "Error converting first argument to UTF-16", 0 TSRMLS_CC );
593 efree( ustr );
594 RETURN_FALSE;
595 }
596
597 /* ucol_getSortKey is exception in that the key length includes the
598 * NUL terminator*/
599 key_len = ucol_getSortKey(co->ucoll, ustr, ustr_len, key, 0);
600 if(!key_len) {
601 efree( ustr );
602 RETURN_FALSE;
603 }
604 key = emalloc(key_len);
605 key_len = ucol_getSortKey(co->ucoll, ustr, ustr_len, key, key_len);
606 efree( ustr );
607 if(!key_len) {
608 RETURN_FALSE;
609 }
610 RETURN_STRINGL((char *)key, key_len - 1, 0);
611 }
612 /* }}} */
613
614 /*
615 * Local variables:
616 * tab-width: 4
617 * c-basic-offset: 4
618 * End:
619 * vim600: noet sw=4 ts=4 fdm=marker
620 * vim<600: noet sw=4 ts=4
621 */
622