1 /* Copyright (C) 2000-2010 Thomas Bopp, Thorsten Hampel, Ludger Merkens
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 * $Id: http.pike,v 1.5 2010/08/20 20:42:25 astra Exp $
23 #include <attributes.h>
28 class http : public login{
39 // #define HTTP_DEBUG(s, args...) get_module("log")->log_debug("http", s, args)
40 #define HTTP_DEBUG(s, args...) debug_http("http", s, args)
42 #define HTTP_DEBUG(s, args...)
45 mixed debug_http(string type, string s, mixed ... args)
47 werror(sprintf("%s: %s\n", type, s), @args);
48 return get_module("log")->log_debug("http", s, args);
55 object __request; // the saved request object
56 string not_query; // initial not_query, saved because of rewrite
58 int __toread; // how many bytes to go to read body
59 string __body; // the request body
68 constant automatic_tasks = ({ "create_group_exit", "remove_group_exit" });
70 void create(object fp, bool admin_port)
74 __admin_port = admin_port;
75 __tasks = get_module("tasks");
76 __cgi = get_module("cgi");
80 int check_notprecondition(object obj)
83 string ifheader = __request->request_headers["if-none-match"];
84 if ( !stringp(ifheader) )
87 etag = obj->get_etag();
89 ifheader = String.trim_all_whites(ifheader);
90 sscanf(ifheader, "\"%s\"", ifheader);
91 array entitytags = ifheader / ",";
92 if ( search(entitytags, "*") >= 0 || search(entitytags, etag) >= 0 )
98 int check_precondition(object obj)
101 string ifheader = __request->request_headers["if-match"];
102 if ( !stringp(ifheader) )
105 etag = obj->get_etag();
107 ifheader = String.trim_all_whites(ifheader);
109 sscanf(ifheader, "\"%s\"", ifheader);
111 array entitytags = ifheader / ",";
112 if ( search(entitytags, "*") >= 0 || search(entitytags, etag) >= 0 )
118 * authenticate with server. Normal http authentication
120 * @param string basic - the auth in base64 encoding
121 * @return 0 if the authentication was successfull, otherwise error codes
123 int authenticate(string basic, void|object obj)
125 string auth, user, pass;
128 auth = __request->cookies->steam_auth;
129 if ( stringp(auth) && strlen(auth) > 0 && auth != "0" )
131 auth = Protocols.HTTP.Server.http_decode_string(auth);
133 __request->variables["auth"] = "cookie";
134 __request->cookies->steam_auth = "****";
138 __request->variables["auth"] = "http";
141 if ( stringp(basic) ) {
143 if ( sscanf(basic, "%s %s", method, auth) == 0 )
146 switch ( lower_case(method) )
156 auth = MIME.decode_base64(auth);
157 auth = string_to_utf8(auth);
158 sscanf(auth, "%s:%s", user, pass);
160 userobj = get_module("auth")->authenticate(user,pass);
161 if ( !objectp(userobj) )
169 else if ( objectp(obj) && obj->query_sanction(_GUEST) & SANCTION_READ )
177 * This thread is used for downloading stuff.
179 * @param object req - the request object.
180 * @param function dataf - the function to call to read data
183 void download_thread(object req, function dataf)
186 while ( stringp(str=dataf()) ) {
187 int sz = strlen(str);
189 while ( written < sz ) {
190 written += req->my_fd->write(str[written..]);
199 int handle_cgi(object obj, mapping vars)
204 ga = gauge(file = __cgi->call_script(obj, vars, __request));
205 HTTP_DEBUG("CGI Call took %f seconds.", ga);
206 // pipe the file to the fd
207 object pipe = ((program)"base/fastpipe")();
208 pipe->set_done_callback(finish_pipe);
210 pipe->output(__request->my_fd);
218 if ( objectp(__request) && mappingp(__request->request_headers) ) {
219 string useragent = __request->request_headers["user-agent"] || "";
220 if ( search(lower_case(useragent), "webdav") >= 0 ||
221 search(useragent, "DAV") >= 0 )
224 return isWebDAV; // heuristic from webdav.pike if PROPFIND is called
227 int exchange_links(object obj)
231 object env = obj->get_environment();
232 if (objectp(env) && env->query_attribute(CONT_EXCHANGE_LINKS))
238 * Handle the GET method of the HTTP protocol
240 * @param object obj - the object to get
241 * @param mapping vars - the variable mapping
242 * @return result mapping with data, type, length
244 mapping handle_GET(object obj, mapping vars)
246 mapping result = ([ ]);
247 mapping extra = ([ ]); // extra headers
248 object _obj = obj; // if some script is used to display another object
252 if ( obj == get_module("home") && _fp == get_module("filepath:url") )
253 return low_answer(302, "Found",
254 ([ "Location": _Server->ssl_redirect("/home/"), ]));
257 // the variable object is default for all requests send to sTeam
258 // whenever some script is executed, object defines the sTeam object
259 // actually requested.
261 return response_notfound(__request->not_query, vars);
263 _obj = find_object((int)vars->object);
264 if ( !objectp(_obj) )
265 return response_notfound(vars->object, vars);
268 HTTP_DEBUG("GET " + obj->describe() + "\n%O\n", vars);
270 string mimetype = obj->query_attribute(DOC_MIME_TYPE) || "";
272 if ( obj->get_object_class() & CLASS_SCRIPT )
274 result = handle_POST(obj, vars);
275 if ( !mappingp(result) )
278 else if ( vars->type == "execute" &&
279 obj->get_object_class() & CLASS_DOCLPC )
281 result = handle_POST(obj, vars);
283 else if ( vars->type == "content" &&
284 ( obj->get_object_class() & CLASS_DOCXML ||
285 mimetype == "text/xml" ) )
287 string xml = obj->get_content();
288 object xsl = httplib.get_stylesheet(obj);
289 if ( objectp(xsl) && !stringp(vars->source) ) {
290 result->data = run_xml(xml, xsl, vars);
291 string method = xsl->get_method();
292 if ( !stringp(method) || method == "" || method == "xml" )
294 result->type = "text/" + method;
296 else if ( vars->source == "transform" ) {
297 vars->object = (string)obj->get_object_id();
298 mixed res = show_object(obj, vars);
304 result->type = "text/xml";
307 else if ( vars->type == "content" &&
308 obj->get_object_class() & CLASS_DOCEXTERN )
310 result->data = redirect(obj->query_attribute(DOC_EXTERN_URL), 0);
311 result->type = "text/html";
313 else if ( vars->type == "content" &&
314 obj->get_object_class() & CLASS_DOCUMENT )
316 object xsl = obj->query_attribute("xsl:public");
317 if ( search(mimetype, "text") >= 0 &&
320 obj->query_attribute("xsl:use_public") )
322 // if xsl:document is set for any document then
323 // instead of downloading the document, do an
324 // xml transformation.
325 // the xml code is generated depending on the stylesheet
326 // This is actually show_object() functionality, but
327 // with type content set.
328 result->data = run_xml(obj, xsl, vars);
329 if ( stringp(vars->source) )
330 result->type = "text/xml";
332 result->type = "text/html";
334 result->encoding = xsl->get_encoding();
336 else if ( objectp(__cgi) && __cgi->get_program(mimetype) )
338 handle_cgi(obj, vars);
341 else if ( mimetype == "text/wiki" ) {
342 object wikimod = get_module("wiki");
343 if ( objectp(wikimod) && !is_dav()) {
344 object wikixsl = get_stylesheet(obj);
345 if ( !objectp(wikixsl) )
346 wikixsl = OBJ("/stylesheets/wiki.xsl");
348 if ( objectp(wikixsl) ) {
349 result->data = run_xml(obj, wikixsl, vars);
350 if ( stringp(vars->source) )
351 result->type = "text/xml";
353 result->type = "text/html";
355 result->encoding = wikixsl->get_encoding();
358 if ( stringp(vars->source) )
359 result->data = obj->get_content();
361 result->data = wikimod->wiki_to_html(obj, _fp, vars);
362 result->type = "text/html";
363 result->encoding = "utf-8";
367 result->type = "text/plain";
368 result->data = obj->get_content();
372 // download documents, but only if type is set to content
373 // because we might want to look at the objects annotations
374 object doc = obj->get_content_file("r", vars, "http");
376 result->type = obj->query_attribute(DOC_MIME_TYPE);
378 modified = doc->stat()->mtime;
379 result->modified = modified;
380 result->len = doc->_sizeof()-1;
381 string objEnc = obj->query_attribute(DOC_ENCODING);
382 if ( stringp(objEnc) )
383 result->encoding = objEnc;
385 if ( mimetype == "text/html" )
386 result->tags = find_tags(OBJ("/tags"));
389 vars->object = (string)obj->get_object_id();
390 mixed res = show_object(obj, vars);
395 result->length = strlen(result->data);
396 result->type = "text/html";
398 if ( stringp(result->type) ) {
399 if( search(result->type, "image") >= 0 ||
400 search(result->type, "css") >= 0 ||
401 search(result->type, "javascript") >= 0 )
402 extra->Expires = http_date(60*60*24*365+time());
404 extra->ETag = "\""+obj->get_etag() + "\"";
406 if ( !mappingp(result->extra_heads) )
407 result->extra_heads = extra;
409 result->extra_heads |= extra;
410 if ( !result->error )
424 * handle the http POST method. Also it might be required to
425 * read additional data from the fd.
427 * @param object obj - script to post data to
428 * @param mapping m - variables
429 * @return result mapping
431 mapping handle_POST(object obj, mapping m)
433 mapping result = ([ ]);
434 //HTTP_DEBUG("POST(%O)", m);
435 // need to read the request yourself...
437 return response_notfound(__request->not_query, m);
440 if (objectp(__cgi) &&
441 __cgi->get_program(obj->query_attribute(DOC_MIME_TYPE)))
446 if ( obj->is_upgrading() ) {
447 string html = result_page("The Script is being upgraded, try again !",
448 "JavaScript:history.back();");
449 return ([ "data": html, "type":"text/html", "error":200, ]);
454 if ( obj->get_object_class() & CLASS_DOCLPC ) {
456 mixed err = catch(script=obj->provide_instance());
457 mixed script_errors = obj->get_errors();
459 if ( sizeof(script_errors) > 0 ) {
460 res->data = error_page("There are errors executing script<br/>"+
461 (script_errors * "<br />"));
462 res->type = "text/html";
465 FATAL("Error handling script: %O, %O", err[0], err[1]);
467 else if ( objectp(script) ) {
468 res = script->execute(m);
471 else if ( obj->get_object_class() & CLASS_WEBSERVICE ) {
472 werror("WEBSERVICE\n%s\n", __request->raw);
474 res->data = obj->show_wsdl();
475 res->type = "text/xml";
477 else if ( !stringp(__body) || strlen(__body) == 0 ) {
478 res = obj->execute(m);
481 // analyse body of request (if available)
482 werror("Request Body = %O\n", __body);
483 object envelope = soap.parse_soap(__body);
484 array callNodes = envelope->lookup_body_elements(
485 "urn:"+obj->get_webservice_urn());
487 mapping callResults = ([ ]);
488 foreach(callNodes, object callNode ) {
489 mapping fcall = soap.parse_service_call(obj->get_object(), callNode);
490 fcall->result = fcall->function(@fcall->params);
491 callResults[callNode] = fcall;
492 if (objectp(fcall->result)) {
494 res->userData = ([ "callResults": callResults, "callNode": callNode]);
499 // create a new SOAP Envelope
500 envelope = soap.SoapEnvelope();
501 soap.add_service_results(envelope, callResults);
502 res->data = envelope->render();
503 res->type = "text/xml";
508 res = obj->execute(m);
510 if ( objectp(res) ) {
511 res->resultFunc = async_respond;
512 __request->my_fd->write("HTTP/1.1 100 Continue\r\n\r\n");
513 res->set_request(__request);
516 else if ( mappingp(res) ) {
519 else if ( intp(res) ) {
522 result->type = "text/plain";
523 result->extra_heads =
524 ([ "WWW-Authenticate": "basic realm=\"steam\"", ]);
528 else if ( arrayp(res) ) {
529 if ( sizeof(res) == 2 )
530 [ result->data, result->type ] = res;
532 [ result->data, result->type, result->modified ] = res;
536 result->type = "text/html";
541 mapping handle_OPTIONS(object obj, mapping vars)
544 mapping result = low_answer(200, "OK");
547 foreach ( indices(this_object()), string ind) {
549 if ( sscanf(ind, "handle_%s", cmd_name) > 0 )
550 allow += cmd_name + ", ";
553 result->extra_heads = ([
559 mapping handle_PUT(object obj, mapping vars)
561 // original handle_PUT only deals with uploading files via webdav
562 // PUT requests for scripts are done in handle_POST
563 // FIXME: this should check for webdav requests
565 if ( obj->get_object_class() & CLASS_SCRIPT )
567 result = handle_POST(obj, vars);
568 if ( !mappingp(result) )
571 else if ( vars->type == "execute" &&
572 obj->get_object_class() & CLASS_DOCLPC )
574 result = handle_POST(obj, vars);
576 else return handle_webdav_PUT(obj, vars);
581 mapping handle_webdav_PUT(object obj, mapping vars)
585 if ( !check_precondition(obj) )
586 return low_answer(412, "Precondition failed");
588 if ( !objectp(obj) ) {
589 string fname = __request->not_query;
591 obj = get_factory(CLASS_DOCUMENT)->execute( ([ "url":fname, ]) );
593 // create Stdio.File wrapper class for a steam object
594 string token = webdavlib.get_opaquelocktoken(__request->request_headers->if);
596 __upload = ((program)"/kernel/DocFile")(obj, "wct", vars, "http", token);
597 __toread = (int)__request->request_headers["content-length"];
599 int max_read = _Server->get_config("upload_max");
600 if (!zero_type(max_read) && __toread > max_read) {
601 steam_error("Maximum upload limit exceeded!");
603 if ( strlen(__request->body_raw) > 0 ) {
604 __toread -= strlen(__request->body_raw);
605 __upload->write(__request->body_raw);
607 if ( __toread > 0 ) {
608 __request->my_fd->set_nonblocking(read_put_data,0,finish_put);
612 return low_answer((__newfile ? 201:200), "created");
618 void read_put_data(mixed id, string data)
620 __upload->write(data);
621 __toread -= strlen(data);
630 void finish_put(mixed id)
636 HTTP_DEBUG("Finish HTTP PUT !");
638 respond(__request, low_answer(201, "created") );
640 respond(__request, low_answer(200, "ok") );
646 mapping handle_MKDIR(object obj, mapping vars)
648 string fname = __request->not_query;
649 if ( fname[-1] == '/' )
650 fname = dirname(fname); // remove trailing /
651 if ( objectp(_fp->path_to_object(fname, 1)) ) { // already exists
652 return low_answer(405, "not allowed");
654 object inter = _fp->path_to_object(dirname(fname));
655 if ( !objectp(inter) )
656 return low_answer(409, "Missing intermediate");
658 if ( !check_precondition(obj) )
659 return low_answer(412, "Precondition failed");
661 obj = _fp->make_directory(dirname(fname), basename(fname));
662 if ( objectp(obj) ) {
663 HTTP_DEBUG("Created/Returned Collection %O", obj);
664 return low_answer(201, "Created.");
666 return low_answer(403, "forbidden");
669 mapping handle_DELETE(object obj, mapping vars)
671 // original handle_DELETE only deals with deleting steam objects directly
672 // presumably via webdav
673 // DELETE requests for scripts are done in handle_POST
674 // FIXME: this should check for webdav requests
676 if ( obj->get_object_class() & CLASS_SCRIPT )
678 result = handle_POST(obj, vars);
679 if ( !mappingp(result) )
682 else if ( vars->type == "execute" &&
683 obj->get_object_class() & CLASS_DOCLPC )
685 result = handle_POST(obj, vars);
687 else return handle_webdav_DELETE(obj, vars);
692 mapping handle_webdav_DELETE(object obj, mapping vars)
695 return response_notfound(__request->not_query, vars);
696 if ( !check_precondition(obj) )
697 return low_answer(412, "Precondition failed");
698 if ( catch(obj->delete()) )
699 return low_answer(403, "forbidden");
700 return low_answer(200, "Ok");
704 mapping handle_HEAD(object obj, mapping vars)
707 return response_notfound(__request->not_query, vars);
709 mapping result = low_answer(200, "Ok");
710 result->type = obj->query_attribute(DOC_MIME_TYPE);
711 result->modified = obj->query_attribute(DOC_LAST_MODIFIED);
712 result->len = obj->get_content_size();
713 mapping lockdata = obj->query_attribute(OBJ_LOCK);
714 if ( mappingp(lockdata) && lockdata->token )
715 result->extra_heads = ([ "Lock-Token": "<" + lockdata->token + ">", ]);
722 * Read the body for a request. Usually the body is ignored, but
723 * POST with multipart form data need the body for the request.
725 * @param object req - the request object
726 * @param int len - the length of the body (as set in request headers)
727 * @return the parsed form data (variables set in a mapping) or 0
729 mapping read_body(object req, int len)
734 HTTP_DEBUG("trying to read length of body = %O", len);
735 if ( stringp(req->body_raw) )
736 len -= strlen(req->body_raw);
738 // i suppose webdav has another method to read the body,
739 // but we need it in __vars to handle PUT for scripts
740 // if ( req->request_type == "PUT" )
747 __body = req->body_raw;
749 string content_type = req->request_headers["content-type"] || "";
751 if ( strlen(__body) == 0 )
754 content_type = lower_case(content_type);
755 if (search(content_type, "multipart/form-data") >= 0 )
756 return parse_multipart_form_data(req, __body);
758 return ([ "__body": __body, ]);
762 * Read the body of a http request - if the POST sends
763 * multipart/formdata, then the request is not read by
764 * Protocols.HTTP.Server.
766 * @param mixed id - id object
767 * @param string data - the body data
769 void read_body_data(mixed id, string data)
771 if ( stringp(data) ) {
773 __toread -= strlen(data);
774 if ( __toread <= 0 ) {
775 __request->body_raw += __body;
776 __request->variables|=parse_multipart_form_data(
777 __request,__request->body_raw);
779 http_request(__request);
781 else if ( strlen(data) < __toread && __toread > 1024 ) {
782 // if another package of same size is not enough
783 werror("continue....\n");
784 __request->my_fd->write("HTTP/1.1 100 Continue\r\n\r\n");
790 * Call a command in the server. Return the result of the call or
791 * if no function was found 501.
793 * @param string cmd - the request_type to call
794 * @param object obj - the object
795 * @param mapping vars - the variables
796 * @return result mapping
799 mapping call_command(string cmd, object obj, mapping vars)
801 mapping result = ([ ]);
803 if ( !stringp(vars->host) )
804 vars->host = _Server->get_server_name();
806 // redirect a request on a container or a room that does not
807 // include the trailing /
808 if ( objectp(obj) && obj->get_object_class() & CLASS_CONTAINER &&
809 __request->not_query[-1] != '/' )
811 // any container access without the trailing /
812 result->data = redirect(
813 replace_uml(__request->not_query)+"/"+
814 (strlen(__request->query) > 0 ? "?"+ __request->query : ""),0);
815 result->type = "text/html";
818 HTTP_DEBUG("HTTP: " + __request->not_query + " (%O)", get_ip());
820 function call = this_object()["handle_"+cmd];
821 vars->__internal->request_method = cmd;
823 if ( functionp(call) ) {
824 result = call(obj, vars);
826 else if ( obj->get_object_class() & CLASS_SCRIPT )
828 result = handle_POST(obj, vars);
829 if ( !mappingp(result) )
834 result->data = "Not implemented";
836 result->extra_heads += ([
837 "Access-Control-Allow-Origin": vars->__internal->request_headers->origin,
838 "Access-Control-Allow-Headers": vars->__internal->request_headers["access-control-request-headers"],
839 "Access-Control-Allow-Methods": vars->__internal->request_headers["access-control-request-method"],
847 * Handle a http request within steam.
849 * @param object req - the request object
851 mapping run_request(object req)
853 mapping result = ([ ]);
855 string host = (req->request_headers->host || _Server->get_server_name());
857 //HTTP_DEBUG("run_request(%O)", req->request_headers);
858 //HTTP_DEBUG("run_request(): host = %O,%O", req->request_headers->host, host);
860 // see if the server is ready for requests
861 if ( !objectp(get_module("package:web") ) &&
862 !objectp(OBJ("/stylesheets")) &&
863 !objectp(get_module("package:spm_support")) )
865 result->data = "<html><head><title>open-sTeam</title></head><body>"+
866 "<h2>Welcome to sTeam</h2><p>"+
867 "Congratulations, you successfully installed an open-sTeam server!"+
869 "To be able to get anything working on this web port, you need to "+
870 "install the web package."+
872 "This open-sTeam server's installation has not finished yet, but "+
873 "automatic installation of some basic packages is currently in "+
874 "progress. Please check back in a few minutes - you will be able "+
875 "to use the package manager then!"+
879 result->type = "text/html";
883 // find the requested object
884 req->not_query = url_to_string(req->not_query);
885 not_query = req->not_query;
886 // if (!__admin_port)
887 // req->not_query = rewrite_url(req->not_query, req->request_headers);
890 // cookie based authorization
891 if ( req->not_query == "/login" ) {
892 login_user(_GUEST); // make sure this_user is set correctly
893 if ( req->variables->user && req->variables->password ) {
896 u = get_module("auth")->authenticate(req->variables->user,
897 req->variables->password);
899 if ( err || !objectp(u) )
900 return response_loginfailed(u, req->variables);
902 result->extra_heads =
903 set_auth_cookie(req->variables->user, req->variables->password);
905 string re = req->variables->area;
908 result->data = redirect(re, 0);
909 result->type = "text/html";
911 result->extra_heads |= ([ "Pragma":"No-Cache",
912 "Cache-Control":"No-Cache" ]);
915 object login = _fp->path_to_object("/documents/login.html");
916 if ( objectp(login) )
917 result = ([ "data": login->get_content(), "type": "text/html", ]);
919 result = ([ "data": "Access denied", "error": 401, ]);
923 else if ( req->not_query == "/logout" ) {
924 result->extra_heads = ([
925 "Set-Cookie":"steam_auth=;expires=Thu, 01-Jan-70 00:00:01 GMT; path=/",
927 object logout = _fp->path_to_object("/documents/logout.html");
928 if ( objectp(logout) )
929 result += ([ "data": logout->get_content(), "type": "text/html", ]);
931 result += ([ "data": "You are logged out!", "type": "text/html", ]);
935 HTTP_DEBUG("run_request(): calling %O->url_to_object(%O,%O)", _fp, host, req->not_query);
936 object obj = _fp->url_to_object(req->not_query, host);
938 if (!objectp(obj) && host[..3] == "www.")
939 return low_answer(301, "Found",
940 ([ "Location": (__admin_port ? "https://" : "http://" )+host[4..]+req->full_query ]));
943 obj = _fp->path_to_object(req->not_query, 1);
945 mapping m = req->variables;
947 // the type variable is crucial for steam, since
948 // It defines how the object is displayed.
949 // the content type is the default and also
950 // means objects are downloaded instead of displayed by show_object()
951 if ( !stringp(m->type) || m->type == "" ) {
952 if ( objectp(obj) && obj->get_object_class() & CLASS_MESSAGEBOARD )
953 m->type = "annotations";
957 // don't silently replace link objects with the destination,
958 // the context of the link object is needed to properly display the content with a stylesheet.
959 //if ( m->type == "content" ) {
960 // if ( objectp(obj) && obj->get_object_class() & CLASS_LINK ) {
961 // if ( objectp(obj->get_link_object()) )
962 // obj = obj->get_link_object();
968 _obj = find_object((int)m->object);
970 if ( objectp(_obj) ) {
971 // handle if-modified-since header as defined in HTTP/1.1 RFC
972 // instead of any script called the actually referred object (m->object)
973 // is used here for DOC_LAST_MODIFIED
974 string mod_since = req->request_headers["if-modified-since"];
975 int modified = _obj->query_attribute(DOC_LAST_MODIFIED);
976 if ( _obj->get_object_class() & CLASS_DOCUMENT &&
977 m->type == "content" &&
978 _obj->query_attribute(DOC_MIME_TYPE) != "text/wiki" &&
979 !is_modified(mod_since, modified, _obj->get_content_size()) )
981 HTTP_DEBUG("Not modified.");
982 return ([ "error":304,
983 "data":"not modified",
984 "extra_heads": (["ETag": "\""+_obj->get_etag()+"\"", ]),
988 if ( !(_obj->get_object_class() & CLASS_SCRIPT) ) {
989 if ( !check_precondition(_obj) ||
990 !check_notprecondition(_obj) )
991 return low_answer(304, "not modified");
995 int authn = authenticate(req->request_headers->authorization, obj);
999 result = response_loginfailed(_GUEST, m);
1000 result->extra_heads =
1001 ([ "WWW-Authenticate": "basic realm=\"steam\"", ]);
1002 // set auth cookie to zero again if auth fails.
1003 if ( req->cookies->steam_auth )
1004 result->extra_heads += ([
1006 "steam_auth=;expires=Thu, 01-Jan-70 00:00:01 GMT; path=/",
1009 result->error=authn;
1014 // make variable mapping compatible with old format used by caudium
1015 string referer = "none";
1016 if ( mappingp(req->request_headers) && req->request_headers->referer )
1017 referer = req->request_headers->referer;
1020 "request_headers": req->request_headers,
1021 "client": ({ "Mozilla", }),
1022 "full_query": req->full_query,
1025 m->referer = referer;
1026 m->interface = (__admin_port ? "admin" : "public" );
1027 if (req->request_headers["content-type"])
1029 //object mime = MIME.Message("Content-Type: "+req->request_headers["content-type"]);
1030 object mime = MIME.Message(req->raw);
1031 m->__internal->type = mime->type;
1032 m->__internal->subtype = mime->subtype;
1033 m->__internal->mime_type = mime->type+"/"+mime->subtype;
1034 m->__internal->charset = mime->charset;
1038 err = catch ( result = call_command(req->request_type, obj, m) );
1041 int slow = (int)_Server->get_config("log_slow_commands_http");
1042 if ( slow && (int)tt > slow )
1043 get_module("log")->log("slow_requests", LOG_LEVEL_INFO,
1044 "%s Request %O in %O took %d ms",
1045 timelib.event_time(time()),
1046 req->request_raw,obj,(int)tt);
1048 if ( mappingp(result) ) {
1049 string encoding = result->encoding;
1050 if ( !stringp(encoding) && objectp(obj) )
1051 encoding = obj->query_attribute(DOC_ENCODING);
1053 if ( mappingp(result->tags) && !is_dav() && exchange_links(obj)) {
1054 // run rxml parsing/replacing when file extension is xhtm
1055 // ! fixme !! how to get the tags mapping ??
1058 if ( result->type == "text/html" ) {
1060 if ( result->file ) {
1061 result->data = result->file->read(result->file->_sizeof());
1062 destruct(result->file);
1064 if ( objectp(obj) && obj->get_object_class() & CLASS_CONTAINER )
1067 m["env"]=_fp->path_to_object(dirname(req->not_query),1);
1072 result->data = htmllib.parse_rxml(result->data,
1076 result->length = strlen(result->data);
1081 if (mappingp(result) && stringp(result->type)&&stringp(result->encoding))
1082 result->type += "; charset="+result->encoding;
1085 if ( arrayp(err) && sizeof(err) == 3 && (err[2] & E_ACCESS) ) {
1086 result = response_noaccess(obj, m);
1087 HTTP_DEBUG("No access returned...\n");
1088 get_module("log")->log("security", LOG_LEVEL_DEBUG, "%O\n%O",
1092 FATAL(sprintf("error:\n%O\n%O\n", err[0], err[1]));
1093 result = response_error(obj, m, err);
1099 string rewrite_url(string url, mapping headers)
1101 if ( !stringp(headers->host) )
1103 mapping virtual_hosts = _ADMIN->query_attribute("virtual_hosts");
1104 // virtual_hosts mapping is in the form
1105 // http://steam.uni-paderborn.de : /steam
1106 if ( mappingp(virtual_hosts) ) {
1107 foreach(indices(virtual_hosts), string host)
1108 if ( search(headers->host, host) >= 0 && _fp->path_to_object(virtual_hosts[host] + url))
1109 return virtual_hosts[host] + url;
1115 * A http request is incoming. Convert the req (Request) object
1118 * @param object req - the incoming request.
1120 void http_request(object req)
1129 // read body always...., if body is too large abort.
1130 len = (int)req->request_headers["content-length"];
1132 mapping body_variables = read_body(req, len);
1133 // sometimes not the full body is read
1134 if ( !mappingp(body_variables) ) {
1135 // in this case we need to read the body
1136 req->my_fd->set_nonblocking(read_body_data,0,0);
1139 req->my_fd->set_blocking();
1141 int slow = (int)_Server->get_config("log_slow_commands_http");
1142 int tt = get_time_millis();
1143 int loaded_objects = master()->get_in_memory();
1144 int saved_objects = _Database->get_saves();
1146 set_this_user(this_object());
1149 req->variables |= body_variables;
1150 result = run_request(req);
1155 result = response_error(0, req->variables, err);
1159 FATAL("Internal Server error on error.\n%O\n%O\n",ie[0],ie[1]);
1160 FATAL("Original Error:\n%O\n%O", err[0], err[1]);
1161 result = ([ "error":500,
1163 "Internal Server Error - contact your administrator",
1164 "type": "text/html", ]);
1168 tt = get_time_millis() - tt;
1169 loaded_objects = master()->get_in_memory() - loaded_objects;
1170 saved_objects = _Database->get_saves() - saved_objects;
1173 MESSAGE("Request %O took %d ms, %d objects loaded, %d saved",
1180 // if zero is returned, then the http request object is
1181 // still working on the request
1182 if ( mappingp(result) ) {
1183 respond( req, result );
1190 * Get the appropriate stylesheet for a user to display obj.
1192 * @param object user - the active user
1193 * @param object obj - the object to show
1194 * @param mapping vars - variables.
1195 * @return the appropriate stylesheet to be used.
1197 object get_xsl_stylesheet(object user, object obj, mapping vars)
1199 mapping xslMap = obj->query_attribute("xsl:content");
1202 // for the presentation port the public stylesheets are used.
1203 if ( !__admin_port ) {
1204 xsl = obj->query_attribute("xsl:public");
1205 if ( !objectp(xsl) )
1206 xsl = OBJ("/stylesheets/public.xsl");
1209 return httplib.get_xsl_stylesheet(user, obj, vars);
1213 * handle the tasks. Call the task module and try to run a task
1214 * if any none-automatic task is in the queue then display
1217 * @param object user - the user to handle tasks for
1218 * @param mapping vars - the variables mapping
1219 * @return string|int result, 0 means no tasks
1221 string|int run_tasks(object user, mapping vars, mapping client_map)
1223 mixed tasks = __tasks->get_tasks(user);
1227 if ( arrayp(tasks) && sizeof(tasks) > 0 ) {
1228 if ( !stringp(vars["type"]) )
1229 vars["type"] = "content";
1230 if ( !stringp(vars->object) )
1231 vars->object = (string)user->query_attribute(USER_WORKROOM)->
1233 html = "<form action='/scripts/browser.pike'>"+
1234 "<input type='hidden' name='_action' value='tasks'/>"+
1235 "<input type='hidden' name='object' value='"+vars["object"]+"'/>"+
1236 "<input type='hidden' name='id' value='"+vars["object"]+"'/>"+
1237 "<input type='hidden' name='type' value='"+vars["type"]+"'/>"+
1238 "<input type='hidden' name='room' value='"+vars["room"]+"'/>"+
1239 "<input type='hidden' name='mode' value='"+vars["mode"]+"'/>"+
1240 "<h3>Tasks:</h3><br/><br/>";
1242 foreach(tasks, object t) {
1244 if ( !objectp(t) ) continue;
1245 if ( search(automatic_tasks, t->func) == -1 ) {
1246 html += "<input type='checkbox' name='tasks' value='"+
1247 t->tid+"' checked='true'/><SPAN CLASS='text0sc'> "+
1248 t->descriptions[client_map["language"]] +
1253 if ( t->obj == __tasks || t->obj ==
1254 _FILEPATH->path_to_object("/scripts/browser.pike") )
1255 __tasks->run_task(t->tid); // risky ?
1257 FATAL("Cannot run unauthorized Task: " +
1258 t->func + " inside %O", t->obj);
1262 FATAL("Error while executing task: %O\n%O", err[0], err[1]);
1265 __tasks->tasks_done(user);
1266 html += "<br/><br/><input type='submit' value='ok'/></form>";
1275 * Show an object by doing the 'normal' xsl transformation with
1276 * stylesheets. Note that the behaviour of this function depends
1277 * of the type of port used. There is the admin port and the presentation
1280 * @param object obj - the object to display
1281 * @param mapping vars - variables mapping
1282 * @return string|mapping result of transformation
1284 string|int|mapping show_object(object obj, mapping vars)
1288 object user = this_user();
1290 HTTP_DEBUG("show_object("+obj->describe()+")");
1292 if ( obj == _ROOTROOM && !_ADMIN->is_member(user) &&
1293 !(user == _GUEST && (_ROOTROOM->query_attribute("html:index") ||
1294 _ROOTROOM->get_object_byname("index.xml") )))
1295 // make compatible with old webinterface
1298 "data": redirect("/home/"+user->get_user_name()+"/", 0),
1299 "extra_heads": ([ "Pragma":"No-Cache",
1300 "Cache-Control":"No-Cache" ]) ]);
1301 result->type = "text/html";
1302 result->error = 200;
1306 mapping client_map = get_client_map(vars);
1307 if ( user != _GUEST ) {
1308 if ( !stringp(user->query_attribute(USER_LANGUAGE)) )
1309 user->set_attribute(USER_LANGUAGE, client_map->language);
1312 string lang = client_map->language;
1313 // the standard presentation port should behave like a normal webserver
1314 // so, if present, index files are used instead of the container.
1315 if ( !__admin_port && obj->get_object_class() & CLASS_CONTAINER )
1317 if ( obj->query_attribute("cont_type") == "multi_language" ) {
1318 mapping index = obj->query_attribute("language_index");
1319 if ( mappingp(index) ) {
1320 object indexfile = obj->get_object_byname(index[lang]);
1321 if ( !objectp(indexfile) )
1322 indexfile = obj->get_object_byname(index->default);
1323 if ( objectp(indexfile) ) {
1324 // indexfile need to be in the container
1325 if ( indexfile->get_environment() == obj ) {
1326 vars->type = "content";
1327 return handle_GET(indexfile, vars);
1332 object indexfile = obj->get_object_byname(obj->query_attribute("html:index"))
1333 ||obj->get_object_byname("index.html")
1334 ||obj->get_object_byname("index.xml");
1335 // old webinterface by default loads /index.xml
1336 if ( objectp(indexfile) ) {
1337 vars->type = "content";
1338 return handle_GET(indexfile, vars);
1342 _SECURITY->check_access(obj, user, SANCTION_READ,ROLE_READ_ALL, false);
1344 if ( obj->get_object_class() & CLASS_ROOM && !user->contains(obj, true) )
1346 // check for move clients !
1347 if ( !(user->get_status() & CLIENT_FEATURES_MOVE) )
1350 user->add_trail(obj, 20);
1351 // possible move other users to their home area
1352 catch(get_module("collect_users")->
1353 check_users_cleanup(obj->get_users()));
1355 else if ( obj->get_object_class() & CLASS_TRASHBIN ) {
1356 if ( !(user->get_status() & CLIENT_FEATURES_MOVE) ) {
1357 object wr = user->query_attribute(USER_WORKROOM);
1363 if ( __admin_port ) {
1364 mixed result = run_tasks(user, vars, client_map);
1365 if ( stringp(result) )
1366 return result_page(result, "no");
1370 // PDA detection - use different stylesheet (xsl:PDA:content, etc)
1371 if ( client_map["xres"] == "240" )
1372 vars->type = "PDA:" + vars->type;
1376 object xsl = get_xsl_stylesheet(user, obj, vars);
1377 if ( !objectp(xsl) )
1378 error("Failed to find xsl stylesheet !");
1380 HTTP_DEBUG("Using stylesheet: "+xsl->describe());
1381 html = run_xml(obj, xsl, vars);
1383 if ( !stringp(html) )
1384 error("Failed to process XSL (Stylesheet is " +
1385 _FILEPATH->object_to_filename(xsl) + ")");
1387 if ( vars->source == "true" )
1390 "length" : strlen(html),
1391 "type" : "text/xml", ]);
1393 string method = "text/"+xsl->get_method();
1395 return ([ "data": html,
1396 "length": strlen(html),
1397 "type": "text/html",
1398 "tags": find_tags(xsl),
1399 "encoding": xsl->get_encoding(),
1404 mapping response_noaccess(object obj, mapping vars)
1408 object noaccess = OBJ("/documents/access.xml");
1409 vars->type = "content";
1410 result = handle_GET(noaccess, vars);
1413 // on the admin port users are already logged in - so just show no access
1414 if ( this_user() == _GUEST && (__admin_port || !_Server->query_config("secure_credentials")) )
1415 result->error = 401;
1417 result->error = (__admin_port ? 403 :
1418 ( _Server->query_config("secure_credentials") ?
1421 if ( result->error == 401 )
1422 result->extra_heads =
1423 ([ "WWW-Authenticate": "basic realm=\"steam\"", ]);
1425 result->type = "text/html";
1426 string ressource = __request->not_query;
1427 if ( objectp(obj) && obj->get_object_class() & CLASS_SCRIPT ) {
1430 obj = find_object((int)vars->id);
1431 else if ( vars->object )
1432 obj = find_object((int)vars->object);
1433 else if ( vars->path ) {
1434 if ( objectp(vars->fp) )
1435 obj = vars->fp->path_to_object(vars->path);
1438 if ( objectp(obj) ) {
1439 if ( !objectp(obj->get_environment()) )
1440 ressource = obj->get_identifier();
1441 else if ( objectp(vars->fp) )
1442 ressource = vars->fp->object_to_filename(obj);
1444 if ( !stringp(ressource) ) ressource = "0";
1445 if ( objectp(script) && vars->_action && script->is_function("translate"))
1446 ressource = script->translate(vars->_action, vars->language) + " " +
1449 result->data = replace(result->data, ({ "{FILE}", "{USER}" }),
1451 this_user()->get_identifier() }));
1455 mapping response_too_large_file(object req)
1457 string html = error_page(
1458 "The amount of form/document data you are trying to "+
1459 "submit is<br/>too large. Use the FTP Protocol to upload files "+
1460 "larger than 20 MB.");
1461 return ([ "data": html, "type":"text/html", "error":413, ]);
1464 mapping response_notfound(string|int f, mapping vars)
1469 HTTP_DEBUG("The object %O was not found on server.", f);
1472 f = __request->not_query;
1474 xsl = OBJ("/stylesheets/notfound.xsl");
1475 if ( !objectp(xsl) || xsl->get_content_size() == 0 ) {
1476 return ([ "error":404,"data":"Unable for find "+f,"type":"text/plain"]);
1480 array tokens = path / "/";
1483 for ( int i = sizeof(tokens)-1; i >= 1; i-- )
1485 path = tokens[..i]*"/";
1486 cont = _fp->path_to_object(path);
1487 if ( objectp(cont) ) {
1488 catch(xsl = cont->query_attribute("xsl:notfound"));
1489 if ( !objectp(xsl) )
1490 xsl = OBJ("/stylesheets/notfound.xsl");
1496 f = "Object(#"+f+")";
1498 if ( !objectp(cont) )
1501 f = string_to_utf8(f);
1502 f = replace(f, "&", "&");
1503 f = replace(f, "%", "%25");
1504 f = replace(f, "<", "<");
1505 f = replace(f, ">", ">");
1507 "<?xml version='1.0' encoding='utf-8'?>\n"+
1508 "<error><actions/>"+
1509 "<message><![CDATA[The Document '"+f+"' was not found on the "+
1510 "Server.]]></message>\n"+
1511 "<orb>"+_fp->get_identifier()+"</orb>\n"+
1512 "<url>"+f+"</url>\n"+
1513 "<user>"+this_user()->get_identifier()+"</user>\n"+
1514 "<container>"+get_module("Converter:XML")->show(cont)+
1515 get_module("Converter:XML")->get_basic_access(cont)+
1519 html = run_xml(xml, xsl, vars);
1520 html += "\n<!--"+xml+"-->\n";
1529 mapping response_error(object obj, mapping vars, mixed err)
1531 string xml, html, btname;
1533 FATAL("err=%O\n", err);
1534 if ( objectp(err) && functionp(err->display) && err->display() == 1 )
1536 html = result_page(err->message(), "JavaScript:history.back();");
1537 return ([ "data": html, "type": "text/html", "error": 500, ]);
1540 FATAL("err=%s\n%O\n", err[0], err[1]);
1542 btname = "html_backtrace_"+errid;
1543 catch(vars->__internal->request_headers["authorization"] = "********");
1545 _Server->insert_backtrace(btname,
1546 "<html><body><b>Requesting "+
1547 __request->not_query+"</b>"+
1548 replace(err[0],({"<",">","\n" }),
1549 ({"<",">","<br/>"}))+
1550 backtrace_html(err[1]) + "<br/><br/>"+
1551 replace(sprintf("%O", vars), "\n", "<br/>")+
1553 object err_cont = OBJ("/documents/errors");
1554 xml = "<h3>An error occured while processing your request !</h3>";
1556 if ( objectp(err_cont) ) {
1557 mapping err_index = err_cont->query_attribute("language_index");
1558 if ( mappingp(err_index) ) {
1559 object errdoc = err_cont->get_object_byname(err_index[identify_language(__request->request_headers)]);
1560 if ( objectp(errdoc) )
1561 xml = errdoc->get_content();
1564 xml += "<br/> "+
1565 "Detailed Error Description: <a href='/backtraces/"+btname+"'>"+ _Server->query_config("web_server") +"/backtraces/"+btname+"</a><br /><br /> Error data:"+
1566 "<ul><li>ID: "+errid+"</li><li>Object: "+
1567 (objectp(obj) ? _FILEPATH->object_to_filename(obj)+
1568 "("+obj->get_object_id()+")":"none")+
1570 "<li>Report Bug: <a href=\"http://www.open-steam.org:8080/jira\">http://www.open-steam.org:8080/jira</a></li>"+
1572 object xsl = OBJ("/stylesheets/errors.xsl");
1576 "<?xml version='1.0' encoding='utf-8'?>\n"+
1577 "<error><actions/><message><![CDATA["+xml+"]]></message></error>";
1578 html = run_xml(xml, xsl, vars);
1579 return ([ "data": html, "type": "text/html", "error": 500, ]);
1582 return ([ "data": xml, "type": "text/plain", "error": 500, ]);
1585 mapping response_loginfailed(object obj, mapping vars)
1587 string message = "<html><head><title>Login failed</title></head><body><br />Login Failed !<br /><br /></body></html>";
1588 object xsl = OBJ("/stylesheets/login_failed.xsl");
1590 if ( !objectp(xsl) )
1591 return ([ "data": message , "type":"text/html", "error":500,]);
1594 "<?xml version='1.0' encoding='utf-8'?>\n"+
1595 "<error><actions/><message><![CDATA["+ message +
1596 "]]></message></error>";
1598 string html = run_xml(xml, xsl, vars);
1599 return ([ "data": html,
1600 "type": "text/html",
1605 void async_respond(object res, mixed data)
1607 if (res->webservice) {
1608 mapping callResults = res->userData->callResults;
1609 res->userData->callResults[res->userData->callNode]->result = data;
1610 foreach(values(callResults), mixed result) {
1611 if ( !stringp(result->result) )
1614 object envelope = soap.SoapEnvelope();
1615 soap.add_service_results(envelope, callResults);
1616 data = ([ "data": envelope->render(), "type": "text/xml", "error":200, ]);
1617 respond(__request, data);
1620 data = ([ "data": data, "type": res->mimetype, "error":200, ]);
1621 respond(__request, data);
1628 void respond(object req, mapping result)
1630 if ( objectp(req->my_fd) )
1631 req->my_fd->set_close_callback(close_connection);
1632 result->server = "sTeam Webserver";
1634 if ( __REAL_MAJOR__ == 7 && __REAL_MINOR__ <= 4 ) {
1635 if ( mappingp(result->extra_heads) )
1636 result->extra_heads->connection = "close";
1638 result->extra_heads = ([ "connection": "close" ]);
1640 if ( !result->error )
1641 result->error = 200;
1642 int length = result->length;
1644 if ( stringp(result->data) )
1645 length = strlen(result->data);
1646 else if ( objectp(result->file) )
1647 length = result->file->_sizeof();
1650 if ( stringp(result->data) )
1651 if ( length != strlen(result->data) )
1652 steam_error("Length mismatch in http!\n");
1654 // FIXME: hits should be logged seperately
1655 get_module("log")->log("http", LOG_LEVEL_ERROR, "%s %s - %s %s \"%s\" %d %d \"%s\" \"%s\"",
1656 __request->request_headers->host || "none",
1657 get_ip() || "unknown",
1658 (this_user()?this_user()->get_identifier():"-"),
1659 timelib.event_time(time()),
1660 __request->request_raw,
1663 __request->request_headers["referer"] || "-",
1664 __request->request_headers["user-agent"] || "-");
1665 req->response_and_finish(result);
1677 addr = __request->my_fd->query_address();
1680 addr = "no connected";
1682 if ( stringp(addr) )
1683 sscanf(addr, "%s %*d", ip);
1688 void close_connection()
1690 HTTP_DEBUG("HTTP: Closing Connection !");
1691 master()->unregister_user(this_object());
1692 if ( objectp(__request) ) {
1693 HTTP_DEBUG("HTTP: Closing request socket: %O", __request->my_fd);
1694 // catch(respond(__request, low_answer(200, "ok") ));
1695 catch(__request->my_fd->close());
1697 if ( objectp(__docfile) ) {
1698 HTTP_DEBUG("HTTP: Closing file... %O", __docfile);
1699 catch(__docfile->close());
1700 catch(destruct(__docfile));
1707 return "HTTP-Request(" + ::describe() +
1708 ",idle="+get_idle()+"s,closed="+is_closed()+")";
1711 int get_last_response()
1714 if ( objectp(__request) ) {
1715 HTTP_DEBUG("HTTP:Last Response is %O\n", __request->my_fd);
1717 if ( !__request->my_fd ||
1718 ( functionp(__request->my_fd->is_open) &&
1719 !__request->my_fd->is_open() == 0) )
1721 HTTP_DEBUG("HTTP: closed connection ...\n");
1728 if ( objectp(__docfile) )
1729 return __docfile->get_last_response();
1733 string get_vhost(){ return __request->request_headers->host; }
1734 string get_socket_name() { return "http"; }
1735 int is_closed() { return __finished; }
1736 int get_client_features() { return 0; }
1737 string get_client_class() { return "http"; }
1738 string get_identifier() { return "http"; }
1739 object get_object() { return this_object(); }
1740 int get_idle() { return time() - __touch; }