private BlockItem getBlockItem(ObjectContainer container, ClientContext context) {
   try {
     synchronized (this) {
       if (finished) return null;
     }
     if (persistent) {
       if (sourceData == null) {
         Logger.error(
             this,
             "getBlockItem(): sourceData = null but active = " + container.ext().isActive(this));
         return null;
       }
     }
     boolean deactivateBucket = false;
     if (persistent) {
       container.activate(uri, 1);
       deactivateBucket = !container.ext().isActive(sourceData);
       if (deactivateBucket) container.activate(sourceData, 1);
     }
     Bucket data = sourceData.createShadow();
     FreenetURI u = uri;
     if (u.getKeyType().equals("CHK") && !persistent) u = FreenetURI.EMPTY_CHK_URI;
     else u = u.clone();
     if (data == null) {
       data = context.tempBucketFactory.makeBucket(sourceData.size());
       BucketTools.copy(sourceData, data);
     }
     if (persistent) {
       if (deactivateBucket) container.deactivate(sourceData, 1);
       container.deactivate(uri, 1);
     }
     return new BlockItem(
         this, data, isMetadata, compressionCodec, sourceLength, u, hashCode(), persistent);
   } catch (IOException e) {
     fail(new InsertException(InsertException.BUCKET_ERROR, e, null), container, context);
     return null;
   }
 }
