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