private void trimBuffer() {
   int bufLen = buffer.length();
   int boundaryLen = getBoundary().length;
   if (bufLen > boundaryLen) {
     // Leave enough bytes in _buffer that we can find an incomplete boundary string
     delegate.appendToPart(buffer.buffer(), 0, bufLen - boundaryLen);
     deleteUpThrough(bufLen - boundaryLen);
   }
 }
 public Range searchFor(byte[] pattern, int start) {
   KMPMatch searcher = new KMPMatch();
   int buffLen = Math.min(buffer.length(), buffer.buffer().length);
   int matchIndex = searcher.indexOf(buffer.buffer(), buffLen, pattern, start);
   if (matchIndex != -1) {
     return new Range(matchIndex, pattern.length);
   } else {
     return new Range(matchIndex, 0);
   }
 }
  private void deleteUpThrough(int location) {
    // int start = location + 1;  // start at the first byte after the location

    if (location <= 0) return;

    byte[] b = buffer.buffer();
    int len = buffer.length();

    int j = 0;
    int i = location;
    while (i < len) {
      b[j++] = b[i++];
    }
    buffer.setLength(j);
  }
  public void appendData(byte[] data, int off, int len) {

    if (buffer == null) {
      return;
    }
    if (len == 0) {
      return;
    }
    buffer.append(data, off, len);

    MultipartReaderState nextState;
    do {
      nextState = MultipartReaderState.kUninitialized;
      int bufLen = buffer.length();
      switch (state) {
        case kAtStart:
          {
            // The entire message might start with a boundary without a leading CRLF.
            byte[] boundaryWithoutLeadingCRLF = getBoundaryWithoutLeadingCRLF();
            if (bufLen >= boundaryWithoutLeadingCRLF.length) {
              if (memcmp(
                  buffer.buffer(), boundaryWithoutLeadingCRLF, boundaryWithoutLeadingCRLF.length)) {
                deleteUpThrough(boundaryWithoutLeadingCRLF.length);
                nextState = MultipartReaderState.kInHeaders;
              } else {
                nextState = MultipartReaderState.kInPrologue;
              }
            }
            break;
          }
        case kInPrologue:
        case kInBody:
          {
            // Look for the next part boundary in the data we just added and the ending bytes of
            // the previous data (in case the boundary string is split across calls)
            if (bufLen < boundary.length) {
              break;
            }
            int start = Math.max(0, bufLen - data.length - boundary.length);
            Range r = searchFor(boundary, start);
            if (r.getLength() > 0) {
              if (state == MultipartReaderState.kInBody) {
                delegate.appendToPart(buffer.buffer(), 0, r.getLocation());
                delegate.finishedPart();
              }
              deleteUpThrough(r.getLocation() + r.getLength());
              nextState = MultipartReaderState.kInHeaders;
            } else {
              trimBuffer();
            }
            break;
          }
        case kInHeaders:
          {
            // First check for the end-of-message string ("--" after separator):
            if (bufLen >= kEOM.length && memcmp(buffer.buffer(), kEOM, kEOM.length)) {
              state = MultipartReaderState.kAtEnd;
              close();
              return;
            }
            // Otherwise look for two CRLFs that delimit the end of the headers:
            Range r = searchFor(kCRLFCRLF, 0);
            if (r.getLength() > 0) {
              String headersString = new String(buffer.buffer(), 0, r.getLocation(), utf8);
              parseHeaders(headersString);
              deleteUpThrough(r.getLocation() + r.getLength());
              delegate.startedPart(headers);
              nextState = MultipartReaderState.kInBody;
            }
            break;
          }
        default:
          {
            throw new IllegalStateException("Unexpected data after end of MIME body");
          }
      }

      if (nextState != MultipartReaderState.kUninitialized) {
        state = nextState;
      }

    } while (nextState != MultipartReaderState.kUninitialized && buffer.length() > 0);
  }