Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chunked encoding #17

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 9 additions & 11 deletions eventmachine_httpserver.gemspec
@@ -1,27 +1,25 @@
# -*- encoding: utf-8 -*-

Gem::Specification.new do |s|
s.name = %q{eventmachine_httpserver}
s.version = "0.2.1"
s.name = "eventmachine_httpserver"
s.version = "0.2.3"

s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Francis Cianfrocca"]
s.cert_chain = nil
s.date = %q{2007-03-16}
s.description = %q{}
s.email = %q{garbagecat10@gmail.com}
s.authors = ["Gregory Hill"]
s.date = "2012-02-20"
s.description = ""
s.email = "garbagecat10@gmail.com"
s.extensions = ["ext/extconf.rb"]
s.extra_rdoc_files = ["docs/COPYING", "docs/README", "docs/RELEASE_NOTES"]
s.files = ["README", "Rakefile", "docs/COPYING", "docs/README", "docs/RELEASE_NOTES", "eventmachine_httpserver.gemspec", "eventmachine_httpserver.gemspec.tmpl", "ext/extconf.rb", "ext/http.cpp", "ext/http.h", "ext/rubyhttp.cpp", "lib/evma_httpserver.rb", "lib/evma_httpserver/response.rb", "test/test_app.rb", "test/test_delegated.rb", "test/test_response.rb"]
s.homepage = %q{https://github.com/eventmachine/evma_httpserver}
s.homepage = "https://github.com/eventmachine/evma_httpserver"
s.rdoc_options = ["--title", "EventMachine_HttpServer", "--main", "docs/README", "--line-numbers"]
s.require_paths = ["lib"]
s.required_ruby_version = Gem::Requirement.new("> 0.0.0")
s.rubygems_version = %q{1.3.7}
s.summary = %q{EventMachine HTTP Server}
s.rubygems_version = "1.8.11"
s.summary = "EventMachine HTTP Server"

if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 1

if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
Expand Down
214 changes: 201 additions & 13 deletions ext/http.cpp
Expand Up @@ -75,6 +75,8 @@ HttpConnection_t::HttpConnection_t()
// instead of buffering it here. To get the latter behavior, user code must call
// dont_accumulate_post.
bAccumulatePost = true;


}


Expand All @@ -84,8 +86,10 @@ HttpConnection_t::~HttpConnection_t

HttpConnection_t::~HttpConnection_t()
{
if (_Content)
if (_Content) {
free (_Content);
_Content = NULL;
}
}


Expand Down Expand Up @@ -125,7 +129,8 @@ void HttpConnection_t::ProcessRequest (const char *method,
int post_length,
const char *post_content,
const char *hdrblock,
int hdrblocksize)
int hdrblocksize,
int content_chunked)
{
cerr << "UNIMPLEMENTED ProcessRequest" << endl;
}
Expand All @@ -141,18 +146,20 @@ void HttpConnection_t::ReceivePostData (const char *data, int len)
}

/*****************************
HttpConnection_t::ConsumeData
HttpConnection_t::
*****************************/

void HttpConnection_t::ConsumeData (const char *data, int length)
{

if (ProtocolState == EndState)
return;

if ((length > 0) && !data)
throw std::runtime_error ("bad args consuming http data");

while (length > 0) {

//----------------------------------- BaseState
// Initialize for a new request. Don't consume any data.
// For anal-retentive security we may want to bzero the header block.
Expand All @@ -162,7 +169,13 @@ void HttpConnection_t::ConsumeData (const char *data, int length)
HeaderLinePos = 0;
HeaderBlockPos = 0;
ContentLength = 0;
ContentChunked = 0;
TrailerProcessing = 0;
ContentPos = 0;
Chunk_req_received = 0;
foundsemi = 0;
foundslashr = 0;
chunklen = 0;
bRequestSeen = false;
bContentLengthSeen = false;
if (_Content) {
Expand Down Expand Up @@ -214,6 +227,8 @@ void HttpConnection_t::ConsumeData (const char *data, int length)
}
else
ProtocolState = HeaderState;

//sleep(30);
}

//----------------------------------- HeaderState
Expand All @@ -240,9 +255,31 @@ void HttpConnection_t::ConsumeData (const char *data, int length)
ContentPos = 0;
ProtocolState = ReadingContentState;
}
else
else if (ContentChunked){
if (_Content){
free (_Content);
}
_Content = NULL;
ContentPos = 0;
ProtocolState = ReadingChunkLen;
//clear our header buffer, reset flags
memset(chunklen_s, '\0',10);
foundsemi = 0;
foundslashr = 0;
foundslashn = 0;
chunklen = 0;
ContentChunked = 0;
}
else{
if(TrailerProcessing){
ContentLength = ContentPos;
Chunk_req_received = 1;
}
// We will come to here for GET or Chunked POST.
ProtocolState = DispatchState;
}
}

