* curl-shim.h, curl-shim.c (curl_easy_setopt, curl_easy_perform): Add
[gnupg.git] / keyserver / curl-shim.c
1 /* curl-shim.c - Implement a small subset of the curl API in terms of
2  * the iobuf HTTP API
3  *
4  * Copyright (C) 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
5  *
6  * This file is part of GnuPG.
7  *
8  * GnuPG is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * GnuPG is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, see <http://www.gnu.org/licenses/>.
20  */
21
22 #include <config.h>
23 #include <stdarg.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <stdio.h>
27 #include <errno.h>
28 #include "http.h"
29 #include "util.h"
30 #include "ksutil.h"
31 #include "curl-shim.h"
32
33 static CURLcode
34 handle_error(CURL *curl,CURLcode err,const char *str)
35 {
36   if(curl->errorbuffer)
37     {
38       /* Make sure you never exceed CURL_ERROR_SIZE, currently set to
39          256 in curl-shim.h */
40       switch(err)
41         {
42         case CURLE_OK:
43           strcpy(curl->errorbuffer,"okay");
44           break;
45
46         case CURLE_UNSUPPORTED_PROTOCOL:
47           strcpy(curl->errorbuffer,"unsupported protocol");
48           break;
49
50         case CURLE_COULDNT_CONNECT:
51           strcpy(curl->errorbuffer,"couldn't connect");
52           break;
53
54         case CURLE_WRITE_ERROR:
55           strcpy(curl->errorbuffer,"write error");
56           break;
57
58         case CURLE_HTTP_RETURNED_ERROR:
59           sprintf(curl->errorbuffer,"url returned error %u",curl->status);
60           break;
61
62         default:
63           strcpy(curl->errorbuffer,"generic error");
64           break;
65         }
66
67       if(str && (strlen(curl->errorbuffer)+2+strlen(str)+1)<=CURL_ERROR_SIZE)
68         {
69           strcat(curl->errorbuffer,": ");
70           strcat(curl->errorbuffer,str);
71         }
72     }
73
74   return err;
75 }
76
77 CURLcode
78 curl_global_init(long flags)
79 {
80   return CURLE_OK;
81 }
82
83 void
84 curl_global_cleanup(void) {}
85
86 CURL *
87 curl_easy_init(void)
88 {
89   CURL *handle;
90
91   handle=calloc(1,sizeof(CURL));
92   if(handle)
93     handle->errors=stderr;
94
95   return handle;
96 }
97
98 void
99 curl_easy_cleanup(CURL *curl)
100 {
101   free(curl);
102 }
103
104 CURLcode
105 curl_easy_setopt(CURL *curl,CURLoption option,...)
106 {
107   va_list ap;
108
109   va_start(ap,option);
110
111   switch(option)
112     {
113     case CURLOPT_URL:
114       curl->url=va_arg(ap,char *);
115       break;
116     case CURLOPT_USERPWD:
117       curl->auth=va_arg(ap,char *);
118       break;
119     case CURLOPT_WRITEFUNCTION:
120       curl->writer=va_arg(ap,write_func);
121       break;
122     case CURLOPT_FILE:
123       curl->file=va_arg(ap,void *);
124       break;
125     case CURLOPT_ERRORBUFFER:
126       curl->errorbuffer=va_arg(ap,char *);
127       break;
128     case CURLOPT_PROXY:
129       curl->proxy=va_arg(ap,char *);
130       break;
131     case CURLOPT_POST:
132       curl->flags.post=va_arg(ap,long)?1:0;
133       break;
134     case CURLOPT_POSTFIELDS:
135       curl->postfields=va_arg(ap,char *);
136       break;
137     case CURLOPT_SRVTAG_GPG_HACK:
138       curl->srvtag=va_arg(ap,char *);
139       break;
140     case CURLOPT_FAILONERROR:
141       curl->flags.failonerror=va_arg(ap,long)?1:0;
142       break;
143     case CURLOPT_VERBOSE:
144       curl->flags.verbose=va_arg(ap,long)?1:0;
145       break;
146     case CURLOPT_STDERR:
147       curl->errors=va_arg(ap,FILE *);
148       break;
149     default:
150       /* We ignore the huge majority of curl options */
151       break;
152     }
153
154   return handle_error(curl,CURLE_OK,NULL);
155 }
156
157 CURLcode
158 curl_easy_perform(CURL *curl)
159 {
160   int rc;
161   CURLcode err=CURLE_OK;
162   const char *errstr=NULL;
163   char *proxy=NULL;
164
165   /* Emulate the libcurl proxy behavior.  If the calling program set a
166      proxy, use it.  If it didn't set a proxy or set it to NULL, check
167      for one in the environment.  If the calling program explicitly
168      set a null-string proxy the http code doesn't use a proxy at
169      all. */
170
171   if(curl->proxy)
172     proxy=curl->proxy;
173   else
174     proxy=getenv(HTTP_PROXY_ENV);
175
176   if(curl->flags.verbose)
177     {
178       fprintf(curl->errors,"* HTTP proxy is \"%s\"\n",proxy?proxy:"null");
179       fprintf(curl->errors,"* HTTP URL is \"%s\"\n",curl->url);
180       fprintf(curl->errors,"* HTTP auth is \"%s\"\n",
181               curl->auth?curl->auth:"null");
182       fprintf(curl->errors,"* HTTP method is %s\n",
183               curl->flags.post?"POST":"GET");
184     }
185
186   if(curl->flags.post)
187     {
188       rc=http_open(&curl->hd,HTTP_REQ_POST,curl->url,curl->auth,0,proxy,
189                    curl->srvtag);
190       if(rc==0)
191         {
192           char content_len[50];
193           unsigned int post_len=strlen(curl->postfields);
194
195           iobuf_writestr(curl->hd.fp_write,
196                          "Content-Type: application/x-www-form-urlencoded\r\n");
197           sprintf(content_len,"Content-Length: %u\r\n",post_len);
198
199           iobuf_writestr(curl->hd.fp_write,content_len);
200
201           http_start_data(&curl->hd);
202           iobuf_write(curl->hd.fp_write,curl->postfields,post_len);
203           rc=http_wait_response(&curl->hd,&curl->status);
204           if(rc==0 && curl->flags.failonerror && curl->status>=300)
205             err=CURLE_HTTP_RETURNED_ERROR;
206         }
207     }
208   else
209     {
210       rc=http_open(&curl->hd,HTTP_REQ_GET,curl->url,curl->auth,0,proxy,
211                    curl->srvtag);
212       if(rc==0)
213         {
214           rc=http_wait_response(&curl->hd,&curl->status);
215           if(rc==0)
216             {
217               if(curl->flags.failonerror && curl->status>=300)
218                 err=CURLE_HTTP_RETURNED_ERROR;
219               else
220                 {
221                   unsigned int maxlen=1024,buflen,len;
222                   byte *line=NULL;
223
224                   while((len=iobuf_read_line(curl->hd.fp_read,
225                                              &line,&buflen,&maxlen)))
226                     {
227                       size_t ret;
228
229                       maxlen=1024;
230
231                       ret=(curl->writer)(line,len,1,curl->file);
232                       if(ret!=len)
233                         {
234                           err=CURLE_WRITE_ERROR;
235                           break;
236                         }
237                     }
238
239                   xfree(line);
240                   http_close(&curl->hd);
241                 }
242             }
243           else
244             http_close(&curl->hd);
245         }
246     }
247
248   switch(rc)
249     {
250     case 0:
251       break;
252
253     case G10ERR_INVALID_URI:
254       err=CURLE_UNSUPPORTED_PROTOCOL;
255       break;
256
257     case G10ERR_NETWORK:
258       errstr=strerror(errno);
259       err=CURLE_COULDNT_CONNECT;
260       break;
261
262     default:
263       errstr=g10_errstr(rc);
264       err=CURLE_COULDNT_CONNECT;
265       break;
266     }
267       
268   return handle_error(curl,err,errstr);
269 }
270
271 /* This is not the same exact set that is allowed according to
272    RFC-2396, but it is what the real curl uses. */
273 #define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz" \
274                         "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
275                         "0123456789"
276
277 char *
278 curl_easy_escape(CURL *curl,char *str,int length)
279 {
280   int len,max,idx,enc_idx=0;
281   char *enc;
282
283   if(length)
284     len=length;
285   else
286     len=strlen(str);
287
288   enc=malloc(len+1);
289   if(!enc)
290     return enc;
291
292   max=len;
293
294   for(idx=0;idx<len;idx++)
295     {
296       if(enc_idx+3>max)
297         {
298           char *tmp;
299
300           max+=100;
301
302           tmp=realloc(enc,max+1);
303           if(!tmp)
304             {
305               free(enc);
306               return NULL;
307             }
308
309           enc=tmp;
310         }
311
312       if(strchr(VALID_URI_CHARS,str[idx]))
313         enc[enc_idx++]=str[idx];
314       else
315         {
316           char numbuf[5];
317           sprintf(numbuf,"%%%02X",(unsigned char)str[idx]);
318           strcpy(&enc[enc_idx],numbuf);
319           enc_idx+=3;
320         }
321     }
322
323   enc[enc_idx]='\0';
324
325   return enc;
326 }
327
328 curl_version_info_data *
329 curl_version_info(int type)
330 {
331   static curl_version_info_data data;
332   static const char *protocols[]={"http",NULL};
333
334   data.protocols=protocols;
335
336   return &data;
337 }