php-internal-docs 8.4.8
Unofficial docs for php/php-src
Loading...
Searching...
No Matches
browscap.c
Go to the documentation of this file.
1/*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
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 | https://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 | Author: Zeev Suraski <zeev@php.net> |
14 +----------------------------------------------------------------------+
15 */
16
17#include "php.h"
18#include "php_browscap.h"
19#include "php_ini.h"
20
21#include "zend_ini_scanner.h"
22#include "zend_globals.h"
23
24#define BROWSCAP_NUM_CONTAINS 5
25
30
31typedef struct {
34 uint32_t kv_start;
35 uint32_t kv_end;
36 /* We ensure that the length fits in 16 bits, so this is fine */
39 uint8_t prefix_len;
41
42typedef struct {
45 uint32_t kv_used;
46 uint32_t kv_size;
49
50/* browser data defined in startup phase, eagerly loaded in MINIT */
51static browser_data global_bdata = {0};
52
53/* browser data defined in activation phase, lazily loaded in get_browser.
54 * Per request and per thread, if applicable */
58
60#define BROWSCAP_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(browscap, v)
61
62#define DEFAULT_SECTION_NAME "Default Browser Capability Settings"
63
64/* OBJECTS_FIXME: This whole extension needs going through. The use of objects looks pretty broken here */
65
66static void browscap_entry_dtor(zval *zvalue)
67{
68 browscap_entry *entry = Z_PTR_P(zvalue);
70 if (entry->parent) {
72 }
73 efree(entry);
74}
75
76static void browscap_entry_dtor_persistent(zval *zvalue)
77{
78 browscap_entry *entry = Z_PTR_P(zvalue);
80 if (entry->parent) {
82 }
83 pefree(entry, 1);
84}
85
86static inline bool is_placeholder(char c) {
87 return c == '?' || c == '*';
88}
89
90/* Length of prefix not containing any wildcards */
91static uint8_t browscap_compute_prefix_len(const zend_string *pattern) {
92 size_t i;
93 for (i = 0; i < ZSTR_LEN(pattern); i++) {
94 if (is_placeholder(ZSTR_VAL(pattern)[i])) {
95 break;
96 }
97 }
98 return (uint8_t)MIN(i, UINT8_MAX);
99}
100
101static size_t browscap_compute_contains(
102 zend_string *pattern, size_t start_pos,
103 uint16_t *contains_start, uint8_t *contains_len) {
104 size_t i = start_pos;
105 /* Find first non-placeholder character after prefix */
106 for (; i < ZSTR_LEN(pattern); i++) {
107 if (!is_placeholder(ZSTR_VAL(pattern)[i])) {
108 /* Skip the case of a single non-placeholder character.
109 * Let's try to find something longer instead. */
110 if (i + 1 < ZSTR_LEN(pattern) &&
111 !is_placeholder(ZSTR_VAL(pattern)[i + 1])) {
112 break;
113 }
114 }
115 }
116 *contains_start = (uint16_t)i;
117
118 /* Find first placeholder character after that */
119 for (; i < ZSTR_LEN(pattern); i++) {
120 if (is_placeholder(ZSTR_VAL(pattern)[i])) {
121 break;
122 }
123 }
124 *contains_len = (uint8_t)MIN(i - *contains_start, UINT8_MAX);
125 return i;
126}
127
128/* Length of regex, including escapes, anchors, etc. */
129static size_t browscap_compute_regex_len(const zend_string *pattern) {
130 size_t i, len = ZSTR_LEN(pattern);
131 for (i = 0; i < ZSTR_LEN(pattern); i++) {
132 switch (ZSTR_VAL(pattern)[i]) {
133 case '*':
134 case '.':
135 case '\\':
136 case '(':
137 case ')':
138 case '~':
139 case '+':
140 len++;
141 break;
142 }
143 }
144
145 return len + sizeof("~^$~")-1;
146}
147
148static zend_string *browscap_convert_pattern(const zend_string *pattern, bool persistent) /* {{{ */
149{
150 size_t i, j=0;
151 char *t;
153
154 res = zend_string_alloc(browscap_compute_regex_len(pattern), persistent);
155 t = ZSTR_VAL(res);
156
157 t[j++] = '~';
158 t[j++] = '^';
159
160 for (i = 0; i < ZSTR_LEN(pattern); i++, j++) {
161 char c = ZSTR_VAL(pattern)[i];
162 switch (c) {
163 case '?':
164 t[j] = '.';
165 break;
166 case '*':
167 t[j++] = '.';
168 t[j] = '*';
169 break;
170 case '.':
171 t[j++] = '\\';
172 t[j] = '.';
173 break;
174 case '\\':
175 t[j++] = '\\';
176 t[j] = '\\';
177 break;
178 case '(':
179 t[j++] = '\\';
180 t[j] = '(';
181 break;
182 case ')':
183 t[j++] = '\\';
184 t[j] = ')';
185 break;
186 case '~':
187 t[j++] = '\\';
188 t[j] = '~';
189 break;
190 case '+':
191 t[j++] = '\\';
192 t[j] = '+';
193 break;
194 default:
195 t[j] = zend_tolower_ascii(c);
196 break;
197 }
198 }
199
200 t[j++] = '$';
201 t[j++] = '~';
202 t[j]=0;
203
204 ZSTR_LEN(res) = j;
205 return res;
206}
207/* }}} */
208
215
216static zend_string *browscap_intern_str(
217 browscap_parser_ctx *ctx, zend_string *str, bool persistent) {
218 zend_string *interned = zend_hash_find_ptr(&ctx->str_interned, str);
219 if (interned) {
220 zend_string_addref(interned);
221 } else {
222 interned = zend_string_copy(str);
223 if (persistent) {
224 interned = zend_new_interned_string(interned);
225 }
226 zend_hash_add_new_ptr(&ctx->str_interned, interned, interned);
227 }
228
229 return interned;
230}
231
232static zend_string *browscap_intern_str_ci(
233 browscap_parser_ctx *ctx, zend_string *str, bool persistent) {
235 zend_string *interned;
236 ALLOCA_FLAG(use_heap);
237
238 ZSTR_ALLOCA_ALLOC(lcname, ZSTR_LEN(str), use_heap);
240 interned = zend_hash_find_ptr(&ctx->str_interned, lcname);
241
242 if (interned) {
243 zend_string_addref(interned);
244 } else {
245 interned = zend_string_init(ZSTR_VAL(lcname), ZSTR_LEN(lcname), persistent);
246 if (persistent) {
247 interned = zend_new_interned_string(interned);
248 }
249 zend_hash_add_new_ptr(&ctx->str_interned, interned, interned);
250 }
251
252 ZSTR_ALLOCA_FREE(lcname, use_heap);
253 return interned;
254}
255
256static void browscap_add_kv(
258 if (bdata->kv_used == bdata->kv_size) {
259 bdata->kv_size *= 2;
260 bdata->kv = safe_perealloc(bdata->kv, sizeof(browscap_kv), bdata->kv_size, 0, persistent);
261 }
262
263 bdata->kv[bdata->kv_used].key = key;
264 bdata->kv[bdata->kv_used].value = value;
265 bdata->kv_used++;
266}
267
268static void browscap_entry_add_kv_to_existing_array(browser_data *bdata, browscap_entry *entry, HashTable *ht) {
269 for (uint32_t i = entry->kv_start; i < entry->kv_end; i++) {
270 zval tmp;
271 ZVAL_STR_COPY(&tmp, bdata->kv[i].value);
272 zend_hash_add(ht, bdata->kv[i].key, &tmp);
273 }
274}
275
276static HashTable *browscap_entry_to_array(browser_data *bdata, browscap_entry *entry) {
277 zval tmp;
278 HashTable *ht = zend_new_array(2 + (entry->parent ? 1 : 0) + (entry->kv_end - entry->kv_start));
279
280 ZVAL_STR(&tmp, browscap_convert_pattern(entry->pattern, 0));
281 zend_string *key = ZSTR_INIT_LITERAL("browser_name_regex", 0);
282 ZSTR_H(key) = zend_inline_hash_func("browser_name_regex", sizeof("browser_name_regex")-1);
283 zend_hash_add_new(ht, key, &tmp);
285
286 ZVAL_STR_COPY(&tmp, entry->pattern);
287 key = ZSTR_INIT_LITERAL("browser_name_pattern", 0);
288 ZSTR_H(key) = zend_inline_hash_func("browser_name_pattern", sizeof("browser_name_pattern")-1);
289 zend_hash_add_new(ht, key, &tmp);
291
292 if (entry->parent) {
293 ZVAL_STR_COPY(&tmp, entry->parent);
294 key = ZSTR_INIT_LITERAL("parent", 0);
295 ZSTR_H(key) = zend_inline_hash_func("parent", sizeof("parent")-1);
296 zend_hash_add_new(ht, key, &tmp);
298 }
299
300 browscap_entry_add_kv_to_existing_array(bdata, entry, ht);
301
302 return ht;
303}
304
305static void php_browscap_parser_cb(zval *arg1, zval *arg2, zval *arg3, int callback_type, void *arg) /* {{{ */
306{
308 browser_data *bdata = ctx->bdata;
310
311 if (!arg1) {
312 return;
313 }
314
315 switch (callback_type) {
317 if (ctx->current_entry != NULL && arg2) {
318 zend_string *new_value;
319
320 /* Set proper value for true/false settings */
324 ) {
325 new_value = ZSTR_CHAR('1');
330 ) {
331 new_value = ZSTR_EMPTY_ALLOC();
332 } else { /* Other than true/false setting */
333 new_value = browscap_intern_str(ctx, Z_STR_P(arg2), persistent);
334 }
335
337 /* parent entry cannot be same as current section -> causes infinite loop! */
338 if (ctx->current_section_name != NULL &&
340 ) {
341 zend_error(E_CORE_ERROR, "Invalid browscap ini file: "
342 "'Parent' value cannot be same as the section name: %s "
343 "(in file %s)", ZSTR_VAL(ctx->current_section_name), INI_STR("browscap"));
344 return;
345 }
346
347 if (ctx->current_entry->parent) {
348 zend_string_release(ctx->current_entry->parent);
349 }
350
351 ctx->current_entry->parent = new_value;
352 } else {
353 zend_string *new_key = browscap_intern_str_ci(ctx, Z_STR_P(arg1), persistent);
354 browscap_add_kv(bdata, new_key, new_value, persistent);
355 ctx->current_entry->kv_end = bdata->kv_used;
356 }
357 }
358 break;
360 {
361 browscap_entry *entry;
362 zend_string *pattern = Z_STR_P(arg1);
363 size_t pos;
364 int i;
365
366 if (ZSTR_LEN(pattern) > UINT16_MAX) {
368 "Skipping excessively long pattern of length %zd", ZSTR_LEN(pattern));
369 break;
370 }
371
372 if (persistent) {
373 pattern = zend_new_interned_string(zend_string_copy(pattern));
374 if (ZSTR_IS_INTERNED(pattern)) {
375 Z_TYPE_FLAGS_P(arg1) = 0;
376 } else {
377 zend_string_release(pattern);
378 }
379 }
380
381 entry = ctx->current_entry
383 zend_hash_update_ptr(bdata->htab, pattern, entry);
384
385 if (ctx->current_section_name) {
386 zend_string_release(ctx->current_section_name);
387 }
388 ctx->current_section_name = zend_string_copy(pattern);
389
390 entry->pattern = zend_string_copy(pattern);
391 entry->kv_end = entry->kv_start = bdata->kv_used;
392 entry->parent = NULL;
393
394 pos = entry->prefix_len = browscap_compute_prefix_len(pattern);
395 for (i = 0; i < BROWSCAP_NUM_CONTAINS; i++) {
396 pos = browscap_compute_contains(pattern, pos,
397 &entry->contains_start[i], &entry->contains_len[i]);
398 }
399 break;
400 }
401 }
402}
403/* }}} */
404
405static zend_result browscap_read_file(char *filename, browser_data *browdata, bool persistent) /* {{{ */
406{
408 browscap_parser_ctx ctx = {0};
409 FILE *fp;
410
411 if (filename == NULL || filename[0] == '\0') {
412 return FAILURE;
413 }
414
415 fp = VCWD_FOPEN(filename, "r");
416 if (!fp) {
417 zend_error(E_CORE_WARNING, "Cannot open \"%s\" for reading", filename);
418 return FAILURE;
419 }
420 zend_stream_init_fp(&fh, fp, filename);
421
422 browdata->htab = pemalloc(sizeof *browdata->htab, persistent);
423 zend_hash_init(browdata->htab, 0, NULL,
424 persistent ? browscap_entry_dtor_persistent : browscap_entry_dtor, persistent);
425
426 browdata->kv_size = 16 * 1024;
427 browdata->kv_used = 0;
428 browdata->kv = pemalloc(sizeof(browscap_kv) * browdata->kv_size, persistent);
429
430 /* Create parser context */
431 ctx.bdata = browdata;
432 ctx.current_entry = NULL;
434 /* No dtor because we don't inc the refcount for the reference stored within the hash table's entry value
435 * as the hash table is only temporary anyway. */
437
439 (zend_ini_parser_cb_t) php_browscap_parser_cb, &ctx);
440
441 /* Destroy parser context */
442 if (ctx.current_section_name) {
443 zend_string_release(ctx.current_section_name);
444 }
447
448 return SUCCESS;
449}
450/* }}} */
451
452#ifdef ZTS
453static void browscap_globals_ctor(zend_browscap_globals *browscap_globals) /* {{{ */
454{
455 browscap_globals->activation_bdata.htab = NULL;
456 browscap_globals->activation_bdata.kv = NULL;
457 browscap_globals->activation_bdata.filename[0] = '\0';
458}
459/* }}} */
460#endif
461
462static void browscap_bdata_dtor(browser_data *bdata, bool persistent) /* {{{ */
463{
464 if (bdata->htab != NULL) {
465 uint32_t i;
466
467 zend_hash_destroy(bdata->htab);
468 pefree(bdata->htab, persistent);
469 bdata->htab = NULL;
470
471 for (i = 0; i < bdata->kv_used; i++) {
472 zend_string_release(bdata->kv[i].key);
473 zend_string_release(bdata->kv[i].value);
474 }
475 pefree(bdata->kv, persistent);
476 bdata->kv = NULL;
477 }
478 bdata->filename[0] = '\0';
479}
480/* }}} */
481
482/* {{{ PHP_INI_MH */
483PHP_INI_MH(OnChangeBrowscap)
484{
485 if (stage == PHP_INI_STAGE_STARTUP) {
486 /* value handled in browscap.c's MINIT */
487 return SUCCESS;
488 } else if (stage == PHP_INI_STAGE_ACTIVATE) {
490 if (bdata->filename[0] != '\0') {
491 browscap_bdata_dtor(bdata, 0);
492 }
493 if (VCWD_REALPATH(ZSTR_VAL(new_value), bdata->filename) == NULL) {
494 return FAILURE;
495 }
496 return SUCCESS;
497 }
498
499 return FAILURE;
500}
501/* }}} */
502
503PHP_MINIT_FUNCTION(browscap) /* {{{ */
504{
505 char *browscap = INI_STR("browscap");
506
507#ifdef ZTS
508 ts_allocate_id(&browscap_globals_id, sizeof(browser_data), (ts_allocate_ctor) browscap_globals_ctor, NULL);
509#endif
510 /* ctor call not really needed for non-ZTS */
511
512 if (browscap && browscap[0]) {
513 if (browscap_read_file(browscap, &global_bdata, true) == FAILURE) {
514 return FAILURE;
515 }
516 }
517
518 return SUCCESS;
519}
520/* }}} */
521
522PHP_RSHUTDOWN_FUNCTION(browscap) /* {{{ */
523{
525 if (bdata->filename[0] != '\0') {
526 browscap_bdata_dtor(bdata, 0);
527 }
528
529 return SUCCESS;
530}
531/* }}} */
532
533PHP_MSHUTDOWN_FUNCTION(browscap) /* {{{ */
534{
535 browscap_bdata_dtor(&global_bdata, 1);
536
537 return SUCCESS;
538}
539/* }}} */
540
541static inline size_t browscap_get_minimum_length(const browscap_entry *entry) {
542 size_t len = entry->prefix_len;
543 int i;
544 for (i = 0; i < BROWSCAP_NUM_CONTAINS; i++) {
545 len += entry->contains_len[i];
546 }
547 return len;
548}
549
550static bool browscap_match_string_wildcard(const char *s, const char *s_end, const char *pattern, const char *pattern_end)
551{
552 const char *pattern_current = pattern;
553 const char *s_current = s;
554
555 const char *wildcard_pattern_restore_pos = NULL;
556 const char *wildcard_s_restore_pos = NULL;
557
558 while (s_current < s_end) {
559 char pattern_char = *pattern_current;
560 char s_char = *s_current;
561
562 if (pattern_char == '*') {
563 /* Collapse wildcards */
564 pattern_current++;
565 while (pattern_current < pattern_end && *pattern_current == '*') {
566 pattern_current++;
567 }
568
569 /* If we're at the end of the pattern, it means that the ending was just '*', so this is a trivial match */
570 if (pattern_current == pattern_end) {
571 return true;
572 }
573
574 /* Optimization: if there is a non-wildcard character X after a *, then we can immediately jump to the first
575 * character X in s starting from s_current because it is the only way to match beyond the *. */
576 if (*pattern_current != '?') {
577 while (s_current < s_end && *s_current != *pattern_current) {
578 s_current++;
579 }
580 }
581
582 /* We will first assume the skipped part by * is a 0-length string (or n-length if the optimization above skipped n characters).
583 * When a mismatch happens we will backtrack and move s one position to assume * skipped a 1-length string.
584 * Then 2, 3, 4, ... */
585 wildcard_pattern_restore_pos = pattern_current;
586 wildcard_s_restore_pos = s_current;
587
588 continue;
589 } else if (pattern_char == s_char || pattern_char == '?') {
590 /* Match */
591 pattern_current++;
592 s_current++;
593
594 /* If this was the last character of the pattern, we either fully matched s, or we have a mismatch */
595 if (pattern_current == pattern_end) {
596 if (s_current == s_end) {
597 return true;
598 }
599 /* Fallthrough to mismatch */
600 } else {
601 continue;
602 }
603 }
604
605 /* Mismatch */
606 if (wildcard_pattern_restore_pos) {
607 pattern_current = wildcard_pattern_restore_pos;
608 wildcard_s_restore_pos++;
609 s_current = wildcard_s_restore_pos;
610 } else {
611 /* No wildcard is active, so it is impossible to match */
612 return false;
613 }
614 }
615
616 /* Skip remaining * wildcards, they match nothing here as we are at the end of s */
617 while (pattern_current < pattern_end && *pattern_current == '*') {
618 pattern_current++;
619 }
620
621 ZEND_ASSERT(s_current == s_end);
622 return pattern_current == pattern_end;
623}
624
625static int browser_reg_compare(browscap_entry *entry, const zend_string *agent_name, browscap_entry **found_entry_ptr, size_t *cached_prev_len) /* {{{ */
626{
627 browscap_entry *found_entry = *found_entry_ptr;
628 ALLOCA_FLAG(use_heap)
629 zend_string *pattern_lc;
630 const char *cur;
631
632 /* Lowercase the pattern, the agent name is already lowercase */
633 ZSTR_ALLOCA_ALLOC(pattern_lc, ZSTR_LEN(entry->pattern), use_heap);
634 zend_str_tolower_copy(ZSTR_VAL(pattern_lc), ZSTR_VAL(entry->pattern), ZSTR_LEN(entry->pattern));
635
636 /* Check if the agent contains the "contains" portions */
637 cur = ZSTR_VAL(agent_name) + entry->prefix_len;
638 for (int i = 0; i < BROWSCAP_NUM_CONTAINS; i++) {
639 if (entry->contains_len[i] != 0) {
640 cur = zend_memnstr(cur,
641 ZSTR_VAL(pattern_lc) + entry->contains_start[i],
642 entry->contains_len[i],
643 ZSTR_VAL(agent_name) + ZSTR_LEN(agent_name));
644 if (!cur) {
645 ZSTR_ALLOCA_FREE(pattern_lc, use_heap);
646 return 0;
647 }
648 cur += entry->contains_len[i];
649 }
650 }
651
652 /* See if we have an exact match, if so, we're done... */
653 if (zend_string_equals(agent_name, pattern_lc)) {
654 *found_entry_ptr = entry;
655 /* cached_prev_len doesn't matter here because we end the search when an exact match is found. */
656 ZSTR_ALLOCA_FREE(pattern_lc, use_heap);
657 return 1;
658 }
659
660 if (browscap_match_string_wildcard(
661 ZSTR_VAL(agent_name) + entry->prefix_len,
662 ZSTR_VAL(agent_name) + ZSTR_LEN(agent_name),
663 ZSTR_VAL(pattern_lc) + entry->prefix_len,
664 ZSTR_VAL(pattern_lc) + ZSTR_LEN(pattern_lc)
665 )) {
666 /* If we've found a possible browser, we need to do a comparison of the
667 number of characters changed in the user agent being checked versus
668 the previous match found and the current match. */
669 size_t curr_len = entry->prefix_len; /* Start from the prefix because the prefix is free of wildcards */
670 const zend_string *current_match = entry->pattern;
671 for (size_t i = curr_len; i < ZSTR_LEN(current_match); i++) {
672 switch (ZSTR_VAL(current_match)[i]) {
673 case '?':
674 case '*':
675 /* do nothing, ignore these characters in the count */
676 break;
677
678 default:
679 ++curr_len;
680 }
681 }
682
683 if (found_entry) {
684 /* Pick which browser pattern replaces the least amount of
685 characters when compared to the original user agent string... */
686 if (*cached_prev_len < curr_len) {
687 *found_entry_ptr = entry;
688 *cached_prev_len = curr_len;
689 }
690 } else {
691 *found_entry_ptr = entry;
692 *cached_prev_len = curr_len;
693 }
694 }
695
696 ZSTR_ALLOCA_FREE(pattern_lc, use_heap);
697 return 0;
698}
699/* }}} */
700
701/* {{{ 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. */
703{
704 zend_string *agent_name = NULL, *lookup_browser_name;
705 bool return_array = 0;
706 browser_data *bdata;
707 browscap_entry *found_entry = NULL;
708 HashTable *agent_ht;
709
712 Z_PARAM_STR_OR_NULL(agent_name)
713 Z_PARAM_BOOL(return_array)
715
716 if (BROWSCAP_G(activation_bdata).filename[0] != '\0') {
718 if (bdata->htab == NULL) { /* not initialized yet */
719 if (browscap_read_file(bdata->filename, bdata, false) == FAILURE) {
721 }
722 }
723 } else {
724 if (!global_bdata.htab) {
725 php_error_docref(NULL, E_WARNING, "browscap ini directive not set");
727 }
728 bdata = &global_bdata;
729 }
730
731 if (agent_name == NULL) {
732 zval *http_user_agent = NULL;
733 if (Z_TYPE(PG(http_globals)[TRACK_VARS_SERVER]) == IS_ARRAY
734 || zend_is_auto_global(ZSTR_KNOWN(ZEND_STR_AUTOGLOBAL_SERVER))) {
735 http_user_agent = zend_hash_str_find(
736 Z_ARRVAL_P(&PG(http_globals)[TRACK_VARS_SERVER]),
737 "HTTP_USER_AGENT", sizeof("HTTP_USER_AGENT")-1);
738 }
739 if (http_user_agent == NULL) {
740 php_error_docref(NULL, E_WARNING, "HTTP_USER_AGENT variable is not set, cannot determine user agent name");
742 }
743 agent_name = Z_STR_P(http_user_agent);
744 }
745
746 lookup_browser_name = zend_string_tolower(agent_name);
747 found_entry = zend_hash_find_ptr(bdata->htab, lookup_browser_name);
748 if (found_entry == NULL) {
749 browscap_entry *entry;
750 size_t cached_prev_len = 0; /* silence compiler warning */
751
752 ZEND_HASH_MAP_FOREACH_PTR(bdata->htab, entry) {
753 /* The following two early-skip checks are inside this loop instead of inside browser_reg_compare().
754 * That's because we want to avoid the call frame overhead, especially as browser_reg_compare() is
755 * a function that uses alloca(). */
756
757 /* Agent name too short */
758 if (ZSTR_LEN(lookup_browser_name) < browscap_get_minimum_length(entry)) {
759 continue;
760 }
761
762 /* Quickly discard patterns where the prefix doesn't match. */
763 bool prefix_matches = true;
764 for (size_t i = 0; i < entry->prefix_len; i++) {
765 if (ZSTR_VAL(lookup_browser_name)[i] != zend_tolower_ascii(ZSTR_VAL(entry->pattern)[i])) {
766 prefix_matches = false;
767 break;
768 }
769 }
770 if (!prefix_matches) {
771 continue;
772 }
773
774 if (browser_reg_compare(entry, lookup_browser_name, &found_entry, &cached_prev_len)) {
775 break;
776 }
778
779 if (found_entry == NULL) {
780 found_entry = zend_hash_str_find_ptr(bdata->htab,
782 if (found_entry == NULL) {
783 zend_string_release_ex(lookup_browser_name, false);
785 }
786 }
787 }
788
789 zend_string_release_ex(lookup_browser_name, false);
790
791 agent_ht = browscap_entry_to_array(bdata, found_entry);
792
793 if (return_array) {
794 RETVAL_ARR(agent_ht);
795 } else {
797 }
798
799 HashTable *target_ht = return_array ? Z_ARRVAL_P(return_value) : Z_OBJPROP_P(return_value);
800
801 while (found_entry->parent) {
802 found_entry = zend_hash_find_ptr(bdata->htab, found_entry->parent);
803 if (found_entry == NULL) {
804 break;
805 }
806
807 browscap_entry_add_kv_to_existing_array(bdata, found_entry, target_ht);
808 }
809}
810/* }}} */
size_t len
Definition apprentice.c:174
get_browser(?string $user_agent=null, bool $return_array=false)
#define BROWSCAP_NUM_CONTAINS
Definition browscap.c:24
browser_data activation_bdata
Definition browscap.c:56
struct _browscap_parser_ctx browscap_parser_ctx
#define BROWSCAP_G(v)
Definition browscap.c:60
#define DEFAULT_SECTION_NAME
Definition browscap.c:62
char s[4]
Definition cdf.c:77
zend_string * res
Definition ffi.c:4692
zval * arg
Definition ffi.c:3975
HashTable * ht
Definition ffi.c:4838
ffi persistent
Definition ffi.c:3633
#define NULL
Definition gdcache.h:45
#define SUCCESS
Definition hash_sha3.c:261
again j
PHPAPI ZEND_COLD void php_error_docref(const char *docref, int type, const char *format,...)
Definition main.c:1173
#define PHP_FUNCTION
Definition php.h:364
#define PHP_MSHUTDOWN_FUNCTION
Definition php.h:401
#define PHP_MINIT_FUNCTION
Definition php.h:400
#define PHP_RSHUTDOWN_FUNCTION
Definition php.h:403
unsigned const char * pos
Definition php_ffi.h:52
#define TRACK_VARS_SERVER
Definition php_globals.h:43
#define PG(v)
Definition php_globals.h:31
#define PHP_INI_STAGE_ACTIVATE
Definition php_ini.h:73
#define PHP_INI_MH
Definition php_ini.h:49
#define PHP_INI_STAGE_STARTUP
Definition php_ini.h:71
unsigned char key[REFLECTION_KEY_LEN]
zend_string * lcname
browscap_entry * current_entry
Definition browscap.c:211
browser_data * bdata
Definition browscap.c:210
HashTable str_interned
Definition browscap.c:213
zend_string * current_section_name
Definition browscap.c:212
Definition browscap.c:31
zend_string * parent
Definition browscap.c:33
uint32_t kv_end
Definition browscap.c:35
uint32_t kv_start
Definition browscap.c:34
uint8_t contains_len[BROWSCAP_NUM_CONTAINS]
Definition browscap.c:38
uint16_t contains_start[BROWSCAP_NUM_CONTAINS]
Definition browscap.c:37
zend_string * pattern
Definition browscap.c:32
uint8_t prefix_len
Definition browscap.c:39
zend_string * value
Definition browscap.c:28
zend_string * key
Definition browscap.c:27
uint32_t kv_used
Definition browscap.c:45
HashTable * htab
Definition browscap.c:43
browscap_kv * kv
Definition browscap.c:44
char filename[MAXPATHLEN]
Definition browscap.c:47
uint32_t kv_size
Definition browscap.c:46
ZEND_API zend_class_entry * zend_standard_class_def
Definition zend.c:83
ZEND_API ZEND_COLD void zend_error(int type, const char *format,...)
Definition zend.c:1666
ZEND_API zend_result object_and_properties_init(zval *arg, zend_class_entry *class_type, HashTable *properties)
Definition zend_API.c:1843
#define ZEND_PARSE_PARAMETERS_END()
Definition zend_API.h:1641
#define RETURN_FALSE
Definition zend_API.h:1058
#define Z_PARAM_STR_OR_NULL(dest)
Definition zend_API.h:2089
#define ZEND_DECLARE_MODULE_GLOBALS(module_name)
Definition zend_API.h:268
#define Z_PARAM_OPTIONAL
Definition zend_API.h:1667
#define RETVAL_ARR(r)
Definition zend_API.h:1024
#define ZEND_PARSE_PARAMETERS_START(min_num_args, max_num_args)
Definition zend_API.h:1620
#define ZEND_END_MODULE_GLOBALS(module_name)
Definition zend_API.h:248
#define Z_PARAM_BOOL(dest)
Definition zend_API.h:1726
#define ZEND_BEGIN_MODULE_GLOBALS(module_name)
Definition zend_API.h:246
#define efree(ptr)
Definition zend_alloc.h:155
#define pefree(ptr, persistent)
Definition zend_alloc.h:191
#define pemalloc(size, persistent)
Definition zend_alloc.h:189
#define safe_perealloc(ptr, nmemb, size, offset, persistent)
Definition zend_alloc.h:203
struct _zval_struct zval
zend_string_release_ex(func->internal_function.function_name, 0)
ZEND_API bool zend_is_auto_global(zend_string *name)
ZEND_API void zend_destroy_file_handle(zend_file_handle *file_handle)
#define E_WARNING
Definition zend_errors.h:24
#define E_CORE_ERROR
Definition zend_errors.h:27
#define E_CORE_WARNING
Definition zend_errors.h:28
ZEND_API void ZEND_FASTCALL zend_hash_destroy(HashTable *ht)
Definition zend_hash.c:1727
ZEND_API zval *ZEND_FASTCALL zend_hash_str_find(const HashTable *ht, const char *str, size_t len)
Definition zend_hash.c:2689
ZEND_API zval *ZEND_FASTCALL zend_hash_add_new(HashTable *ht, zend_string *key, zval *pData)
Definition zend_hash.c:1007
ZEND_API zval *ZEND_FASTCALL zend_hash_add(HashTable *ht, zend_string *key, zval *pData)
Definition zend_hash.c:992
#define zend_hash_init(ht, nSize, pHashFunction, pDestructor, persistent)
Definition zend_hash.h:108
#define ZEND_HASH_MAP_FOREACH_PTR(ht, _ptr)
Definition zend_hash.h:1326
#define zend_new_array(size)
Definition zend_hash.h:338
#define ZEND_HASH_FOREACH_END()
Definition zend_hash.h:1086
ZEND_API zend_result zend_parse_ini_file(zend_file_handle *fh, bool unbuffered_errors, int scanner_mode, zend_ini_parser_cb_t ini_parser_cb, void *arg)
#define ZEND_INI_PARSER_ENTRY
Definition zend_ini.h:244
void(* zend_ini_parser_cb_t)(zval *arg1, zval *arg2, zval *arg3, int callback_type, void *arg)
Definition zend_ini.h:237
#define ZEND_INI_PARSER_SECTION
Definition zend_ini.h:245
#define INI_STR(name)
Definition zend_ini.h:195
struct _zend_file_handle zend_file_handle
#define ZEND_INI_SCANNER_RAW
struct _zend_string zend_string
ZEND_API char *ZEND_FASTCALL zend_str_tolower_copy(char *dest, const char *source, size_t length)
#define zend_tolower_ascii(c)
#define ALLOCA_FLAG(name)
#define MIN(a, b)
#define ZEND_ASSERT(c)
ZEND_API void zend_stream_init_fp(zend_file_handle *handle, FILE *fp, const char *filename)
Definition zend_stream.c:63
ZEND_API zend_new_interned_string_func_t zend_new_interned_string
Definition zend_string.c:30
#define ZSTR_ALLOCA_FREE(str, use_heap)
#define ZSTR_H(zstr)
Definition zend_string.h:70
#define ZSTR_IS_INTERNED(s)
Definition zend_string.h:84
#define ZSTR_VAL(zstr)
Definition zend_string.h:68
#define ZSTR_INIT_LITERAL(s, persistent)
#define ZSTR_KNOWN(idx)
#define ZSTR_EMPTY_ALLOC()
#define zend_string_equals_ci(s1, s2)
#define ZSTR_LEN(zstr)
Definition zend_string.h:69
#define zend_string_equals_literal_ci(str, c)
#define ZSTR_ALLOCA_ALLOC(str, _len, use_heap)
#define ZSTR_CHAR(c)
#define ZVAL_STR(z, s)
#define Z_ARRVAL_P(zval_p)
Definition zend_types.h:987
#define ZVAL_STR_COPY(z, s)
struct _zend_array HashTable
Definition zend_types.h:386
#define IS_ARRAY
Definition zend_types.h:607
#define Z_STR_P(zval_p)
Definition zend_types.h:972
#define Z_PTR_P(zval_p)
#define GC_FLAGS(p)
Definition zend_types.h:756
@ FAILURE
Definition zend_types.h:61
#define Z_OBJPROP_P(zval_p)
ZEND_RESULT_CODE zend_result
Definition zend_types.h:64
#define Z_TYPE_FLAGS_P(zval_p)
Definition zend_types.h:663
#define IS_ARRAY_PERSISTENT
Definition zend_types.h:824
#define Z_TYPE(zval)
Definition zend_types.h:659
#define VCWD_FOPEN(path, mode)
#define MAXPATHLEN
#define VCWD_REALPATH(path, real_path)
zval * return_value
zval * arg1
zval * arg2
zval * arg3
value