1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Web server to server generated files and modify the wiki from a web form
23 intrude import wiki_html
25 # Page for editing markdown source
29 # Part of the title before the name of the page
30 var title_prefix
: String
32 # Markdown content, for previews
35 # Custom HTML code, for forms and links
38 init do content
= (md
or else "").md_to_html
.to_s
+ html
40 redef fun dir_href
do return "edit" / href
45 s
.title
= title_prefix
+ title
49 # Fill and return a new `HttpResponse` with this page content
50 fun to_http_response
: HttpResponse
52 var resp
= new HttpResponse(200)
58 # Action to serve edit forms, show previews and apply changes
62 # Full public URL for the root of this wiki
65 # Path to the wiki config
66 var config_file_path
: String
68 # Configuration of the Wiki, loaded once
69 var wiki_config
= new WikiConfig(config_file_path
) is lazy
71 # Path to the root of the wiki
72 private var wiki_root
: String = config_file_path
.dirname
is lazy
74 # Path to the source files
75 private var source_dir
: String = (wiki_root
/ wiki_config
.source_dir
).simplify_path
+ "/" is lazy
77 # List of acceptable password to apply modifications
79 # If `null`, no password checks are applied and all modifications are accepted.
80 var passwords
: nullable Collection[String]
82 # Reload the wiki instance with the latest changes
85 var wiki
= new Nitiwiki(wiki_config
)
90 redef fun answer
(http_request
, turi
)
92 var action
= http_request
.string_arg
("action")
93 var markdown
= http_request
.post_args
.get_or_default
("content", "")
95 var file_path
= turi
.strip_leading_slash
96 file_path
= wiki_root
/ file_path
98 var abs_file_path
= file_path
.to_absolute_path
99 var abs_source_dir
= source_dir
.to_absolute_path
101 if not abs_file_path
.has_prefix
(abs_source_dir
) then
102 # Attempting to access a file outside the source directory
103 var entity
= new WikiEditForm(wiki
, turi
.strip_leading_slash
,
104 "Access denied: ", "", "<p>Target outside of the source directory</p>")
105 return entity
.to_http_response
108 if action
== "Submit" then
109 var passwords
= passwords
110 var password
= http_request
.post_args
.get_or_null
("password")
111 if passwords
!= null and (password
== null or not passwords
.has
(password
.md5
)) then
113 var entity
= new WikiEditForm(wiki
, turi
.strip_leading_slash
,
114 "Changes rejected: ", "", "<p>Password invalid</p>")
115 return entity
.to_http_response
118 # Save markdown source
119 markdown
= markdown
.replace
('\r', "")
120 markdown
.write_to_file file_path
127 if turi
.has_prefix
("/pages/") then
128 link
= root_url
/ turi
.substring_from
(7)
129 else link
= root_url
/ turi
130 link
= link
.strip_extension
(".md") + ".html"
134 <p>Your edits were recorded and the file is updated: <a href="{{{link}}}">{{{link}}}</a></p>
136 var entity
= new WikiEditForm(wiki
, turi
.strip_leading_slash
, "Changes saved: ", "", body
)
137 return entity
.to_http_response
139 # Show edit form, and preview when requested
141 # When not in a preview, use the local content of the file
142 if action
!= "Preview" then markdown
= file_path
.to_path
.read_all
145 <form method="POST" action="/edit{{{turi}}}">
146 You may edit the file. When you are done, click on "Submit".<br/>
147 <textarea name="content" rows="30" cols="80">{{{markdown.html_escape}}}</textarea><br/>
149 if passwords
!= null then form
+= """
150 Password: <input type="password" name="password"><br/>
153 <input type="submit" name="action" value="Preview">
154 <input type="submit" name="action" value="Submit">
158 # Show processed markdown only on preview
159 if action
!= "Preview" then markdown
= ""
161 var entity
= new WikiEditForm(wiki
, turi
.strip_leading_slash
, "Edit source: ", markdown
, form
)
162 return entity
.to_http_response
168 private fun strip_leading_slash
: String
170 if has_prefix
("/") then return substring_from
(1)
174 private fun to_absolute_path
: String
176 return (getcwd
/ self).simplify_path
180 var opt_config
= new OptionString("Path to config.ini file", "-c", "--config")
181 var opt_host
= new OptionString("Host to bind the server to", "--host")
182 var opt_port
= new OptionInt("Port to bind the server to", 8000, "--port")
183 var opt_pass
= new OptionString("Password file path", "--pass")
185 var config
= new Config
186 config
.add_option
(opt_config
, opt_host
, opt_port
, opt_pass
)
188 var usage
= new Buffer
189 usage
.append
"Usage: wiki_edit [OPTION]...\n"
190 usage
.append
"Web server to server generated files and modify the wiki from a web form."
191 config
.tool_description
= usage
.write_to_string
193 config
.parse_options
(args
)
195 if config
.opt_help
.value
then
200 var config_file_path
= opt_config
.value
or else "config.ini"
201 var iface
= "{opt_host.value or else "localhost"}:{opt_port.value}"
202 var password_file_path
= opt_pass
.value
or else "passwords"
204 # Load passwords for file
205 var passwords
= if password_file_path
.file_exists
then
206 password_file_path
.to_path
.read_lines
209 var vh
= new VirtualHost(iface
)
211 # Serve Markdown editing form
212 var action
= new EditAction("http://" + iface
, config_file_path
, passwords
)
213 vh
.routes
.add
new Route("/edit", action
)
215 # Serve the static (and generated) content
216 var path_to_public_files
= config_file_path
.dirname
/ action
.wiki_config
.out_dir
217 vh
.routes
.add
new Route(null, new FileServer(path_to_public_files
))
219 var factory
= new HttpFactory.and_libevent
220 factory
.config
.virtual_hosts
.add vh