9de5c05178f20dfc80c68e0463fa45c3d9399f65
[pazpar2-moved-to-github.git] / http.c
1 /*
2  * $Id: http.c,v 1.1 2006-11-21 18:46:43 quinn Exp $
3  */
4
5 #include <stdio.h>
6 #include <sys/socket.h>
7 #include <sys/types.h>
8 #include <sys/uio.h>
9 #include <unistd.h>
10 #include <stdlib.h>
11 #include <strings.h>
12 #include <ctype.h>
13 #include <fcntl.h>
14
15 #include <yaz/yaz-util.h>
16 #include <yaz/comstack.h>
17 #include <netdb.h>
18
19 #include "command.h"
20 #include "util.h"
21 #include "eventl.h"
22 #include "pazpar2.h"
23 #include "http.h"
24 #include "http_command.h"
25
26 extern IOCHAN channel_list;
27
28 void http_addheader(struct http_response *r, const char *name, const char *value)
29 {
30     struct http_channel *c = r->channel;
31     struct http_header *h = nmem_malloc(c->nmem, sizeof *h);
32     h->name = nmem_strdup(c->nmem, name);
33     h->value = nmem_strdup(c->nmem, value);
34     h->next = r->headers;
35     r->headers = h;
36 }
37
38 char *argbyname(struct http_request *r, char *name)
39 {
40     struct http_argument *p;
41     for (p = r->arguments; p; p = p->next)
42         if (!strcmp(p->name, name))
43             return p->value;
44     return 0;
45 }
46
47 char *headerbyname(struct http_request *r, char *name)
48 {
49     struct http_header *p;
50     for (p = r->headers; p; p = p->next)
51         if (!strcmp(p->name, name))
52             return p->value;
53     return 0;
54 }
55
56 struct http_response *http_create_response(struct http_channel *c)
57 {
58     struct http_response *r = nmem_malloc(c->nmem, sizeof(*r));
59     strcpy(r->code, "200");
60     r->msg = "OK";
61     r->channel = c;
62     r->headers = 0;
63     r->payload = 0;
64     return r;
65 }
66
67 // Check if we have a complete request. Return 0 or length (including trailing newline)
68 // FIXME: Does not deal gracefully with requests carrying payload
69 // but this is kind of OK since we will reject anything other than an empty GET
70 static int request_check(const char *buf)
71 {
72     int len = 0;
73
74     while (*buf) // Check if we have a sequence of lines terminated by an empty line
75     {
76         char *b = strstr(buf, "\r\n");
77
78         if (!b)
79             return 0;
80
81         len += (b - buf) + 2;
82         if (b == buf)
83             return len;
84         buf = b + 2;
85     }
86     return 0;
87 }
88
89 struct http_request *http_parse_request(struct http_channel *c, char *buf)
90 {
91     struct http_request *r = nmem_malloc(c->nmem, sizeof(*r));
92     char *p, *p2;
93
94     r->channel = c;
95     // Parse first line
96     if (!strncmp(buf, "GET ", 4))
97         r->method = Method_GET;
98     else
99     {
100         yaz_log(YLOG_WARN, "Unexpected HTTP method in request");
101         return 0;
102     }
103     if (!(buf = strchr(buf, ' ')))
104     {
105         yaz_log(YLOG_WARN, "Syntax error in request (1)");
106         return 0;
107     }
108     buf++;
109     if (!(p = strchr(buf, ' ')))
110     {
111         yaz_log(YLOG_WARN, "Syntax error in request (2)");
112         return 0;
113     }
114     *(p++) = '\0';
115     if ((p2 = strchr(buf, '?'))) // Do we have arguments?
116         *(p2++) = '\0';
117     r->path = nmem_strdup(c->nmem, buf);
118     if (p2)
119     {
120         // Parse Arguments
121         while (*p2)
122         {
123             struct http_argument *a;
124             char *equal = strchr(p2, '=');
125             char *eoa = strchr(p2, '&');
126             if (!equal)
127             {
128                 yaz_log(YLOG_WARN, "Expected '=' in argument");
129                 return 0;
130             }
131             if (!eoa)
132                 eoa = equal + strlen(equal); // last argument
133             else
134                 *(eoa++) = '\0';
135             a = nmem_malloc(c->nmem, sizeof(struct http_argument));
136             *(equal++) = '\0';
137             a->name = nmem_strdup(c->nmem, p2);
138             a->value = nmem_strdup(c->nmem, equal);
139             a->next = r->arguments;
140             r->arguments = a;
141             p2 = eoa;
142         }
143     }
144     buf = p;
145
146     if (strncmp(buf, "HTTP/", 5))
147         strcpy(r->http_version, "1.0");
148     else
149     {
150         buf += 5;
151         if (!(p = strstr(buf, "\r\n")))
152             return 0;
153         *(p++) = '\0';
154         strcpy(r->http_version, buf);
155         buf = p;
156     }
157     strcpy(c->version, r->http_version);
158
159     r->headers = 0; // We might want to parse these someday
160
161     return r;
162 }
163
164
165 static char *http_serialize_response(struct http_channel *c, struct http_response *r)
166 {
167     wrbuf_rewind(c->wrbuf);
168     struct http_header *h;
169
170     wrbuf_printf(c->wrbuf, "HTTP/1.1 %s %s\r\n", r->code, r->msg);
171     for (h = r->headers; h; h = h->next)
172         wrbuf_printf(c->wrbuf, "%s: %s\r\n", h->name, h->value);
173     wrbuf_printf(c->wrbuf, "Content-length: %d\r\n", r->payload ? strlen(r->payload) : 0);
174     wrbuf_printf(c->wrbuf, "Content-type: text/xml\r\n");
175     wrbuf_puts(c->wrbuf, "\r\n");
176
177     if (r->payload)
178         wrbuf_puts(c->wrbuf, r->payload);
179
180     wrbuf_putc(c->wrbuf, '\0');
181     return wrbuf_buf(c->wrbuf);
182 }
183
184 // Cleanup
185 static void http_destroy(IOCHAN i)
186 {
187     struct http_channel *s = iochan_getdata(i);
188
189     yaz_log(YLOG_DEBUG, "Destroying http channel");
190     nmem_destroy(s->nmem);
191     wrbuf_free(s->wrbuf, 1);
192     xfree(s);
193     close(iochan_getfd(i));
194     iochan_destroy(i);
195 }
196
197 static void http_io(IOCHAN i, int event)
198 {
199     struct http_channel *hc = iochan_getdata(i);
200     struct http_request *request;
201     struct http_response *response;
202
203     switch (event)
204     {
205         int res;
206
207         case EVENT_INPUT:
208             yaz_log(YLOG_DEBUG, "HTTP Input event");
209
210             res = read(iochan_getfd(i), hc->ibuf + hc->read, IBUF_SIZE - (hc->read + 1));
211             if (res <= 0)
212             {
213                 yaz_log(YLOG_WARN|YLOG_ERRNO, "HTTP read");
214                 http_destroy(i);
215                 return;
216             }
217             yaz_log(YLOG_DEBUG, "HTTP read %d octets", res);
218             hc->read += res;
219             hc->ibuf[hc->read] = '\0';
220
221             if ((res = request_check(hc->ibuf)) <= 2)
222             {
223                 yaz_log(YLOG_DEBUG, "We don't have a complete HTTP request yet");
224                 return;
225             }
226             yaz_log(YLOG_DEBUG, "We think we have a complete HTTP request (len %d): \n%s", res,  hc->ibuf);
227             nmem_reset(hc->nmem);
228             if (!(request = http_parse_request(hc, hc->ibuf)))
229             {
230                 yaz_log(YLOG_WARN, "Failed to parse request");
231                 http_destroy(i);
232                 return;
233             }
234             response = http_command(request);
235             if (!response)
236             {
237                 http_destroy(i);
238                 return;
239             }
240             // FIXME -- do something to cause the response to be sent to the client
241             if (!(hc->obuf = http_serialize_response(hc, response)))
242             {
243                 http_destroy(i);
244                 return;
245             }
246             yaz_log(YLOG_DEBUG, "Response ready:\n%s", hc->obuf);
247             hc->writ = 0;
248             hc->read = 0;
249             iochan_setflags(i, EVENT_OUTPUT); // Turns off input selecting
250             break;
251
252         case EVENT_OUTPUT:
253             yaz_log(YLOG_DEBUG, "HTTP output event");
254             res = write(iochan_getfd(hc->iochan), hc->obuf + hc->writ,
255                         strlen(hc->obuf + hc->writ));
256             if (res <= 0)
257             {
258                 yaz_log(YLOG_WARN|YLOG_ERRNO, "write");
259                 http_destroy(i);
260                 return;
261             }
262             hc->writ += res;
263             if (!hc->obuf[hc->writ]) {
264                 yaz_log(YLOG_DEBUG, "Writing finished");
265                 if (!strcmp(hc->version, "1.0"))
266                 {
267                     yaz_log(YLOG_DEBUG, "Closing 1.0 connection");
268                     http_destroy(i);
269                 }
270                 else
271                     iochan_setflags(i, EVENT_INPUT); // Turns off output flag
272             }
273             break;
274         default:
275             yaz_log(YLOG_WARN, "Unexpected event on connection");
276             http_destroy(i);
277     }
278 }
279
280 /* Accept a new command connection */
281 static void http_accept(IOCHAN i, int event)
282 {
283     struct sockaddr_in addr;
284     int fd = iochan_getfd(i);
285     socklen_t len;
286     int s;
287     IOCHAN c;
288     int flags;
289     struct http_channel *ch;
290
291     len = sizeof addr;
292     if ((s = accept(fd, (struct sockaddr *) &addr, &len)) < 0)
293     {
294         yaz_log(YLOG_WARN|YLOG_ERRNO, "accept");
295         return;
296     }
297     if ((flags = fcntl(s, F_GETFL, 0)) < 0) 
298         yaz_log(YLOG_FATAL|YLOG_ERRNO, "fcntl");
299     if (fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0)
300         yaz_log(YLOG_FATAL|YLOG_ERRNO, "fcntl2");
301
302     yaz_log(YLOG_LOG, "New command connection");
303     c = iochan_create(s, http_io, EVENT_INPUT | EVENT_EXCEPT);
304
305     ch = xmalloc(sizeof(*ch));
306     ch->read = 0;
307     ch->nmem = nmem_create();
308     ch->wrbuf = wrbuf_alloc();
309     ch->iochan = c;
310     iochan_setdata(c, ch);
311
312     c->next = channel_list;
313     channel_list = c;
314 }
315
316
317 /* Create a http-channel listener */
318 void http_init(int port)
319 {
320     IOCHAN c;
321     int l;
322     struct protoent *p;
323     struct sockaddr_in myaddr;
324     int one = 1;
325
326     yaz_log(YLOG_LOG, "HTTP port is %d", port);
327     if (!(p = getprotobyname("tcp"))) {
328         abort();
329     }
330     if ((l = socket(PF_INET, SOCK_STREAM, p->p_proto)) < 0)
331         yaz_log(YLOG_FATAL|YLOG_ERRNO, "socket");
332     if (setsockopt(l, SOL_SOCKET, SO_REUSEADDR, (char*)
333                     &one, sizeof(one)) < 0)
334         abort();
335
336     bzero(&myaddr, sizeof myaddr);
337     myaddr.sin_family = AF_INET;
338     myaddr.sin_addr.s_addr = INADDR_ANY;
339     myaddr.sin_port = htons(port);
340     if (bind(l, (struct sockaddr *) &myaddr, sizeof myaddr) < 0) 
341         yaz_log(YLOG_FATAL|YLOG_ERRNO, "bind");
342     if (listen(l, SOMAXCONN) < 0) 
343         yaz_log(YLOG_FATAL|YLOG_ERRNO, "listen");
344
345     c = iochan_create(l, http_accept, EVENT_INPUT | EVENT_EXCEPT);
346     c->next = channel_list;
347     channel_list = c;
348 }
349
350
351 /*
352  * Local variables:
353  * c-basic-offset: 4
354  * indent-tabs-mode: nil
355  * End:
356  * vim: shiftwidth=4 tabstop=8 expandtab
357  */