1 | /* |
2 | +----------------------------------------------------------------------+ |
3 | | PHP Version 5 | |
4 | +----------------------------------------------------------------------+ |
5 | | Copyright (c) 1997-2015 The PHP Group | |
6 | +----------------------------------------------------------------------+ |
7 | | This source file is subject to version 3.01 of the PHP license, | |
8 | | that is bundled with this package in the file LICENSE, and is | |
9 | | available through the world-wide-web at the following url: | |
10 | | http://www.php.net/license/3_01.txt | |
11 | | If you did not receive a copy of the PHP license and are unable to | |
12 | | obtain it through the world-wide-web, please send a note to | |
13 | | license@php.net so we can mail you a copy immediately. | |
14 | +----------------------------------------------------------------------+ |
15 | | Author: Zeev Suraski <zeev@zend.com> | |
16 | +----------------------------------------------------------------------+ |
17 | */ |
18 | |
19 | /* $Id$ */ |
20 | |
21 | #include "php.h" |
22 | #include "php_browscap.h" |
23 | #include "php_ini.h" |
24 | #include "php_string.h" |
25 | #include "ext/pcre/php_pcre.h" |
26 | |
27 | #include "zend_ini_scanner.h" |
28 | #include "zend_globals.h" |
29 | |
30 | typedef struct { |
31 | HashTable *htab; |
32 | zval *current_section; |
33 | char *current_section_name; |
34 | char filename[MAXPATHLEN]; |
35 | } browser_data; |
36 | |
37 | /* browser data defined in startup phase, eagerly loaded in MINIT */ |
38 | static browser_data global_bdata = {0}; |
39 | |
40 | /* browser data defined in activation phase, lazily loaded in get_browser. |
41 | * Per request and per thread, if applicable */ |
42 | ZEND_BEGIN_MODULE_GLOBALS(browscap) |
43 | browser_data activation_bdata; |
44 | ZEND_END_MODULE_GLOBALS(browscap) |
45 | |
46 | ZEND_DECLARE_MODULE_GLOBALS(browscap) |
47 | |
48 | #ifdef ZTS |
49 | #define BROWSCAP_G(v) TSRMG(browscap_globals_id, zend_browscap_globals *, v) |
50 | #else |
51 | #define BROWSCAP_G(v) (browscap_globals.v) |
52 | #endif |
53 | |
54 | #define DEFAULT_SECTION_NAME "Default Browser Capability Settings" |
55 | |
56 | /* OBJECTS_FIXME: This whole extension needs going through. The use of objects looks pretty broken here */ |
57 | |
58 | static void browscap_entry_dtor_request(zval **zvalue) /* {{{ */ |
59 | { |
60 | if (Z_TYPE_PP(zvalue) == IS_ARRAY) { |
61 | zend_hash_destroy(Z_ARRVAL_PP(zvalue)); |
62 | efree(Z_ARRVAL_PP(zvalue)); |
63 | } else if (Z_TYPE_PP(zvalue) == IS_STRING) { |
64 | if (Z_STRVAL_PP(zvalue)) { |
65 | efree(Z_STRVAL_PP(zvalue)); |
66 | } |
67 | } |
68 | efree(*zvalue); |
69 | } |
70 | /* }}} */ |
71 | |
72 | static void browscap_entry_dtor_persistent(zval **zvalue) /* {{{ */ { |
73 | if (Z_TYPE_PP(zvalue) == IS_ARRAY) { |
74 | zend_hash_destroy(Z_ARRVAL_PP(zvalue)); |
75 | free(Z_ARRVAL_PP(zvalue)); |
76 | } else if (Z_TYPE_PP(zvalue) == IS_STRING) { |
77 | if (Z_STRVAL_PP(zvalue)) { |
78 | free(Z_STRVAL_PP(zvalue)); |
79 | } |
80 | } |
81 | free(*zvalue); |
82 | } |
83 | /* }}} */ |
84 | |
85 | static void convert_browscap_pattern(zval *pattern, int persistent) /* {{{ */ |
86 | { |
87 | int i, j=0; |
88 | char *t; |
89 | |
90 | php_strtolower(Z_STRVAL_P(pattern), Z_STRLEN_P(pattern)); |
91 | |
92 | t = (char *) safe_pemalloc(Z_STRLEN_P(pattern), 2, 5, persistent); |
93 | |
94 | t[j++] = '~'; |
95 | t[j++] = '^'; |
96 | |
97 | for (i=0; i<Z_STRLEN_P(pattern); i++, j++) { |
98 | switch (Z_STRVAL_P(pattern)[i]) { |
99 | case '?': |
100 | t[j] = '.'; |
101 | break; |
102 | case '*': |
103 | t[j++] = '.'; |
104 | t[j] = '*'; |
105 | break; |
106 | case '.': |
107 | t[j++] = '\\'; |
108 | t[j] = '.'; |
109 | break; |
110 | case '\\': |
111 | t[j++] = '\\'; |
112 | t[j] = '\\'; |
113 | break; |
114 | case '(': |
115 | t[j++] = '\\'; |
116 | t[j] = '('; |
117 | break; |
118 | case ')': |
119 | t[j++] = '\\'; |
120 | t[j] = ')'; |
121 | break; |
122 | case '~': |
123 | t[j++] = '\\'; |
124 | t[j] = '~'; |
125 | break; |
126 | default: |
127 | t[j] = Z_STRVAL_P(pattern)[i]; |
128 | break; |
129 | } |
130 | } |
131 | |
132 | t[j++] = '$'; |
133 | t[j++] = '~'; |
134 | |
135 | t[j]=0; |
136 | Z_STRVAL_P(pattern) = t; |
137 | Z_STRLEN_P(pattern) = j; |
138 | } |
139 | /* }}} */ |
140 | |
141 | static void php_browscap_parser_cb(zval *arg1, zval *arg2, zval *arg3, int callback_type, void *arg TSRMLS_DC) /* {{{ */ |
142 | { |
143 | browser_data *bdata = arg; |
144 | int persistent = bdata->htab->persistent; |
145 | |
146 | if (!arg1) { |
147 | return; |
148 | } |
149 | |
150 | switch (callback_type) { |
151 | case ZEND_INI_PARSER_ENTRY: |
152 | if (bdata->current_section && arg2) { |
153 | zval *new_property; |
154 | char *new_key; |
155 | |
156 | /* parent entry can not be same as current section -> causes infinite loop! */ |
157 | if (!strcasecmp(Z_STRVAL_P(arg1), "parent" ) && |
158 | bdata->current_section_name != NULL && |
159 | !strcasecmp(bdata->current_section_name, Z_STRVAL_P(arg2)) |
160 | ) { |
161 | zend_error(E_CORE_ERROR, "Invalid browscap ini file: " |
162 | "'Parent' value cannot be same as the section name: %s " |
163 | "(in file %s)" , bdata->current_section_name, INI_STR("browscap" )); |
164 | return; |
165 | } |
166 | |
167 | new_property = (zval *) pemalloc(sizeof(zval), persistent); |
168 | INIT_PZVAL(new_property); |
169 | Z_TYPE_P(new_property) = IS_STRING; |
170 | |
171 | /* Set proper value for true/false settings */ |
172 | if ((Z_STRLEN_P(arg2) == 2 && !strncasecmp(Z_STRVAL_P(arg2), "on" , sizeof("on" ) - 1)) || |
173 | (Z_STRLEN_P(arg2) == 3 && !strncasecmp(Z_STRVAL_P(arg2), "yes" , sizeof("yes" ) - 1)) || |
174 | (Z_STRLEN_P(arg2) == 4 && !strncasecmp(Z_STRVAL_P(arg2), "true" , sizeof("true" ) - 1)) |
175 | ) { |
176 | Z_STRVAL_P(new_property) = pestrndup("1" , 1, persistent); |
177 | Z_STRLEN_P(new_property) = 1; |
178 | } else if ( |
179 | (Z_STRLEN_P(arg2) == 2 && !strncasecmp(Z_STRVAL_P(arg2), "no" , sizeof("no" ) - 1)) || |
180 | (Z_STRLEN_P(arg2) == 3 && !strncasecmp(Z_STRVAL_P(arg2), "off" , sizeof("off" ) - 1)) || |
181 | (Z_STRLEN_P(arg2) == 4 && !strncasecmp(Z_STRVAL_P(arg2), "none" , sizeof("none" ) - 1)) || |
182 | (Z_STRLEN_P(arg2) == 5 && !strncasecmp(Z_STRVAL_P(arg2), "false" , sizeof("false" ) - 1)) |
183 | ) { |
184 | Z_STRVAL_P(new_property) = pestrndup("" , 0, persistent); |
185 | Z_STRLEN_P(new_property) = 0; |
186 | } else { /* Other than true/false setting */ |
187 | Z_STRVAL_P(new_property) = pestrndup(Z_STRVAL_P(arg2), |
188 | Z_STRLEN_P(arg2), persistent); |
189 | Z_STRLEN_P(new_property) = Z_STRLEN_P(arg2); |
190 | } |
191 | new_key = pestrndup(Z_STRVAL_P(arg1), Z_STRLEN_P(arg1), persistent); |
192 | zend_str_tolower(new_key, Z_STRLEN_P(arg1)); |
193 | zend_hash_update(Z_ARRVAL_P(bdata->current_section), new_key, Z_STRLEN_P(arg1) + 1, &new_property, sizeof(zval *), NULL); |
194 | pefree(new_key, persistent); |
195 | } |
196 | break; |
197 | case ZEND_INI_PARSER_SECTION: { |
198 | zval *processed; |
199 | zval *unprocessed; |
200 | HashTable *section_properties; |
201 | |
202 | /*printf("'%s' (%d)\n",$1.value.str.val,$1.value.str.len + 1);*/ |
203 | bdata->current_section = (zval *) pemalloc(sizeof(zval), persistent); |
204 | INIT_PZVAL(bdata->current_section); |
205 | processed = (zval *) pemalloc(sizeof(zval), persistent); |
206 | INIT_PZVAL(processed); |
207 | unprocessed = (zval *) pemalloc(sizeof(zval), persistent); |
208 | INIT_PZVAL(unprocessed); |
209 | |
210 | section_properties = (HashTable *) pemalloc(sizeof(HashTable), persistent); |
211 | zend_hash_init(section_properties, 0, NULL, |
212 | (dtor_func_t) (persistent?browscap_entry_dtor_persistent |
213 | :browscap_entry_dtor_request), |
214 | persistent); |
215 | Z_ARRVAL_P(bdata->current_section) = section_properties; |
216 | Z_TYPE_P(bdata->current_section) = IS_ARRAY; |
217 | if (bdata->current_section_name) { |
218 | pefree(bdata->current_section_name, persistent); |
219 | } |
220 | bdata->current_section_name = pestrndup(Z_STRVAL_P(arg1), |
221 | Z_STRLEN_P(arg1), persistent); |
222 | |
223 | zend_hash_update(bdata->htab, Z_STRVAL_P(arg1), Z_STRLEN_P(arg1) + 1, (void *) &bdata->current_section, sizeof(zval *), NULL); |
224 | |
225 | Z_STRVAL_P(processed) = Z_STRVAL_P(arg1); |
226 | Z_STRLEN_P(processed) = Z_STRLEN_P(arg1); |
227 | Z_TYPE_P(processed) = IS_STRING; |
228 | Z_STRVAL_P(unprocessed) = Z_STRVAL_P(arg1); |
229 | Z_STRLEN_P(unprocessed) = Z_STRLEN_P(arg1); |
230 | Z_TYPE_P(unprocessed) = IS_STRING; |
231 | Z_STRVAL_P(unprocessed) = pestrndup(Z_STRVAL_P(unprocessed), Z_STRLEN_P(unprocessed), persistent); |
232 | |
233 | convert_browscap_pattern(processed, persistent); |
234 | zend_hash_update(section_properties, "browser_name_regex" , sizeof("browser_name_regex" ), (void *) &processed, sizeof(zval *), NULL); |
235 | zend_hash_update(section_properties, "browser_name_pattern" , sizeof("browser_name_pattern" ), (void *) &unprocessed, sizeof(zval *), NULL); |
236 | } |
237 | break; |
238 | } |
239 | } |
240 | /* }}} */ |
241 | |
242 | static int browscap_read_file(char *filename, browser_data *browdata, int persistent TSRMLS_DC) /* {{{ */ |
243 | { |
244 | zend_file_handle fh = {0}; |
245 | |
246 | if (filename == NULL || filename[0] == '\0') { |
247 | return FAILURE; |
248 | } |
249 | |
250 | browdata->htab = pemalloc(sizeof *browdata->htab, persistent); |
251 | if (browdata->htab == NULL) { |
252 | return FAILURE; |
253 | } |
254 | |
255 | if (zend_hash_init_ex(browdata->htab, 0, NULL, |
256 | (dtor_func_t) (persistent?browscap_entry_dtor_persistent |
257 | :browscap_entry_dtor_request), |
258 | persistent, 0) == FAILURE) { |
259 | pefree(browdata->htab, persistent); |
260 | browdata->htab = NULL; |
261 | return FAILURE; |
262 | } |
263 | |
264 | fh.handle.fp = VCWD_FOPEN(filename, "r" ); |
265 | fh.opened_path = NULL; |
266 | fh.free_filename = 0; |
267 | if (!fh.handle.fp) { |
268 | zend_hash_destroy(browdata->htab); |
269 | pefree(browdata->htab, persistent); |
270 | browdata->htab = NULL; |
271 | zend_error(E_CORE_WARNING, "Cannot open '%s' for reading" , filename); |
272 | return FAILURE; |
273 | } |
274 | fh.filename = filename; |
275 | Z_TYPE(fh) = ZEND_HANDLE_FP; |
276 | browdata->current_section_name = NULL; |
277 | zend_parse_ini_file(&fh, 1, ZEND_INI_SCANNER_RAW, |
278 | (zend_ini_parser_cb_t) php_browscap_parser_cb, browdata TSRMLS_CC); |
279 | if (browdata->current_section_name != NULL) { |
280 | pefree(browdata->current_section_name, persistent); |
281 | browdata->current_section_name = NULL; |
282 | } |
283 | |
284 | return SUCCESS; |
285 | } |
286 | /* }}} */ |
287 | |
288 | #ifdef ZTS |
289 | static void browscap_globals_ctor(zend_browscap_globals *browscap_globals TSRMLS_DC) /* {{{ */ |
290 | { |
291 | browscap_globals->activation_bdata.htab = NULL; |
292 | browscap_globals->activation_bdata.current_section = NULL; |
293 | browscap_globals->activation_bdata.current_section_name = NULL; |
294 | browscap_globals->activation_bdata.filename[0] = '\0'; |
295 | } |
296 | /* }}} */ |
297 | #endif |
298 | |
299 | static void browscap_bdata_dtor(browser_data *bdata, int persistent TSRMLS_DC) /* {{{ */ |
300 | { |
301 | if (bdata->htab != NULL) { |
302 | zend_hash_destroy(bdata->htab); |
303 | pefree(bdata->htab, persistent); |
304 | bdata->htab = NULL; |
305 | } |
306 | bdata->filename[0] = '\0'; |
307 | /* current_section_* are only used during parsing */ |
308 | } |
309 | /* }}} */ |
310 | |
311 | /* {{{ PHP_INI_MH |
312 | */ |
313 | PHP_INI_MH(OnChangeBrowscap) |
314 | { |
315 | if (stage == PHP_INI_STAGE_STARTUP) { |
316 | /* value handled in browscap.c's MINIT */ |
317 | return SUCCESS; |
318 | } else if (stage == PHP_INI_STAGE_ACTIVATE) { |
319 | browser_data *bdata = &BROWSCAP_G(activation_bdata); |
320 | if (bdata->filename[0] != '\0') { |
321 | browscap_bdata_dtor(bdata, 0 TSRMLS_CC); |
322 | } |
323 | if (VCWD_REALPATH(new_value, bdata->filename) == NULL) { |
324 | return FAILURE; |
325 | } |
326 | return SUCCESS; |
327 | } |
328 | |
329 | return FAILURE; |
330 | } |
331 | /* }}} */ |
332 | |
333 | PHP_MINIT_FUNCTION(browscap) /* {{{ */ |
334 | { |
335 | char *browscap = INI_STR("browscap" ); |
336 | |
337 | #ifdef ZTS |
338 | ts_allocate_id(&browscap_globals_id, sizeof(browser_data), |
339 | browscap_globals_ctor, NULL); |
340 | #endif |
341 | /* ctor call not really needed for non-ZTS */ |
342 | |
343 | if (browscap && browscap[0]) { |
344 | if (browscap_read_file(browscap, &global_bdata, 1 TSRMLS_CC) == FAILURE) { |
345 | return FAILURE; |
346 | } |
347 | } |
348 | |
349 | return SUCCESS; |
350 | } |
351 | /* }}} */ |
352 | |
353 | PHP_RSHUTDOWN_FUNCTION(browscap) /* {{{ */ |
354 | { |
355 | browser_data *bdata = &BROWSCAP_G(activation_bdata); |
356 | if (bdata->filename[0] != '\0') { |
357 | browscap_bdata_dtor(bdata, 0 TSRMLS_CC); |
358 | } |
359 | |
360 | return SUCCESS; |
361 | } |
362 | /* }}} */ |
363 | |
364 | PHP_MSHUTDOWN_FUNCTION(browscap) /* {{{ */ |
365 | { |
366 | browscap_bdata_dtor(&global_bdata, 1 TSRMLS_CC); |
367 | |
368 | return SUCCESS; |
369 | } |
370 | /* }}} */ |
371 | |
372 | static int browser_reg_compare(zval **browser TSRMLS_DC, int num_args, va_list args, zend_hash_key *key) /* {{{ */ |
373 | { |
374 | zval **browser_regex, **previous_match; |
375 | pcre *re; |
376 | int re_options; |
377 | pcre_extra *; |
378 | char *lookup_browser_name = va_arg(args, char *); |
379 | int lookup_browser_length = va_arg(args, int); |
380 | zval **found_browser_entry = va_arg(args, zval **); |
381 | |
382 | /* See if we have an exact match, if so, we're done... */ |
383 | if (*found_browser_entry) { |
384 | if (zend_hash_find(Z_ARRVAL_PP(found_browser_entry), "browser_name_pattern" , sizeof("browser_name_pattern" ), (void**) &previous_match) == FAILURE) { |
385 | return 0; |
386 | } |
387 | else if (!strcasecmp(Z_STRVAL_PP(previous_match), lookup_browser_name)) { |
388 | return 0; |
389 | } |
390 | } |
391 | |
392 | if (zend_hash_find(Z_ARRVAL_PP(browser), "browser_name_regex" , sizeof("browser_name_regex" ), (void **) &browser_regex) == FAILURE) { |
393 | return 0; |
394 | } |
395 | |
396 | re = pcre_get_compiled_regex(Z_STRVAL_PP(browser_regex), &re_extra, &re_options TSRMLS_CC); |
397 | if (re == NULL) { |
398 | return 0; |
399 | } |
400 | |
401 | if (pcre_exec(re, re_extra, lookup_browser_name, lookup_browser_length, 0, re_options, NULL, 0) == 0) { |
402 | /* If we've found a possible browser, we need to do a comparison of the |
403 | number of characters changed in the user agent being checked versus |
404 | the previous match found and the current match. */ |
405 | if (*found_browser_entry) { |
406 | int i, prev_len = 0, curr_len = 0, ua_len; |
407 | zval **current_match; |
408 | |
409 | if (zend_hash_find(Z_ARRVAL_PP(browser), "browser_name_pattern" , sizeof("browser_name_pattern" ), (void**) ¤t_match) == FAILURE) { |
410 | return 0; |
411 | } |
412 | |
413 | ua_len = lookup_browser_length; |
414 | |
415 | for (i = 0; i < Z_STRLEN_PP(previous_match); i++) { |
416 | switch (Z_STRVAL_PP(previous_match)[i]) { |
417 | case '?': |
418 | case '*': |
419 | /* do nothing, ignore these characters in the count */ |
420 | break; |
421 | |
422 | default: |
423 | ++prev_len; |
424 | } |
425 | } |
426 | |
427 | for (i = 0; i < Z_STRLEN_PP(current_match); i++) { |
428 | switch (Z_STRVAL_PP(current_match)[i]) { |
429 | case '?': |
430 | case '*': |
431 | /* do nothing, ignore these characters in the count */ |
432 | break; |
433 | |
434 | default: |
435 | ++curr_len; |
436 | } |
437 | } |
438 | |
439 | /* Pick which browser pattern replaces the least amount of |
440 | characters when compared to the original user agent string... */ |
441 | if (ua_len - prev_len > ua_len - curr_len) { |
442 | *found_browser_entry = *browser; |
443 | } |
444 | } |
445 | else { |
446 | *found_browser_entry = *browser; |
447 | } |
448 | } |
449 | |
450 | return 0; |
451 | } |
452 | /* }}} */ |
453 | |
454 | static void browscap_zval_copy_ctor(zval **p) /* {{{ */ |
455 | { |
456 | zval *new; |
457 | |
458 | ALLOC_ZVAL(new); |
459 | *new = **p; |
460 | |
461 | zval_copy_ctor(new); |
462 | |
463 | INIT_PZVAL(new); |
464 | *p = new; |
465 | } /* }}} */ |
466 | |
467 | /* {{{ proto mixed get_browser([string browser_name [, bool return_array]]) |
468 | Get information about the capabilities of a browser. If browser_name is omitted or null, HTTP_USER_AGENT is used. Returns an object by default; if return_array is true, returns an array. */ |
469 | PHP_FUNCTION(get_browser) |
470 | { |
471 | char *agent_name = NULL; |
472 | int agent_name_len = 0; |
473 | zend_bool return_array = 0; |
474 | zval **agent, **z_agent_name, **http_user_agent; |
475 | zval *found_browser_entry, *tmp_copy; |
476 | char *lookup_browser_name; |
477 | browser_data *bdata; |
478 | |
479 | if (BROWSCAP_G(activation_bdata).filename[0] != '\0') { |
480 | bdata = &BROWSCAP_G(activation_bdata); |
481 | if (bdata->htab == NULL) { /* not initialized yet */ |
482 | if (browscap_read_file(bdata->filename, bdata, 0 TSRMLS_CC) == FAILURE) { |
483 | RETURN_FALSE; |
484 | } |
485 | } |
486 | } else { |
487 | if (!global_bdata.htab) { |
488 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "browscap ini directive not set" ); |
489 | RETURN_FALSE; |
490 | } |
491 | bdata = &global_bdata; |
492 | } |
493 | |
494 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!b" , &agent_name, &agent_name_len, &return_array) == FAILURE) { |
495 | return; |
496 | } |
497 | |
498 | if (agent_name == NULL) { |
499 | zend_is_auto_global("_SERVER" , sizeof("_SERVER" ) - 1 TSRMLS_CC); |
500 | if (!PG(http_globals)[TRACK_VARS_SERVER] || |
501 | zend_hash_find(HASH_OF(PG(http_globals)[TRACK_VARS_SERVER]), "HTTP_USER_AGENT" , sizeof("HTTP_USER_AGENT" ), (void **) &http_user_agent) == FAILURE |
502 | ) { |
503 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "HTTP_USER_AGENT variable is not set, cannot determine user agent name" ); |
504 | RETURN_FALSE; |
505 | } |
506 | agent_name = Z_STRVAL_PP(http_user_agent); |
507 | agent_name_len = Z_STRLEN_PP(http_user_agent); |
508 | } |
509 | |
510 | lookup_browser_name = estrndup(agent_name, agent_name_len); |
511 | php_strtolower(lookup_browser_name, agent_name_len); |
512 | |
513 | if (zend_hash_find(bdata->htab, lookup_browser_name, agent_name_len + 1, (void **) &agent) == FAILURE) { |
514 | found_browser_entry = NULL; |
515 | zend_hash_apply_with_arguments(bdata->htab TSRMLS_CC, (apply_func_args_t) browser_reg_compare, 3, lookup_browser_name, agent_name_len, &found_browser_entry); |
516 | |
517 | if (found_browser_entry) { |
518 | agent = &found_browser_entry; |
519 | } else if (zend_hash_find(bdata->htab, DEFAULT_SECTION_NAME, sizeof(DEFAULT_SECTION_NAME), (void **) &agent) == FAILURE) { |
520 | efree(lookup_browser_name); |
521 | RETURN_FALSE; |
522 | } |
523 | } |
524 | |
525 | if (return_array) { |
526 | array_init(return_value); |
527 | zend_hash_copy(Z_ARRVAL_P(return_value), Z_ARRVAL_PP(agent), (copy_ctor_func_t) browscap_zval_copy_ctor, (void *) &tmp_copy, sizeof(zval *)); |
528 | } |
529 | else { |
530 | object_init(return_value); |
531 | zend_hash_copy(Z_OBJPROP_P(return_value), Z_ARRVAL_PP(agent), (copy_ctor_func_t) browscap_zval_copy_ctor, (void *) &tmp_copy, sizeof(zval *)); |
532 | } |
533 | |
534 | while (zend_hash_find(Z_ARRVAL_PP(agent), "parent" , sizeof("parent" ), (void **) &z_agent_name) == SUCCESS) { |
535 | if (zend_hash_find(bdata->htab, Z_STRVAL_PP(z_agent_name), Z_STRLEN_PP(z_agent_name) + 1, (void **)&agent) == FAILURE) { |
536 | break; |
537 | } |
538 | |
539 | if (return_array) { |
540 | zend_hash_merge(Z_ARRVAL_P(return_value), Z_ARRVAL_PP(agent), (copy_ctor_func_t) browscap_zval_copy_ctor, (void *) &tmp_copy, sizeof(zval *), 0); |
541 | } |
542 | else { |
543 | zend_hash_merge(Z_OBJPROP_P(return_value), Z_ARRVAL_PP(agent), (copy_ctor_func_t) browscap_zval_copy_ctor, (void *) &tmp_copy, sizeof(zval *), 0); |
544 | } |
545 | } |
546 | |
547 | efree(lookup_browser_name); |
548 | } |
549 | /* }}} */ |
550 | |
551 | /* |
552 | * Local variables: |
553 | * tab-width: 4 |
554 | * c-basic-offset: 4 |
555 | * End: |
556 | * vim600: sw=4 ts=4 fdm=marker |
557 | * vim<600: sw=4 ts=4 |
558 | */ |
559 | |