1 /*
2     +--------------------------------------------------------------------+
3     | PECL :: http                                                       |
4     +--------------------------------------------------------------------+
5     | Redistribution and use in source and binary forms, with or without |
6     | modification, are permitted provided that the conditions mentioned |
7     | in the accompanying LICENSE file are met.                          |
8     +--------------------------------------------------------------------+
9     | Copyright (c) 2004-2014, Michael Wallner <mike@php.net>            |
10     +--------------------------------------------------------------------+
11 */
12 
13 #include "php_http_api.h"
14 
15 #ifndef PHP_HTTP_DEBUG_NEG
16 # define PHP_HTTP_DEBUG_NEG 0
17 #endif
18 
php_http_negotiate_sort(const void * first,const void * second)19 static int php_http_negotiate_sort(const void *first, const void *second)
20 {
21 	Bucket *b1 = (Bucket *) first, *b2 = (Bucket *) second;
22 	int result = numeric_compare_function(&b1->val, &b2->val);
23 
24 	return (result > 0 ? -1 : (result < 0 ? 1 : 0));
25 }
26 
27 #define M_PRI 5
28 #define M_SEC 2
29 #define M_ANY -1
30 #define M_NOT 0
31 #define M_ALL ~0
php_http_negotiate_match(const char * param_str,size_t param_len,const char * supported_str,size_t supported_len,const char * sep_str,size_t sep_len)32 static inline unsigned php_http_negotiate_match(const char *param_str, size_t param_len, const char *supported_str, size_t supported_len, const char *sep_str, size_t sep_len)
33 {
34 	unsigned match = M_NOT;
35 
36 	if (param_len == supported_len && !strncasecmp(param_str, supported_str, param_len)) {
37 		/* that was easy */
38 		match = M_ALL;
39 	} else if (sep_str && sep_len) {
40 		const char *param_sec = php_http_locate_str(param_str, param_len, sep_str, sep_len);
41 		size_t param_pri_len = param_sec ? param_sec - param_str : param_len;
42 		const char *supported_sec = php_http_locate_str(supported_str, supported_len, sep_str, sep_len);
43 		size_t supported_pri_len = supported_sec ? supported_sec - supported_str : supported_len;
44 		size_t cmp_len = MIN(param_pri_len, supported_pri_len);
45 
46 		if (((*param_str == '*') || (*supported_str == '*'))
47 		||	((param_pri_len == supported_pri_len) && !strncasecmp(param_str, supported_str, param_pri_len))
48 		||	((!param_sec || !supported_sec) && cmp_len && !strncasecmp(param_str, supported_str, cmp_len))
49 		) {
50 			match += M_PRI;
51 		}
52 
53 		if (param_sec && supported_sec
54 		&& ((*(param_sec + sep_len) == '*' || *(supported_sec + sep_len) == '*')
55 			|| !strcasecmp(param_sec, supported_sec)
56 			)
57 		) {
58 			match += M_SEC;
59 		}
60 
61 		if ((param_sec && *(param_sec + sep_len) == '*')
62 		||	(supported_sec && *(supported_sec + sep_len) == '*')
63 		||	((*param_str == '*') || (*supported_str == '*'))
64 		) {
65 			match += M_ANY;
66 		}
67 	}
68 #if PHP_HTTP_DEBUG_NEG
69 	fprintf(stderr, "match: %s == %s => %u\n", supported_str, param_str, match);
70 #endif
71 	return match;
72 }
php_http_negotiate_reduce(zval * p,int num_args,va_list args,zend_hash_key * hash_key)73 static int php_http_negotiate_reduce(zval *p, int num_args, va_list args, zend_hash_key *hash_key)
74 {
75 	unsigned best_match = 0;
76 	double q = 0;
77 	php_http_arrkey_t key;
78 	zval *value;
79 	zend_string *supported = zval_get_string(p);
80 	HashTable *params = va_arg(args, HashTable *);
81 	HashTable *result = va_arg(args, HashTable *);
82 	const char *sep_str = va_arg(args, const char *);
83 	size_t sep_len = va_arg(args, size_t);
84 
85 	ZEND_HASH_FOREACH_KEY_VAL(params, key.h, key.key, value)
86 	{
87 		unsigned match;
88 
89 #if PHP_HTTP_DEBUG_NEG
90 			fprintf(stderr, "match(%u) > best_match(%u) = %u (q=%f)\n", match, best_match, match>best_match, Z_DVAL_PP(val));
91 #endif
92 		php_http_arrkey_stringify(&key, NULL);
93 		match = php_http_negotiate_match(key.key->val, key.key->len, supported->val, supported->len, sep_str, sep_len);
94 
95 		if (match > best_match) {
96 			best_match = match;
97 			q = Z_DVAL_P(value) - 0.1 / match;
98 		}
99 		php_http_arrkey_dtor(&key);
100 	}
101 	ZEND_HASH_FOREACH_END();
102 
103 	if (q > 0) {
104 		zval tmp;
105 
106 		ZVAL_DOUBLE(&tmp, q);
107 		zend_hash_update(result, supported, &tmp);
108 	}
109 
110 	zend_string_release(supported);
111 	return ZEND_HASH_APPLY_KEEP;
112 }
113 
php_http_negotiate(const char * value_str,size_t value_len,HashTable * supported,const char * primary_sep_str,size_t primary_sep_len)114 HashTable *php_http_negotiate(const char *value_str, size_t value_len, HashTable *supported, const char *primary_sep_str, size_t primary_sep_len)
115 {
116 	HashTable *result = NULL;
117 
118 	if (value_str && value_len) {
119 		unsigned i = 0;
120 		zval arr, *val, *arg, *zq;
121 		HashTable params;
122 		php_http_arrkey_t key;
123 		php_http_params_opts_t opts;
124 
125 		zend_hash_init(&params, 10, NULL, ZVAL_PTR_DTOR, 0);
126 		php_http_params_opts_default_get(&opts);
127 		opts.input.str = estrndup(value_str, value_len);
128 		opts.input.len = value_len;
129 		opts.flags &= ~PHP_HTTP_PARAMS_RFC5987;
130 		php_http_params_parse(&params, &opts);
131 		efree(opts.input.str);
132 
133 		array_init(&arr);
134 
135 		ZEND_HASH_FOREACH_KEY_VAL(&params, key.h, key.key, val)
136 		{
137 			double q;
138 
139 			if ((arg = zend_hash_str_find(Z_ARRVAL_P(val), ZEND_STRL("arguments")))
140 			&&	(IS_ARRAY == Z_TYPE_P(arg))
141 			&&	(zq = zend_hash_str_find(Z_ARRVAL_P(arg), ZEND_STRL("q")))) {
142 				q = zval_get_double(zq);
143 			} else {
144 				q = 1.0 - (((double) ++i) / 100.0);
145 			}
146 
147 #if 0
148 			fprintf(stderr, "Q: %s=%1.3f\n", key.key->val, q);
149 #endif
150 
151 			if (key.key) {
152 				add_assoc_double_ex(&arr, key.key->val, key.key->len, q);
153 			} else {
154 				add_index_double(&arr, key.h, q);
155 			}
156 
157 		}
158 		ZEND_HASH_FOREACH_END();
159 
160 #if PHP_HTTP_DEBUG_NEG
161 		zend_print_zval_r(&arr, 1);
162 #endif
163 
164 		ALLOC_HASHTABLE(result);
165 		zend_hash_init(result, zend_hash_num_elements(supported), NULL, ZVAL_PTR_DTOR, 0);
166 		zend_hash_apply_with_arguments(supported, php_http_negotiate_reduce, 4, Z_ARRVAL(arr), result, primary_sep_str, primary_sep_len);
167 		zend_hash_destroy(&params);
168 		zval_dtor(&arr);
169 		zend_hash_sort(result, php_http_negotiate_sort, 0);
170 	}
171 
172 	return result;
173 }
174 
175 
176 
177 /*
178  * Local variables:
179  * tab-width: 4
180  * c-basic-offset: 4
181  * End:
182  * vim600: noet sw=4 ts=4 fdm=marker
183  * vim<600: noet sw=4 ts=4
184  */
185 
186 
187