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