985f1c040866b9fb7d9add1f095219fb4a194934
[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 3 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, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "includes.h"
23 #include "smbd/service_task.h"
24 #include "web_server/web_server.h"
25 #include "smbd/service_stream.h"
26 #include "smbd/service.h"
27 #include "lib/events/events.h"
28 #include "system/time.h"
29 #include "system/wait.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 #include "param/param.h"
36
37 #define SAMBA_SESSION_KEY "SambaSessionId"
38 #define HTTP_PREAUTH_URI  "/scripting/preauth.esp"
39 #define JSONRPC_REQUEST   "/services"
40 #define JSONRPC_SERVER    "/request.esp"
41
42 /* state of the esp subsystem for a specific request */
43 struct esp_state {
44         struct websrv_context *web;
45         struct EspRequest *req;
46         struct MprVar variables[ESP_OBJ_MAX];
47         struct session_data *session;
48 };
49
50 /*
51   output the http headers
52 */
53 static void http_output_headers(struct websrv_context *web)
54 {
55         int i;
56         char *s;
57         DATA_BLOB b;
58         uint32_t content_length = 0;
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
90         /* work out the content length */
91         content_length = web->output.content.length;
92         if (web->output.fd != -1) {
93                 struct stat st;
94                 fstat(web->output.fd, &st);
95                 content_length += st.st_size;
96         }
97         s = talloc_asprintf_append(s, "Content-Length: %u\r\n\r\n", content_length);
98         if (s == NULL) return;
99
100         b = web->output.content;
101         web->output.content = data_blob_string_const(s);
102         data_blob_append(web, &web->output.content, b.data, b.length);
103         data_blob_free(&b);
104 }
105
106 /*
107   return the local path for a URL
108 */
109 static const char *http_local_path(struct websrv_context *web,
110                                    const char *url,
111                                    const char *base_dir)
112 {
113         int i;
114         char *path;
115
116         /* check that the url is OK */
117         if (url[0] != '/') return NULL;
118
119         for (i=0;url[i];i++) {
120                 if ((!isalnum((unsigned char)url[i]) && !strchr("./_-", url[i])) ||
121                     (url[i] == '.' && strchr("/.", url[i+1]))) {
122                         return NULL;
123                 }
124         }
125
126         path = talloc_asprintf(web, "%s/%s", base_dir, url+1);
127         if (path == NULL) return NULL;
128
129         if (directory_exist(path)) {
130                 path = talloc_asprintf_append(path, "/index.esp");
131         }
132         return path;
133 }
134
135 /*
136   called when esp wants to read a file to support include() calls
137 */
138 static int http_readFile(EspHandle handle,
139                          char **buf,
140                          int *len,
141                          const char *path,
142                          const char *base_dir)
143 {
144         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
145         int fd = -1;
146         struct stat st;
147         *buf = NULL;
148
149         path = http_local_path(web, path, base_dir);
150         if (path == NULL) goto failed;
151
152         fd = open(path, O_RDONLY);
153         if (fd == -1 || fstat(fd, &st) != 0 || !S_ISREG(st.st_mode)) goto failed;
154
155         *buf = talloc_size(handle, st.st_size+1);
156         if (*buf == NULL) goto failed;
157
158         if (read(fd, *buf, st.st_size) != st.st_size) goto failed;
159
160         (*buf)[st.st_size] = 0;
161
162         close(fd);
163         *len = st.st_size;
164         return 0;
165
166 failed:
167         DEBUG(0,("Failed to read file %s - %s\n", path, strerror(errno)));
168         if (fd != -1) close(fd);
169         talloc_free(*buf);
170         *buf = NULL;
171         return -1;
172 }
173
174 static int http_readFileFromWebappsDir(EspHandle handle,
175                                        char **buf,
176                                        int *len,
177                                        const char *path)
178 {
179     return http_readFile(handle, buf, len, path, lp_webapps_directory());
180 }
181
182
183
184 /*
185   called when esp wants to find the real path of a file
186 */
187 static int http_mapToStorage(EspHandle handle, char *path, int len, const char *uri, int flags)
188 {
189         if (uri == NULL || strlen(uri) >= len) return -1;
190         strncpy(path, uri, len);
191         return 0;
192 }
193
194 /*
195   called when esp wants to output something
196 */
197 static int http_writeBlock(EspHandle handle, const char *buf, int size)
198 {
199         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
200         if (!data_blob_append(web, &web->output.content, buf, size))
201                 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, "HTTP_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 static jmp_buf ejs_exception_buf;
488 static const char *exception_reason;
489
490 static void web_server_ejs_exception(const char *reason)
491 {
492         Ejs *ep = ejsPtr(0);
493         if (ep) {
494                 ejsSetErrorMsg(0, "%s", reason);
495                 exception_reason = ep->error;
496         } else {
497                 exception_reason = reason;
498         }
499         DEBUG(0,("%s", exception_reason));
500         longjmp(ejs_exception_buf, -1);
501 }
502 #else
503 static void web_server_ejs_exception(const char *reason)
504 {
505         DEBUG(0,("%s", reason));
506         smb_panic(reason);
507 }
508 #endif
509
510 /*
511   process a esp request
512 */
513 static void esp_request(struct esp_state *esp, const char *url)
514 {
515         struct websrv_context *web = esp->web;
516         int size;
517         int res;
518         char *emsg = NULL, *buf;
519
520         if (http_readFile(web, &buf, &size, url, lp_webapps_directory()) != 0) {
521                 http_error_unix(web, url);
522                 return;
523         }
524
525 #if HAVE_SETJMP_H
526         if (setjmp(ejs_exception_buf) != 0) {
527                 http_error(web, 500, exception_reason);
528                 return;
529         }
530 #endif
531
532         res = espProcessRequest(esp->req, url, buf, &emsg);
533         if (res != 0 && emsg) {
534                 http_writeBlock(web, "<pre>", 5);
535                 http_writeBlock(web, emsg, strlen(emsg));
536                 http_writeBlock(web, "</pre>", 6);
537         }
538         talloc_free(buf);
539 }
540
541 /*
542   process a JSON RPC request
543 */
544 static void jsonrpc_request(struct esp_state *esp)
545 {
546         struct websrv_context *web = esp->web;
547         const char *path = http_local_path(web,
548                                            JSONRPC_SERVER,
549                                            lp_jsonrpc_services_dir());
550         MprVar *global;
551         MprVar v;
552         MprVar temp;
553         int size;
554         int res;
555         char *emsg = NULL;
556         char *emsg2 = NULL;
557         char *buf;
558         char *error_script =
559                 "error.setOrigin(jsonrpc.Constant.ErrorOrigin.Server); "
560                 "error.setError(jsonrpc.Constant.ErrorCode.UnexpectedOutput, "
561                 "               global.errorString);"
562                 "error.Send();";
563
564         /* Ensure we got a valid path. */
565         if (path == NULL) {
566                 /* should never occur */
567                 http_error(esp->web, 500, "Internal server error");
568                 return;
569         }
570
571         /* Ensure that the JSON-RPC server request script exists */
572         if (!file_exist(path)) {
573                 http_error_unix(esp->web, path);
574                 return;
575         }
576
577         /* Call the server request script */
578         if (http_readFile(web, &buf, &size,
579                           JSONRPC_SERVER, lp_jsonrpc_services_dir()) != 0) {
580                 http_error_unix(web, JSONRPC_SERVER);
581                 return;
582         }
583
584 #if HAVE_SETJMP_H
585         if (setjmp(ejs_exception_buf) != 0) {
586                 http_error(web, 500, exception_reason);
587                 return;
588         }
589 #endif
590
591         res = espProcessRequest(esp->req, JSONRPC_SERVER, buf, &emsg);
592         if (res != 0 && emsg) {
593                 /* Save the error in a string accessible from javascript */
594                 global = ejsGetGlobalObject(0);
595                 v = mprString(emsg);
596                 mprCreateProperty(global, "errorString", &v);
597
598                 /* Create and send a JsonRpcError object */
599                 if (ejsEvalScript(0,
600                                   error_script,
601                                   &temp,
602                                   &emsg2) != 0) {
603                         http_writeBlock(web, "<pre>", 5);
604                         http_writeBlock(web, emsg, strlen(emsg));
605                         http_writeBlock(web, "</pre>", 6);
606                 }
607         }
608         talloc_free(buf);
609 }
610
611 /*
612   perform pre-authentication on every page if /scripting/preauth.esp
613   exists.  If this script generates any non-whitepace output at all,
614   then we don't run the requested URL.
615
616   note that the preauth is run even for static pages such as images, but not
617   for JSON-RPC service requests which do their own authentication via the
618   JSON-RPC server.
619 */
620 static BOOL http_preauth(struct esp_state *esp)
621 {
622         const char *path = http_local_path(esp->web,
623                                            HTTP_PREAUTH_URI,
624                                            lp_webapps_directory());
625         int i;
626         if (path == NULL) {
627                 http_error(esp->web, 500, "Internal server error");
628                 return False;
629         }
630         if (!file_exist(path)) {
631                 /* if the preath script is not installed then allow access */
632                 return True;
633         }
634         esp_request(esp, HTTP_PREAUTH_URI);
635         for (i=0;i<esp->web->output.content.length;i++) {
636                 if (!isspace(esp->web->output.content.data[i])) {
637                         /* if the preauth has generated content, then force it
638                            to be html, so that we can show the login page for
639                            failed access to images */
640                         http_setHeader(esp->web, "Content-Type: text/html", 0);
641                         return False;
642                 }
643         }
644         data_blob_free(&esp->web->output.content);
645         return True;
646 }
647
648
649 /* 
650    handling of + and % escapes in http variables 
651 */
652 static const char *http_unescape(TALLOC_CTX *mem_ctx, const char *p)
653 {
654         char *s0 = talloc_strdup(mem_ctx, p);
655         char *s = s0;
656         if (s == NULL) return NULL;
657
658         while (*s) {
659                 unsigned v;
660                 if (*s == '+') *s = ' ';
661                 if (*s == '%' && sscanf(s+1, "%02x", &v) == 1) {
662                         *s = (char)v;
663                         memmove(s+1, s+3, strlen(s+3)+1);
664                 }
665                 s++;
666         }
667
668         return s0;
669 }
670
671 /*
672   set a form or GET variable
673 */
674 static void esp_putvar(struct esp_state *esp, const char *var, const char *value)
675 {
676         if (strcasecmp(var, SAMBA_SESSION_KEY) == 0) {
677                 /* special case support for browsers without cookie
678                  support */
679                 esp->web->input.session_key = talloc_strdup(esp, value);
680         } else {
681                 mprSetPropertyValue(&esp->variables[ESP_FORM_OBJ], 
682                                     http_unescape(esp, var),
683                                     mprCreateStringVar(http_unescape(esp, value), 0));
684         }
685 }
686
687
688 /*
689   parse the variables in a POST style request
690 */
691 static NTSTATUS http_parse_post(struct esp_state *esp)
692 {
693         DATA_BLOB b = esp->web->input.partial;
694
695         while (b.length) {
696                 char *p, *line;
697                 size_t len;
698
699                 p = memchr(b.data, '&', b.length);
700                 if (p == NULL) {
701                         len = b.length;
702                 } else {
703                         len = p - (char *)b.data;
704                 }
705                 line = talloc_strndup(esp, (char *)b.data, len);
706                 NT_STATUS_HAVE_NO_MEMORY(line);
707                                      
708                 p = strchr(line,'=');
709                 if (p) {
710                         *p = 0;
711                         esp_putvar(esp, line, p+1);
712                 }
713                 talloc_free(line);
714                 b.length -= len;
715                 b.data += len;
716                 if (b.length > 0) {
717                         b.length--;
718                         b.data++;
719                 }
720         }
721
722         return NT_STATUS_OK;
723 }
724
725 /*
726   parse the variables in a GET style request
727 */
728 static NTSTATUS http_parse_get(struct esp_state *esp)
729 {
730         struct websrv_context *web = esp->web;
731         char *p, *s, *tok;
732         char *pp;
733
734         p = strchr(web->input.url, '?');
735         web->input.query_string = p+1;
736         *p = 0;
737
738         s = talloc_strdup(esp, esp->web->input.query_string);
739         NT_STATUS_HAVE_NO_MEMORY(s);
740
741         for (tok=strtok_r(s,"&;", &pp);tok;tok=strtok_r(NULL,"&;", &pp)) {
742                 p = strchr(tok,'=');
743                 if (p) {
744                         *p = 0;
745                         esp_putvar(esp, tok, p+1);
746                 }
747         }
748         return NT_STATUS_OK;
749 }
750
751 /*
752   called when a session times out
753 */
754 static void session_timeout(struct event_context *ev, struct timed_event *te, 
755                             struct timeval t, void *private)
756 {
757         struct session_data *s = talloc_get_type(private, struct session_data);
758         talloc_free(s);
759 }
760
761 /*
762   destroy a session
763  */
764 static int session_destructor(struct session_data *s)
765 {
766         DLIST_REMOVE(s->edata->sessions, s);
767         return 0;
768 }
769
770 /*
771   setup the session for this request
772 */
773 static void http_setup_session(struct esp_state *esp)
774 {
775         const char *session_key = SAMBA_SESSION_KEY;
776         char *p;
777         const char *cookie = esp->web->input.cookie;
778         const char *key = NULL;
779         struct esp_data *edata = talloc_get_type(esp->web->task->private, struct esp_data);
780         struct session_data *s;
781         BOOL generated_key = False;
782
783         /* look for our session key */
784         if (cookie && (p = strstr(cookie, session_key)) && 
785             p[strlen(session_key)] == '=') {
786                 p += strlen(session_key)+1;
787                 key = talloc_strndup(esp, p, strcspn(p, ";"));
788         }
789
790         if (key == NULL && esp->web->input.session_key) {
791                 key = esp->web->input.session_key;
792         } else if (key == NULL) {
793                 key = generate_random_str_list(esp, 16, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
794                 generated_key = True;
795         }
796
797         /* try to find this session in the existing session list */
798         for (s=edata->sessions;s;s=s->next) {
799                 if (strcmp(key, s->id) == 0) {
800                         break;
801                 }
802         }
803
804         if (s == NULL) {
805                 /* create a new session */
806                 s = talloc_zero(edata, struct session_data);
807                 s->id = talloc_steal(s, key);
808                 s->data = NULL;
809                 s->te = NULL;
810                 s->edata = edata;
811                 s->lifetime = lp_parm_int(-1, "web", "sessiontimeout", 900);
812                 DLIST_ADD(edata->sessions, s);
813                 talloc_set_destructor(s, session_destructor);
814                 if (!generated_key) {
815                         mprSetPropertyValue(&esp->variables[ESP_REQUEST_OBJ], 
816                                             "SESSION_EXPIRED", mprCreateStringVar("True", 0));
817                 }
818         }
819
820         http_setCookie(esp->web, session_key, key, s->lifetime, "/", 0);
821
822         if (s->data) {
823                 mprCopyVar(&esp->variables[ESP_SESSION_OBJ], s->data, MPR_DEEP_COPY);
824         }
825
826         esp->web->session = s;
827 }
828
829
830 /* callbacks for esp processing */
831 static const struct Esp esp_control = {
832         .maxScriptSize   = 60000,
833         .writeBlock      = http_writeBlock,
834         .setHeader       = http_setHeader,
835         .redirect        = http_redirect,
836         .setResponseCode = http_setResponseCode,
837         .readFile        = http_readFileFromWebappsDir,
838         .mapToStorage    = http_mapToStorage,
839         .setCookie       = http_setCookie,
840         .createSession   = http_createSession,
841         .destroySession  = http_destroySession,
842         .getSessionId    = http_getSessionId
843 };
844
845 /*
846   process a complete http request
847 */
848 void http_process_input(struct websrv_context *web)
849 {
850         NTSTATUS status;
851         struct esp_state *esp = NULL;
852         struct esp_data *edata = talloc_get_type(web->task->private, struct esp_data);
853         struct smbcalls_context *smbcalls_ctx;
854         char *p;
855         void *save_mpr_ctx = mprMemCtx();
856         void *ejs_save = ejs_save_state();
857         int i;
858         const char *file_type = NULL;
859         enum page_type {
860                 page_type_simple,
861                 page_type_esp,
862                 page_type_jsonrpc
863         };
864         enum page_type page_type;
865         const struct {
866                 const char *extension;
867                 const char *mime_type;
868                 enum page_type page_type;
869         } mime_types[] = {
870                 {"gif",  "image/gif"},
871                 {"png",  "image/png"},
872                 {"jpg",  "image/jpeg"},
873                 {"txt",  "text/plain"},
874                 {"ico",  "image/x-icon"},
875                 {"css",  "text/css"},
876                 {"esp",  "text/html", True}
877         };
878
879         /*
880          * give the smbcalls a chance to find the event context
881          * and messaging context 
882          */
883         smbcalls_ctx = talloc(web, struct smbcalls_context);
884         if (smbcalls_ctx == NULL) goto internal_error;
885         smbcalls_ctx->event_ctx = web->conn->event.ctx;
886         smbcalls_ctx->msg_ctx = web->conn->msg_ctx;
887
888         esp = talloc_zero(smbcalls_ctx, struct esp_state);
889         if (esp == NULL) goto internal_error;
890
891         esp->web = web;
892
893         mprSetCtx(esp);
894
895         if (espOpen(&esp_control) != 0) goto internal_error;
896
897         for (i=0;i<ARRAY_SIZE(esp->variables);i++) {
898                 esp->variables[i] = mprCreateUndefinedVar();
899         }
900         esp->variables[ESP_HEADERS_OBJ]     = mprCreateObjVar("headers", ESP_HASH_SIZE);
901         esp->variables[ESP_FORM_OBJ]        = mprCreateObjVar("form", ESP_HASH_SIZE);
902         esp->variables[ESP_APPLICATION_OBJ] = mprCreateObjVar("application", ESP_HASH_SIZE);
903         esp->variables[ESP_COOKIES_OBJ]     = mprCreateObjVar("cookies", ESP_HASH_SIZE);
904         esp->variables[ESP_FILES_OBJ]       = mprCreateObjVar("files", ESP_HASH_SIZE);
905         esp->variables[ESP_REQUEST_OBJ]     = mprCreateObjVar("request", ESP_HASH_SIZE);
906         esp->variables[ESP_SERVER_OBJ]      = mprCreateObjVar("server", ESP_HASH_SIZE);
907         esp->variables[ESP_SESSION_OBJ]     = mprCreateObjVar("session", ESP_HASH_SIZE);
908
909         if (edata->application_data) {
910                 mprCopyVar(&esp->variables[ESP_APPLICATION_OBJ], 
911                            edata->application_data, MPR_DEEP_COPY);
912         }
913
914         smb_setup_ejs_functions(web_server_ejs_exception);
915
916         if (web->input.url == NULL) {
917                 http_error(web, 400, "You must specify a GET or POST request");
918                 mprSetCtx(save_mpr_ctx);
919                 ejs_restore_state(ejs_save);
920                 return;
921         }
922         
923         /* parse any form or get variables */
924         if (web->input.post_request) {
925                 status = http_parse_post(esp);
926                 if (!NT_STATUS_IS_OK(status)) {
927                         http_error(web, 400, "Malformed POST data");
928                         mprSetCtx(save_mpr_ctx);
929                         ejs_restore_state(ejs_save);
930                         return;
931                 }
932         } 
933         if (strchr(web->input.url, '?')) {
934                 status = http_parse_get(esp);
935                 if (!NT_STATUS_IS_OK(status)) {
936                         http_error(web, 400, "Malformed GET data");
937                         mprSetCtx(save_mpr_ctx);
938                         ejs_restore_state(ejs_save);
939                         return;
940                 }
941         }
942
943         http_setup_session(esp);
944
945         esp->req = espCreateRequest(web, web->input.url, esp->variables);
946         if (esp->req == NULL) goto internal_error;
947
948         /*
949          * Work out the mime type.  First, we see if the request is a JSON-RPC
950          * service request.  If not, we look at the extension.
951          */
952         if (strncmp(web->input.url,
953                     JSONRPC_REQUEST,
954                     sizeof(JSONRPC_REQUEST) - 1) == 0 &&
955             (web->input.url[sizeof(JSONRPC_REQUEST) - 1] == '\0' ||
956              web->input.url[sizeof(JSONRPC_REQUEST) - 1] == '/')) {
957             page_type = page_type_jsonrpc;
958             file_type = "text/json";
959             
960         } else {
961             p = strrchr(web->input.url, '.');
962             if (p == NULL) {
963                     page_type = page_type_esp;
964                     file_type = "text/html";
965             }
966             for (i=0;p && i<ARRAY_SIZE(mime_types);i++) {
967                 if (strcmp(mime_types[i].extension, p+1) == 0) {
968                     page_type = mime_types[i].page_type;
969                     file_type = mime_types[i].mime_type;
970                 }
971             }
972             if (file_type == NULL) {
973                 page_type = page_type_simple;
974                 file_type = "text/html";
975             }
976         }
977
978         /* setup basic headers */
979         http_setResponseCode(web, 200);
980         http_setHeader(web, talloc_asprintf(esp, "Date: %s", 
981                                             http_timestring(esp, time(NULL))), 0);
982         http_setHeader(web, "Server: Samba", 0);
983         http_setHeader(web, "Connection: close", 0);
984         http_setHeader(web, talloc_asprintf(esp, "Content-Type: %s", file_type), 0);
985
986         http_setup_arrays(esp);
987
988         /*
989          * Do pre-authentication.  If pre-authentication succeeds, do
990          * page-type-specific processing.
991          */
992         switch(page_type)
993         {
994         case page_type_simple:
995                 if (http_preauth(esp)) {
996                         http_simple_request(web);
997                 }
998                 break;
999
1000         case page_type_esp:
1001                 if (http_preauth(esp)) {
1002                         esp_request(esp, web->input.url);
1003                 }
1004                 break;
1005
1006         case page_type_jsonrpc:
1007                 jsonrpc_request(esp);
1008                 break;
1009         }
1010
1011         if (web->conn == NULL) {
1012                 /* the connection has been terminated above us, probably
1013                    via a timeout */
1014                 goto internal_error;
1015         }
1016
1017         if (!web->output.output_pending) {
1018                 http_output_headers(web);
1019                 EVENT_FD_WRITEABLE(web->conn->event.fde);
1020                 web->output.output_pending = True;
1021         }
1022
1023         /* copy any application data to long term storage in edata */
1024         talloc_free(edata->application_data);
1025         edata->application_data = talloc_zero(edata, struct MprVar);
1026         mprSetCtx(edata->application_data);
1027         mprCopyVar(edata->application_data, &esp->variables[ESP_APPLICATION_OBJ], 
1028                    MPR_DEEP_COPY);
1029         mprSetCtx(esp);
1030
1031         /* copy any session data */
1032         if (web->session) {
1033                 talloc_free(web->session->data);
1034                 web->session->data = talloc_zero(web->session, struct MprVar);
1035                 if (esp->variables[ESP_SESSION_OBJ].properties == NULL ||
1036                     esp->variables[ESP_SESSION_OBJ].properties[0].numItems == 0) {
1037                         talloc_free(web->session);
1038                         web->session = NULL;
1039                 } else {
1040                         mprSetCtx(web->session->data);
1041                         mprCopyVar(web->session->data, &esp->variables[ESP_SESSION_OBJ], 
1042                                    MPR_DEEP_COPY);
1043                         /* setup the timeout for the session data */
1044                         mprSetCtx(esp);
1045                         talloc_free(web->session->te);
1046                         web->session->te = event_add_timed(web->conn->event.ctx, web->session, 
1047                                                            timeval_current_ofs(web->session->lifetime, 0), 
1048                                                            session_timeout, web->session);
1049                 }
1050         }
1051
1052         talloc_free(esp);
1053         mprSetCtx(save_mpr_ctx);
1054         ejs_restore_state(ejs_save);
1055         return;
1056         
1057 internal_error:
1058         mprSetCtx(esp);
1059         talloc_free(esp);
1060         if (web->conn != NULL) {
1061                 http_error(web, 500, "Internal server error");
1062         }
1063         mprSetCtx(save_mpr_ctx);
1064         ejs_restore_state(ejs_save);
1065 }
1066
1067
1068 /*
1069   parse one line of header input
1070 */
1071 NTSTATUS http_parse_header(struct websrv_context *web, const char *line)
1072 {
1073         if (line[0] == 0) {
1074                 web->input.end_of_headers = True;
1075         } else if (strncasecmp(line,"GET ", 4)==0) {
1076                 web->input.url = talloc_strndup(web, &line[4], strcspn(&line[4], " \t"));
1077         } else if (strncasecmp(line,"POST ", 5)==0) {
1078                 web->input.post_request = True;
1079                 web->input.url = talloc_strndup(web, &line[5], strcspn(&line[5], " \t"));
1080         } else if (strchr(line, ':') == NULL) {
1081                 http_error(web, 400, "This server only accepts GET and POST requests");
1082                 return NT_STATUS_INVALID_PARAMETER;
1083         } else if (strncasecmp(line,"Content-Length: ", 16)==0) {
1084                 web->input.content_length = strtoul(&line[16], NULL, 10);
1085         } else {
1086 #define PULL_HEADER(v, s) do { \
1087         if (strncmp(line, s, strlen(s)) == 0) { \
1088                 web->input.v = talloc_strdup(web, &line[strlen(s)]); \
1089                 return NT_STATUS_OK; \
1090         } \
1091 } while (0)
1092                 PULL_HEADER(content_type, "Content-Type: ");
1093                 PULL_HEADER(user_agent, "User-Agent: ");
1094                 PULL_HEADER(referer, "Referer: ");
1095                 PULL_HEADER(host, "Host: ");
1096                 PULL_HEADER(accept_encoding, "Accept-Encoding: ");
1097                 PULL_HEADER(accept_language, "Accept-Language: ");
1098                 PULL_HEADER(accept_charset, "Accept-Charset: ");
1099                 PULL_HEADER(cookie, "Cookie: ");
1100         }
1101
1102         /* ignore all other headers for now */
1103         return NT_STATUS_OK;
1104 }
1105
1106
1107 /*
1108   setup the esp processor - called at task initialisation
1109 */
1110 NTSTATUS http_setup_esp(struct task_server *task)
1111 {
1112         struct esp_data *edata;
1113
1114         edata = talloc_zero(task, struct esp_data);
1115         NT_STATUS_HAVE_NO_MEMORY(edata);
1116
1117         task->private = edata;
1118
1119         edata->tls_params = tls_initialise(edata);
1120         NT_STATUS_HAVE_NO_MEMORY(edata->tls_params);
1121
1122         return NT_STATUS_OK;
1123 }