Fix vhost problem YAZ-654
[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             printf ("\n<<<");
265             int j;
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 = 2, content_len = 0, chunked = 0;
335
336     if (len < 6)
337         return 0;
338
339     /* if dealing with HTTP responses - then default
340        content length is unlimited (socket close) */
341     if (!head_only && !memcmp(buf, "HTTP/", 5))
342         content_len = -1;
343
344 #if 0
345     printf("len = %d\n", len);
346     fwrite (buf, 1, len, stdout);
347     printf("----------\n");
348 #endif
349     while (i <= len-2)
350     {
351         if (i > 8192)
352         {
353             return i;  /* do not allow more than 8K HTTP header */
354         }
355         if (skip_crlf(buf, len, &i))
356         {
357             if (skip_crlf(buf, len, &i))
358             {
359                 /* inside content */
360                 if (chunked)
361                     return cs_read_chunk(buf, i, len);
362                 else
363                 {   /* not chunked ; inside body */
364                     if (content_len == -1)
365                         return 0;   /* no content length */
366                     else if (len >= i + content_len)
367                     {
368                         return i + content_len;
369                     }
370                 }
371                 break;
372             }
373             else if (i < len - 20 &&
374                      !strncasecmp((const char *) buf+i, "Transfer-Encoding:", 18))
375             {
376                 i+=18;
377                 while (buf[i] == ' ')
378                     i++;
379                 if (i < len - 8)
380                     if (!strncasecmp((const char *) buf+i, "chunked", 7))
381                         chunked = 1;
382             }
383             else if (i < len - 17 &&
384                      !strncasecmp((const char *)buf+i, "Content-Length:", 15))
385             {
386                 i+= 15;
387                 while (buf[i] == ' ')
388                     i++;
389                 content_len = 0;
390                 while (i <= len-4 && yaz_isdigit(buf[i]))
391                     content_len = content_len*10 + (buf[i++] - '0');
392                 if (content_len < 0) /* prevent negative offsets */
393                     content_len = 0;
394             }
395             else
396                 i++;
397         }
398         else
399             i++;
400     }
401     return 0;
402 }
403
404 static int cs_complete_auto_x(const char *buf, int len, int head_only)
405 {
406     if (len > 5 && buf[0] >= 0x20 && buf[0] < 0x7f
407                 && buf[1] >= 0x20 && buf[1] < 0x7f
408                 && buf[2] >= 0x20 && buf[2] < 0x7f)
409     {
410         int r = cs_complete_http(buf, len, head_only);
411         return r;
412     }
413     return completeBER((const unsigned char *) buf, len);
414 }
415
416
417 int cs_complete_auto(const char *buf, int len)
418 {
419     return cs_complete_auto_x(buf, len, 0);
420 }
421
422 int cs_complete_auto_head(const char *buf, int len)
423 {
424     return cs_complete_auto_x(buf, len, 1);
425 }
426
427 void cs_set_max_recv_bytes(COMSTACK cs, int max_recv_bytes)
428 {
429     cs->max_recv_bytes = max_recv_bytes;
430 }
431
432 /*
433  * Local variables:
434  * c-basic-offset: 4
435  * c-file-style: "Stroustrup"
436  * indent-tabs-mode: nil
437  * End:
438  * vim: shiftwidth=4 tabstop=8 expandtab
439  */
440