Merge remote-tracking branch 'beak/next' into next
[Sone.git] / src / main / java / net / pterodactylus / sone / web / ajax / JsonPage.java
1 /*
2  * Sone - JsonPage.java - Copyright © 2010–2012 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.ajax;
19
20 import java.io.ByteArrayOutputStream;
21 import java.io.IOException;
22 import java.io.OutputStreamWriter;
23 import java.io.PrintWriter;
24 import java.net.URI;
25
26 import net.pterodactylus.sone.data.Sone;
27 import net.pterodactylus.sone.web.WebInterface;
28 import net.pterodactylus.sone.web.page.FreenetPage;
29 import net.pterodactylus.sone.web.page.FreenetRequest;
30 import net.pterodactylus.util.io.Closer;
31 import net.pterodactylus.util.json.JsonObject;
32 import net.pterodactylus.util.json.JsonUtils;
33 import net.pterodactylus.util.web.Page;
34 import net.pterodactylus.util.web.Response;
35 import freenet.clients.http.SessionManager.Session;
36 import freenet.clients.http.ToadletContext;
37
38 /**
39  * A JSON page is a specialized {@link Page} that will always return a JSON
40  * object to the browser, e.g. for use with AJAX or other scripting frameworks.
41  *
42  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
43  */
44 public abstract class JsonPage implements FreenetPage {
45
46         /** The path of the page. */
47         private final String path;
48
49         /** The Sone web interface. */
50         protected final WebInterface webInterface;
51
52         /**
53          * Creates a new JSON page at the given path.
54          *
55          * @param path
56          *            The path of the page
57          * @param webInterface
58          *            The Sone web interface
59          */
60         public JsonPage(String path, WebInterface webInterface) {
61                 this.path = path;
62                 this.webInterface = webInterface;
63         }
64
65         //
66         // ACCESSORS
67         //
68
69         /**
70          * Returns the current session, creating a new session if there is no
71          * current session.
72          *
73          * @param toadletContenxt
74          *            The toadlet context
75          * @return The current session, or {@code null} if there is no current
76          *         session
77          */
78         protected Session getCurrentSession(ToadletContext toadletContenxt) {
79                 return webInterface.getCurrentSession(toadletContenxt);
80         }
81
82         /**
83          * Returns the current session, creating a new session if there is no
84          * current session and {@code create} is {@code true}.
85          *
86          * @param toadletContenxt
87          *            The toadlet context
88          * @param create
89          *            {@code true} to create a new session if there is no current
90          *            session, {@code false} otherwise
91          * @return The current session, or {@code null} if there is no current
92          *         session
93          */
94         protected Session getCurrentSession(ToadletContext toadletContenxt, boolean create) {
95                 return webInterface.getCurrentSession(toadletContenxt, create);
96         }
97
98         /**
99          * Returns the currently logged in Sone.
100          *
101          * @param toadletContext
102          *            The toadlet context
103          * @return The currently logged in Sone, or {@code null} if no Sone is
104          *         currently logged in
105          */
106         protected Sone getCurrentSone(ToadletContext toadletContext) {
107                 return webInterface.getCurrentSone(toadletContext);
108         }
109
110         /**
111          * Returns the currently logged in Sone.
112          *
113          * @param toadletContext
114          *            The toadlet context
115          * @param create
116          *            {@code true} to create a new session if no session exists,
117          *            {@code false} to not create a new session
118          * @return The currently logged in Sone, or {@code null} if no Sone is
119          *         currently logged in
120          */
121         protected Sone getCurrentSone(ToadletContext toadletContext, boolean create) {
122                 return webInterface.getCurrentSone(toadletContext, create);
123         }
124
125         //
126         // METHODS FOR SUBCLASSES TO OVERRIDE
127         //
128
129         /**
130          * This method is called to create the JSON object that is returned back to
131          * the browser.
132          *
133          * @param request
134          *            The request to handle
135          * @return The created JSON object
136          */
137         protected abstract JsonObject createJsonObject(FreenetRequest request);
138
139         /**
140          * Returns whether this command needs the form password for authentication
141          * and to prevent abuse.
142          *
143          * @return {@code true} if the form password (given as “formPassword”) is
144          *         required, {@code false} otherwise
145          */
146         @SuppressWarnings("static-method")
147         protected boolean needsFormPassword() {
148                 return true;
149         }
150
151         /**
152          * Returns whether this page requires the user to be logged in.
153          *
154          * @return {@code true} if the user needs to be logged in to use this page,
155          *         {@code false} otherwise
156          */
157         @SuppressWarnings("static-method")
158         protected boolean requiresLogin() {
159                 return true;
160         }
161
162         //
163         // PROTECTED METHODS
164         //
165
166         /**
167          * Creates a success reply.
168          *
169          * @return A reply signaling success
170          */
171         protected static JsonObject createSuccessJsonObject() {
172                 return new JsonObject().put("success", true);
173         }
174
175         /**
176          * Creates an error reply.
177          *
178          * @param error
179          *            The error that has occured
180          * @return The JSON object, signalling failure and the error code
181          */
182         protected static JsonObject createErrorJsonObject(String error) {
183                 return new JsonObject().put("success", false).put("error", error);
184         }
185
186         //
187         // PAGE METHODS
188         //
189
190         /**
191          * {@inheritDoc}
192          */
193         @Override
194         public String getPath() {
195                 return path;
196         }
197
198         /**
199          * {@inheritDoc}
200          */
201         @Override
202         public boolean isPrefixPage() {
203                 return false;
204         }
205
206         /**
207          * {@inheritDoc}
208          */
209         @Override
210         public Response handleRequest(FreenetRequest request, Response response) throws IOException {
211                 if (webInterface.getCore().getPreferences().isRequireFullAccess() && !request.getToadletContext().isAllowedFullAccess()) {
212                         return response.setStatusCode(403).setStatusText("Forbidden").setContentType("application/json").write(JsonUtils.format(new JsonObject().put("success", false).put("error", "auth-required")));
213                 }
214                 if (needsFormPassword()) {
215                         String formPassword = request.getHttpRequest().getParam("formPassword");
216                         if (!webInterface.getFormPassword().equals(formPassword)) {
217                                 return response.setStatusCode(403).setStatusText("Forbidden").setContentType("application/json").write(JsonUtils.format(new JsonObject().put("success", false).put("error", "auth-required")));
218                         }
219                 }
220                 if (requiresLogin()) {
221                         if (getCurrentSone(request.getToadletContext(), false) == null) {
222                                 return response.setStatusCode(403).setStatusText("Forbidden").setContentType("application/json").write(JsonUtils.format(new JsonObject().put("success", false).put("error", "auth-required")));
223                         }
224                 }
225                 try {
226                         JsonObject jsonObject = createJsonObject(request);
227                         return response.setStatusCode(200).setStatusText("OK").setContentType("application/json").write(JsonUtils.format(jsonObject));
228                 } catch (Exception e1) {
229                         return response.setStatusCode(500).setStatusText(e1.getMessage()).setContentType("text/plain").write(dumpStackTrace(e1));
230                 }
231         }
232
233         /**
234          * {@inheritDoc}
235          */
236         @Override
237         public boolean isLinkExcepted(URI link) {
238                 return false;
239         }
240
241         //
242         // PRIVATE METHODS
243         //
244
245         /**
246          * Returns a byte array containing the stack trace of the given throwable.
247          *
248          * @param t
249          *            The throwable whose stack trace to dump into an array
250          * @return The array with the stack trace, or an empty array if the stack
251          *         trace could not be dumped
252          */
253         private static byte[] dumpStackTrace(Throwable t) {
254                 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
255                 OutputStreamWriter writer = null;
256                 PrintWriter printWriter = null;
257                 try {
258                         writer = new OutputStreamWriter(byteArrayOutputStream, "uTF-8");
259                         printWriter = new PrintWriter(writer);
260                         t.printStackTrace(printWriter);
261                         byteArrayOutputStream.flush();
262                         return byteArrayOutputStream.toByteArray();
263                 } catch (IOException ioe1) {
264                         /* quite not possible. */
265                         return new byte[0];
266                 } finally {
267                         Closer.close(printWriter);
268                         Closer.close(writer);
269                         Closer.close(byteArrayOutputStream);
270                 }
271         }
272
273 }