Example #2
0
  private void makeMainPage(
      ToadletContext ctx,
      List<String> errors,
      String key,
      int hexWidth,
      boolean automf,
      boolean deep,
      boolean ml)
      throws ToadletContextClosedException, IOException, RedirectException, URISyntaxException {
    PageNode page = pluginContext.pageMaker.getPageNode(i18n("KeyExplorer.PageTitle"), ctx);
    HTMLNode pageNode = page.outer;
    HTMLNode contentNode = page.content;

    byte[] data = null;
    GetResult getresult = null;
    String extraParams = "&hexwidth=" + hexWidth;
    if (automf) {
      extraParams += "&automf=checked";
    }
    if (deep) {
      extraParams += "&deep=checked";
    }
    if (ml) {
      extraParams += "&ml=checked";
    }
    FreenetURI furi = null;
    FreenetURI retryUri = null;

    try {
      if (key != null && (key.trim().length() > 0)) {
        furi =
            URISanitizer.sanitizeURI(
                errors,
                key,
                false,
                URISanitizer.Options.NOMETASTRINGS,
                URISanitizer.Options.SSKFORUSK);
        retryUri = furi;
        if (ml) { // multilevel is requestet
          Metadata tempMD =
              KeyExplorerUtils.simpleManifestGet(pluginContext.pluginRespirator, furi);
          FetchResult tempResult =
              KeyExplorerUtils.splitGet(pluginContext.pluginRespirator, tempMD);
          getresult = new GetResult(tempResult.asBucket(), true);
          data = tempResult.asByteArray();
        } else { // normal get
          getresult = KeyExplorerUtils.simpleGet(pluginContext.pluginRespirator, furi);
          data = BucketTools.toByteArray(getresult.getData());
        }
      }
    } catch (MalformedURLException e) {
      errors.add("MalformedURL: " + key);
    } catch (IOException e) {
      Logger.error(this, "500", e);
      errors.add("IO Error: " + e.getMessage());
    } catch (MetadataParseException e) {
      errors.add("Metadata Parse Error: " + e.getMessage());
    } catch (FetchException e) {
      errors.add("Get failed (" + e.mode + "): " + e.getMessage());
    } catch (KeyListenerConstructionException e) {
      Logger.error(this, "Hu?", e);
      errors.add("Internal Error: " + e.getMessage());
    } finally {
      if (getresult != null) getresult.free();
    }

    HTMLNode uriBox =
        createUriBox(
            pluginContext,
            ((furi == null) ? null : furi.toString(false, false)),
            hexWidth,
            automf,
            deep,
            errors);

    if (errors.size() > 0) {
      contentNode.addChild(createErrorBox(errors, path(), retryUri, extraParams));
      errors.clear();
    }

    contentNode.addChild(uriBox);

    if (data != null) {
      Metadata md = null;

      if (getresult.isMetaData()) {
        try {
          md = Metadata.construct(data);
        } catch (MetadataParseException e) {
          errors.add("Metadata parse error: " + e.getMessage());
        }
        if (md != null) {
          if (automf && md.isArchiveManifest()) {
            if (md.getArchiveType() == ARCHIVE_TYPE.TAR) {
              writeTemporaryRedirect(
                  ctx,
                  "",
                  KeyUtilsPlugin.PLUGIN_URI
                      + "/Site/?mftype=TARmanifest&key="
                      + furi
                      + extraParams);
              return;
            } else if (md.getArchiveType() == ARCHIVE_TYPE.ZIP) {
              writeTemporaryRedirect(
                  ctx,
                  "",
                  KeyUtilsPlugin.PLUGIN_URI
                      + "/Site/?mftype=ZIPmanifest&key="
                      + furi
                      + extraParams);
              return;
            } else {
              errors.add("Unknown Archive Type: " + md.getArchiveType().name());
            }
          }
          if (automf && md.isSimpleManifest()) {
            writeTemporaryRedirect(
                ctx,
                "",
                KeyUtilsPlugin.PLUGIN_URI
                    + "/Site/?mftype=simplemanifest&key="
                    + furi
                    + extraParams);
            return;
          }
        }
      }

      String title = "Key: " + furi.toString(false, false);
      if (getresult.isMetaData()) title = title + "\u00a0(MetaData)";
      HTMLNode dataBox2 = pluginContext.pageMaker.getInfobox("#", title, contentNode);

      dataBox2.addChild("%", "<pre lang=\"en\" style=\"font-family: monospace;\">\n");
      dataBox2.addChild("#", hexDump(data, hexWidth));
      dataBox2.addChild("%", "\n</pre>");

      if (getresult.isMetaData()) {
        if (md != null) {
          HTMLNode metaBox =
              pluginContext.pageMaker.getInfobox("#", "Decomposed metadata", contentNode);

          metaBox.addChild("#", "Metadata version " + Short.toString(md.getParsedVersion()));
          metaBox.addChild("br");
          metaBox.addChild("#", "Document type:\u00a0");
          if (md.isSimpleRedirect()) {
            metaBox.addChild("#", "SimpleRedirect");
          } else if (md.isSimpleManifest()) {
            metaBox.addChild("#", "SimpleManifest");
          } else if (md.isArchiveInternalRedirect()) {
            metaBox.addChild("#", "ArchiveInternalRedirect");
          } else if (md.isArchiveMetadataRedirect()) {
            metaBox.addChild("#", "ArchiveMetadataRedirect");
          } else if (md.isArchiveManifest()) {
            metaBox.addChild("#", "ArchiveManifest");
          } else if (md.isMultiLevelMetadata()) {
            metaBox.addChild("#", "MultiLevelMetadata");
          } else if (md.isSymbolicShortlink()) {
            metaBox.addChild("#", "SymbolicShortlink");
          } else {
            metaBox.addChild("#", "<Unknown document type>");
          }
          metaBox.addChild("br");

          final String MIMEType = md.getMIMEType();
          if (MIMEType != null) {
            metaBox.addChild("#", "MIME Type: " + MIMEType);
            metaBox.addChild("br");
          }

          if (md.haveFlags()) {
            metaBox.addChild("#", "Flags:\u00a0");
            boolean isFirst = true;

            if (md.isSplitfile()) {
              metaBox.addChild("#", "SplitFile");
              isFirst = false;
            }
            if (md.isCompressed()) {
              if (isFirst) isFirst = false;
              else metaBox.addChild("#", "\u00a0");
              metaBox.addChild("#", "Compressed (" + md.getCompressionCodec().name + ")");
            }
            if (md.hasTopData()) {
              if (isFirst) isFirst = false;
              else metaBox.addChild("#", "\u00a0");
              metaBox.addChild("#", "HasTopData");
            }
            if (isFirst) metaBox.addChild("#", "<No flag set>");
          }
          metaBox.addChild("br");

          if (md.isCompressed()) {
            metaBox.addChild("#", "Decompressed size: " + md.uncompressedDataLength() + " bytes.");
          } else {
            metaBox.addChild("#", "Uncompressed");
          }

          metaBox.addChild("br");

          if (md.topCompatibilityMode != 0) {
            metaBox.addChild("#", "Compatibility mode: " + md.getTopCompatibilityMode().toString());
            metaBox.addChild("br");
          }

          if (md.hasTopData()) {
            metaBox.addChild("#", "Top Block Data:");
            metaBox.addChild("br");
            metaBox.addChild(
                "#", "\u00a0\u00a0DontCompress: " + Boolean.toString(md.topDontCompress));
            metaBox.addChild("br");
            metaBox.addChild(
                "#",
                "\u00a0\u00a0Compressed size: " + Long.toString(md.topCompressedSize) + " bytes.");
            metaBox.addChild("br");
            metaBox.addChild(
                "#", "\u00a0\u00a0Decompressed Size: " + Long.toString(md.topSize) + " bytes.");
            metaBox.addChild("br");
            metaBox.addChild(
                "#",
                "\u00a0\u00a0Blocks: "
                    + Integer.toString(md.topBlocksRequired)
                    + " required, "
                    + Integer.toString(md.topBlocksTotal)
                    + " total.");
            metaBox.addChild("br");
          }
          final HashResult[] hashes = md.getHashes();
          if (hashes != null && hashes.length > 0) {
            metaBox.addChild("#", "Hashes:");
            metaBox.addChild("br");
            for (final HashResult hash : hashes) {
              metaBox.addChild(
                  "#", "\u00a0\u00a0" + hash.type.name() + ": " + HexUtil.bytesToHex(hash.result));
              metaBox.addChild("br");
            }
          }

          if (md.isSplitfile()) {
            metaBox.addChild("#", "Splitfile size\u00a0=\u00a0" + md.dataLength() + " bytes.");
            metaBox.addChild("br");

            byte[] splitfileCryptoKey = md.getCustomSplitfileKey();
            if (splitfileCryptoKey != null) {
              metaBox.addChild(
                  "#", "Splitfile CryptoKey\u00a0=\u00a0" + HexUtil.bytesToHex(splitfileCryptoKey));
              metaBox.addChild("br");
            }
          }

          metaBox.addChild("#", "Options:");
          metaBox.addChild("br");

          if (md.isSimpleManifest()) {
            metaBox.addChild(
                new HTMLNode(
                    "a",
                    "href",
                    KeyUtilsPlugin.PLUGIN_URI
                        + "/Site/?mftype=simplemanifest&key="
                        + furi
                        + extraParams,
                    "reopen as manifest"));
            metaBox.addChild("br");
          }
          if (md.isArchiveManifest()) {
            metaBox.addChild(
                new HTMLNode(
                    "a",
                    "href",
                    KeyUtilsPlugin.PLUGIN_URI
                        + "/Site/?mftype="
                        + md.getArchiveType().name()
                        + "manifest&key="
                        + furi
                        + extraParams,
                    "reopen as manifest"));
            metaBox.addChild("br");
          }
          if (md.isMultiLevelMetadata()) {
            if (ml)
              metaBox.addChild(
                  new HTMLNode(
                      "a",
                      "href",
                      KeyUtilsPlugin.PLUGIN_URI + "/?key=" + furi + extraParams,
                      "explore multilevel"));
            else
              metaBox.addChild(
                  new HTMLNode(
                      "a",
                      "href",
                      KeyUtilsPlugin.PLUGIN_URI + "/?ml=checked&key=" + furi + extraParams,
                      "explore multilevel"));
            metaBox.addChild("br");
          }

          FreenetURI uri = md.getSingleTarget();
          if (uri != null) {
            String sfrUri = uri.toString(false, false);
            metaBox.addChild("#", sfrUri);
            metaBox.addChild("#", "\u00a0");
            metaBox.addChild(new HTMLNode("a", "href", "/?key=" + sfrUri, "open"));
            metaBox.addChild("#", "\u00a0");
            metaBox.addChild(
                new HTMLNode(
                    "a",
                    "href",
                    KeyUtilsPlugin.PLUGIN_URI + "/?key=" + sfrUri + extraParams,
                    "explore"));
          } else {
            metaBox.addChild(new HTMLNode("a", "href", "/?key=" + furi, "reopen normal"));
          }
          metaBox.addChild("br");

          if ((uri == null) && md.isSplitfile()) {
            metaBox.addChild(
                new HTMLNode(
                    "a",
                    "href",
                    KeyUtilsPlugin.PLUGIN_URI + "/Split?key=" + furi.toString(false, false),
                    "reopen as splitfile"));
            metaBox.addChild("br");
            metaBox.addChild(
                new HTMLNode(
                    "a",
                    "href",
                    KeyUtilsPlugin.PLUGIN_URI
                        + "/Download?action=splitdownload&key="
                        + furi.toString(false, false),
                    "split-download"));
            metaBox.addChild("br");
          }
        }
      }
      if (errors.size() > 0) contentNode.addChild(createErrorBox(errors));
    }
    contentNode.addChild(Utils.makeDonateFooter(_intl));
    writeHTMLReply(ctx, 200, "OK", pageNode.generate());
  }
