r6989: - added support for esp style includes (which include a esp file, instead...
[bbaumbach/samba-autobuild/.git] / source4 / web_server / http.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    http handling code
5
6    Copyright (C) Andrew Tridgell 2005
7    
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 2 of the License, or
11    (at your option) any later version.
12    
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17    
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 */
22
23 #include "includes.h"
24 #include "web_server/web_server.h"
25 #include "smbd/service_stream.h"
26 #include "lib/events/events.h"
27 #include "system/filesys.h"
28 #include "system/iconv.h"
29 #include "system/time.h"
30 #include "web_server/esp/esp.h"
31
32 /* state of the esp subsystem */
33 struct esp_state {
34         struct websrv_context *web;
35         struct MprVar variables[ESP_OBJ_MAX];
36         struct EspRequest *req;
37 };
38
39 /* destroy a esp session */
40 static int esp_destructor(void *ptr)
41 {
42         struct esp_state *esp = talloc_get_type(ptr, struct esp_state);
43         if (esp->req) {
44                 espDestroyRequest(esp->req);
45         }
46         espClose();
47         mprFreeAll();
48         return 0;
49 }
50
51 /*
52   output the http headers
53 */
54 static void http_output_headers(struct websrv_context *web)
55 {
56         int i;
57         char *s;
58         DATA_BLOB b;
59         const char *response_string = "Unknown Code";
60         const struct {
61                 unsigned code;
62                 const char *response_string;
63         } codes[] = {
64                 { 200, "OK" },
65                 { 301, "Moved" },
66                 { 302, "Found" },
67                 { 303, "Method" },
68                 { 304, "Not Modified" },
69                 { 400, "Bad request" },
70                 { 401, "Unauthorized" },
71                 { 403, "Forbidden" },
72                 { 404, "Not Found" },
73                 { 500, "Internal Server Error" },
74                 { 501, "Not implemented" }
75         };
76         for (i=0;i<ARRAY_SIZE(codes);i++) {
77                 if (codes[i].code == web->output.response_code) {
78                         response_string = codes[i].response_string;
79                 }
80         }
81
82         if (web->output.headers == NULL) return;
83         s = talloc_asprintf(web, "HTTP/1.0 %u %s\r\n", 
84                             web->output.response_code, response_string);
85         if (s == NULL) return;
86         for (i=0;web->output.headers[i];i++) {
87                 s = talloc_asprintf_append(s, "%s\r\n", web->output.headers[i]);
88         }
89         s = talloc_asprintf_append(s, "\r\n");
90         if (s == NULL) return;
91
92         b = web->output.content;
93         web->output.content.data = s;
94         web->output.content.length = strlen(s);
95         data_blob_append(web, &web->output.content, b.data, b.length);
96         data_blob_free(&b);
97 }
98
99 /*
100   return the local path for a URL
101 */
102 static const char *http_local_path(struct websrv_context *web, const char *url)
103 {
104         int i;
105         char *path;
106
107         /* check that the url is OK */
108         if (url[0] != '/') return NULL;
109
110         for (i=0;url[i];i++) {
111                 if ((!isalnum(url[i]) && !strchr("./", url[i])) ||
112                     (url[i] == '.' && strchr("/.", url[i+1]))) {
113                         return NULL;
114                 }
115         }
116
117         path = talloc_asprintf(web, "%s/%s", lp_swat_directory(), url+1);
118         if (path == NULL) return NULL;
119
120         if (directory_exist(path)) {
121                 path = talloc_asprintf_append(path, "/index.html");
122         }
123         return path;
124 }
125
126 /*
127   called when esp wants to read a file to support include() calls
128 */
129 static int http_readFile(EspHandle handle, char **buf, int *len, const char *path)
130 {
131         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
132         int fd = -1;
133         struct stat st;
134         *buf = NULL;
135
136         path = http_local_path(web, path);
137         if (path == NULL) goto failed;
138
139         fd = open(path, O_RDONLY);
140         if (fd == -1 || fstat(fd, &st) != 0 || !S_ISREG(st.st_mode)) goto failed;
141
142         *buf = talloc_size(handle, st.st_size+1);
143         if (*buf == NULL) goto failed;
144
145         if (read(fd, *buf, st.st_size) != st.st_size) goto failed;
146
147         (*buf)[st.st_size] = 0;
148
149         close(fd);
150         *len = st.st_size;
151         return 0;
152
153 failed:
154         if (fd != -1) close(fd);
155         talloc_free(*buf);
156         *buf = NULL;
157         return -1;
158 }
159
160 /*
161   called when esp wants to find the real path of a file
162 */
163 static int http_mapToStorage(EspHandle handle, char *path, int len, const char *uri, int flags)
164 {
165         if (uri == NULL || strlen(uri) >= len) return -1;
166         strncpy(path, uri, len);
167         return 0;
168 }
169
170 /*
171   called when esp wants to output something
172 */
173 static int http_writeBlock(EspHandle handle, char *buf, int size)
174 {
175         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
176         NTSTATUS status;
177         status = data_blob_append(web, &web->output.content, buf, size);
178         if (!NT_STATUS_IS_OK(status)) return -1;
179         return size;
180 }
181
182
183 /*
184   set a http header
185 */
186 static void http_setHeader(EspHandle handle, const char *value, bool allowMultiple)
187 {
188         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
189         char *p = strchr(value, ':');
190
191         if (p && !allowMultiple && web->output.headers) {
192                 int i;
193                 for (i=0;web->output.headers[i];i++) {
194                         if (strncmp(web->output.headers[i], value, (p+1)-value) == 0) {
195                                 web->output.headers[i] = talloc_strdup(web, value);
196                                 return;
197                         }
198                 }
199         }
200
201         web->output.headers = str_list_add(web->output.headers, value);
202 }
203
204 /*
205   set a http response code
206 */
207 static void http_setResponseCode(EspHandle handle, int code)
208 {
209         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
210         web->output.response_code = code;
211 }
212
213 /*
214   redirect to another web page
215  */
216 static void http_redirect(EspHandle handle, int code, char *url)
217 {
218         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
219         const char *host = web->input.host;
220         
221         /* form the full url, unless it already looks like a url */
222         if (strchr(url, ':') == NULL) {
223                 if (host == NULL) {
224                         host = talloc_asprintf(web, "%s:%u",
225                                                socket_get_my_addr(web->conn->socket, web),
226                                                socket_get_my_port(web->conn->socket));
227                 }
228                 if (host == NULL) goto internal_error;
229                 if (url[0] != '/') {
230                         char *p = strrchr(web->input.url, '/');
231                         if (p == web->input.url) {
232                                 url = talloc_asprintf(web, "http://%s/%s", host, url);
233                         } else {
234                                 int dirlen = p - web->input.url;
235                                 url = talloc_asprintf(web, "http://%s%*.*s/%s",
236                                                       host, 
237                                                       dirlen, dirlen, web->input.url,
238                                                       url);
239                         }
240                         if (url == NULL) goto internal_error;
241                 }
242         }
243
244         http_setHeader(handle, talloc_asprintf(web, "Location: %s", url), 0);
245
246         /* make sure we give a valid redirect code */
247         if (code >= 300 && code < 400) {
248                 http_setResponseCode(handle, code);
249         } else {
250                 http_setResponseCode(handle, 302);
251         }
252         return;
253
254 internal_error:
255         http_error(web, 500, "Internal server error");
256 }
257
258
259 /* callbacks for esp processing */
260 static const struct Esp esp_control = {
261         .maxScriptSize   = 60000,
262         .writeBlock      = http_writeBlock,
263         .setHeader       = http_setHeader,
264         .redirect        = http_redirect,
265         .setResponseCode = http_setResponseCode,
266         .readFile        = http_readFile,
267         .mapToStorage    = http_mapToStorage
268 };
269
270
271 /*
272   setup for a raw http level error
273 */
274 void http_error(struct websrv_context *web, int code, const char *info)
275 {
276         char *s;
277         s = talloc_asprintf(web,"<HTML><HEAD><TITLE>Error %u</TITLE></HEAD><BODY><H1>Error %u</H1>%s<p></BODY></HTML>\r\n\r\n", 
278                             code, code, info);
279         if (s == NULL) {
280                 stream_terminate_connection(web->conn, "http_error: out of memory");
281                 return;
282         }
283         http_writeBlock(web, s, strlen(s));
284         http_setResponseCode(web, code);
285         http_output_headers(web);
286         EVENT_FD_NOT_READABLE(web->conn->event.fde);
287         EVENT_FD_WRITEABLE(web->conn->event.fde);
288 }
289
290 /*
291   map a unix error code to a http error
292 */
293 void http_error_unix(struct websrv_context *web, const char *info)
294 {
295         int code = 500;
296         switch (errno) {
297         case ENOENT:
298         case EISDIR:
299                 code = 404;
300                 break;
301         case EACCES:
302                 code = 403;
303                 break;
304         }
305         http_error(web, code, info);
306 }
307
308
309 /*
310   a simple file request
311 */
312 static void http_simple_request(struct websrv_context *web)
313 {
314         const char *url = web->input.url;
315         const char *path;
316         struct stat st;
317
318         path = http_local_path(web, url);
319         if (path == NULL) goto invalid;
320
321         /* looks ok */
322         web->output.fd = open(path, O_RDONLY);
323         if (web->output.fd == -1) {
324                 http_error_unix(web, path);
325                 return;
326         }
327
328         if (fstat(web->output.fd, &st) != 0 || !S_ISREG(st.st_mode)) {
329                 close(web->output.fd);
330                 goto invalid;
331         }
332
333         http_output_headers(web);
334         EVENT_FD_WRITEABLE(web->conn->event.fde);
335         return;
336
337 invalid:
338         http_error(web, 400, "Malformed URL");
339 }
340
341 /*
342   setup the standard ESP arrays
343 */
344 static void http_setup_arrays(struct esp_state *esp)
345 {
346         struct websrv_context *web = esp->web;
347         struct EspRequest *req = esp->req;
348         char *p;
349
350         espSetStringVar(req, ESP_REQUEST_OBJ, "CONTENT_LENGTH", 
351                         talloc_asprintf(esp, "%u", web->input.content_length));
352         if (web->input.query_string) {
353                 espSetStringVar(req, ESP_REQUEST_OBJ, "QUERY_STRING", 
354                                 web->input.query_string);
355         }
356         espSetStringVar(req, ESP_REQUEST_OBJ, "REQUEST_METHOD", 
357                         web->input.post_request?"POST":"GET");
358         espSetStringVar(req, ESP_REQUEST_OBJ, "REQUEST_URI", web->input.url);
359         p = strrchr(web->input.url, '/');
360         espSetStringVar(req, ESP_REQUEST_OBJ, "SCRIPT_NAME", p+1);
361
362         if (web->input.referer) {
363                 espSetStringVar(req, ESP_HEADERS_OBJ, "HTT_REFERER", web->input.referer);
364         }
365         if (web->input.user_agent) {
366                 espSetStringVar(req, ESP_HEADERS_OBJ, "USER_AGENT", web->input.user_agent);
367         }
368
369         espSetStringVar(req, ESP_SERVER_OBJ, "SERVER_ADDR", 
370                         socket_get_my_addr(web->conn->socket, esp));
371         espSetStringVar(req, ESP_SERVER_OBJ, "SERVER_PORT", 
372                         talloc_asprintf(esp, "%u", socket_get_my_port(web->conn->socket)));
373         espSetStringVar(req, ESP_SERVER_OBJ, "SERVER_PROTOCOL", "http");
374         espSetStringVar(esp->req, ESP_REQUEST_OBJ, "SCRIPT_FILENAME", web->input.url);
375 }
376
377
378
379
380
381 /*
382   process a esp request
383 */
384 static void esp_request(struct esp_state *esp)
385 {
386         struct websrv_context *web = esp->web;
387         const char *url = web->input.url;
388         size_t size;
389         int res;
390         char *emsg = NULL, *buf;
391
392         http_setup_arrays(esp);
393
394         if (http_readFile(web, &buf, &size, url) != 0) {
395                 http_error_unix(web, url);
396                 return;
397         }
398
399         res = espProcessRequest(esp->req, url, buf, &emsg);
400         if (res != 0 && emsg) {
401                 http_writeBlock(web, emsg, strlen(emsg));
402         }
403         talloc_free(buf);
404         http_output_headers(web);
405         EVENT_FD_WRITEABLE(web->conn->event.fde);
406 }
407
408
409 /* 
410    handling of + and % escapes in http variables 
411 */
412 static const char *http_unescape(TALLOC_CTX *mem_ctx, const char *p)
413 {
414         char *s0 = talloc_strdup(mem_ctx, p);
415         char *s = s0;
416         if (s == NULL) return NULL;
417
418         while (*s) {
419                 unsigned v;
420                 if (*s == '+') *s = ' ';
421                 if (*s == '%' && sscanf(s+1, "%02x", &v) == 1) {
422                         *s = (char)v;
423                         memmove(s+1, s+3, strlen(s+3)+1);
424                 }
425                 s++;
426         }
427
428         return s0;
429 }
430
431 /*
432   set a form or GET variable
433 */
434 static void esp_putvar(struct esp_state *esp, const char *var, const char *value)
435 {
436         espSetStringVar(esp->req, ESP_FORM_OBJ, 
437                         http_unescape(esp, var),
438                         http_unescape(esp, value));
439 }
440
441
442 /*
443   parse the variables in a POST style request
444 */
445 static NTSTATUS http_parse_post(struct esp_state *esp)
446 {
447         DATA_BLOB b = esp->web->input.partial;
448
449         while (b.length) {
450                 char *p, *line;
451                 size_t len;
452
453                 p = memchr(b.data, '&', b.length);
454                 if (p == NULL) {
455                         len = b.length;
456                 } else {
457                         len = p - (char *)b.data;
458                 }
459                 line = talloc_strndup(esp, b.data, len);
460                 NT_STATUS_HAVE_NO_MEMORY(line);
461                                      
462                 p = strchr(line,'=');
463                 if (p) {
464                         *p = 0;
465                         esp_putvar(esp, line, p+1);
466                 }
467                 talloc_free(line);
468                 b.length -= len;
469                 b.data += len;
470                 if (b.length > 0) {
471                         b.length--;
472                         b.data++;
473                 }
474         }
475
476         return NT_STATUS_OK;
477 }
478
479 /*
480   parse the variables in a GET style request
481 */
482 static NTSTATUS http_parse_get(struct esp_state *esp)
483 {
484         struct websrv_context *web = esp->web;
485         char *p, *s, *tok;
486         char *pp;
487
488         p = strchr(web->input.url, '?');
489         web->input.query_string = p+1;
490         *p = 0;
491
492         s = talloc_strdup(esp, esp->web->input.query_string);
493         NT_STATUS_HAVE_NO_MEMORY(s);
494
495         for (tok=strtok_r(s,"&;", &pp);tok;tok=strtok_r(NULL,"&;", &pp)) {
496                 p = strchr(tok,'=');
497                 if (p) {
498                         *p = 0;
499                         esp_putvar(esp, tok, p+1);
500                 }
501         }
502         return NT_STATUS_OK;
503 }
504
505 /*
506   setup some standard variables
507 */
508 static void http_setup_vars(struct esp_state *esp)
509 {
510         int i;
511
512         for (i = 0; i < ESP_OBJ_MAX; i++) {
513                 esp->variables[i] = mprCreateUndefinedVar();
514         }
515         esp->variables[ESP_HEADERS_OBJ]     = mprCreateObjVar("headers", ESP_HASH_SIZE);
516         esp->variables[ESP_FORM_OBJ]        = mprCreateObjVar("form", ESP_HASH_SIZE);
517         esp->variables[ESP_APPLICATION_OBJ] = mprCreateObjVar("application", ESP_HASH_SIZE);
518         esp->variables[ESP_COOKIES_OBJ]     = mprCreateObjVar("cookies", ESP_HASH_SIZE);
519         esp->variables[ESP_FILES_OBJ]       = mprCreateObjVar("files", ESP_HASH_SIZE);
520         esp->variables[ESP_REQUEST_OBJ]     = mprCreateObjVar("request", ESP_HASH_SIZE);
521         esp->variables[ESP_SERVER_OBJ]      = mprCreateObjVar("server", ESP_HASH_SIZE);
522         esp->variables[ESP_SESSION_OBJ]     = mprCreateObjVar("session", ESP_HASH_SIZE);
523 }
524
525 /*
526   process a complete http request
527 */
528 void http_process_input(struct websrv_context *web)
529 {
530         NTSTATUS status;
531         struct esp_state *esp;
532         char *p;
533         int i;
534         const char *file_type = NULL;
535         const struct {
536                 const char *extension;
537                 const char *mime_type;
538         } mime_types[] = {
539                 {"gif",  "image/gif"},
540                 {"png",  "image/png"},
541                 {"jpg",  "image/jpeg"},
542                 {"txt",  "text/plain"}
543         };
544
545         esp = talloc_zero(web, struct esp_state);
546         if (esp == NULL) goto internal_error;
547
548         esp->web = web;
549
550         mprSetCtx(esp);
551
552         talloc_set_destructor(esp, esp_destructor);
553
554         if (espOpen(&esp_control) != 0) goto internal_error;
555
556         http_setup_vars(esp);
557         
558         esp->req = espCreateRequest(web, web->input.url, esp->variables);
559         if (esp->req == NULL) goto internal_error;
560
561         if (web->input.url == NULL) {
562                 http_error(web, 400, "You must specify a GET or POST request");
563                 return;
564         }
565
566         if (web->input.post_request) {
567                 status = http_parse_post(esp);
568                 if (!NT_STATUS_IS_OK(status)) {
569                         http_error(web, 400, "Malformed POST data");
570                         return;
571                 }
572         } else if (strchr(web->input.url, '?')) {
573                 status = http_parse_get(esp);
574                 if (!NT_STATUS_IS_OK(status)) {
575                         http_error(web, 400, "Malformed GET data");
576                         return;
577                 }
578         }
579
580         /* process all html files as ESP */
581         p = strrchr(web->input.url, '.');
582         for (i=0;p && i<ARRAY_SIZE(mime_types);i++) {
583                 if (strcmp(mime_types[i].extension, p+1) == 0) {
584                         file_type = mime_types[i].mime_type;
585                 }
586         }
587         if (file_type == NULL) {
588                 file_type = "text/html";
589         }
590
591         /* setup basic headers */
592         http_setResponseCode(web, 200);
593         http_setHeader(web, talloc_asprintf(esp, "Date: %s", 
594                                             http_timestring(esp, time(NULL))), 0);
595         http_setHeader(web, "Server: Samba", 0);
596         http_setHeader(web, "Connection: close", 0);
597         http_setHeader(web, talloc_asprintf(esp, "Content-Type: %s", file_type), 0);
598
599         if (strcmp(file_type, "text/html") == 0) {
600                 esp_request(esp);
601         } else {
602                 http_simple_request(web);
603         }
604         talloc_free(esp);
605         return;
606         
607 internal_error:
608         talloc_free(esp);
609         http_error(web, 500, "Internal server error");
610 }
611
612
613 /*
614   parse one line of header input
615 */
616 NTSTATUS http_parse_header(struct websrv_context *web, const char *line)
617 {
618         if (line[0] == 0) {
619                 web->input.end_of_headers = True;
620         } else if (strncasecmp(line,"GET ", 4)==0) {
621                 web->input.url = talloc_strndup(web, &line[4], strcspn(&line[4], " \t"));
622         } else if (strncasecmp(line,"POST ", 5)==0) {
623                 web->input.post_request = True;
624                 web->input.url = talloc_strndup(web, &line[5], strcspn(&line[5], " \t"));
625         } else if (strchr(line, ':') == NULL) {
626                 http_error(web, 400, "This server only accepts GET and POST requests");
627                 return NT_STATUS_INVALID_PARAMETER;
628         } else if (strncasecmp(line,"Content-Length: ", 16)==0) {
629                 web->input.content_length = strtoul(&line[16], NULL, 10);
630         } else {
631 #define PULL_HEADER(v, s) do { \
632         if (strncmp(line, s, strlen(s)) == 0) { \
633                 web->input.v = talloc_strdup(web, &line[strlen(s)]); \
634                 return NT_STATUS_OK; \
635         } \
636 } while (0)
637                 PULL_HEADER(content_type, "Content-Type: ");
638                 PULL_HEADER(user_agent, "User-Agent: ");
639                 PULL_HEADER(referer, "Referer: ");
640                 PULL_HEADER(host, "Host: ");
641                 PULL_HEADER(accept_encoding, "Accept-Encoding: ");
642         }
643
644         /* ignore all other headers for now */
645         return NT_STATUS_OK;
646 }
647
648