1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2013 Jean-Philippe Caissy <jpcaissy@piji.ca>
4 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
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
10 # http://www.apache.org/licenses/LICENSE-2.0
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.
18 # Provides the `FileServer` action, which is a standard and minimal file server
27 # Returns a `String` copy of `self` without any of the prefixed '/'s
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
36 for i
in chars
.length
.times
do if chars
[i
] != '/' then return substring_from
(i
)
41 # A simple file server
45 # Root folder of `self` file system
52 # Simplify the root path as each file requested will also be simplified
53 root
= root
.simplify_path
55 # Make sure the root ends with '/', this makes a difference in the security
56 # check on each file access.
62 # Error page template for a given `code`
63 fun error_page
(code
: Int): Writable do return new ErrorTemplate(code
)
65 # Header of each directory page
66 var header
: nullable Writable = null is writable
68 # Custom JavaScript code added within a `<script>` block to each page
69 var javascript_header
: nullable Writable = null is writable
71 # Caching attributes of served files, used as the `cache-control` field in response headers
72 var cache_control
= "public, max-age=360" is writable
74 # Show directory listing?
75 var show_directory_listing
= true is writable
77 # Default file returned when no static file matches the requested URI.
79 # If no `default_file` is provided, the FileServer responds 404 error to
81 var default_file
: nullable String = null is writable
83 redef fun answer
(request
, turi
)
87 var local_file
= root
.join_path
(turi
.strip_start_slashes
)
88 local_file
= local_file
.simplify_path
92 # This make sure that the requested file is within the root folder.
93 if (local_file
+ "/").has_prefix
(root
) then
95 var file_stat
= local_file
.file_stat
96 if file_stat
!= null then
97 if file_stat
.is_dir
then
98 # If we target a directory without an ending `/`,
99 # redirect to the directory ending with `/`.
100 var uri
= request
.uri
101 if not uri
.is_empty
and uri
.chars
.last
!= '/' then
102 return answer_redirection
(request
.uri
+ "/")
105 # Show index file instead of the directory listing
106 # only if `index.html` or `index.htm` is available
107 var index_file
= local_file
.join_path
("index.html")
108 if index_file
.file_exists
then
109 local_file
= index_file
111 index_file
= local_file
.join_path
("index.htm")
112 if index_file
.file_exists
then local_file
= index_file
116 file_stat
= local_file
.file_stat
117 if show_directory_listing
and file_stat
!= null and file_stat
.is_dir
then
118 response
= answer_directory_listing
(request
, turi
, local_file
)
119 else if file_stat
!= null and not file_stat
.is_dir
then # It's a single file
120 response
= answer_file
(local_file
)
121 else response
= answer_default
122 else response
= answer_default
123 else response
= new HttpResponse(403)
125 if response
.status_code
!= 200 then
126 var tmpl
= error_page
(response
.status_code
)
127 if header
!= null and tmpl
isa ErrorTemplate then tmpl
.header
= header
134 # Answer the `default_file` if any.
135 fun answer_default
: HttpResponse do
136 var default_file
= self.default_file
137 if default_file
== null then
138 return new HttpResponse(404)
141 var local_file
= (root
/ default_file
).simplify_path
142 return answer_file
(local_file
)
145 # Answer a 303 redirection to `location`.
146 fun answer_redirection
(location
: String): HttpResponse do
147 var response
= new HttpResponse(303)
148 response
.header
["Location"] = location
152 # Build a reponse containing a single `local_file`.
154 # Returns a 404 error if local_file does not exists.
155 fun answer_file
(local_file
: String): HttpResponse do
156 if not local_file
.file_exists
then return new HttpResponse(404)
158 var response
= new HttpResponse(200)
159 response
.files
.add local_file
161 # Set Content-Type depending on the file extension
162 var ext
= local_file
.file_extension
164 var media_type
= media_types
[ext
]
165 if media_type
!= null then
166 response
.header
["Content-Type"] = media_type
167 else response
.header
["Content-Type"] = "application/octet-stream"
171 response
.header
["cache-control"] = cache_control
175 # Answer with a directory listing for files within `local_files`.
176 fun answer_directory_listing
(request
: HttpRequest, turi
, local_file
: String): HttpResponse do
177 # Show the directory listing
179 var files
= local_file
.files
181 alpha_comparator
.sort files
183 var links
= new Array[String]
184 if turi
.length
> 1 then
185 var path
= (request
.uri
+ "/..").simplify_path
186 links
.add
"<a href=\"{path}/\
">..</a>"
189 var local_path
= local_file
.join_path
(file
).simplify_path
190 var web_path
= file
.simplify_path
191 var file_stat
= local_path
.file_stat
192 if file_stat
!= null and file_stat
.is_dir
then web_path
= web_path
+ "/"
193 links
.add
"<a href=\"{web_path}\
">{file}</a>"
196 var header
= self.header
198 if header
!= null then
199 header_code
= header
.write_to_string
200 else header_code
= ""
202 var response
= new HttpResponse(200)
206 <meta charset="utf-8">
207 <meta http-equiv="X-UA-Compatible" content="IE=edge">
208 <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
210 {{{javascript_header or else ""}}}
212 <title>{{{title}}}</title>
216 <div class="container">
219 <li>{{{links.join("</li>\n\t\t\t<li>")}}}</li>
225 response
.header
["Content-Type"] = media_types
["html"].as(not null)