Fix HTML encoding of ampersand.
[Sone.git] / src / main / java / net / pterodactylus / sone / web / page / Page.java
1 /*
2  * Sone - Page.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.ByteArrayInputStream;
21 import java.io.InputStream;
22 import java.io.UnsupportedEncodingException;
23 import java.net.URI;
24 import java.util.HashMap;
25 import java.util.Map;
26
27 import freenet.clients.http.ToadletContext;
28 import freenet.support.api.HTTPRequest;
29
30 /**
31  * A page is responsible for handling HTTP requests and creating appropriate
32  * responses.
33  *
34  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
35  */
36 public interface Page {
37
38         /**
39          * Returns the path of this toadlet.
40          *
41          * @return The path of this toadlet
42          */
43         public String getPath();
44
45         /**
46          * Handles a request.
47          *
48          * @param request
49          *            The request to handle
50          * @return The response
51          */
52         public Response handleRequest(Request request);
53
54         /**
55          * Container for request data.
56          *
57          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
58          */
59         public class Request {
60
61                 /**
62                  * Enumeration for all possible HTTP request methods.
63                  *
64                  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’
65                  *         Roden</a>
66                  */
67                 public enum Method {
68
69                         /** GET. */
70                         GET,
71
72                         /** POST. */
73                         POST,
74
75                         /** PUT. */
76                         PUT,
77
78                         /** DELETE. */
79                         DELETE,
80
81                         /** HEAD. */
82                         HEAD,
83
84                         /** OPTIONS. */
85                         OPTIONS,
86
87                         /** TRACE. */
88                         TRACE,
89
90                 }
91
92                 /** The URI that was accessed. */
93                 private final URI uri;
94
95                 /** The HTTP method that was used. */
96                 private final Method method;
97
98                 /** The HTTP request. */
99                 private final HTTPRequest httpRequest;
100
101                 /** The toadlet context. */
102                 private final ToadletContext toadletContext;
103
104                 /**
105                  * Creates a new request that holds the given data.
106                  *
107                  * @param uri
108                  *            The URI of the request
109                  * @param method
110                  *            The HTTP method of the request
111                  * @param httpRequest
112                  *            The HTTP request
113                  * @param toadletContext
114                  *            The toadlet context of the request
115                  */
116                 public Request(URI uri, Method method, HTTPRequest httpRequest, ToadletContext toadletContext) {
117                         this.uri = uri;
118                         this.method = method;
119                         this.httpRequest = httpRequest;
120                         this.toadletContext = toadletContext;
121                 }
122
123                 /**
124                  * Returns the URI that was accessed.
125                  *
126                  * @return The accessed URI
127                  */
128                 public URI getUri() {
129                         return uri;
130                 }
131
132                 /**
133                  * Returns the HTTP method that was used to access the page.
134                  *
135                  * @return The HTTP method
136                  */
137                 public Method getMethod() {
138                         return method;
139                 }
140
141                 /**
142                  * Returns the HTTP request.
143                  *
144                  * @return The HTTP request
145                  */
146                 public HTTPRequest getHttpRequest() {
147                         return httpRequest;
148                 }
149
150                 /**
151                  * Returns the toadlet context.
152                  *
153                  * @return The toadlet context
154                  */
155                 public ToadletContext getToadletContext() {
156                         return toadletContext;
157                 }
158
159         }
160
161         /**
162          * Container for the HTTP response of a {@link Page}.
163          *
164          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
165          */
166         public class Response {
167
168                 /** The HTTP status code of the response. */
169                 private final int statusCode;
170
171                 /** The HTTP status text of the response. */
172                 private final String statusText;
173
174                 /** The content type of the response. */
175                 private final String contentType;
176
177                 /** The headers of the response. */
178                 private final Map<String, String> headers;
179
180                 /** The content of the response body. */
181                 private final InputStream content;
182
183                 /**
184                  * Creates a new response.
185                  *
186                  * @param statusCode
187                  *            The HTTP status code of the response
188                  * @param statusText
189                  *            The HTTP status text of the response
190                  * @param contentType
191                  *            The content type of the response
192                  * @param text
193                  *            The text in the response body
194                  */
195                 public Response(int statusCode, String statusText, String contentType, String text) {
196                         this(statusCode, statusText, contentType, getBytes(text));
197                 }
198
199                 /**
200                  * Creates a new response.
201                  *
202                  * @param statusCode
203                  *            The HTTP status code of the response
204                  * @param statusText
205                  *            The HTTP status text of the response
206                  * @param contentType
207                  *            The content type of the response
208                  * @param content
209                  *            The content of the reponse body
210                  */
211                 public Response(int statusCode, String statusText, String contentType, byte[] content) {
212                         this(statusCode, statusText, contentType, new HashMap<String, String>(), content);
213                 }
214
215                 /**
216                  * Creates a new response.
217                  *
218                  * @param statusCode
219                  *            The HTTP status code of the response
220                  * @param statusText
221                  *            The HTTP status text of the response
222                  * @param contentType
223                  *            The content type of the response
224                  * @param headers
225                  *            The headers of the response
226                  */
227                 public Response(int statusCode, String statusText, String contentType, Map<String, String> headers) {
228                         this(statusCode, statusText, contentType, headers, (InputStream) null);
229                 }
230
231                 /**
232                  * Creates a new response.
233                  *
234                  * @param statusCode
235                  *            The HTTP status code of the response
236                  * @param statusText
237                  *            The HTTP status text of the response
238                  * @param contentType
239                  *            The content type of the response
240                  * @param headers
241                  *            The headers of the response
242                  * @param content
243                  *            The content of the reponse body
244                  */
245                 public Response(int statusCode, String statusText, String contentType, Map<String, String> headers, byte[] content) {
246                         this(statusCode, statusText, contentType, headers, new ByteArrayInputStream(content));
247                 }
248
249                 /**
250                  * Creates a new response.
251                  *
252                  * @param statusCode
253                  *            The HTTP status code of the response
254                  * @param statusText
255                  *            The HTTP status text of the response
256                  * @param contentType
257                  *            The content type of the response
258                  * @param headers
259                  *            The headers of the response
260                  * @param content
261                  *            The content of the reponse body
262                  */
263                 public Response(int statusCode, String statusText, String contentType, Map<String, String> headers, InputStream content) {
264                         this.statusCode = statusCode;
265                         this.statusText = statusText;
266                         this.contentType = contentType;
267                         this.headers = headers;
268                         this.content = content;
269                 }
270
271                 /**
272                  * Returns the HTTP status code of the response.
273                  *
274                  * @return The HTTP status code
275                  */
276                 public int getStatusCode() {
277                         return statusCode;
278                 }
279
280                 /**
281                  * Returns the HTTP status text.
282                  *
283                  * @return The HTTP status text
284                  */
285                 public String getStatusText() {
286                         return statusText;
287                 }
288
289                 /**
290                  * Returns the content type of the response.
291                  *
292                  * @return The content type of the reponse
293                  */
294                 public String getContentType() {
295                         return contentType;
296                 }
297
298                 /**
299                  * Returns HTTP headers of the response. May be {@code null} if no
300                  * headers are returned.
301                  *
302                  * @return The response headers, or {@code null} if there are no
303                  *         response headers
304                  */
305                 public Map<String, String> getHeaders() {
306                         return headers;
307                 }
308
309                 /**
310                  * Sets the HTTP header with the given name to the given value. Multiple
311                  * headers with the same name are not implemented so that latest call to
312                  * {@link #setHeader(String, String)} determines what is sent to the
313                  * browser.
314                  *
315                  * @param name
316                  *            The name of the header
317                  * @param value
318                  *            The value of the header
319                  */
320                 public void setHeader(String name, String value) {
321                         headers.put(name, value);
322                 }
323
324                 /**
325                  * Returns the content of the response body. May be {@code null} if the
326                  * response does not have a body.
327                  *
328                  * @return The content of the response body
329                  */
330                 public InputStream getContent() {
331                         return content;
332                 }
333
334                 //
335                 // PRIVATE METHODS
336                 //
337
338                 /**
339                  * Returns the UTF-8 representation of the given text.
340                  *
341                  * @param text
342                  *            The text to encode
343                  * @return The encoded text
344                  */
345                 private static byte[] getBytes(String text) {
346                         try {
347                                 return text.getBytes("UTF-8");
348                         } catch (UnsupportedEncodingException uee1) {
349                                 /* every JVM needs to support UTF-8. */
350                         }
351                         return null;
352                 }
353
354                 /**
355                  * Creates a header map containing a single header.
356                  *
357                  * @param name
358                  *            The name of the header
359                  * @param value
360                  *            The value of the header
361                  * @return The map containing the single header
362                  */
363                 protected static Map<String, String> createHeader(String name, String value) {
364                         Map<String, String> headers = new HashMap<String, String>();
365                         headers.put(name, value);
366                         return headers;
367                 }
368
369         }
370
371         /**
372          * {@link Response} implementation that performs an HTTP redirect.
373          *
374          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
375          */
376         public class RedirectResponse extends Response {
377
378                 /**
379                  * Creates a new redirect response to the new location.
380                  *
381                  * @param newLocation
382                  *            The new location
383                  */
384                 public RedirectResponse(String newLocation) {
385                         this(newLocation, true);
386                 }
387
388                 /**
389                  * Creates a new redirect response to the new location.
390                  *
391                  * @param newLocation
392                  *            The new location
393                  * @param permanent
394                  *            Whether the redirect should be marked as permanent
395                  */
396                 public RedirectResponse(String newLocation, boolean permanent) {
397                         super(permanent ? 302 : 307, "Redirected", null, createHeader("Location", newLocation));
398                 }
399
400         }
401
402 }