Allow HTTP protocol on unix local socket
[yaz-moved-to-github.git] / src / comstack.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) 1995-2012 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         else
130         {
131             *connect_host = xstrdup(uri);
132             uri += strlen(uri); /* set to "" */
133         }
134 #ifdef WIN32
135         return 0;
136 #else
137         *t = unix_type;
138 #endif
139     }
140
141     if (strncmp (uri, "tcp:", 4) == 0)
142     {
143         *host = uri + 4;
144         *proto = PROTO_Z3950;
145     }
146     else if (strncmp (uri, "ssl:", 4) == 0)
147     {
148 #if ENABLE_SSL
149         *t = ssl_type;
150         *host = uri + 4;
151         *proto = PROTO_Z3950;
152 #else
153         return 0;
154 #endif
155     }
156     else if (strncmp(uri, "http:", 5) == 0)
157     {
158         *host = uri + 5;
159         while (**host == '/')
160             (*host)++;
161         *proto = PROTO_HTTP;
162     }
163     else if (strncmp(uri, "https:", 6) == 0)
164     {
165 #if ENABLE_SSL
166         *t = ssl_type;
167         *host = uri + 6;
168         while (**host == '/')
169             (*host)++;
170         *proto = PROTO_HTTP;
171 #else
172         return 0;
173 #endif
174     }
175     else
176     {
177         *host = uri;
178         *proto = PROTO_Z3950;
179     }
180     return 1;
181 }
182
183 COMSTACK cs_create_host(const char *vhost, int blocking, void **vp)
184 {
185     return cs_create_host_proxy(vhost, blocking, vp, 0);
186 }
187
188 COMSTACK cs_create_host_proxy(const char *vhost, int blocking, void **vp,
189                               const char *proxy_host)
190 {
191     enum oid_proto proto = PROTO_Z3950;
192     const char *host = 0;
193     COMSTACK cs;
194     CS_TYPE t;
195     char *connect_host = 0;
196
197     if (!cs_parse_host(vhost, &host, &t, &proto, &connect_host))
198         return 0;
199
200     if (t == tcpip_type)
201     {
202         cs = yaz_tcpip_create(-1, blocking, proto, connect_host ? host : 0);
203     }
204     else
205     {
206         cs = cs_create(t, blocking, proto);
207     }
208     if (cs)
209     {
210         if (proxy_host)
211             host = proxy_host;
212         if (!(*vp = cs_straddr(cs, connect_host ? connect_host : host)))
213         {
214             cs_close (cs);
215             cs = 0;
216         }
217     }
218     xfree(connect_host);
219     return cs;
220 }
221
222 int cs_look (COMSTACK cs)
223 {
224     return cs->event;
225 }
226
227 static int skip_crlf(const char *buf, int len, int *i)
228 {
229     if (*i < len)
230     {
231         if (buf[*i] == '\r' && *i < len-1 && buf[*i + 1] == '\n')
232         {
233             (*i) += 2;
234             return 1;
235         }
236         else if (buf[*i] == '\n')
237         {
238             (*i)++;
239             return 1;
240         }
241     }
242     return 0;
243 }
244
245 #define CHUNK_DEBUG 0
246
247 static int cs_read_chunk(const char *buf, int i, int len)
248 {
249     /* inside chunked body .. */
250     while (1)
251     {
252         int chunk_len = 0;
253 #if CHUNK_DEBUG
254         if (i < len-2)
255         {
256             printf ("\n<<<");
257             int j;
258             for (j = i; j <= i+3; j++)
259                 printf ("%c", buf[j]);
260             printf (">>>\n");
261         }
262 #endif
263         /* read chunk length */
264         while (1)
265             if (i >= len-2) {
266 #if CHUNK_DEBUG
267                 printf ("returning incomplete read at 1\n");
268                 printf ("i=%d len=%d\n", i, len);
269 #endif
270                 return 0;
271             } else if (yaz_isdigit(buf[i]))
272                 chunk_len = chunk_len * 16 +
273                     (buf[i++] - '0');
274             else if (yaz_isupper(buf[i]))
275                 chunk_len = chunk_len * 16 +
276                     (buf[i++] - ('A'-10));
277             else if (yaz_islower(buf[i]))
278                 chunk_len = chunk_len * 16 +
279                     (buf[i++] - ('a'-10));
280             else
281                 break;
282         if (chunk_len == 0)
283             break;
284         if (chunk_len < 0)
285             return i;
286
287         while (1)
288         {
289             if (i >= len -1)
290                 return 0;
291             if (skip_crlf(buf, len, &i))
292                 break;
293             i++;
294         }
295         /* got CRLF */
296 #if CHUNK_DEBUG
297         printf ("chunk_len=%d\n", chunk_len);
298 #endif
299         i += chunk_len;
300         if (i >= len-2)
301             return 0;
302         if (!skip_crlf(buf, len, &i))
303             return 0;
304     }
305     /* consider trailing headers .. */
306     while (i < len)
307     {
308         if (skip_crlf(buf, len, &i))
309         {
310             if (skip_crlf(buf, len, &i))
311                 return i;
312         }
313         else
314             i++;
315     }
316 #if CHUNK_DEBUG
317     printf ("returning incomplete read at 2\n");
318     printf ("i=%d len=%d\n", i, len);
319 #endif
320     return 0;
321 }
322
323 static int cs_complete_http(const char *buf, int len, int head_only)
324 {
325     /* deal with HTTP request/response */
326     int i = 2, content_len = 0, chunked = 0;
327
328     if (len < 6)
329         return 0;
330
331     /* if dealing with HTTP responses - then default
332        content length is unlimited (socket close) */
333     if (!head_only && !memcmp(buf, "HTTP/", 5))
334         content_len = -1;
335
336 #if 0
337     printf("len = %d\n", len);
338     fwrite (buf, 1, len, stdout);
339     printf("----------\n");
340 #endif
341     while (i <= len-2)
342     {
343         if (i > 8192)
344         {
345             return i;  /* do not allow more than 8K HTTP header */
346         }
347         if (skip_crlf(buf, len, &i))
348         {
349             if (skip_crlf(buf, len, &i))
350             {
351                 /* inside content */
352                 if (chunked)
353                     return cs_read_chunk(buf, i, len);
354                 else
355                 {   /* not chunked ; inside body */
356                     if (content_len == -1)
357                         return 0;   /* no content length */
358                     else if (len >= i + content_len)
359                     {
360                         return i + content_len;
361                     }
362                 }
363                 break;
364             }
365             else if (i < len - 20 &&
366                      !strncasecmp((const char *) buf+i, "Transfer-Encoding:", 18))
367             {
368                 i+=18;
369                 while (buf[i] == ' ')
370                     i++;
371                 if (i < len - 8)
372                     if (!strncasecmp((const char *) buf+i, "chunked", 7))
373                         chunked = 1;
374             }
375             else if (i < len - 17 &&
376                      !strncasecmp((const char *)buf+i, "Content-Length:", 15))
377             {
378                 i+= 15;
379                 while (buf[i] == ' ')
380                     i++;
381                 content_len = 0;
382                 while (i <= len-4 && yaz_isdigit(buf[i]))
383                     content_len = content_len*10 + (buf[i++] - '0');
384                 if (content_len < 0) /* prevent negative offsets */
385                     content_len = 0;
386             }
387             else
388                 i++;
389         }
390         else
391             i++;
392     }
393     return 0;
394 }
395
396 static int cs_complete_auto_x(const char *buf, int len, int head_only)
397 {
398     if (len > 5 && buf[0] >= 0x20 && buf[0] < 0x7f
399                 && buf[1] >= 0x20 && buf[1] < 0x7f
400                 && buf[2] >= 0x20 && buf[2] < 0x7f)
401     {
402         int r = cs_complete_http(buf, len, head_only);
403         return r;
404     }
405     return completeBER((const unsigned char *) buf, len);
406 }
407
408
409 int cs_complete_auto(const char *buf, int len)
410 {
411     return cs_complete_auto_x(buf, len, 0);
412 }
413
414 int cs_complete_auto_head(const char *buf, int len)
415 {
416     return cs_complete_auto_x(buf, len, 1);
417 }
418
419 void cs_set_max_recv_bytes(COMSTACK cs, int max_recv_bytes)
420 {
421     cs->max_recv_bytes = max_recv_bytes;
422 }
423
424 /*
425  * Local variables:
426  * c-basic-offset: 4
427  * c-file-style: "Stroustrup"
428  * indent-tabs-mode: nil
429  * End:
430  * vim: shiftwidth=4 tabstop=8 expandtab
431  */
432