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