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