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