Makefile: Document deeply-nested libraries.
[nit.git] / lib / nitcorn / file_server.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2013 Jean-Philippe Caissy <jpcaissy@piji.ca>
4 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17
18 # Provides the `FileServer` action, which is a standard and minimal file server
19 module file_server
20
21 import reactor
22 import sessions
23 import media_types
24 import http_errors
25
26 redef class String
27 # Returns a `String` copy of `self` without any of the prefixed '/'s
28 #
29 # Examples:
30 #
31 # assert "/home/".strip_start_slashes == "home/"
32 # assert "////home/".strip_start_slashes == "home/"
33 # assert "../home/".strip_start_slashes == "../home/"
34 fun strip_start_slashes: String
35 do
36 for i in chars.length.times do if chars[i] != '/' then return substring_from(i)
37 return ""
38 end
39 end
40
41 # A simple file server
42 class FileServer
43 super Action
44
45 # Root of `self` file system
46 var root: String
47
48 # Error page template for a given `code`
49 fun error_page(code: Int): Streamable do return new ErrorTemplate(code)
50
51 # Header of each directory page
52 var header: nullable Streamable = null is writable
53
54 redef fun answer(request, turi)
55 do
56 var response
57
58 var local_file = root.join_path(turi.strip_start_slashes)
59 local_file = local_file.simplify_path
60
61 # HACK
62 if turi == "/" then local_file = root
63
64 # Is it reachable?
65 if local_file.has_prefix(root) then
66 # Does it exists?
67 if local_file.file_exists then
68 response = new HttpResponse(200)
69
70 if local_file.file_stat.is_dir then
71 # Show index.html instead of the directory listing
72 var index_file = local_file.join_path("index.html")
73 if index_file.file_exists then
74 local_file = index_file
75 else
76 index_file = local_file.join_path("index.htm")
77 if index_file.file_exists then local_file = index_file
78 end
79 end
80
81 if local_file.file_stat.is_dir then
82 # Show the directory listing
83 var title = turi
84 var files = local_file.files
85
86 var links = new Array[String]
87 if local_file.length > 1 then
88 # The extra / is a hack
89 var path = "/" + (turi + "/..").simplify_path
90 links.add "<a href=\"{path}\">..</a>"
91 end
92 for file in files do
93 var path = (turi + "/" + file).simplify_path
94 links.add "<a href=\"{path}\">{file}</a>"
95 end
96
97 var header = self.header
98 var header_code
99 if header != null then
100 header_code = header.write_to_string
101 else header_code = ""
102
103 response.body = """
104 <!DOCTYPE html>
105 <head>
106 <meta charset="utf-8">
107 <meta http-equiv="X-UA-Compatible" content="IE=edge">
108 <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
109 <title>{{{title}}}</title>
110 </head>
111 <body>
112 {{{header_code}}}
113 <div class="container">
114 <h1>{{{title}}}</h1>
115 <ul>
116 <li>{{{links.join("</li>\n\t\t\t<li>")}}}</li>
117 </ul>
118 </div>
119 </body>
120 </html>"""
121
122 response.header["Content-Type"] = media_types["html"].as(not null)
123 else
124 # It's a single file
125 var file = new IFStream.open(local_file)
126 response.body = file.read_all
127
128 var ext = local_file.file_extension
129 if ext != null then
130 var media_type = media_types[ext]
131 if media_type != null then
132 response.header["Content-Type"] = media_type
133 else response.header["Content-Type"] = "application/octet-stream"
134 end
135
136 file.close
137 end
138
139 else response = new HttpResponse(404)
140 else response = new HttpResponse(403)
141
142 if response.status_code != 200 then
143 var tmpl = error_page(response.status_code)
144 if header != null and tmpl isa ErrorTemplate then tmpl.header = header
145 response.body = tmpl.to_s
146 end
147
148 return response
149 end
150 end