1 /******************************************************************************
2 * $Id$
3 *
4 * Project: MapServer
5 * Purpose: MapCache tile caching HTTP request support
6 * Author: Thomas Bonfort and the MapServer team.
7 *
8 ******************************************************************************
9 * Copyright (c) 1996-2011 Regents of the University of Minnesota.
10 *
11 * Permission is hereby granted, free of charge, to any person obtaining a
12 * copy of this software and associated documentation files (the "Software"),
13 * to deal in the Software without restriction, including without limitation
14 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 * and/or sell copies of the Software, and to permit persons to whom the
16 * Software is furnished to do so, subject to the following conditions:
17 *
18 * The above copyright notice and this permission notice shall be included in
19 * all copies of this Software or works derived from this Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 * DEALINGS IN THE SOFTWARE.
28 *****************************************************************************/
29
30 #include "mapcache.h"
31 #include <curl/curl.h>
32 #include <apr_hash.h>
33 #include <apr_strings.h>
34 #include <ctype.h>
35
36 #define MAX_STRING_LEN 10000
37
38 struct _header_struct {
39 apr_table_t *headers;
40 mapcache_context *ctx;
41 };
42
_mapcache_curl_memory_callback(void * ptr,size_t size,size_t nmemb,void * data)43 size_t _mapcache_curl_memory_callback(void *ptr, size_t size, size_t nmemb, void *data)
44 {
45 mapcache_buffer *buffer = (mapcache_buffer*)data;
46 size_t realsize = size * nmemb;
47 return mapcache_buffer_append(buffer, realsize, ptr);
48 }
49
_mapcache_curl_header_callback(void * ptr,size_t size,size_t nmemb,void * userdata)50 size_t _mapcache_curl_header_callback( void *ptr, size_t size, size_t nmemb, void *userdata)
51 {
52 char *colonptr;
53 struct _header_struct *h = (struct _header_struct*)userdata;
54 char *header = apr_pstrndup(h->ctx->pool,ptr,size*nmemb);
55 char *endptr = strstr(header,"\r\n");
56 if(!endptr) {
57 endptr = strstr(header,"\n");
58 if(!endptr) {
59 /* skip invalid header */
60 #ifdef DEBUG
61 h->ctx->log(h->ctx,MAPCACHE_DEBUG,"received header %s with no trailing \\r\\n",header);
62 #endif
63 return size*nmemb;
64 }
65 }
66 colonptr = strchr(header,':');
67 if(colonptr) {
68 *colonptr = '\0';
69 *endptr = '\0';
70 apr_table_setn(h->headers,header,colonptr+2);
71 }
72
73 return size*nmemb;
74 }
75
76 /*update val replacing {foo_header} with the Value of foo_header from the orginal request */
_header_replace_str(mapcache_context * ctx,apr_table_t * headers,char ** val)77 static void _header_replace_str(mapcache_context *ctx, apr_table_t *headers, char **val) {
78 char *value = *val;
79 char *start_tag, *end_tag;
80 size_t start_tag_offset;
81 start_tag = strchr(value,'{');
82 while(start_tag) {
83 start_tag_offset = start_tag - value; /*record where we found the '{' so we can look for the next one after that spot
84 (avoids infinite loop if tag was not found/replaced) */
85 *start_tag=0;
86 end_tag = strchr(start_tag+1,'}');
87 if(end_tag) {
88 const char *header_value;
89 *end_tag=0;
90 header_value = apr_table_get(headers,start_tag+1);
91 if(header_value) {
92 value = apr_pstrcat(ctx->pool,value,header_value,end_tag+1,NULL);
93 }
94 *end_tag='}';
95 }
96 *start_tag='{';
97 start_tag = strchr(value+start_tag_offset+1,'{');
98 }
99 *val = value;
100 }
101
mapcache_http_do_request(mapcache_context * ctx,mapcache_http * req,mapcache_buffer * data,apr_table_t * headers,long * http_code)102 void mapcache_http_do_request(mapcache_context *ctx, mapcache_http *req, mapcache_buffer *data, apr_table_t *headers, long *http_code)
103 {
104 CURL *curl_handle;
105 char error_msg[CURL_ERROR_SIZE];
106 int ret;
107 struct curl_slist *curl_headers=NULL;
108 struct _header_struct h;
109 curl_handle = curl_easy_init();
110
111
112 /* specify URL to get */
113 curl_easy_setopt(curl_handle, CURLOPT_URL, req->url);
114 #ifdef DEBUG
115 ctx->log(ctx, MAPCACHE_DEBUG, "curl requesting url %s",req->url);
116 #endif
117 /* send all data to this function */
118 curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, _mapcache_curl_memory_callback);
119
120 /* we pass our mapcache_buffer struct to the callback function */
121 curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)data);
122
123 if(headers != NULL) {
124 /* intercept headers */
125 h.headers = headers;
126 h.ctx=ctx;
127 curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, _mapcache_curl_header_callback);
128 curl_easy_setopt(curl_handle, CURLOPT_WRITEHEADER, (void*)(&h));
129 }
130
131 curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_msg);
132 curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
133 curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, req->connection_timeout);
134 curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, req->timeout);
135 curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
136
137
138
139 if(req->headers) {
140 const apr_array_header_t *array = apr_table_elts(req->headers);
141 apr_table_entry_t *elts = (apr_table_entry_t *) array->elts;
142 int i;
143 for (i = 0; i < array->nelts; i++) {
144 char *val = elts[i].val;
145 if(val && strchr(val,'{') && ctx->headers_in) {
146 _header_replace_str(ctx,ctx->headers_in,&val);
147 }
148 curl_headers = curl_slist_append(curl_headers, apr_pstrcat(ctx->pool,elts[i].key,": ",val,NULL));
149 }
150 }
151 if(!req->headers || !apr_table_get(req->headers,"User-Agent")) {
152 curl_headers = curl_slist_append(curl_headers, "User-Agent: "MAPCACHE_USERAGENT);
153 }
154 curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, curl_headers);
155
156 if(req->post_body && req->post_len>0) {
157 curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, req->post_body);
158 }
159
160 if(!http_code)
161 curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1);
162
163 /* get it! */
164 ret = curl_easy_perform(curl_handle);
165 if(http_code)
166 curl_easy_getinfo (curl_handle, CURLINFO_RESPONSE_CODE, http_code);
167
168 if(ret != CURLE_OK) {
169 ctx->set_error(ctx, 502, "curl failed to request url %s : %s", req->url, error_msg);
170 }
171 /* cleanup curl stuff */
172 curl_slist_free_all(curl_headers);
173 curl_easy_cleanup(curl_handle);
174 }
175
mapcache_http_do_request_with_params(mapcache_context * ctx,mapcache_http * req,apr_table_t * params,mapcache_buffer * data,apr_table_t * headers,long * http_code)176 void mapcache_http_do_request_with_params(mapcache_context *ctx, mapcache_http *req, apr_table_t *params,
177 mapcache_buffer *data, apr_table_t *headers, long *http_code)
178 {
179 mapcache_http *request = mapcache_http_clone(ctx,req);
180 request->url = mapcache_http_build_url(ctx,req->url,params);
181 mapcache_http_do_request(ctx,request,data,headers, http_code);
182 }
183
184 typedef struct header_cb_struct{
185 apr_pool_t *pool;
186 char *str;
187 } header_cb_struct;
188
189 /* Converts an integer value to its hex character*/
to_hex(char code)190 char to_hex(char code) {
191 static char hex[] = "0123456789abcdef";
192 return hex[code & 15];
193 }
194
195 /* Returns a url-encoded version of str */
url_encode(apr_pool_t * p,const char * str)196 char *url_encode(apr_pool_t *p, const char *str) {
197 char *buf = apr_pcalloc(p, strlen(str) * 3 + 1), *pbuf = buf;
198 while (*str) {
199 if (isalnum(*str) || *str == '-' || *str == '_' || *str == '.' || *str == '~')
200 *pbuf++ = *str;
201 else if (*str == ' ')
202 *pbuf++ = '+';
203 else
204 *pbuf++ = '%', *pbuf++ = to_hex(*str >> 4), *pbuf++ = to_hex(*str & 15);
205 str++;
206 }
207 *pbuf = '\0';
208 return buf;
209 }
210
211 #ifdef _WIN32
_mapcache_key_value_append_callback(void * cnt,const char * key,const char * value)212 static int _mapcache_key_value_append_callback(void *cnt, const char *key, const char *value)
213 {
214 #else
215 static APR_DECLARE_NONSTD(int) _mapcache_key_value_append_callback(void *cnt, const char *key, const char *value)
216 {
217 #endif
218 #define _mystr (((header_cb_struct*)cnt)->str)
219 header_cb_struct *hcs = (header_cb_struct*)cnt;
220 hcs->str = apr_pstrcat(hcs->pool, hcs->str, key, "=", NULL);
221 if(value && *value) {
222 hcs->str = apr_pstrcat(hcs->pool, hcs->str, url_encode(hcs->pool, value), "&", NULL);
223 }
224 else {
225 hcs->str = apr_pstrcat(hcs->pool, hcs->str, "&", NULL);
226 }
227 return 1;
228 #undef _mystr
229 }
230
231 static char _mapcache_x2c(const char *what)
232 {
233 register char digit;
234 digit = ((what[0] >= 'A') ? ((what[0] & 0xdf) - 'A') + 10
235 : (what[0] - '0'));
236 digit *= 16;
237 digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10
238 : (what[1] - '0'));
239 return (digit);
240 }
241
242 #ifdef _WIN32
243 #define IS_SLASH(s) ((s == '/') || (s == '\\'))
244 #else
245 #define IS_SLASH(s) (s == '/')
246 #endif
247
248 int _mapcache_unescape_url(char *url)
249 {
250 register int badesc, badpath;
251 char *x, *y;
252
253 badesc = 0;
254 badpath = 0;
255 /* Initial scan for first '%'. Don't bother writing values before
256 * seeing a '%' */
257 y = strchr(url, '%');
258 if (y == NULL) {
259 return MAPCACHE_SUCCESS;
260 }
261 for (x = y; *y; ++x, ++y) {
262 if (*y != '%')
263 *x = *y;
264 else {
265 if (!isxdigit(*(y + 1)) || !isxdigit(*(y + 2))) {
266 badesc = 1;
267 *x = '%';
268 } else {
269 *x = _mapcache_x2c(y + 1);
270 y += 2;
271 if (IS_SLASH(*x) || *x == '\0')
272 badpath = 1;
273 }
274 }
275 }
276 *x = '\0';
277 if (badesc)
278 return MAPCACHE_FAILURE;
279 else if (badpath)
280 return MAPCACHE_FAILURE;
281 else
282 return MAPCACHE_SUCCESS;
283 }
284
285
286
287 char* mapcache_http_build_url(mapcache_context *ctx, char *base, apr_table_t *params)
288 {
289 if(!apr_is_empty_table(params)) {
290 int baseLength;
291 header_cb_struct hcs;
292 baseLength = strlen(base);
293 hcs.pool = ctx->pool;
294 hcs.str = base;
295
296 if(strchr(base,'?')) {
297 /* base already contains a '?' , shall we be adding a '&' to the end */
298 if(base[baseLength-1] != '?' && base[baseLength-1] != '&') {
299 hcs.str = apr_pstrcat(ctx->pool, hcs.str, "&", NULL);
300 }
301 } else {
302 /* base does not contain a '?', we will be adding it */
303 hcs.str = apr_pstrcat(ctx->pool, hcs.str, "?", NULL);
304 }
305
306 apr_table_do(_mapcache_key_value_append_callback, (void*)&hcs, params, NULL);
307 baseLength = strlen(hcs.str);
308 hcs.str[baseLength-1] = '\0';
309 return hcs.str;
310 } else {
311 return base;
312 }
313 }
314
315 /* Parse form data from a string. The input string is preserved. */
316 apr_table_t *mapcache_http_parse_param_string(mapcache_context *r, char *args_str)
317 {
318 apr_table_t *params;
319 char *args = apr_pstrdup(r->pool,args_str);
320 char *key;
321 char *value;
322 const char *delim = "&";
323 char *last;
324 if (args == NULL) {
325 return apr_table_make(r->pool,0);
326 }
327 params = apr_table_make(r->pool,20);
328 /* Split the input on '&' */
329 for (key = apr_strtok(args, delim, &last); key != NULL;
330 key = apr_strtok(NULL, delim, &last)) {
331 /* key is a pointer to the key=value string */
332 /*loop through key=value string to replace '+' by ' ' */
333 for (value = key; *value; ++value) {
334 if (*value == '+') {
335 *value = ' ';
336 }
337 }
338
339 /* split into Key / Value and unescape it */
340 value = strchr(key, '=');
341 if (value) {
342 *value++ = '\0'; /* replace '=' by \0, thus terminating the key string */
343 _mapcache_unescape_url(key);
344 _mapcache_unescape_url(value);
345 } else {
346 value = "";
347 _mapcache_unescape_url(key);
348 }
349 /* Store key/value pair in our form hash. */
350 apr_table_addn(params, key, value);
351 }
352 return params;
353 }
354
355 #ifdef DEBUG
356 static void http_cleanup(void *dummy)
357 {
358 curl_global_cleanup();
359 }
360 #endif
361
362 mapcache_http* mapcache_http_configuration_parse_xml(mapcache_context *ctx, ezxml_t node)
363 {
364 ezxml_t http_node;
365 mapcache_http *req;
366 curl_global_init(CURL_GLOBAL_ALL);
367 #ifdef DEBUG
368 /* make valgrind happy */
369 apr_pool_cleanup_register(ctx->pool, NULL,(void*)http_cleanup, apr_pool_cleanup_null);
370 #endif
371 req = (mapcache_http*)apr_pcalloc(ctx->pool,
372 sizeof(mapcache_http));
373 if ((http_node = ezxml_child(node,"url")) != NULL) {
374 req->url = apr_pstrdup(ctx->pool,http_node->txt);
375 }
376 if(!req->url) {
377 ctx->set_error(ctx,400,"got an <http> object with no <url>");
378 return NULL;
379 }
380
381 if ((http_node = ezxml_child(node,"connection_timeout")) != NULL) {
382 char *endptr;
383 req->connection_timeout = (int)strtol(http_node->txt,&endptr,10);
384 if(*endptr != 0 || req->connection_timeout<1) {
385 ctx->set_error(ctx,400,"invalid <http> <connection_timeout> \"%s\" (positive integer expected)",
386 http_node->txt);
387 return NULL;
388 }
389 } else {
390 req->connection_timeout = 30;
391 }
392
393 if ((http_node = ezxml_child(node,"timeout")) != NULL) {
394 char *endptr;
395 req->timeout = (int)strtol(http_node->txt,&endptr,10);
396 if(*endptr != 0 || req->timeout<1) {
397 ctx->set_error(ctx,400,"invalid <http> <timeout> \"%s\" (positive integer expected)",
398 http_node->txt);
399 return NULL;
400 }
401 } else {
402 req->timeout = 600;
403 }
404
405 req->headers = apr_table_make(ctx->pool,1);
406 if((http_node = ezxml_child(node,"headers")) != NULL) {
407 ezxml_t header_node;
408 for(header_node = http_node->child; header_node; header_node = header_node->sibling) {
409 apr_table_set(req->headers, header_node->name, header_node->txt);
410 }
411 }
412 return req;
413 /* TODO: parse <proxy> and <auth> elements */
414 }
415
416
417 mapcache_http* mapcache_http_clone(mapcache_context *ctx, mapcache_http *orig)
418 {
419 mapcache_http *ret = apr_pcalloc(ctx->pool, sizeof(mapcache_http));
420 ret->headers = apr_table_clone(ctx->pool,orig->headers);
421 ret->url = apr_pstrdup(ctx->pool, orig->url);
422 ret->connection_timeout = orig->connection_timeout;
423 ret->timeout = orig->timeout;
424 return ret;
425 }
426
427 /* vim: ts=2 sts=2 et sw=2
428 */
429