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