Package freenet.clients.http

Source Code of freenet.clients.http.Toadlet

/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.clients.http;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URI;

import freenet.client.FetchContext;
import freenet.client.FetchException;
import freenet.client.FetchResult;
import freenet.client.FetchWaiter;
import freenet.client.HighLevelSimpleClient;
import freenet.client.InsertBlock;
import freenet.client.InsertException;
import freenet.client.async.ClientGetter;
import freenet.keys.FreenetURI;
import freenet.l10n.NodeL10n;
import freenet.node.RequestClient;
import freenet.support.HTMLEncoder;
import freenet.support.HTMLNode;
import freenet.support.Logger;
import freenet.support.MultiValueTable;
import freenet.support.api.Bucket;
import freenet.support.api.HTTPRequest;

/**
* API similar to Servlets. Originally the reason for not using servlets was to support
* continuations, but that hasn't been implemented, and modern servlets do support continuations
* anyway. Also it was supposed to be simpler, which may not be true any more. Many plugins wrap
* their own API around this!
* FIXME consider using servlets.
*
* Important API complexity: The methods for handling the actual requests are synthetic:
*
* Methods are handled via handleMethodGET/POST/whatever ( URI uri, HTTPRequest request, ToadletContext ctx )
* Typically this throws IOException and ToadletContextClosedException.
*/
public abstract class Toadlet {

  /** Handle a GET request.
   * Other methods are accessed via handleMethodPOST etc, invoked via reflection. But all toadlets
   * are expected to support GET.
   * @param uri The URI being fetched.
   * @param request The original HTTPRequest, convenient for e.g. fetching ?blah=blah parameters.
   * @param ctx The request context. Mainly used for sending a reply; this identifies which
   * request we are replying to. Also gives access to lots of important objects e.g. PageMaker. */
  public abstract void handleMethodGET(URI uri, HTTPRequest request, ToadletContext ctx) throws ToadletContextClosedException, IOException, RedirectException;
 
  public static final String HANDLE_METHOD_PREFIX = "handleMethod";

  public abstract String path();
 
  /**
   * When displaying this Toadlet, the web interface should show the menu from which it was selected as opened, and mark the
   * appropriate entry as selected in the menu. This function may return the Toadlet whose menu shall be opened and whose
   * entry shall be marked as selected in the menu.
   *
   * It is necessary to have this function instead of just marking <code>Toadlet.this</code> as selected:
   * Some Toadlets won't be added to a menu. They will be only accessible through other Toadlets. For example
   * a Toadlet for deleting a single download might only be accessible through the Toadlet which shows all downloads.
   * For still being able to figure out the menu entry through which those so-called invisible Toadlets where accessed,
   * this function is necessary.
   *
   * @return <code>this</code> by default. Override the function to return something else for invisible Toadlets.
   */
  public Toadlet showAsToadlet() {
    return this;
  }
 
  /**
   * Override to return true if the toadlet should handle POSTs that don't have the correct form
   * password. Otherwise they will be rejected and not passed to the toadlet.
   */
  public boolean allowPOSTWithoutPassword() {
    return false;
  }
 
  protected Toadlet(HighLevelSimpleClient client) {
    this.client = client;
  }

  final HighLevelSimpleClient client;
  ToadletContainer container;

  private String supportedMethodsCache;

  private static String l10n(String key, String pattern, String value) {
    return NodeL10n.getBase().getString("Toadlet."+key, new String[] { pattern }, new String[] { value });
  }

  private static String l10n(String key) {
    return NodeL10n.getBase().getString("Toadlet."+key);
  }

