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