1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
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: Kirti Velankar <kirtig@yahoo-inc.com>                       |
14   +----------------------------------------------------------------------+
15*/
16
17/* $Id$ */
18
19#ifdef HAVE_CONFIG_H
20#include "config.h"
21#endif
22
23#include <unicode/ustring.h>
24#include <unicode/udata.h>
25#include <unicode/putil.h>
26#include <unicode/ures.h>
27
28#include "php_intl.h"
29#include "locale.h"
30#include "locale_class.h"
31#include "locale_methods.h"
32#include "intl_convert.h"
33#include "intl_data.h"
34
35#include <zend_API.h>
36#include <zend.h>
37#include <php.h>
38#include "main/php_ini.h"
39#include "zend_smart_str.h"
40
41ZEND_EXTERN_MODULE_GLOBALS( intl )
42
43/* Sizes required for the strings "variant15" , "extlang11", "private12" etc. */
44#define SEPARATOR "_"
45#define SEPARATOR1 "-"
46#define DELIMITER "-_"
47#define EXTLANG_PREFIX "a"
48#define PRIVATE_PREFIX "x"
49#define DISP_NAME "name"
50
51#define MAX_NO_VARIANT  15
52#define MAX_NO_EXTLANG  3
53#define MAX_NO_PRIVATE  15
54#define MAX_NO_LOOKUP_LANG_TAG  100
55
56#define LOC_NOT_FOUND 1
57
58/* Sizes required for the strings "variant15" , "extlang3", "private12" etc. */
59#define VARIANT_KEYNAME_LEN  11
60#define EXTLANG_KEYNAME_LEN  10
61#define PRIVATE_KEYNAME_LEN  11
62
63/* Based on IANA registry at the time of writing this code
64*
65*/
66static const char * const LOC_GRANDFATHERED[] = {
67    "art-lojban",       "i-klingon",        "i-lux",            "i-navajo",     "no-bok",       "no-nyn",
68    "cel-gaulish",      "en-GB-oed",        "i-ami",
69    "i-bnn",        "i-default",        "i-enochian",
70    "i-mingo",      "i-pwn",        "i-tao",
71    "i-tay",        "i-tsu",        "sgn-BE-fr",
72    "sgn-BE-nl",        "sgn-CH-de",        "zh-cmn",
73    "zh-cmn-Hans",      "zh-cmn-Hant",      "zh-gan" ,
74    "zh-guoyu",         "zh-hakka",         "zh-min",
75    "zh-min-nan",       "zh-wuu",       "zh-xiang",
76    "zh-yue",       NULL
77};
78
79/* Based on IANA registry at the time of writing this code
80*  This array lists the preferred values for the grandfathered tags if applicable
81*  This is in sync with the array LOC_GRANDFATHERED
82*  e.g. the offsets of the grandfathered tags match the offset of the preferred  value
83*/
84static const int        LOC_PREFERRED_GRANDFATHERED_LEN = 6;
85static const char * const   LOC_PREFERRED_GRANDFATHERED[]  = {
86    "jbo",          "tlh",          "lb",
87    "nv",           "nb",           "nn",
88    NULL
89};
90
91/*returns TRUE if a is an ID separator FALSE otherwise*/
92#define isIDSeparator(a) (a == '_' || a == '-')
93#define isKeywordSeparator(a) (a == '@' )
94#define isEndOfTag(a) (a == '\0' )
95
96#define isPrefixLetter(a) ((a=='x')||(a=='X')||(a=='i')||(a=='I'))
97
98/*returns TRUE if one of the special prefixes is here (s=string)
99  'x-' or 'i-' */
100#define isIDPrefix(s) (isPrefixLetter(s[0])&&isIDSeparator(s[1]))
101#define isKeywordPrefix(s) ( isKeywordSeparator(s[0]) )
102
103/* Dot terminates it because of POSIX form  where dot precedes the codepage
104 * except for variant */
105#define isTerminator(a)  ((a==0)||(a=='.')||(a=='@'))
106
107/* {{{ return the offset of 'key' in the array 'list'.
108 * returns -1 if not present */
109static int16_t findOffset(const char* const* list, const char* key)
110{
111    const char* const* anchor = list;
112    while (*list != NULL) {
113        if (strcmp(key, *list) == 0) {
114            return (int16_t)(list - anchor);
115        }
116        list++;
117    }
118
119    return -1;
120
121}
122/*}}}*/
123
124static char* getPreferredTag(const char* gf_tag)
125{
126    char* result = NULL;
127    int grOffset = 0;
128
129    grOffset = findOffset( LOC_GRANDFATHERED ,gf_tag);
130    if(grOffset < 0) {
131        return NULL;
132    }
133    if( grOffset < LOC_PREFERRED_GRANDFATHERED_LEN ){
134        /* return preferred tag */
135        result = estrdup( LOC_PREFERRED_GRANDFATHERED[grOffset] );
136    } else {
137        /* Return correct grandfathered language tag */
138        result = estrdup( LOC_GRANDFATHERED[grOffset] );
139    }
140    return result;
141}
142
143/* {{{
144* returns the position of next token for lookup
145* or -1 if no token
146* strtokr equivalent search for token in reverse direction
147*/
148static int getStrrtokenPos(char* str, int savedPos)
149{
150    int result =-1;
151    int i;
152
153    for(i=savedPos-1; i>=0; i--) {
154        if(isIDSeparator(*(str+i)) ){
155            /* delimiter found; check for singleton */
156            if(i>=2 && isIDSeparator(*(str+i-2)) ){
157                /* a singleton; so send the position of token before the singleton */
158                result = i-2;
159            } else {
160                result = i;
161            }
162            break;
163        }
164    }
165    if(result < 1){
166        /* Just in case inavlid locale e.g. '-x-xyz' or '-sl_Latn' */
167        result =-1;
168    }
169    return result;
170}
171/* }}} */
172
173/* {{{
174* returns the position of a singleton if present
175* returns -1 if no singleton
176* strtok equivalent search for singleton
177*/
178static int getSingletonPos(const char* str)
179{
180    int result =-1;
181    int i=0;
182    int len = 0;
183
184    if( str && ((len=strlen(str))>0) ){
185        for( i=0; i<len ; i++){
186            if( isIDSeparator(*(str+i)) ){
187                if( i==1){
188                    /* string is of the form x-avy or a-prv1 */
189                    result =0;
190                    break;
191                } else {
192                    /* delimiter found; check for singleton */
193                    if( isIDSeparator(*(str+i+2)) ){
194                        /* a singleton; so send the position of separator before singleton */
195                        result = i+1;
196                        break;
197                    }
198                }
199            }
200        }/* end of for */
201
202    }
203    return result;
204}
205/* }}} */
206
207/* {{{ proto static string Locale::getDefault(  )
208   Get default locale */
209/* }}} */
210/* {{{ proto static string locale_get_default( )
211   Get default locale */
212PHP_NAMED_FUNCTION(zif_locale_get_default)
213{
214    RETURN_STRING( intl_locale_get_default(  ) );
215}
216
217/* }}} */
218
219/* {{{ proto static string Locale::setDefault( string $locale )
220   Set default locale */
221/* }}} */
222/* {{{ proto static string locale_set_default( string $locale )
223   Set default locale */
224PHP_NAMED_FUNCTION(zif_locale_set_default)
225{
226    zend_string* locale_name;
227    zend_string *ini_name;
228    char *default_locale = NULL;
229
230    if(zend_parse_parameters( ZEND_NUM_ARGS(),  "S", &locale_name) == FAILURE)
231    {
232        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
233                "locale_set_default: unable to parse input params", 0 );
234
235        RETURN_FALSE;
236    }
237
238    if (ZSTR_LEN(locale_name) == 0) {
239        default_locale = (char *)uloc_getDefault();
240        locale_name = zend_string_init(default_locale, strlen(default_locale), 0);
241    }
242
243    ini_name = zend_string_init(LOCALE_INI_NAME, sizeof(LOCALE_INI_NAME) - 1, 0);
244    zend_alter_ini_entry(ini_name, locale_name, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
245    zend_string_release(ini_name);
246    if (default_locale != NULL) {
247        zend_string_release(locale_name);
248    }
249
250    RETURN_TRUE;
251}
252/* }}} */
253
254/* {{{
255* Gets the value from ICU
256* common code shared by get_primary_language,get_script or get_region or get_variant
257* result = 0 if error, 1 if successful , -1 if no value
258*/
259static zend_string* get_icu_value_internal( const char* loc_name , char* tag_name, int* result , int fromParseLocale)
260{
261    zend_string*    tag_value   = NULL;
262    int32_t         tag_value_len   = 512;
263
264    int     singletonPos    = 0;
265    char*           mod_loc_name    = NULL;
266    int         grOffset    = 0;
267
268    int32_t         buflen          = 512;
269    UErrorCode      status          = U_ZERO_ERROR;
270
271
272    if( strcmp(tag_name, LOC_CANONICALIZE_TAG) != 0 ){
273        /* Handle  grandfathered languages */
274        grOffset =  findOffset( LOC_GRANDFATHERED , loc_name );
275        if( grOffset >= 0 ){
276            if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
277                return zend_string_init(loc_name, strlen(loc_name), 0);
278            } else {
279                /* Since Grandfathered , no value , do nothing , retutn NULL */
280                return NULL;
281            }
282        }
283
284    if( fromParseLocale==1 ){
285        /* Handle singletons */
286        if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
287            if( strlen(loc_name)>1 && (isIDPrefix(loc_name) == 1) ){
288                return zend_string_init(loc_name, strlen(loc_name), 0);
289            }
290        }
291
292        singletonPos = getSingletonPos( loc_name );
293        if( singletonPos == 0){
294            /* singleton at start of script, region , variant etc.
295             * or invalid singleton at start of language */
296            return NULL;
297        } else if( singletonPos > 0 ){
298            /* singleton at some position except at start
299             * strip off the singleton and rest of the loc_name */
300            mod_loc_name = estrndup ( loc_name , singletonPos-1);
301        }
302    } /* end of if fromParse */
303
304    } /* end of if != LOC_CANONICAL_TAG */
305
306    if( mod_loc_name == NULL){
307        mod_loc_name = estrdup(loc_name );
308    }
309
310    /* Proceed to ICU */
311    do{
312        if (tag_value) {
313            tag_value = zend_string_realloc( tag_value , buflen, 0);
314        } else {
315            tag_value = zend_string_alloc( buflen, 0);
316        }
317        tag_value_len = buflen;
318
319        if( strcmp(tag_name , LOC_SCRIPT_TAG)==0 ){
320            buflen = uloc_getScript ( mod_loc_name , tag_value->val , tag_value_len , &status);
321        }
322        if( strcmp(tag_name , LOC_LANG_TAG )==0 ){
323            buflen = uloc_getLanguage ( mod_loc_name , tag_value->val , tag_value_len , &status);
324        }
325        if( strcmp(tag_name , LOC_REGION_TAG)==0 ){
326            buflen = uloc_getCountry ( mod_loc_name , tag_value->val , tag_value_len , &status);
327        }
328        if( strcmp(tag_name , LOC_VARIANT_TAG)==0 ){
329            buflen = uloc_getVariant ( mod_loc_name , tag_value->val , tag_value_len , &status);
330        }
331        if( strcmp(tag_name , LOC_CANONICALIZE_TAG)==0 ){
332            buflen = uloc_canonicalize ( mod_loc_name , tag_value->val , tag_value_len , &status);
333        }
334
335        if( U_FAILURE( status ) ) {
336            if( status == U_BUFFER_OVERFLOW_ERROR ) {
337                status = U_ZERO_ERROR;
338                continue;
339            }
340
341            /* Error in retriving data */
342            *result = 0;
343            if( tag_value ){
344                zend_string_release( tag_value );
345            }
346            if( mod_loc_name ){
347                efree( mod_loc_name);
348            }
349            return NULL;
350        }
351    } while( buflen > tag_value_len );
352
353    if(  buflen ==0 ){
354        /* No value found */
355        *result = -1;
356        if( tag_value ){
357            zend_string_release( tag_value );
358        }
359        if( mod_loc_name ){
360            efree( mod_loc_name);
361        }
362        return NULL;
363    } else {
364        *result = 1;
365    }
366
367    if( mod_loc_name ){
368        efree( mod_loc_name);
369    }
370
371    tag_value->len = strlen(tag_value->val);
372    return tag_value;
373}
374/* }}} */
375
376/* {{{
377* Gets the value from ICU , called when PHP userspace function is called
378* common code shared by get_primary_language,get_script or get_region or get_variant
379*/
380static void get_icu_value_src_php( char* tag_name, INTERNAL_FUNCTION_PARAMETERS)
381{
382
383    const char* loc_name            = NULL;
384    size_t         loc_name_len     = 0;
385
386    zend_string*   tag_value        = NULL;
387    char*       empty_result    = "";
388
389    int         result          = 0;
390    char*       msg             = NULL;
391
392    UErrorCode  status              = U_ZERO_ERROR;
393
394    intl_error_reset( NULL );
395
396    if(zend_parse_parameters( ZEND_NUM_ARGS(), "s",
397    &loc_name ,&loc_name_len ) == FAILURE) {
398        spprintf(&msg , 0, "locale_get_%s : unable to parse input params", tag_name );
399        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,  msg , 1 );
400        efree(msg);
401
402        RETURN_FALSE;
403    }
404
405    if(loc_name_len == 0) {
406        loc_name = intl_locale_get_default();
407    }
408
409    /* Call ICU get */
410    tag_value = get_icu_value_internal( loc_name , tag_name , &result ,0);
411
412    /* No value found */
413    if( result == -1 ) {
414        if( tag_value){
415            zend_string_release( tag_value);
416        }
417        RETURN_STRING( empty_result);
418    }
419
420    /* value found */
421    if( tag_value){
422        RETVAL_STR( tag_value );
423        return;
424    }
425
426    /* Error encountered while fetching the value */
427    if( result ==0) {
428        spprintf(&msg , 0, "locale_get_%s : unable to get locale %s", tag_name , tag_name );
429        intl_error_set( NULL, status, msg , 1 );
430        efree(msg);
431        RETURN_NULL();
432    }
433
434}
435/* }}} */
436
437/* {{{ proto static string Locale::getScript($locale)
438 * gets the script for the $locale
439 }}} */
440/* {{{ proto static string locale_get_script($locale)
441 * gets the script for the $locale
442 */
443PHP_FUNCTION( locale_get_script )
444{
445    get_icu_value_src_php( LOC_SCRIPT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
446}
447/* }}} */
448
449/* {{{ proto static string Locale::getRegion($locale)
450 * gets the region for the $locale
451 }}} */
452/* {{{ proto static string locale_get_region($locale)
453 * gets the region for the $locale
454 */
455PHP_FUNCTION( locale_get_region )
456{
457    get_icu_value_src_php( LOC_REGION_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
458}
459/* }}} */
460
461/* {{{ proto static string Locale::getPrimaryLanguage($locale)
462 * gets the primary language for the $locale
463 }}} */
464/* {{{ proto static string locale_get_primary_language($locale)
465 * gets the primary language for the $locale
466 */
467PHP_FUNCTION(locale_get_primary_language )
468{
469    get_icu_value_src_php( LOC_LANG_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
470}
471/* }}} */
472
473
474/* {{{
475 * common code shared by display_xyz functions to  get the value from ICU
476 }}} */
477static void get_icu_disp_value_src_php( char* tag_name, INTERNAL_FUNCTION_PARAMETERS)
478{
479    const char* loc_name            = NULL;
480    size_t         loc_name_len     = 0;
481
482    const char* disp_loc_name       = NULL;
483    size_t      disp_loc_name_len   = 0;
484    int         free_loc_name       = 0;
485
486    UChar*      disp_name       = NULL;
487    int32_t     disp_name_len   = 0;
488
489    char*       mod_loc_name        = NULL;
490
491    int32_t     buflen              = 512;
492    UErrorCode  status              = U_ZERO_ERROR;
493
494    zend_string* u8str;
495
496    char*       msg                 = NULL;
497    int         grOffset        = 0;
498
499    intl_error_reset( NULL );
500
501    if(zend_parse_parameters( ZEND_NUM_ARGS(), "s|s",
502        &loc_name, &loc_name_len ,
503        &disp_loc_name ,&disp_loc_name_len ) == FAILURE)
504    {
505        spprintf(&msg , 0, "locale_get_display_%s : unable to parse input params", tag_name );
506        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,  msg , 1 );
507        efree(msg);
508        RETURN_FALSE;
509    }
510
511    if(loc_name_len > ULOC_FULLNAME_CAPACITY) {
512        /* See bug 67397: overlong locale names cause trouble in uloc_getDisplayName */
513        spprintf(&msg , 0, "locale_get_display_%s : name too long", tag_name );
514        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,  msg , 1 );
515        efree(msg);
516        RETURN_FALSE;
517    }
518
519    if(loc_name_len == 0) {
520        loc_name = intl_locale_get_default();
521    }
522
523    if( strcmp(tag_name, DISP_NAME) != 0 ){
524        /* Handle grandfathered languages */
525        grOffset = findOffset( LOC_GRANDFATHERED , loc_name );
526        if( grOffset >= 0 ){
527            if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
528                mod_loc_name = getPreferredTag( loc_name );
529            } else {
530                /* Since Grandfathered, no value, do nothing, retutn NULL */
531                RETURN_FALSE;
532            }
533        }
534    } /* end of if != LOC_CANONICAL_TAG */
535
536    if( mod_loc_name==NULL ){
537        mod_loc_name = estrdup( loc_name );
538    }
539
540    /* Check if disp_loc_name passed , if not use default locale */
541    if( !disp_loc_name){
542        disp_loc_name = estrdup(intl_locale_get_default());
543        free_loc_name = 1;
544    }
545
546    /* Get the disp_value for the given locale */
547    do{
548        disp_name = erealloc( disp_name , buflen * sizeof(UChar)  );
549        disp_name_len = buflen;
550
551        if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
552            buflen = uloc_getDisplayLanguage ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
553        } else if( strcmp(tag_name , LOC_SCRIPT_TAG)==0 ){
554            buflen = uloc_getDisplayScript ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
555        } else if( strcmp(tag_name , LOC_REGION_TAG)==0 ){
556            buflen = uloc_getDisplayCountry ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
557        } else if( strcmp(tag_name , LOC_VARIANT_TAG)==0 ){
558            buflen = uloc_getDisplayVariant ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
559        } else if( strcmp(tag_name , DISP_NAME)==0 ){
560            buflen = uloc_getDisplayName ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
561        }
562
563        /* U_STRING_NOT_TERMINATED_WARNING is admissible here; don't look for it */
564        if( U_FAILURE( status ) )
565        {
566            if( status == U_BUFFER_OVERFLOW_ERROR )
567            {
568                status = U_ZERO_ERROR;
569                continue;
570            }
571
572            spprintf(&msg, 0, "locale_get_display_%s : unable to get locale %s", tag_name , tag_name );
573            intl_error_set( NULL, status, msg , 1 );
574            efree(msg);
575            if( disp_name){
576                efree( disp_name );
577            }
578            if( mod_loc_name){
579                efree( mod_loc_name );
580            }
581            if (free_loc_name) {
582                efree((void *)disp_loc_name);
583                disp_loc_name = NULL;
584            }
585            RETURN_FALSE;
586        }
587    } while( buflen > disp_name_len );
588
589    if( mod_loc_name){
590        efree( mod_loc_name );
591    }
592    if (free_loc_name) {
593        efree((void *)disp_loc_name);
594        disp_loc_name = NULL;
595    }
596    /* Convert display locale name from UTF-16 to UTF-8. */
597    u8str = intl_convert_utf16_to_utf8(disp_name, buflen, &status );
598    efree( disp_name );
599    if( !u8str )
600    {
601        spprintf(&msg, 0, "locale_get_display_%s :error converting display name for %s to UTF-8", tag_name , tag_name );
602        intl_error_set( NULL, status, msg , 1 );
603        efree(msg);
604        RETURN_FALSE;
605    }
606
607    RETVAL_NEW_STR( u8str );
608}
609/* }}} */
610
611/* {{{ proto static string Locale::getDisplayName($locale[, $in_locale = null])
612* gets the name for the $locale in $in_locale or default_locale
613 }}} */
614/* {{{ proto static string get_display_name($locale[, $in_locale = null])
615* gets the name for the $locale in $in_locale or default_locale
616*/
617PHP_FUNCTION(locale_get_display_name)
618{
619    get_icu_disp_value_src_php( DISP_NAME , INTERNAL_FUNCTION_PARAM_PASSTHRU );
620}
621/* }}} */
622
623/* {{{ proto static string Locale::getDisplayLanguage($locale[, $in_locale = null])
624* gets the language for the $locale in $in_locale or default_locale
625 }}} */
626/* {{{ proto static string get_display_language($locale[, $in_locale = null])
627* gets the language for the $locale in $in_locale or default_locale
628*/
629PHP_FUNCTION(locale_get_display_language)
630{
631    get_icu_disp_value_src_php( LOC_LANG_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
632}
633/* }}} */
634
635/* {{{ proto static string Locale::getDisplayScript($locale, $in_locale = null)
636* gets the script for the $locale in $in_locale or default_locale
637 }}} */
638/* {{{ proto static string get_display_script($locale, $in_locale = null)
639* gets the script for the $locale in $in_locale or default_locale
640*/
641PHP_FUNCTION(locale_get_display_script)
642{
643    get_icu_disp_value_src_php( LOC_SCRIPT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
644}
645/* }}} */
646
647/* {{{ proto static string Locale::getDisplayRegion($locale, $in_locale = null)
648* gets the region for the $locale in $in_locale or default_locale
649 }}} */
650/* {{{ proto static string get_display_region($locale, $in_locale = null)
651* gets the region for the $locale in $in_locale or default_locale
652*/
653PHP_FUNCTION(locale_get_display_region)
654{
655    get_icu_disp_value_src_php( LOC_REGION_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
656}
657/* }}} */
658
659/* {{{
660* proto static string Locale::getDisplayVariant($locale, $in_locale = null)
661* gets the variant for the $locale in $in_locale or default_locale
662 }}} */
663/* {{{
664* proto static string get_display_variant($locale, $in_locale = null)
665* gets the variant for the $locale in $in_locale or default_locale
666*/
667PHP_FUNCTION(locale_get_display_variant)
668{
669    get_icu_disp_value_src_php( LOC_VARIANT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
670}
671/* }}} */
672
673 /* {{{ proto static array getKeywords(string $locale) {
674 * return an associative array containing keyword-value
675 * pairs for this locale. The keys are keys to the array (doh!)
676 * }}}*/
677 /* {{{ proto static array locale_get_keywords(string $locale) {
678 * return an associative array containing keyword-value
679 * pairs for this locale. The keys are keys to the array (doh!)
680 */
681PHP_FUNCTION( locale_get_keywords )
682{
683    UEnumeration*   e        = NULL;
684    UErrorCode      status   = U_ZERO_ERROR;
685
686    const char*     kw_key        = NULL;
687    int32_t         kw_key_len    = 0;
688
689    const char*         loc_name        = NULL;
690    size_t              loc_name_len    = 0;
691
692/*
693    ICU expects the buffer to be allocated  before calling the function
694    and so the buffer size has been explicitly specified
695    ICU uloc.h #define  ULOC_KEYWORD_AND_VALUES_CAPACITY   100
696    hence the kw_value buffer size is 100
697*/
698    zend_string *kw_value_str;
699    int32_t     kw_value_len = 100;
700
701    intl_error_reset( NULL );
702
703    if(zend_parse_parameters( ZEND_NUM_ARGS(), "s",
704        &loc_name, &loc_name_len ) == FAILURE)
705    {
706        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
707             "locale_get_keywords: unable to parse input params", 0 );
708
709        RETURN_FALSE;
710    }
711
712    if(loc_name_len == 0) {
713        loc_name = intl_locale_get_default();
714    }
715
716    /* Get the keywords */
717    e = uloc_openKeywords( loc_name, &status );
718    if( e != NULL )
719    {
720        /* Traverse it, filling the return array. */
721        array_init( return_value );
722
723        while( ( kw_key = uenum_next( e, &kw_key_len, &status ) ) != NULL ){
724            kw_value_len = 100;
725            kw_value_str = zend_string_alloc(kw_value_len, 0);
726
727            /* Get the keyword value for each keyword */
728            kw_value_len=uloc_getKeywordValue( loc_name, kw_key, ZSTR_VAL(kw_value_str), kw_value_len, &status );
729            if (status == U_BUFFER_OVERFLOW_ERROR) {
730                status = U_ZERO_ERROR;
731                kw_value_str = zend_string_extend(kw_value_str, kw_value_len, 0);
732                kw_value_len=uloc_getKeywordValue( loc_name,kw_key, ZSTR_VAL(kw_value_str), kw_value_len+1, &status );
733            } else if(!U_FAILURE(status)) {
734                kw_value_str = zend_string_truncate(kw_value_str, kw_value_len, 0);
735            }
736            if (U_FAILURE(status)) {
737                intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "locale_get_keywords: Error encountered while getting the keyword  value for the  keyword", 0 );
738                if( kw_value_str){
739                    zend_string_free( kw_value_str );
740                }
741                zval_dtor(return_value);
742                RETURN_FALSE;
743            }
744
745            add_assoc_str( return_value, (char *)kw_key, kw_value_str);
746        } /* end of while */
747
748    } /* end of if e!=NULL */
749
750    uenum_close( e );
751}
752/* }}} */
753
754 /* {{{ proto static string Locale::canonicalize($locale)
755 * @return string the canonicalized locale
756 * }}} */
757 /* {{{ proto static string locale_canonicalize(Locale $loc, string $locale)
758 * @param string $locale    The locale string to canonicalize
759 */
760PHP_FUNCTION(locale_canonicalize)
761{
762    get_icu_value_src_php( LOC_CANONICALIZE_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
763}
764/* }}} */
765
766/* {{{ append_key_value
767* Internal function which is called from locale_compose
768* gets the value for the key_name and appends to the loc_name
769* returns 1 if successful , -1 if not found ,
770* 0 if array element is not a string , -2 if buffer-overflow
771*/
772static int append_key_value(smart_str* loc_name, HashTable* hash_arr, char* key_name)
773{
774    zval *ele_value;
775
776    if ((ele_value = zend_hash_str_find(hash_arr , key_name, strlen(key_name))) != NULL ) {
777        if(Z_TYPE_P(ele_value)!= IS_STRING ){
778            /* element value is not a string */
779            return FAILURE;
780        }
781        if(strcmp(key_name, LOC_LANG_TAG) != 0 &&
782           strcmp(key_name, LOC_GRANDFATHERED_LANG_TAG)!=0 ) {
783            /* not lang or grandfathered tag */
784            smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
785        }
786        smart_str_appendl(loc_name, Z_STRVAL_P(ele_value) , Z_STRLEN_P(ele_value));
787        return SUCCESS;
788    }
789
790    return LOC_NOT_FOUND;
791}
792/* }}} */
793
794/* {{{ append_prefix , appends the prefix needed
795* e.g. private adds 'x'
796*/
797static void add_prefix(smart_str* loc_name, char* key_name)
798{
799    if( strncmp(key_name , LOC_PRIVATE_TAG , 7) == 0 ){
800        smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
801        smart_str_appendl(loc_name, PRIVATE_PREFIX , sizeof(PRIVATE_PREFIX)-1);
802    }
803}
804/* }}} */
805
806/* {{{ append_multiple_key_values
807* Internal function which is called from locale_compose
808* gets the multiple values for the key_name and appends to the loc_name
809* used for 'variant','extlang','private'
810* returns 1 if successful , -1 if not found ,
811* 0 if array element is not a string , -2 if buffer-overflow
812*/
813static int append_multiple_key_values(smart_str* loc_name, HashTable* hash_arr, char* key_name)
814{
815    zval    *ele_value;
816    int     i       = 0;
817    int     isFirstSubtag   = 0;
818    int     max_value   = 0;
819
820    /* Variant/ Extlang/Private etc. */
821    if ((ele_value = zend_hash_str_find( hash_arr , key_name , strlen(key_name))) != NULL) {
822        if( Z_TYPE_P(ele_value) == IS_STRING ){
823            add_prefix( loc_name , key_name);
824
825            smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
826            smart_str_appendl(loc_name, Z_STRVAL_P(ele_value) , Z_STRLEN_P(ele_value));
827            return SUCCESS;
828        } else if(Z_TYPE_P(ele_value) == IS_ARRAY ) {
829            HashTable *arr = Z_ARRVAL_P(ele_value);
830            zval *data;
831
832            ZEND_HASH_FOREACH_VAL(arr, data) {
833                if(Z_TYPE_P(data) != IS_STRING) {
834                    return FAILURE;
835                }
836                if (isFirstSubtag++ == 0){
837                    add_prefix(loc_name , key_name);
838                }
839                smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
840                smart_str_appendl(loc_name, Z_STRVAL_P(data) , Z_STRLEN_P(data));
841            } ZEND_HASH_FOREACH_END();
842            return SUCCESS;
843        } else {
844            return FAILURE;
845        }
846    } else {
847        char cur_key_name[31];
848        /* Decide the max_value: the max. no. of elements allowed */
849        if( strcmp(key_name , LOC_VARIANT_TAG) ==0 ){
850            max_value  = MAX_NO_VARIANT;
851        }
852        if( strcmp(key_name , LOC_EXTLANG_TAG) ==0 ){
853            max_value  = MAX_NO_EXTLANG;
854        }
855        if( strcmp(key_name , LOC_PRIVATE_TAG) ==0 ){
856            max_value  = MAX_NO_PRIVATE;
857        }
858
859        /* Multiple variant values as variant0, variant1 ,variant2 */
860        isFirstSubtag = 0;
861        for( i=0 ; i< max_value; i++ ){
862            snprintf( cur_key_name , 30, "%s%d", key_name , i);
863            if ((ele_value = zend_hash_str_find( hash_arr , cur_key_name , strlen(cur_key_name))) != NULL) {
864                if( Z_TYPE_P(ele_value)!= IS_STRING ){
865                    /* variant is not a string */
866                    return FAILURE;
867                }
868                /* Add the contents */
869                if (isFirstSubtag++ == 0){
870                    add_prefix(loc_name , cur_key_name);
871                }
872                smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
873                smart_str_appendl(loc_name, Z_STRVAL_P(ele_value) , Z_STRLEN_P(ele_value));
874            }
875        } /* end of for */
876    } /* end of else */
877
878    return SUCCESS;
879}
880/* }}} */
881
882/*{{{
883* If applicable sets error message and aborts locale_compose gracefully
884* returns 0  if locale_compose needs to be aborted
885* otherwise returns 1
886*/
887static int handleAppendResult( int result, smart_str* loc_name)
888{
889    intl_error_reset( NULL );
890    if( result == FAILURE) {
891        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
892             "locale_compose: parameter array element is not a string", 0 );
893        smart_str_free(loc_name);
894        return 0;
895    }
896    return 1;
897}
898/* }}} */
899
900#define RETURN_SMART_STR(str) smart_str_0((str)); RETURN_NEW_STR((str)->s)
901/* {{{ proto static string Locale::composeLocale($array)
902* Creates a locale by combining the parts of locale-ID passed
903* }}} */
904/* {{{ proto static string compose_locale($array)
905* Creates a locale by combining the parts of locale-ID passed
906* }}} */
907PHP_FUNCTION(locale_compose)
908{
909    smart_str       loc_name_s = {0};
910    smart_str *loc_name = &loc_name_s;
911    zval*           arr = NULL;
912    HashTable*      hash_arr = NULL;
913    int             result = 0;
914
915    intl_error_reset( NULL );
916
917    if(zend_parse_parameters( ZEND_NUM_ARGS(), "a",
918        &arr) == FAILURE)
919    {
920        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
921             "locale_compose: unable to parse input params", 0 );
922        RETURN_FALSE;
923    }
924
925    hash_arr = Z_ARRVAL_P( arr );
926
927    if( !hash_arr || zend_hash_num_elements( hash_arr ) == 0 )
928        RETURN_FALSE;
929
930    /* Check for grandfathered first */
931    result = append_key_value(loc_name, hash_arr,  LOC_GRANDFATHERED_LANG_TAG);
932    if( result == SUCCESS){
933        RETURN_SMART_STR(loc_name);
934    }
935    if( !handleAppendResult( result, loc_name)){
936        RETURN_FALSE;
937    }
938
939    /* Not grandfathered */
940    result = append_key_value(loc_name, hash_arr , LOC_LANG_TAG);
941    if( result == LOC_NOT_FOUND ){
942        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
943        "locale_compose: parameter array does not contain 'language' tag.", 0 );
944        smart_str_free(loc_name);
945        RETURN_FALSE;
946    }
947    if( !handleAppendResult( result, loc_name)){
948        RETURN_FALSE;
949    }
950
951    /* Extlang */
952    result = append_multiple_key_values(loc_name, hash_arr , LOC_EXTLANG_TAG);
953    if( !handleAppendResult( result, loc_name)){
954        RETURN_FALSE;
955    }
956
957    /* Script */
958    result = append_key_value(loc_name, hash_arr , LOC_SCRIPT_TAG);
959    if( !handleAppendResult( result, loc_name)){
960        RETURN_FALSE;
961    }
962
963    /* Region */
964    result = append_key_value( loc_name, hash_arr , LOC_REGION_TAG);
965    if( !handleAppendResult( result, loc_name)){
966        RETURN_FALSE;
967    }
968
969    /* Variant */
970    result = append_multiple_key_values( loc_name, hash_arr , LOC_VARIANT_TAG);
971    if( !handleAppendResult( result, loc_name)){
972        RETURN_FALSE;
973    }
974
975    /* Private */
976    result = append_multiple_key_values( loc_name, hash_arr , LOC_PRIVATE_TAG);
977    if( !handleAppendResult( result, loc_name)){
978        RETURN_FALSE;
979    }
980
981    RETURN_SMART_STR(loc_name);
982}
983/* }}} */
984
985
986/*{{{
987* Parses the locale and returns private subtags  if existing
988* else returns NULL
989* e.g. for locale='en_US-x-prv1-prv2-prv3'
990* returns a pointer to the string 'prv1-prv2-prv3'
991*/
992static zend_string* get_private_subtags(const char* loc_name)
993{
994    zend_string*    result =NULL;
995    int     singletonPos = 0;
996    int     len =0;
997    const char*     mod_loc_name =NULL;
998
999    if( loc_name && (len = strlen(loc_name)>0 ) ){
1000        mod_loc_name = loc_name ;
1001        len   = strlen(mod_loc_name);
1002        while( (singletonPos = getSingletonPos(mod_loc_name))!= -1){
1003
1004            if( singletonPos!=-1){
1005                if( (*(mod_loc_name+singletonPos)=='x') || (*(mod_loc_name+singletonPos)=='X') ){
1006                    /* private subtag start found */
1007                    if( singletonPos + 2 ==  len){
1008                        /* loc_name ends with '-x-' ; return  NULL */
1009                    }
1010                    else{
1011                        /* result = mod_loc_name + singletonPos +2; */
1012                        result = zend_string_init(mod_loc_name + singletonPos+2  , (len -( singletonPos +2) ), 0);
1013                    }
1014                    break;
1015                }
1016                else{
1017                    if( singletonPos + 1 >=  len){
1018                        /* String end */
1019                        break;
1020                    } else {
1021                        /* singleton found but not a private subtag , hence check further in the string for the private subtag */
1022                        mod_loc_name = mod_loc_name + singletonPos +1;
1023                        len = strlen(mod_loc_name);
1024                    }
1025                }
1026            }
1027
1028        } /* end of while */
1029    }
1030
1031    return result;
1032}
1033/* }}} */
1034
1035/* {{{ code used by locale_parse
1036*/
1037static int add_array_entry(const char* loc_name, zval* hash_arr, char* key_name)
1038{
1039    zend_string*   key_value    = NULL;
1040    char*   cur_key_name    = NULL;
1041    char*   token           = NULL;
1042    char*   last_ptr    = NULL;
1043
1044    int result      = 0;
1045    int     cur_result      = 0;
1046    int     cnt         = 0;
1047
1048
1049    if( strcmp(key_name , LOC_PRIVATE_TAG)==0 ){
1050        key_value = get_private_subtags( loc_name );
1051        result = 1;
1052    } else {
1053        key_value = get_icu_value_internal( loc_name , key_name , &result,1 );
1054    }
1055    if( (strcmp(key_name , LOC_PRIVATE_TAG)==0) ||
1056        ( strcmp(key_name , LOC_VARIANT_TAG)==0) ){
1057        if( result > 0 && key_value){
1058            /* Tokenize on the "_" or "-"  */
1059            token = php_strtok_r( key_value->val , DELIMITER ,&last_ptr);
1060            if( cur_key_name ){
1061                efree( cur_key_name);
1062            }
1063            cur_key_name = (char*)ecalloc( 25,  25);
1064            sprintf( cur_key_name , "%s%d", key_name , cnt++);
1065            add_assoc_string( hash_arr, cur_key_name , token);
1066            /* tokenize on the "_" or "-" and stop  at singleton if any */
1067            while( (token = php_strtok_r(NULL , DELIMITER , &last_ptr)) && (strlen(token)>1) ){
1068                sprintf( cur_key_name , "%s%d", key_name , cnt++);
1069                add_assoc_string( hash_arr, cur_key_name , token);
1070            }
1071/*
1072            if( strcmp(key_name, LOC_PRIVATE_TAG) == 0 ){
1073            }
1074*/
1075        }
1076        if (key_value) {
1077            zend_string_release(key_value);
1078        }
1079    } else {
1080        if( result == 1 ){
1081            add_assoc_str( hash_arr, key_name , key_value);
1082            cur_result = 1;
1083        } else if (key_value) {
1084            zend_string_release(key_value);
1085        }
1086    }
1087
1088    if( cur_key_name ){
1089        efree( cur_key_name);
1090    }
1091    /*if( key_name != LOC_PRIVATE_TAG && key_value){*/
1092    return cur_result;
1093}
1094/* }}} */
1095
1096/* {{{ proto static array Locale::parseLocale($locale)
1097* parses a locale-id into an array the different parts of it
1098 }}} */
1099/* {{{ proto static array parse_locale($locale)
1100* parses a locale-id into an array the different parts of it
1101*/
1102PHP_FUNCTION(locale_parse)
1103{
1104    const char* loc_name        = NULL;
1105    size_t         loc_name_len    = 0;
1106    int         grOffset        = 0;
1107
1108    intl_error_reset( NULL );
1109
1110    if(zend_parse_parameters( ZEND_NUM_ARGS(), "s",
1111        &loc_name, &loc_name_len ) == FAILURE)
1112    {
1113        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
1114             "locale_parse: unable to parse input params", 0 );
1115
1116        RETURN_FALSE;
1117    }
1118
1119    if(loc_name_len == 0) {
1120        loc_name = intl_locale_get_default();
1121    }
1122
1123    array_init( return_value );
1124
1125    grOffset =  findOffset( LOC_GRANDFATHERED , loc_name );
1126    if( grOffset >= 0 ){
1127        add_assoc_string( return_value , LOC_GRANDFATHERED_LANG_TAG, (char *)loc_name);
1128    }
1129    else{
1130        /* Not grandfathered */
1131        add_array_entry( loc_name , return_value , LOC_LANG_TAG);
1132        add_array_entry( loc_name , return_value , LOC_SCRIPT_TAG);
1133        add_array_entry( loc_name , return_value , LOC_REGION_TAG);
1134        add_array_entry( loc_name , return_value , LOC_VARIANT_TAG);
1135        add_array_entry( loc_name , return_value , LOC_PRIVATE_TAG);
1136    }
1137}
1138/* }}} */
1139
1140/* {{{ proto static array Locale::getAllVariants($locale)
1141* gets an array containing the list of variants, or null
1142 }}} */
1143/* {{{ proto static array locale_get_all_variants($locale)
1144* gets an array containing the list of variants, or null
1145*/
1146PHP_FUNCTION(locale_get_all_variants)
1147{
1148    const char*     loc_name        = NULL;
1149    size_t          loc_name_len    = 0;
1150
1151    int result      = 0;
1152    char*   token       = NULL;
1153    zend_string*    variant     = NULL;
1154    char*   saved_ptr   = NULL;
1155
1156    intl_error_reset( NULL );
1157
1158    if(zend_parse_parameters( ZEND_NUM_ARGS(), "s",
1159    &loc_name, &loc_name_len ) == FAILURE)
1160    {
1161        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
1162         "locale_parse: unable to parse input params", 0 );
1163
1164        RETURN_FALSE;
1165    }
1166
1167    if(loc_name_len == 0) {
1168        loc_name = intl_locale_get_default();
1169    }
1170
1171
1172    array_init( return_value );
1173
1174    /* If the locale is grandfathered, stop, no variants */
1175    if( findOffset( LOC_GRANDFATHERED , loc_name ) >=  0 ){
1176        /* ("Grandfathered Tag. No variants."); */
1177    }
1178    else {
1179    /* Call ICU variant */
1180        variant = get_icu_value_internal( loc_name , LOC_VARIANT_TAG , &result ,0);
1181        if( result > 0 && variant){
1182            /* Tokenize on the "_" or "-" */
1183            token = php_strtok_r( variant->val , DELIMITER , &saved_ptr);
1184            add_next_index_stringl( return_value, token , strlen(token));
1185            /* tokenize on the "_" or "-" and stop  at singleton if any */
1186            while( (token = php_strtok_r(NULL , DELIMITER, &saved_ptr)) && (strlen(token)>1) ){
1187                add_next_index_stringl( return_value, token , strlen(token));
1188            }
1189        }
1190        if( variant ){
1191            zend_string_release( variant );
1192        }
1193    }
1194
1195
1196}
1197/* }}} */
1198
1199/*{{{
1200* Converts to lower case and also replaces all hyphens with the underscore
1201*/
1202static int strToMatch(const char* str ,char *retstr)
1203{
1204    char*   anchor  = NULL;
1205    const char*     anchor1 = NULL;
1206    int     result  = 0;
1207
1208    if( (!str) || str[0] == '\0'){
1209        return result;
1210    } else {
1211    anchor = retstr;
1212    anchor1 = str;
1213        while( (*str)!='\0' ){
1214        if( *str == '-' ){
1215            *retstr =  '_';
1216        } else {
1217            *retstr = tolower(*str);
1218        }
1219            str++;
1220            retstr++;
1221    }
1222    *retstr = '\0';
1223    retstr=  anchor;
1224    str=  anchor1;
1225    result = 1;
1226    }
1227
1228    return(result);
1229}
1230/* }}} */
1231
1232/* {{{ proto static boolean Locale::filterMatches(string $langtag, string $locale[, bool $canonicalize])
1233* Checks if a $langtag filter matches with $locale according to RFC 4647's basic filtering algorithm
1234*/
1235/* }}} */
1236/* {{{ proto boolean locale_filter_matches(string $langtag, string $locale[, bool $canonicalize])
1237* Checks if a $langtag filter matches with $locale according to RFC 4647's basic filtering algorithm
1238*/
1239PHP_FUNCTION(locale_filter_matches)
1240{
1241    char*           lang_tag        = NULL;
1242    size_t          lang_tag_len    = 0;
1243    const char*     loc_range       = NULL;
1244    size_t          loc_range_len   = 0;
1245
1246    int     result      = 0;
1247    char*       token       = 0;
1248    char*       chrcheck    = NULL;
1249
1250    zend_string*    can_lang_tag    = NULL;
1251    zend_string*    can_loc_range   = NULL;
1252
1253    char*           cur_lang_tag    = NULL;
1254    char*           cur_loc_range   = NULL;
1255
1256    zend_bool   boolCanonical   = 0;
1257    UErrorCode  status      = U_ZERO_ERROR;
1258
1259    intl_error_reset( NULL );
1260
1261    if(zend_parse_parameters( ZEND_NUM_ARGS(), "ss|b",
1262        &lang_tag, &lang_tag_len , &loc_range , &loc_range_len ,
1263        &boolCanonical) == FAILURE)
1264    {
1265        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
1266        "locale_filter_matches: unable to parse input params", 0 );
1267
1268        RETURN_FALSE;
1269    }
1270
1271    if(loc_range_len == 0) {
1272        loc_range = intl_locale_get_default();
1273    }
1274
1275    if( strcmp(loc_range,"*")==0){
1276        RETURN_TRUE;
1277    }
1278
1279    if( boolCanonical ){
1280        /* canonicalize loc_range */
1281        can_loc_range=get_icu_value_internal( loc_range , LOC_CANONICALIZE_TAG , &result , 0);
1282        if( result ==0) {
1283            intl_error_set( NULL, status,
1284                "locale_filter_matches : unable to canonicalize loc_range" , 0 );
1285            RETURN_FALSE;
1286        }
1287
1288        /* canonicalize lang_tag */
1289        can_lang_tag = get_icu_value_internal( lang_tag , LOC_CANONICALIZE_TAG , &result ,  0);
1290        if( result ==0) {
1291            intl_error_set( NULL, status,
1292                "locale_filter_matches : unable to canonicalize lang_tag" , 0 );
1293            RETURN_FALSE;
1294        }
1295
1296        /* Convert to lower case for case-insensitive comparison */
1297        cur_lang_tag = ecalloc( 1, can_lang_tag->len + 1);
1298
1299        /* Convert to lower case for case-insensitive comparison */
1300        result = strToMatch( can_lang_tag->val , cur_lang_tag);
1301        if( result == 0) {
1302            efree( cur_lang_tag );
1303            zend_string_release( can_lang_tag );
1304            RETURN_FALSE;
1305        }
1306
1307        cur_loc_range = ecalloc( 1, can_loc_range->len + 1);
1308        result = strToMatch( can_loc_range->val , cur_loc_range );
1309        if( result == 0) {
1310            efree( cur_lang_tag );
1311            zend_string_release( can_lang_tag );
1312            efree( cur_loc_range );
1313            zend_string_release( can_loc_range );
1314            RETURN_FALSE;
1315        }
1316
1317        /* check if prefix */
1318        token   = strstr( cur_lang_tag , cur_loc_range );
1319
1320        if( token && (token==cur_lang_tag) ){
1321            /* check if the char. after match is SEPARATOR */
1322            chrcheck = token + (strlen(cur_loc_range));
1323            if( isIDSeparator(*chrcheck) || isEndOfTag(*chrcheck) ){
1324                if( cur_lang_tag){
1325                    efree( cur_lang_tag );
1326                }
1327                if( cur_loc_range){
1328                    efree( cur_loc_range );
1329                }
1330                if( can_lang_tag){
1331                    zend_string_release( can_lang_tag );
1332                }
1333                if( can_loc_range){
1334                    zend_string_release( can_loc_range );
1335                }
1336                RETURN_TRUE;
1337            }
1338        }
1339
1340        /* No prefix as loc_range */
1341        if( cur_lang_tag){
1342            efree( cur_lang_tag );
1343        }
1344        if( cur_loc_range){
1345            efree( cur_loc_range );
1346        }
1347        if( can_lang_tag){
1348            zend_string_release( can_lang_tag );
1349        }
1350        if( can_loc_range){
1351            zend_string_release( can_loc_range );
1352        }
1353        RETURN_FALSE;
1354
1355    } /* end of if isCanonical */
1356    else{
1357        /* Convert to lower case for case-insensitive comparison */
1358        cur_lang_tag = ecalloc( 1, strlen(lang_tag ) + 1);
1359
1360        result = strToMatch( lang_tag , cur_lang_tag);
1361        if( result == 0) {
1362            efree( cur_lang_tag );
1363            RETURN_FALSE;
1364        }
1365        cur_loc_range = ecalloc( 1, strlen(loc_range ) + 1);
1366        result = strToMatch( loc_range , cur_loc_range );
1367        if( result == 0) {
1368            efree( cur_lang_tag );
1369            efree( cur_loc_range );
1370            RETURN_FALSE;
1371        }
1372
1373        /* check if prefix */
1374        token   = strstr( cur_lang_tag , cur_loc_range );
1375
1376        if( token && (token==cur_lang_tag) ){
1377            /* check if the char. after match is SEPARATOR */
1378            chrcheck = token + (strlen(cur_loc_range));
1379            if( isIDSeparator(*chrcheck) || isEndOfTag(*chrcheck) ){
1380                if( cur_lang_tag){
1381                    efree( cur_lang_tag );
1382                }
1383                if( cur_loc_range){
1384                    efree( cur_loc_range );
1385                }
1386                RETURN_TRUE;
1387            }
1388        }
1389
1390        /* No prefix as loc_range */
1391        if( cur_lang_tag){
1392            efree( cur_lang_tag );
1393        }
1394        if( cur_loc_range){
1395            efree( cur_loc_range );
1396        }
1397        RETURN_FALSE;
1398
1399    }
1400}
1401/* }}} */
1402
1403static void array_cleanup( char* arr[] , int arr_size)
1404{
1405    int i=0;
1406    for( i=0; i< arr_size; i++ ){
1407        if( arr[i*2] ){
1408            efree( arr[i*2]);
1409        }
1410    }
1411    efree(arr);
1412}
1413
1414#define LOOKUP_CLEAN_RETURN(value)  array_cleanup(cur_arr, cur_arr_len); return (value)
1415/* {{{
1416* returns the lookup result to lookup_loc_range_src_php
1417* internal function
1418*/
1419static zend_string* lookup_loc_range(const char* loc_range, HashTable* hash_arr, int canonicalize )
1420{
1421    int i = 0;
1422    int cur_arr_len = 0;
1423    int result = 0;
1424
1425    zend_string* lang_tag = NULL;
1426    zval* ele_value = NULL;
1427    char** cur_arr = NULL;
1428
1429    char* cur_loc_range = NULL;
1430    zend_string* can_loc_range  = NULL;
1431    int saved_pos = 0;
1432
1433    zend_string* return_value = NULL;
1434
1435    cur_arr = ecalloc(zend_hash_num_elements(hash_arr)*2, sizeof(char *));
1436    ZEND_HASH_FOREACH_VAL(hash_arr, ele_value) {
1437    /* convert the array to lowercase , also replace hyphens with the underscore and store it in cur_arr */
1438        if(Z_TYPE_P(ele_value)!= IS_STRING) {
1439            /* element value is not a string */
1440            intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: locale array element is not a string", 0);
1441            LOOKUP_CLEAN_RETURN(NULL);
1442        }
1443        cur_arr[cur_arr_len*2] = estrndup(Z_STRVAL_P(ele_value), Z_STRLEN_P(ele_value));
1444        result = strToMatch(Z_STRVAL_P(ele_value), cur_arr[cur_arr_len*2]);
1445        if(result == 0) {
1446            intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag", 0);
1447            LOOKUP_CLEAN_RETURN(NULL);
1448        }
1449        cur_arr[cur_arr_len*2+1] = Z_STRVAL_P(ele_value);
1450        cur_arr_len++ ;
1451    } ZEND_HASH_FOREACH_END(); /* end of for */
1452
1453    /* Canonicalize array elements */
1454    if(canonicalize) {
1455        for(i=0; i<cur_arr_len; i++) {
1456            lang_tag = get_icu_value_internal(cur_arr[i*2], LOC_CANONICALIZE_TAG, &result, 0);
1457            if(result != 1 || lang_tag == NULL || !lang_tag->val[0]) {
1458                if(lang_tag) {
1459                    zend_string_release(lang_tag);
1460                }
1461                intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag" , 0);
1462                LOOKUP_CLEAN_RETURN(NULL);
1463            }
1464            cur_arr[i*2] = erealloc(cur_arr[i*2], lang_tag->len+1);
1465            result = strToMatch(lang_tag->val, cur_arr[i*2]);
1466            zend_string_release(lang_tag);
1467            if(result == 0) {
1468                intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag" , 0);
1469                LOOKUP_CLEAN_RETURN(NULL);
1470            }
1471        }
1472
1473    }
1474
1475    if(canonicalize) {
1476        /* Canonicalize the loc_range */
1477        can_loc_range = get_icu_value_internal(loc_range, LOC_CANONICALIZE_TAG, &result , 0);
1478        if( result != 1 || can_loc_range == NULL || !can_loc_range->val[0]) {
1479            /* Error */
1480            intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize loc_range" , 0 );
1481            if(can_loc_range) {
1482                zend_string_release(can_loc_range);
1483            }
1484            LOOKUP_CLEAN_RETURN(NULL);
1485        } else {
1486            loc_range = can_loc_range->val;
1487        }
1488    }
1489
1490    cur_loc_range = ecalloc(1, strlen(loc_range)+1);
1491    /* convert to lower and replace hyphens */
1492    result = strToMatch(loc_range, cur_loc_range);
1493    if(can_loc_range) {
1494        zend_string_release(can_loc_range);
1495    }
1496    if(result == 0) {
1497        intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag" , 0);
1498        LOOKUP_CLEAN_RETURN(NULL);
1499    }
1500
1501    /* Lookup for the lang_tag match */
1502    saved_pos = strlen(cur_loc_range);
1503    while(saved_pos > 0) {
1504        for(i=0; i< cur_arr_len; i++){
1505            if(cur_arr[i*2] != NULL && strlen(cur_arr[i*2]) == saved_pos && strncmp(cur_loc_range, cur_arr[i*2], saved_pos) == 0) {
1506                /* Match found */
1507                char *str = canonicalize ? cur_arr[i*2] : cur_arr[i*2+1];
1508                return_value = zend_string_init(str, strlen(str), 0);
1509                efree(cur_loc_range);
1510                LOOKUP_CLEAN_RETURN(return_value);
1511            }
1512        }
1513        saved_pos = getStrrtokenPos(cur_loc_range, saved_pos);
1514    }
1515
1516    /* Match not found */
1517    efree(cur_loc_range);
1518    LOOKUP_CLEAN_RETURN(NULL);
1519}
1520/* }}} */
1521
1522/* {{{ proto string Locale::lookup(array $langtag, string $locale[, bool $canonicalize[, string $default = null]])
1523* Searchs the items in $langtag for the best match to the language
1524* range
1525*/
1526/* }}} */
1527/* {{{ proto string locale_lookup(array $langtag, string $locale[, bool $canonicalize[, string $default = null]])
1528* Searchs the items in $langtag for the best match to the language
1529* range
1530*/
1531PHP_FUNCTION(locale_lookup)
1532{
1533    zend_string*    fallback_loc_str    = NULL;
1534    const char*     loc_range           = NULL;
1535    size_t          loc_range_len       = 0;
1536
1537    zval*       arr             = NULL;
1538    HashTable*  hash_arr        = NULL;
1539    zend_bool   boolCanonical   = 0;
1540    zend_string*    result_str  = NULL;
1541
1542    intl_error_reset( NULL );
1543
1544    if(zend_parse_parameters( ZEND_NUM_ARGS(), "as|bS", &arr, &loc_range, &loc_range_len,
1545        &boolCanonical, &fallback_loc_str) == FAILURE) {
1546        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "locale_lookup: unable to parse input params", 0 );
1547        RETURN_FALSE;
1548    }
1549
1550    if(loc_range_len == 0) {
1551        loc_range = intl_locale_get_default();
1552    }
1553
1554    hash_arr = Z_ARRVAL_P(arr);
1555
1556    if( !hash_arr || zend_hash_num_elements( hash_arr ) == 0 ) {
1557        RETURN_EMPTY_STRING();
1558    }
1559
1560    result_str = lookup_loc_range(loc_range, hash_arr, boolCanonical);
1561    if(result_str == NULL || ZSTR_VAL(result_str)[0] == '\0') {
1562        if( fallback_loc_str ) {
1563            result_str = zend_string_copy(fallback_loc_str);
1564        } else {
1565            RETURN_EMPTY_STRING();
1566        }
1567    }
1568
1569    RETURN_STR(result_str);
1570}
1571/* }}} */
1572
1573/* {{{ proto string Locale::acceptFromHttp(string $http_accept)
1574* Tries to find out best available locale based on HTTP �Accept-Language� header
1575*/
1576/* }}} */
1577/* {{{ proto string locale_accept_from_http(string $http_accept)
1578* Tries to find out best available locale based on HTTP �Accept-Language� header
1579*/
1580PHP_FUNCTION(locale_accept_from_http)
1581{
1582    UEnumeration *available;
1583    char *http_accept = NULL;
1584    size_t http_accept_len;
1585    UErrorCode status = 0;
1586    int len;
1587    char resultLocale[INTL_MAX_LOCALE_LEN+1];
1588    UAcceptResult outResult;
1589
1590    if(zend_parse_parameters( ZEND_NUM_ARGS(), "s", &http_accept, &http_accept_len) == FAILURE)
1591    {
1592        intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
1593        "locale_accept_from_http: unable to parse input parameters", 0 );
1594        RETURN_FALSE;
1595    }
1596
1597    available = ures_openAvailableLocales(NULL, &status);
1598    INTL_CHECK_STATUS(status, "locale_accept_from_http: failed to retrieve locale list");
1599    len = uloc_acceptLanguageFromHTTP(resultLocale, INTL_MAX_LOCALE_LEN,
1600                        &outResult, http_accept, available, &status);
1601    uenum_close(available);
1602    INTL_CHECK_STATUS(status, "locale_accept_from_http: failed to find acceptable locale");
1603    if (len < 0 || outResult == ULOC_ACCEPT_FAILED) {
1604        RETURN_FALSE;
1605    }
1606    RETURN_STRINGL(resultLocale, len);
1607}
1608/* }}} */
1609
1610/*
1611 * Local variables:
1612 * tab-width: 4
1613 * c-basic-offset: 4
1614 * End:
1615 * vim600: noet sw=4 ts=4 fdm=marker
1616 * vim<600: noet sw=4 ts=4
1617 *can_loc_len
1618*/
1619