示例#1
0
  /**
   * This method encodes name/value pairs and files into a byte array using the multipart/form-data
   * encoding. The boundary is returned as part of <var>ct_hdr</var>. <br>
   * Example:
   *
   * <PRE>
   *     NVPair[] opts = { new NVPair("option", "doit") };
   *     NVPair[] file = { new NVPair("comment", "comment.txt") };
   *     NVPair[] hdrs = new NVPair[1];
   *     byte[]   data = Codecs.mpFormDataEncode(opts, file, hdrs);
   *     con.Post("/cgi-bin/handle-it", data, hdrs);
   * </PRE>
   *
   * <VAR>data</VAR> will look something like the following:
   *
   * <PRE>
   * -----------------------------114975832116442893661388290519
   * Content-Disposition: form-data; name="option"
   *                                                         &nbsp;
   * doit
   * -----------------------------114975832116442893661388290519
   * Content-Disposition: form-data; name="comment"; filename="comment.txt"
   * Content-Type: text/plain
   *                                                         &nbsp;
   * Gnus and Gnats are not Gnomes.
   * -----------------------------114975832116442893661388290519--
   * </PRE>
   *
   * where the "Gnus and Gnats ..." is the contents of the file <VAR>comment.txt</VAR> in the
   * current directory.
   *
   * <p>If no elements are found in the parameters then a zero-length byte[] is returned and the
   * content-type is set to <var>application/octet-string</var> (because a multipart must always
   * have at least one part.
   *
   * <p>For files an attempt is made to discover the content-type, and if found a Content-Type
   * header will be added to that part. The content type is retrieved using
   * java.net.URLConnection.guessContentTypeFromName() - see java.net.URLConnection.setFileNameMap()
   * for how to modify that map. Note that under JDK 1.1 by default the map seems to be empty. If
   * you experience troubles getting the server to accept the data then make sure the fileNameMap is
   * returning a content-type for each file (this may mean you'll have to set your own).
   *
   * @param opts the simple form-data to encode (may be null); for each NVPair the name refers to
   *     the 'name' attribute to be used in the header of the part, and the value is contents of the
   *     part. null elements in the array are ingored.
   * @param files the files to encode (may be null); for each NVPair the name refers to the 'name'
   *     attribute to be used in the header of the part, and the value is the actual filename (the
   *     file will be read and it's contents put in the body of that part). null elements in the
   *     array are ingored.
   * @param ct_hdr this returns a new NVPair in the 0'th element which contains name =
   *     "Content-Type", value = "multipart/form-data; boundary=..." (the reason this parameter is
   *     an array is because a) that's the only way to simulate pass-by-reference and b) you need an
   *     array for the headers parameter to the Post() or Put() anyway). The exception to this is
   *     that if no opts or files are given the type is set to "application/octet-stream" instead.
   * @param mangler the filename mangler, or null if no mangling is to be done. This allows you to
   *     change the name used in the <var>filename</var> attribute of the Content-Disposition
   *     header. Note: the mangler will be invoked twice for each filename.
   * @return an encoded byte array containing all the opts and files.
   * @exception IOException If any file operation fails.
   */
  public static final byte[] mpFormDataEncode(
      NVPair[] opts, NVPair[] files, NVPair[] ct_hdr, FilenameMangler mangler) throws IOException {
    byte[] boundary = Boundary.getBytes("8859_1"),
        cont_disp = ContDisp.getBytes("8859_1"),
        cont_type = ContType.getBytes("8859_1"),
        filename = FileName.getBytes("8859_1");
    int len = 0, hdr_len = boundary.length + cont_disp.length + 1 + 2 + 2;
    //        \r\n --  bnd      \r\n C-D: ..; n=".." \r\n \r\n

    if (opts == null) opts = dummy;
    if (files == null) files = dummy;

    // Calculate the length of the data

    for (int idx = 0; idx < opts.length; idx++) {
      if (opts[idx] == null) continue;

      len += hdr_len + opts[idx].getName().length() + opts[idx].getValue().length();
    }

    for (int idx = 0; idx < files.length; idx++) {
      if (files[idx] == null) continue;

      File file = new File(files[idx].getValue());
      String fname = file.getName();
      if (mangler != null) fname = mangler.mangleFilename(fname, files[idx].getName());
      if (fname != null) {
        len += hdr_len + files[idx].getName().length() + filename.length;
        len += fname.length() + file.length();

        String ct = CT.getContentType(file.getName());
        if (ct != null) len += cont_type.length + ct.length();
      }
    }

    if (len == 0) {
      ct_hdr[0] = new NVPair("Content-Type", "application/octet-stream");
      return new byte[0];
    }

    len -= 2; // first CR LF is not written
    len += boundary.length + 2 + 2; // \r\n -- bnd -- \r\n

    // Now fill array

    byte[] res = new byte[len];
    int pos = 0;

    NewBound:
    for (int new_c = 0x30303030; new_c != 0x7A7A7A7A; new_c++) {
      pos = 0;

      // modify boundary in hopes that it will be unique
      while (!BoundChar.get(new_c & 0xff)) new_c += 0x00000001;
      while (!BoundChar.get(new_c >> 8 & 0xff)) new_c += 0x00000100;
      while (!BoundChar.get(new_c >> 16 & 0xff)) new_c += 0x00010000;
      while (!BoundChar.get(new_c >> 24 & 0xff)) new_c += 0x01000000;
      boundary[40] = (byte) (new_c & 0xff);
      boundary[42] = (byte) (new_c >> 8 & 0xff);
      boundary[44] = (byte) (new_c >> 16 & 0xff);
      boundary[46] = (byte) (new_c >> 24 & 0xff);

      int off = 2;
      int[] bnd_cmp = Util.compile_search(boundary);

      for (int idx = 0; idx < opts.length; idx++) {
        if (opts[idx] == null) continue;

        System.arraycopy(boundary, off, res, pos, boundary.length - off);
        pos += boundary.length - off;
        off = 0;
        int start = pos;

        System.arraycopy(cont_disp, 0, res, pos, cont_disp.length);
        pos += cont_disp.length;

        int nlen = opts[idx].getName().length();
        System.arraycopy(opts[idx].getName().getBytes("8859_1"), 0, res, pos, nlen);
        pos += nlen;

        res[pos++] = (byte) '"';
        res[pos++] = (byte) '\r';
        res[pos++] = (byte) '\n';
        res[pos++] = (byte) '\r';
        res[pos++] = (byte) '\n';

        int vlen = opts[idx].getValue().length();
        System.arraycopy(opts[idx].getValue().getBytes("8859_1"), 0, res, pos, vlen);
        pos += vlen;

        if ((pos - start) >= boundary.length
            && Util.findStr(boundary, bnd_cmp, res, start, pos) != -1) continue NewBound;
      }

      for (int idx = 0; idx < files.length; idx++) {
        if (files[idx] == null) continue;

        File file = new File(files[idx].getValue());
        String fname = file.getName();
        if (mangler != null) fname = mangler.mangleFilename(fname, files[idx].getName());
        if (fname == null) continue;

        System.arraycopy(boundary, off, res, pos, boundary.length - off);
        pos += boundary.length - off;
        off = 0;
        int start = pos;

        System.arraycopy(cont_disp, 0, res, pos, cont_disp.length);
        pos += cont_disp.length;

        int nlen = files[idx].getName().length();
        System.arraycopy(files[idx].getName().getBytes("8859_1"), 0, res, pos, nlen);
        pos += nlen;

        System.arraycopy(filename, 0, res, pos, filename.length);
        pos += filename.length;

        nlen = fname.length();
        System.arraycopy(fname.getBytes("8859_1"), 0, res, pos, nlen);
        pos += nlen;

        res[pos++] = (byte) '"';

        String ct = CT.getContentType(file.getName());
        if (ct != null) {
          System.arraycopy(cont_type, 0, res, pos, cont_type.length);
          pos += cont_type.length;
          System.arraycopy(ct.getBytes("8859_1"), 0, res, pos, ct.length());
          pos += ct.length();
        }

        res[pos++] = (byte) '\r';
        res[pos++] = (byte) '\n';
        res[pos++] = (byte) '\r';
        res[pos++] = (byte) '\n';

        nlen = (int) file.length();
        FileInputStream fin = new FileInputStream(file);
        while (nlen > 0) {
          int got = fin.read(res, pos, nlen);
          nlen -= got;
          pos += got;
        }
        fin.close();

        if ((pos - start) >= boundary.length
            && Util.findStr(boundary, bnd_cmp, res, start, pos) != -1) continue NewBound;
      }

      break NewBound;
    }

    System.arraycopy(boundary, 0, res, pos, boundary.length);
    pos += boundary.length;
    res[pos++] = (byte) '-';
    res[pos++] = (byte) '-';
    res[pos++] = (byte) '\r';
    res[pos++] = (byte) '\n';

    if (pos != len) throw new Error("Calculated " + len + " bytes but wrote " + pos + " bytes!");

    /* the boundary parameter should be quoted (rfc-2046, section 5.1.1)
     * but too many script authors are not capable of reading specs...
     * So, I give up and don't quote it.
     */
    ct_hdr[0] =
        new NVPair(
            "Content-Type",
            "multipart/form-data; boundary="
                + new String(boundary, 4, boundary.length - 4, "8859_1"));

    return res;
  }
