browscap.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  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. #include "php.h"
  17. #include "php_browscap.h"
  18. #include "php_ini.h"
  19. #include "php_string.h"
  20. #include "ext/pcre/php_pcre.h"
  21. #include "zend_ini_scanner.h"
  22. #include "zend_globals.h"
  23. #define BROWSCAP_NUM_CONTAINS 5
  24. typedef struct {
  25. zend_string *key;
  26. zend_string *value;
  27. } browscap_kv;
  28. typedef struct {
  29. zend_string *pattern;
  30. zend_string *parent;
  31. uint32_t kv_start;
  32. uint32_t kv_end;
  33. /* We ensure that the length fits in 16 bits, so this is fine */
  34. uint16_t contains_start[BROWSCAP_NUM_CONTAINS];
  35. uint8_t contains_len[BROWSCAP_NUM_CONTAINS];
  36. uint8_t prefix_len;
  37. } browscap_entry;
  38. typedef struct {
  39. HashTable *htab;
  40. browscap_kv *kv;
  41. uint32_t kv_used;
  42. uint32_t kv_size;
  43. char filename[MAXPATHLEN];
  44. } browser_data;
  45. /* browser data defined in startup phase, eagerly loaded in MINIT */
  46. static browser_data global_bdata = {0};
  47. /* browser data defined in activation phase, lazily loaded in get_browser.
  48. * Per request and per thread, if applicable */
  49. ZEND_BEGIN_MODULE_GLOBALS(browscap)
  50. browser_data activation_bdata;
  51. ZEND_END_MODULE_GLOBALS(browscap)
  52. ZEND_DECLARE_MODULE_GLOBALS(browscap)
  53. #define BROWSCAP_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(browscap, v)
  54. #define DEFAULT_SECTION_NAME "Default Browser Capability Settings"
  55. /* OBJECTS_FIXME: This whole extension needs going through. The use of objects looks pretty broken here */
  56. static void browscap_entry_dtor(zval *zvalue)
  57. {
  58. browscap_entry *entry = Z_PTR_P(zvalue);
  59. zend_string_release_ex(entry->pattern, 0);
  60. if (entry->parent) {
  61. zend_string_release_ex(entry->parent, 0);
  62. }
  63. efree(entry);
  64. }
  65. static void browscap_entry_dtor_persistent(zval *zvalue)
  66. {
  67. browscap_entry *entry = Z_PTR_P(zvalue);
  68. zend_string_release_ex(entry->pattern, 1);
  69. if (entry->parent) {
  70. zend_string_release_ex(entry->parent, 1);
  71. }
  72. pefree(entry, 1);
  73. }
  74. static inline bool is_placeholder(char c) {
  75. return c == '?' || c == '*';
  76. }
  77. /* Length of prefix not containing any wildcards */
  78. static uint8_t browscap_compute_prefix_len(zend_string *pattern) {
  79. size_t i;
  80. for (i = 0; i < ZSTR_LEN(pattern); i++) {
  81. if (is_placeholder(ZSTR_VAL(pattern)[i])) {
  82. break;
  83. }
  84. }
  85. return (uint8_t)MIN(i, UINT8_MAX);
  86. }
  87. static size_t browscap_compute_contains(
  88. zend_string *pattern, size_t start_pos,
  89. uint16_t *contains_start, uint8_t *contains_len) {
  90. size_t i = start_pos;
  91. /* Find first non-placeholder character after prefix */
  92. for (; i < ZSTR_LEN(pattern); i++) {
  93. if (!is_placeholder(ZSTR_VAL(pattern)[i])) {
  94. /* Skip the case of a single non-placeholder character.
  95. * Let's try to find something longer instead. */
  96. if (i + 1 < ZSTR_LEN(pattern) &&
  97. !is_placeholder(ZSTR_VAL(pattern)[i + 1])) {
  98. break;
  99. }
  100. }
  101. }
  102. *contains_start = (uint16_t)i;
  103. /* Find first placeholder character after that */
  104. for (; i < ZSTR_LEN(pattern); i++) {
  105. if (is_placeholder(ZSTR_VAL(pattern)[i])) {
  106. break;
  107. }
  108. }
  109. *contains_len = (uint8_t)MIN(i - *contains_start, UINT8_MAX);
  110. return i;
  111. }
  112. /* Length of regex, including escapes, anchors, etc. */
  113. static size_t browscap_compute_regex_len(zend_string *pattern) {
  114. size_t i, len = ZSTR_LEN(pattern);
  115. for (i = 0; i < ZSTR_LEN(pattern); i++) {
  116. switch (ZSTR_VAL(pattern)[i]) {
  117. case '*':
  118. case '.':
  119. case '\\':
  120. case '(':
  121. case ')':
  122. case '~':
  123. case '+':
  124. len++;
  125. break;
  126. }
  127. }
  128. return len + sizeof("~^$~")-1;
  129. }
  130. static zend_string *browscap_convert_pattern(zend_string *pattern, int persistent) /* {{{ */
  131. {
  132. size_t i, j=0;
  133. char *t;
  134. zend_string *res;
  135. char *lc_pattern;
  136. ALLOCA_FLAG(use_heap);
  137. res = zend_string_alloc(browscap_compute_regex_len(pattern), persistent);
  138. t = ZSTR_VAL(res);
  139. lc_pattern = do_alloca(ZSTR_LEN(pattern) + 1, use_heap);
  140. zend_str_tolower_copy(lc_pattern, ZSTR_VAL(pattern), ZSTR_LEN(pattern));
  141. t[j++] = '~';
  142. t[j++] = '^';
  143. for (i = 0; i < ZSTR_LEN(pattern); i++, j++) {
  144. switch (lc_pattern[i]) {
  145. case '?':
  146. t[j] = '.';
  147. break;
  148. case '*':
  149. t[j++] = '.';
  150. t[j] = '*';
  151. break;
  152. case '.':
  153. t[j++] = '\\';
  154. t[j] = '.';
  155. break;
  156. case '\\':
  157. t[j++] = '\\';
  158. t[j] = '\\';
  159. break;
  160. case '(':
  161. t[j++] = '\\';
  162. t[j] = '(';
  163. break;
  164. case ')':
  165. t[j++] = '\\';
  166. t[j] = ')';
  167. break;
  168. case '~':
  169. t[j++] = '\\';
  170. t[j] = '~';
  171. break;
  172. case '+':
  173. t[j++] = '\\';
  174. t[j] = '+';
  175. break;
  176. default:
  177. t[j] = lc_pattern[i];
  178. break;
  179. }
  180. }
  181. t[j++] = '$';
  182. t[j++] = '~';
  183. t[j]=0;
  184. ZSTR_LEN(res) = j;
  185. free_alloca(lc_pattern, use_heap);
  186. return res;
  187. }
  188. /* }}} */
  189. typedef struct _browscap_parser_ctx {
  190. browser_data *bdata;
  191. browscap_entry *current_entry;
  192. zend_string *current_section_name;
  193. HashTable str_interned;
  194. } browscap_parser_ctx;
  195. static zend_string *browscap_intern_str(
  196. browscap_parser_ctx *ctx, zend_string *str, bool persistent) {
  197. zend_string *interned = zend_hash_find_ptr(&ctx->str_interned, str);
  198. if (interned) {
  199. zend_string_addref(interned);
  200. } else {
  201. interned = zend_string_copy(str);
  202. if (persistent) {
  203. interned = zend_new_interned_string(str);
  204. }
  205. zend_hash_add_new_ptr(&ctx->str_interned, interned, interned);
  206. }
  207. return interned;
  208. }
  209. static zend_string *browscap_intern_str_ci(
  210. browscap_parser_ctx *ctx, zend_string *str, bool persistent) {
  211. zend_string *lcname;
  212. zend_string *interned;
  213. ALLOCA_FLAG(use_heap);
  214. ZSTR_ALLOCA_ALLOC(lcname, ZSTR_LEN(str), use_heap);
  215. zend_str_tolower_copy(ZSTR_VAL(lcname), ZSTR_VAL(str), ZSTR_LEN(str));
  216. interned = zend_hash_find_ptr(&ctx->str_interned, lcname);
  217. if (interned) {
  218. zend_string_addref(interned);
  219. } else {
  220. interned = zend_string_init(ZSTR_VAL(lcname), ZSTR_LEN(lcname), persistent);
  221. if (persistent) {
  222. interned = zend_new_interned_string(interned);
  223. }
  224. zend_hash_add_new_ptr(&ctx->str_interned, interned, interned);
  225. }
  226. ZSTR_ALLOCA_FREE(lcname, use_heap);
  227. return interned;
  228. }
  229. static void browscap_add_kv(
  230. browser_data *bdata, zend_string *key, zend_string *value, bool persistent) {
  231. if (bdata->kv_used == bdata->kv_size) {
  232. bdata->kv_size *= 2;
  233. bdata->kv = safe_perealloc(bdata->kv, sizeof(browscap_kv), bdata->kv_size, 0, persistent);
  234. }
  235. bdata->kv[bdata->kv_used].key = key;
  236. bdata->kv[bdata->kv_used].value = value;
  237. bdata->kv_used++;
  238. }
  239. static HashTable *browscap_entry_to_array(browser_data *bdata, browscap_entry *entry) {
  240. zval tmp;
  241. uint32_t i;
  242. HashTable *ht = zend_new_array(8);
  243. ZVAL_STR(&tmp, browscap_convert_pattern(entry->pattern, 0));
  244. zend_hash_str_add(ht, "browser_name_regex", sizeof("browser_name_regex")-1, &tmp);
  245. ZVAL_STR_COPY(&tmp, entry->pattern);
  246. zend_hash_str_add(ht, "browser_name_pattern", sizeof("browser_name_pattern")-1, &tmp);
  247. if (entry->parent) {
  248. ZVAL_STR_COPY(&tmp, entry->parent);
  249. zend_hash_str_add(ht, "parent", sizeof("parent")-1, &tmp);
  250. }
  251. for (i = entry->kv_start; i < entry->kv_end; i++) {
  252. ZVAL_STR_COPY(&tmp, bdata->kv[i].value);
  253. zend_hash_add(ht, bdata->kv[i].key, &tmp);
  254. }
  255. return ht;
  256. }
  257. static void php_browscap_parser_cb(zval *arg1, zval *arg2, zval *arg3, int callback_type, void *arg) /* {{{ */
  258. {
  259. browscap_parser_ctx *ctx = arg;
  260. browser_data *bdata = ctx->bdata;
  261. int persistent = GC_FLAGS(bdata->htab) & IS_ARRAY_PERSISTENT;
  262. if (!arg1) {
  263. return;
  264. }
  265. switch (callback_type) {
  266. case ZEND_INI_PARSER_ENTRY:
  267. if (ctx->current_entry != NULL && arg2) {
  268. zend_string *new_key, *new_value;
  269. /* Set proper value for true/false settings */
  270. if (zend_string_equals_literal_ci(Z_STR_P(arg2), "on")
  271. || zend_string_equals_literal_ci(Z_STR_P(arg2), "yes")
  272. || zend_string_equals_literal_ci(Z_STR_P(arg2), "true")
  273. ) {
  274. new_value = ZSTR_CHAR('1');
  275. } else if (zend_string_equals_literal_ci(Z_STR_P(arg2), "no")
  276. || zend_string_equals_literal_ci(Z_STR_P(arg2), "off")
  277. || zend_string_equals_literal_ci(Z_STR_P(arg2), "none")
  278. || zend_string_equals_literal_ci(Z_STR_P(arg2), "false")
  279. ) {
  280. new_value = ZSTR_EMPTY_ALLOC();
  281. } else { /* Other than true/false setting */
  282. new_value = browscap_intern_str(ctx, Z_STR_P(arg2), persistent);
  283. }
  284. if (zend_string_equals_literal_ci(Z_STR_P(arg1), "parent")) {
  285. /* parent entry can not be same as current section -> causes infinite loop! */
  286. if (ctx->current_section_name != NULL &&
  287. zend_string_equals_ci(ctx->current_section_name, Z_STR_P(arg2))
  288. ) {
  289. zend_error(E_CORE_ERROR, "Invalid browscap ini file: "
  290. "'Parent' value cannot be same as the section name: %s "
  291. "(in file %s)", ZSTR_VAL(ctx->current_section_name), INI_STR("browscap"));
  292. return;
  293. }
  294. if (ctx->current_entry->parent) {
  295. zend_string_release(ctx->current_entry->parent);
  296. }
  297. ctx->current_entry->parent = new_value;
  298. } else {
  299. new_key = browscap_intern_str_ci(ctx, Z_STR_P(arg1), persistent);
  300. browscap_add_kv(bdata, new_key, new_value, persistent);
  301. ctx->current_entry->kv_end = bdata->kv_used;
  302. }
  303. }
  304. break;
  305. case ZEND_INI_PARSER_SECTION:
  306. {
  307. browscap_entry *entry;
  308. zend_string *pattern = Z_STR_P(arg1);
  309. size_t pos;
  310. int i;
  311. if (ZSTR_LEN(pattern) > UINT16_MAX) {
  312. php_error_docref(NULL, E_WARNING,
  313. "Skipping excessively long pattern of length %zd", ZSTR_LEN(pattern));
  314. break;
  315. }
  316. if (persistent) {
  317. pattern = zend_new_interned_string(zend_string_copy(pattern));
  318. if (ZSTR_IS_INTERNED(pattern)) {
  319. Z_TYPE_FLAGS_P(arg1) = 0;
  320. } else {
  321. zend_string_release(pattern);
  322. }
  323. }
  324. entry = ctx->current_entry
  325. = pemalloc(sizeof(browscap_entry), persistent);
  326. zend_hash_update_ptr(bdata->htab, pattern, entry);
  327. if (ctx->current_section_name) {
  328. zend_string_release(ctx->current_section_name);
  329. }
  330. ctx->current_section_name = zend_string_copy(pattern);
  331. entry->pattern = zend_string_copy(pattern);
  332. entry->kv_end = entry->kv_start = bdata->kv_used;
  333. entry->parent = NULL;
  334. pos = entry->prefix_len = browscap_compute_prefix_len(pattern);
  335. for (i = 0; i < BROWSCAP_NUM_CONTAINS; i++) {
  336. pos = browscap_compute_contains(pattern, pos,
  337. &entry->contains_start[i], &entry->contains_len[i]);
  338. }
  339. break;
  340. }
  341. }
  342. }
  343. /* }}} */
  344. static void str_interned_dtor(zval *zv) {
  345. zend_string_release(Z_STR_P(zv));
  346. }
  347. static int browscap_read_file(char *filename, browser_data *browdata, int persistent) /* {{{ */
  348. {
  349. zend_file_handle fh;
  350. browscap_parser_ctx ctx = {0};
  351. FILE *fp;
  352. if (filename == NULL || filename[0] == '\0') {
  353. return FAILURE;
  354. }
  355. fp = VCWD_FOPEN(filename, "r");
  356. if (!fp) {
  357. zend_error(E_CORE_WARNING, "Cannot open \"%s\" for reading", filename);
  358. return FAILURE;
  359. }
  360. zend_stream_init_fp(&fh, fp, filename);
  361. browdata->htab = pemalloc(sizeof *browdata->htab, persistent);
  362. zend_hash_init(browdata->htab, 0, NULL,
  363. persistent ? browscap_entry_dtor_persistent : browscap_entry_dtor, persistent);
  364. browdata->kv_size = 16 * 1024;
  365. browdata->kv_used = 0;
  366. browdata->kv = pemalloc(sizeof(browscap_kv) * browdata->kv_size, persistent);
  367. /* Create parser context */
  368. ctx.bdata = browdata;
  369. ctx.current_entry = NULL;
  370. ctx.current_section_name = NULL;
  371. zend_hash_init(&ctx.str_interned, 8, NULL, str_interned_dtor, persistent);
  372. zend_parse_ini_file(&fh, 1, ZEND_INI_SCANNER_RAW,
  373. (zend_ini_parser_cb_t) php_browscap_parser_cb, &ctx);
  374. /* Destroy parser context */
  375. if (ctx.current_section_name) {
  376. zend_string_release(ctx.current_section_name);
  377. }
  378. zend_hash_destroy(&ctx.str_interned);
  379. zend_destroy_file_handle(&fh);
  380. return SUCCESS;
  381. }
  382. /* }}} */
  383. #ifdef ZTS
  384. static void browscap_globals_ctor(zend_browscap_globals *browscap_globals) /* {{{ */
  385. {
  386. browscap_globals->activation_bdata.htab = NULL;
  387. browscap_globals->activation_bdata.kv = NULL;
  388. browscap_globals->activation_bdata.filename[0] = '\0';
  389. }
  390. /* }}} */
  391. #endif
  392. static void browscap_bdata_dtor(browser_data *bdata, int persistent) /* {{{ */
  393. {
  394. if (bdata->htab != NULL) {
  395. uint32_t i;
  396. zend_hash_destroy(bdata->htab);
  397. pefree(bdata->htab, persistent);
  398. bdata->htab = NULL;
  399. for (i = 0; i < bdata->kv_used; i++) {
  400. zend_string_release(bdata->kv[i].key);
  401. zend_string_release(bdata->kv[i].value);
  402. }
  403. pefree(bdata->kv, persistent);
  404. bdata->kv = NULL;
  405. }
  406. bdata->filename[0] = '\0';
  407. }
  408. /* }}} */
  409. /* {{{ PHP_INI_MH */
  410. PHP_INI_MH(OnChangeBrowscap)
  411. {
  412. if (stage == PHP_INI_STAGE_STARTUP) {
  413. /* value handled in browscap.c's MINIT */
  414. return SUCCESS;
  415. } else if (stage == PHP_INI_STAGE_ACTIVATE) {
  416. browser_data *bdata = &BROWSCAP_G(activation_bdata);
  417. if (bdata->filename[0] != '\0') {
  418. browscap_bdata_dtor(bdata, 0);
  419. }
  420. if (VCWD_REALPATH(ZSTR_VAL(new_value), bdata->filename) == NULL) {
  421. return FAILURE;
  422. }
  423. return SUCCESS;
  424. }
  425. return FAILURE;
  426. }
  427. /* }}} */
  428. PHP_MINIT_FUNCTION(browscap) /* {{{ */
  429. {
  430. char *browscap = INI_STR("browscap");
  431. #ifdef ZTS
  432. ts_allocate_id(&browscap_globals_id, sizeof(browser_data), (ts_allocate_ctor) browscap_globals_ctor, NULL);
  433. #endif
  434. /* ctor call not really needed for non-ZTS */
  435. if (browscap && browscap[0]) {
  436. if (browscap_read_file(browscap, &global_bdata, 1) == FAILURE) {
  437. return FAILURE;
  438. }
  439. }
  440. return SUCCESS;
  441. }
  442. /* }}} */
  443. PHP_RSHUTDOWN_FUNCTION(browscap) /* {{{ */
  444. {
  445. browser_data *bdata = &BROWSCAP_G(activation_bdata);
  446. if (bdata->filename[0] != '\0') {
  447. browscap_bdata_dtor(bdata, 0);
  448. }
  449. return SUCCESS;
  450. }
  451. /* }}} */
  452. PHP_MSHUTDOWN_FUNCTION(browscap) /* {{{ */
  453. {
  454. browscap_bdata_dtor(&global_bdata, 1);
  455. return SUCCESS;
  456. }
  457. /* }}} */
  458. static inline size_t browscap_get_minimum_length(browscap_entry *entry) {
  459. size_t len = entry->prefix_len;
  460. int i;
  461. for (i = 0; i < BROWSCAP_NUM_CONTAINS; i++) {
  462. len += entry->contains_len[i];
  463. }
  464. return len;
  465. }
  466. static int browser_reg_compare(browscap_entry *entry, zend_string *agent_name, browscap_entry **found_entry_ptr) /* {{{ */
  467. {
  468. browscap_entry *found_entry = *found_entry_ptr;
  469. ALLOCA_FLAG(use_heap)
  470. zend_string *pattern_lc, *regex;
  471. const char *cur;
  472. int i;
  473. pcre2_code *re;
  474. pcre2_match_data *match_data;
  475. uint32_t capture_count;
  476. int rc;
  477. /* Agent name too short */
  478. if (ZSTR_LEN(agent_name) < browscap_get_minimum_length(entry)) {
  479. return 0;
  480. }
  481. /* Quickly discard patterns where the prefix doesn't match. */
  482. if (zend_binary_strcasecmp(
  483. ZSTR_VAL(agent_name), entry->prefix_len,
  484. ZSTR_VAL(entry->pattern), entry->prefix_len) != 0) {
  485. return 0;
  486. }
  487. /* Lowercase the pattern, the agent name is already lowercase */
  488. ZSTR_ALLOCA_ALLOC(pattern_lc, ZSTR_LEN(entry->pattern), use_heap);
  489. zend_str_tolower_copy(ZSTR_VAL(pattern_lc), ZSTR_VAL(entry->pattern), ZSTR_LEN(entry->pattern));
  490. /* Check if the agent contains the "contains" portions */
  491. cur = ZSTR_VAL(agent_name) + entry->prefix_len;
  492. for (i = 0; i < BROWSCAP_NUM_CONTAINS; i++) {
  493. if (entry->contains_len[i] != 0) {
  494. cur = zend_memnstr(cur,
  495. ZSTR_VAL(pattern_lc) + entry->contains_start[i],
  496. entry->contains_len[i],
  497. ZSTR_VAL(agent_name) + ZSTR_LEN(agent_name));
  498. if (!cur) {
  499. ZSTR_ALLOCA_FREE(pattern_lc, use_heap);
  500. return 0;
  501. }
  502. cur += entry->contains_len[i];
  503. }
  504. }
  505. /* See if we have an exact match, if so, we're done... */
  506. if (zend_string_equals(agent_name, pattern_lc)) {
  507. *found_entry_ptr = entry;
  508. ZSTR_ALLOCA_FREE(pattern_lc, use_heap);
  509. return 1;
  510. }
  511. regex = browscap_convert_pattern(entry->pattern, 0);
  512. re = pcre_get_compiled_regex(regex, &capture_count);
  513. if (re == NULL) {
  514. ZSTR_ALLOCA_FREE(pattern_lc, use_heap);
  515. zend_string_release(regex);
  516. return 0;
  517. }
  518. match_data = php_pcre_create_match_data(capture_count, re);
  519. if (!match_data) {
  520. ZSTR_ALLOCA_FREE(pattern_lc, use_heap);
  521. zend_string_release(regex);
  522. return 0;
  523. }
  524. rc = pcre2_match(re, (PCRE2_SPTR)ZSTR_VAL(agent_name), ZSTR_LEN(agent_name), 0, 0, match_data, php_pcre_mctx());
  525. php_pcre_free_match_data(match_data);
  526. if (PCRE2_ERROR_NOMATCH != rc) {
  527. /* If we've found a possible browser, we need to do a comparison of the
  528. number of characters changed in the user agent being checked versus
  529. the previous match found and the current match. */
  530. if (found_entry) {
  531. size_t i, prev_len = 0, curr_len = 0;
  532. zend_string *previous_match = found_entry->pattern;
  533. zend_string *current_match = entry->pattern;
  534. for (i = 0; i < ZSTR_LEN(previous_match); i++) {
  535. switch (ZSTR_VAL(previous_match)[i]) {
  536. case '?':
  537. case '*':
  538. /* do nothing, ignore these characters in the count */
  539. break;
  540. default:
  541. ++prev_len;
  542. }
  543. }
  544. for (i = 0; i < ZSTR_LEN(current_match); i++) {
  545. switch (ZSTR_VAL(current_match)[i]) {
  546. case '?':
  547. case '*':
  548. /* do nothing, ignore these characters in the count */
  549. break;
  550. default:
  551. ++curr_len;
  552. }
  553. }
  554. /* Pick which browser pattern replaces the least amount of
  555. characters when compared to the original user agent string... */
  556. if (prev_len < curr_len) {
  557. *found_entry_ptr = entry;
  558. }
  559. } else {
  560. *found_entry_ptr = entry;
  561. }
  562. }
  563. ZSTR_ALLOCA_FREE(pattern_lc, use_heap);
  564. zend_string_release(regex);
  565. return 0;
  566. }
  567. /* }}} */
  568. static void browscap_zval_copy_ctor(zval *p) /* {{{ */
  569. {
  570. if (Z_REFCOUNTED_P(p)) {
  571. zend_string *str;
  572. ZEND_ASSERT(Z_TYPE_P(p) == IS_STRING);
  573. str = Z_STR_P(p);
  574. if (!(GC_FLAGS(str) & GC_PERSISTENT)) {
  575. GC_ADDREF(str);
  576. } else {
  577. ZVAL_NEW_STR(p, zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), 0));
  578. }
  579. }
  580. }
  581. /* }}} */
  582. /* {{{ 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. */
  583. PHP_FUNCTION(get_browser)
  584. {
  585. zend_string *agent_name = NULL, *lookup_browser_name;
  586. bool return_array = 0;
  587. browser_data *bdata;
  588. browscap_entry *found_entry = NULL;
  589. HashTable *agent_ht;
  590. ZEND_PARSE_PARAMETERS_START(0, 2)
  591. Z_PARAM_OPTIONAL
  592. Z_PARAM_STR_OR_NULL(agent_name)
  593. Z_PARAM_BOOL(return_array)
  594. ZEND_PARSE_PARAMETERS_END();
  595. if (BROWSCAP_G(activation_bdata).filename[0] != '\0') {
  596. bdata = &BROWSCAP_G(activation_bdata);
  597. if (bdata->htab == NULL) { /* not initialized yet */
  598. if (browscap_read_file(bdata->filename, bdata, 0) == FAILURE) {
  599. RETURN_FALSE;
  600. }
  601. }
  602. } else {
  603. if (!global_bdata.htab) {
  604. php_error_docref(NULL, E_WARNING, "browscap ini directive not set");
  605. RETURN_FALSE;
  606. }
  607. bdata = &global_bdata;
  608. }
  609. if (agent_name == NULL) {
  610. zval *http_user_agent = NULL;
  611. if (Z_TYPE(PG(http_globals)[TRACK_VARS_SERVER]) == IS_ARRAY
  612. || zend_is_auto_global(ZSTR_KNOWN(ZEND_STR_AUTOGLOBAL_SERVER))) {
  613. http_user_agent = zend_hash_str_find(
  614. Z_ARRVAL_P(&PG(http_globals)[TRACK_VARS_SERVER]),
  615. "HTTP_USER_AGENT", sizeof("HTTP_USER_AGENT")-1);
  616. }
  617. if (http_user_agent == NULL) {
  618. php_error_docref(NULL, E_WARNING, "HTTP_USER_AGENT variable is not set, cannot determine user agent name");
  619. RETURN_FALSE;
  620. }
  621. agent_name = Z_STR_P(http_user_agent);
  622. }
  623. lookup_browser_name = zend_string_tolower(agent_name);
  624. found_entry = zend_hash_find_ptr(bdata->htab, lookup_browser_name);
  625. if (found_entry == NULL) {
  626. browscap_entry *entry;
  627. ZEND_HASH_FOREACH_PTR(bdata->htab, entry) {
  628. if (browser_reg_compare(entry, lookup_browser_name, &found_entry)) {
  629. break;
  630. }
  631. } ZEND_HASH_FOREACH_END();
  632. if (found_entry == NULL) {
  633. found_entry = zend_hash_str_find_ptr(bdata->htab,
  634. DEFAULT_SECTION_NAME, sizeof(DEFAULT_SECTION_NAME)-1);
  635. if (found_entry == NULL) {
  636. zend_string_release(lookup_browser_name);
  637. RETURN_FALSE;
  638. }
  639. }
  640. }
  641. agent_ht = browscap_entry_to_array(bdata, found_entry);
  642. if (return_array) {
  643. RETVAL_ARR(agent_ht);
  644. } else {
  645. object_and_properties_init(return_value, zend_standard_class_def, agent_ht);
  646. }
  647. while (found_entry->parent) {
  648. found_entry = zend_hash_find_ptr(bdata->htab, found_entry->parent);
  649. if (found_entry == NULL) {
  650. break;
  651. }
  652. agent_ht = browscap_entry_to_array(bdata, found_entry);
  653. if (return_array) {
  654. zend_hash_merge(Z_ARRVAL_P(return_value), agent_ht, (copy_ctor_func_t) browscap_zval_copy_ctor, 0);
  655. } else {
  656. zend_hash_merge(Z_OBJPROP_P(return_value), agent_ht, (copy_ctor_func_t) browscap_zval_copy_ctor, 0);
  657. }
  658. zend_hash_destroy(agent_ht);
  659. efree(agent_ht);
  660. }
  661. zend_string_release_ex(lookup_browser_name, 0);
  662. }
  663. /* }}} */