/* * Copyright (c) 2003, 2004 The University of Wroclaw. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the University may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL THE UNIVERSITY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ namespace Sioux { using Nemerle.Collections; using Nemerle.Xml; using System.Xml; using System.Net.Sockets; /** * An HTTP server response */ public class Response { /* -- CONSTRUCTORS ----------------------------------------------------- */ /** * Creates a new response object */ internal this (stream : NetworkStream) { response_headers = []; response_stream = stream; logger = Httpd.GetLogger (); config = Httpd.GetConfig () } /* -- PUBLIC METHODS --------------------------------------------------- */ /** * Writes a string to the response stream */ public Write (s : string) : void { def buf = System.Text.Encoding.ASCII.GetBytes (s); response_stream.Write (buf, 0, buf.Length) } /** * Writes a CR/LF pair to the response stream */ public WriteLine () : void { Write ("\n") } /** * Writes a string followed by a CR/LF pair to the response stream */ public WriteLine (s : string) : void { Write (s); WriteLine () } /** * Writes the server name header to the response stream */ public WriteServerName () : void { WriteLine ("Server: " + config.Get ("server/name")) } /** * Redirects browser to given url */ public WriteRedirect (url : string) : void { WriteLine("HTTP/1.1 301 Moved Permanently"); WriteServerName(); WriteLine("Location: " + url); WriteLine("Connection: close"); WriteLine("Content-Type: text/html"); WriteLine(); } public WriteServerFull () : void { WriteLine("HTTP/1.1 503 Service Unavailable"); WriteServerName (); WriteLine(); WriteLine ("[html][body]" + config.Get ("replies/server_full") + ""); } /** * Writes an error message concerning an invalid request */ public WriteInvalid () : void { WriteLine ("HTTP/1.0 403 Error"); WriteServerName (); WriteLine (); WriteLine (config.Get ("replies/invalid")); } /** * Writes not implemented message, when reqest type is other than POST or GET */ public WriteNotImplemented() : void { WriteLine("HTTP/1.1 501 Not Implemented"); WriteServerName (); WriteLine(); } /** * Writes an error message concerning a file not being found */ public WriteNotFound () : void { WriteLine ("HTTP/1.0 404 Not found"); WriteServerName (); WriteLine (); WriteLine (config.Get ("replies/not_found")); } /** * Writes the MIME content type header */ public WriteMimeType (file_name : string) : void { def b = System.IO.File.Exists (file_name); when (b) { def mime_type = if (file_name.EndsWith (".htm") || file_name.EndsWith (".html")) "text/html" else if (file_name.EndsWith (".css")) "text/css" else if (file_name.EndsWith (".png")) "image/png" else if (file_name.EndsWith (".gif")) "image/gif" else "application/octet-stream"; WriteLine ("Content-Type: " + mime_type) } } /** * Writes the contents of an XML document to the response stream */ public WriteXml (document : XmlDocument, content_type : string, charset : string) : void { WriteLine ("HTTP/1.0 200 OK"); WriteLine ("Content-type: " + content_type + "; charset=" + charset); write_headers(); WriteLine (); document.Save (response_stream) } /** * Writes the contents of an XHTML document to the response stream */ public WriteXhtml (document : XmlDocument) : void { WriteXml (document, "text/html", "utf-8") } /** * Checks if the URL from a request is valid. * * NOTE: this is the best place for spawning security bugs. */ public static CheckUrl (url : string) : string { def url = check_directory_access (url); def buf = System.Text.StringBuilder (); def len = url.Length; mutable i = 0; while (i < len && !System.Char.IsWhiteSpace (url [i])) { ignore (buf.Append (url [i])); i = i + 1; }; buf.ToString () } /** * Converts an URL from a request into a local file name * with the path relative to the server's repository. */ public static GetLocalFileName (url : string) : string { def s = Httpd.GetConfig ().Get ("server/root") + CheckUrl (url); if (s.EndsWith ("/")) s + "index.xml" else s } /** * Serves a static file. */ public ServeStaticFile (url : string) : void { def checked_url = CheckUrl (url); def local_file_name = GetLocalFileName (checked_url); serve_static_file (local_file_name) } /** * Adds a response header with the given name and value */ public AddHeader(name : string, value : string) : void { response_headers = (name,value) :: response_headers } /** * Adds the specified cookie to the response. * This method can be called multiple times to set more than one cookie. */ public AddCookie(cookie : Cookie) : void { response_headers = cookie.Header :: response_headers } /* -- PRIVATE METHODS -------------------------------------------------- */ /** * Writes headers to response stream */ private write_headers() : void { def loop(lst) { match(lst) { | [] => () | (name,value) :: rest => WriteLine(name + ": " + value); loop(rest) } } loop(List.Rev(response_headers)) } /** * Redirects contents of a static file to the response stream */ private serve_static_file (file_name : string) : void { /* FIXME: these are the headers that worked for serving files with a PHP application of mine: Header ("Content-Type: " . $resource ["CONTENT_TYPE"]); Header ("Content-Length: " . $resource ["FILE_SIZE"]); Header ("Content-Disposition: inline; filename=" . $resource ["FILE_NAME"]); */ if (!System.IO.File.Exists (file_name)) { WriteNotFound (); } else { try { WriteLine ("HTTP/1.0 200 OK"); // write_type (fname, stream); WriteLine ("Server: " + config.Get ("server/name")); WriteLine (); write_file_contents (file_name) } catch { e => logger.Log ("Could not read file " + file_name + " " + e.Message); } } } /** * Writes the contents of a file to the response stream */ private write_file_contents (file_name : string) : void { def fp = System.IO.BinaryReader (System.IO.File.Open (file_name, System.IO.FileMode.Open)); def buf = (array (10000) : array [System.Byte]); def loop () { match (fp.Read (buf, 0, buf.Length)) { | 0 => () | cnt => response_stream.Write (buf, 0, cnt); loop () } }; loop (); fp.Close () } /** * NOTE: this should perform some dynamic tests to see if the * URL's real directory is nested in the server's repository. */ private static check_directory_access (url : string) : string { if (url.IndexOf ("/..") != -1) "" else url } /* -- PRIVATE FIELDS --------------------------------------------------- */ private mutable response_headers : list [string * string]; private response_stream : NetworkStream; private logger : Logger; private config : Config; } }