record_conv: change construct prototype
[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 (*type_and_host && strncmp(type_and_host, "unix:", 5))
72     {
73         const char *cp;
74         cp = strstr(type_and_host, "://");
75         if (cp)
76             cp = cp+3;
77         else
78             cp = type_and_host;
79         cp = strchr(cp, '/');
80         if (cp)
81             *args = cp+1;
82     }
83 }
84
85 static int cs_parse_host(const char *uri, const char **host,
86                          CS_TYPE *t, enum oid_proto *proto,
87                          char **connect_host)
88 {
89     *connect_host = 0;
90     if (strncmp(uri, "connect:", 8) == 0)
91     {
92         const char *cp = strchr(uri, ',');
93         if (cp)
94         {
95             size_t len = cp - (uri + 8);
96             *connect_host = (char *) xmalloc(len+1);
97             memcpy(*connect_host, uri + 8, len);
98             (*connect_host)[len] = '\0';
99             uri = cp+1;
100         }
101     }
102
103     if (strncmp (uri, "tcp:", 4) == 0)
104     {
105         *t = tcpip_type;
106         *host = uri + 4;
107         *proto = PROTO_Z3950;
108     }
109     else if (strncmp (uri, "ssl:", 4) == 0)
110     {
111 #if ENABLE_SSL
112         *t = ssl_type;
113         *host = uri + 4;
114         *proto = PROTO_Z3950;
115 #else
116         return 0;
117 #endif
118     }
119     else if (strncmp (uri, "unix:", 5) == 0)
120     {
121 #ifndef WIN32
122         *t = unix_type;
123         *host = uri + 5;
124         *proto = PROTO_Z3950;
125 #else
126         return 0;
127 #endif
128     }
129     else if (strncmp(uri, "http:", 5) == 0)
130     {
131         *t = tcpip_type;
132         *host = uri + 5;
133         while (**host == '/')
134             (*host)++;
135         *proto = PROTO_HTTP;
136     }
137     else if (strncmp(uri, "https:", 6) == 0)
138     {
139 #if ENABLE_SSL
140         *t = ssl_type;
141         *host = uri + 6;
142         while (**host == '/')
143             (*host)++;
144         *proto = PROTO_HTTP;
145 #else
146         return 0;
147 #endif
148     }
149     else
150     {
151         *proto = PROTO_Z3950;
152         *t = tcpip_type;
153         *host = uri;
154     }
155     return 1;
156 }
157
158 COMSTACK cs_create_host(const char *vhost, int blocking, void **vp)
159 {
160     return cs_create_host_proxy(vhost, blocking, vp, 0);
161 }
162
163 COMSTACK cs_create_host_proxy(const char *vhost, int blocking, void **vp,
164                               const char *proxy_host)
165 {
166     enum oid_proto proto = PROTO_Z3950;
167     const char *host = 0;
168     COMSTACK cs;
169     CS_TYPE t;
170     char *connect_host = 0;
171
172     if (!cs_parse_host(vhost, &host, &t, &proto, &connect_host))
173         return 0;
174
175     if (t == tcpip_type)
176     {
177         cs = yaz_tcpip_create(-1, blocking, proto, connect_host ? host : 0);
178     }
179     else
180     {
181         cs = cs_create(t, blocking, proto);
182     }
183     if (cs)
184     {
185         if (proxy_host)
186             host = proxy_host;
187         if (!(*vp = cs_straddr(cs, connect_host ? connect_host : host)))
188         {
189             cs_close (cs);
190             cs = 0;
191         }    
192     }
193     xfree(connect_host);
194     return cs;
195 }
196
197 int cs_look (COMSTACK cs)
198 {
199     return cs->event;
200 }
201
202 static int skip_crlf(const char *buf, int len, int *i)
203 {
204     if (*i < len)
205     {
206         if (buf[*i] == '\r' && *i < len-1 && buf[*i + 1] == '\n')
207         {
208             (*i) += 2;
209             return 1;
210         }
211         else if (buf[*i] == '\n')
212         {
213             (*i)++;
214             return 1;
215         }
216     }
217     return 0;
218 }
219
220 #define CHUNK_DEBUG 0
221
222 static int cs_read_chunk(const char *buf, int i, int len)
223 {
224     /* inside chunked body .. */
225     while (1)
226     {
227         int chunk_len = 0;
228 #if CHUNK_DEBUG
229         if (i < len-2)
230         {
231             printf ("\n<<<");
232             int j;
233             for (j = i; j <= i+3; j++)
234                 printf ("%c", buf[j]);
235             printf (">>>\n");
236         }
237 #endif
238         /* read chunk length */
239         while (1)
240             if (i >= len-2) {
241 #if CHUNK_DEBUG
242                 printf ("returning incomplete read at 1\n");
243                 printf ("i=%d len=%d\n", i, len);
244 #endif
245                 return 0;
246             } else if (yaz_isdigit(buf[i]))
247                 chunk_len = chunk_len * 16 + 
248                     (buf[i++] - '0');
249             else if (yaz_isupper(buf[i]))
250                 chunk_len = chunk_len * 16 + 
251                     (buf[i++] - ('A'-10));
252             else if (yaz_islower(buf[i]))
253                 chunk_len = chunk_len * 16 + 
254                     (buf[i++] - ('a'-10));
255             else
256                 break;
257         if (chunk_len == 0)
258             break;
259         if (chunk_len < 0)
260             return i;
261         
262         while (1)
263         {
264             if (i >= len -1)
265                 return 0;
266             if (skip_crlf(buf, len, &i))
267                 break;
268             i++;
269         }
270         /* got CRLF */
271 #if CHUNK_DEBUG
272         printf ("chunk_len=%d\n", chunk_len);
273 #endif                      
274         i += chunk_len;
275         if (i >= len-2)
276             return 0;
277         if (!skip_crlf(buf, len, &i))
278             return 0;
279     }
280     /* consider trailing headers .. */
281     while (i < len)
282     {
283         if (skip_crlf(buf, len, &i))
284         {
285             if (skip_crlf(buf, len, &i))
286                 return i;
287         }
288         else
289             i++;
290     }
291 #if CHUNK_DEBUG
292     printf ("returning incomplete read at 2\n");
293     printf ("i=%d len=%d\n", i, len);
294 #endif
295     return 0;
296 }
297
298 static int cs_complete_http(const char *buf, int len, int head_only)
299 {
300     /* deal with HTTP request/response */
301     int i = 2, content_len = 0, chunked = 0;
302
303     if (len < 6)
304         return 0;
305
306     /* if dealing with HTTP responses - then default
307        content length is unlimited (socket close) */
308     if (!head_only && !memcmp(buf, "HTTP/", 5))
309         content_len = -1; 
310
311 #if 0
312     printf("len = %d\n", len);
313     fwrite (buf, 1, len, stdout);
314     printf("----------\n");
315 #endif
316     while (i <= len-2)
317     {
318         if (i > 8192)
319         {
320             return i;  /* do not allow more than 8K HTTP header */
321         }
322         if (skip_crlf(buf, len, &i))
323         {
324             if (skip_crlf(buf, len, &i))
325             {
326                 /* inside content */
327                 if (chunked)
328                     return cs_read_chunk(buf, i, len);
329                 else
330                 {   /* not chunked ; inside body */
331                     if (content_len == -1)
332                         return 0;   /* no content length */
333                     else if (len >= i + content_len)
334                     {
335                         return i + content_len;
336                     }
337                 }
338                 break;
339             }
340             else if (i < len - 20 && 
341                      !strncasecmp((const char *) buf+i, "Transfer-Encoding:", 18))
342             {
343                 i+=18;
344                 while (buf[i] == ' ')
345                     i++;
346                 if (i < len - 8)
347                     if (!strncasecmp((const char *) buf+i, "chunked", 7))
348                         chunked = 1;
349             }
350             else if (i < len - 17 &&
351                      !strncasecmp((const char *)buf+i, "Content-Length:", 15))
352             {
353                 i+= 15;
354                 while (buf[i] == ' ')
355                     i++;
356                 content_len = 0;
357                 while (i <= len-4 && yaz_isdigit(buf[i]))
358                     content_len = content_len*10 + (buf[i++] - '0');
359                 if (content_len < 0) /* prevent negative offsets */
360                     content_len = 0;
361             }
362             else
363                 i++;
364         }
365         else
366             i++;
367     }
368     return 0;
369 }
370
371 static int cs_complete_auto_x(const char *buf, int len, int head_only)
372 {
373     if (len > 5 && buf[0] >= 0x20 && buf[0] < 0x7f
374                 && buf[1] >= 0x20 && buf[1] < 0x7f
375                 && buf[2] >= 0x20 && buf[2] < 0x7f)
376     {
377         int r = cs_complete_http(buf, len, head_only);
378         return r;
379     }
380     return completeBER((const unsigned char *) buf, len);
381 }
382
383
384 int cs_complete_auto(const char *buf, int len)
385 {
386     return cs_complete_auto_x(buf, len, 0);
387 }
388
389 int cs_complete_auto_head(const char *buf, int len)
390 {
391     return cs_complete_auto_x(buf, len, 1);
392 }
393
394 void cs_set_max_recv_bytes(COMSTACK cs, int max_recv_bytes)
395 {
396     cs->max_recv_bytes = max_recv_bytes;
397 }
398
399 /*
400  * Local variables:
401  * c-basic-offset: 4
402  * c-file-style: "Stroustrup"
403  * indent-tabs-mode: nil
404  * End:
405  * vim: shiftwidth=4 tabstop=8 expandtab
406  */
407