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