Fix problem with HTTP decoding
[yaz-moved-to-github.git] / src / comstack.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) 1995-2013 Index Data
3  * See the file LICENSE for details.
4  */
5 /**
6  * \file comstack.c
7  * \brief Implements Generic COMSTACK functions
8  */
9 #if HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12
13 #include <string.h>
14 #include <errno.h>
15
16 #include <yaz/yaz-iconv.h>
17 #include <yaz/log.h>
18 #include <yaz/comstack.h>
19 #include <yaz/tcpip.h>
20 #include <yaz/unix.h>
21 #include <yaz/odr.h>
22
23 #ifdef WIN32
24 #define strncasecmp _strnicmp
25 #endif
26
27 #if HAVE_GNUTLS_H
28 #define ENABLE_SSL 1
29 #endif
30
31 #if HAVE_OPENSSL_SSL_H
32 #define ENABLE_SSL 1
33 #endif
34
35 static const char *cs_errlist[] =
36 {
37     "No error or unspecified error",
38     "System (lower-layer) error",
39     "Operation out of state",
40     "No data (operation would block)",
41     "New data while half of old buffer is on the line (flow control)",
42     "Permission denied",
43     "SSL error",
44     "Too large incoming buffer"
45 };
46
47 const char *cs_errmsg(int n)
48 {
49     static char buf[250];
50
51     if (n < CSNONE || n > CSLASTERROR) {
52         sprintf(buf, "unknown comstack error %d", n);
53         return buf;
54     }
55     if (n == CSYSERR) {
56         sprintf(buf, "%s: %s", cs_errlist[n], strerror(errno));
57         return buf;
58     }
59     return cs_errlist[n];
60 }
61
62 const char *cs_strerror(COMSTACK h)
63 {
64     return cs_errmsg(h->cerrno);
65 }
66
67 void cs_get_host_args(const char *type_and_host, const char **args)
68 {
69
70     *args = "";
71     if (!strncmp(type_and_host, "unix:", 5))
72     {
73         const char *cp = strchr(type_and_host + 5, ':');
74         if (cp)
75             type_and_host = cp + 1;
76         else
77             type_and_host += strlen(type_and_host); /* empty string */
78     }
79     if (*type_and_host)
80     {
81         const char *cp;
82         cp = strstr(type_and_host, "://");
83         if (cp)
84             cp = cp+3;
85         else
86             cp = type_and_host;
87         cp = strchr(cp, '/');
88         if (cp)
89             *args = cp+1;
90     }
91 }
92
93 static int cs_parse_host(const char *uri, const char **host,
94                          CS_TYPE *t, enum oid_proto *proto,
95                          char **connect_host)
96 {
97     *connect_host = 0;
98
99     *t = tcpip_type;
100     if (strncmp(uri, "connect:", 8) == 0)
101     {
102         const char *cp = strchr(uri, ',');
103         if (cp)
104         {
105             size_t len;
106
107             uri += 8;
108             len = cp - uri;
109             *connect_host = (char *) xmalloc(len + 1);
110             memcpy(*connect_host, uri, len);
111             (*connect_host)[len] = '\0';
112             uri = cp + 1;
113         }
114     }
115     else if (strncmp(uri, "unix:", 5) == 0)
116     {
117         const char *cp;
118
119         uri += 5;
120         cp = strchr(uri, ':');
121         if (cp)
122         {
123             size_t len = cp - uri;
124             *connect_host = (char *) xmalloc(len + 1);
125             memcpy(*connect_host, uri, len);
126             (*connect_host)[len] = '\0';
127             uri = cp + 1;
128         }
129 #ifdef WIN32
130         return 0;
131 #else
132         *t = unix_type;
133 #endif
134     }
135
136     if (strncmp (uri, "tcp:", 4) == 0)
137     {
138         *host = uri + 4;
139         *proto = PROTO_Z3950;
140     }
141     else if (strncmp (uri, "ssl:", 4) == 0)
142     {
143 #if ENABLE_SSL
144         *t = ssl_type;
145         *host = uri + 4;
146         *proto = PROTO_Z3950;
147 #else
148         return 0;
149 #endif
150     }
151     else if (strncmp(uri, "http:", 5) == 0)
152     {
153         *host = uri + 5;
154         while (**host == '/')
155             (*host)++;
156         *proto = PROTO_HTTP;
157     }
158     else if (strncmp(uri, "https:", 6) == 0)
159     {
160 #if ENABLE_SSL
161         *t = ssl_type;
162         *host = uri + 6;
163         while (**host == '/')
164             (*host)++;
165         *proto = PROTO_HTTP;
166 #else
167         return 0;
168 #endif
169     }
170     else
171     {
172         *host = uri;
173         *proto = PROTO_Z3950;
174     }
175     return 1;
176 }
177
178 COMSTACK cs_create_host(const char *vhost, int blocking, void **vp)
179 {
180     return cs_create_host_proxy(vhost, blocking, vp, 0);
181 }
182
183 COMSTACK cs_create_host_proxy(const char *vhost, int blocking, void **vp,
184                               const char *proxy_host)
185 {
186     enum oid_proto proto = PROTO_Z3950;
187     const char *host = 0;
188     COMSTACK cs;
189     CS_TYPE t;
190     char *connect_host = 0;
191
192     if (!cs_parse_host(vhost, &host, &t, &proto, &connect_host))
193     {
194         xfree(connect_host);
195         return 0;
196     }
197
198     if (proxy_host)
199     {
200         enum oid_proto proto1;
201
202         xfree(connect_host);
203         if (!cs_parse_host(proxy_host, &host, &t, &proto1, &connect_host))
204         {
205             xfree(connect_host);
206             return 0;
207         }
208     }
209
210     if (t == tcpip_type)
211     {
212         cs = yaz_tcpip_create(-1, blocking, proto, connect_host ? host : 0);
213     }
214     else
215     {
216         cs = cs_create(t, blocking, proto);
217     }
218     if (cs)
219     {
220         if (!(*vp = cs_straddr(cs, connect_host ? connect_host : host)))
221         {
222             cs_close (cs);
223             cs = 0;
224         }
225     }
226     xfree(connect_host);
227     return cs;
228 }
229
230 int cs_look (COMSTACK cs)
231 {
232     return cs->event;
233 }
234
235 static int skip_crlf(const char *buf, int len, int *i)
236 {
237     if (*i < len)
238     {
239         if (buf[*i] == '\r' && *i < len-1 && buf[*i + 1] == '\n')
240         {
241             (*i) += 2;
242             return 1;
243         }
244         else if (buf[*i] == '\n')
245         {
246             (*i)++;
247             return 1;
248         }
249     }
250     return 0;
251 }
252
253 #define CHUNK_DEBUG 0
254
255 static int cs_read_chunk(const char *buf, int i, int len)
256 {
257     /* inside chunked body .. */
258     while (1)
259     {
260         int chunk_len = 0;
261 #if CHUNK_DEBUG
262         if (i < len-2)
263         {
264             int j;
265             printf ("\n<<<");
266             for (j = i; j <= i+3; j++)
267                 printf ("%c", buf[j]);
268             printf (">>>\n");
269         }
270 #endif
271         /* read chunk length */
272         while (1)
273             if (i >= len-2) {
274 #if CHUNK_DEBUG
275                 printf ("returning incomplete read at 1\n");
276                 printf ("i=%d len=%d\n", i, len);
277 #endif
278                 return 0;
279             } else if (yaz_isdigit(buf[i]))
280                 chunk_len = chunk_len * 16 +
281                     (buf[i++] - '0');
282             else if (yaz_isupper(buf[i]))
283                 chunk_len = chunk_len * 16 +
284                     (buf[i++] - ('A'-10));
285             else if (yaz_islower(buf[i]))
286                 chunk_len = chunk_len * 16 +
287                     (buf[i++] - ('a'-10));
288             else
289                 break;
290         if (chunk_len == 0)
291             break;
292         if (chunk_len < 0)
293             return i;
294
295         while (1)
296         {
297             if (i >= len -1)
298                 return 0;
299             if (skip_crlf(buf, len, &i))
300                 break;
301             i++;
302         }
303         /* got CRLF */
304 #if CHUNK_DEBUG
305         printf ("chunk_len=%d\n", chunk_len);
306 #endif
307         i += chunk_len;
308         if (i >= len-2)
309             return 0;
310         if (!skip_crlf(buf, len, &i))
311             return 0;
312     }
313     /* consider trailing headers .. */
314     while (i < len)
315     {
316         if (skip_crlf(buf, len, &i))
317         {
318             if (skip_crlf(buf, len, &i))
319                 return i;
320         }
321         else
322             i++;
323     }
324 #if CHUNK_DEBUG
325     printf ("returning incomplete read at 2\n");
326     printf ("i=%d len=%d\n", i, len);
327 #endif
328     return 0;
329 }
330
331 static int cs_complete_http(const char *buf, int len, int head_only)
332 {
333     /* deal with HTTP request/response */
334     int i, content_len = 0, chunked = 0;
335
336     /* need at least one line followed by \n or \r .. */
337     for (i = 0; ; i++)
338         if (i == len)
339             return 0; /* incomplete */
340         else if (buf[i] == '\n' || buf[i] == '\r')
341             break;
342
343     /* check to see if it's a response with content */
344     if (!head_only && !memcmp(buf, "HTTP/", 5))
345     {
346         int j;
347         for (j = 5; j < i; j++)
348             if (buf[j] == ' ')
349             {
350                 ++j;
351                 if (buf[j] == '1') /* 1XX */
352                     ;
353                 else if (!memcmp(buf + j, "204", 3))
354                     ;
355                 else if (!memcmp(buf + j, "304", 3))
356                     ;
357                 else
358                     content_len = -1;
359                 break;
360             }
361     }
362 #if 0
363     printf("len = %d\n", len);
364     fwrite (buf, 1, len, stdout);
365     printf("----------\n");
366 #endif
367     for (i = 2; i <= len-2; )
368     {
369         if (i > 8192)
370         {
371             return i;  /* do not allow more than 8K HTTP header */
372         }
373         if (skip_crlf(buf, len, &i))
374         {
375             if (skip_crlf(buf, len, &i))
376             {
377                 /* inside content */
378                 if (chunked)
379                     return cs_read_chunk(buf, i, len);
380                 else
381                 {   /* not chunked ; inside body */
382                     if (content_len == -1)
383                         return 0;   /* no content length */
384                     else if (len >= i + content_len)
385                     {
386                         return i + content_len;
387                     }
388                 }
389                 break;
390             }
391             else if (i < len - 20 &&
392                      !strncasecmp((const char *) buf+i, "Transfer-Encoding:", 18))
393             {
394                 i+=18;
395                 while (buf[i] == ' ')
396                     i++;
397                 if (i < len - 8)
398                     if (!strncasecmp((const char *) buf+i, "chunked", 7))
399                         chunked = 1;
400             }
401             else if (i < len - 17 &&
402                      !strncasecmp((const char *)buf+i, "Content-Length:", 15))
403             {
404                 i+= 15;
405                 while (buf[i] == ' ')
406                     i++;
407                 content_len = 0;
408                 while (i <= len-4 && yaz_isdigit(buf[i]))
409                     content_len = content_len*10 + (buf[i++] - '0');
410                 if (content_len < 0) /* prevent negative offsets */
411                     content_len = 0;
412             }
413             else
414                 i++;
415         }
416         else
417             i++;
418     }
419     return 0;
420 }
421
422 static int cs_complete_auto_x(const char *buf, int len, int head_only)
423 {
424     if (len > 5 && buf[0] >= 0x20 && buf[0] < 0x7f
425                 && buf[1] >= 0x20 && buf[1] < 0x7f
426                 && buf[2] >= 0x20 && buf[2] < 0x7f)
427     {
428         int r = cs_complete_http(buf, len, head_only);
429         return r;
430     }
431     return completeBER((const unsigned char *) buf, len);
432 }
433
434
435 int cs_complete_auto(const char *buf, int len)
436 {
437     return cs_complete_auto_x(buf, len, 0);
438 }
439
440 int cs_complete_auto_head(const char *buf, int len)
441 {
442     return cs_complete_auto_x(buf, len, 1);
443 }
444
445 void cs_set_max_recv_bytes(COMSTACK cs, int max_recv_bytes)
446 {
447     cs->max_recv_bytes = max_recv_bytes;
448 }
449
450 /*
451  * Local variables:
452  * c-basic-offset: 4
453  * c-file-style: "Stroustrup"
454  * indent-tabs-mode: nil
455  * End:
456  * vim: shiftwidth=4 tabstop=8 expandtab
457  */
458