  /**
   * Which methods are supported by this Toadlet.
   * Should return a string containing the methods supported, separated by commas
   * For example: "GET, PUT" (in which case both 'handleMethodGET()' and 'handleMethodPUT()'
   * must be implemented).
   *
   * IMPORTANT: This will discover inherited methods because of getMethod()
   * below. If you do not want to expose a method implemented by a parent
   * class, then *OVERRIDE THIS METHOD*.
   */
  public final String findSupportedMethods() {
    if (supportedMethodsCache == null) {
      Method methlist[] = this.getClass().getMethods();
      StringBuilder sb = new StringBuilder();
      for (Method m: methlist) {
        String name = m.getName();
        if (name.startsWith(HANDLE_METHOD_PREFIX)) {
          sb.append(name.substring(HANDLE_METHOD_PREFIX.length()));
          sb.append(", ");
        }
      }
      if (sb.length() >= 2) {
        // remove last ", "
        sb.deleteCharAt(sb.length()-1);
        sb.deleteCharAt(sb.length()-1);
      }
      supportedMethodsCache = sb.toString();
    }
    return supportedMethodsCache;
  }

  /**
   * Client calls from the above messages to run a Freenet request.
   * This method may block (or suspend).
   * @param maxSize Maximum length of returned content.
   * @param clientContext Client context object. This should be the same for any group of related requests, but different
   * for any two unrelated requests. Request selection round-robin's over these, within any priority and retry count class,
   * and above the level of individual block fetches.
   */
  FetchResult fetch(FreenetURI uri, long maxSize, RequestClient clientContext, FetchContext fctx) throws FetchException {
    // For now, just run it blocking.
    FetchWaiter fw = new FetchWaiter(clientContext);
    @SuppressWarnings("unused")
    ClientGetter getter = client.fetch(uri, 1, fw, fctx);
    return fw.waitForCompletion();

  }
  /**
   * Returns a default FetchContext
   * @param maxSize The maximum allowable size of the fetch's result
   * @return A default FetchContext
   */
  FetchContext getFetchContext(long maxSize) {
    //We want to retrieve a FetchContext we may override
    return client.getFetchContext(maxSize);
  }

  FreenetURI insert(InsertBlock insert, String filenameHint, boolean getCHKOnly) throws InsertException {
    // For now, just run it blocking.
    insert.desiredURI.checkInsertURI();
    return client.insert(insert, getCHKOnly, filenameHint);
  }

  /**
   * Write an HTTP response, e.g. a page, an image, an error message, with no special headers.
   * @param ctx The specific request to reply to.
   * @param code The HTTP reply code to use.
   * @param mimeType The MIME type of the data we are returning.
   * @param desc The HTTP response description for the code.
   * @param data The data to write as the response body.
   * @param offset The offset within data of the first byte to send.
   * @param length The number of bytes of data to send as the response body.
   */
  protected void writeReply(ToadletContext ctx, int code, String mimeType, String desc, byte[] data, int offset, int length) throws ToadletContextClosedException, IOException {
    ctx.sendReplyHeaders(code, desc, null, mimeType, length);
    ctx.writeData(data, offset, length);
  }

  /**
   * Write an HTTP response, e.g. a page, an image, an error message, with no special headers.
   * @param ctx The specific request to reply to.
   * @param code The HTTP reply code to use.
   * @param mimeType The MIME type of the data we are returning.
   * @param desc The HTTP response description for the code.
   * @param data The Bucket which contains the reply data. This
   *        function assumes ownership of the Bucket, calling free()
   *        on it when done. If this behavior is undesired, callers
   *        can wrap their Bucket in a NoFreeBucket.
   *
   * @see freenet.support.io.NoFreeBucket
   */
  protected void writeReply(ToadletContext ctx, int code, String mimeType, String desc, Bucket data) throws ToadletContextClosedException, IOException {
    writeReply(ctx, code, mimeType, desc, null, data);
  }
 
  /**
   * Write an HTTP response, e.g. a page, an image, an error message, possibly with custom
   * headers, for example, we may want to send a redirect, or a file with a specified filename.
   * @param ctx The specific request to reply to.
   * @param code The HTTP reply code to use.
   * @param mimeType The MIME type of the data we are returning.
   * @param desc The HTTP response description for the code.
   * @param headers The additional HTTP headers to send.
   * @param data The Bucket which contains the reply data. This
   *        function assumes ownership of the Bucket, calling free()
   *        on it when done. If this behavior is undesired, callers
   *        can wrap their Bucket in a NoFreeBucket.
   *
   * @see freenet.support.io.NoFreeBucket
   */
  protected void writeReply(ToadletContext context, int code, String mimeType, String desc, MultiValueTable<String, String> headers, Bucket data) throws ToadletContextClosedException, IOException {
    context.sendReplyHeaders(code, desc, headers, mimeType, data.size());
    context.writeData(data);
  }