HeaderLinePos = 0;
data++;
length--;
Expand All @@ -255,10 +292,12 @@ void HttpConnection_t::ConsumeData (const char *data, int length)
else {
const char *nl = strpbrk (data, "\r\n");
int len = nl ? (nl - data) : length;

if ((size_t)(HeaderLinePos + len) >= sizeof(HeaderLine)) {
// TODO, log this
goto fail_connection;
}

memcpy (HeaderLine + HeaderLinePos, data, len);
data += len;
length -= len;
Expand All @@ -271,9 +310,9 @@ void HttpConnection_t::ConsumeData (const char *data, int length)
// Read POST content.
while ((ProtocolState == ReadingContentState) && (length > 0)) {
int len = ContentLength - ContentPos;
if (len > length)
if (len > length){
len = length;

}
if (bAccumulatePost)
memcpy (_Content + ContentPos, data, len);
else
Expand All @@ -283,16 +322,142 @@ void HttpConnection_t::ConsumeData (const char *data, int length)
length -= len;
ContentPos += len;
if (ContentPos == ContentLength) {
if (bAccumulatePost)
if (bAccumulatePost){
_Content[ContentPos] = 0;
ProtocolState = DispatchState;
}
ProtocolState = DispatchState;
}
}


int chunklen_pos = 0;
//----------------------------------- ReadingChunkLen
// Read POST chunked content.
while ((ProtocolState == ReadingChunkLen) && length > 0)
{
if(*data == ';')
{
//The standards say that there can be a semi-colon after the chunk length, plus some data;
foundsemi = 1;
}
else if(*data == '\r')
{
foundslashr =1;
}
else if(*data == '\n')
{
//If we find a slash n without a slash r then this message is malformatted
if(! foundslashr)
{
goto send_error;
}
foundslashn = 1;
}
else
{
if( ! foundsemi )
{
chunklen_s[chunklen_pos] = *data;
chunklen_pos++;
}
}

data = data+1;
length--;
if (foundslashn)
{
//convert the length to a long
chunklen = (int)strtol ( chunklen_s, NULL, 16 );
if (chunklen == 0 )
{
ProtocolState = HeaderState;

}
else
{
ProtocolState = ReadingChunkedContent;
}
}
}
int readamount =0;
int readischunk=0;
//----------------------------------- ReadingChunkedContent
// Read POST chunked content.
while ((ProtocolState == ReadingChunkedContent && length > 0))
{

if(length <= chunklen)
{
readamount = length;
chunklen-=length;

}
else
{
readischunk = 1;
readamount = chunklen;
}

char *_temp_content = NULL;
//copy to temp variable so we don't overwrite _Content with null if we can't alloc.
_temp_content = (char*) realloc(_Content, ContentPos + readamount + 1);

if (!_temp_content){
// free(_Content);
// _Content = NULL;
throw std::runtime_error ("resource exhaustion");
}
_Content = _temp_content;

memcpy(_Content + ContentPos, data, readamount);
ContentPos += readamount;
_Content[ContentPos] = '\0';
data += readamount;
length -= readamount;

//If we just read the entire chunk then we can expect the next
// two characters to be \r\n
if(readischunk)
{
if(*data != '\r')
{
goto send_error;
}

data++;
length--;

if(*data != '\n')
{
goto send_error;
}
data++;
length--;
ProtocolState = ReadingChunkLen;
memset(chunklen_s, '\0',10); //clear our header buffer.
foundsemi = 0;
foundslashr = 0;
chunklen = 0;
foundslashn = 0;
}

}

//----------------------------------- DispatchState
if (ProtocolState == DispatchState) {
ProcessRequest (RequestMethod, Cookie.c_str(), IfNoneMatch.c_str(), ContentType.c_str(), QueryString.c_str(), PathInfo.c_str(), RequestUri.c_str(), Protocol.c_str(), ContentLength, _Content, HeaderBlock, HeaderBlockPos);
ProcessRequest (RequestMethod,
Cookie.c_str(),
IfNoneMatch.c_str(),
ContentType.c_str(),
QueryString.c_str(),
PathInfo.c_str(),
RequestUri.c_str(),
Protocol.c_str(),
ContentLength,
_Content,
HeaderBlock,
HeaderBlockPos,
Chunk_req_received);

ProtocolState = BaseState;
}
}
Expand All @@ -317,7 +482,6 @@ void HttpConnection_t::ConsumeData (const char *data, int length)
/**************************************
HttpConnection_t::_InterpretHeaderLine
**************************************/

bool HttpConnection_t::_InterpretHeaderLine (const char *header)
{
/* Return T/F to indicate whether we should continue processing
Expand Down Expand Up @@ -389,15 +553,39 @@ bool HttpConnection_t::_InterpretHeaderLine (const char *header)
setenv ("IF_NONE_MATCH", s, true);
}
else if (!strncasecmp (header, "Content-type:", 13)) {
//If we receive this header after we have processed chunked data
//send an error.
if(TrailerProcessing){
_SendError (RESPONSE_CODE_406);
return false;
}
const char *s = header + 13;
while (*s && ((*s==' ') || (*s=='\t')))
s++;
ContentType = s;
if (bSetEnvironmentStrings)
setenv ("CONTENT_TYPE", s, true);
}


else if (!strncasecmp (header, "Transfer-Encoding:", 18)) {
if(TrailerProcessing){
_SendError (RESPONSE_CODE_406);
return false;
}
const char *s = header + 18;
while (*s && ((*s==' ') || (*s=='\t')))
s++;
if (!strncasecmp (s, "chunked", 7)){
TrailerProcessing = 1;
ContentChunked = 1;
}

}
else if (!strncasecmp (header, "Trailer:", 8)) {
if(TrailerProcessing){
_SendError (RESPONSE_CODE_406);
return false;
}
}
// Copy the incoming header into a block
if ((HeaderBlockPos + strlen(header) + 1) < HeaderBlockSize) {
int len = strlen(header);
Expand Down