Implement link filter exceptions in base Freenet template page.
[Sone.git] / src / main / java / net / pterodactylus / sone / web / page / FreenetTemplatePage.java
1 /*
2  * Sone - FreenetTemplatePage.java - Copyright © 2010 David Roden
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 package net.pterodactylus.sone.web.page;
19
20 import java.io.IOException;
21 import java.io.StringWriter;
22 import java.net.URI;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.logging.Level;
29 import java.util.logging.Logger;
30
31 import net.pterodactylus.util.logging.Logging;
32 import net.pterodactylus.util.template.Template;
33 import net.pterodactylus.util.template.TemplateContext;
34 import net.pterodactylus.util.template.TemplateContextFactory;
35 import net.pterodactylus.util.web.Method;
36 import net.pterodactylus.util.web.Page;
37 import net.pterodactylus.util.web.RedirectResponse;
38 import net.pterodactylus.util.web.Response;
39 import freenet.clients.http.LinkEnabledCallback;
40 import freenet.clients.http.PageMaker;
41 import freenet.clients.http.PageNode;
42 import freenet.clients.http.ToadletContext;
43 import freenet.support.HTMLNode;
44
45 /**
46  * Base class for all {@link Page}s that are rendered with {@link Template}s and
47  * fit into Freenet’s web interface.
48  *
49  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
50  */
51 public class FreenetTemplatePage implements FreenetPage, LinkEnabledCallback {
52
53         /** The logger. */
54         private static final Logger logger = Logging.getLogger(FreenetTemplatePage.class);
55
56         /** The path of the page. */
57         private final String path;
58
59         /** The template context factory. */
60         private final TemplateContextFactory templateContextFactory;
61
62         /** The template to render. */
63         private final Template template;
64
65         /** Where to redirect for invalid form passwords. */
66         private final String invalidFormPasswordRedirectTarget;
67
68         /**
69          * Creates a new template page.
70          *
71          * @param path
72          *            The path of the page
73          * @param templateContextFactory
74          *            The template context factory
75          * @param template
76          *            The template to render
77          * @param invalidFormPasswordRedirectTarget
78          *            The target to redirect to if a POST request does not contain
79          *            the correct form password
80          */
81         public FreenetTemplatePage(String path, TemplateContextFactory templateContextFactory, Template template, String invalidFormPasswordRedirectTarget) {
82                 this.path = path;
83                 this.templateContextFactory = templateContextFactory;
84                 this.template = template;
85                 this.invalidFormPasswordRedirectTarget = invalidFormPasswordRedirectTarget;
86         }
87
88         /**
89          * {@inheritDoc}
90          */
91         @Override
92         public String getPath() {
93                 return path;
94         }
95
96         /**
97          * Returns the title of the page.
98          *
99          * @param request
100          *            The request to serve
101          * @return The title of the page
102          */
103         protected String getPageTitle(FreenetRequest request) {
104                 return null;
105         }
106
107         /**
108          * {@inheritDoc}
109          */
110         @Override
111         public boolean isPrefixPage() {
112                 return false;
113         }
114
115         /**
116          * {@inheritDoc}
117          */
118         @Override
119         public Response handleRequest(FreenetRequest request, Response response) throws IOException {
120                 String redirectTarget = getRedirectTarget(request);
121                 if (redirectTarget != null) {
122                         return new RedirectResponse(redirectTarget);
123                 }
124
125                 if (isFullAccessOnly() && !request.getToadletContext().isAllowedFullAccess()) {
126                         return response.setStatusCode(401).setStatusText("Not authorized").setContentType("text/html");
127                 }
128                 ToadletContext toadletContext = request.getToadletContext();
129                 if (request.getMethod() == Method.POST) {
130                         /* require form password. */
131                         String formPassword = request.getHttpRequest().getPartAsStringFailsafe("formPassword", 32);
132                         if (!formPassword.equals(toadletContext.getContainer().getFormPassword())) {
133                                 return new RedirectResponse(invalidFormPasswordRedirectTarget);
134                         }
135                 }
136                 PageMaker pageMaker = toadletContext.getPageMaker();
137                 PageNode pageNode = pageMaker.getPageNode(getPageTitle(request), toadletContext);
138                 for (String styleSheet : getStyleSheets()) {
139                         pageNode.addCustomStyleSheet(styleSheet);
140                 }
141                 for (Map<String, String> linkNodeParameters : getAdditionalLinkNodes(request)) {
142                         HTMLNode linkNode = pageNode.headNode.addChild("link");
143                         for (Entry<String, String> parameter : linkNodeParameters.entrySet()) {
144                                 linkNode.addAttribute(parameter.getKey(), parameter.getValue());
145                         }
146                 }
147                 String shortcutIcon = getShortcutIcon();
148                 if (shortcutIcon != null) {
149                         pageNode.addForwardLink("icon", shortcutIcon);
150                 }
151
152                 TemplateContext templateContext = templateContextFactory.createTemplateContext();
153                 templateContext.mergeContext(template.getInitialContext());
154                 try {
155                         long start = System.nanoTime();
156                         processTemplate(request, templateContext);
157                         long finish = System.nanoTime();
158                         logger.log(Level.FINEST, "Template was rendered in " + ((finish - start) / 1000) / 1000.0 + "ms.");
159                 } catch (RedirectException re1) {
160                         return new RedirectResponse(re1.getTarget());
161                 }
162
163                 StringWriter stringWriter = new StringWriter();
164                 template.render(templateContext, stringWriter);
165                 pageNode.content.addChild("%", stringWriter.toString());
166
167                 postProcess(request, templateContext);
168
169                 return response.setStatusCode(200).setStatusText("OK").setContentType("text/html").write(pageNode.outer.generate());
170         }
171
172         /**
173          * Can be overridden to return a custom set of style sheets that are to be
174          * included in the page’s header.
175          *
176          * @return Additional style sheets to load
177          */
178         protected Collection<String> getStyleSheets() {
179                 return Collections.emptySet();
180         }
181
182         /**
183          * Returns the name of the shortcut icon to include in the page’s header.
184          *
185          * @return The URL of the shortcut icon, or {@code null} for no icon
186          */
187         protected String getShortcutIcon() {
188                 return null;
189         }
190
191         /**
192          * Can be overridden when extending classes need to set variables in the
193          * template before it is rendered.
194          *
195          * @param request
196          *            The request that is rendered
197          * @param templateContext
198          *            The template context to set variables in
199          * @throws RedirectException
200          *             if the processing page wants to redirect after processing
201          */
202         protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
203                 /* do nothing. */
204         }
205
206         /**
207          * This method will be called after
208          * {@link #processTemplate(FreenetRequest, TemplateContext)} has processed
209          * the template and the template was rendered. This method will not be
210          * called if {@link #processTemplate(FreenetRequest, TemplateContext)}
211          * throws a {@link RedirectException}!
212          *
213          * @param request
214          *            The request being processed
215          * @param templateContext
216          *            The template context that supplied the rendered data
217          */
218         protected void postProcess(FreenetRequest request, TemplateContext templateContext) {
219                 /* do nothing. */
220         }
221
222         /**
223          * Can be overridden to redirect the user to a different page, in case a log
224          * in is required, or something else is wrong.
225          *
226          * @param request
227          *            The request that is processed
228          * @return The URL to redirect to, or {@code null} to not redirect
229          */
230         protected String getRedirectTarget(FreenetRequest request) {
231                 return null;
232         }
233
234         /**
235          * Returns additional &lt;link&gt; nodes for the HTML’s &lt;head&gt; node.
236          *
237          * @param request
238          *            The request for which to return the link nodes
239          * @return All link nodes that should be added to the HTML head
240          */
241         protected List<Map<String, String>> getAdditionalLinkNodes(FreenetRequest request) {
242                 return Collections.emptyList();
243         }
244
245         /**
246          * Returns whether this page should only be allowed for requests from hosts
247          * with full access.
248          *
249          * @return {@code true} if this page should only be allowed for hosts with
250          *         full access, {@code false} to allow this page for any host
251          */
252         protected boolean isFullAccessOnly() {
253                 return false;
254         }
255
256         /**
257          * {@inheritDoc}
258          */
259         @Override
260         public boolean isLinkExcepted(URI link) {
261                 return false;
262         }
263
264         //
265         // INTERFACE LinkEnabledCallback
266         //
267
268         /**
269          * {@inheritDoc}
270          */
271         @Override
272         public boolean isEnabled(ToadletContext toadletContext) {
273                 return !isFullAccessOnly();
274         }
275
276         /**
277          * Exception that can be thrown to signal that a subclassed {@link Page}
278          * wants to redirect the user during the
279          * {@link FreenetTemplatePage#processTemplate(FreenetRequest, TemplateContext)}
280          * method call.
281          *
282          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
283          */
284         public static class RedirectException extends Exception {
285
286                 /** The target to redirect to. */
287                 private final String target;
288
289                 /**
290                  * Creates a new redirect exception.
291                  *
292                  * @param target
293                  *            The target of the redirect
294                  */
295                 public RedirectException(String target) {
296                         this.target = target;
297                 }
298
299                 /**
300                  * Returns the target to redirect to.
301                  *
302                  * @return The target to redirect to
303                  */
304                 public String getTarget() {
305                         return target;
306                 }
307
308         }
309
310 }