1 package org.cacert.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 org.cacert.gigi.GigiApiException;
13 import org.cacert.gigi.localisation.Language;
14 import org.cacert.gigi.pages.Page;
15 import org.cacert.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 instanceof RedirectResult) {
140 resp.sendRedirect(((RedirectResult) res).target);
143 if (res.endsForm()) {
144 HttpSession hs = req.getSession();
145 hs.removeAttribute("form/" + getClass().getName() + "/" + csrf);
148 } catch (PermamentFormException e) {
149 req.setAttribute(SUBMIT_RESULT, e);
151 } catch (GigiApiException e) {
152 req.setAttribute(SUBMIT_RESULT, e);
158 * Prints any errors in any form submits on this request.
161 * The request to extract the errors from.
163 * the output stream to the user to write the errors to.
164 * @return true if no permanent errors occurred and the form should be
165 * reprinted (and it has not already been successfully submitted)
167 public static boolean printFormErrors(HttpServletRequest req, PrintWriter out) {
168 Object o = req.getAttribute(SUBMIT_RESULT);
169 if (o != null && (o instanceof PermamentFormException)) {
170 ((PermamentFormException) o).getCause().format(out, Page.getLanguage(req));
173 if (o != null && (o instanceof GigiApiException)) {
174 ((GigiApiException) o).format(out, Page.getLanguage(req));
177 if (o != null && (o instanceof FormContinue)) {
180 if (o != null && (o instanceof SuccessMessageResult)) {
181 Outputable message = ((SuccessMessageResult) o).message;
182 if (message != null) {
183 out.println("<div class='alert alert-success'>");
184 message.output(out, Page.getLanguage(req), new HashMap<String, Object>());
185 out.println("</div>");
192 protected String getCsrfFieldName() {
197 public void output(PrintWriter out, Language l, Map<String, Object> vars) {
198 if (action == null) {
199 out.println("<form method='POST'>");
201 out.println("<form method='POST' action='" + action + "'>");
203 outputContent(out, l, vars);
204 out.print("<input type='hidden' name='" + CSRF_FIELD + "' value='");
205 out.print(getCSRFToken());
206 out.println("'></form>");
210 * Outputs the forms contents.
213 * Stream to the user.
215 * {@link Language} to translate text to.
217 * Variables supplied from the outside.
219 protected abstract void outputContent(PrintWriter out, Language l, Map<String, Object> vars);
221 protected String getCSRFToken() {
226 * Re-fetches a form e.g. when a Post-request is received.
229 * the request that is directed to the form.
231 * the {@link Class} of the expected form.
232 * @return the form where this request is directed to.
233 * @throws CSRFException
234 * if no CSRF-token is found or the token is wrong.
236 @SuppressWarnings("unchecked")
237 public static <T extends Form> T getForm(HttpServletRequest req, Class<T> target) throws CSRFException {
238 String csrf = req.getParameter(CSRF_FIELD);
240 throw new CSRFException();
242 HttpSession hs = req.getSession();
244 throw new CSRFException();
246 Object f = hs.getAttribute("form/" + target.getName() + "/" + csrf);
248 throw new CSRFException();
250 if ( !(f instanceof Form)) {
251 throw new CSRFException();
253 if ( !target.isInstance(f)) {
254 throw new CSRFException();
256 // Dynamic Cast checked by previous if statement
260 public static class CSRFException extends IOException {
262 private static final long serialVersionUID = 59708247477988362L;