--- /dev/null
+/*
+ * shortener - CSSPage.java - Copyright © 2010 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.page;
+
+import java.io.InputStream;
+
+/**
+ * {@link Page} implementation that delivers CSS files from the class path.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class CSSPage implements Page {
+
+ /** The prefix for {@link #getPath()}. */
+ private final String pathPrefix;
+
+ /** The path used as prefix when loading resources. */
+ private final String resourcePathPrefix;
+
+ /**
+ * Creates a new CSS page.
+ *
+ * @param pathPrefix
+ * The prefix for {@link #getPath()}
+ * @param resourcePathPrefix
+ * The path prefix when loading resources
+ */
+ public CSSPage(String pathPrefix, String resourcePathPrefix) {
+ this.pathPrefix = pathPrefix;
+ this.resourcePathPrefix = resourcePathPrefix;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getPath() {
+ return pathPrefix;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Response handleRequest(Request request) {
+ String path = request.getURI().getPath();
+ int lastSlash = path.lastIndexOf('/');
+ String cssFilename = path.substring(lastSlash + 1);
+ InputStream cssInputStream = getClass().getResourceAsStream(resourcePathPrefix + cssFilename);
+ if (cssInputStream == null) {
+ return new Response(404, "Not found.", null, (String) null);
+ }
+ return new Response(200, "OK", "text/css", null, cssInputStream);
+ }
+
+}
--- /dev/null
+/*
+ * shortener - Page.java - Copyright © 2010 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.page;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import freenet.clients.http.ToadletContext;
+import freenet.support.api.HTTPRequest;
+
+/**
+ * A page is responsible for handling HTTP requests and creating appropriate
+ * responses.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface Page {
+
+ /**
+ * Returns the path of this toadlet.
+ *
+ * @return The path of this toadlet
+ */
+ public String getPath();
+
+ /**
+ * Handles a request.
+ *
+ * @param request
+ * The request to handle
+ * @return The response
+ */
+ public Response handleRequest(Request request);
+
+ /**
+ * Container for request data.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+ public class Request {
+
+ /** The URI that was accessed. */
+ private final URI uri;
+
+ /** The HTTP method that was used. */
+ private final String method;
+
+ /** The HTTP request. */
+ private final HTTPRequest httpRequest;
+
+ /** The toadlet context. */
+ private final ToadletContext toadletContext;
+
+ /**
+ * Creates a new request that holds the given data.
+ *
+ * @param uri
+ * The URI of the request
+ * @param method
+ * The HTTP method of the request
+ * @param httpRequest
+ * The HTTP request
+ * @param toadletContext
+ * The toadlet context of the request
+ */
+ public Request(URI uri, String method, HTTPRequest httpRequest, ToadletContext toadletContext) {
+ this.uri = uri;
+ this.method = method;
+ this.httpRequest = httpRequest;
+ this.toadletContext = toadletContext;
+ }
+
+ /**
+ * Returns the URI that was accessed.
+ *
+ * @return The accessed URI
+ */
+ public URI getURI() {
+ return uri;
+ }
+
+ /**
+ * Returns the HTTP method that was used to access the page.
+ *
+ * @return The HTTP method
+ */
+ public String getMethod() {
+ return method;
+ }
+
+ /**
+ * Returns the HTTP request.
+ *
+ * @return The HTTP request
+ */
+ public HTTPRequest getHttpRequest() {
+ return httpRequest;
+ }
+
+ /**
+ * Returns the toadlet context.
+ *
+ * @return The toadlet context
+ */
+ public ToadletContext getToadletContext() {
+ return toadletContext;
+ }
+
+ }
+
+ /**
+ * Container for the HTTP response of a {@link Page}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+ public class Response {
+
+ /** The HTTP status code of the response. */
+ private final int statusCode;
+
+ /** The HTTP status text of the response. */
+ private final String statusText;
+
+ /** The content type of the response. */
+ private final String contentType;
+
+ /** The headers of the response. */
+ private final Map<String, String> headers;
+
+ /** The content of the response body. */
+ private final InputStream content;
+
+ /**
+ * Creates a new response.
+ *
+ * @param statusCode
+ * The HTTP status code of the response
+ * @param statusText
+ * The HTTP status text of the response
+ * @param contentType
+ * The content type of the response
+ * @param text
+ * The text in the response body
+ */
+ public Response(int statusCode, String statusText, String contentType, String text) {
+ this(statusCode, statusText, contentType, getBytes(text));
+ }
+
+ /**
+ * Creates a new response.
+ *
+ * @param statusCode
+ * The HTTP status code of the response
+ * @param statusText
+ * The HTTP status text of the response
+ * @param contentType
+ * The content type of the response
+ * @param content
+ * The content of the reponse body
+ */
+ public Response(int statusCode, String statusText, String contentType, byte[] content) {
+ this(statusCode, statusText, contentType, null, content);
+ }
+
+ /**
+ * Creates a new response.
+ *
+ * @param statusCode
+ * The HTTP status code of the response
+ * @param statusText
+ * The HTTP status text of the response
+ * @param contentType
+ * The content type of the response
+ * @param headers
+ * The headers of the response
+ */
+ public Response(int statusCode, String statusText, String contentType, Map<String, String> headers) {
+ this(statusCode, statusText, contentType, headers, (InputStream) null);
+ }
+
+ /**
+ * Creates a new response.
+ *
+ * @param statusCode
+ * The HTTP status code of the response
+ * @param statusText
+ * The HTTP status text of the response
+ * @param contentType
+ * The content type of the response
+ * @param headers
+ * The headers of the response
+ * @param content
+ * The content of the reponse body
+ */
+ public Response(int statusCode, String statusText, String contentType, Map<String, String> headers, byte[] content) {
+ this(statusCode, statusText, contentType, headers, new ByteArrayInputStream(content));
+ }
+
+ /**
+ * Creates a new response.
+ *
+ * @param statusCode
+ * The HTTP status code of the response
+ * @param statusText
+ * The HTTP status text of the response
+ * @param contentType
+ * The content type of the response
+ * @param headers
+ * The headers of the response
+ * @param content
+ * The content of the reponse body
+ */
+ public Response(int statusCode, String statusText, String contentType, Map<String, String> headers, InputStream content) {
+ this.statusCode = statusCode;
+ this.statusText = statusText;
+ this.contentType = contentType;
+ this.headers = headers;
+ this.content = content;
+ }
+
+ /**
+ * Returns the HTTP status code of the response.
+ *
+ * @return The HTTP status code
+ */
+ public int getStatusCode() {
+ return statusCode;
+ }
+
+ /**
+ * Returns the HTTP status text.
+ *
+ * @return The HTTP status text
+ */
+ public String getStatusText() {
+ return statusText;
+ }
+
+ /**
+ * Returns the content type of the response.
+ *
+ * @return The content type of the reponse
+ */
+ public String getContentType() {
+ return contentType;
+ }
+
+ /**
+ * Returns HTTP headers of the response. May be {@code null} if no
+ * headers are returned.
+ *
+ * @return The response headers, or {@code null} if there are no
+ * response headers
+ */
+ public Map<String, String> getHeaders() {
+ return headers;
+ }
+
+ /**
+ * Returns the content of the response body. May be {@code null} if the
+ * response does not have a body.
+ *
+ * @return The content of the response body
+ */
+ public InputStream getContent() {
+ return content;
+ }
+
+ //
+ // PRIVATE METHODS
+ //
+
+ /**
+ * Returns the UTF-8 representation of the given text.
+ *
+ * @param text
+ * The text to encode
+ * @return The encoded text
+ */
+ private static byte[] getBytes(String text) {
+ try {
+ return text.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException uee1) {
+ /* every JVM needs to support UTF-8. */
+ }
+ return null;
+ }
+
+ /**
+ * Creates a header map containing a single header.
+ *
+ * @param name
+ * The name of the header
+ * @param value
+ * The value of the header
+ * @return The map containing the single header
+ */
+ protected static Map<String, String> createHeader(String name, String value) {
+ Map<String, String> headers = new HashMap<String, String>();
+ headers.put(name, value);
+ return headers;
+ }
+
+ }
+
+ /**
+ * {@link Response} implementation that performs an HTTP redirect.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+ public class RedirectResponse extends Response {
+
+ /**
+ * Creates a new redirect response to the new location.
+ *
+ * @param newLocation
+ * The new location
+ */
+ public RedirectResponse(String newLocation) {
+ this(newLocation, true);
+ }
+
+ /**
+ * Creates a new redirect response to the new location.
+ *
+ * @param newLocation
+ * The new location
+ * @param permanent
+ * Whether the redirect should be marked as permanent
+ */
+ public RedirectResponse(String newLocation, boolean permanent) {
+ super(permanent ? 302 : 307, "Redirected", null, createHeader("Location", newLocation));
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * shortener - PageToadlet.java - Copyright © 2010 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.page;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Map.Entry;
+
+import freenet.client.HighLevelSimpleClient;
+import freenet.clients.http.LinkEnabledCallback;
+import freenet.clients.http.Toadlet;
+import freenet.clients.http.ToadletContext;
+import freenet.clients.http.ToadletContextClosedException;
+import freenet.support.MultiValueTable;
+import freenet.support.api.Bucket;
+import freenet.support.api.HTTPRequest;
+import freenet.support.io.BucketTools;
+import freenet.support.io.Closer;
+
+/**
+ * {@link Toadlet} implementation that is wrapped around a {@link Page}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PageToadlet extends Toadlet implements LinkEnabledCallback {
+
+ /** The name of the menu item. */
+ private final String menuName;
+
+ /** The page that handles processing. */
+ private final Page page;
+
+ /** The path prefix for the page. */
+ private final String pathPrefix;
+
+ /**
+ * Creates a new toadlet that hands off processing to a {@link Page}.
+ *
+ * @param highLevelSimpleClient
+ * @param menuName
+ * The name of the menu item
+ * @param page
+ * The page to handle processing
+ * @param pathPrefix
+ * Prefix that is prepended to all {@link Page#getPath()} return
+ * values
+ */
+ protected PageToadlet(HighLevelSimpleClient highLevelSimpleClient, String menuName, Page page, String pathPrefix) {
+ super(highLevelSimpleClient);
+ this.menuName = menuName;
+ this.page = page;
+ this.pathPrefix = pathPrefix;
+ }
+
+ /**
+ * Returns the name to display in the menu.
+ *
+ * @return The name in the menu
+ */
+ public String getMenuName() {
+ return menuName;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String path() {
+ return pathPrefix + page.getPath();
+ }
+
+ /**
+ * Handles a HTTP GET request.
+ *
+ * @param uri
+ * The URI of the request
+ * @param httpRequest
+ * The HTTP request
+ * @param toadletContext
+ * The toadlet context
+ * @throws IOException
+ * if an I/O error occurs
+ * @throws ToadletContextClosedException
+ * if the toadlet context is closed
+ */
+ public void handleMethodGET(URI uri, HTTPRequest httpRequest, ToadletContext toadletContext) throws IOException, ToadletContextClosedException {
+ handleRequest(new Page.Request(uri, "GET", httpRequest, toadletContext));
+ }
+
+ /**
+ * Handles a HTTP POST request.
+ *
+ * @param uri
+ * The URI of the request
+ * @param httpRequest
+ * The HTTP request
+ * @param toadletContext
+ * The toadlet context
+ * @throws IOException
+ * if an I/O error occurs
+ * @throws ToadletContextClosedException
+ * if the toadlet context is closed
+ */
+ public void handleMethodPOST(URI uri, HTTPRequest httpRequest, ToadletContext toadletContext) throws IOException, ToadletContextClosedException {
+ handleRequest(new Page.Request(uri, "POST", httpRequest, toadletContext));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return getClass().getName() + "[path=" + path() + ",page=" + page + "]";
+ }
+
+ /**
+ * Handles a HTTP request.
+ *
+ * @param pageRequest
+ * The request to handle
+ * @throws IOException
+ * if an I/O error occurs
+ * @throws ToadletContextClosedException
+ * if the toadlet context is closed
+ */
+ private void handleRequest(Page.Request pageRequest) throws IOException, ToadletContextClosedException {
+ Bucket data = null;
+ try {
+ Page.Response pageResponse = page.handleRequest(pageRequest);
+ MultiValueTable<String, String> headers = new MultiValueTable<String, String>();
+ if (pageResponse.getHeaders() != null) {
+ for (Entry<String, String> headerEntry : pageResponse.getHeaders().entrySet()) {
+ headers.put(headerEntry.getKey(), headerEntry.getValue());
+ }
+ }
+ data = pageRequest.getToadletContext().getBucketFactory().makeBucket(-1);
+ if (pageResponse.getContent() != null) {
+ try {
+ BucketTools.copyFrom(data, pageResponse.getContent(), -1);
+ } finally {
+ Closer.close(pageResponse.getContent());
+ }
+ } else {
+ /* get an OutputStream and close it immediately. */
+ Closer.close(data.getOutputStream());
+ }
+ writeReply(pageRequest.getToadletContext(), pageResponse.getStatusCode(), pageResponse.getContentType(), pageResponse.getStatusText(), headers, data);
+ } catch (Throwable t1) {
+ writeInternalError(t1, pageRequest.getToadletContext());
+ } finally {
+ Closer.close(data);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isEnabled(ToadletContext toadletContext) {
+ return true;
+ }
+
+}
--- /dev/null
+/*
+ * shortener - PageToadletFactory.java - Copyright © 2010 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.page;
+
+import freenet.client.HighLevelSimpleClient;
+
+/**
+ * Factory that creates {@link PageToadlet}s using a given
+ * {@link HighLevelSimpleClient}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PageToadletFactory {
+
+ /** The client to use when creating the toadlets. */
+ private final HighLevelSimpleClient highLevelSimpleClient;
+
+ /** The prefix for all pages’ paths. */
+ private final String pathPrefix;
+
+ /**
+ * Creates a new {@link PageToadlet} factory.
+ *
+ * @param highLevelSimpleClient
+ * The client to use when creating the toadlets
+ * @param pathPrefix
+ * The path that is prepended to all pages’ paths
+ */
+ public PageToadletFactory(HighLevelSimpleClient highLevelSimpleClient, String pathPrefix) {
+ this.highLevelSimpleClient = highLevelSimpleClient;
+ this.pathPrefix = pathPrefix;
+ }
+
+ /**
+ * Creates a {@link PageToadlet} that wraps the given page and does not
+ * appear in the node’s menu.
+ *
+ * @param page
+ * The page to wrap
+ * @return The toadlet wrapped around the page
+ */
+ public PageToadlet createPageToadlet(Page page) {
+ return createPageToadlet(page, null);
+ }
+
+ /**
+ * Creates a {@link PageToadlet} that wraps the given page and appears in
+ * the node’s menu under the given name.
+ *
+ * @param page
+ * The page to wrap
+ * @param menuName
+ * The name of the menu item
+ * @return The toadlet wrapped around the page
+ */
+ public PageToadlet createPageToadlet(Page page, String menuName) {
+ return new PageToadlet(highLevelSimpleClient, menuName, page, pathPrefix);
+ }
+
+}
--- /dev/null
+/*
+ * shortener - TemplatePage.java - Copyright © 2010 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.page;
+
+import java.io.StringWriter;
+import java.util.Collection;
+import java.util.Collections;
+
+import net.pterodactylus.util.template.Template;
+import freenet.clients.http.PageMaker;
+import freenet.clients.http.PageNode;
+import freenet.clients.http.ToadletContext;
+import freenet.l10n.BaseL10n;
+
+/**
+ * Base class for all {@link Page}s that are rendered with {@link Template}s.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class TemplatePage implements Page {
+
+ /** The path of the page. */
+ private final String path;
+
+ /** The template to render. */
+ private final Template template;
+
+ /** The L10n handler. */
+ private final BaseL10n l10n;
+
+ /** The l10n key for the page title. */
+ private final String pageTitleKey;
+
+ /**
+ * Creates a new template page.
+ *
+ * @param path
+ * The path of the page
+ * @param template
+ * The template to render
+ * @param l10n
+ * The L10n handler
+ * @param pageTitleKey
+ * The l10n key of the title page
+ */
+ public TemplatePage(String path, Template template, BaseL10n l10n, String pageTitleKey) {
+ this.path = path;
+ this.template = template;
+ this.l10n = l10n;
+ this.pageTitleKey = pageTitleKey;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Response handleRequest(Request request) {
+ String redirectTarget = getRedirectTarget(request);
+ if (redirectTarget != null) {
+ return new RedirectResponse(redirectTarget);
+ }
+
+ ToadletContext toadletContext = request.getToadletContext();
+ PageMaker pageMaker = toadletContext.getPageMaker();
+ PageNode pageNode = pageMaker.getPageNode(l10n.getString(pageTitleKey), toadletContext);
+ for (String styleSheet : getStyleSheets()) {
+ pageNode.addCustomStyleSheet(styleSheet);
+ }
+
+ processTemplate(request, template);
+ StringWriter stringWriter = new StringWriter();
+ template.render(stringWriter);
+ pageNode.content.addChild("%", stringWriter.toString());
+
+ return new Response(200, "OK", "text/html", pageNode.outer.generate());
+ }
+
+ /**
+ * Can be overridden to return a custom set of style sheets that are to be
+ * included in the page’s header.
+ *
+ * @return Additional style sheets to load
+ */
+ protected Collection<String> getStyleSheets() {
+ return Collections.emptySet();
+ }
+
+ /**
+ * Can be overridden when extending classes need to set variables in the
+ * template before it is rendered.
+ *
+ * @param request
+ * The request that is rendered
+ * @param template
+ * The template to set variables in
+ */
+ protected void processTemplate(Request request, Template template) {
+ /* do nothing. */
+ }
+
+ /**
+ * Can be overridden to redirect the user to a different page, in case a log
+ * in is required, or something else is wrong.
+ *
+ * @param request
+ * The request that is processed
+ * @return The URL to redirect to, or {@code null} to not redirect
+ */
+ protected String getRedirectTarget(Page.Request request) {
+ return null;
+ }
+
+}