public HttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) {
this.request = checkNotNull(request, "request");
this.charset = checkNotNull(charset, "charset");
this.factory = checkNotNull(factory, "factory");
// Fill default values
setMultipart(this.request.headers().get(HttpHeaderNames.CONTENT_TYPE));
if (request instanceof HttpContent) {
// Offer automatically if the given request is als type of HttpContent
// See #1089
offer((HttpContent) request);
} else {
undecodedChunk = buffer();
parseBody();
}
}
setMultipart()
这里会读取dataBoundary分隔符
request的header里contentType(有分隔符):
multipart/form-data; boundary=55WHkZTkjEACdyX0N-cAsQZeSDA1VP9
/**
* Set from the request ContentType the multipartDataBoundary and the possible charset.
*/
private void setMultipart(String contentType) {
String[] dataBoundary = HttpPostRequestDecoder.getMultipartDataBoundary(contentType);
if (dataBoundary != null) {
multipartDataBoundary = dataBoundary[0];
if (dataBoundary.length > 1 && dataBoundary[1] != null) {
charset = Charset.forName(dataBoundary[1]);
}
} else {
multipartDataBoundary = null;
}
currentStatus = MultiPartStatus.HEADERDELIMITER;
}
offer()
若undecodedChunk为null会copy()一份!!!
public HttpPostMultipartRequestDecoder offer(HttpContent content) {
checkDestroyed();
// Maybe we should better not copy here for performance reasons but this will need
// more care by the caller to release the content in a correct manner later
// So maybe something to optimize on a later stage
ByteBuf buf = content.content();
if (undecodedChunk == null) {
undecodedChunk = buf.copy();
} else {
undecodedChunk.writeBytes(buf);
}
if (content instanceof LastHttpContent) {
isLastChunk = true;
}
parseBody();
if (undecodedChunk != null && undecodedChunk.writerIndex() > discardThreshold) {
undecodedChunk.discardReadBytes();
}
return this;
}
parseBody() 解析
currentStatus ==HEADERDELIMITER
/*** This getMethod will parse as much as possible data and fill the list and map*/
private void parseBody() {
if (currentStatus == MultiPartStatus.PREEPILOGUE || currentStatus == MultiPartStatus.EPILOGUE) {
if (isLastChunk) {
currentStatus = MultiPartStatus.EPILOGUE;
}
return;
}
parseBodyMultipart();
}
parseBodyMultipart()
调用decodeMultipart(currentStatus);
private void parseBodyMultipart() {
if (undecodedChunk == null || undecodedChunk.readableBytes() == 0) {
// nothing to decode
return;
}
InterfaceHttpData data = decodeMultipart(currentStatus);
while (data != null) {
addHttpData(data);
if (currentStatus == MultiPartStatus.PREEPILOGUE || currentStatus == MultiPartStatus.EPILOGUE) {
break;
}
data = decodeMultipart(currentStatus);
}
}
decodeMultipart()
HEADERDELIMITER
private InterfaceHttpData decodeMultipart(MultiPartStatus state) {
switch (state) {
case NOTSTARTED:
throw new ErrorDataDecoderException("Should not be called with the current getStatus");
case PREAMBLE:
// Content-type: multipart/form-data, boundary=AaB03x
throw new ErrorDataDecoderException("Should not be called with the current getStatus");
case HEADERDELIMITER: {
// --AaB03x or --AaB03x--
return findMultipartDelimiter(multipartDataBoundary, MultiPartStatus.DISPOSITION,
MultiPartStatus.PREEPILOGUE);
}
case DISPOSITION: {
// content-disposition: form-data; name="field1"
// content-disposition: form-data; name="pics"; filename="file1.txt"
// and other immediate values like
// Content-type: image/gif
// Content-Type: text/plain
// Content-Type: text/plain; charset=ISO-8859-1
// Content-Transfer-Encoding: binary
// The following line implies a change of mode (mixed mode)
// Content-type: multipart/mixed, boundary=BbC04y
return findMultipartDisposition();
}
case FILEUPLOAD: {
// eventually restart from existing FileUpload
return getFileUpload(multipartDataBoundary);
}
.....
}
}
findMultipartDelimiter
–DJnmlbPjeuyEGa5Z43as4IreARL2DfGKCvISp
1.newline.equals(delimiter),设置currentStatus = dispositionStatus;
2.然后调用decodeMultipart(dispositionStatus),再重新调用decodeMultipart(dispositionStatus)
private InterfaceHttpData decodeMultipart(MultiPartStatus state) {
case DISPOSITION: {
// content-disposition: form-data; name="field1"
// content-disposition: form-data; name="pics"; filename="file1.txt"
// and other immediate values like
// Content-type: image/gif
// Content-Type: text/plain
// Content-Type: text/plain; charset=ISO-8859-1
// Content-Transfer-Encoding: binary
// The following line implies a change of mode (mixed mode)
// Content-type: multipart/mixed, boundary=BbC04y
return findMultipartDisposition();
}
/** * Find the next Multipart Delimiter */
private InterfaceHttpData findMultipartDelimiter(String delimiter, MultiPartStatus dispositionStatus,
MultiPartStatus closeDelimiterStatus) {
// --AaB03x or --AaB03x--
int readerIndex = undecodedChunk.readerIndex();
try {
skipControlCharacters(undecodedChunk);
} catch (NotEnoughDataDecoderException ignored) {
undecodedChunk.readerIndex(readerIndex);
return null;
}
skipOneLine();
String newline;
try {
newline = readDelimiter(undecodedChunk, delimiter);
} catch (NotEnoughDataDecoderException ignored) {
undecodedChunk.readerIndex(readerIndex);
return null;
}
if (newline.equals(delimiter)) {
currentStatus = dispositionStatus;
return decodeMultipart(dispositionStatus);
}
if (newline.equals(delimiter + "--")) {
// CLOSEDELIMITER or MIXED CLOSEDELIMITER found
currentStatus = closeDelimiterStatus;
if (currentStatus == MultiPartStatus.HEADERDELIMITER) {
// MIXEDCLOSEDELIMITER
// end of the Mixed part
currentFieldAttributes = null;
return decodeMultipart(MultiPartStatus.HEADERDELIMITER);
}
return null;
}
undecodedChunk.readerIndex(readerIndex);
throw new ErrorDataDecoderException("No Multipart delimiter found");
}
currentStatus->DISPOSITION
/
* Read one line up to --delimiter or --delimiter-- and if existing the CRLF
* or LF. Note that CRLF or LF are mandatory for opening delimiter
* (--delimiter) but not for closing delimiter (--delimiter--) since some
* clients does not include CRLF in this case.
*/
private static String readDelimiter(ByteBuf undecodedChunk, String delimiter) {
if (!undecodedChunk.hasArray()) {
return readDelimiterStandard(undecodedChunk, delimiter);
}
SeekAheadOptimize sao = new SeekAheadOptimize(undecodedChunk);
int readerIndex = undecodedChunk.readerIndex();
int delimiterPos = 0;
int len = delimiter.length();
try {
StringBuilder sb = new StringBuilder(64);
// check conformity with delimiter
while (sao.pos < sao.limit && delimiterPos < len) {
byte nextByte = sao.bytes[sao.pos++];
if (nextByte == delimiter.charAt(delimiterPos)) {
delimiterPos++;
sb.append((char) nextByte);
} else {
// delimiter not found so break here !
undecodedChunk.readerIndex(readerIndex);
throw new NotEnoughDataDecoderException();
}
}
// Now check if either opening delimiter or closing delimiter
if (sao.pos < sao.limit) {
byte nextByte = sao.bytes[sao.pos++];
if (nextByte == HttpConstants.CR) {
// first check for opening delimiter
if (sao.pos < sao.limit) {
nextByte = sao.bytes[sao.pos++];
if (nextByte == HttpConstants.LF) {
sao.setReadPosition(0);
return sb.toString();
} else {
// error CR without LF
// delimiter not found so break here !
undecodedChunk.readerIndex(readerIndex);
throw new NotEnoughDataDecoderException();
}
} else {
// error since CR must be followed by LF
// delimiter not found so break here !
undecodedChunk.readerIndex(readerIndex);
throw new NotEnoughDataDecoderException();
}
} else if (nextByte == HttpConstants.LF) {
// same first check for opening delimiter where LF used with
// no CR
sao.setReadPosition(0);
return sb.toString();
} else if (nextByte == '-') {
sb.append('-');
// second check for closing delimiter
if (sao.pos < sao.limit) {
nextByte = sao.bytes[sao.pos++];
if (nextByte == '-') {
sb.append('-');
// now try to find if CRLF or LF there
if (sao.pos < sao.limit) {
nextByte = sao.bytes[sao.pos++];
if (nextByte == HttpConstants.CR) {
if (sao.pos < sao.limit) {
nextByte = sao.bytes[sao.pos++];
if (nextByte == HttpConstants.LF) {
sao.setReadPosition(0);
return sb.toString();
} else {
// error CR without LF
// delimiter not found so break here !
undecodedChunk.readerIndex(readerIndex);
throw new NotEnoughDataDecoderException();
}
} else {
// error CR without LF
// delimiter not found so break here !
undecodedChunk.readerIndex(readerIndex);
throw new NotEnoughDataDecoderException();
}
} else if (nextByte == HttpConstants.LF) {
sao.setReadPosition(0);
return sb.toString();
} else {
// No CRLF but ok however (Adobe Flash
// uploader)
// minus 1 since we read one char ahead but
// should not
sao.setReadPosition(1);
return sb.toString();
}
}
// FIXME what do we do here?
// either considering it is fine, either waiting for
// more data to come?
// lets try considering it is fine...
sao.setReadPosition(0);
return sb.toString();
}
// whatever now => error since incomplete
// only one '-' => not enough or whatever not enough
// element
}
}
}
} catch (IndexOutOfBoundsException e) {
undecodedChunk.readerIndex(readerIndex);
throw new NotEnoughDataDecoderException(e);
}
undecodedChunk.readerIndex(readerIndex);
throw new NotEnoughDataDecoderException();
}
findMultipartDisposition() 重要!!!
1.读取到一行行的数据:
Content-Disposition: form-data; name=“file”; filename=“b.xls”
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
currentStatus = MultiPartStatus.FILEUPLOAD;
2.然后设置为属性currentFieldAttributes.put(attribute.getName(), attribute)
3. 然后调用
if (currentStatus == MultiPartStatus.DISPOSITION) {
if (filenameAttribute != null) {
// FileUpload
currentStatus = MultiPartStatus.FILEUPLOAD;
// do not change the buffer position
return decodeMultipart(MultiPartStatus.FILEUPLOAD);
4.设置currentStatus = MultiPartStatus.FILEUPLOAD
再调用decodeMultipart(MultiPartStatus.FILEUPLOAD);
private InterfaceHttpData findMultipartDisposition() {
int readerIndex = undecodedChunk.readerIndex();
if (currentStatus == MultiPartStatus.DISPOSITION) {
currentFieldAttributes = new TreeMap<CharSequence, Attribute>(CaseIgnoringComparator.INSTANCE);
}
// read many lines until empty line with newline found! Store all data
while (!skipOneLine()) {
String newline;
try {
skipControlCharacters(undecodedChunk);
newline = readLine(undecodedChunk, charset);
} catch (NotEnoughDataDecoderException ignored) {
undecodedChunk.readerIndex(readerIndex);
return null;
}
String[] contents = splitMultipartHeader(newline);
if (HttpHeaderNames.CONTENT_DISPOSITION.contentEqualsIgnoreCase(contents[0])) {
boolean checkSecondArg;
if (currentStatus == MultiPartStatus.DISPOSITION) {
checkSecondArg = HttpHeaderValues.FORM_DATA.contentEqualsIgnoreCase(contents[1]);
} else {
checkSecondArg = HttpHeaderValues.ATTACHMENT.contentEqualsIgnoreCase(contents[1])
|| HttpHeaderValues.FILE.contentEqualsIgnoreCase(contents[1]);
}
if (checkSecondArg) {
// read next values and store them in the map as Attribute
for (int i = 2; i < contents.length; i++) {
String[] values = contents[i].split("=", 2);
Attribute attribute;
try {
attribute = getContentDispositionAttribute(values);
} catch (NullPointerException e) {
throw new ErrorDataDecoderException(e);
} catch (IllegalArgumentException e) {
throw new ErrorDataDecoderException(e);
}
currentFieldAttributes.put(attribute.getName(), attribute);
}
}
} else if (HttpHeaderNames.CONTENT_TRANSFER_ENCODING.contentEqualsIgnoreCase(contents[0])) {
Attribute attribute;
try {
attribute = factory.createAttribute(request, HttpHeaderNames.CONTENT_TRANSFER_ENCODING.toString(),
cleanString(contents[1]));
} catch (NullPointerException e) {
throw new ErrorDataDecoderException(e);
} catch (IllegalArgumentException e) {
throw new ErrorDataDecoderException(e);
}
currentFieldAttributes.put(HttpHeaderNames.CONTENT_TRANSFER_ENCODING, attribute);
} else if (HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(contents[0])) {
Attribute attribute;
try {
attribute = factory.createAttribute(request, HttpHeaderNames.CONTENT_LENGTH.toString(),
cleanString(contents[1]));
} catch (NullPointerException e) {
throw new ErrorDataDecoderException(e);
} catch (IllegalArgumentException e) {
throw new ErrorDataDecoderException(e);
}
currentFieldAttributes.put(HttpHeaderNames.CONTENT_LENGTH, attribute);
} else if (HttpHeaderNames.CONTENT_TYPE.contentEqualsIgnoreCase(contents[0])) {
// Take care of possible "multipart/mixed"
if (HttpHeaderValues.MULTIPART_MIXED.contentEqualsIgnoreCase(contents[1])) {
if (currentStatus == MultiPartStatus.DISPOSITION) {
String values = StringUtil.substringAfter(contents[2], '=');
multipartMixedBoundary = "--" + values;
currentStatus = MultiPartStatus.MIXEDDELIMITER;
return decodeMultipart(MultiPartStatus.MIXEDDELIMITER);
} else {
throw new ErrorDataDecoderException("Mixed Multipart found in a previous Mixed Multipart");
}
} else {
for (int i = 1; i < contents.length; i++) {
final String charsetHeader = HttpHeaderValues.CHARSET.toString();
if (contents[i].regionMatches(true, 0, charsetHeader, 0, charsetHeader.length())) {
String values = StringUtil.substringAfter(contents[i], '=');
Attribute attribute;
try {
attribute = factory.createAttribute(request, charsetHeader, cleanString(values));
} catch (NullPointerException e) {
throw new ErrorDataDecoderException(e);
} catch (IllegalArgumentException e) {
throw new ErrorDataDecoderException(e);
}
currentFieldAttributes.put(HttpHeaderValues.CHARSET, attribute);
} else {
Attribute attribute;
try {
attribute = factory.createAttribute(request,
cleanString(contents[0]), contents[i]);
} catch (NullPointerException e) {
throw new ErrorDataDecoderException(e);
} catch (IllegalArgumentException e) {
throw new ErrorDataDecoderException(e);
}
currentFieldAttributes.put(attribute.getName(), attribute);
}
}
}
} else {
throw new ErrorDataDecoderException("Unknown Params: " + newline);
}
}
// Is it a FileUpload
Attribute filenameAttribute = currentFieldAttributes.get(HttpHeaderValues.FILENAME);
if (currentStatus == MultiPartStatus.DISPOSITION) {
if (filenameAttribute != null) {
// FileUpload
currentStatus = MultiPartStatus.FILEUPLOAD;
// do not change the buffer position
return decodeMultipart(MultiPartStatus.FILEUPLOAD);
} else {
// Field
currentStatus = MultiPartStatus.FIELD;
// do not change the buffer position
return decodeMultipart(MultiPartStatus.FIELD);
}
} else {
if (filenameAttribute != null) {
// FileUpload
currentStatus = MultiPartStatus.MIXEDFILEUPLOAD;
// do not change the buffer position
return decodeMultipart(MultiPartStatus.MIXEDFILEUPLOAD);
} else {
// Field is not supported in MIXED mode
throw new ErrorDataDecoderException("Filename not found");
}
}
}
getFileUpload
- mechanism = TransferEncodingMechanism.BINARY;
- factory.createFileUpload()
/** Get the FileUpload (new one or current one)*/
protected InterfaceHttpData getFileUpload(String delimiter) {
// eventually restart from existing FileUpload
// Now get value according to Content-Type and Charset
Attribute encoding = currentFieldAttributes.get(HttpHeaderNames.CONTENT_TRANSFER_ENCODING);
Charset localCharset = charset;
// Default
TransferEncodingMechanism mechanism = TransferEncodingMechanism.BIT7;
if (encoding != null) {
String code;
try {
code = encoding.getValue().toLowerCase();
} catch (IOException e) {
throw new ErrorDataDecoderException(e);
}
if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BIT7.value())) {
localCharset = CharsetUtil.US_ASCII;
} else if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BIT8.value())) {
localCharset = CharsetUtil.ISO_8859_1;
mechanism = TransferEncodingMechanism.BIT8;
} else if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value())) {
// no real charset, so let the default
mechanism = TransferEncodingMechanism.BINARY;
} else {
throw new ErrorDataDecoderException("TransferEncoding Unknown: " + code);
}
}
Attribute charsetAttribute = currentFieldAttributes.get(HttpHeaderValues.CHARSET);
if (charsetAttribute != null) {
try {
localCharset = Charset.forName(charsetAttribute.getValue());
} catch (IOException e) {
throw new ErrorDataDecoderException(e);
} catch (UnsupportedCharsetException e) {
throw new ErrorDataDecoderException(e);
}
}
if (currentFileUpload == null) {
Attribute filenameAttribute = currentFieldAttributes.get(HttpHeaderValues.FILENAME);
Attribute nameAttribute = currentFieldAttributes.get(HttpHeaderValues.NAME);
Attribute contentTypeAttribute = currentFieldAttributes.get(HttpHeaderNames.CONTENT_TYPE);
Attribute lengthAttribute = currentFieldAttributes.get(HttpHeaderNames.CONTENT_LENGTH);
long size;
try {
size = lengthAttribute != null ? Long.parseLong(lengthAttribute.getValue()) : 0L;
} catch (IOException e) {
throw new ErrorDataDecoderException(e);
} catch (NumberFormatException ignored) {
size = 0;
}
try {
String contentType;
if (contentTypeAttribute != null) {
contentType = contentTypeAttribute.getValue();
} else {
contentType = HttpPostBodyUtil.DEFAULT_BINARY_CONTENT_TYPE;
}
currentFileUpload = factory.createFileUpload(request,
cleanString(nameAttribute.getValue()), cleanString(filenameAttribute.getValue()),
contentType, mechanism.value(), localCharset,
size);
} catch (NullPointerException e) {
throw new ErrorDataDecoderException(e);
} catch (IllegalArgumentException e) {
throw new ErrorDataDecoderException(e);
} catch (IOException e) {
throw new ErrorDataDecoderException(e);
}
}
// load data as much as possible
if (!loadDataMultipart(undecodedChunk, delimiter, currentFileUpload)) {
// Delimiter is not found. Need more chunks.
return null;
}
if (currentFileUpload.isCompleted()) {
// ready to load the next one
if (currentStatus == MultiPartStatus.FILEUPLOAD) {
currentStatus = MultiPartStatus.HEADERDELIMITER;
currentFieldAttributes = null;
} else {
currentStatus = MultiPartStatus.MIXEDDELIMITER;
cleanMixedAttributes();
}
FileUpload fileUpload = currentFileUpload;
currentFileUpload = null;
return fileUpload;
}
// do not change the buffer position
// since some can be already saved into FileUpload
// So do not change the currentStatus
return null;
}
DefaultHttpDataFactory.createFileUpload
@Override
public FileUpload createFileUpload(HttpRequest request, String name, String filename,
String contentType, String contentTransferEncoding, Charset charset,
long size) {
if (useDisk) {
FileUpload fileUpload = new DiskFileUpload(name, filename, contentType,
contentTransferEncoding, charset, size);
fileUpload.setMaxSize(maxSize);
checkHttpDataSize(fileUpload);
List<HttpData> fileToDelete = getList(request);
fileToDelete.add(fileUpload);
return fileUpload;
}
if (checkSize) {
FileUpload fileUpload = new MixedFileUpload(name, filename, contentType,
contentTransferEncoding, charset, size, minSize);
fileUpload.setMaxSize(maxSize);
checkHttpDataSize(fileUpload);
List<HttpData> fileToDelete = getList(request);
fileToDelete.add(fileUpload);
return fileUpload;
}
MemoryFileUpload fileUpload = new MemoryFileUpload(name, filename, contentType,
contentTransferEncoding, charset, size);
fileUpload.setMaxSize(maxSize);
checkHttpDataSize(fileUpload);
return fileUpload;
}
public MixedFileUpload(String name, String filename, String contentType,
String contentTransferEncoding, Charset charset, long size,
long limitSize) {
this.limitSize = limitSize;
if (size > this.limitSize) {
fileUpload = new DiskFileUpload(name, filename, contentType,
contentTransferEncoding, charset, size);
} else {
fileUpload = new MemoryFileUpload(name, filename, contentType,
contentTransferEncoding, charset, size);
}
definedSize = size;
}
loadDataMultipart()
这里用了copy
ByteBuf content = undecodedChunk.copy(startReaderIndex, lastPosition - startReaderIndex); httpData.addContent(content, delimiterFound);
/**
* Load the field value from a Multipart request
*
* @return {@code true} if the last chunk is loaded (boundary delimiter found), {@code false} if need more chunks
* @throws ErrorDataDecoderException
*/
private static boolean loadDataMultipart(ByteBuf undecodedChunk, String delimiter, HttpData httpData) {
if (!undecodedChunk.hasArray()) {
return loadDataMultipartStandard(undecodedChunk, delimiter, httpData);
}
final SeekAheadOptimize sao = new SeekAheadOptimize(undecodedChunk);
final int startReaderIndex = undecodedChunk.readerIndex();
final int delimeterLength = delimiter.length();
int index = 0;
int lastRealPos = sao.pos;
byte prevByte = HttpConstants.LF;
boolean delimiterFound = false;
while (sao.pos < sao.limit) {
final byte nextByte = sao.bytes[sao.pos++];
// Check the delimiter
if (prevByte == HttpConstants.LF && nextByte == delimiter.codePointAt(index)) {
index++;
if (delimeterLength == index) {
delimiterFound = true;
break;
}
continue;
}
lastRealPos = sao.pos;
if (nextByte == HttpConstants.LF) {
index = 0;
lastRealPos -= (prevByte == HttpConstants.CR)? 2 : 1;
}
prevByte = nextByte;
}
if (prevByte == HttpConstants.CR) {
lastRealPos--;
}
final int lastPosition = sao.getReadPosition(lastRealPos);
final ByteBuf content = undecodedChunk.copy(startReaderIndex, lastPosition - startReaderIndex);
try {
httpData.addContent(content, delimiterFound);
} catch (IOException e) {
throw new ErrorDataDecoderException(e);
}
undecodedChunk.readerIndex(lastPosition);
return delimiterFound;
}
MixedFileUpload.addContent
public void addContent(ByteBuf buffer, boolean last)
throws IOException {
if (fileUpload instanceof MemoryFileUpload) {
checkSize(fileUpload.length() + buffer.readableBytes());
if (fileUpload.length() + buffer.readableBytes() > limitSize) {
DiskFileUpload diskFileUpload = new DiskFileUpload(fileUpload
.getName(), fileUpload.getFilename(), fileUpload
.getContentType(), fileUpload
.getContentTransferEncoding(), fileUpload.getCharset(),
definedSize);
diskFileUpload.setMaxSize(maxSize);
ByteBuf data = fileUpload.getByteBuf();
if (data != null && data.isReadable()) {
diskFileUpload.addContent(data.retain(), false);
}
// release old upload
fileUpload.release();
fileUpload = diskFileUpload;
}
}
fileUpload.addContent(buffer, last);
}
/**
* Read one line up to the CRLF or LF
*
* @return the String from one line
* @throws NotEnoughDataDecoderException
* Need more chunks and reset the {@code readerIndex} to the previous
* value
*/
private static String readLine(ByteBuf undecodedChunk, Charset charset) {
if (!undecodedChunk.hasArray()) {
return readLineStandard(undecodedChunk, charset);
}
SeekAheadOptimize sao = new SeekAheadOptimize(undecodedChunk);
int readerIndex = undecodedChunk.readerIndex();
try {
ByteBuf line = buffer(64);
while (sao.pos < sao.limit) {
byte nextByte = sao.bytes[sao.pos++];
if (nextByte == HttpConstants.CR) {
if (sao.pos < sao.limit) {
nextByte = sao.bytes[sao.pos++];
if (nextByte == HttpConstants.LF) {
sao.setReadPosition(0);
return line.toString(charset);
} else {
// Write CR (not followed by LF)
sao.pos--;
line.writeByte(HttpConstants.CR);
}
} else {
line.writeByte(nextByte);
}
} else if (nextByte == HttpConstants.LF) {
sao.setReadPosition(0);
return line.toString(charset);
} else {
line.writeByte(nextByte);
}
}
} catch (IndexOutOfBoundsException e) {
undecodedChunk.readerIndex(readerIndex);
throw new NotEnoughDataDecoderException(e);
}
undecodedChunk.readerIndex(readerIndex);
throw new NotEnoughDataDecoderException();
}
/**
* Skip control Characters
*
* @throws NotEnoughDataDecoderException
*/
private static void skipControlCharacters(ByteBuf undecodedChunk) {
if (!undecodedChunk.hasArray()) {
try {
skipControlCharactersStandard(undecodedChunk);
} catch (IndexOutOfBoundsException e1) {
throw new NotEnoughDataDecoderException(e1);
}
return;
}
SeekAheadOptimize sao = new SeekAheadOptimize(undecodedChunk);
while (sao.pos < sao.limit) {
char c = (char) (sao.bytes[sao.pos++] & 0xFF);
if (!Character.isISOControl(c) && !Character.isWhitespace(c)) {
sao.setReadPosition(1);
return;
}
}
throw new NotEnoughDataDecoderException("Access out of bounds");
}
/**
* Skip one empty line
*
* @return True if one empty line was skipped
*/
private boolean skipOneLine() {
if (!undecodedChunk.isReadable()) {
return false;
}
byte nextByte = undecodedChunk.readByte();
if (nextByte == HttpConstants.CR) {
if (!undecodedChunk.isReadable()) {
undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
return false;
}
nextByte = undecodedChunk.readByte();
if (nextByte == HttpConstants.LF) {
return true;
}
undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 2);
return false;
}
if (nextByte == HttpConstants.LF) {
return true;
}
undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
return false;
}