示例#2
0
  /**
   * This method decodes a multipart/form-data encoded string. The boundary is parsed from the
   * <var>cont_type</var> parameter, which must be of the form 'multipart/form-data; boundary=...'.
   * Any encoded files are created in the directory specified by <var>dir</var> using the encoded
   * filename.
   *
   * <p><em>Note:</em> Does not handle nested encodings (yet).
   *
   * <p>Examples: If you're receiving a multipart/form-data encoded response from a server you could
   * use something like:
   *
   * <PRE>
   *     NVPair[] opts = Codecs.mpFormDataDecode(resp.getData(),
   *                                  resp.getHeader("Content-type"), ".");
   * </PRE>
   *
   * If you're using this in a Servlet to decode the body of a request from a client you could use
   * something like:
   *
   * <PRE>
   *     byte[] body = new byte[req.getContentLength()];
   *     new DataInputStream(req.getInputStream()).readFully(body);
   *     NVPair[] opts = Codecs.mpFormDataDecode(body, req.getContentType(),
   *                                             ".");
   * </PRE>
   *
   * (where 'req' is the HttpServletRequest).
   *
   * <p>Assuming the data received looked something like:
   *
   * <PRE>
   * -----------------------------114975832116442893661388290519
   * Content-Disposition: form-data; name="option"
   *                                                         &nbsp;
   * doit
   * -----------------------------114975832116442893661388290519
   * Content-Disposition: form-data; name="comment"; filename="comment.txt"
   *                                                         &nbsp;
   * Gnus and Gnats are not Gnomes.
   * -----------------------------114975832116442893661388290519--
   * </PRE>
   *
   * you would get one file called <VAR>comment.txt</VAR> in the current directory, and opts would
   * contain two elements: {"option", "doit"} and {"comment", "comment.txt"}
   *
   * @param data the form-data to decode.
   * @param cont_type the content type header (must contain the boundary string).
   * @param dir the directory to create the files in.
   * @param mangler the filename mangler, or null if no mangling is to be done. This is invoked just
   *     before each file is created and written, thereby allowing you to control the names of the
   *     files.
   * @return an array of name/value pairs, one for each part; the name is the 'name' attribute given
   *     in the Content-Disposition header; the value is either the name of the file if a filename
   *     attribute was found, or the contents of the part.
   * @exception IOException If any file operation fails.
   * @exception ParseException If an error during parsing occurs.
   */
  public static final NVPair[] mpFormDataDecode(
      byte[] data, String cont_type, String dir, FilenameMangler mangler)
      throws IOException, ParseException {
    // Find and extract boundary string

    String bndstr = Util.getParameter("boundary", cont_type);
    if (bndstr == null)
      throw new ParseException("'boundary' parameter not found in Content-type: " + cont_type);

    byte[] srtbndry = ("--" + bndstr + "\r\n").getBytes("8859_1"),
        boundary = ("\r\n--" + bndstr + "\r\n").getBytes("8859_1"),
        endbndry = ("\r\n--" + bndstr + "--").getBytes("8859_1");

    // setup search routines

    int[] bs = Util.compile_search(srtbndry),
        bc = Util.compile_search(boundary),
        be = Util.compile_search(endbndry);

    // let's start parsing the actual data

    int start = Util.findStr(srtbndry, bs, data, 0, data.length);
    if (start == -1) // didn't even find the start
    throw new ParseException("Starting boundary not found: " + new String(srtbndry, "8859_1"));
    start += srtbndry.length;

    NVPair[] res = new NVPair[10];
    boolean done = false;
    int idx;

    for (idx = 0; !done; idx++) {
      // find end of this part

      int end = Util.findStr(boundary, bc, data, start, data.length);
      if (end == -1) // must be the last part
      {
        end = Util.findStr(endbndry, be, data, start, data.length);
        if (end == -1)
          throw new ParseException("Ending boundary not found: " + new String(endbndry, "8859_1"));
        done = true;
      }

      // parse header(s)

      String hdr, name = null, value, filename = null, cont_disp = null;

      while (true) {
        int next = findEOL(data, start) + 2;
        if (next - 2 <= start) break; // empty line -> end of headers
        hdr = new String(data, start, next - 2 - start, "8859_1");
        start = next;

        // handle line continuation
        byte ch;
        while (next < data.length - 1 && ((ch = data[next]) == ' ' || ch == '\t')) {
          next = findEOL(data, start) + 2;
          hdr += new String(data, start, next - 2 - start, "8859_1");
          start = next;
        }

        if (!hdr.regionMatches(true, 0, "Content-Disposition", 0, 19)) continue;
        Vector pcd = Util.parseHeader(hdr.substring(hdr.indexOf(':') + 1));
        HttpHeaderElement elem = Util.getElement(pcd, "form-data");

        if (elem == null)
          throw new ParseException("Expected 'Content-Disposition: form-data' in line: " + hdr);

        NVPair[] params = elem.getParams();
        name = filename = null;
        for (int pidx = 0; pidx < params.length; pidx++) {
          if (params[pidx].getName().equalsIgnoreCase("name")) name = params[pidx].getValue();
          if (params[pidx].getName().equalsIgnoreCase("filename"))
            filename = params[pidx].getValue();
        }
        if (name == null) throw new ParseException("'name' parameter not found in header: " + hdr);

        cont_disp = hdr;
      }

      start += 2;
      if (start > end) throw new ParseException("End of header not found at offset " + end);

      if (cont_disp == null)
        throw new ParseException("Missing 'Content-Disposition' header at offset " + start);

      // handle data for this part

      if (filename != null) // It's a file
      {
        if (mangler != null) filename = mangler.mangleFilename(filename, name);
        if (filename != null && filename.length() > 0) {
          File file = new File(dir, filename);
          FileOutputStream out = new FileOutputStream(file);

          out.write(data, start, end - start);
          out.close();
        }

        value = filename;
      } else // It's simple data
      {
        value = new String(data, start, end - start, "8859_1");
      }

      if (idx >= res.length) res = Util.resizeArray(res, idx + 10);
      res[idx] = new NVPair(name, value);

      start = end + boundary.length;
    }

    return Util.resizeArray(res, idx);
  }