Example #3
0
 /**
  * @param data The Bucket which contains the reply data. This function does not free() the Bucket!
  *     <p>FIXME: For all references to this function, check whether they free() the Bucket.
  */
 public void writeData(Bucket data) throws ToadletContextClosedException, IOException {
   if (closed) throw new ToadletContextClosedException();
   BucketTools.copyTo(data, sockOutputStream, Long.MAX_VALUE);
 }
Example #4
0
  /** Handle an incoming connection. Blocking, obviously. */
  public static void handle(Socket sock, ToadletContainer container, PageMaker pageMaker) {
    try {
      InputStream is = new BufferedInputStream(sock.getInputStream(), 4096);

      LineReadingInputStream lis = new LineReadingInputStream(is);

      while (true) {

        String firstLine = lis.readLine(32768, 128, false); // ISO-8859-1 or US-ASCII, _not_ UTF-8
        if (firstLine == null) {
          sock.close();
          return;
        } else if (firstLine.equals("")) {
          continue;
        }

        boolean logMINOR = Logger.shouldLog(Logger.MINOR, ToadletContextImpl.class);

        if (logMINOR) Logger.minor(ToadletContextImpl.class, "first line: " + firstLine);

        String[] split = firstLine.split(" ");

        if (split.length != 3)
          throw new ParseException(
              "Could not parse request line (split.length=" + split.length + "): " + firstLine);

        if (!split[2].startsWith("HTTP/1."))
          throw new ParseException("Unrecognized protocol " + split[2]);

        URI uri;
        try {
          uri = URIPreEncoder.encodeURI(split[1]).normalize();
          if (logMINOR)
            Logger.minor(
                ToadletContextImpl.class,
                "URI: "
                    + uri
                    + " path "
                    + uri.getPath()
                    + " host "
                    + uri.getHost()
                    + " frag "
                    + uri.getFragment()
                    + " port "
                    + uri.getPort()
                    + " query "
                    + uri.getQuery()
                    + " scheme "
                    + uri.getScheme());
        } catch (URISyntaxException e) {
          sendURIParseError(sock.getOutputStream(), true, e);
          return;
        }

        String method = split[0];

        MultiValueTable<String, String> headers = new MultiValueTable<String, String>();

        while (true) {
          String line = lis.readLine(32768, 128, false); // ISO-8859 or US-ASCII, not UTF-8
          if (line == null) {
            sock.close();
            return;
          }
          // System.out.println("Length="+line.length()+": "+line);
          if (line.length() == 0) break;
          int index = line.indexOf(':');
          if (index < 0) {
            throw new ParseException("Missing ':' in request header field");
          }
          String before = line.substring(0, index).toLowerCase();
          String after = line.substring(index + 1);
          after = after.trim();
          headers.put(before, after);
        }

        boolean disconnect =
            shouldDisconnectAfterHandled(split[2].equals("HTTP/1.0"), headers)
                || !container.enablePersistentConnections();

        boolean allowPost = container.allowPosts();
        BucketFactory bf = container.getBucketFactory();

        ToadletContextImpl ctx = new ToadletContextImpl(sock, headers, bf, pageMaker, container);
        ctx.shouldDisconnect = disconnect;

        /*
         * copy the data into a bucket now,
         * before we go into the redirect loop
         */

        Bucket data;

        boolean methodIsConfigurable = true;

        String slen = headers.get("content-length");

        if (METHODS_MUST_HAVE_DATA.contains(method)) {
          // <method> must have data
          methodIsConfigurable = false;
          if (slen == null) {
            ctx.shouldDisconnect = true;
            ctx.sendReplyHeaders(400, "Bad Request", null, null, -1);
            return;
          }
        } else if (METHODS_CANNOT_HAVE_DATA.contains(method)) {
          // <method> can not have data
          methodIsConfigurable = false;
          if (slen != null) {
            ctx.shouldDisconnect = true;
            ctx.sendReplyHeaders(400, "Bad Request", null, null, -1);
            return;
          }
        }

        if (slen != null) {
          long len;
          try {
            len = Integer.parseInt(slen);
            if (len < 0) throw new NumberFormatException("content-length less than 0");
          } catch (NumberFormatException e) {
            ctx.shouldDisconnect = true;
            ctx.sendReplyHeaders(400, "Bad Request", null, null, -1);
            return;
          }
          if (allowPost && ((!container.publicGatewayMode()) || ctx.isAllowedFullAccess())) {
            data = bf.makeBucket(len);
            BucketTools.copyFrom(data, is, len);
          } else {
            FileUtil.skipFully(is, len);
            if (method.equals("POST")) {
              ctx.sendMethodNotAllowed("POST", true);
            } else {
              sendError(
                  sock.getOutputStream(),
                  403,
                  "Forbidden",
                  "Content not allowed in this configuration",
                  true,
                  null);
            }
            ctx.close();
            return;
          }
        } else {
          // we're not doing to use it, but we have to keep
          // the compiler happy
          data = null;
        }

        if (!container.enableExtendedMethodHandling()) {
          if (!METHODS_RESTRICTED_MODE.contains(method)) {
            sendError(
                sock.getOutputStream(),
                403,
                "Forbidden",
                "Method not allowed in this configuration",
                true,
                null);
            return;
          }
        }

        // Handle it.
        try {
          boolean redirect = true;
          while (redirect) {
            // don't go around the loop unless set explicitly
            redirect = false;

            Toadlet t;
            try {
              t = container.findToadlet(uri);
            } catch (PermanentRedirectException e) {
              Toadlet.writePermanentRedirect(ctx, "Found elsewhere", e.newuri.toASCIIString());
              break;
            }

            if (t == null) {
              ctx.sendNoToadletError(ctx.shouldDisconnect);
              break;
            }

            // if the Toadlet does not support the method, we don't need to parse the data
            // also due this pre check a 'NoSuchMethodException' should never appear
            if (!(t.findSupportedMethods().contains(method))) {
              ctx.sendMethodNotAllowed(method, ctx.shouldDisconnect);
              break;
            }

            HTTPRequestImpl req = new HTTPRequestImpl(uri, data, ctx, method);
            try {
              String methodName = "handleMethod" + method;
              try {
                Class<? extends Toadlet> c = t.getClass();
                Method m = c.getMethod(methodName, HANDLE_PARAMETERS);
                if (methodIsConfigurable) {
                  AllowData anno = m.getAnnotation(AllowData.class);
                  if (anno == null) {
                    if (data != null) {
                      sendError(
                          sock.getOutputStream(),
                          400,
                          "Bad Request",
                          "Content not allowed",
                          true,
                          null);
                      ctx.close();
                      return;
                    }
                  } else if (anno.value()) {
                    if (data == null) {
                      sendError(
                          sock.getOutputStream(),
                          400,
                          "Bad Request",
                          "Missing Content",
                          true,
                          null);
                      ctx.close();
                      return;
                    }
                  }
                }
                ctx.setActiveToadlet(t);
                Object arglist[] = new Object[] {uri, req, ctx};
                m.invoke(t, arglist);
              } catch (InvocationTargetException ite) {
                throw ite.getCause();
              }
            } catch (RedirectException re) {
              uri = re.newuri;
              redirect = true;
            } finally {
              req.freeParts();
            }
          }
          if (ctx.shouldDisconnect) {
            sock.close();
            return;
          }
        } finally {
          if (data != null) data.free();
        }
      }

    } catch (ParseException e) {
      try {
        sendError(
            sock.getOutputStream(),
            400,
            "Bad Request",
            l10n("parseErrorWithError", "error", e.getMessage()),
            true,
            null);
      } catch (IOException e1) {
        // Ignore
      }
    } catch (TooLongException e) {
      try {
        sendError(
            sock.getOutputStream(), 400, "Bad Request", l10n("headersLineTooLong"), true, null);
      } catch (IOException e1) {
        // Ignore
      }
    } catch (IOException e) {
      // ignore and return
    } catch (ToadletContextClosedException e) {
      Logger.error(
          ToadletContextImpl.class, "ToadletContextClosedException while handling connection!");
    } catch (Throwable t) {
      Logger.error(ToadletContextImpl.class, "Caught error: " + t + " handling socket", t);
      try {
        sendError(sock.getOutputStream(), 500, "Internal Error", t.toString(), true, null);
      } catch (IOException e1) {
        // ignore and return
      }
    }
  }
  /** Handle an incoming connection. Blocking, obviously. */
  public static void handle(Socket sock, ToadletContainer container, PageMaker pageMaker) {
    try {
      InputStream is = new BufferedInputStream(sock.getInputStream(), 4096);

      LineReadingInputStream lis = new LineReadingInputStream(is);

      while (true) {

        String firstLine = lis.readLine(32768, 128, false); // ISO-8859-1 or US-ASCII, _not_ UTF-8
        if (firstLine == null) {
          sock.close();
          return;
        } else if (firstLine.equals("")) {
          continue;
        }

        boolean logMINOR = Logger.shouldLog(Logger.MINOR, ToadletContextImpl.class);

        if (logMINOR) Logger.minor(ToadletContextImpl.class, "first line: " + firstLine);

        String[] split = firstLine.split(" ");

        if (split.length != 3)
          throw new ParseException(
              "Could not parse request line (split.length=" + split.length + "): " + firstLine);

        if (!split[2].startsWith("HTTP/1."))
          throw new ParseException("Unrecognized protocol " + split[2]);

        URI uri;
        try {
          uri = URIPreEncoder.encodeURI(split[1]).normalize();
          if (logMINOR)
            Logger.minor(
                ToadletContextImpl.class,
                "URI: "
                    + uri
                    + " path "
                    + uri.getPath()
                    + " host "
                    + uri.getHost()
                    + " frag "
                    + uri.getFragment()
                    + " port "
                    + uri.getPort()
                    + " query "
                    + uri.getQuery()
                    + " scheme "
                    + uri.getScheme());
        } catch (URISyntaxException e) {
          sendURIParseError(sock.getOutputStream(), true, e);
          return;
        }

        String method = split[0];

        MultiValueTable<String, String> headers = new MultiValueTable<String, String>();

        while (true) {
          String line = lis.readLine(32768, 128, false); // ISO-8859 or US-ASCII, not UTF-8
          if (line == null) {
            sock.close();
            return;
          }
          // System.out.println("Length="+line.length()+": "+line);
          if (line.length() == 0) break;
          int index = line.indexOf(':');
          if (index < 0) {
            throw new ParseException("Missing ':' in request header field");
          }
          String before = line.substring(0, index).toLowerCase();
          String after = line.substring(index + 1);
          after = after.trim();
          headers.put(before, after);
        }

        boolean disconnect =
            shouldDisconnectAfterHandled(split[2].equals("HTTP/1.0"), headers)
                || !container.enablePersistentConnections();

        boolean allowPost = container.allowPosts();
        BucketFactory bf = container.getBucketFactory();

        ToadletContextImpl ctx = new ToadletContextImpl(sock, headers, bf, pageMaker, container);
        ctx.shouldDisconnect = disconnect;

        /*
         * if we're handling a POST, copy the data into a bucket now,
         * before we go into the redirect loop
         */

        Bucket data;

        if (method.equals("POST")) {
          String slen = headers.get("content-length");
          if (slen == null) {
            sendError(
                sock.getOutputStream(),
                400,
                "Bad Request",
                l10n("noContentLengthInPOST"),
                true,
                null);
            return;
          }
          long len;
          try {
            len = Integer.parseInt(slen);
            if (len < 0) throw new NumberFormatException("content-length less than 0");
          } catch (NumberFormatException e) {
            sendError(
                sock.getOutputStream(),
                400,
                "Bad Request",
                l10n("cannotParseContentLengthWithError", "error", e.toString()),
                true,
                null);
            return;
          }
          if (allowPost && ((!container.publicGatewayMode()) || ctx.isAllowedFullAccess())) {

            data = bf.makeBucket(len);
            BucketTools.copyFrom(data, is, len);
          } else {
            FileUtil.skipFully(is, len);
            ctx.sendMethodNotAllowed("POST", true);
            ctx.close();
            return;
          }
        } else {
          // we're not doing to use it, but we have to keep
          // the compiler happy
          data = null;
        }

        // Handle it.
        try {
          boolean redirect = true;
          while (redirect) {
            // don't go around the loop unless set explicitly
            redirect = false;

            Toadlet t;
            try {
              t = container.findToadlet(uri);
            } catch (PermanentRedirectException e) {
              Toadlet.writePermanentRedirect(ctx, "Found elsewhere", e.newuri.toASCIIString());
              break;
            }

            if (t == null) {
              ctx.sendNoToadletError(ctx.shouldDisconnect);
              break;
            }

            HTTPRequestImpl req = new HTTPRequestImpl(uri, data, ctx, method);
            try {
              if (method.equals("GET")) {
                ctx.setActiveToadlet(t);
                t.handleGet(uri, req, ctx);
                ctx.close();
              } else if (method.equals("POST")) {
                ctx.setActiveToadlet(t);
                t.handlePost(uri, req, ctx);
              } else {
                ctx.sendMethodNotAllowed(method, ctx.shouldDisconnect);
                ctx.close();
              }
            } catch (RedirectException re) {
              uri = re.newuri;
              redirect = true;
            } finally {
              req.freeParts();
            }
          }
          if (ctx.shouldDisconnect) {
            sock.close();
            return;
          }
        } finally {
          if (data != null) data.free();
        }
      }

    } catch (ParseException e) {
      try {
        sendError(
            sock.getOutputStream(),
            400,
            "Bad Request",
            l10n("parseErrorWithError", "error", e.getMessage()),
            true,
            null);
      } catch (IOException e1) {
        // Ignore
      }
    } catch (TooLongException e) {
      try {
        sendError(
            sock.getOutputStream(), 400, "Bad Request", l10n("headersLineTooLong"), true, null);
      } catch (IOException e1) {
        // Ignore
      }
    } catch (IOException e) {
      // ignore and return
    } catch (ToadletContextClosedException e) {
      Logger.error(
          ToadletContextImpl.class, "ToadletContextClosedException while handling connection!");
    } catch (Throwable t) {
      Logger.error(ToadletContextImpl.class, "Caught error: " + t + " handling socket", t);
      try {
        sendError(sock.getOutputStream(), 500, "Internal Error", t.toString(), true, null);
      } catch (IOException e1) {
        // ignore and return
      }
    }
  }
  /**
   * Extract data to cache. Call synchronized on ctx.
   *
   * @param key The key the data was fetched from.
   * @param archiveType The archive type. Must be Metadata.ARCHIVE_ZIP | Metadata.ARCHIVE_TAR.
   * @param data The actual data fetched.
   * @param archiveContext The context for the whole fetch process.
   * @param ctx The ArchiveStoreContext for this key.
   * @param element A particular element that the caller is especially interested in, or null.
   * @param callback A callback to be called if we find that element, or if we don't.
   * @throws ArchiveFailureException If we could not extract the data, or it was too big, etc.
   * @throws ArchiveRestartException
   * @throws ArchiveRestartException If the request needs to be restarted because the archive
   *     changed.
   */
  public void extractToCache(
      FreenetURI key,
      ARCHIVE_TYPE archiveType,
      COMPRESSOR_TYPE ctype,
      final Bucket data,
      ArchiveContext archiveContext,
      ArchiveStoreContext ctx,
      String element,
      ArchiveExtractCallback callback,
      ClientContext context)
      throws ArchiveFailureException, ArchiveRestartException {
    logMINOR = Logger.shouldLog(LogLevel.MINOR, this);

    MutableBoolean gotElement = element != null ? new MutableBoolean() : null;

    if (logMINOR) Logger.minor(this, "Extracting " + key);
    ctx.removeAllCachedItems(this); // flush cache anyway
    final long expectedSize = ctx.getLastSize();
    final long archiveSize = data.size();
    /**
     * Set if we need to throw a RestartedException rather than returning success, after we have
     * unpacked everything.
     */
    boolean throwAtExit = false;
    if ((expectedSize != -1) && (archiveSize != expectedSize)) {
      throwAtExit = true;
      ctx.setLastSize(archiveSize);
    }
    byte[] expectedHash = ctx.getLastHash();
    if (expectedHash != null) {
      byte[] realHash;
      try {
        realHash = BucketTools.hash(data);
      } catch (IOException e) {
        throw new ArchiveFailureException("Error reading archive data: " + e, e);
      }
      if (!Arrays.equals(realHash, expectedHash)) throwAtExit = true;
      ctx.setLastHash(realHash);
    }

    if (archiveSize > archiveContext.maxArchiveSize)
      throw new ArchiveFailureException(
          "Archive too big (" + archiveSize + " > " + archiveContext.maxArchiveSize + ")!");
    else if (archiveSize <= 0)
      throw new ArchiveFailureException("Archive too small! (" + archiveSize + ')');
    else if (logMINOR)
      Logger.minor(this, "Container size (possibly compressed): " + archiveSize + " for " + data);

    InputStream is = null;
    try {
      final ExceptionWrapper wrapper;
      if ((ctype == null) || (ARCHIVE_TYPE.ZIP == archiveType)) {
        if (logMINOR) Logger.minor(this, "No compression");
        is = data.getInputStream();
        wrapper = null;
      } else if (ctype == COMPRESSOR_TYPE.BZIP2) {
        if (logMINOR) Logger.minor(this, "dealing with BZIP2");
        is = new BZip2CompressorInputStream(data.getInputStream());
        wrapper = null;
      } else if (ctype == COMPRESSOR_TYPE.GZIP) {
        if (logMINOR) Logger.minor(this, "dealing with GZIP");
        is = new GZIPInputStream(data.getInputStream());
        wrapper = null;
      } else if (ctype == COMPRESSOR_TYPE.LZMA_NEW) {
        // LZMA internally uses pipe streams, so we may as well do it here.
        // In fact we need to for LZMA_NEW, because of the properties bytes.
        PipedInputStream pis = new PipedInputStream();
        PipedOutputStream pos = new PipedOutputStream();
        pis.connect(pos);
        final OutputStream os = new BufferedOutputStream(pos);
        wrapper = new ExceptionWrapper();
        context.mainExecutor.execute(
            new Runnable() {

              @Override
              public void run() {
                InputStream is = null;
                try {
                  Compressor.COMPRESSOR_TYPE.LZMA_NEW.decompress(
                      is = data.getInputStream(), os, data.size(), expectedSize);
                } catch (CompressionOutputSizeException e) {
                  Logger.error(this, "Failed to decompress archive: " + e, e);
                  wrapper.set(e);
                } catch (IOException e) {
                  Logger.error(this, "Failed to decompress archive: " + e, e);
                  wrapper.set(e);
                } finally {
                  try {
                    os.close();
                  } catch (IOException e) {
                    Logger.error(this, "Failed to close PipedOutputStream: " + e, e);
                  }
                  Closer.close(is);
                }
              }
            });
        is = pis;
      } else if (ctype == COMPRESSOR_TYPE.LZMA) {
        if (logMINOR) Logger.minor(this, "dealing with LZMA");
        is = new LzmaInputStream(data.getInputStream());
        wrapper = null;
      } else {
        wrapper = null;
      }

      if (ARCHIVE_TYPE.ZIP == archiveType)
        handleZIPArchive(ctx, key, is, element, callback, gotElement, throwAtExit, context);
      else if (ARCHIVE_TYPE.TAR == archiveType)
        handleTARArchive(ctx, key, is, element, callback, gotElement, throwAtExit, context);
      else
        throw new ArchiveFailureException(
            "Unknown or unsupported archive algorithm " + archiveType);
      if (wrapper != null) {
        Exception e = wrapper.get();
        if (e != null)
          throw new ArchiveFailureException(
              "An exception occured decompressing: " + e.getMessage(), e);
      }
    } catch (IOException ioe) {
      throw new ArchiveFailureException("An IOE occured: " + ioe.getMessage(), ioe);
    } finally {
      Closer.close(is);
    }
  }
    public boolean send(
        NodeClientCore core, RequestScheduler sched, final ClientContext context, ChosenBlock req) {
      // Ignore keyNum, key, since we're only sending one block.
      ClientKeyBlock b;
      ClientKey key = null;
      if (SingleBlockInserter.logMINOR)
        Logger.minor(this, "Starting request: " + SingleBlockInserter.this);
      BlockItem block = (BlockItem) req.token;
      try {
        try {
          b =
              innerEncode(
                  context.random,
                  block.uri,
                  block.copyBucket,
                  block.isMetadata,
                  block.compressionCodec,
                  block.sourceLength,
                  compressorDescriptor);
        } catch (CHKEncodeException e) {
          throw new LowLevelPutException(
              LowLevelPutException.INTERNAL_ERROR, e.toString() + ":" + e.getMessage(), e);
        } catch (SSKEncodeException e) {
          throw new LowLevelPutException(
              LowLevelPutException.INTERNAL_ERROR, e.toString() + ":" + e.getMessage(), e);
        } catch (MalformedURLException e) {
          throw new LowLevelPutException(
              LowLevelPutException.INTERNAL_ERROR, e.toString() + ":" + e.getMessage(), e);
        } catch (InsertException e) {
          throw new LowLevelPutException(
              LowLevelPutException.INTERNAL_ERROR, e.toString() + ":" + e.getMessage(), e);
        } catch (IOException e) {
          throw new LowLevelPutException(
              LowLevelPutException.INTERNAL_ERROR, e.toString() + ":" + e.getMessage(), e);
        } catch (InvalidCompressionCodecException e) {
          throw new LowLevelPutException(
              LowLevelPutException.INTERNAL_ERROR, e.toString() + ":" + e.getMessage(), e);
        }
        if (b == null) {
          Logger.error(
              this,
              "Asked to send empty block on " + SingleBlockInserter.this,
              new Exception("error"));
          return false;
        }
        key = b.getClientKey();
        final ClientKey k = key;
        if (block.persistent) {
          context.jobRunner.queue(
              new DBJob() {

                public boolean run(ObjectContainer container, ClientContext context) {
                  if (!container.ext().isStored(SingleBlockInserter.this)) return false;
                  container.activate(SingleBlockInserter.this, 1);
                  onEncode(k, container, context);
                  container.deactivate(SingleBlockInserter.this, 1);
                  return false;
                }
              },
              NativeThread.NORM_PRIORITY + 1,
              false);
        } else {
          context.mainExecutor.execute(
              new Runnable() {

                public void run() {
                  onEncode(k, null, context);
                }
              },
              "Got URI");
        }
        if (req.localRequestOnly)
          try {
            core.node.store(b, false, req.canWriteClientCache, true, false);
          } catch (KeyCollisionException e) {
            throw new LowLevelPutException(LowLevelPutException.COLLISION);
          }
        else core.realPut(b, req.canWriteClientCache, req.forkOnCacheable);
      } catch (LowLevelPutException e) {
        if (e.code == LowLevelPutException.COLLISION) {
          // Collision
          try {
            ClientSSKBlock collided =
                (ClientSSKBlock)
                    core.node.fetch((ClientSSK) key, true, true, req.canWriteClientCache);
            byte[] data = collided.memoryDecode(true);
            byte[] inserting = BucketTools.toByteArray(block.copyBucket);
            if (collided.isMetadata() == block.isMetadata
                && collided.getCompressionCodec() == block.compressionCodec
                && Arrays.equals(data, inserting)) {
              if (SingleBlockInserter.logMINOR)
                Logger.minor(this, "Collided with identical data: " + SingleBlockInserter.this);
              req.onInsertSuccess(context);
              return true;
            }
          } catch (KeyVerifyException e1) {
            Logger.error(this, "Caught " + e1 + " when checking collision!", e1);
          } catch (KeyDecodeException e1) {
            Logger.error(this, "Caught " + e1 + " when checking collision!", e1);
          } catch (IOException e1) {
            Logger.error(this, "Caught " + e1 + " when checking collision!", e1);
          }
        }
        req.onFailure(e, context);
        if (SingleBlockInserter.logMINOR)
          Logger.minor(this, "Request failed: " + SingleBlockInserter.this + " for " + e);
        return true;
      } catch (DatabaseDisabledException e) {
        // Impossible, and nothing to do.
        Logger.error(this, "Running persistent insert but database is disabled!");
      } finally {
        block.copyBucket.free();
      }
      if (SingleBlockInserter.logMINOR)
        Logger.minor(this, "Request succeeded: " + SingleBlockInserter.this);
      req.onInsertSuccess(context);
      return true;
    }