/**
   * Parse a single part
   *
   * @param ts
   * @throws java.io.IOException
   * @throws org.apache.cocoon.servlet.multipart.MultipartException
   */
  private void parsePart(DSpaceTokenStream ts) throws IOException, MultipartException {

    Hashtable headers = new Hashtable();
    headers = readHeaders(ts);
    try {
      if (headers.containsKey("filename")) {
        if (!"".equals(headers.get("filename"))) {
          parseFilePart(ts, headers);
        } else {
          // IE6 sends an empty part with filename="" for
          // empty upload fields. Just parse away the part
          byte[] buf = new byte[32];
          while (ts.getState() == DSpaceTokenStream.STATE_READING) ts.read(buf);
        }
      } else if (((String) headers.get("content-disposition")).toLowerCase().equals("form-data")) {
        parseInlinePart(ts, headers);
      }

      // FIXME: multipart/mixed parts are untested.
      else if (((String) headers.get("content-disposition")).toLowerCase().indexOf("multipart")
          > -1) {
        parseMultiPart(
            new DSpaceTokenStream(ts, MAX_BOUNDARY_SIZE), "--" + (String) headers.get("boundary"));
        ts.read(); // read past boundary
      } else {
        throw new MultipartException("Unknown part type");
      }
    } catch (IOException e) {
      throw new MultipartException("Malformed stream: " + e.getMessage());
    } catch (NullPointerException e) {
      e.printStackTrace();
      throw new MultipartException("Malformed header");
    }
  }
  /**
   * Read string until newline or end of stream
   *
   * @param in
   * @throws java.io.IOException
   */
  private String readln(DSpaceTokenStream in) throws IOException {

    ByteArrayOutputStream bos = new ByteArrayOutputStream();

    int b = in.read();

    while ((b != -1) && (b != '\r')) {
      bos.write(b);
      b = in.read();
    }

    if (b == '\r') {
      in.read(); // read '\n'
    }

    return new String(bos.toByteArray(), this.characterEncoding);
  }
  /**
   * Parse a multipart block
   *
   * @param ts
   * @param boundary
   * @throws java.io.IOException
   * @throws org.apache.cocoon.servlet.multipart.MultipartException
   */
  private void parseMultiPart(DSpaceTokenStream ts, String boundary)
      throws IOException, MultipartException {

    ts.setBoundary(boundary.getBytes());
    ts.read(); // read first boundary away
    ts.setBoundary(("\r\n" + boundary).getBytes());

    while (ts.getState() == DSpaceTokenStream.STATE_NEXTPART) {
      ts.nextPart();
      parsePart(ts);
    }

    if (ts.getState() != DSpaceTokenStream.STATE_ENDMULTIPART) { // sanity check
      throw new MultipartException("Malformed stream");
    }
  }
  /**
   * Parse an inline part
   *
   * @param in
   * @param headers
   * @throws java.io.IOException
   */
  private void parseInlinePart(DSpaceTokenStream in, Hashtable headers) throws IOException {

    // Buffer incoming bytes for proper string decoding (there can be multibyte chars)
    ByteArrayOutputStream bos = new ByteArrayOutputStream();

    while (in.getState() == DSpaceTokenStream.STATE_READING) {
      int c = in.read();
      if (c != -1) bos.write(c);
    }

    String field = (String) headers.get("name");
    Vector v = (Vector) this.parts.get(field);

    if (v == null) {
      v = new Vector();
      this.parts.put(field, v);
    }

    v.add(new String(bos.toByteArray(), this.characterEncoding));
  }
  /**
   * Parse a file part
   *
   * @param in
   * @param headers
   * @throws java.io.IOException
   * @throws org.apache.cocoon.servlet.multipart.MultipartException
   */
  private void parseFilePart(DSpaceTokenStream in, Hashtable headers)
      throws IOException, MultipartException {

    byte[] buf = new byte[FILE_BUFFER_SIZE];
    OutputStream out;
    File file = null;

    if (oversized) {
      out = new NullOutputStream();
    } else if (!saveUploadedFilesToDisk) {
      out = new ByteArrayOutputStream();
    } else {
      String fileName = (String) headers.get("filename");
      if (File.separatorChar == '\\') fileName = fileName.replace('/', '\\');
      else fileName = fileName.replace('\\', '/');

      String filePath = uploadDirectory.getPath() + File.separator;
      fileName = new File(fileName).getName();
      file = new File(filePath + fileName);

      if (!allowOverwrite && !file.createNewFile()) {
        if (silentlyRename) {
          int c = 0;
          do {
            file = new File(filePath + c++ + "_" + fileName);
          } while (!file.createNewFile());
        } else {
          throw new MultipartException(
              "Duplicate file '" + file.getName() + "' in '" + file.getParent() + "'");
        }
      }

      out = new FileOutputStream(file);
    }

    if (hasSession) { // upload widget support
      this.uploadStatus.put("finished", Boolean.FALSE);
      this.uploadStatus.put("started", Boolean.TRUE);
      this.uploadStatus.put("widget", headers.get("name"));
      this.uploadStatus.put("filename", headers.get("filename"));
    }

    int length = 0; // Track length for OversizedPart
    try {
      int read = 0;
      while (in.getState() == DSpaceTokenStream.STATE_READING) {
        // read data
        read = in.read(buf);
        length += read;
        out.write(buf, 0, read);

        if (this.hasSession) {
          this.uploadStatus.put(
              "sent", new Integer(((Integer) this.uploadStatus.get("sent")).intValue() + read));
        }
      }
      if (this.hasSession) { // upload widget support
        this.uploadStatus.put(
            "uploadsdone",
            new Integer(((Integer) this.uploadStatus.get("uploadsdone")).intValue() + 1));
        this.uploadStatus.put("error", Boolean.FALSE);
      }
    } catch (IOException ioe) {
      // don't let incomplete file uploads pile up in the upload dir.
      // this usually happens with aborted form submits containing very large files.
      out.close();
      out = null;
      if (file != null) file.delete();
      if (this.hasSession) { // upload widget support
        this.uploadStatus.put("error", Boolean.TRUE);
      }
      throw ioe;
    } finally {
      if (out != null) out.close();
    }

    String name = (String) headers.get("name");
    if (oversized) {
      this.parts.put(
          name, new RejectedPart(headers, length, this.contentLength, this.maxUploadSize));
    } else if (file == null) {
      byte[] bytes = ((ByteArrayOutputStream) out).toByteArray();
      this.parts.put(name, new PartInMemory(headers, bytes));
    } else {
      this.parts.put(name, new PartOnDisk(headers, file));
    }
  }