  /**
   * Write a text-based HTTP response, e.g. a page or an error message, with no special headers.
   * @param ctx The specific request to reply to.
   * @param code The HTTP reply code to use.
   * @param mimeType The MIME type of the data we are returning.
   * @param desc The HTTP response description for the code.
   * @param reply The reply data, as a String (so only use this for text-based replies, e.g.
   * HTML, plain text etc).
   */
  protected void writeReply(ToadletContext ctx, int code, String mimeType, String desc, String reply) throws ToadletContextClosedException, IOException {
    writeReply(ctx, code, mimeType, desc, null, reply, false);
  }
 
  /**
   * Write an HTTP response as HTML.
   * @param ctx The specific request to reply to.
   * @param code The HTTP reply code to use.
   * @param desc The HTTP response description for the code.
   * @param reply The HTML page, as a String.
   */
  protected void writeHTMLReply(ToadletContext ctx, int code, String desc, String reply) throws ToadletContextClosedException, IOException {
    writeReply(ctx, code, "text/html; charset=utf-8", desc, null, reply, false);
  }
 
  /**
   * Write an HTTP response as plain text.
   * @param ctx The specific request to reply to.
   * @param code The HTTP reply code to use.
   * @param desc The HTTP response description for the code.
   * @param reply The text of the page, as a String.
   */
  protected void writeTextReply(ToadletContext ctx, int code, String desc, String reply) throws ToadletContextClosedException, IOException {
    writeReply(ctx, code, "text/plain; charset=utf-8", desc, null, reply, true);
  }

    /**
     * Write an HTTP response as HTML, possibly with custom headers, for example, we may want to
     * send a redirect, or a file with a specified filename.
     * @param ctx The specific request to reply to.
     * @param code The HTTP reply code to use.
     * @param desc The HTTP response description for the code.
     * @param headers The additional HTTP headers to send.
     * @param reply The HTML page, as a String.
     */
    protected void writeHTMLReply(ToadletContext ctx, int code, String desc, MultiValueTable<String, String> headers, String reply) throws ToadletContextClosedException, IOException {
        writeHTMLReply(ctx, code, desc, headers, reply, false);
    }
 
  /**
   * Write an HTTP response as HTML, possibly with custom headers, for example, we may want to
   * send a redirect, or a file with a specified filename.
   * @param ctx The specific request to reply to.
   * @param code The HTTP reply code to use.
   * @param desc The HTTP response description for the code.
   * @param headers The additional HTTP headers to send.
   * @param reply The HTML page, as a String.
   */
  protected void writeHTMLReply(ToadletContext ctx, int code, String desc, MultiValueTable<String, String> headers, String reply, boolean forceDisableJavascript) throws ToadletContextClosedException, IOException {
    writeReply(ctx, code, "text/html; charset=utf-8", desc, headers, reply, forceDisableJavascript);
  }
 
  /**
   * Write an HTTP response as plain text, possibly with custom headers, for example, we may want
   * to send a redirect, or a file with a specified filename.
   * @param ctx The specific request to reply to.
   * @param code The HTTP reply code to use.
   * @param desc The HTTP response description for the code.
   * @param headers The additional HTTP headers to send.
   * @param reply The text of the page, as a String.
   */
  protected void writeTextReply(ToadletContext ctx, int code, String desc, MultiValueTable<String, String> headers, String reply) throws ToadletContextClosedException, IOException {
    writeReply(ctx, code, "text/plain; charset=utf-8", desc, headers, reply, true);
  }

  protected void writeReply(ToadletContext context, int code, String mimeType, String desc, MultiValueTable<String, String> headers, String reply) throws ToadletContextClosedException, IOException {
      writeReply(context, code, mimeType, desc, headers, reply, false);
  }
 
