blob: 98d96a0545bdfd8b4bc0bd60a5233dd1675c710f [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jetty.http.HttpTokens.EndOfContent;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/**
* HttpGenerator. Builds HTTP Messages.
*
* If the system property "org.eclipse.jetty.http.HttpGenerator.STRICT" is set to true,
* then the generator will strictly pass on the exact strings received from methods and header
* fields. Otherwise a fast case insensitive string lookup is used that may alter the
* case and white space of some methods/headers
* </p>
*/
public class HttpGenerator
{
private final static Logger LOG = Log.getLogger(HttpGenerator.class);
public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpGenerator.STRICT");
private final static byte[] __colon_space = new byte[] {':',' '};
private final static HttpHeaderValue[] CLOSE = {HttpHeaderValue.CLOSE};
public static final ResponseInfo CONTINUE_100_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,100,null,false);
public static final ResponseInfo PROGRESS_102_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,102,null,false);
public final static ResponseInfo RESPONSE_500_INFO =
new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(){{put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);}},0,HttpStatus.INTERNAL_SERVER_ERROR_500,null,false);
// states
public enum State { START, COMMITTED, COMPLETING, COMPLETING_1XX, END }
public enum Result { NEED_CHUNK,NEED_INFO,NEED_HEADER,FLUSH,CONTINUE,SHUTDOWN_OUT,DONE}
// other statics
public static final int CHUNK_SIZE = 12;
private State _state = State.START;
private EndOfContent _endOfContent = EndOfContent.UNKNOWN_CONTENT;
private long _contentPrepared = 0;
private boolean _noContent = false;
private Boolean _persistent = null;
private final int _send;
private final static int SEND_SERVER = 0x01;
private final static int SEND_XPOWEREDBY = 0x02;
private final static Set<String> __assumedContentMethods = new HashSet<>(Arrays.asList(new String[]{HttpMethod.POST.asString(),HttpMethod.PUT.asString()}));
/* ------------------------------------------------------------------------------- */
public static void setJettyVersion(String serverVersion)
{
SEND[SEND_SERVER] = StringUtil.getBytes("Server: " + serverVersion + "\015\012");
SEND[SEND_XPOWEREDBY] = StringUtil.getBytes("X-Powered-By: " + serverVersion + "\015\012");
SEND[SEND_SERVER | SEND_XPOWEREDBY] = StringUtil.getBytes("Server: " + serverVersion + "\015\012X-Powered-By: " +
serverVersion + "\015\012");
}
/* ------------------------------------------------------------------------------- */
// data
private boolean _needCRLF = false;
/* ------------------------------------------------------------------------------- */
public HttpGenerator()
{
this(false,false);
}
/* ------------------------------------------------------------------------------- */
public HttpGenerator(boolean sendServerVersion,boolean sendXPoweredBy)
{
_send=(sendServerVersion?SEND_SERVER:0) | (sendXPoweredBy?SEND_XPOWEREDBY:0);
}
/* ------------------------------------------------------------------------------- */
public void reset()
{
_state = State.START;
_endOfContent = EndOfContent.UNKNOWN_CONTENT;
_noContent=false;
_persistent = null;
_contentPrepared = 0;
_needCRLF = false;
}
/* ------------------------------------------------------------ */
@Deprecated
public boolean getSendServerVersion ()
{
return (_send&SEND_SERVER)!=0;
}
/* ------------------------------------------------------------ */
@Deprecated
public void setSendServerVersion (boolean sendServerVersion)
{
throw new UnsupportedOperationException();
}
/* ------------------------------------------------------------ */
public State getState()
{
return _state;
}
/* ------------------------------------------------------------ */
public boolean isState(State state)
{
return _state == state;
}
/* ------------------------------------------------------------ */
public boolean isIdle()
{
return _state == State.START;
}
/* ------------------------------------------------------------ */
public boolean isEnd()
{
return _state == State.END;
}
/* ------------------------------------------------------------ */
public boolean isCommitted()
{
return _state.ordinal() >= State.COMMITTED.ordinal();
}
/* ------------------------------------------------------------ */
public boolean isChunking()
{
return _endOfContent==EndOfContent.CHUNKED_CONTENT;
}
/* ------------------------------------------------------------ */
public boolean isNoContent()
{
return _noContent;
}
/* ------------------------------------------------------------ */
public void setPersistent(boolean persistent)
{
_persistent=persistent;
}
/* ------------------------------------------------------------ */
/**
* @return true if known to be persistent
*/
public boolean isPersistent()
{
return Boolean.TRUE.equals(_persistent);
}
/* ------------------------------------------------------------ */
public boolean isWritten()
{
return _contentPrepared>0;
}
/* ------------------------------------------------------------ */
public long getContentPrepared()
{
return _contentPrepared;
}
/* ------------------------------------------------------------ */
public void abort()
{
_persistent=false;
_state=State.END;
_endOfContent=null;
}
/* ------------------------------------------------------------ */
public Result generateRequest(RequestInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
{
switch(_state)
{
case START:
{
if (info==null)
return Result.NEED_INFO;
// Do we need a request header
if (header==null)
return Result.NEED_HEADER;
// If we have not been told our persistence, set the default
if (_persistent==null)
_persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
// prepare the header
int pos=BufferUtil.flipToFill(header);
try
{
// generate ResponseLine
generateRequestLine(info,header);
if (info.getHttpVersion()==HttpVersion.HTTP_0_9)
_noContent=true;
else
generateHeaders(info,header,content,last);
boolean expect100 = info.getHttpFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
if (expect100)
{
_state = State.COMMITTED;
}
else
{
// handle the content.
int len = BufferUtil.length(content);
if (len>0)
{
_contentPrepared+=len;
if (isChunking())
prepareChunk(header,len);
}
_state = last?State.COMPLETING:State.COMMITTED;
}
return Result.FLUSH;
}
catch(Exception e)
{
String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
throw new IOException(message,e);
}
finally
{
BufferUtil.flipToFlush(header,pos);
}
}
case COMMITTED:
{
int len = BufferUtil.length(content);
if (len>0)
{
// Do we need a chunk buffer?
if (isChunking())
{
// Do we need a chunk buffer?
if (chunk==null)
return Result.NEED_CHUNK;
BufferUtil.clearToFill(chunk);
prepareChunk(chunk,len);
BufferUtil.flipToFlush(chunk,0);
}
_contentPrepared+=len;
}
if (last)
{
_state=State.COMPLETING;
return len>0?Result.FLUSH:Result.CONTINUE;
}
return Result.FLUSH;
}
case COMPLETING:
{
if (BufferUtil.hasContent(content))
{
if (LOG.isDebugEnabled())
LOG.debug("discarding content in COMPLETING");
BufferUtil.clear(content);
}
if (isChunking())
{
// Do we need a chunk buffer?
if (chunk==null)
return Result.NEED_CHUNK;
BufferUtil.clearToFill(chunk);
prepareChunk(chunk,0);
BufferUtil.flipToFlush(chunk,0);
_endOfContent=EndOfContent.UNKNOWN_CONTENT;
return Result.FLUSH;
}
_state=State.END;
return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
}
case END:
if (BufferUtil.hasContent(content))
{
if (LOG.isDebugEnabled())
LOG.debug("discarding content in COMPLETING");
BufferUtil.clear(content);
}
return Result.DONE;
default:
throw new IllegalStateException();
}
}
/* ------------------------------------------------------------ */
public Result generateResponse(ResponseInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
{
switch(_state)
{
case START:
{
if (info==null)
return Result.NEED_INFO;
// Handle 0.9
if (info.getHttpVersion() == HttpVersion.HTTP_0_9)
{
_persistent = false;
_endOfContent=EndOfContent.EOF_CONTENT;
if (BufferUtil.hasContent(content))
_contentPrepared+=content.remaining();
_state = last?State.COMPLETING:State.COMMITTED;
return Result.FLUSH;
}
// Do we need a response header
if (header==null)
return Result.NEED_HEADER;
// If we have not been told our persistence, set the default
if (_persistent==null)
_persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
// prepare the header
int pos=BufferUtil.flipToFill(header);
try
{
// generate ResponseLine
generateResponseLine(info,header);
// Handle 1xx and no content responses
int status=info.getStatus();
if (status>=100 && status<200 )
{
_noContent=true;
if (status!=HttpStatus.SWITCHING_PROTOCOLS_101 )
{
header.put(HttpTokens.CRLF);
_state=State.COMPLETING_1XX;
return Result.FLUSH;
}
}
else if (status==HttpStatus.NO_CONTENT_204 || status==HttpStatus.NOT_MODIFIED_304)
{
_noContent=true;
}
generateHeaders(info,header,content,last);
// handle the content.
int len = BufferUtil.length(content);
if (len>0)
{
_contentPrepared+=len;
if (isChunking() && !info.isHead())
prepareChunk(header,len);
}
_state = last?State.COMPLETING:State.COMMITTED;
}
catch(Exception e)
{
String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
throw new IOException(message,e);
}
finally
{
BufferUtil.flipToFlush(header,pos);
}
return Result.FLUSH;
}
case COMMITTED:
{
int len = BufferUtil.length(content);
// handle the content.
if (len>0)
{
if (isChunking())
{
if (chunk==null)
return Result.NEED_CHUNK;
BufferUtil.clearToFill(chunk);
prepareChunk(chunk,len);
BufferUtil.flipToFlush(chunk,0);
}
_contentPrepared+=len;
}
if (last)
{
_state=State.COMPLETING;
return len>0?Result.FLUSH:Result.CONTINUE;
}
return len>0?Result.FLUSH:Result.DONE;
}
case COMPLETING_1XX:
{
reset();
return Result.DONE;
}
case COMPLETING:
{
if (BufferUtil.hasContent(content))
{
if (LOG.isDebugEnabled())
LOG.debug("discarding content in COMPLETING");
BufferUtil.clear(content);
}
if (isChunking())
{
// Do we need a chunk buffer?
if (chunk==null)
return Result.NEED_CHUNK;
// Write the last chunk
BufferUtil.clearToFill(chunk);
prepareChunk(chunk,0);
BufferUtil.flipToFlush(chunk,0);
_endOfContent=EndOfContent.UNKNOWN_CONTENT;
return Result.FLUSH;
}
_state=State.END;
return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
}
case END:
if (BufferUtil.hasContent(content))
{
if (LOG.isDebugEnabled())
LOG.debug("discarding content in COMPLETING");
BufferUtil.clear(content);
}
return Result.DONE;
default:
throw new IllegalStateException();
}
}
/* ------------------------------------------------------------ */
private void prepareChunk(ByteBuffer chunk, int remaining)
{
// if we need CRLF add this to header
if (_needCRLF)
BufferUtil.putCRLF(chunk);
// Add the chunk size to the header
if (remaining>0)
{
BufferUtil.putHexInt(chunk, remaining);
BufferUtil.putCRLF(chunk);
_needCRLF=true;
}
else
{
chunk.put(LAST_CHUNK);
_needCRLF=false;
}
}
/* ------------------------------------------------------------ */
private void generateRequestLine(RequestInfo request,ByteBuffer header)
{
header.put(StringUtil.getBytes(request.getMethod()));
header.put((byte)' ');
header.put(StringUtil.getBytes(request.getUri()));
switch(request.getHttpVersion())
{
case HTTP_1_0:
case HTTP_1_1:
header.put((byte)' ');
header.put(request.getHttpVersion().toBytes());
break;
default:
throw new IllegalStateException();
}
header.put(HttpTokens.CRLF);
}
/* ------------------------------------------------------------ */
private void generateResponseLine(ResponseInfo response, ByteBuffer header)
{
// Look for prepared response line
int status=response.getStatus();
PreparedResponse preprepared = status<__preprepared.length?__preprepared[status]:null;
String reason=response.getReason();
if (preprepared!=null)
{
if (reason==null)
header.put(preprepared._responseLine);
else
{
header.put(preprepared._schemeCode);
header.put(getReasonBytes(reason));
header.put(HttpTokens.CRLF);
}
}
else // generate response line
{
header.put(HTTP_1_1_SPACE);
header.put((byte) ('0' + status / 100));
header.put((byte) ('0' + (status % 100) / 10));
header.put((byte) ('0' + (status % 10)));
header.put((byte) ' ');
if (reason==null)
{
header.put((byte) ('0' + status / 100));
header.put((byte) ('0' + (status % 100) / 10));
header.put((byte) ('0' + (status % 10)));
}
else
header.put(getReasonBytes(reason));
header.put(HttpTokens.CRLF);
}
}
/* ------------------------------------------------------------ */
private byte[] getReasonBytes(String reason)
{
if (reason.length()>1024)
reason=reason.substring(0,1024);
byte[] _bytes = StringUtil.getBytes(reason);
for (int i=_bytes.length;i-->0;)
if (_bytes[i]=='\r' || _bytes[i]=='\n')
_bytes[i]='?';
return _bytes;
}
/* ------------------------------------------------------------ */
private void generateHeaders(Info _info,ByteBuffer header,ByteBuffer content,boolean last)
{
final RequestInfo request=(_info instanceof RequestInfo)?(RequestInfo)_info:null;
final ResponseInfo response=(_info instanceof ResponseInfo)?(ResponseInfo)_info:null;
// default field values
int send=_send;
HttpField transfer_encoding=null;
boolean keep_alive=false;
boolean close=false;
boolean content_type=false;
StringBuilder connection = null;
// Generate fields
if (_info.getHttpFields() != null)
{
for (HttpField field : _info.getHttpFields())
{
HttpHeader h = field.getHeader();
switch (h==null?HttpHeader.UNKNOWN:h)
{
case CONTENT_LENGTH:
// handle specially below
if (_info.getContentLength()>=0)
_endOfContent=EndOfContent.CONTENT_LENGTH;
break;
case CONTENT_TYPE:
{
if (field.getValue().startsWith(MimeTypes.Type.MULTIPART_BYTERANGES.toString()))
_endOfContent=EndOfContent.SELF_DEFINING_CONTENT;
// write the field to the header
content_type=true;
putTo(field,header);
break;
}
case TRANSFER_ENCODING:
{
if (_info.getHttpVersion() == HttpVersion.HTTP_1_1)
transfer_encoding = field;
// Do NOT add yet!
break;
}
case CONNECTION:
{
if (request!=null)
putTo(field,header);
// Lookup and/or split connection value field
HttpHeaderValue[] values = HttpHeaderValue.CLOSE.is(field.getValue())?CLOSE:new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())};
String[] split = null;
if (values[0]==null)
{
split = StringUtil.csvSplit(field.getValue());
if (split.length>0)
{
values=new HttpHeaderValue[split.length];
for (int i=0;i<split.length;i++)
values[i]=HttpHeaderValue.CACHE.get(split[i]);
}
}
// Handle connection values
for (int i=0;i<values.length;i++)
{
HttpHeaderValue value=values[i];
switch (value==null?HttpHeaderValue.UNKNOWN:value)
{
case UPGRADE:
{
// special case for websocket connection ordering
header.put(HttpHeader.CONNECTION.getBytesColonSpace()).put(HttpHeader.UPGRADE.getBytes());
header.put(CRLF);
break;
}
case CLOSE:
{
close=true;
if (response!=null)
{
_persistent=false;
if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
_endOfContent=EndOfContent.EOF_CONTENT;
}
break;
}
case KEEP_ALIVE:
{
if (_info.getHttpVersion() == HttpVersion.HTTP_1_0)
{
keep_alive = true;
if (response!=null)
_persistent=true;
}
break;
}
default:
{
if (connection==null)
connection=new StringBuilder();
else
connection.append(',');
connection.append(split==null?field.getValue():split[i]);
}
}
}
// Do NOT add yet!
break;
}
case SERVER:
{
send=send&~SEND_SERVER;
putTo(field,header);
break;
}
default:
putTo(field,header);
}
}
}
// Calculate how to end _content and connection, _content length and transfer encoding
// settings.
// From RFC 2616 4.4:
// 1. No body for 1xx, 204, 304 & HEAD response
// 2. Force _content-length?
// 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
// 4. Content-Length
// 5. multipart/byteranges
// 6. close
int status=response!=null?response.getStatus():-1;
switch (_endOfContent)
{
case UNKNOWN_CONTENT:
// It may be that we have no _content, or perhaps _content just has not been
// written yet?
// Response known not to have a body
if (_contentPrepared == 0 && response!=null && (status < 200 || status == 204 || status == 304))
_endOfContent=EndOfContent.NO_CONTENT;
else if (_info.getContentLength()>0)
{
// we have been given a content length
_endOfContent=EndOfContent.CONTENT_LENGTH;
long content_length = _info.getContentLength();
if ((response!=null || content_length>0 || content_type ) && !_noContent)
{
// known length but not actually set.
header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
BufferUtil.putDecLong(header, content_length);
header.put(HttpTokens.CRLF);
}
}
else if (last)
{
// we have seen all the _content there is, so we can be content-length limited.
_endOfContent=EndOfContent.CONTENT_LENGTH;
long content_length = _contentPrepared+BufferUtil.length(content);
// Do we need to tell the headers about it
if (content_length>0)
{
header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
BufferUtil.putDecLong(header, content_length);
header.put(HttpTokens.CRLF);
}
else if (!_noContent)
{
if (content_type || response!=null || (request!=null && __assumedContentMethods.contains(request.getMethod())))
header.put(CONTENT_LENGTH_0);
}
}
else
{
// No idea, so we must assume that a body is coming.
_endOfContent = EndOfContent.CHUNKED_CONTENT;
// HTTP 1.0 does not understand chunked content, so we must use EOF content.
// For a request with HTTP 1.0 & Connection: keep-alive
// we *must* close the connection, otherwise the client
// has no way to detect the end of the content.
if (!isPersistent() || _info.getHttpVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal())
_endOfContent = EndOfContent.EOF_CONTENT;
}
break;
case CONTENT_LENGTH:
long content_length = _info.getContentLength();
if (content_length>0)
{
header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
BufferUtil.putDecLong(header, content_length);
header.put(HttpTokens.CRLF);
}
else if (!_noContent)
{
if (content_type || response!=null || (request!=null && __assumedContentMethods.contains(request.getMethod())))
header.put(CONTENT_LENGTH_0);
}
break;
case NO_CONTENT:
throw new IllegalStateException();
case EOF_CONTENT:
_persistent = request!=null;
break;
case CHUNKED_CONTENT:
break;
default:
break;
}
// Add transfer_encoding if needed
if (isChunking())
{
// try to use user supplied encoding as it may have other values.
if (transfer_encoding != null && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue()))
{
String c = transfer_encoding.getValue();
if (c.endsWith(HttpHeaderValue.CHUNKED.toString()))
putTo(transfer_encoding,header);
else
throw new IllegalArgumentException("BAD TE");
}
else
header.put(TRANSFER_ENCODING_CHUNKED);
}
// Handle connection if need be
if (_endOfContent==EndOfContent.EOF_CONTENT)
{
keep_alive=false;
_persistent=false;
}
// If this is a response, work out persistence
if (response!=null)
{
if (!isPersistent() && (close || _info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal()))
{
if (connection==null)
header.put(CONNECTION_CLOSE);
else
{
header.put(CONNECTION_CLOSE,0,CONNECTION_CLOSE.length-2);
header.put((byte)',');
header.put(StringUtil.getBytes(connection.toString()));
header.put(CRLF);
}
}
else if (keep_alive)
{
if (connection==null)
header.put(CONNECTION_KEEP_ALIVE);
else
{
header.put(CONNECTION_KEEP_ALIVE,0,CONNECTION_KEEP_ALIVE.length-2);
header.put((byte)',');
header.put(StringUtil.getBytes(connection.toString()));
header.put(CRLF);
}
}
else if (connection!=null)
{
header.put(HttpHeader.CONNECTION.getBytesColonSpace());
header.put(StringUtil.getBytes(connection.toString()));
header.put(CRLF);
}
}
if (status>199)
header.put(SEND[send]);
// end the header.
header.put(HttpTokens.CRLF);
}
/* ------------------------------------------------------------------------------- */
public static byte[] getReasonBuffer(int code)
{
PreparedResponse status = code<__preprepared.length?__preprepared[code]:null;
if (status!=null)
return status._reason;
return null;
}
/* ------------------------------------------------------------------------------- */
@Override
public String toString()
{
return String.format("%s{s=%s}",
getClass().getSimpleName(),
_state);
}
/* ------------------------------------------------------------------------------- */
/* ------------------------------------------------------------------------------- */
/* ------------------------------------------------------------------------------- */
// common _content
private static final byte[] LAST_CHUNK = { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1+" ");
private static final byte[] CRLF = StringUtil.getBytes("\015\012");
private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
private static final byte[][] SEND = new byte[][]{
new byte[0],
StringUtil.getBytes("Server: Jetty(9.x.x)\015\012"),
StringUtil.getBytes("X-Powered-By: Jetty(9.x.x)\015\012"),
StringUtil.getBytes("Server: Jetty(9.x.x)\015\012X-Powered-By: Jetty(9.x.x)\015\012")
};
/* ------------------------------------------------------------------------------- */
/* ------------------------------------------------------------------------------- */
/* ------------------------------------------------------------------------------- */
// Build cache of response lines for status
private static class PreparedResponse
{
byte[] _reason;
byte[] _schemeCode;
byte[] _responseLine;
}
private static final PreparedResponse[] __preprepared = new PreparedResponse[HttpStatus.MAX_CODE+1];
static
{
int versionLength=HttpVersion.HTTP_1_1.toString().length();
for (int i=0;i<__preprepared.length;i++)
{
HttpStatus.Code code = HttpStatus.getCode(i);
if (code==null)
continue;
String reason=code.getMessage();
byte[] line=new byte[versionLength+5+reason.length()+2];
HttpVersion.HTTP_1_1.toBuffer().get(line,0,versionLength);
line[versionLength+0]=' ';
line[versionLength+1]=(byte)('0'+i/100);
line[versionLength+2]=(byte)('0'+(i%100)/10);
line[versionLength+3]=(byte)('0'+(i%10));
line[versionLength+4]=' ';
for (int j=0;j<reason.length();j++)
line[versionLength+5+j]=(byte)reason.charAt(j);
line[versionLength+5+reason.length()]=HttpTokens.CARRIAGE_RETURN;
line[versionLength+6+reason.length()]=HttpTokens.LINE_FEED;
__preprepared[i] = new PreparedResponse();
__preprepared[i]._schemeCode = Arrays.copyOfRange(line, 0,versionLength+5);
__preprepared[i]._reason = Arrays.copyOfRange(line, versionLength+5, line.length-2);
__preprepared[i]._responseLine=line;
}
}
public static class Info
{
final HttpVersion _httpVersion;
final HttpFields _httpFields;
final long _contentLength;
private Info(HttpVersion httpVersion, HttpFields httpFields, long contentLength)
{
_httpVersion = httpVersion;
_httpFields = httpFields;
_contentLength = contentLength;
}
public HttpVersion getHttpVersion()
{
return _httpVersion;
}
public HttpFields getHttpFields()
{
return _httpFields;
}
public long getContentLength()
{
return _contentLength;
}
}
public static class RequestInfo extends Info
{
private final String _method;
private final String _uri;
public RequestInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, String method, String uri)
{
super(httpVersion,httpFields,contentLength);
_method = method;
_uri = uri;
}
public String getMethod()
{
return _method;
}
public String getUri()
{
return _uri;
}
@Override
public String toString()
{
return String.format("RequestInfo{%s %s %s,%d}",_method,_uri,_httpVersion,_contentLength);
}
}
public static class ResponseInfo extends Info
{
private final int _status;
private final String _reason;
private final boolean _head;
public ResponseInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, int status, String reason, boolean head)
{
super(httpVersion,httpFields,contentLength);
_status = status;
_reason = reason;
_head = head;
}
public boolean isInformational()
{
return _status>=100 && _status<200;
}
public int getStatus()
{
return _status;
}
public String getReason()
{
return _reason;
}
public boolean isHead()
{
return _head;
}
@Override
public String toString()
{
return String.format("ResponseInfo{%s %s %s,%d,%b}",_httpVersion,_status,_reason,_contentLength,_head);
}
}
private static void putSanitisedName(String s,ByteBuffer buffer)
{
int l=s.length();
for (int i=0;i<l;i++)
{
char c=s.charAt(i);
if (c<0 || c>0xff || c=='\r' || c=='\n'|| c==':')
buffer.put((byte)'?');
else
buffer.put((byte)(0xff&c));
}
}
private static void putSanitisedValue(String s,ByteBuffer buffer)
{
int l=s.length();
for (int i=0;i<l;i++)
{
char c=s.charAt(i);
if (c<0 || c>0xff || c=='\r' || c=='\n')
buffer.put((byte)' ');
else
buffer.put((byte)(0xff&c));
}
}
public static void putTo(HttpField field, ByteBuffer bufferInFillMode)
{
if (field instanceof CachedHttpField)
{
((CachedHttpField)field).putTo(bufferInFillMode);
}
else
{
HttpHeader header=field.getHeader();
if (header!=null)
{
bufferInFillMode.put(header.getBytesColonSpace());
putSanitisedValue(field.getValue(),bufferInFillMode);
}
else
{
putSanitisedName(field.getName(),bufferInFillMode);
bufferInFillMode.put(__colon_space);
putSanitisedValue(field.getValue(),bufferInFillMode);
}
BufferUtil.putCRLF(bufferInFillMode);
}
}
public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode)
{
for (HttpField field : fields)
{
if (field != null)
putTo(field,bufferInFillMode);
}
BufferUtil.putCRLF(bufferInFillMode);
}
public static class CachedHttpField extends HttpField
{
private final byte[] _bytes;
public CachedHttpField(HttpHeader header,String value)
{
super(header,value);
int cbl=header.getBytesColonSpace().length;
_bytes=Arrays.copyOf(header.getBytesColonSpace(), cbl+value.length()+2);
System.arraycopy(value.getBytes(StandardCharsets.ISO_8859_1),0,_bytes,cbl,value.length());
_bytes[_bytes.length-2]=(byte)'\r';
_bytes[_bytes.length-1]=(byte)'\n';
}
public void putTo(ByteBuffer bufferInFillMode)
{
bufferInFillMode.put(_bytes);
}
}
}