// #define DEBUG_HTTPD #include #include #include #include #include #include #include #include #include #include eHTTPDataSource::eHTTPDataSource(eHTTPConnection *c): connection(c) { } eHTTPDataSource::~eHTTPDataSource() { } void eHTTPDataSource::haveData(void *data, int len) { } int eHTTPDataSource::doWrite(int) { return 0; } DEFINE_REF(eHTTPError); eHTTPError::eHTTPError(eHTTPConnection *c, int errcode): eHTTPDataSource(c), errcode(errcode) { std::string error="unknown error"; switch (errcode) { case 400: error="Bad Request"; break; case 401: error="Unauthorized"; break; case 403: error="Forbidden"; break; case 404: error="Not found"; break; case 405: error="Method not allowed"; break; case 500: error="Internal server error"; break; } connection->code_descr=error; connection->code=errcode; connection->local_header["Content-Type"]=std::string("text/html"); } int eHTTPError::doWrite(int w) { std::string html; html+="Error " + getNum(connection->code) + ""+ "

Error " + getNum(errcode) + ": " + connection->code_descr + "

\n"; connection->writeBlock(html.c_str(), html.length()); return -1; } eHTTPConnection::eHTTPConnection(int socket, int issocket, eHTTPD *parent, int persistent): eSocket(socket, issocket, parent->ml), parent(parent), persistent(persistent) { #ifdef DEBUG_HTTPD eDebug("eHTTPConnection"); #endif CONNECT(this->readyRead_ , eHTTPConnection::readData); CONNECT(this->bytesWritten_ , eHTTPConnection::bytesWritten); CONNECT(this->error_ , eHTTPConnection::gotError); CONNECT(this->connectionClosed_ , eHTTPConnection::destruct); CONNECT(this->hangup , eHTTPConnection::gotHangup); buffersize=128*1024; localstate=stateWait; remotestate=stateRequest; data=0; } void eHTTPConnection::destruct() { eDebug("destruct, this %p!", this); gotHangup(); delete this; } eHTTPConnection::eHTTPConnection(eMainloop *ml): eSocket(ml), parent(0), persistent(0) { CONNECT(this->readyRead_ , eHTTPConnection::readData); CONNECT(this->bytesWritten_ , eHTTPConnection::bytesWritten); CONNECT(this->error_ , eHTTPConnection::gotError); CONNECT(this->connected_ , eHTTPConnection::hostConnected); CONNECT(this->connectionClosed_ , eHTTPConnection::destruct); localstate=stateWait; remotestate=stateWait; buffersize=64*1024; data=0; } void eHTTPConnection::hostConnected() { processLocalState(); } void eHTTPConnection::start() { if (localstate==stateWait) { localstate=stateRequest; processLocalState(); } } void eHTTPConnection::gotHangup() { if (data && remotestate == stateData) data->haveData(0, 0); data = 0; transferDone(0); localstate=stateWait; remotestate=stateRequest; remote_header.clear(); local_header.clear(); } eHTTPConnection *eHTTPConnection::doRequest(const char *uri, eMainloop *ml, int *error) { if (error) *error=0; char *defaultproto="http"; std::string proto, host, path; int port=80; int state=0; // 0 proto, 1 host, 2 port 3 path while (*uri) { switch (state) { case 0: if (!strncmp(uri, "://", 3)) { state=1; uri+=3; } else if ((*uri=='/') || (*uri==':')) { host=proto; state=1; proto=defaultproto; } else proto.push_back(*uri++); break; case 1: if (*uri=='/') state=3; else if (*uri==':') { state=2; port=0; uri++; } else host.push_back(*uri++); break; case 2: if (*uri=='/') state=3; else { if (!isdigit(*uri)) { port=-1; state=3; } else { port*=10; port+=*uri++-'0'; } } break; case 3: path.push_back(*uri++); } } if (state==0) { path=proto; proto=defaultproto; } #ifdef DEBUG_HTTPD eDebug("proto: '%s', host '%s', path '%s', port '%d'", proto.c_str(), host.c_str(), path.c_str(), port); #endif if (!host.size()) { eDebug("no host given"); if (error) *error=ENOENT; return 0; } if (strcmp(proto.c_str(), "http")) { eDebug("invalid protocol (%s)", proto.c_str()); if (error) *error=EINVAL; return 0; } if (port == -1) { eDebug("invalid port"); if (error) *error=EINVAL; return 0; } if (!path.size()) path="/"; eHTTPConnection *c=new eHTTPConnection(ml); c->request="GET"; c->requestpath=path.c_str(); c->httpversion="HTTP/1.0"; c->local_header["Host"]=host; if ((*error=c->connectToHost(host, port))) // already deleted by error return 0; return c; } void eHTTPConnection::readData() { processRemoteState(); } void eHTTPConnection::bytesWritten(int) { processLocalState(); } int eHTTPConnection::processLocalState() { switch (state()) { case Connection: break; default: return 0; } int done=0; while (!done) { #ifdef DEBUG_HTTPD eDebug("processing local state %d", localstate); #endif switch (localstate) { case stateWait: #ifdef DEBUG_HTTPD eDebug("local wait"); #endif done=1; break; case stateRequest: { #ifdef DEBUG_HTTPD eDebug("local request"); #endif std::string req=request+" "+requestpath+" "+httpversion+"\r\n"; writeBlock(req.c_str(), req.length()); localstate=stateHeader; remotestate=stateResponse; break; } case stateResponse: { #ifdef DEBUG_HTTPD eDebug("local Response"); #endif writeString( (httpversion + " " + getNum(code) + " " + code_descr + "\r\n").c_str() ); localstate=stateHeader; local_header["Connection"]="close"; break; } case stateHeader: #ifdef DEBUG_HTTPD eDebug("local header"); #endif for (std::map::iterator cur=local_header.begin(); cur!=local_header.end(); ++cur) { writeString(cur->first.c_str()); writeString(": "); writeString(cur->second.c_str()); writeString("\r\n"); } writeString("\r\n"); if (request=="HEAD") localstate=stateDone; else localstate=stateData; break; case stateData: #ifdef DEBUG_HTTPD eDebug("local data"); #endif if (data) { int btw=buffersize-bytesToWrite(); if (btw>0) { if (data->doWrite(btw)<0) { localstate=stateDone; } else done=1; } else done=1; } else done=1; // wait for remote response break; case stateDone: #if 0 // move to stateClose if (remote_header.find("Connection") != remote_header.end()) { std::string &connection=remote_header["Connection"]; if (connection == "keep-alive") localstate=stateWait; else localstate=stateClose; } #endif #ifdef DEBUG_HTTPD eDebug("locate state done"); #endif if (!persistent) localstate=stateClose; else localstate=stateWait; break; case stateClose: #ifdef DEBUG_HTTPD eDebug("closedown"); #endif if (persistent) { data = 0; localstate = stateWait; } else close(); // bye, bye, remote return 1; } } #ifdef DEBUG_HTTPD eDebug("end local"); #endif return 0; } int eHTTPConnection::processRemoteState() { int abort=0, done=0; #ifdef DEBUG_HTTPD eDebug("%d bytes avail", bytesAvailable()); #endif while (((!done) || bytesAvailable()) && !abort) { switch (remotestate) { case stateWait: { int i=0; #ifdef DEBUG_HTTPD eDebug("remote stateWait"); #endif char buffer[1024]; while (bytesAvailable()) { i=readBlock(buffer, 1024); } done=1; break; } case stateRequest: { #ifdef DEBUG_HTTPD eDebug("stateRequest"); #endif std::string line; if (!getLine(line)) { done=1; abort=1; break; } int del[2]; del[0]=line.find(" "); del[1]=line.find(" ", del[0]+1); if (del[0]==-1) { data = 0; eDebug("request buggy"); httpversion="HTTP/1.0"; data=new eHTTPError(this, 400); done=0; localstate=stateResponse; remotestate=stateDone; if (processLocalState()) return -1; break; } request=line.substr(0, del[0]); requestpath=line.substr(del[0]+1, (del[1]==-1)?-1:(del[1]-del[0]-1)); if (del[1]!=-1) { is09=0; httpversion=line.substr(del[1]+1); } else is09=1; if (is09 || (httpversion.substr(0, 7) != "HTTP/1.") || httpversion.size()!=8) { remotestate=stateData; done=0; httpversion="HTTP/1.0"; content_length_remaining=content_length_remaining=0; data=new eHTTPError(this, 400); // bad request - not supporting version 0.9 yet } else remotestate=stateHeader; break; } case stateResponse: { #ifdef DEBUG_HTTPD eDebug("state response.."); #endif std::string line; if (!getLine(line)) { done=1; abort=1; break; } #ifdef DEBUG_HTTPD eDebug("line: %s", line.c_str()); #endif int del[2]; del[0]=line.find(" "); del[1]=line.find(" ", del[0]+1); if (del[0]==-1) code=-1; else { httpversion=line.substr(0, del[0]); code=atoi(line.substr(del[0]+1, (del[1]==-1)?-1:(del[1]-del[0]-1)).c_str()); if (del[1] != -1) code_descr=line.substr(del[1]+1); else code_descr=""; } remotestate=stateHeader; break; } case stateHeader: { #ifdef DEBUG_HTTPD eDebug("remote stateHeader"); #endif std::string line; if (!getLine(line)) { done=1; abort=1; break; } if (!line.length()) { content_length=0; content_length_remaining=-1; if (remote_header.count("Content-Length")) { content_length=atoi(remote_header["Content-Length"].c_str()); content_length_remaining=content_length; } if (parent) { for (eSmartPtrList::iterator i(parent->resolver); i != parent->resolver.end(); ++i) if (!(i->getDataSource(data, request, requestpath, this))) break; localstate=stateResponse; // can be overridden by dataSource } else data=createDataSource(this); if (!data) { data = 0; data = new eHTTPError(this, 404); } if (content_length || // if content-length is set, we have content remote_header.count("Content-Type") || // content-type - the same (localstate != stateResponse)) // however, if we are NOT in response-state, so we are NOT server, there's ALWAYS more data to come. (exception: http/1.1 persistent) remotestate=stateData; else { data->haveData(0, 0); remotestate=stateDone; } if (processLocalState()) return -1; } else { int del=line.find(":"); std::string name=line.substr(0, del), value=line.substr(del+1); if (value[0]==' ') value=value.substr(1); remote_header[std::string(name)]=std::string(value); } done=1; break; } case stateData: { #ifdef DEBUG_HTTPD eDebug("remote stateData"); #endif ASSERT(data); char buffer[16284]; int len; while (bytesAvailable()) { int tr=sizeof(buffer); if (content_length_remaining != -1) if (tr>content_length_remaining) tr=content_length_remaining; len=readBlock(buffer, tr); data->haveData(buffer, len); if (content_length_remaining != -1) content_length_remaining-=len; if (!content_length_remaining) { data->haveData(0, 0); remotestate=stateDone; break; } } done=1; if (processLocalState()) return -1; break; } case stateDone: remotestate=stateClose; break; case stateClose: // if (!persistent) remotestate=stateWait; // else // remotestate=stateRequest; abort=1; break; default: eDebug("HTTP: invalid state %d", remotestate); done=1; } } #ifdef DEBUG_HTTPD eDebug("end remote"); #endif return 0; } void eHTTPConnection::writeString(const char *data) { writeBlock(data, strlen(data)); } int eHTTPConnection::getLine(std::string &line) { if (!canReadLine()) return 0; line = readLine(); line.erase(line.length()-1); if (line[(line.length()-1)] == '\r') line.erase(line.length()-1); return 1; } void eHTTPConnection::gotError(int err) { data = 0; transferDone(err); delete this; } eHTTPD::eHTTPD(int port, eMainloop *ml): eServerSocket(port, ml), ml(ml) { if (!ok()) eDebug("[NET] httpd server FAILED on port %d", port); else eDebug("[NET] httpd server started on port %d", port); } eHTTPConnection::~eHTTPConnection() { eDebug("HTTP connection destruct"); if ((!persistent) && (state()!=Idle)) eWarning("~eHTTPConnection, status still %d", state()); } void eHTTPD::newConnection(int socket) { new eHTTPConnection(socket, 1, this); }