r19139: typos; consitent ordering of operations
[jelmer/samba4-debian.git] / source / 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 "smbd/service_task.h"
25 #include "web_server/web_server.h"
26 #include "smbd/service_stream.h"
27 #include "smbd/service.h"
28 #include "lib/events/events.h"
29 #include "system/time.h"
30 #include "lib/appweb/esp/esp.h"
31 #include "lib/appweb/ejs/ejsInternal.h"
32 #include "lib/util/dlinklist.h"
33 #include "lib/tls/tls.h"
34 #include "scripting/ejs/smbcalls.h"
35
36 #define SWAT_SESSION_KEY "SwatSessionId"
37 #define HTTP_PREAUTH_URI "/scripting/preauth.esp"
38 #define JSONRPC_REQUEST "/services"
39 #define JSONRPC_SERVER "/services/request.esp"
40
41 /* state of the esp subsystem for a specific request */
42 struct esp_state {
43         struct websrv_context *web;
44         struct EspRequest *req;
45         struct MprVar variables[ESP_OBJ_MAX];
46         struct session_data *session;
47 };
48
49 /*
50   output the http headers
51 */
52 static void http_output_headers(struct websrv_context *web)
53 {
54         int i;
55         char *s;
56         DATA_BLOB b;
57         uint32_t content_length = 0;
58         const char *response_string = "Unknown Code";
59         const struct {
60                 unsigned code;
61                 const char *response_string;
62         } codes[] = {
63                 { 200, "OK" },
64                 { 301, "Moved" },
65                 { 302, "Found" },
66                 { 303, "Method" },
67                 { 304, "Not Modified" },
68                 { 400, "Bad request" },
69                 { 401, "Unauthorized" },
70                 { 403, "Forbidden" },
71                 { 404, "Not Found" },
72                 { 500, "Internal Server Error" },
73                 { 501, "Not implemented" }
74         };
75         for (i=0;i<ARRAY_SIZE(codes);i++) {
76                 if (codes[i].code == web->output.response_code) {
77                         response_string = codes[i].response_string;
78                 }
79         }
80
81         if (web->output.headers == NULL) return;
82         s = talloc_asprintf(web, "HTTP/1.0 %u %s\r\n", 
83                             web->output.response_code, response_string);
84         if (s == NULL) return;
85         for (i=0;web->output.headers[i];i++) {
86                 s = talloc_asprintf_append(s, "%s\r\n", web->output.headers[i]);
87         }
88
89         /* work out the content length */
90         content_length = web->output.content.length;
91         if (web->output.fd != -1) {
92                 struct stat st;
93                 fstat(web->output.fd, &st);
94                 content_length += st.st_size;
95         }
96         s = talloc_asprintf_append(s, "Content-Length: %u\r\n\r\n", content_length);
97         if (s == NULL) return;
98
99         b = web->output.content;
100         web->output.content = data_blob_string_const(s);
101         data_blob_append(web, &web->output.content, b.data, b.length);
102         data_blob_free(&b);
103 }
104
105 /*
106   return the local path for a URL
107 */
108 static const char *http_local_path(struct websrv_context *web, const char *url)
109 {
110         int i;
111         char *path;
112
113         /* check that the url is OK */
114         if (url[0] != '/') return NULL;
115
116         for (i=0;url[i];i++) {
117                 if ((!isalnum((unsigned char)url[i]) && !strchr("./_-", url[i])) ||
118                     (url[i] == '.' && strchr("/.", url[i+1]))) {
119                         return NULL;
120                 }
121         }
122
123         path = talloc_asprintf(web, "%s/%s", lp_swat_directory(), url+1);
124         if (path == NULL) return NULL;
125
126         if (directory_exist(path)) {
127                 path = talloc_asprintf_append(path, "/index.esp");
128         }
129         return path;
130 }
131
132 /*
133   called when esp wants to read a file to support include() calls
134 */
135 static int http_readFile(EspHandle handle, char **buf, int *len, const char *path)
136 {
137         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
138         int fd = -1;
139         struct stat st;
140         *buf = NULL;
141
142         path = http_local_path(web, path);
143         if (path == NULL) goto failed;
144
145         fd = open(path, O_RDONLY);
146         if (fd == -1 || fstat(fd, &st) != 0 || !S_ISREG(st.st_mode)) goto failed;
147
148         *buf = talloc_size(handle, st.st_size+1);
149         if (*buf == NULL) goto failed;
150
151         if (read(fd, *buf, st.st_size) != st.st_size) goto failed;
152
153         (*buf)[st.st_size] = 0;
154
155         close(fd);
156         *len = st.st_size;
157         return 0;
158
159 failed:
160         DEBUG(0,("Failed to read file %s - %s\n", path, strerror(errno)));
161         if (fd != -1) close(fd);
162         talloc_free(*buf);
163         *buf = NULL;
164         return -1;
165 }
166
167 /*
168   called when esp wants to find the real path of a file
169 */
170 static int http_mapToStorage(EspHandle handle, char *path, int len, const char *uri, int flags)
171 {
172         if (uri == NULL || strlen(uri) >= len) return -1;
173         strncpy(path, uri, len);
174         return 0;
175 }
176
177 /*
178   called when esp wants to output something
179 */
180 static int http_writeBlock(EspHandle handle, const char *buf, int size)
181 {
182         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
183         NTSTATUS status;
184         status = data_blob_append(web, &web->output.content, buf, size);
185         if (!NT_STATUS_IS_OK(status)) return -1;
186         return size;
187 }
188
189
190 /*
191   set a http header
192 */
193 static void http_setHeader(EspHandle handle, const char *value, bool allowMultiple)
194 {
195         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
196         char *p = strchr(value, ':');
197
198         if (p && !allowMultiple && web->output.headers) {
199                 int i;
200                 for (i=0;web->output.headers[i];i++) {
201                         if (strncmp(web->output.headers[i], value, (p+1)-value) == 0) {
202                                 web->output.headers[i] = talloc_strdup(web, value);
203                                 return;
204                         }
205                 }
206         }
207
208         web->output.headers = str_list_add(web->output.headers, value);
209         talloc_steal(web, web->output.headers);
210 }
211
212 /*
213   set a http response code
214 */
215 static void http_setResponseCode(EspHandle handle, int code)
216 {
217         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
218         web->output.response_code = code;
219 }
220
221 /*
222   redirect to another web page
223  */
224 static void http_redirect(EspHandle handle, int code, char *url)
225 {
226         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
227         const char *host = web->input.host;
228         
229         /* form the full url, unless it already looks like a url */
230         if (strchr(url, ':') == NULL) {
231                 if (host == NULL) {
232                         struct socket_address *socket_address = socket_get_my_addr(web->conn->socket, web);
233                         if (socket_address == NULL) goto internal_error;
234                         host = talloc_asprintf(web, "%s:%u",
235                                                socket_address->addr, socket_address->port);
236                 }
237                 if (host == NULL) goto internal_error;
238                 if (url[0] != '/') {
239                         char *p = strrchr(web->input.url, '/');
240                         if (p == web->input.url) {
241                                 url = talloc_asprintf(web, "http%s://%s/%s", 
242                                                       tls_enabled(web->conn->socket)?"s":"",
243                                                       host, url);
244                         } else {
245                                 int dirlen = p - web->input.url;
246                                 url = talloc_asprintf(web, "http%s://%s%*.*s/%s",
247                                                       tls_enabled(web->conn->socket)?"s":"",
248                                                       host, 
249                                                       dirlen, dirlen, web->input.url,
250                                                       url);
251                         }
252                         if (url == NULL) goto internal_error;
253                 }
254         }
255
256         http_setHeader(handle, talloc_asprintf(web, "Location: %s", url), 0);
257
258         /* make sure we give a valid redirect code */
259         if (code >= 300 && code < 400) {
260                 http_setResponseCode(handle, code);
261         } else {
262                 http_setResponseCode(handle, 302);
263         }
264         return;
265
266 internal_error:
267         http_error(web, 500, "Internal server error");
268 }
269
270
271 /*
272   setup a cookie
273 */
274 static void http_setCookie(EspHandle handle, const char *name, const char *value, 
275                            int lifetime, const char *path, bool secure)
276 {
277         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
278         char *buf;
279         
280         if (lifetime > 0) {
281                 buf = talloc_asprintf(web, "Set-Cookie: %s=%s; path=%s; Expires=%s; %s",
282                                       name, value, path?path:"/", 
283                                       http_timestring(web, time(NULL)+lifetime),
284                                       secure?"secure":"");
285         } else {
286                 buf = talloc_asprintf(web, "Set-Cookie: %s=%s; path=%s; %s",
287                                       name, value, path?path:"/", 
288                                       secure?"secure":"");
289         }
290         http_setHeader(handle, "Cache-control: no-cache=\"set-cookie\"", 0);
291         http_setHeader(handle, buf, 0);
292         talloc_free(buf);
293 }
294
295 /*
296   return the session id
297 */
298 static const char *http_getSessionId(EspHandle handle)
299 {
300         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
301         return web->session->id;
302 }
303
304 /*
305   setup a session
306 */
307 static void http_createSession(EspHandle handle, int timeout)
308 {
309         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
310         if (web->session) {
311                 web->session->lifetime = timeout;
312                 http_setCookie(web, SWAT_SESSION_KEY, web->session->id, 
313                                web->session->lifetime, "/", 0);
314         }
315 }
316
317 /*
318   destroy a session
319 */
320 static void http_destroySession(EspHandle handle)
321 {
322         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
323         talloc_free(web->session);
324         web->session = NULL;
325 }
326
327
328 /*
329   setup for a raw http level error
330 */
331 void http_error(struct websrv_context *web, int code, const char *info)
332 {
333         char *s;
334         s = talloc_asprintf(web,"<HTML><HEAD><TITLE>Error %u</TITLE></HEAD><BODY><H1>Error %u</H1><pre>%s</pre><p></BODY></HTML>\r\n\r\n", 
335                             code, code, info);
336         if (s == NULL) {
337                 stream_terminate_connection(web->conn, "http_error: out of memory");
338                 return;
339         }
340         http_writeBlock(web, s, strlen(s));
341         http_setResponseCode(web, code);
342         http_output_headers(web);
343         EVENT_FD_NOT_READABLE(web->conn->event.fde);
344         EVENT_FD_WRITEABLE(web->conn->event.fde);
345         web->output.output_pending = True;
346 }
347
348 /*
349   map a unix error code to a http error
350 */
351 void http_error_unix(struct websrv_context *web, const char *info)
352 {
353         int code = 500;
354         switch (errno) {
355         case ENOENT:
356         case EISDIR:
357                 code = 404;
358                 break;
359         case EACCES:
360                 code = 403;
361                 break;
362         }
363         info = talloc_asprintf(web, "%s<p>%s<p>\n", info, strerror(errno));
364         http_error(web, code, info);
365 }
366
367
368 /*
369   a simple file request
370 */
371 static void http_simple_request(struct websrv_context *web)
372 {
373         const char *url = web->input.url;
374         const char *path;
375         struct stat st;
376
377         path = http_local_path(web, url);
378         if (path == NULL) goto invalid;
379
380         /* looks ok */
381         web->output.fd = open(path, O_RDONLY);
382         if (web->output.fd == -1) {
383                 DEBUG(0,("Failed to read file %s - %s\n", path, strerror(errno)));
384                 http_error_unix(web, path);
385                 return;
386         }
387
388         if (fstat(web->output.fd, &st) != 0 || !S_ISREG(st.st_mode)) {
389                 close(web->output.fd);
390                 goto invalid;
391         }
392
393         return;
394
395 invalid:
396         http_error(web, 400, "Malformed URL");
397 }
398
399 /*
400   setup the standard ESP arrays
401 */
402 static void http_setup_arrays(struct esp_state *esp)
403 {
404         struct websrv_context *web = esp->web;
405         struct esp_data *edata = talloc_get_type(web->task->private, struct esp_data);
406         struct EspRequest *req = esp->req;
407         struct socket_address *socket_address = socket_get_my_addr(web->conn->socket, esp);
408         struct socket_address *peer_address = socket_get_peer_addr(web->conn->socket, esp);
409         char *p;
410
411 #define SETVAR(type, name, value) do { \
412                 const char *v = value; \
413                 if (v) espSetStringVar(req, type, name, v); \
414 } while (0)
415
416         SETVAR(ESP_REQUEST_OBJ, "CONTENT_LENGTH", 
417                talloc_asprintf(esp, "%u", web->input.content_length));
418         SETVAR(ESP_REQUEST_OBJ, "QUERY_STRING", web->input.query_string);
419         SETVAR(ESP_REQUEST_OBJ, "POST_DATA",
420                talloc_strndup(esp,
421                               web->input.partial.data,
422                               web->input.partial.length));
423         SETVAR(ESP_REQUEST_OBJ, "REQUEST_METHOD", web->input.post_request?"POST":"GET");
424         SETVAR(ESP_REQUEST_OBJ, "REQUEST_URI", web->input.url);
425         p = strrchr(web->input.url, '/');
426         SETVAR(ESP_REQUEST_OBJ, "SCRIPT_NAME", p+1);
427         SETVAR(ESP_REQUEST_OBJ, "SCRIPT_FILENAME", web->input.url);
428         if (peer_address) {
429                 struct MprVar mpv = mprObject("socket_address");
430                 mprSetPtrChild(&mpv, "socket_address", peer_address);
431                 espSetVar(req, ESP_REQUEST_OBJ, "REMOTE_SOCKET_ADDRESS", mpv);
432                 SETVAR(ESP_REQUEST_OBJ, "REMOTE_ADDR", peer_address->addr);
433         }
434         p = socket_get_peer_name(web->conn->socket, esp);
435         SETVAR(ESP_REQUEST_OBJ, "REMOTE_HOST", p);
436         SETVAR(ESP_REQUEST_OBJ, "REMOTE_USER", "");
437         SETVAR(ESP_REQUEST_OBJ, "CONTENT_TYPE", web->input.content_type);
438         if (web->session) {
439                 SETVAR(ESP_REQUEST_OBJ, "SESSION_ID", web->session->id);
440         }
441         SETVAR(ESP_REQUEST_OBJ, "COOKIE_SUPPORT", web->input.cookie?"True":"False");
442
443         SETVAR(ESP_HEADERS_OBJ, "HTT_REFERER", web->input.referer);
444         SETVAR(ESP_HEADERS_OBJ, "HOST", web->input.host);
445         SETVAR(ESP_HEADERS_OBJ, "ACCEPT_ENCODING", web->input.accept_encoding);
446         SETVAR(ESP_HEADERS_OBJ, "ACCEPT_LANGUAGE", web->input.accept_language);
447         SETVAR(ESP_HEADERS_OBJ, "ACCEPT_CHARSET", web->input.accept_charset);
448         SETVAR(ESP_HEADERS_OBJ, "COOKIE", web->input.cookie);
449         SETVAR(ESP_HEADERS_OBJ, "USER_AGENT", web->input.user_agent);
450
451         if (socket_address) {
452                 SETVAR(ESP_SERVER_OBJ, "SERVER_ADDR", socket_address->addr);
453                 SETVAR(ESP_SERVER_OBJ, "SERVER_NAME", socket_address->addr);
454                 SETVAR(ESP_SERVER_OBJ, "SERVER_HOST", socket_address->addr);
455                 SETVAR(ESP_SERVER_OBJ, "SERVER_PORT", 
456                        talloc_asprintf(esp, "%u", socket_address->port));
457         }
458
459         SETVAR(ESP_SERVER_OBJ, "DOCUMENT_ROOT", lp_swat_directory());
460         SETVAR(ESP_SERVER_OBJ, "SERVER_PROTOCOL", tls_enabled(web->conn->socket)?"https":"http");
461         SETVAR(ESP_SERVER_OBJ, "SERVER_SOFTWARE", "SWAT");
462         SETVAR(ESP_SERVER_OBJ, "GATEWAY_INTERFACE", "CGI/1.1");
463         SETVAR(ESP_SERVER_OBJ, "TLS_SUPPORT", tls_support(edata->tls_params)?"True":"False");
464 }
465
466 #if HAVE_SETJMP_H
467 /* the esp scripting lirary generates exceptions when
468    it hits a major error. We need to catch these and
469    report a internal server error via http
470 */
471 #include <setjmp.h>
472 static jmp_buf ejs_exception_buf;
473 static const char *exception_reason;
474
475 static void web_server_ejs_exception(const char *reason)
476 {
477         Ejs *ep = ejsPtr(0);
478         if (ep) {
479                 ejsSetErrorMsg(0, "%s", reason);
480                 exception_reason = ep->error;
481         } else {
482                 exception_reason = reason;
483         }
484         DEBUG(0,("%s", exception_reason));
485         longjmp(ejs_exception_buf, -1);
486 }
487 #else
488 static void web_server_ejs_exception(const char *reason)
489 {
490         DEBUG(0,("%s", reason));
491         smb_panic(reason);
492 }
493 #endif
494
495 /*
496   process a esp request
497 */
498 static void esp_request(struct esp_state *esp, const char *url)
499 {
500         struct websrv_context *web = esp->web;
501         int size;
502         int res;
503         char *emsg = NULL, *buf;
504
505         if (http_readFile(web, &buf, &size, url) != 0) {
506                 http_error_unix(web, url);
507                 return;
508         }
509
510 #if HAVE_SETJMP_H
511         if (setjmp(ejs_exception_buf) != 0) {
512                 http_error(web, 500, exception_reason);
513                 return;
514         }
515 #endif
516
517         res = espProcessRequest(esp->req, url, buf, &emsg);
518         if (res != 0 && emsg) {
519                 http_writeBlock(web, "<pre>", 5);
520                 http_writeBlock(web, emsg, strlen(emsg));
521                 http_writeBlock(web, "</pre>", 6);
522         }
523         talloc_free(buf);
524 }
525
526 /*
527   process a JSON RPC request
528 */
529 static void jsonrpc_request(struct esp_state *esp)
530 {
531         const char *path = http_local_path(esp->web, JSONRPC_SERVER);
532
533         /* Ensure we got a valid path. */
534         if (path == NULL) {
535                 /* should never occur */
536                 http_error(esp->web, 500, "Internal server error");
537                 return;
538         }
539
540         /* Ensure that the JSON-RPC server request script exists */
541         if (!file_exist(path)) {
542                 http_error_unix(esp->web, path);
543                 return;
544         }
545
546         /* Call the server request script */
547         esp_request(esp, JSONRPC_SERVER);
548 }
549
550 /*
551   perform pre-authentication on every page if /scripting/preauth.esp
552   exists.  If this script generates any non-whitepace output at all,
553   then we don't run the requested URL.
554
555   note that the preauth is run even for static pages such as images, but not
556   for JSON-RPC service requests which do their own authentication via the
557   JSON-RPC server.
558 */
559 static BOOL http_preauth(struct esp_state *esp)
560 {
561         const char *path = http_local_path(esp->web, HTTP_PREAUTH_URI);
562         int i;
563         if (path == NULL) {
564                 http_error(esp->web, 500, "Internal server error");
565                 return False;
566         }
567         if (!file_exist(path)) {
568                 /* if the preath script is not installed then allow access */
569                 return True;
570         }
571         esp_request(esp, HTTP_PREAUTH_URI);
572         for (i=0;i<esp->web->output.content.length;i++) {
573                 if (!isspace(esp->web->output.content.data[i])) {
574                         /* if the preauth has generated content, then force it
575                            to be html, so that we can show the login page for
576                            failed access to images */
577                         http_setHeader(esp->web, "Content-Type: text/html", 0);
578                         return False;
579                 }
580         }
581         data_blob_free(&esp->web->output.content);
582         return True;
583 }
584
585
586 /* 
587    handling of + and % escapes in http variables 
588 */
589 static const char *http_unescape(TALLOC_CTX *mem_ctx, const char *p)
590 {
591         char *s0 = talloc_strdup(mem_ctx, p);
592         char *s = s0;
593         if (s == NULL) return NULL;
594
595         while (*s) {
596                 unsigned v;
597                 if (*s == '+') *s = ' ';
598                 if (*s == '%' && sscanf(s+1, "%02x", &v) == 1) {
599                         *s = (char)v;
600                         memmove(s+1, s+3, strlen(s+3)+1);
601                 }
602                 s++;
603         }
604
605         return s0;
606 }
607
608 /*
609   set a form or GET variable
610 */
611 static void esp_putvar(struct esp_state *esp, const char *var, const char *value)
612 {
613         if (strcasecmp(var, SWAT_SESSION_KEY) == 0) {
614                 /* special case support for browsers without cookie
615                  support */
616                 esp->web->input.session_key = talloc_strdup(esp, value);
617         } else {
618                 mprSetPropertyValue(&esp->variables[ESP_FORM_OBJ], 
619                                     http_unescape(esp, var),
620                                     mprCreateStringVar(http_unescape(esp, value), 0));
621         }
622 }
623
624
625 /*
626   parse the variables in a POST style request
627 */
628 static NTSTATUS http_parse_post(struct esp_state *esp)
629 {
630         DATA_BLOB b = esp->web->input.partial;
631
632         while (b.length) {
633                 char *p, *line;
634                 size_t len;
635
636                 p = memchr(b.data, '&', b.length);
637                 if (p == NULL) {
638                         len = b.length;
639                 } else {
640                         len = p - (char *)b.data;
641                 }
642                 line = talloc_strndup(esp, (char *)b.data, len);
643                 NT_STATUS_HAVE_NO_MEMORY(line);
644                                      
645                 p = strchr(line,'=');
646                 if (p) {
647                         *p = 0;
648                         esp_putvar(esp, line, p+1);
649                 }
650                 talloc_free(line);
651                 b.length -= len;
652                 b.data += len;
653                 if (b.length > 0) {
654                         b.length--;
655                         b.data++;
656                 }
657         }
658
659         return NT_STATUS_OK;
660 }
661
662 /*
663   parse the variables in a GET style request
664 */
665 static NTSTATUS http_parse_get(struct esp_state *esp)
666 {
667         struct websrv_context *web = esp->web;
668         char *p, *s, *tok;
669         char *pp;
670
671         p = strchr(web->input.url, '?');
672         web->input.query_string = p+1;
673         *p = 0;
674
675         s = talloc_strdup(esp, esp->web->input.query_string);
676         NT_STATUS_HAVE_NO_MEMORY(s);
677
678         for (tok=strtok_r(s,"&;", &pp);tok;tok=strtok_r(NULL,"&;", &pp)) {
679                 p = strchr(tok,'=');
680                 if (p) {
681                         *p = 0;
682                         esp_putvar(esp, tok, p+1);
683                 }
684         }
685         return NT_STATUS_OK;
686 }
687
688 /*
689   called when a session times out
690 */
691 static void session_timeout(struct event_context *ev, struct timed_event *te, 
692                             struct timeval t, void *private)
693 {
694         struct session_data *s = talloc_get_type(private, struct session_data);
695         talloc_free(s);
696 }
697
698 /*
699   destroy a session
700  */
701 static int session_destructor(struct session_data *s)
702 {
703         DLIST_REMOVE(s->edata->sessions, s);
704         return 0;
705 }
706
707 /*
708   setup the session for this request
709 */
710 static void http_setup_session(struct esp_state *esp)
711 {
712         const char *session_key = SWAT_SESSION_KEY;
713         char *p;
714         const char *cookie = esp->web->input.cookie;
715         const char *key = NULL;
716         struct esp_data *edata = talloc_get_type(esp->web->task->private, struct esp_data);
717         struct session_data *s;
718         BOOL generated_key = False;
719
720         /* look for our session key */
721         if (cookie && (p = strstr(cookie, session_key)) && 
722             p[strlen(session_key)] == '=') {
723                 p += strlen(session_key)+1;
724                 key = talloc_strndup(esp, p, strcspn(p, ";"));
725         }
726
727         if (key == NULL && esp->web->input.session_key) {
728                 key = esp->web->input.session_key;
729         } else if (key == NULL) {
730                 key = generate_random_str_list(esp, 16, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
731                 generated_key = True;
732         }
733
734         /* try to find this session in the existing session list */
735         for (s=edata->sessions;s;s=s->next) {
736                 if (strcmp(key, s->id) == 0) {
737                         break;
738                 }
739         }
740
741         if (s == NULL) {
742                 /* create a new session */
743                 s = talloc_zero(edata, struct session_data);
744                 s->id = talloc_steal(s, key);
745                 s->data = NULL;
746                 s->te = NULL;
747                 s->edata = edata;
748                 s->lifetime = lp_parm_int(-1, "web", "sessiontimeout", 900);
749                 DLIST_ADD(edata->sessions, s);
750                 talloc_set_destructor(s, session_destructor);
751                 if (!generated_key) {
752                         mprSetPropertyValue(&esp->variables[ESP_REQUEST_OBJ], 
753                                             "SESSION_EXPIRED", mprCreateStringVar("True", 0));
754                 }
755         }
756
757         http_setCookie(esp->web, session_key, key, s->lifetime, "/", 0);
758
759         if (s->data) {
760                 mprCopyVar(&esp->variables[ESP_SESSION_OBJ], s->data, MPR_DEEP_COPY);
761         }
762
763         esp->web->session = s;
764 }
765
766
767 /* callbacks for esp processing */
768 static const struct Esp esp_control = {
769         .maxScriptSize   = 60000,
770         .writeBlock      = http_writeBlock,
771         .setHeader       = http_setHeader,
772         .redirect        = http_redirect,
773         .setResponseCode = http_setResponseCode,
774         .readFile        = http_readFile,
775         .mapToStorage    = http_mapToStorage,
776         .setCookie       = http_setCookie,
777         .createSession   = http_createSession,
778         .destroySession  = http_destroySession,
779         .getSessionId    = http_getSessionId
780 };
781
782 /*
783   process a complete http request
784 */
785 void http_process_input(struct websrv_context *web)
786 {
787         NTSTATUS status;
788         struct esp_state *esp = NULL;
789         struct esp_data *edata = talloc_get_type(web->task->private, struct esp_data);
790         struct smbcalls_context *smbcalls_ctx;
791         char *p;
792         void *save_mpr_ctx = mprMemCtx();
793         void *ejs_save = ejs_save_state();
794         int i;
795         const char *file_type = NULL;
796         enum page_type {
797                 page_type_simple,
798                 page_type_esp,
799                 page_type_jsonrpc
800         };
801         enum page_type page_type;
802         const struct {
803                 const char *extension;
804                 const char *mime_type;
805                 enum page_type page_type;
806         } mime_types[] = {
807                 {"gif",  "image/gif"},
808                 {"png",  "image/png"},
809                 {"jpg",  "image/jpeg"},
810                 {"txt",  "text/plain"},
811                 {"ico",  "image/x-icon"},
812                 {"css",  "text/css"},
813                 {"esp",  "text/html", True}
814         };
815
816         /*
817          * give the smbcalls a chance to find the event context
818          * and messaging context 
819          */
820         smbcalls_ctx = talloc(web, struct smbcalls_context);
821         if (smbcalls_ctx == NULL) goto internal_error;
822         smbcalls_ctx->event_ctx = web->conn->event.ctx;
823         smbcalls_ctx->msg_ctx = web->conn->msg_ctx;
824
825         esp = talloc_zero(smbcalls_ctx, struct esp_state);
826         if (esp == NULL) goto internal_error;
827
828         esp->web = web;
829
830         mprSetCtx(esp);
831
832         if (espOpen(&esp_control) != 0) goto internal_error;
833
834         for (i=0;i<ARRAY_SIZE(esp->variables);i++) {
835                 esp->variables[i] = mprCreateUndefinedVar();
836         }
837         esp->variables[ESP_HEADERS_OBJ]     = mprCreateObjVar("headers", ESP_HASH_SIZE);
838         esp->variables[ESP_FORM_OBJ]        = mprCreateObjVar("form", ESP_HASH_SIZE);
839         esp->variables[ESP_APPLICATION_OBJ] = mprCreateObjVar("application", ESP_HASH_SIZE);
840         esp->variables[ESP_COOKIES_OBJ]     = mprCreateObjVar("cookies", ESP_HASH_SIZE);
841         esp->variables[ESP_FILES_OBJ]       = mprCreateObjVar("files", ESP_HASH_SIZE);
842         esp->variables[ESP_REQUEST_OBJ]     = mprCreateObjVar("request", ESP_HASH_SIZE);
843         esp->variables[ESP_SERVER_OBJ]      = mprCreateObjVar("server", ESP_HASH_SIZE);
844         esp->variables[ESP_SESSION_OBJ]     = mprCreateObjVar("session", ESP_HASH_SIZE);
845
846         if (edata->application_data) {
847                 mprCopyVar(&esp->variables[ESP_APPLICATION_OBJ], 
848                            edata->application_data, MPR_DEEP_COPY);
849         }
850
851         smb_setup_ejs_functions(web_server_ejs_exception);
852
853         if (web->input.url == NULL) {
854                 http_error(web, 400, "You must specify a GET or POST request");
855                 mprSetCtx(save_mpr_ctx);
856                 ejs_restore_state(ejs_save);
857                 return;
858         }
859         
860         /* parse any form or get variables */
861         if (web->input.post_request) {
862                 status = http_parse_post(esp);
863                 if (!NT_STATUS_IS_OK(status)) {
864                         http_error(web, 400, "Malformed POST data");
865                         mprSetCtx(save_mpr_ctx);
866                         ejs_restore_state(ejs_save);
867                         return;
868                 }
869         } 
870         if (strchr(web->input.url, '?')) {
871                 status = http_parse_get(esp);
872                 if (!NT_STATUS_IS_OK(status)) {
873                         http_error(web, 400, "Malformed GET data");
874                         mprSetCtx(save_mpr_ctx);
875                         ejs_restore_state(ejs_save);
876                         return;
877                 }
878         }
879
880         http_setup_session(esp);
881
882         esp->req = espCreateRequest(web, web->input.url, esp->variables);
883         if (esp->req == NULL) goto internal_error;
884
885         /*
886          * Work out the mime type.  First, we see if the request is a JSON-RPC
887          * service request.  If not, we look at the extension.
888          */
889         if (strncmp(web->input.url,
890                     JSONRPC_REQUEST,
891                     sizeof(JSONRPC_REQUEST) - 1) == 0 &&
892             (web->input.url[sizeof(JSONRPC_REQUEST) - 1] == '\0' ||
893              web->input.url[sizeof(JSONRPC_REQUEST) - 1] == '/')) {
894             page_type = page_type_jsonrpc;
895             file_type = "text/json";
896             
897         } else {
898             p = strrchr(web->input.url, '.');
899             if (p == NULL) {
900                     page_type = page_type_esp;
901                     file_type = "text/html";
902             }
903             for (i=0;p && i<ARRAY_SIZE(mime_types);i++) {
904                 if (strcmp(mime_types[i].extension, p+1) == 0) {
905                     page_type = mime_types[i].page_type;
906                     file_type = mime_types[i].mime_type;
907                 }
908             }
909             if (file_type == NULL) {
910                 page_type = page_type_simple;
911                 file_type = "text/html";
912             }
913         }
914
915         /* setup basic headers */
916         http_setResponseCode(web, 200);
917         http_setHeader(web, talloc_asprintf(esp, "Date: %s", 
918                                             http_timestring(esp, time(NULL))), 0);
919         http_setHeader(web, "Server: Samba", 0);
920         http_setHeader(web, "Connection: close", 0);
921         http_setHeader(web, talloc_asprintf(esp, "Content-Type: %s", file_type), 0);
922
923         http_setup_arrays(esp);
924
925         /*
926          * Do pre-authentication.  If pre-authentication succeeds, do
927          * page-type-specific processing.
928          */
929         switch(page_type)
930         {
931         case page_type_simple:
932                 if (http_preauth(esp)) {
933                         http_simple_request(web);
934                 }
935                 break;
936
937         case page_type_esp:
938                 if (http_preauth(esp)) {
939                         esp_request(esp, web->input.url);
940                 }
941                 break;
942
943         case page_type_jsonrpc:
944                 jsonrpc_request(esp);
945                 break;
946         }
947
948         if (web->conn == NULL) {
949                 /* the connection has been terminated above us, probably
950                    via a timeout */
951                 goto internal_error;
952         }
953
954         if (!web->output.output_pending) {
955                 http_output_headers(web);
956                 EVENT_FD_WRITEABLE(web->conn->event.fde);
957                 web->output.output_pending = True;
958         }
959
960         /* copy any application data to long term storage in edata */
961         talloc_free(edata->application_data);
962         edata->application_data = talloc_zero(edata, struct MprVar);
963         mprSetCtx(edata->application_data);
964         mprCopyVar(edata->application_data, &esp->variables[ESP_APPLICATION_OBJ], 
965                    MPR_DEEP_COPY);
966         mprSetCtx(esp);
967
968         /* copy any session data */
969         if (web->session) {
970                 talloc_free(web->session->data);
971                 web->session->data = talloc_zero(web->session, struct MprVar);
972                 if (esp->variables[ESP_SESSION_OBJ].properties == NULL ||
973                     esp->variables[ESP_SESSION_OBJ].properties[0].numItems == 0) {
974                         talloc_free(web->session);
975                         web->session = NULL;
976                 } else {
977                         mprSetCtx(web->session->data);
978                         mprCopyVar(web->session->data, &esp->variables[ESP_SESSION_OBJ], 
979                                    MPR_DEEP_COPY);
980                         /* setup the timeout for the session data */
981                         mprSetCtx(esp);
982                         talloc_free(web->session->te);
983                         web->session->te = event_add_timed(web->conn->event.ctx, web->session, 
984                                                            timeval_current_ofs(web->session->lifetime, 0), 
985                                                            session_timeout, web->session);
986                 }
987         }
988
989         talloc_free(esp);
990         mprSetCtx(save_mpr_ctx);
991         ejs_restore_state(ejs_save);
992         return;
993         
994 internal_error:
995         mprSetCtx(esp);
996         talloc_free(esp);
997         if (web->conn != NULL) {
998                 http_error(web, 500, "Internal server error");
999         }
1000         mprSetCtx(save_mpr_ctx);
1001         ejs_restore_state(ejs_save);
1002 }
1003
1004
1005 /*
1006   parse one line of header input
1007 */
1008 NTSTATUS http_parse_header(struct websrv_context *web, const char *line)
1009 {
1010         if (line[0] == 0) {
1011                 web->input.end_of_headers = True;
1012         } else if (strncasecmp(line,"GET ", 4)==0) {
1013                 web->input.url = talloc_strndup(web, &line[4], strcspn(&line[4], " \t"));
1014         } else if (strncasecmp(line,"POST ", 5)==0) {
1015                 web->input.post_request = True;
1016                 web->input.url = talloc_strndup(web, &line[5], strcspn(&line[5], " \t"));
1017         } else if (strchr(line, ':') == NULL) {
1018                 http_error(web, 400, "This server only accepts GET and POST requests");
1019                 return NT_STATUS_INVALID_PARAMETER;
1020         } else if (strncasecmp(line,"Content-Length: ", 16)==0) {
1021                 web->input.content_length = strtoul(&line[16], NULL, 10);
1022         } else {
1023 #define PULL_HEADER(v, s) do { \
1024         if (strncmp(line, s, strlen(s)) == 0) { \
1025                 web->input.v = talloc_strdup(web, &line[strlen(s)]); \
1026                 return NT_STATUS_OK; \
1027         } \
1028 } while (0)
1029                 PULL_HEADER(content_type, "Content-Type: ");
1030                 PULL_HEADER(user_agent, "User-Agent: ");
1031                 PULL_HEADER(referer, "Referer: ");
1032                 PULL_HEADER(host, "Host: ");
1033                 PULL_HEADER(accept_encoding, "Accept-Encoding: ");
1034                 PULL_HEADER(accept_language, "Accept-Language: ");
1035                 PULL_HEADER(accept_charset, "Accept-Charset: ");
1036                 PULL_HEADER(cookie, "Cookie: ");
1037         }
1038
1039         /* ignore all other headers for now */
1040         return NT_STATUS_OK;
1041 }
1042
1043
1044 /*
1045   setup the esp processor - called at task initialisation
1046 */
1047 NTSTATUS http_setup_esp(struct task_server *task)
1048 {
1049         struct esp_data *edata;
1050
1051         edata = talloc_zero(task, struct esp_data);
1052         NT_STATUS_HAVE_NO_MEMORY(edata);
1053
1054         task->private = edata;
1055
1056         edata->tls_params = tls_initialise(edata);
1057         NT_STATUS_HAVE_NO_MEMORY(edata->tls_params);
1058
1059         return NT_STATUS_OK;
1060 }