import org.cacert.gigi.localisation.Language;
+/**
+ * Outputs an {@link Outputable} multiple times based on a given
+ * {@link IterableDataset}.
+ */
public final class ForeachStatement implements Translatable {
private final String variable;
private final TemplateBlock body;
+ /**
+ * Creates a new {@link ForeachStatement}.
+ *
+ * @param variable
+ * the variable to take the {@link IterableDataset} from.
+ * @param body
+ * the body to output multiple times.
+ */
public ForeachStatement(String variable, TemplateBlock body) {
this.variable = variable;
this.body = body;
import org.cacert.gigi.pages.Page;
import org.cacert.gigi.util.RandomToken;
+/**
+ * A generic HTML-form that handles CSRF-token creation.
+ */
public abstract class Form implements Outputable {
public static final String CSRF_FIELD = "csrf";
private final String action;
+ /**
+ * Creates a new {@link Form}.
+ *
+ * @param hsr
+ * the request to register the form against.
+ */
public Form(HttpServletRequest hsr) {
this(hsr, null);
}
+ /**
+ * Creates a new {@link Form}.
+ *
+ * @param hsr
+ * the request to register the form against.
+ * @param action
+ * the target path where the form should be submitted
+ */
public Form(HttpServletRequest hsr, String action) {
csrf = RandomToken.generateToken(32);
this.action = action;
hs.setAttribute("form/" + getClass().getName() + "/" + csrf, this);
}
+ /**
+ * Update the forms internal state based on submitted data.
+ *
+ * @param out
+ * the stream to the user.
+ * @param req
+ * the request to take the initial data from
+ * @return true, iff the form succeeded an the user should be redirected.
+ * @throws GigiApiException
+ * if internal operations went wrong.
+ */
public abstract boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException;
protected String getCsrfFieldName() {
out.println("'></form>");
}
+ /**
+ * Outputs the forms contents.
+ *
+ * @param out
+ * Stream to the user
+ * @param l
+ * {@link Language} to translate text to
+ * @param vars
+ * Variables supplied from the outside.
+ */
protected abstract void outputContent(PrintWriter out, Language l, Map<String, Object> vars);
- boolean failed;
+ private boolean failed;
protected void outputError(PrintWriter out, ServletRequest req, String text, Object... contents) {
if ( !failed) {
return csrf;
}
+ /**
+ * Re-fetches a form e.g. when a Post-request is received.
+ *
+ * @param req
+ * the request that is directed to the form.
+ * @param target
+ * the {@link Class} of the expected form
+ * @return the form where this request is directed to.
+ * @throws CSRFException
+ * if no CSRF-token is found or the token is wrong.
+ */
public static <T extends Form> T getForm(HttpServletRequest req, Class<T> target) throws CSRFException {
String csrf = req.getParameter(CSRF_FIELD);
if (csrf == null) {
import org.cacert.gigi.localisation.Language;
+/**
+ * One ore two {@link Outputable}s that are emitted conditionally if a given
+ * variable is neither <code>null</code> nor {@link Boolean#FALSE}.
+ */
public final class IfStatement implements Translatable {
private final String variable;
private final TemplateBlock iffalse;
+ /**
+ * Creates a new {@link IfStatement} with an empty else-part.
+ *
+ * @param variable
+ * the variable to check
+ * @param body
+ * the body to emit conditionally.
+ */
public IfStatement(String variable, TemplateBlock body) {
this.variable = variable;
this.iftrue = body;
this.iffalse = null;
}
+ /**
+ * Creates a new {@link IfStatement} with an else-block.
+ *
+ * @param variable
+ * the variable to check
+ * @param iftrue
+ * the block to emit if the check succeeds.
+ * @param iffalse
+ * the block to emit if the check fails
+ */
public IfStatement(String variable, TemplateBlock iftrue, TemplateBlock iffalse) {
this.variable = variable;
this.iftrue = iftrue;
import org.cacert.gigi.localisation.Language;
/**
- * Represents some kind of data, that may be iterated over in a template.
+ * Represents some kind of data, that may be iterated over in a template using
+ * the <code>foreach</code> statement.
*/
public interface IterableDataset {
import org.cacert.gigi.localisation.Language;
+/**
+ * Emits a variable.
+ */
public final class OutputVariableCommand implements Translatable {
private final String raw;
private final boolean unescaped;
+ /**
+ * Creates a new OutputVariableCommand.
+ *
+ * @param raw
+ * the variable to emit. If starting with <code>!</code> the
+ * variable is emitted non-HTML-escaped.
+ */
public OutputVariableCommand(String raw) {
if (raw.charAt(0) == '!') {
unescaped = true;
import org.cacert.gigi.localisation.Language;
+/**
+ * An object that is outputable to the user normally in an HTML-page.
+ */
public interface Outputable {
+ /**
+ * Writes this object's content to the given output stream.
+ *
+ * @param out
+ * the PrintWriter to the user.
+ * @param l
+ * the {@link Language} to translate localizable strings to.
+ * @param vars
+ * a map of variable assignments for this template.
+ */
public void output(PrintWriter out, Language l, Map<String, Object> vars);
}
import org.cacert.gigi.localisation.Language;
+/**
+ * Generic implementation of {@link IterableDataset} that is fed by an array.
+ */
public class OutputableArrayIterable implements IterableDataset {
- Object[] content;
+ private Object[] content;
- String targetName;
+ private String targetName;
- int index = 0;
+ private int index = 0;
+ /**
+ * Creates a new {@link OutputableArrayIterable}.
+ *
+ * @param content
+ * the objects to be iterated over.
+ * @param targetName
+ * the variable where the contents of the array to be put in the
+ * loop.
+ */
public OutputableArrayIterable(Object[] content, String targetName) {
this.content = content;
this.targetName = targetName;
import org.cacert.gigi.localisation.Language;
+/**
+ * Builds a variable scope around another {@link Outputable}, statically filling
+ * variables.
+ */
public class Scope implements Outputable {
private Map<String, Object> vars;
private Outputable out;
+ /**
+ * Creates a new {@link Scope}.
+ *
+ * @param out
+ * the enclosed {@link Outputable}
+ * @param vars
+ * the variables to assign in the inner scope.
+ */
public Scope(Outputable out, Map<String, Object> vars) {
this.out = out;
this.vars = vars;
import org.cacert.gigi.localisation.Language;
import org.cacert.gigi.util.HTMLEncoder;
+/**
+ * A pattern that is to be translated before variables are inserted.
+ */
public final class SprintfCommand implements Translatable {
private final String text;
private final String[] store;
+ /**
+ * Creates a new SprintfCommand based on its pre-parsed contents
+ *
+ * @param text
+ * a string with <code>{0},{1},..</code> as placeholders.
+ * @param store
+ * the data to put into the placeholders: ${var}, $!{var},
+ * !'plain'.
+ */
public SprintfCommand(String text, List<String> store) {
this.text = text;
this.store = store.toArray(new String[store.size()]);
private static final Pattern processingInstruction = Pattern.compile("(" + VARIABLE + ")|(!'[^{}'\\$]*)'");
- public SprintfCommand(String content) {
+ /**
+ * Creates a new SprintfCommand that is parsed as from template source.
+ *
+ * @param content
+ * the part from the template that is to be parsed
+ */
+ protected SprintfCommand(String content) {
StringBuffer raw = new StringBuffer();
List<String> var = new LinkedList<String>();
int counter = 0;
s.add(text);
}
+ /**
+ * Creates a simple {@link SprintfCommand} wrapped in a {@link Scope} to fit
+ * in now constant variables into this template.
+ *
+ * @param msg
+ * the message (to be translated) with <code>{0},{1},...</code>
+ * as placeholders.
+ * @param vars
+ * the variables to put into the placeholders.
+ * @return the constructed {@link Outputable}
+ */
public static Outputable createSimple(String msg, String... vars) {
HashMap<String, Object> scope = new HashMap<>();
String[] store = new String[vars.length];
import org.cacert.gigi.util.DayDate;
import org.cacert.gigi.util.HTMLEncoder;
+/**
+ * Represents a loaded template file.
+ */
public class Template implements Outputable {
private static class ParseResult {
private static final Pattern ELSE_PATTERN = Pattern.compile(" ?\\} ?else ?\\{ ?");
+ /**
+ * Creates a new template by parsing the contents from the given URL. This
+ * constructor will fail on syntax error. When the URL points to a file,
+ * {@link File#lastModified()} is monitored for changes of the template.
+ *
+ * @param u
+ * the URL to load the template from. UTF-8 is chosen as charset.
+ */
public Template(URL u) {
try {
Reader r = new InputStreamReader(u.openStream(), "UTF-8");
}
}
+ /**
+ * Creates a new template by parsing the contents from the given reader.
+ * This constructor will fail on syntax error.
+ *
+ * @param r
+ * the Reader containing the data.
+ */
public Template(Reader r) {
try {
data = parse(r).getBlock(null);
import java.util.Collection;
+/**
+ * An {@link Outputable} that wants to give static strings to translation
+ * collection.
+ */
public interface Translatable extends Outputable {
+ /**
+ * Adds all static translation Strings to the given {@link Collection}.
+ *
+ * @param s
+ * the {@link Collection} to add the Strings to.
+ */
public void addTranslations(Collection<String> s);
}
import org.cacert.gigi.localisation.Language;
import org.cacert.gigi.util.HTMLEncoder;
+/**
+ * Wraps a String that needs to be translated before it is printed to the user.
+ */
public final class TranslateCommand implements Translatable {
private final String raw;
+ /**
+ * Creates a new TranslateCommand that wraps the given String.
+ *
+ * @param raw
+ * the String to be translated.
+ */
public TranslateCommand(String raw) {
this.raw = raw;
}
out.print(HTMLEncoder.encodeHTML(l.getTranslation(raw)));
}
+ /**
+ * Gets the raw, untranslated String.
+ *
+ * @return the raw, untranslated String.
+ */
public String getRaw() {
return raw;
}
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class Verify extends Page {
- private static final SprintfCommand emailAddressVerified = new SprintfCommand("Email address ${subject} verified");
+ private static final SprintfCommand emailAddressVerified = new SprintfCommand("Email address {0} verified", Arrays.asList("${subject}"));
- private static final SprintfCommand domainVerified = new SprintfCommand("Domain ${subject} verified");
+ private static final SprintfCommand domainVerified = new SprintfCommand("Domain {0} verified", Arrays.asList("${subject}"));
private class VerificationForm extends Form {
}
out.println(l.getTranslation("with"));
- out.println(vars.get("loginMethod"));
+ ((Outputable) vars.get("loginMethod")).output(out, l, vars);
+ out.println();
out.println("</div>");
if (supporterTicketId != null) {
out.println("<div>");
package org.cacert.gigi;
+import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.io.IOException;
+import java.net.URLConnection;
+import org.cacert.gigi.testUtils.IOUtils;
import org.cacert.gigi.testUtils.ManagedTest;
import org.junit.Test;
get(cookie, "/logout").getHeaderField("Location");
}
+ @Test
+ public void testLoginMethodDisplay() throws IOException {
+ String email = createUniqueName() + "@testmail.org";
+ createVerifiedUser("an", "bn", email, TEST_PASSWORD);
+ String l = login(email, TEST_PASSWORD);
+ URLConnection c = get(l, "");
+ String readURL = IOUtils.readURL(c);
+ assertThat(readURL, containsString("Password"));
+ }
+
}