Todo
[pazpar2-moved-to-github.git] / http.c
1 /*
2  * $Id: http.c,v 1.4 2006-11-27 14:35:15 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 #include <netdb.h>
15 #include <errno.h>
16
17 #include <yaz/yaz-util.h>
18 #include <yaz/comstack.h>
19 #include <netdb.h>
20
21 #include "command.h"
22 #include "util.h"
23 #include "eventl.h"
24 #include "pazpar2.h"
25 #include "http.h"
26 #include "http_command.h"
27
28 static void proxy_io(IOCHAN i, int event);
29
30 extern IOCHAN channel_list;
31
32 static struct sockaddr_in *proxy_addr = 0; // If this is set, we proxy normal HTTP requests
33 static char proxy_url[256] = "";
34 static struct http_buf *http_buf_freelist = 0;
35
36 static struct http_buf *http_buf_create()
37 {
38     struct http_buf *r;
39
40     if (http_buf_freelist)
41     {
42         r = http_buf_freelist;
43         http_buf_freelist = http_buf_freelist->next;
44     }
45     else
46         r = xmalloc(sizeof(struct http_buf));
47     r->offset = 0;
48     r->len = 0;
49     r->next = 0;
50     return r;
51 }
52
53 static void http_buf_destroy(struct http_buf *b)
54 {
55     b->next = http_buf_freelist;
56     http_buf_freelist = b;
57 }
58
59 static void http_buf_destroy_queue(struct http_buf *b)
60 {
61     struct http_buf *p;
62     while (b)
63     {
64         p = b->next;
65         http_buf_destroy(b);
66         b = p;
67     }
68 }
69
70 #ifdef GAGA
71 // Calculate length of chain
72 static int http_buf_len(struct http_buf *b)
73 {
74     int sum = 0;
75     for (; b; b = b->next)
76         sum += b->len;
77     return sum;
78 }
79 #endif
80
81 static struct http_buf *http_buf_bybuf(char *b, int len)
82 {
83     struct http_buf *res = 0;
84     struct http_buf **p = &res;
85
86     while (len)
87     {
88         *p = http_buf_create();
89         int tocopy = len;
90         if (tocopy > HTTP_BUF_SIZE)
91             tocopy = HTTP_BUF_SIZE;
92         memcpy((*p)->buf, b, tocopy);
93         (*p)->len = tocopy;
94         len -= tocopy;
95         b += tocopy;
96         p = &(*p)->next;
97     }
98     return res;
99 }
100
101 // Add a (chain of) buffers to the end of an existing queue.
102 static void http_buf_enqueue(struct http_buf **queue, struct http_buf *b)
103 {
104     while (*queue)
105         queue = &(*queue)->next;
106     *queue = b;
107 }
108
109 static struct http_buf *http_buf_bywrbuf(WRBUF wrbuf)
110 {
111     return http_buf_bybuf(wrbuf_buf(wrbuf), wrbuf_len(wrbuf));
112 }
113
114 // Non-destructively collapse chain of buffers into a string (max *len)
115 // Return
116 static int http_buf_peek(struct http_buf *b, char *buf, int len)
117 {
118     int rd = 0;
119     while (b && rd < len)
120     {
121         int toread = len - rd;
122         if (toread > b->len)
123             toread = b->len;
124         memcpy(buf + rd, b->buf + b->offset, toread);
125         rd += toread;
126         b = b->next;
127     }
128     buf[rd] = '\0';
129     return rd;
130 }
131
132 // Ddestructively munch up to len  from head of queue.
133 static int http_buf_read(struct http_buf **b, char *buf, int len)
134 {
135     int rd = 0;
136     while ((*b) && rd < len)
137     {
138         int toread = len - rd;
139         if (toread > (*b)->len)
140             toread = (*b)->len;
141         memcpy(buf + rd, (*b)->buf + (*b)->offset, toread);
142         rd += toread;
143         if (toread < (*b)->len)
144         {
145             (*b)->len -= toread;
146             (*b)->offset += toread;
147             break;
148         }
149         else
150         {
151             struct http_buf *n = (*b)->next;
152             http_buf_destroy(*b);
153             *b = n;
154         }
155     }
156     buf[rd] = '\0';
157     return rd;
158 }
159
160 void static urldecode(char *i, char *o)
161 {
162     while (*i)
163     {
164         if (*i == '+')
165         {
166             *(o++) = ' ';
167             i++;
168         }
169         else if (*i == '%')
170         {
171             i++;
172             sscanf(i, "%2hhx", o);
173             i += 2;
174             o++;
175         }
176         else
177             *(o++) = *(i++);
178     }
179     *o = '\0';
180 }
181
182 void http_addheader(struct http_response *r, const char *name, const char *value)
183 {
184     struct http_channel *c = r->channel;
185     struct http_header *h = nmem_malloc(c->nmem, sizeof *h);
186     h->name = nmem_strdup(c->nmem, name);
187     h->value = nmem_strdup(c->nmem, value);
188     h->next = r->headers;
189     r->headers = h;
190 }
191
192 char *http_argbyname(struct http_request *r, char *name)
193 {
194     struct http_argument *p;
195     if (!name)
196         return 0;
197     for (p = r->arguments; p; p = p->next)
198         if (!strcmp(p->name, name))
199             return p->value;
200     return 0;
201 }
202
203 char *http_headerbyname(struct http_request *r, char *name)
204 {
205     struct http_header *p;
206     for (p = r->headers; p; p = p->next)
207         if (!strcmp(p->name, name))
208             return p->value;
209     return 0;
210 }
211
212 struct http_response *http_create_response(struct http_channel *c)
213 {
214     struct http_response *r = nmem_malloc(c->nmem, sizeof(*r));
215     strcpy(r->code, "200");
216     r->msg = "OK";
217     r->channel = c;
218     r->headers = 0;
219     r->payload = 0;
220     return r;
221 }
222
223 // Check if we have a complete request. Return 0 or length (including trailing newline)
224 // FIXME: Does not deal gracefully with requests carrying payload
225 // but this is kind of OK since we will reject anything other than an empty GET
226 static int request_check(struct http_buf *queue)
227 {
228     char tmp[4096];
229     int len = 0;
230     char *buf = tmp;
231
232     http_buf_peek(queue, tmp, 4096);
233     while (*buf) // Check if we have a sequence of lines terminated by an empty line
234     {
235         char *b = strstr(buf, "\r\n");
236
237         if (!b)
238             return 0;
239
240         len += (b - buf) + 2;
241         if (b == buf)
242             return len;
243         buf = b + 2;
244     }
245     return 0;
246 }
247
248 struct http_request *http_parse_request(struct http_channel *c, struct http_buf **queue,
249         int len)
250 {
251     struct http_request *r = nmem_malloc(c->nmem, sizeof(*r));
252     char *p, *p2;
253     char tmp[4096];
254     char *buf = tmp;
255
256     if (len > 4096)
257         return 0;
258     if (http_buf_read(queue, buf, len) < len)
259         return 0;
260
261     r->channel = c;
262     r->arguments = 0;
263     r->headers = 0;
264     // Parse first line
265     for (p = buf, p2 = r->method; *p && *p != ' ' && p - buf < 19; p++)
266         *(p2++) = *p;
267     if (*p != ' ')
268     {
269         yaz_log(YLOG_WARN, "Unexpected HTTP method in request");
270         return 0;
271     }
272     *p2 = '\0';
273
274     if (!(buf = strchr(buf, ' ')))
275     {
276         yaz_log(YLOG_WARN, "Syntax error in request (1)");
277         return 0;
278     }
279     buf++;
280     if (!(p = strchr(buf, ' ')))
281     {
282         yaz_log(YLOG_WARN, "Syntax error in request (2)");
283         return 0;
284     }
285     *(p++) = '\0';
286     if ((p2 = strchr(buf, '?'))) // Do we have arguments?
287         *(p2++) = '\0';
288     r->path = nmem_strdup(c->nmem, buf);
289     if (p2)
290     {
291         // Parse Arguments
292         while (*p2)
293         {
294             struct http_argument *a;
295             char *equal = strchr(p2, '=');
296             char *eoa = strchr(p2, '&');
297             if (!equal)
298             {
299                 yaz_log(YLOG_WARN, "Expected '=' in argument");
300                 return 0;
301             }
302             if (!eoa)
303                 eoa = equal + strlen(equal); // last argument
304             else
305                 *(eoa++) = '\0';
306             a = nmem_malloc(c->nmem, sizeof(struct http_argument));
307             *(equal++) = '\0';
308             a->name = nmem_strdup(c->nmem, p2);
309             urldecode(equal, equal);
310             a->value = nmem_strdup(c->nmem, equal);
311             a->next = r->arguments;
312             r->arguments = a;
313             p2 = eoa;
314         }
315     }
316     buf = p;
317
318     if (strncmp(buf, "HTTP/", 5))
319         strcpy(r->http_version, "1.0");
320     else
321     {
322         buf += 5;
323         if (!(p = strstr(buf, "\r\n")))
324             return 0;
325         *(p++) = '\0';
326         p++;
327         strcpy(r->http_version, buf);
328         buf = p;
329     }
330     strcpy(c->version, r->http_version);
331
332     r->headers = 0;
333     while (*buf)
334     {
335         if (!(p = strstr(buf, "\r\n")))
336             return 0;
337         if (p == buf)
338             break;
339         else
340         {
341             struct http_header *h = nmem_malloc(c->nmem, sizeof(*h));
342             if (!(p2 = strchr(buf, ':')))
343                 return 0;
344             *(p2++) = '\0';
345             h->name = nmem_strdup(c->nmem, buf);
346             while (isspace(*p2))
347                 p2++;
348             if (p2 >= p) // Empty header?
349             {
350                 buf = p + 2;
351                 continue;
352             }
353             *p = '\0';
354             h->value = nmem_strdup(c->nmem, p2);
355             h->next = r->headers;
356             r->headers = h;
357             buf = p + 2;
358         }
359     }
360
361     return r;
362 }
363
364
365 static struct http_buf *http_serialize_response(struct http_channel *c,
366         struct http_response *r)
367 {
368     wrbuf_rewind(c->wrbuf);
369     struct http_header *h;
370
371     wrbuf_printf(c->wrbuf, "HTTP/1.1 %s %s\r\n", r->code, r->msg);
372     for (h = r->headers; h; h = h->next)
373         wrbuf_printf(c->wrbuf, "%s: %s\r\n", h->name, h->value);
374     wrbuf_printf(c->wrbuf, "Content-length: %d\r\n", r->payload ? strlen(r->payload) : 0);
375     wrbuf_printf(c->wrbuf, "Content-type: text/xml\r\n");
376     wrbuf_puts(c->wrbuf, "\r\n");
377
378     if (r->payload)
379         wrbuf_puts(c->wrbuf, r->payload);
380
381     return http_buf_bywrbuf(c->wrbuf);
382 }
383
384 // Serialize a HTTP request
385 static struct http_buf *http_serialize_request(struct http_request *r)
386 {
387     struct http_channel *c = r->channel;
388     wrbuf_rewind(c->wrbuf);
389     struct http_header *h;
390     struct http_argument *a;
391
392     wrbuf_printf(c->wrbuf, "%s %s", r->method, r->path);
393
394     if (r->arguments)
395     {
396         wrbuf_putc(c->wrbuf, '?');
397         for (a = r->arguments; a; a = a->next) {
398             if (a != r->arguments)
399                 wrbuf_putc(c->wrbuf, '&');
400             wrbuf_printf(c->wrbuf, "%s=%s", a->name, a->value);
401         }
402     }
403
404     wrbuf_printf(c->wrbuf, " HTTP/%s\r\n", r->http_version);
405
406     for (h = r->headers; h; h = h->next)
407         wrbuf_printf(c->wrbuf, "%s: %s\r\n", h->name, h->value);
408
409     wrbuf_puts(c->wrbuf, "\r\n");
410     
411     return http_buf_bywrbuf(c->wrbuf);
412 }
413
414
415 // Cleanup
416 static void http_destroy(IOCHAN i)
417 {
418     struct http_channel *s = iochan_getdata(i);
419
420     if (s->proxy)
421     {
422         if (s->proxy->iochan)
423         {
424             close(iochan_getfd(s->proxy->iochan));
425             iochan_destroy(s->proxy->iochan);
426         }
427         http_buf_destroy_queue(s->proxy->oqueue);
428         xfree(s->proxy);
429     }
430     http_buf_destroy_queue(s->iqueue);
431     http_buf_destroy_queue(s->oqueue);
432     nmem_destroy(s->nmem);
433     wrbuf_free(s->wrbuf, 1);
434     xfree(s);
435     close(iochan_getfd(i));
436     iochan_destroy(i);
437 }
438
439 static int http_weshouldproxy(struct http_request *rq)
440 {
441     if (proxy_addr && !strstr(rq->path, "search.pz2"))
442         return 1;
443     return 0;
444 }
445
446 static int http_proxy(struct http_request *rq)
447 {
448     struct http_channel *c = rq->channel;
449     struct http_proxy *p = c->proxy;
450     struct http_header *hp;
451     struct http_buf *requestbuf;
452
453     if (!p) // This is a new connection. Create a proxy channel
454     {
455         int sock;
456         struct protoent *pe;
457         int one = 1;
458         int flags;
459
460         if (!(pe = getprotobyname("tcp"))) {
461             abort();
462         }
463         if ((sock = socket(PF_INET, SOCK_STREAM, pe->p_proto)) < 0)
464         {
465             yaz_log(YLOG_WARN|YLOG_ERRNO, "socket");
466             return -1;
467         }
468         if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)
469                         &one, sizeof(one)) < 0)
470             abort();
471         if ((flags = fcntl(sock, F_GETFL, 0)) < 0) 
472             yaz_log(YLOG_FATAL|YLOG_ERRNO, "fcntl");
473         if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0)
474             yaz_log(YLOG_FATAL|YLOG_ERRNO, "fcntl2");
475         if (connect(sock, (struct sockaddr *) proxy_addr, sizeof(*proxy_addr)) < 0)
476             if (errno != EINPROGRESS)
477             {
478                 yaz_log(YLOG_WARN|YLOG_ERRNO, "Proxy connect");
479                 return -1;
480             }
481
482         p = xmalloc(sizeof(struct http_proxy));
483         p->oqueue = 0;
484         p->channel = c;
485         c->proxy = p;
486         // We will add EVENT_OUTPUT below
487         p->iochan = iochan_create(sock, proxy_io, EVENT_INPUT);
488         iochan_setdata(p->iochan, p);
489         p->iochan->next = channel_list;
490         channel_list = p->iochan;
491     }
492
493     // Modify Host: header
494     for (hp = rq->headers; hp; hp = hp->next)
495         if (!strcmp(hp->name, "Host"))
496             break;
497     if (!hp)
498     {
499         yaz_log(YLOG_WARN, "Failed to find Host header in proxy");
500         return -1;
501     }
502     hp->value = nmem_strdup(c->nmem, proxy_url);
503     requestbuf = http_serialize_request(rq);
504     http_buf_enqueue(&p->oqueue, requestbuf);
505     iochan_setflag(p->iochan, EVENT_OUTPUT);
506     return 0;
507 }
508
509 static void http_io(IOCHAN i, int event)
510 {
511     struct http_channel *hc = iochan_getdata(i);
512     struct http_request *request;
513     struct http_response *response;
514
515     switch (event)
516     {
517         int res, reqlen;
518         struct http_buf *htbuf;
519
520         case EVENT_INPUT:
521             htbuf = http_buf_create();
522             res = read(iochan_getfd(i), htbuf->buf, HTTP_BUF_SIZE -1);
523             if (res <= 0 && errno != EAGAIN)
524             {
525                 http_buf_destroy(htbuf);
526                 http_destroy(i);
527                 return;
528             }
529             if (res > 0)
530             {
531                 htbuf->buf[res] = '\0';
532                 htbuf->len = res;
533                 http_buf_enqueue(&hc->iqueue, htbuf);
534             }
535
536             if ((reqlen = request_check(hc->iqueue)) <= 2)
537                 return;
538
539             nmem_reset(hc->nmem);
540             if (!(request = http_parse_request(hc, &hc->iqueue, reqlen)))
541             {
542                 yaz_log(YLOG_WARN, "Failed to parse request");
543                 http_destroy(i);
544                 return;
545             }
546             yaz_log(YLOG_LOG, "Request: %s %s v %s", request->method,  request->path,
547                     request->http_version);
548             if (http_weshouldproxy(request))
549                 http_proxy(request);
550             else
551             {
552                 struct http_buf *hb;
553                 // Execute our business logic!
554                 response = http_command(request);
555                 if (!response)
556                 {
557                     http_destroy(i);
558                     return;
559                 }
560                 if (!(hb =  http_serialize_response(hc, response)))
561                 {
562                     http_destroy(i);
563                     return;
564                 }
565                 http_buf_enqueue(&hc->oqueue, hb);
566                 iochan_setflags(i, EVENT_OUTPUT); // Turns off input selecting
567             }
568             if (hc->iqueue)
569             {
570                 yaz_log(YLOG_DEBUG, "We think we have more input to read. Forcing event");
571                 iochan_setevent(i, EVENT_INPUT);
572             }
573
574             break;
575
576         case EVENT_OUTPUT:
577             if (hc->oqueue)
578             {
579                 struct http_buf *wb = hc->oqueue;
580                 res = write(iochan_getfd(hc->iochan), wb->buf + wb->offset, wb->len);
581                 if (res <= 0)
582                 {
583                     yaz_log(YLOG_WARN|YLOG_ERRNO, "write");
584                     http_destroy(i);
585                     return;
586                 }
587                 if (res == wb->len)
588                 {
589                     hc->oqueue = hc->oqueue->next;
590                     http_buf_destroy(wb);
591                 }
592                 else
593                 {
594                     wb->len -= res;
595                     wb->offset += res;
596                 }
597                 if (!hc->oqueue) {
598                     if (!strcmp(hc->version, "1.0"))
599                     {
600                         http_destroy(i);
601                         return;
602                     }
603                     else
604                         iochan_setflags(i, EVENT_INPUT); // Turns off output flag
605                 }
606             }
607
608             if (!hc->oqueue && hc->proxy && !hc->proxy->iochan) 
609                 http_destroy(i); // Server closed; we're done
610             break;
611         default:
612             yaz_log(YLOG_WARN, "Unexpected event on connection");
613             http_destroy(i);
614     }
615 }
616
617 // Handles I/O on a client connection to a backend web server (proxy mode)
618 static void proxy_io(IOCHAN pi, int event)
619 {
620     struct http_proxy *pc = iochan_getdata(pi);
621     struct http_channel *hc = pc->channel;
622
623     switch (event)
624     {
625         int res;
626         struct http_buf *htbuf;
627
628         case EVENT_INPUT:
629             htbuf = http_buf_create();
630             res = read(iochan_getfd(pi), htbuf->buf, HTTP_BUF_SIZE -1);
631             if (res == 0 || (res < 0 && errno != EINPROGRESS))
632             {
633                 if (hc->oqueue)
634                 {
635                     yaz_log(YLOG_WARN, "Proxy read came up short");
636                     // Close channel and alert client HTTP channel that we're gone
637                     http_buf_destroy(htbuf);
638                     close(iochan_getfd(pi));
639                     iochan_destroy(pi);
640                     pc->iochan = 0;
641                 }
642                 else
643                 {
644                     http_destroy(hc->iochan);
645                     return;
646                 }
647             }
648             else
649             {
650                 htbuf->buf[res] = '\0';
651                 htbuf->len = res;
652                 http_buf_enqueue(&hc->oqueue, htbuf);
653             }
654             iochan_setflag(hc->iochan, EVENT_OUTPUT);
655             break;
656         case EVENT_OUTPUT:
657             if (!(htbuf = pc->oqueue))
658             {
659                 iochan_clearflag(pi, EVENT_OUTPUT);
660                 return;
661             }
662             res = write(iochan_getfd(pi), htbuf->buf + htbuf->offset, htbuf->len);
663             if (res <= 0)
664             {
665                 yaz_log(YLOG_WARN|YLOG_ERRNO, "write");
666                 http_destroy(hc->iochan);
667                 return;
668             }
669             if (res == htbuf->len)
670             {
671                 struct http_buf *np = htbuf->next;
672                 http_buf_destroy(htbuf);
673                 pc->oqueue = np;
674             }
675             else
676             {
677                 htbuf->len -= res;
678                 htbuf->offset += res;
679             }
680
681             if (!pc->oqueue) {
682                 iochan_setflags(pi, EVENT_INPUT); // Turns off output flag
683             }
684             break;
685         default:
686             yaz_log(YLOG_WARN, "Unexpected event on connection");
687             http_destroy(hc->iochan);
688     }
689 }
690
691 /* Accept a new command connection */
692 static void http_accept(IOCHAN i, int event)
693 {
694     struct sockaddr_in addr;
695     int fd = iochan_getfd(i);
696     socklen_t len;
697     int s;
698     IOCHAN c;
699     int flags;
700     struct http_channel *ch;
701
702     len = sizeof addr;
703     if ((s = accept(fd, (struct sockaddr *) &addr, &len)) < 0)
704     {
705         yaz_log(YLOG_WARN|YLOG_ERRNO, "accept");
706         return;
707     }
708     if ((flags = fcntl(s, F_GETFL, 0)) < 0) 
709         yaz_log(YLOG_FATAL|YLOG_ERRNO, "fcntl");
710     if (fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0)
711         yaz_log(YLOG_FATAL|YLOG_ERRNO, "fcntl2");
712
713     yaz_log(YLOG_LOG, "New command connection");
714     c = iochan_create(s, http_io, EVENT_INPUT | EVENT_EXCEPT);
715
716     ch = xmalloc(sizeof(*ch));
717     ch->proxy = 0;
718     ch->nmem = nmem_create();
719     ch->wrbuf = wrbuf_alloc();
720     ch->iochan = c;
721     ch->iqueue = ch->oqueue = 0;
722     iochan_setdata(c, ch);
723
724     c->next = channel_list;
725     channel_list = c;
726 }
727
728 /* Create a http-channel listener */
729 void http_init(int port)
730 {
731     IOCHAN c;
732     int l;
733     struct protoent *p;
734     struct sockaddr_in myaddr;
735     int one = 1;
736
737     yaz_log(YLOG_LOG, "HTTP port is %d", port);
738     if (!(p = getprotobyname("tcp"))) {
739         abort();
740     }
741     if ((l = socket(PF_INET, SOCK_STREAM, p->p_proto)) < 0)
742         yaz_log(YLOG_FATAL|YLOG_ERRNO, "socket");
743     if (setsockopt(l, SOL_SOCKET, SO_REUSEADDR, (char*)
744                     &one, sizeof(one)) < 0)
745         abort();
746
747     bzero(&myaddr, sizeof myaddr);
748     myaddr.sin_family = AF_INET;
749     myaddr.sin_addr.s_addr = INADDR_ANY;
750     myaddr.sin_port = htons(port);
751     if (bind(l, (struct sockaddr *) &myaddr, sizeof myaddr) < 0) 
752         yaz_log(YLOG_FATAL|YLOG_ERRNO, "bind");
753     if (listen(l, SOMAXCONN) < 0) 
754         yaz_log(YLOG_FATAL|YLOG_ERRNO, "listen");
755
756     c = iochan_create(l, http_accept, EVENT_INPUT | EVENT_EXCEPT);
757     c->next = channel_list;
758     channel_list = c;
759 }
760
761 void http_set_proxyaddr(char *host)
762 {
763     char *p;
764     int port;
765     struct hostent *he;
766
767     strcpy(proxy_url, host);
768     p = strchr(host, ':');
769     yaz_log(YLOG_DEBUG, "Proxying for %s", host);
770     if (p) {
771         port = atoi(p + 1);
772         *p = '\0';
773     }
774     else
775         port = 80;
776     if (!(he = gethostbyname(host))) 
777     {
778         fprintf(stderr, "Failed to lookup '%s'\n", host);
779         exit(1);
780     }
781     proxy_addr = xmalloc(sizeof(struct sockaddr_in));
782     proxy_addr->sin_family = he->h_addrtype;
783     memcpy(&proxy_addr->sin_addr.s_addr, he->h_addr_list[0], he->h_length);
784     proxy_addr->sin_port = htons(port);
785 }
786
787 /*
788  * Local variables:
789  * c-basic-offset: 4
790  * indent-tabs-mode: nil
791  * End:
792  * vim: shiftwidth=4 tabstop=8 expandtab
793  */