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