- fix behaviour of http_build_url() when second parameter is NULL
[m6w6/ext-http] / http_url_api.c
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-2006, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
11 */
12
13 /* $Id$ */
14
15 #define HTTP_WANT_SAPI
16 #define HTTP_WANT_NETDB
17 #include "php_http.h"
18
19 #include "zend_ini.h"
20 #include "php_output.h"
21 #include "ext/standard/php_string.h"
22
23 #include "php_http_api.h"
24 #include "php_http_querystring_api.h"
25 #include "php_http_url_api.h"
26
27 static inline char *localhostname(void)
28 {
29 char hostname[1024] = {0};
30
31 #ifdef PHP_WIN32
32 if (SUCCESS == gethostname(hostname, lenof(hostname))) {
33 return estrdup(hostname);
34 }
35 #elif defined(HAVE_UNISTD_H)
36 if (SUCCESS == gethostname(hostname, lenof(hostname))) {
37 size_t hlen = strlen(hostname);
38
39 if (hlen <= lenof(hostname) - lenof("(none)")) {
40 hostname[hlen++] = '.';
41 if (SUCCESS == getdomainname(&hostname[hlen], lenof(hostname) - hlen)) {
42 if (!strcmp(&hostname[hlen], "(none)")) {
43 hostname[hlen - 1] = '\0';
44 }
45 return estrdup(hostname);
46 }
47 }
48 }
49 #endif
50 return estrdup("localhost");
51 }
52
53 PHP_MINIT_FUNCTION(http_url)
54 {
55 HTTP_LONG_CONSTANT("HTTP_URL_REPLACE", HTTP_URL_REPLACE);
56 HTTP_LONG_CONSTANT("HTTP_URL_JOIN_PATH", HTTP_URL_JOIN_PATH);
57 HTTP_LONG_CONSTANT("HTTP_URL_JOIN_QUERY", HTTP_URL_JOIN_QUERY);
58 HTTP_LONG_CONSTANT("HTTP_URL_STRIP_USER", HTTP_URL_STRIP_USER);
59 HTTP_LONG_CONSTANT("HTTP_URL_STRIP_PASS", HTTP_URL_STRIP_PASS);
60 HTTP_LONG_CONSTANT("HTTP_URL_STRIP_AUTH", HTTP_URL_STRIP_AUTH);
61 HTTP_LONG_CONSTANT("HTTP_URL_STRIP_PORT", HTTP_URL_STRIP_PORT);
62 HTTP_LONG_CONSTANT("HTTP_URL_STRIP_PATH", HTTP_URL_STRIP_PATH);
63 HTTP_LONG_CONSTANT("HTTP_URL_STRIP_QUERY", HTTP_URL_STRIP_QUERY);
64 HTTP_LONG_CONSTANT("HTTP_URL_STRIP_FRAGMENT", HTTP_URL_STRIP_FRAGMENT);
65 return SUCCESS;
66 }
67
68 PHP_HTTP_API char *_http_absolute_url(const char *url TSRMLS_DC)
69 {
70 char *abs = NULL;
71 php_url *purl = NULL;
72
73 if (url) {
74 purl = php_url_parse(abs = estrdup(url));
75 STR_SET(abs, NULL);
76 if (!purl) {
77 http_error_ex(HE_WARNING, HTTP_E_URL, "Could not parse URL (%s)", url);
78 return NULL;
79 }
80 }
81
82 http_build_url(0, purl, NULL, NULL, &abs, NULL);
83
84 if (purl) {
85 php_url_free(purl);
86 }
87
88 return abs;
89 }
90
91 /* {{{ void http_build_url(int flags, const php_url *, const php_url *, php_url **, char **, size_t *) */
92 PHP_HTTP_API void _http_build_url(int flags, const php_url *old_url, const php_url *new_url, php_url **url_ptr, char **url_str, size_t *url_len TSRMLS_DC)
93 {
94 #ifdef HTTP_HAVE_NETDB
95 struct servent *se;
96 #endif
97 php_url *url = ecalloc(1, sizeof(php_url));
98
99 #define __URLSET(u,n) \
100 ((u)&&(u)->n)
101 #define __URLCPY(n) \
102 url->n = __URLSET(new_url,n) ? estrdup(new_url->n) : (__URLSET(old_url,n) ? estrdup(old_url->n) : NULL)
103
104 if (!(flags & HTTP_URL_STRIP_PORT)) {
105 url->port = (new_url&&new_url->port) ? new_url->port : ((old_url) ? old_url->port : 0);
106 }
107 if ((!(flags & HTTP_URL_STRIP_AUTH)) && (!(flags & HTTP_URL_STRIP_USER))) {
108 __URLCPY(user);
109 }
110 if ((!(flags & HTTP_URL_STRIP_AUTH)) && (!(flags & HTTP_URL_STRIP_PASS))) {
111 __URLCPY(pass);
112 }
113
114 __URLCPY(scheme);
115 __URLCPY(host);
116
117 if (!(flags & HTTP_URL_STRIP_PATH)) {
118 if ((flags & HTTP_URL_JOIN_PATH) && __URLSET(old_url, path) && __URLSET(new_url, path) && *new_url->path != '/') {
119 size_t old_path_len = strlen(old_url->path), new_path_len = strlen(new_url->path);
120
121 url->path = ecalloc(1, old_path_len + new_path_len + 1 + 1);
122
123 strcat(url->path, old_url->path);
124 if (url->path[old_path_len - 1] != '/') {
125 php_dirname(url->path, old_path_len);
126 strcat(url->path, "/");
127 }
128 strcat(url->path, new_url->path);
129 } else {
130 __URLCPY(path);
131 }
132 }
133 if (!(flags & HTTP_URL_STRIP_QUERY)) {
134 if ((flags & HTTP_URL_JOIN_QUERY) && __URLSET(new_url, query) && __URLSET(old_url, query)) {
135 zval qarr, qstr;
136
137 INIT_PZVAL(&qstr);
138 INIT_PZVAL(&qarr);
139 array_init(&qarr);
140
141 ZVAL_STRING(&qstr, old_url->query, 0);
142 http_querystring_modify(&qarr, &qstr);
143 ZVAL_STRING(&qstr, new_url->query, 0);
144 http_querystring_modify(&qarr, &qstr);
145
146 ZVAL_NULL(&qstr);
147 http_querystring_update(&qarr, &qstr);
148 url->query = Z_STRVAL(qstr);
149 zval_dtor(&qarr);
150 } else {
151 __URLCPY(query);
152 }
153 }
154 if (!(flags & HTTP_URL_STRIP_FRAGMENT)) {
155 __URLCPY(fragment);
156 }
157
158 if (!url->scheme) {
159 switch (url->port)
160 {
161 case 443:
162 url->scheme = estrndup("https", lenof("https"));
163 break;
164
165 #ifndef HTTP_HAVE_NETDB
166 default:
167 #endif
168 case 80:
169 url->scheme = estrndup("http", lenof("http"));
170 break;
171
172 #ifdef HTTP_HAVE_NETDB
173 default:
174 if ((se = getservbyport(htons(url->port), "tcp")) && se->s_name) {
175 url->scheme = estrdup(se->s_name);
176 } else {
177 url->scheme = estrndup("http", lenof("http"));
178 }
179 break;
180 #endif
181 }
182 }
183
184 if (!url->host) {
185 zval *zhost;
186
187 if ((((zhost = http_get_server_var("HTTP_HOST")) ||
188 (zhost = http_get_server_var("SERVER_NAME")))) && Z_STRLEN_P(zhost)) {
189 url->host = estrndup(Z_STRVAL_P(zhost), Z_STRLEN_P(zhost));
190 } else {
191 url->host = localhostname();
192 }
193 }
194
195 if (!url->path) {
196 if (SG(request_info).request_uri && *SG(request_info).request_uri) {
197 const char *q = strchr(SG(request_info).request_uri, '?');
198
199 if (q) {
200 url->path = estrndup(SG(request_info).request_uri, q - SG(request_info).request_uri);
201 } else {
202 url->path = estrdup(SG(request_info).request_uri);
203 }
204 } else {
205 url->path = estrndup("/", 1);
206 }
207 } else if (*url->path != '/') {
208 if (SG(request_info).request_uri && *SG(request_info).request_uri) {
209 const char *q = strchr(SG(request_info).request_uri, '?');
210 char *uri, *path;
211 size_t len;
212
213 if (q) {
214 uri = estrndup(SG(request_info).request_uri, len = q - SG(request_info).request_uri);
215 } else {
216 uri = estrndup(SG(request_info).request_uri, len = strlen(SG(request_info).request_uri));
217 }
218
219 php_dirname(uri, len);
220 spprintf(&path, 0, "%s/%s", uri, url->path);
221 efree(uri);
222 STR_SET(url->path, path);
223 } else {
224 char *uri;
225
226 spprintf(&uri, 0, "/%s", url->path);
227 STR_SET(url->path, uri);
228 }
229 }
230 if (url->path) {
231 char *ptr, *end = url->path + strlen(url->path) + 1;
232
233 for (ptr = strstr(url->path, "/."); ptr; ptr = strstr(ptr, "/.")) {
234 switch (ptr[2])
235 {
236 case '\0':
237 ptr[1] = '\0';
238 break;
239
240 case '/':
241 memmove(&ptr[1], &ptr[3], end - &ptr[3]);
242 break;
243
244 case '.':
245 if (ptr[3] == '/') {
246 char *pos = &ptr[4];
247 while (ptr != url->path) {
248 if (*--ptr == '/') {
249 break;
250 }
251 }
252 memmove(&ptr[1], pos, end - pos);
253 }
254 break;
255
256 default:
257 /* something else */
258 ++ptr;
259 break;
260 }
261 }
262 }
263
264 if (url->port) {
265 if ( ((url->port == 80) && !strcmp(url->scheme, "http"))
266 || ((url->port ==443) && !strcmp(url->scheme, "https"))
267 #ifdef HTTP_HAVE_NETDB
268 || ((se = getservbyname(url->scheme, "tcp")) && se->s_port &&
269 (url->port == ntohs(se->s_port)))
270 #endif
271 ) {
272 url->port = 0;
273 }
274 }
275
276 if (url_str) {
277 size_t len;
278
279 *url_str = emalloc(HTTP_URL_MAXLEN + 1);
280
281 **url_str = '\0';
282 strlcat(*url_str, url->scheme, HTTP_URL_MAXLEN);
283 strlcat(*url_str, "://", HTTP_URL_MAXLEN);
284
285 if (url->user && *url->user) {
286 strlcat(*url_str, url->user, HTTP_URL_MAXLEN);
287 if (url->pass && *url->pass) {
288 strlcat(*url_str, ":", HTTP_URL_MAXLEN);
289 strlcat(*url_str, url->pass, HTTP_URL_MAXLEN);
290 }
291 strlcat(*url_str, "@", HTTP_URL_MAXLEN);
292 }
293
294 strlcat(*url_str, url->host, HTTP_URL_MAXLEN);
295
296 if (url->port) {
297 char port_str[8] = {0};
298
299 snprintf(port_str, lenof(port_str), "%d", (int) url->port);
300 strlcat(*url_str, ":", HTTP_URL_MAXLEN);
301 strlcat(*url_str, port_str, HTTP_URL_MAXLEN);
302 }
303
304 if (*url->path != '/') {
305 strlcat(*url_str, "/", HTTP_URL_MAXLEN);
306 }
307 strlcat(*url_str, url->path, HTTP_URL_MAXLEN);
308
309 if (url->query && *url->query) {
310 strlcat(*url_str, "?", HTTP_URL_MAXLEN);
311 strlcat(*url_str, url->query, HTTP_URL_MAXLEN);
312 }
313
314 if (url->fragment && *url->fragment) {
315 strlcat(*url_str, "#", HTTP_URL_MAXLEN);
316 strlcat(*url_str, url->fragment, HTTP_URL_MAXLEN);
317 }
318
319 if (HTTP_URL_MAXLEN == (len = strlen(*url_str))) {
320 http_error(HE_NOTICE, HTTP_E_URL, "Length of URL exceeds HTTP_URL_MAXLEN");
321 }
322 if (url_len) {
323 *url_len = len;
324 }
325 }
326
327 if (url_ptr) {
328 *url_ptr = url;
329 } else {
330 php_url_free(url);
331 }
332 }
333 /* }}} */
334
335 /* {{{ STATUS http_urlencode_hash_ex(HashTable *, zend_bool, char *, size_t, char **, size_t *) */
336 PHP_HTTP_API STATUS _http_urlencode_hash_ex(HashTable *hash, zend_bool override_argsep,
337 char *pre_encoded_data, size_t pre_encoded_len,
338 char **encoded_data, size_t *encoded_len TSRMLS_DC)
339 {
340 char *arg_sep;
341 size_t arg_sep_len;
342 phpstr *qstr = phpstr_new();
343
344 if (override_argsep || !(arg_sep_len = strlen(arg_sep = INI_STR("arg_separator.output")))) {
345 arg_sep = HTTP_URL_ARGSEP;
346 arg_sep_len = lenof(HTTP_URL_ARGSEP);
347 }
348
349 if (pre_encoded_len && pre_encoded_data) {
350 phpstr_append(qstr, pre_encoded_data, pre_encoded_len);
351 }
352
353 if (SUCCESS != http_urlencode_hash_recursive(hash, qstr, arg_sep, arg_sep_len, NULL, 0)) {
354 phpstr_free(&qstr);
355 return FAILURE;
356 }
357
358 phpstr_data(qstr, encoded_data, encoded_len);
359 phpstr_free(&qstr);
360
361 return SUCCESS;
362 }
363 /* }}} */
364
365 /* {{{ http_urlencode_hash_recursive */
366 PHP_HTTP_API STATUS _http_urlencode_hash_recursive(HashTable *ht, phpstr *str, const char *arg_sep, size_t arg_sep_len, const char *prefix, size_t prefix_len TSRMLS_DC)
367 {
368 char *key = NULL;
369 uint len = 0;
370 ulong idx = 0;
371 zval **data = NULL;
372 HashPosition pos;
373
374 if (!ht || !str) {
375 http_error(HE_WARNING, HTTP_E_INVALID_PARAM, "Invalid parameters");
376 return FAILURE;
377 }
378 if (ht->nApplyCount > 0) {
379 return SUCCESS;
380 }
381
382 FOREACH_HASH_KEYLENVAL(pos, ht, key, len, idx, data) {
383 char *encoded_key;
384 int encoded_len;
385 phpstr new_prefix;
386
387 if (!data || !*data) {
388 phpstr_dtor(str);
389 return FAILURE;
390 }
391
392 if (key) {
393 if (len && key[len - 1] == '\0') {
394 --len;
395 }
396 encoded_key = php_url_encode(key, len, &encoded_len);
397 key = NULL;
398 } else {
399 encoded_len = spprintf(&encoded_key, 0, "%ld", idx);
400 }
401
402 {
403 phpstr_init(&new_prefix);
404 if (prefix && prefix_len) {
405 phpstr_append(&new_prefix, prefix, prefix_len);
406 phpstr_appends(&new_prefix, "%5B");
407 }
408
409 phpstr_append(&new_prefix, encoded_key, encoded_len);
410 efree(encoded_key);
411
412 if (prefix && prefix_len) {
413 phpstr_appends(&new_prefix, "%5D");
414 }
415 phpstr_fix(&new_prefix);
416 }
417
418 if (Z_TYPE_PP(data) == IS_ARRAY) {
419 STATUS status;
420 ++ht->nApplyCount;
421 status = http_urlencode_hash_recursive(Z_ARRVAL_PP(data), str, arg_sep, arg_sep_len, PHPSTR_VAL(&new_prefix), PHPSTR_LEN(&new_prefix));
422 --ht->nApplyCount;
423 if (SUCCESS != status) {
424 phpstr_dtor(&new_prefix);
425 phpstr_dtor(str);
426 return FAILURE;
427 }
428 } else {
429 zval *val = zval_copy(IS_STRING, *data);
430
431 if (PHPSTR_LEN(str)) {
432 phpstr_append(str, arg_sep, arg_sep_len);
433 }
434 phpstr_append(str, PHPSTR_VAL(&new_prefix), PHPSTR_LEN(&new_prefix));
435 phpstr_appends(str, "=");
436
437 if (Z_STRLEN_P(val) && Z_STRVAL_P(val)) {
438 char *encoded_val;
439 int encoded_len;
440
441 encoded_val = php_url_encode(Z_STRVAL_P(val), Z_STRLEN_P(val), &encoded_len);
442 phpstr_append(str, encoded_val, encoded_len);
443 efree(encoded_val);
444 }
445
446 zval_free(&val);
447 }
448 phpstr_dtor(&new_prefix);
449 }
450 return SUCCESS;
451 }
452 /* }}} */
453
454 /*
455 * Local variables:
456 * tab-width: 4
457 * c-basic-offset: 4
458 * End:
459 * vim600: noet sw=4 ts=4 fdm=marker
460 * vim<600: noet sw=4 ts=4
461 */
462