  protected void writeReply(ToadletContext context, int code, String mimeType, String desc, MultiValueTable<String, String> headers, String reply, boolean forceDisableJavascript) throws ToadletContextClosedException, IOException {
      byte[] buffer = reply.getBytes("UTF-8");
      writeReply(context, code, mimeType, desc, headers, buffer, 0, buffer.length, forceDisableJavascript);
  }

  /**
   * Write a generated HTTP response, e.g. a page, an image, an error message, possibly with
   * custom headers, for example, we may want to send a redirect, or a file with a specified
   * filename. This should not be used for fproxy content i.e. content downloaded from Freenet.
   * @param context The specific request to reply to.
   * @param code The HTTP reply code to use.
   * @param mimeType The MIME type of the data we are returning.
   * @param desc The HTTP response description for the code.
   * @param headers The additional HTTP headers to send.
   * @param data The data to write as the response body.
   * @param offset The offset within data of the first byte to send.
   * @param length The number of bytes of data to send as the response body.
   */
  private void writeReply(ToadletContext context, int code, String mimeType, String desc, MultiValueTable<String, String> headers, byte[] buffer, int startIndex, int length, boolean forceDisableJavascript) throws ToadletContextClosedException, IOException {
      context.sendReplyHeaders(code, desc, headers, mimeType, length, forceDisableJavascript);
    context.writeData(buffer, startIndex, length);
  }
 
