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