1 package club.wpia.gigi.output.template;
3 import java.io.IOException;
4 import java.io.PrintWriter;
5 import java.util.HashMap;
8 import javax.servlet.http.HttpServletRequest;
9 import javax.servlet.http.HttpServletResponse;
10 import javax.servlet.http.HttpSession;
12 import club.wpia.gigi.GigiApiException;
13 import club.wpia.gigi.localisation.Language;
14 import club.wpia.gigi.pages.Page;
15 import club.wpia.gigi.util.RandomToken;
18 * A generic HTML-form that handles CSRF-token creation.
20 public abstract class Form implements Outputable {
22 public static class PermamentFormException extends RuntimeException {
24 public PermamentFormException(GigiApiException cause) {
29 public synchronized GigiApiException getCause() {
30 return (GigiApiException) super.getCause();
35 * Encapsulates a (non-failure) outcome of a form.
37 public static abstract class SubmissionResult {
39 public abstract boolean endsForm();
43 * The form has finished and the user should see the successful completion
46 public static class RedirectResult extends SubmissionResult {
48 private final String target;
50 public RedirectResult(String target) {
55 public boolean endsForm() {
62 * The form has not finished and should be re-emitted, however no error
65 public static class FormContinue extends SubmissionResult {
68 public boolean endsForm() {
74 * The form has successfully finished and a message should be emitted on a
77 public static class SuccessMessageResult extends SubmissionResult {
79 private final Outputable message;
81 public SuccessMessageResult(Outputable message) {
82 this.message = message;
86 public boolean endsForm() {
91 public static final String CSRF_FIELD = "csrf";
93 public static final String SUBMIT_RESULT = "form-submit-result";
95 private final String csrf;
97 private final String action;
100 * Creates a new {@link Form}.
103 * the request to register the form against.
105 public Form(HttpServletRequest hsr) {
110 * Creates a new {@link Form}.
113 * the request to register the form against.
115 * the target path where the form should be submitted.
117 public Form(HttpServletRequest hsr, String action) {
118 csrf = RandomToken.generateToken(32);
119 this.action = action;
120 HttpSession hs = hsr.getSession();
121 hs.setAttribute("form/" + getClass().getName() + "/" + csrf, this);
125 * Update the forms internal state based on submitted data.
128 * the request to take the initial data from.
129 * @return true, iff the form succeeded and the user should be redirected.
130 * @throws GigiApiException
131 * if form data had problems or operations went wrong.
133 public abstract SubmissionResult submit(HttpServletRequest req) throws GigiApiException;
135 public boolean submitExceptionProtected(HttpServletRequest req, HttpServletResponse resp) throws IOException {
137 SubmissionResult res = submit(req);
138 req.setAttribute(SUBMIT_RESULT, res);
139 if (res.endsForm()) {
140 HttpSession hs = req.getSession();
141 hs.removeAttribute("form/" + getClass().getName() + "/" + csrf);
143 if (res instanceof RedirectResult) {
144 resp.sendRedirect(((RedirectResult) res).target);
148 } catch (PermamentFormException e) {
149 req.setAttribute(SUBMIT_RESULT, e);
150 req.getSession().removeAttribute("form/" + getClass().getName() + "/" + csrf);
152 } catch (GigiApiException e) {
153 req.setAttribute(SUBMIT_RESULT, e);
159 * Prints any errors in any form submits on this request.
162 * The request to extract the errors from.
164 * the output stream to the user to write the errors to.
165 * @return true if no permanent errors occurred and the form should be
166 * reprinted (and it has not already been successfully submitted)
168 public static boolean printFormErrors(HttpServletRequest req, PrintWriter out) {
169 Object o = req.getAttribute(SUBMIT_RESULT);
170 if (o != null && (o instanceof PermamentFormException)) {
171 ((PermamentFormException) o).getCause().format(out, Page.getLanguage(req), Page.getDefaultVars(req));
174 if (o != null && (o instanceof GigiApiException)) {
175 ((GigiApiException) o).format(out, Page.getLanguage(req), Page.getDefaultVars(req));
178 if (o != null && (o instanceof FormContinue)) {
181 if (o != null && (o instanceof SuccessMessageResult)) {
182 Outputable message = ((SuccessMessageResult) o).message;
183 if (message != null) {
184 out.println("<div class='alert alert-success'>");
185 message.output(out, Page.getLanguage(req), new HashMap<String, Object>());
186 out.println("</div>");
193 protected String getCsrfFieldName() {
198 public void output(PrintWriter out, Language l, Map<String, Object> vars) {
199 if (action == null) {
200 out.println("<form method='POST'>");
202 out.println("<form method='POST' action='" + action + "'>");
204 outputContent(out, l, vars);
205 out.print("<input type='hidden' name='" + CSRF_FIELD + "' value='");
206 out.print(getCSRFToken());
207 out.println("'></form>");
211 * Outputs the forms contents.
214 * Stream to the user.
216 * {@link Language} to translate text to.
218 * Variables supplied from the outside.
220 protected abstract void outputContent(PrintWriter out, Language l, Map<String, Object> vars);
222 protected String getCSRFToken() {
227 * Re-fetches a form e.g. when a Post-request is received.
230 * the request that is directed to the form.
232 * the {@link Class} of the expected form.
233 * @return the form where this request is directed to.
234 * @throws CSRFException
235 * if no CSRF-token is found or the token is wrong.
237 @SuppressWarnings("unchecked")
238 public static <T extends Form> T getForm(HttpServletRequest req, Class<T> target) throws CSRFException {
239 String csrf = req.getParameter(CSRF_FIELD);
241 throw new CSRFException();
243 HttpSession hs = req.getSession();
245 throw new CSRFException();
247 Object f = hs.getAttribute("form/" + target.getName() + "/" + csrf);
249 throw new CSRFException();
251 if ( !(f instanceof Form)) {
252 throw new CSRFException();
254 if ( !target.isInstance(f)) {
255 throw new CSRFException();
257 // Dynamic Cast checked by previous if statement
261 public static class CSRFException extends IOException {
263 private static final long serialVersionUID = 59708247477988362L;