lib/standard/stream: Renamed streams for more explicit denomination
[nit.git] / lib / nitcorn / file_server.nit
index 348383e..d46d2a4 100644 (file)
@@ -21,6 +21,7 @@ module file_server
 import reactor
 import sessions
 import media_types
+import http_errors
 
 redef class String
        # Returns a `String` copy of `self` without any of the prefixed '/'s
@@ -41,9 +42,29 @@ end
 class FileServer
        super Action
 
-       # Root of `self` file system
+       # Root folder of `self` file system
        var root: String
 
+       init
+       do
+               var root = self.root
+
+               # Simplify the root path as each file requested will also be simplified
+               root = root.simplify_path
+
+               # Make sure the root ends with '/', this makes a difference in the security
+               # check on each file access.
+               root = root + "/"
+
+               self.root = root
+       end
+
+       # Error page template for a given `code`
+       fun error_page(code: Int): Writable do return new ErrorTemplate(code)
+
+       # Header of each directory page
+       var header: nullable Writable = null is writable
+
        redef fun answer(request, turi)
        do
                var response
@@ -51,17 +72,24 @@ class FileServer
                var local_file = root.join_path(turi.strip_start_slashes)
                local_file = local_file.simplify_path
 
-               # HACK
-               if turi == "/" then local_file = root
-
                # Is it reachable?
-               if local_file.has_prefix(root) then
+               #
+               # This make sure that the requested file is within the root folder.
+               if (local_file + "/").has_prefix(root) then
                        # Does it exists?
                        if local_file.file_exists then
-                               response = new HttpResponse(200)
-
                                if local_file.file_stat.is_dir then
-                                       # Show index.html instead of the directory listing
+                                       # If we target a directory without an ending `/`,
+                                       # redirect to the directory ending with `/`.
+                                       if not request.uri.is_empty and
+                                          request.uri.chars.last != '/' then
+                                               response = new HttpResponse(303)
+                                               response.header["Location"] = request.uri + "/"
+                                               return response
+                                       end
+
+                                       # Show index file instead of the directory listing
+                                       # only if `index.html` or `index.htm` is available
                                        var index_file = local_file.join_path("index.html")
                                        if index_file.file_exists then
                                                local_file = index_file
@@ -71,22 +99,30 @@ class FileServer
                                        end
                                end
 
+                               response = new HttpResponse(200)
                                if local_file.file_stat.is_dir then
                                        # Show the directory listing
                                        var title = turi
                                        var files = local_file.files
 
                                        var links = new Array[String]
-                                       if local_file.length > 1 then
-                                               # The extra / is a hack
-                                               var path = "/" + (turi + "/..").simplify_path
-                                               links.add "<a href=\"{path}\">..</a>"
+                                       if turi.length > 1 then
+                                               var path = (request.uri + "/..").simplify_path
+                                               links.add "<a href=\"{path}/\">..</a>"
                                        end
                                        for file in files do
-                                               var path = (turi + "/" + file).simplify_path
-                                               links.add "<a href=\"{path}\">{file}</a>"
+                                               var local_path = local_file.join_path(file).simplify_path
+                                               var web_path = file.simplify_path
+                                               if local_path.file_stat.is_dir then web_path = web_path + "/"
+                                               links.add "<a href=\"{web_path}\">{file}</a>"
                                        end
 
+                                       var header = self.header
+                                       var header_code
+                                       if header != null then
+                                               header_code = header.write_to_string
+                                       else header_code = ""
+
                                        response.body = """
 <!DOCTYPE html>
 <head>
@@ -96,6 +132,7 @@ class FileServer
        <title>{{{title}}}</title>
 </head>
 <body>
+       {{{header_code}}}
        <div class="container">
                <h1>{{{title}}}</h1>
                <ul>
@@ -108,13 +145,15 @@ class FileServer
                                        response.header["Content-Type"] = media_types["html"].as(not null)
                                else
                                        # It's a single file
-                                       var file = new IFStream.open(local_file)
+                                       var file = new FileReader.open(local_file)
                                        response.body = file.read_all
 
                                        var ext = local_file.file_extension
                                        if ext != null then
                                                var media_type = media_types[ext]
-                                               if media_type != null then response.header["Content-Type"] = media_type
+                                               if media_type != null then
+                                                       response.header["Content-Type"] = media_type
+                                               else response.header["Content-Type"] = "application/octet-stream"
                                        end
 
                                        file.close
@@ -123,6 +162,12 @@ class FileServer
                        else response = new HttpResponse(404)
                else response = new HttpResponse(403)
 
+               if response.status_code != 200 then
+                       var tmpl = error_page(response.status_code)
+                       if header != null and tmpl isa ErrorTemplate then tmpl.header = header
+                       response.body = tmpl.to_s
+               end
+
                return response
        end
 end