  /**
   * Do a permanent redirect (HTTP Status 301).
   *
   * This will write rudimentary HTML, but typically browsers will follow
   * the Location header.
   * TODO Refactor with writeTemporaryRedirect.
   * @param ctx
   * @param msg
   * @param location
   * @throws ToadletContextClosedException
   * @throws IOException
   */
  static void writePermanentRedirect(ToadletContext ctx, String msg, String location) throws ToadletContextClosedException, IOException {
    MultiValueTable<String, String> mvt = new MultiValueTable<String, String>();
    mvt.put("Location", location);
    if(msg == null) msg = "";
    else msg = HTMLEncoder.encode(msg);
    String redirDoc =
      "<html><head><title>"+msg+"</title></head><body><h1>" +
      l10n("permRedirectWithReason", "reason", msg)+
      "</h1><a href=\""+HTMLEncoder.encode(location)+"\">"+l10n("clickHere")+"</a></body></html>";
    byte[] buf;
    try {
      buf = redirDoc.getBytes("UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw new Error("Impossible: JVM doesn't support UTF-8: " + e, e);
    }
    ctx.sendReplyHeaders(301, "Moved Permanently", mvt, "text/html; charset=UTF-8", buf.length);
    ctx.writeData(buf, 0, buf.length);
  }
 
  /**
   * Do a temporary redirect (HTTP Status 302).
   *
   * This will write rudimentary HTML, but typically browsers will follow
   * the Location header.
   * TODO Refactor with writePermanentRedirect.
   * @param ctx
   * @param msg Message to be used in HTML (not visible in general).
   * @param location New location (URL)
   * @throws ToadletContextClosedException
   * @throws IOException
   */
  protected void writeTemporaryRedirect(ToadletContext ctx, String msg, String location) throws ToadletContextClosedException, IOException {
    MultiValueTable<String, String> mvt = new MultiValueTable<String, String>();
    mvt.put("Location", location);
    if(msg == null) msg = "";
    else msg = HTMLEncoder.encode(msg);
    String redirDoc =
      "<html><head><title>"+msg+"</title></head><body><h1>" +
      l10n("tempRedirectWithReason", "reason", msg)+
      "</h1><a href=\""+HTMLEncoder.encode(location)+"\">" +
      l10n("clickHere") + "</a></body></html>";
    byte[] buf;
    try {
      buf = redirDoc.getBytes("UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw new Error("Impossible: JVM doesn't support UTF-8: " + e, e);
    }
    ctx.sendReplyHeaders(302, "Found", mvt, "text/html; charset=UTF-8", buf.length);
    ctx.writeData(buf, 0, buf.length);
  }
 
  /**
   * Send a simple error page.
   */
  protected void sendErrorPage(ToadletContext ctx, int code, String desc, String message) throws ToadletContextClosedException, IOException {
    sendErrorPage(ctx, code, desc, new HTMLNode("#", message));
  }

  /**
   * Send a slightly more complex error page.
   */
  protected void sendErrorPage(ToadletContext ctx, int code, String desc, HTMLNode message) throws ToadletContextClosedException, IOException {
    PageNode page = ctx.getPageMaker().getPageNode(desc, ctx);
    HTMLNode pageNode = page.outer;
    HTMLNode contentNode = page.content;
   
    HTMLNode infoboxContent = ctx.getPageMaker().getInfobox("infobox-error", desc, contentNode, null, true);
    infoboxContent.addChild(message);
    infoboxContent.addChild("br");
    infoboxContent.addChild("a", "href", ".", l10n("returnToPrevPage"));
    infoboxContent.addChild("br");
    addHomepageLink(infoboxContent);
   
    writeHTMLReply(ctx, code, desc, pageNode.generate());
  }

  /**
   * Send an error page from an exception.
   * @param ctx The context object for this request.
   * @param desc The title of the error page
   * @param message The message to be sent to the user. The stack trace will follow.
   * @param t The Throwable which caused the error.
   * @throws IOException If there is an error writing the reply.
   * @throws ToadletContextClosedException If the context has already been closed.
   */
  protected void sendErrorPage(ToadletContext ctx, String desc, String message, Throwable t) throws ToadletContextClosedException, IOException {
    PageNode page = ctx.getPageMaker().getPageNode(desc, ctx);
    HTMLNode pageNode = page.outer;
    HTMLNode contentNode = page.content;
   
    HTMLNode infoboxContent = ctx.getPageMaker().getInfobox("infobox-error", desc, contentNode, null, true);
    infoboxContent.addChild("#", message);
    infoboxContent.addChild("br");
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    pw.println(t);
    t.printStackTrace(pw);
    pw.close();
    // FIXME what is the modern (CSS/XHTML) equivalent of <pre>?
    infoboxContent.addChild("pre", sw.toString());
    infoboxContent.addChild("br");
    infoboxContent.addChild("a", "href", ".", l10n("returnToPrevPage"));
    addHomepageLink(infoboxContent);
   
    writeHTMLReply(ctx, 500, desc, pageNode.generate());
  }
 
  /**
   * @throws IOException See {@link #sendErrorPage(ToadletContext, int, String, String)}
   * @throws ToadletContextClosedException See {@link #sendErrorPage(ToadletContext, int, String, String)}
   */
    void sendUnauthorizedPage(ToadletContext ctx) throws ToadletContextClosedException, IOException {
        sendErrorPage(ctx, 403, NodeL10n.getBase().getString("Toadlet.unauthorizedTitle"), NodeL10n.getBase().getString("Toadlet.unauthorized"));
    }

  protected void writeInternalError(Throwable t, ToadletContext ctx) throws ToadletContextClosedException, IOException {
    Logger.error(this, "Caught "+t, t);
    String msg = "<html><head><title>"+NodeL10n.getBase().getString("Toadlet.internalErrorTitle")+
        "</title></head><body><h1>"+NodeL10n.getBase().getString("Toadlet.internalErrorPleaseReport")+"</h1><pre>";
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    while (t != null) {
      t.printStackTrace(pw);
      t = t.getCause();
    }
    pw.flush();
    msg = msg + sw.toString() + "</pre></body></html>";
    writeHTMLReply(ctx, 500, "Internal Error", msg);
  }
 
  protected static void addHomepageLink(HTMLNode content) {
    content.addChild("a", new String[]{"href", "title"}, new String[]{"/", l10n("homepage")}, l10n("returnToNodeHomepage"));
  }

  /**
   * Get the client impl. DO NOT call the blocking methods on it!!
   * Just use it for configuration etc.
   */
  protected HighLevelSimpleClient getClientImpl() {
    return client;
  }
}
TOP

Related Classes of freenet.clients.http.Toadlet

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.