e2019ad47a4a4de301b7bc0f0a902ab9e4e7b3b9
[m6w6/ext-http] / http_api.c
1 /*
2 +----------------------------------------------------------------------+
3 | PECL :: http |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.0 of the PHP license, that |
6 | is bundled with this package in the file LICENSE, and is available |
7 | through the world-wide-web at http://www.php.net/license/3_0.txt. |
8 | If you did not receive a copy of the PHP license and are unable to |
9 | obtain it through the world-wide-web, please send a note to |
10 | license@php.net so we can mail you a copy immediately. |
11 +----------------------------------------------------------------------+
12 | Copyright (c) 2004-2005 Michael Wallner <mike@php.net> |
13 +----------------------------------------------------------------------+
14 */
15
16 /* $Id$ */
17
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
21 #include "php.h"
22
23 #include "SAPI.h"
24 #include "ext/standard/url.h"
25
26 #include "php_http.h"
27 #include "php_http_std_defs.h"
28 #include "php_http_api.h"
29 #include "php_http_headers_api.h"
30 #include "php_http_send_api.h"
31
32 #ifdef ZEND_ENGINE_2
33 # include "zend_exceptions.h"
34 # include "php_http_exception_object.h"
35 #endif
36
37 #include <ctype.h>
38
39 #ifdef HTTP_HAVE_MAGIC
40 # include <magic.h>
41 #endif
42
43 ZEND_EXTERN_MODULE_GLOBALS(http);
44
45 /* char *pretty_key(char *, size_t, zend_bool, zend_bool) */
46 char *_http_pretty_key(char *key, size_t key_len, zend_bool uctitle, zend_bool xhyphen)
47 {
48 if (key && key_len) {
49 size_t i;
50 int wasalpha;
51 if (wasalpha = isalpha((int) key[0])) {
52 key[0] = (char) (uctitle ? toupper((int) key[0]) : tolower((int) key[0]));
53 }
54 for (i = 1; i < key_len; i++) {
55 if (isalpha((int) key[i])) {
56 key[i] = (char) (((!wasalpha) && uctitle) ? toupper((int) key[i]) : tolower((int) key[i]));
57 wasalpha = 1;
58 } else {
59 if (xhyphen && (key[i] == '_')) {
60 key[i] = '-';
61 }
62 wasalpha = 0;
63 }
64 }
65 }
66 return key;
67 }
68 /* }}} */
69
70 /* {{{ */
71 void _http_key_list_default_decoder(const char *encoded, size_t encoded_len, char **decoded, size_t *decoded_len TSRMLS_DC)
72 {
73 *decoded = estrndup(encoded, encoded_len);
74 *decoded_len = (size_t) php_url_decode(*decoded, encoded_len);
75 }
76 /* }}} */
77
78 /* {{{ */
79 STATUS _http_parse_key_list(const char *list, HashTable *items, char separator, http_key_list_decode_t decode, zend_bool first_entry_is_name_value_pair TSRMLS_DC)
80 {
81 const char *key = list, *val = NULL;
82 int vallen = 0, keylen = 0, done = 0;
83 zval array;
84
85 Z_ARRVAL(array) = items;
86
87 if (!(val = strchr(list, '='))) {
88 return FAILURE;
89 }
90
91 #define HTTP_KEYLIST_VAL(array, k, str, len) \
92 { \
93 char *decoded; \
94 size_t decoded_len; \
95 if (decode) { \
96 decode(str, len, &decoded, &decoded_len TSRMLS_CC); \
97 } else { \
98 decoded_len = len; \
99 decoded = estrndup(str, decoded_len); \
100 } \
101 add_assoc_stringl(array, k, decoded, decoded_len, 0); \
102 }
103 #define HTTP_KEYLIST_FIXKEY() \
104 { \
105 while (isspace(*key)) ++key; \
106 keylen = val - key; \
107 while (isspace(key[keylen - 1])) --keylen; \
108 }
109 #define HTTP_KEYLIST_FIXVAL() \
110 { \
111 ++val; \
112 while (isspace(*val)) ++val; \
113 vallen = key - val; \
114 while (isspace(val[vallen - 1])) --vallen; \
115 }
116
117 HTTP_KEYLIST_FIXKEY();
118
119 if (first_entry_is_name_value_pair) {
120 HTTP_KEYLIST_VAL(&array, "name", key, keylen);
121
122 /* just one name=value */
123 if (!(key = strchr(val, separator))) {
124 key = val + strlen(val);
125 HTTP_KEYLIST_FIXVAL();
126 HTTP_KEYLIST_VAL(&array, "value", val, vallen);
127 return SUCCESS;
128 }
129 /* additional info appended */
130 else {
131 HTTP_KEYLIST_FIXVAL();
132 HTTP_KEYLIST_VAL(&array, "value", val, vallen);
133 }
134 }
135
136 do {
137 char *keydup = NULL;
138
139 if (!(val = strchr(key, '='))) {
140 break;
141 }
142
143 /* start at 0 if first_entry_is_name_value_pair==0 */
144 if (zend_hash_num_elements(items)) {
145 ++key;
146 }
147
148 HTTP_KEYLIST_FIXKEY();
149 keydup = estrndup(key, keylen);
150 if (!(key = strchr(val, separator))) {
151 done = 1;
152 key = val + strlen(val);
153 }
154 HTTP_KEYLIST_FIXVAL();
155 HTTP_KEYLIST_VAL(&array, keydup, val, vallen);
156 efree(keydup);
157 } while (!done);
158
159 return SUCCESS;
160 }
161 /* }}} */
162
163 /* {{{ void http_error(long, long, char*) */
164 void _http_error_ex(long type, long code, const char *format, ...)
165 {
166 va_list args;
167 TSRMLS_FETCH();
168
169 va_start(args, format);
170 if (type == E_THROW) {
171 #ifdef ZEND_ENGINE_2
172 char *message;
173 vspprintf(&message, 0, format, args);
174 zend_throw_exception(http_exception_get_for_code(code), message, code TSRMLS_CC);
175 #else
176 type = E_WARNING;
177 #endif
178 }
179 if (type != E_THROW) {
180 php_verror(NULL, "", type, format, args TSRMLS_CC);
181 }
182 va_end(args);
183 }
184 /* }}} */
185
186 /* {{{ void http_log(char *, char *, char *) */
187 void _http_log_ex(char *file, const char *ident, const char *message TSRMLS_DC)
188 {
189 time_t now;
190 struct tm nowtm;
191 char datetime[128];
192
193 time(&now);
194 strftime(datetime, sizeof(datetime), "%Y-%m-%d %H:%M:%S", php_localtime_r(&now, &nowtm));
195
196 #define HTTP_LOG_WRITE(file, type, msg) \
197 if (file && strlen(file)) { \
198 php_stream *log = php_stream_open_wrapper(file, "ab", REPORT_ERRORS|ENFORCE_SAFE_MODE, NULL); \
199 \
200 if (log) { \
201 php_stream_printf(log TSRMLS_CC, "%s [%12s] %32s <%s>%s", datetime, type, msg, SG(request_info).request_uri, PHP_EOL); \
202 php_stream_close(log); \
203 } \
204 \
205 }
206
207 HTTP_LOG_WRITE(file, ident, message);
208 HTTP_LOG_WRITE(HTTP_G(log).composite, ident, message);
209 }
210 /* }}} */
211
212 /* {{{ STATUS http_exit(int, char*, char*) */
213 STATUS _http_exit_ex(int status, char *header, char *body, zend_bool send_header TSRMLS_DC)
214 {
215 if (status || send_header) {
216 if (SUCCESS != http_send_status_header(status, send_header ? header : NULL)) {
217 http_error_ex(HE_WARNING, HTTP_E_HEADER, "Failed to exit with status/header: %d - %s", status, header ? header : "");
218 STR_FREE(header);
219 STR_FREE(body);
220 return FAILURE;
221 }
222 }
223
224 if (body) {
225 PHPWRITE(body, strlen(body));
226 }
227
228 switch (status)
229 {
230 case 301: http_log(HTTP_G(log).redirect, "301-REDIRECT", header); break;
231 case 302: http_log(HTTP_G(log).redirect, "302-REDIRECT", header); break;
232 case 304: http_log(HTTP_G(log).cache, "304-CACHE", header); break;
233 case 405: http_log(HTTP_G(log).allowed_methods, "405-ALLOWED", header); break;
234 default: http_log(NULL, header, body); break;
235 }
236
237 STR_FREE(header);
238 STR_FREE(body);
239
240 zend_bailout();
241 /* fake */
242 return SUCCESS;
243 }
244 /* }}} */
245
246 /* {{{ STATUS http_check_method(char *) */
247 STATUS _http_check_method_ex(const char *method, const char *methods)
248 {
249 const char *found;
250
251 if ( (found = strstr(methods, method)) &&
252 (found == method || !isalpha(found[-1])) &&
253 (!isalpha(found[strlen(method) + 1]))) {
254 return SUCCESS;
255 }
256 return FAILURE;
257 }
258 /* }}} */
259
260 /* {{{ zval *http_get_server_var_ex(char *, size_t) */
261 PHP_HTTP_API zval *_http_get_server_var_ex(const char *key, size_t key_size, zend_bool check TSRMLS_DC)
262 {
263 zval **hsv;
264 zval **var;
265
266 if (SUCCESS != zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **) &hsv)) {
267 return NULL;
268 }
269 if (SUCCESS != zend_hash_find(Z_ARRVAL_PP(hsv), (char *) key, key_size, (void **) &var)) {
270 return NULL;
271 }
272 if (check && !(Z_STRVAL_PP(var) && Z_STRLEN_PP(var))) {
273 return NULL;
274 }
275 return *var;
276 }
277 /* }}} */
278
279 /* {{{ STATUS http_get_request_body(char **, size_t *) */
280 PHP_HTTP_API STATUS _http_get_request_body_ex(char **body, size_t *length, zend_bool dup TSRMLS_DC)
281 {
282 *length = 0;
283 *body = NULL;
284
285 if (SG(request_info).raw_post_data) {
286 *length = SG(request_info).raw_post_data_length;
287 *body = (char *) (dup ? estrndup(SG(request_info).raw_post_data, *length) : SG(request_info).raw_post_data);
288 return SUCCESS;
289 }
290 return FAILURE;
291 }
292 /* }}} */
293
294 /* {{{ char *http_chunked_decode(char *, size_t, char **, size_t *) */
295 PHP_HTTP_API const char *_http_chunked_decode(const char *encoded, size_t encoded_len, char **decoded, size_t *decoded_len TSRMLS_DC)
296 {
297 const char *e_ptr;
298 char *d_ptr;
299
300 *decoded_len = 0;
301 *decoded = ecalloc(1, encoded_len);
302 d_ptr = *decoded;
303 e_ptr = encoded;
304
305 while (((e_ptr - encoded) - encoded_len) > 0) {
306 size_t chunk_len = 0, EOL_len = 0;
307 int eol_mismatch = 0;
308 char *n_ptr;
309
310 chunk_len = strtol(e_ptr, &n_ptr, 16);
311
312 /* check if:
313 * - we could not read in chunk size
314 * - chunk size is not followed by (CR)LF|NUL
315 */
316 if ((n_ptr == e_ptr) || (*n_ptr && (eol_mismatch = n_ptr != http_locate_eol(e_ptr, &EOL_len)))) {
317 /* don't fail on apperently not encoded data */
318 if (e_ptr == encoded) {
319 memcpy(*decoded, encoded, encoded_len);
320 *decoded_len = encoded_len;
321 return encoded + encoded_len;
322 } else {
323 efree(*decoded);
324 if (eol_mismatch) {
325 if (EOL_len == 2) {
326 http_error_ex(HE_WARNING, HTTP_E_ENCODING, "Invalid character (expected 0x0D 0x0A; got: 0x%X 0x%X)", *n_ptr, *(n_ptr + 1));
327 } else {
328 http_error_ex(HE_WARNING, HTTP_E_ENCODING, "Invalid character (expected 0x0A; got: 0x%X)", *n_ptr);
329 }
330 } else {
331 char *error = estrndup(n_ptr, strcspn(n_ptr, "\r\n "));
332 http_error_ex(HE_WARNING, HTTP_E_ENCODING, "Invalid chunk size: '%s' at pos %d", error, n_ptr - encoded);
333 efree(error);
334 }
335 return NULL;
336 }
337 } else {
338 e_ptr = n_ptr;
339 }
340
341 /* reached the end */
342 if (!chunk_len) {
343 break;
344 }
345
346 memcpy(d_ptr, e_ptr += EOL_len, chunk_len);
347 d_ptr += chunk_len;
348 e_ptr += chunk_len + EOL_len;
349 *decoded_len += chunk_len;
350 }
351
352 return e_ptr;
353 }
354 /* }}} */
355
356 /* {{{ char *http_guess_content_type(char *magic_file, long magic_mode, void *data, size_t size, http_send_mode mode) */
357 PHP_HTTP_API char *_http_guess_content_type(const char *magicfile, long magicmode, void *data_ptr, size_t data_len, http_send_mode data_mode TSRMLS_DC)
358 {
359 char *ct = NULL;
360
361 #ifdef HTTP_HAVE_MAGIC
362 struct magic_set *magic = magic_open(magicmode);
363
364 if (!magic) {
365 http_error_ex(HE_WARNING, HTTP_E_INVALID_PARAM, "Invalid magic mode: %ld", magicmode);
366 } else if (-1 == magic_load(magic, magicfile)) {
367 http_error_ex(HE_WARNING, HTTP_E_RUNTIME, "Failed to load magic database '%s'", magicfile);
368 } else {
369 const char *ctype = NULL;
370
371 switch (data_mode)
372 {
373 case SEND_RSRC:
374 {
375 char *buffer;
376 size_t b_len;
377
378 b_len = php_stream_copy_to_mem(data_ptr, &buffer, 65536, 0);
379 ctype = magic_buffer(magic, buffer, b_len);
380 efree(buffer);
381 }
382 break;
383
384 case SEND_DATA:
385 ctype = magic_buffer(magic, data_ptr, data_len);
386 break;
387
388 default:
389 ctype = magic_file(magic, data_ptr);
390 break;
391 }
392
393 if (ctype) {
394 ct = estrdup(ctype);
395 } else {
396 http_error(HE_WARNING, HTTP_E_RUNTIME, "Failed to guess Content-Type");
397 }
398
399 if (magic) {
400 magic_close(magic);
401 }
402 }
403 #else
404 http_error(HE_WARNING, HTTP_E_RUNTIME, "Cannot guess Content-Type; libmagic not available");
405 #endif
406
407 return ct;
408 }
409 /* }}} */
410 /*
411 * Local variables:
412 * tab-width: 4
413 * c-basic-offset: 4
414 * End:
415 * vim600: noet sw=4 ts=4 fdm=marker
416 * vim<600: noet sw=4 ts=4
417 */
418