http._pike
Go to the documentation of this file.
1 /* Copyright (C) 2000-2010 Thomas Bopp, Thorsten Hampel, Ludger Merkens
2  *
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.
7  *
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.
12  *
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
16  *
17  * $Id: http.pike,v 1.5 2010/08/20 20:42:25 astra Exp $
18  */
19 inherit "coal/login";
20 #include <macros.h>
21 #include <database.h>
22 #include <classes.h>
23 #include <attributes.h>
24 #include <access.h>
25 #include <roles.h>
26 #include <config.h>
27 #include <client.h>
28 class http : public login{
29 public:
30 
31 
32 
33 
34 import httplib;
35 
36 #define DEBUG_HTTP
37 
38 #ifdef DEBUG_HTTP
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)
41 #else
42 #define HTTP_DEBUG(s, args...)
43 #endif
44 
45 mixed debug_http(string type, string s, mixed ... args)
46 {
47  werror(sprintf("%s: %s\n", type, s), @args);
48  return get_module("log")->log_debug("http", s, args);
49 }
50 
51 object _fp;
52  object __notfound;
53  int __finished;
54  bool __admin_port;
55  object __request; // the saved request object
56  string not_query; // initial not_query, saved because of rewrite
57 
58  int __toread; // how many bytes to go to read body
59  string __body; // the request body
60  object __upload;
61  object __tasks;
62  object __cgi;
63  int __touch;
64  int __newfile;
65  object __docfile;
66  int isWebDAV = 0;
67 
68 constant automatic_tasks = ({ "create_group_exit", "remove_group_exit" });
69 
70 void create(object fp, bool admin_port)
71 {
72  _fp = fp;
73  __finished = 0;
74  __admin_port = admin_port;
75  __tasks = get_module("tasks");
76  __cgi = get_module("cgi");
77  __touch = time();
78 }
79 
80 int check_notprecondition(object obj)
81 {
82  string etag;
83  string ifheader = __request->request_headers["if-none-match"];
84  if ( !stringp(ifheader) )
85  return 1;
86  if ( objectp(obj) )
87  etag = obj->get_etag();
88 
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 )
93  return 0;
94  return 1;
95 }
96 
97 
98 int check_precondition(object obj)
99 {
100  string etag;
101  string ifheader = __request->request_headers["if-match"];
102  if ( !stringp(ifheader) )
103  return 1;
104  if ( objectp(obj) )
105  etag = obj->get_etag();
106 
107  ifheader = String.trim_all_whites(ifheader);
108 
109  sscanf(ifheader, "\"%s\"", ifheader);
110 
111  array entitytags = ifheader / ",";
112  if ( search(entitytags, "*") >= 0 || search(entitytags, etag) >= 0 )
113  return 1;
114  return 0;
115 }
116 
117 /**
118  * authenticate with server. Normal http authentication
119  *
120  * @param string basic - the auth in base64 encoding
121  * @return 0 if the authentication was successfull, otherwise error codes
122  */
123 int authenticate(string basic, void|object obj)
124 {
125  string auth, user, pass;
126  object userobj;
127 
128  auth = __request->cookies->steam_auth;
129  if ( stringp(auth) && strlen(auth) > 0 && auth != "0" )
130  {
131  auth = Protocols.HTTP.Server.http_decode_string(auth);
132  basic = auth;
133  __request->variables["auth"] = "cookie";
134  __request->cookies->steam_auth = "****";
135 
136  }
137  else {
138  __request->variables["auth"] = "http";
139  }
140 
141  if ( stringp(basic) ) {
142  string method;
143  if ( sscanf(basic, "%s %s", method, auth) == 0 )
144  auth = basic;
145  else
146  switch ( lower_case(method) )
147  {
148  case "negotiate":
149  login_user(_GUEST);
150  return 0;
151  case "basic":
152  default:
153  break;
154  }
155 
156  auth = MIME.decode_base64(auth);
157  auth = string_to_utf8(auth);
158  sscanf(auth, "%s:%s", user, pass);
159 
160  userobj = get_module("auth")->authenticate(user,pass);
161  if ( !objectp(userobj) )
162  return 401;
163  login_user(userobj);
164  return 0;
165  }
166 
167  if ( !__admin_port )
168  login_user(_GUEST);
169  else if ( objectp(obj) && obj->query_sanction(_GUEST) & SANCTION_READ )
170  login_user(_GUEST);
171  else
172  return 401;
173  return 0;
174 }
175 
176 /**
177  * This thread is used for downloading stuff.
178  *
179  * @param object req - the request object.
180  * @param function dataf - the function to call to read data
181  */
182 protected:
183  void download_thread(object req, function dataf)
184 {
185  string str;
186  while ( stringp(str=dataf()) ) {
187  int sz = strlen(str);
188  int written = 0;
189  while ( written < sz ) {
190  written += req->my_fd->write(str[written..]);
191  }
192  }
193 }
194 
195 public:
196 
197 
198 protected:
199  int handle_cgi(object obj, mapping vars)
200 {
201  object file;
202  float ga;
203 
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);
209  pipe->input(file);
210  pipe->output(__request->my_fd);
211  return 0;
212 }
213 
214 public:
215 
216 int is_dav()
217 {
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 )
222  return 1;
223  }
224  return isWebDAV; // heuristic from webdav.pike if PROPFIND is called
225 }
226 
227 int exchange_links(object obj)
228 {
229  if (!objectp(obj))
230  return 0;
231  object env = obj->get_environment();
232  if (objectp(env) && env->query_attribute(CONT_EXCHANGE_LINKS))
233  return 1;
234  return 0;
235 }
236 
237 /**
238  * Handle the GET method of the HTTP protocol
239  *
240  * @param object obj - the object to get
241  * @param mapping vars - the variable mapping
242  * @return result mapping with data, type, length
243  */
244 mapping handle_GET(object obj, mapping vars)
245 {
246  mapping result = ([ ]);
247  mapping extra = ([ ]); // extra headers
248  object _obj = obj; // if some script is used to display another object
249  int modified;
250 
251  // redirection
252  if ( obj == get_module("home") && _fp == get_module("filepath:url") )
253  return low_answer(302, "Found",
254  ([ "Location": _Server->ssl_redirect("/home/"), ]));
255 
256 
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.
260  if ( !objectp(obj) )
261  return response_notfound(__request->not_query, vars);
262  if ( vars->object )
263  _obj = find_object((int)vars->object);
264  if ( !objectp(_obj) )
265  return response_notfound(vars->object, vars);
266 
267 
268  HTTP_DEBUG("GET " + obj->describe() + "\n%O\n", vars);
269 
270  string mimetype = obj->query_attribute(DOC_MIME_TYPE) || "";
271 
272  if ( obj->get_object_class() & CLASS_SCRIPT )
273  {
274  result = handle_POST(obj, vars);
275  if ( !mappingp(result) )
276  return result;
277  }
278  else if ( vars->type == "execute" &&
279  obj->get_object_class() & CLASS_DOCLPC )
280  {
281  result = handle_POST(obj, vars);
282  }
283  else if ( vars->type == "content" &&
284  ( obj->get_object_class() & CLASS_DOCXML ||
285  mimetype == "text/xml" ) )
286  {
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" )
293  method = "html";
294  result->type = "text/" + method;
295  }
296  else if ( vars->source == "transform" ) {
297  vars->object = (string)obj->get_object_id();
298  mixed res = show_object(obj, vars);
299  if ( mappingp(res) )
300  return res;
301  }
302  else {
303  result->data = xml;
304  result->type = "text/xml";
305  }
306  }
307  else if ( vars->type == "content" &&
308  obj->get_object_class() & CLASS_DOCEXTERN )
309  {
310  result->data = redirect(obj->query_attribute(DOC_EXTERN_URL), 0);
311  result->type = "text/html";
312  }
313  else if ( vars->type == "content" &&
314  obj->get_object_class() & CLASS_DOCUMENT )
315  {
316  object xsl = obj->query_attribute("xsl:public");
317  if ( search(mimetype, "text") >= 0 &&
318  objectp(xsl) &&
319  !is_dav() &&
320  obj->query_attribute("xsl:use_public") )
321  {
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";
331  else
332  result->type = "text/html";
333 
334  result->encoding = xsl->get_encoding();
335  }
336  else if ( objectp(__cgi) && __cgi->get_program(mimetype) )
337  {
338  handle_cgi(obj, vars);
339  return 0;
340  }
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");
347 
348  if ( objectp(wikixsl) ) {
349  result->data = run_xml(obj, wikixsl, vars);
350  if ( stringp(vars->source) )
351  result->type = "text/xml";
352  else
353  result->type = "text/html";
354 
355  result->encoding = wikixsl->get_encoding();
356  }
357  else {
358  if ( stringp(vars->source) )
359  result->data = obj->get_content();
360  else
361  result->data = wikimod->wiki_to_html(obj, _fp, vars);
362  result->type = "text/html";
363  result->encoding = "utf-8";
364  }
365  }
366  else {
367  result->type = "text/plain";
368  result->data = obj->get_content();
369  }
370  }
371  else {
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");
375  result->file = doc;
376  result->type = obj->query_attribute(DOC_MIME_TYPE);
377  __docfile = doc;
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;
384  }
385  if ( mimetype == "text/html" )
386  result->tags = find_tags(OBJ("/tags"));
387  }
388  else {
389  vars->object = (string)obj->get_object_id();
390  mixed res = show_object(obj, vars);
391  if ( mappingp(res) )
392  return res;
393 
394  result->data = res;
395  result->length = strlen(result->data);
396  result->type = "text/html";
397  }
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());
403  }
404  extra->ETag = "\""+obj->get_etag() + "\"";
405 
406  if ( !mappingp(result->extra_heads) )
407  result->extra_heads = extra;
408  else
409  result->extra_heads |= extra;
410  if ( !result->error )
411  result->error = 200;
412  return result;
413 }
414 
415 protected:
416  void finish_pipe()
417 {
418  __request->finish();
419 }
420 
421 public:
422 
423 /**
424  * handle the http POST method. Also it might be required to
425  * read additional data from the fd.
426  *
427  * @param object obj - script to post data to
428  * @param mapping m - variables
429  * @return result mapping
430  */
431 mapping handle_POST(object obj, mapping m)
432 {
433  mapping result = ([ ]);
434  //HTTP_DEBUG("POST(%O)", m);
435  // need to read the request yourself...
436  if ( !objectp(obj) )
437  return response_notfound(__request->not_query, m);
438 
439 
440  if (objectp(__cgi) &&
441  __cgi->get_program(obj->query_attribute(DOC_MIME_TYPE)))
442  {
443  handle_cgi(obj, m);
444  return 0;
445  }
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, ]);
450  }
451  m->fp = _fp;
452  mixed res = ([ ]);
453 
454  if ( obj->get_object_class() & CLASS_DOCLPC ) {
455  object script;
456  mixed err = catch(script=obj->provide_instance());
457  mixed script_errors = obj->get_errors();
458 
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";
463  }
464  else if ( err ) {
465  FATAL("Error handling script: %O, %O", err[0], err[1]);
466  }
467  else if ( objectp(script) ) {
468  res = script->execute(m);
469  }
470  }
471  else if ( obj->get_object_class() & CLASS_WEBSERVICE ) {
472  werror("WEBSERVICE\n%s\n", __request->raw);
473  if ( m->wsdl ) {
474  res->data = obj->show_wsdl();
475  res->type = "text/xml";
476  }
477  else if ( !stringp(__body) || strlen(__body) == 0 ) {
478  res = obj->execute(m);
479  }
480  else {
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());
486 
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)) {
493  res = fcall->result;
494  res->userData = ([ "callResults": callResults, "callNode": callNode]);
495  res->webservice = 1;
496  }
497  }
498  if (!objectp(res)) {
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";
504  }
505  }
506  }
507  else
508  res = obj->execute(m);
509 
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);
514  return 0;
515  }
516  else if ( mappingp(res) ) {
517  return res;
518  }
519  else if ( intp(res) ) {
520  if ( res == -1 ) {
521  result->error = 401;
522  result->type = "text/plain";
523  result->extra_heads =
524  ([ "WWW-Authenticate": "basic realm=\"steam\"", ]);
525  return result;
526  }
527  }
528  else if ( arrayp(res) ) {
529  if ( sizeof(res) == 2 )
530  [ result->data, result->type ] = res;
531  else
532  [ result->data, result->type, result->modified ] = res;
533  }
534  else {
535  result->data = res;
536  result->type = "text/html";
537  }
538  return result;
539 }
540 
541 mapping handle_OPTIONS(object obj, mapping vars)
542 {
543  string allow = "";
544  mapping result = low_answer(200, "OK");
545 
546 
547  foreach ( indices(this_object()), string ind) {
548  string cmd_name;
549  if ( sscanf(ind, "handle_%s", cmd_name) > 0 )
550  allow += cmd_name + ", ";
551  }
552 
553  result->extra_heads = ([
554  "Allow": allow,
555  ]);
556  return result;
557 }
558 
559 mapping handle_PUT(object obj, mapping vars)
560 {
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
564  mapping result;
565  if ( obj->get_object_class() & CLASS_SCRIPT )
566  {
567  result = handle_POST(obj, vars);
568  if ( !mappingp(result) )
569  return result;
570  }
571  else if ( vars->type == "execute" &&
572  obj->get_object_class() & CLASS_DOCLPC )
573  {
574  result = handle_POST(obj, vars);
575  }
576  else return handle_webdav_PUT(obj, vars);
577 
578  return result;
579 }
580 
581 mapping handle_webdav_PUT(object obj, mapping vars)
582 {
583  __newfile = 0;
584 
585  if ( !check_precondition(obj) )
586  return low_answer(412, "Precondition failed");
587 
588  if ( !objectp(obj) ) {
589  string fname = __request->not_query;
590  __newfile = 1;
591  obj = get_factory(CLASS_DOCUMENT)->execute( ([ "url":fname, ]) );
592  }
593  // create Stdio.File wrapper class for a steam object
594  string token = webdavlib.get_opaquelocktoken(__request->request_headers->if);
595 
596  __upload = ((program)"/kernel/DocFile")(obj, "wct", vars, "http", token);
597  __toread = (int)__request->request_headers["content-length"];
598 
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!");
602  }
603  if ( strlen(__request->body_raw) > 0 ) {
604  __toread -= strlen(__request->body_raw);
605  __upload->write(__request->body_raw);
606  }
607  if ( __toread > 0 ) {
608  __request->my_fd->set_nonblocking(read_put_data,0,finish_put);
609  }
610  else {
611  __upload->close();
612  return low_answer((__newfile ? 201:200), "created");
613  }
614  return 0;
615 }
616 
617 protected:
618  void read_put_data(mixed id, string data)
619 {
620  __upload->write(data);
621  __toread -= strlen(data);
622  __touch = time();
623  if ( __toread <= 0 )
624  finish_put(0);
625 }
626 
627 public:
628 
629 protected:
630  void finish_put(mixed id)
631 {
632  __upload->close();
633  __upload = 0;
634  __finished = 1;
635  __touch = time();
636  HTTP_DEBUG("Finish HTTP PUT !");
637  if ( __newfile )
638  respond(__request, low_answer(201, "created") );
639  else
640  respond(__request, low_answer(200, "ok") );
641 }
642 
643 public:
644 
645 
646 mapping handle_MKDIR(object obj, mapping vars)
647 {
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");
653  }
654  object inter = _fp->path_to_object(dirname(fname));
655  if ( !objectp(inter) )
656  return low_answer(409, "Missing intermediate");
657 
658  if ( !check_precondition(obj) )
659  return low_answer(412, "Precondition failed");
660 
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.");
665  }
666  return low_answer(403, "forbidden");
667 }
668 
669 mapping handle_DELETE(object obj, mapping vars)
670 {
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
675  mapping result;
676  if ( obj->get_object_class() & CLASS_SCRIPT )
677  {
678  result = handle_POST(obj, vars);
679  if ( !mappingp(result) )
680  return result;
681  }
682  else if ( vars->type == "execute" &&
683  obj->get_object_class() & CLASS_DOCLPC )
684  {
685  result = handle_POST(obj, vars);
686  }
687  else return handle_webdav_DELETE(obj, vars);
688 
689  return result;
690 }
691 
692 mapping handle_webdav_DELETE(object obj, mapping vars)
693 {
694  if ( !objectp(obj) )
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");
701 }
702 
703 
704 mapping handle_HEAD(object obj, mapping vars)
705 {
706  if ( !objectp(obj) )
707  return response_notfound(__request->not_query, vars);
708 
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 + ">", ]);
716  return result;
717 }
718 
719 
720 
721 /**
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.
724  *
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
728  */
729 mapping read_body(object req, int len)
730 {
731  if ( len == 0 )
732  return ([ ]);
733 
734  HTTP_DEBUG("trying to read length of body = %O", len);
735  if ( stringp(req->body_raw) )
736  len -= strlen(req->body_raw);
737 
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" )
741 // return ([ ]);
742 
743  __toread = len;
744  __body = "";
745  if ( len > 0 )
746  return 0;
747  __body = req->body_raw;
748 
749  string content_type = req->request_headers["content-type"] || "";
750 
751  if ( strlen(__body) == 0 )
752  return ([ ]);
753 
754  content_type = lower_case(content_type);
755  if (search(content_type, "multipart/form-data") >= 0 )
756  return parse_multipart_form_data(req, __body);
757  else
758  return ([ "__body": __body, ]);
759 }
760 
761 /**
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.
765  *
766  * @param mixed id - id object
767  * @param string data - the body data
768  */
769 void read_body_data(mixed id, string data)
770 {
771  if ( stringp(data) ) {
772  __body += 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);
778  __body = "";
779  http_request(__request);
780  }
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");
785  }
786  }
787 }
788 
789 /**
790  * Call a command in the server. Return the result of the call or
791  * if no function was found 501.
792  *
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
797  */
798 protected:
799  mapping call_command(string cmd, object obj, mapping vars)
800 {
801  mapping result = ([ ]);
802 
803  if ( !stringp(vars->host) )
804  vars->host = _Server->get_server_name();
805 
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] != '/' )
810  {
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";
816  return result;
817  }
818  HTTP_DEBUG("HTTP: " + __request->not_query + " (%O)", get_ip());
819 
820  function call = this_object()["handle_"+cmd];
821  vars->__internal->request_method = cmd;
822 
823  if ( functionp(call) ) {
824  result = call(obj, vars);
825  }
826  else if ( obj->get_object_class() & CLASS_SCRIPT )
827  {
828  result = handle_POST(obj, vars);
829  if ( !mappingp(result) )
830  return result;
831  }
832  else {
833  result->error = 501;
834  result->data = "Not implemented";
835  }
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"],
840  ]);
841  return result;
842 }
843 
844 public:
845 
846 /**
847  * Handle a http request within steam.
848  *
849  * @param object req - the request object
850  */
851 mapping run_request(object req)
852 {
853  mapping result = ([ ]);
854  mixed err;
855  string host = (req->request_headers->host || _Server->get_server_name());
856 
857  //HTTP_DEBUG("run_request(%O)", req->request_headers);
858  //HTTP_DEBUG("run_request(): host = %O,%O", req->request_headers->host, host);
859 
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")) )
864  {
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!"+
868  "</p><p>"+
869  "To be able to get anything working on this web port, you need to "+
870  "install the web package."+
871  "</p><p><strong>"+
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!"+
876  "</strong></p>"+
877  "</body></html>";
878  result->error = 200;
879  result->type = "text/html";
880  return result;
881  }
882 
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);
888 
889 
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 ) {
894  object u;
895  err = catch {
896  u = get_module("auth")->authenticate(req->variables->user,
897  req->variables->password);
898  };
899  if ( err || !objectp(u) )
900  return response_loginfailed(u, req->variables);
901 
902  result->extra_heads =
903  set_auth_cookie(req->variables->user, req->variables->password);
904  // redirect !
905  string re = req->variables->area;
906  if ( !stringp(re) )
907  re = "/";
908  result->data = redirect(re, 0);
909  result->type = "text/html";
910  result->error = 200;
911  result->extra_heads |= ([ "Pragma":"No-Cache",
912  "Cache-Control":"No-Cache" ]);
913  }
914  else {
915  object login = _fp->path_to_object("/documents/login.html");
916  if ( objectp(login) )
917  result = ([ "data": login->get_content(), "type": "text/html", ]);
918  else
919  result = ([ "data": "Access denied", "error": 401, ]);
920  }
921  return result;
922  }
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=/",
926  ]);
927  object logout = _fp->path_to_object("/documents/logout.html");
928  if ( objectp(logout) )
929  result += ([ "data": logout->get_content(), "type": "text/html", ]);
930  else
931  result += ([ "data": "You are logged out!", "type": "text/html", ]);
932  return result;
933  }
934 
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);
937 
938  if (!objectp(obj) && host[..3] == "www.")
939  return low_answer(301, "Found",
940  ([ "Location": (__admin_port ? "https://" : "http://" )+host[4..]+req->full_query ]));
941 
942  if ( !objectp(obj) )
943  obj = _fp->path_to_object(req->not_query, 1);
944 
945  mapping m = req->variables;
946 
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";
954  else
955  m->type = "content";
956  }
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();
963  // }
964  //}
965 
966  object _obj = obj;
967  if ( m->object )
968  _obj = find_object((int)m->object);
969 
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()) )
980  {
981  HTTP_DEBUG("Not modified.");
982  return ([ "error":304,
983  "data":"not modified",
984  "extra_heads": (["ETag": "\""+_obj->get_etag()+"\"", ]),
985  ]);
986  }
987 
988  if ( !(_obj->get_object_class() & CLASS_SCRIPT) ) {
989  if ( !check_precondition(_obj) ||
990  !check_notprecondition(_obj) )
991  return low_answer(304, "not modified");
992  }
993  }
994 
995  int authn = authenticate(req->request_headers->authorization, obj);
996  if ( authn > 0 )
997  {
998  login_user(_GUEST);
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 += ([
1005  "Set-Cookie":
1006  "steam_auth=;expires=Thu, 01-Jan-70 00:00:01 GMT; path=/",
1007  ]);
1008 
1009  result->error=authn;
1010  return result;
1011  }
1012 
1013 
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;
1018 
1019  m->__internal = ([
1020  "request_headers": req->request_headers,
1021  "client": ({ "Mozilla", }),
1022  "full_query": req->full_query,
1023  "raw": req->raw
1024  ]);
1025  m->referer = referer;
1026  m->interface = (__admin_port ? "admin" : "public" );
1027  if (req->request_headers["content-type"])
1028  {
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;
1035  }
1036 
1037  float tt = gauge {
1038  err = catch ( result = call_command(req->request_type, obj, m) );
1039  };
1040  tt = tt*1000.0;
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);
1047 
1048  if ( mappingp(result) ) {
1049  string encoding = result->encoding;
1050  if ( !stringp(encoding) && objectp(obj) )
1051  encoding = obj->query_attribute(DOC_ENCODING);
1052 
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 ??
1056  float t;
1057 
1058  if ( result->type == "text/html" ) {
1059  t = gauge {
1060  if ( result->file ) {
1061  result->data = result->file->read(result->file->_sizeof());
1062  destruct(result->file);
1063  }
1064  if ( objectp(obj) && obj->get_object_class() & CLASS_CONTAINER )
1065  m["env"] = obj;
1066  else
1067  m["env"]=_fp->path_to_object(dirname(req->not_query),1);
1068 
1069  m["fp"] = _fp;
1070  m["obj"] = obj;
1071 
1072  result->data = htmllib.parse_rxml(result->data,
1073  m,
1074  result->tags,
1075  encoding);
1076  result->length = strlen(result->data);
1077  };
1078  }
1079  }
1080  }
1081  if (mappingp(result) && stringp(result->type)&&stringp(result->encoding))
1082  result->type += "; charset="+result->encoding;
1083  if ( err )
1084  {
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",
1089  err[0], err[1]);
1090  }
1091  else {
1092  FATAL(sprintf("error:\n%O\n%O\n", err[0], err[1]));
1093  result = response_error(obj, m, err);
1094  }
1095  }
1096  return result;
1097 }
1098 
1099 string rewrite_url(string url, mapping headers)
1100 {
1101  if ( !stringp(headers->host) )
1102  return url;
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;
1110  }
1111  return url;
1112 }
1113 
1114 /**
1115  * A http request is incoming. Convert the req (Request) object
1116  * into a mapping.
1117  *
1118  * @param object req - the incoming request.
1119  */
1120 void http_request(object req)
1121 {
1122  mapping result;
1123  int len;
1124 
1125  __request = req;
1126  __touch = time();
1127 
1128 
1129  // read body always...., if body is too large abort.
1130  len = (int)req->request_headers["content-length"];
1131 
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);
1137  return;
1138  }
1139  req->my_fd->set_blocking();
1140 
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();
1145 
1146  set_this_user(this_object());
1147 
1148  mixed err = catch {
1149  req->variables |= body_variables;
1150  result = run_request(req);
1151  };
1152 
1153  if ( err != 0 ) {
1154  mixed ie = catch {
1155  result = response_error(0, req->variables, err);
1156  };
1157 
1158  if ( ie ) {
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,
1162  "data":
1163  "Internal Server Error - contact your administrator",
1164  "type": "text/html", ]);
1165  }
1166  }
1167  if (slow) {
1168  tt = get_time_millis() - tt;
1169  loaded_objects = master()->get_in_memory() - loaded_objects;
1170  saved_objects = _Database->get_saves() - saved_objects;
1171 
1172  if ( tt > slow )
1173  MESSAGE("Request %O took %d ms, %d objects loaded, %d saved",
1174  req->request_raw,
1175  tt,
1176  loaded_objects,
1177  saved_objects);
1178  }
1179 
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 );
1184  __finished = 1;
1185  }
1186  set_this_user(0);
1187 }
1188 
1189 /**
1190  * Get the appropriate stylesheet for a user to display obj.
1191  *
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.
1196  */
1197 object get_xsl_stylesheet(object user, object obj, mapping vars)
1198 {
1199  mapping xslMap = obj->query_attribute("xsl:content");
1200  object xsl;
1201 
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");
1207  return xsl;
1208  }
1209  return httplib.get_xsl_stylesheet(user, obj, vars);
1210 }
1211 
1212 /**
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
1215  * a html page.
1216  *
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
1220  */
1221 string|int run_tasks(object user, mapping vars, mapping client_map)
1222 {
1223  mixed tasks = __tasks->get_tasks(user);
1224  string html;
1225  int todo = 0;
1226 
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)->
1232  get_object_id();
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/>";
1241 
1242  foreach(tasks, object t) {
1243  mixed err = catch {
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"]] +
1249  "</SPAN><br/>\n";
1250  todo++;
1251  }
1252  else {
1253  if ( t->obj == __tasks || t->obj ==
1254  _FILEPATH->path_to_object("/scripts/browser.pike") )
1255  __tasks->run_task(t->tid); // risky ?
1256  else
1257  FATAL("Cannot run unauthorized Task: " +
1258  t->func + " inside %O", t->obj);
1259  }
1260  };
1261  if ( err ) {
1262  FATAL("Error while executing task: %O\n%O", err[0], err[1]);
1263  }
1264  }
1265  __tasks->tasks_done(user);
1266  html += "<br/><br/><input type='submit' value='ok'/></form>";
1267  if ( todo > 0 )
1268  return html;
1269  }
1270  return 0;
1271 }
1272 
1273 
1274 /**
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
1278  * port.
1279  *
1280  * @param object obj - the object to display
1281  * @param mapping vars - variables mapping
1282  * @return string|mapping result of transformation
1283  */
1284 string|int|mapping show_object(object obj, mapping vars)
1285 {
1286  string html;
1287 
1288  object user = this_user();
1289 
1290  HTTP_DEBUG("show_object("+obj->describe()+")");
1291 
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
1296  {
1297  mapping result = ([
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;
1303  return result;
1304  }
1305 
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);
1310  }
1311 
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 )
1316  {
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);
1328  }
1329  }
1330  }
1331  }
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);
1339  }
1340  }
1341 
1342  _SECURITY->check_access(obj, user, SANCTION_READ,ROLE_READ_ALL, false);
1343 
1344  if ( obj->get_object_class() & CLASS_ROOM && !user->contains(obj, true) )
1345  {
1346  // check for move clients !
1347  if ( !(user->get_status() & CLIENT_FEATURES_MOVE) )
1348  user->move(obj);
1349  else
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()));
1354  }
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);
1358  if ( objectp(wr) )
1359  user->move(wr);
1360  }
1361  }
1362 
1363  if ( __admin_port ) {
1364  mixed result = run_tasks(user, vars, client_map);
1365  if ( stringp(result) )
1366  return result_page(result, "no");
1367  }
1368 
1369 
1370  // PDA detection - use different stylesheet (xsl:PDA:content, etc)
1371  if ( client_map["xres"] == "240" )
1372  vars->type = "PDA:" + vars->type;
1373 
1374  vars |= client_map;
1375 
1376  object xsl = get_xsl_stylesheet(user, obj, vars);
1377  if ( !objectp(xsl) )
1378  error("Failed to find xsl stylesheet !");
1379 
1380  HTTP_DEBUG("Using stylesheet: "+xsl->describe());
1381  html = run_xml(obj, xsl, vars);
1382 
1383  if ( !stringp(html) )
1384  error("Failed to process XSL (Stylesheet is " +
1385  _FILEPATH->object_to_filename(xsl) + ")");
1386 
1387  if ( vars->source == "true" )
1388  return ([
1389  "data" : html,
1390  "length" : strlen(html),
1391  "type" : "text/xml", ]);
1392 
1393  string method = "text/"+xsl->get_method();
1394 
1395  return ([ "data": html,
1396  "length": strlen(html),
1397  "type": "text/html",
1398  "tags": find_tags(xsl),
1399  "encoding": xsl->get_encoding(),
1400  ]);
1401 }
1402 
1403 
1404 mapping response_noaccess(object obj, mapping vars)
1405 {
1406  mapping result;
1407  object script;
1408  object noaccess = OBJ("/documents/access.xml");
1409  vars->type = "content";
1410  result = handle_GET(noaccess, vars);
1411 
1412 
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;
1416  else
1417  result->error = (__admin_port ? 403 :
1418  ( _Server->query_config("secure_credentials") ?
1419  403 : 401 ) );
1420 
1421  if ( result->error == 401 )
1422  result->extra_heads =
1423  ([ "WWW-Authenticate": "basic realm=\"steam\"", ]);
1424 
1425  result->type = "text/html";
1426  string ressource = __request->not_query;
1427  if ( objectp(obj) && obj->get_object_class() & CLASS_SCRIPT ) {
1428  script = obj;
1429  if ( vars->id )
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);
1436  }
1437  }
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);
1443  }
1444  if ( !stringp(ressource) ) ressource = "0";
1445  if ( objectp(script) && vars->_action && script->is_function("translate"))
1446  ressource = script->translate(vars->_action, vars->language) + " " +
1447  ressource;
1448 
1449  result->data = replace(result->data, ({ "{FILE}", "{USER}" }),
1450  ({ ressource,
1451  this_user()->get_identifier() }));
1452  return result;
1453 }
1454 
1455 mapping response_too_large_file(object req)
1456 {
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, ]);
1462 }
1463 
1464 mapping response_notfound(string|int f, mapping vars)
1465 {
1466  string html = "";
1467  object xsl, cont;
1468 
1469  HTTP_DEBUG("The object %O was not found on server.", f);
1470 
1471  if ( zero_type(f) )
1472  f = __request->not_query;
1473 
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"]);
1477  }
1478  if ( stringp(f) ) {
1479  string path = f;
1480  array tokens = path / "/";
1481 
1482 
1483  for ( int i = sizeof(tokens)-1; i >= 1; i-- )
1484  {
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");
1491  break;
1492  }
1493  }
1494  }
1495  else {
1496  f = "Object(#"+f+")";
1497  }
1498  if ( !objectp(cont) )
1499  cont = _ROOTROOM;
1500 
1501  f = string_to_utf8(f);
1502  f = replace(f, "&", "&amp;");
1503  f = replace(f, "%", "%25");
1504  f = replace(f, "<", "&lt;");
1505  f = replace(f, ">", "&gt;");
1506  string xml =
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)+
1516  "</container>\n"+
1517  "</error>";
1518 
1519  html = run_xml(xml, xsl, vars);
1520  html += "\n<!--"+xml+"-->\n";
1521  mapping result = ([
1522  "error":404,
1523  "data":html,
1524  "type":"text/html",
1525  ]);
1526  return result;
1527 }
1528 
1529 mapping response_error(object obj, mapping vars, mixed err)
1530 {
1531  string xml, html, btname;
1532 
1533  FATAL("err=%O\n", err);
1534  if ( objectp(err) && functionp(err->display) && err->display() == 1 )
1535  {
1536  html = result_page(err->message(), "JavaScript:history.back();");
1537  return ([ "data": html, "type": "text/html", "error": 500, ]);
1538  }
1539 
1540  FATAL("err=%s\n%O\n", err[0], err[1]);
1541  int errid = time();
1542  btname = "html_backtrace_"+errid;
1543  catch(vars->__internal->request_headers["authorization"] = "********");
1544  object bt =
1545  _Server->insert_backtrace(btname,
1546  "<html><body><b>Requesting "+
1547  __request->not_query+"</b>"+
1548  replace(err[0],({"<",">","\n" }),
1549  ({"&lt;","&gt;","<br/>"}))+
1550  backtrace_html(err[1]) + "<br/><br/>"+
1551  replace(sprintf("%O", vars), "\n", "<br/>")+
1552  "</body></html>");
1553  object err_cont = OBJ("/documents/errors");
1554  xml = "<h3>An error occured while processing your request !</h3>";
1555 
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();
1562  }
1563  }
1564  xml += "<br/>&#160;"+
1565  "Detailed Error Description: <a href='/backtraces/"+btname+"'>"+ _Server->query_config("web_server") +"/backtraces/"+btname+"</a><br /><br />&#160;Error data:"+
1566  "<ul><li>ID: "+errid+"</li><li>Object: "+
1567  (objectp(obj) ? _FILEPATH->object_to_filename(obj)+
1568  "("+obj->get_object_id()+")":"none")+
1569  "</li>"+
1570  "<li>Report Bug: <a href=\"http://www.open-steam.org:8080/jira\">http://www.open-steam.org:8080/jira</a></li>"+
1571  "</ul>";
1572  object xsl = OBJ("/stylesheets/errors.xsl");
1573  if (xsl)
1574  {
1575  xml =
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, ]);
1580  }
1581  else
1582  return ([ "data": xml, "type": "text/plain", "error": 500, ]);
1583 }
1584 
1585 mapping response_loginfailed(object obj, mapping vars)
1586 {
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");
1589 
1590  if ( !objectp(xsl) )
1591  return ([ "data": message , "type":"text/html", "error":500,]);
1592 
1593  string xml =
1594  "<?xml version='1.0' encoding='utf-8'?>\n"+
1595  "<error><actions/><message><![CDATA["+ message +
1596  "]]></message></error>";
1597 
1598  string html = run_xml(xml, xsl, vars);
1599  return ([ "data": html,
1600  "type": "text/html",
1601  "error": 500, ]);
1602 }
1603 
1604 protected:
1605  void async_respond(object res, mixed data)
1606 {
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) )
1612  return;
1613  }
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);
1618  }
1619  else {
1620  data = ([ "data": data, "type": res->mimetype, "error":200, ]);
1621  respond(__request, data);
1622  }
1623 }
1624 
1625 public:
1626 
1627 protected:
1628  void respond(object req, mapping result)
1629 {
1630  if ( objectp(req->my_fd) )
1631  req->my_fd->set_close_callback(close_connection);
1632  result->server = "sTeam Webserver";
1633 
1634  if ( __REAL_MAJOR__ == 7 && __REAL_MINOR__ <= 4 ) {
1635  if ( mappingp(result->extra_heads) )
1636  result->extra_heads->connection = "close";
1637  else
1638  result->extra_heads = ([ "connection": "close" ]);
1639  }
1640  if ( !result->error )
1641  result->error = 200;
1642  int length = result->length;
1643  if ( !length ) {
1644  if ( stringp(result->data) )
1645  length = strlen(result->data);
1646  else if ( objectp(result->file) )
1647  length = result->file->_sizeof();
1648  }
1649  else {
1650  if ( stringp(result->data) )
1651  if ( length != strlen(result->data) )
1652  steam_error("Length mismatch in http!\n");
1653  }
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,
1661  result->error,
1662  length,
1663  __request->request_headers["referer"] || "-",
1664  __request->request_headers["user-agent"] || "-");
1665  req->response_and_finish(result);
1666  req = 0;
1667 }
1668 
1669 public:
1670 
1671 string|int get_ip()
1672 {
1673  mixed err;
1674  string addr;
1675 
1676  err = catch {
1677  addr = __request->my_fd->query_address();
1678  };
1679  if ( err != 0 )
1680  addr = "no connected";
1681  string ip = 0;
1682  if ( stringp(addr) )
1683  sscanf(addr, "%s %*d", ip);
1684 
1685  return ip;
1686 }
1687 
1688 void close_connection()
1689 {
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());
1696  }
1697  if ( objectp(__docfile) ) {
1698  HTTP_DEBUG("HTTP: Closing file... %O", __docfile);
1699  catch(__docfile->close());
1700  catch(destruct(__docfile));
1701  }
1702  logout_user();
1703 }
1704 
1705 string describe()
1706 {
1707  return "HTTP-Request(" + ::describe() +
1708  ",idle="+get_idle()+"s,closed="+is_closed()+")";
1709 }
1710 
1711 int get_last_response()
1712 {
1713  mixed err;
1714  if ( objectp(__request) ) {
1715  HTTP_DEBUG("HTTP:Last Response is %O\n", __request->my_fd);
1716  err = catch {
1717  if ( !__request->my_fd ||
1718  ( functionp(__request->my_fd->is_open) &&
1719  !__request->my_fd->is_open() == 0) )
1720  {
1721  HTTP_DEBUG("HTTP: closed connection ...\n");
1722  return 0;
1723  }
1724  };
1725  if ( err != 0 )
1726  return 0;
1727  }
1728  if ( objectp(__docfile) )
1729  return __docfile->get_last_response();
1730  return __touch;
1731 }
1732 
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; }
1741 
1742 
1743 };