From 0742e3921f185613f98c2b31cd03182d8aa1d6f5 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 25 Jan 2009 09:58:40 +0000 Subject: [PATCH] 2009-01-25 Tatsuhiro Tsujikawa Added experimental built-in HTTP server. Currently, when a client accesses to the server, it responds with the current download progress. By default, it is disabled. To enable the server, give --enable-http-server option. To change the default port number for the server to listen to, use --http-server-listen-port option. The response HTML is very simple and refreshes it self each 1 second. Because of this refresh, you see flicker in normal web browser such as Firefox. I recommend to use console-based browser such as elinks, w3m. To connect to the server, run 'elinks http://localhost:6800/' while running aria2. Please replace port number '6800'(which is default) with your preference. * src/DownloadEngineFactory.cc * src/HttpHeader.cc * src/HttpHeader.h * src/HttpHeaderProcessor.cc * src/HttpHeaderProcessor.h * src/HttpListenCommand.cc * src/HttpListenCommand.h * src/HttpServer.cc * src/HttpServer.h * src/HttpServerCommand.cc * src/HttpServerCommand.h * src/HttpServerResponseCommand.cc * src/HttpServerResponseCommand.h * src/Makefile.am * src/OptionHandlerFactory.cc * src/Util.cc * src/Util.h * src/help_tags.h * src/option_processing.cc * src/prefs.cc * src/prefs.h * src/usage_text.h * test/HttpHeaderProcessorTest.cc * test/UtilTest.cc --- ChangeLog | 38 +++++ src/DownloadEngineFactory.cc | 10 ++ src/HttpHeader.cc | 20 +++ src/HttpHeader.h | 18 ++- src/HttpHeaderProcessor.cc | 25 ++++ src/HttpHeaderProcessor.h | 4 +- src/HttpListenCommand.cc | 94 ++++++++++++ src/HttpListenCommand.h | 62 ++++++++ src/HttpServer.cc | 96 ++++++++++++ src/HttpServer.h | 74 +++++++++ src/HttpServerCommand.cc | 247 +++++++++++++++++++++++++++++++ src/HttpServerCommand.h | 65 ++++++++ src/HttpServerResponseCommand.cc | 80 ++++++++++ src/HttpServerResponseCommand.h | 67 +++++++++ src/Makefile.am | 6 +- src/Makefile.in | 28 +++- src/OptionHandlerFactory.cc | 20 ++- src/Util.cc | 22 +++ src/Util.h | 2 + src/help_tags.h | 1 + src/option_processing.cc | 9 ++ src/prefs.cc | 4 + src/prefs.h | 4 + src/usage_text.h | 12 ++ test/HttpHeaderProcessorTest.cc | 19 +++ test/UtilTest.cc | 8 + 26 files changed, 1023 insertions(+), 12 deletions(-) create mode 100644 src/HttpListenCommand.cc create mode 100644 src/HttpListenCommand.h create mode 100644 src/HttpServer.cc create mode 100644 src/HttpServer.h create mode 100644 src/HttpServerCommand.cc create mode 100644 src/HttpServerCommand.h create mode 100644 src/HttpServerResponseCommand.cc create mode 100644 src/HttpServerResponseCommand.h diff --git a/ChangeLog b/ChangeLog index 50315468..24b2c6f9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,41 @@ +2009-01-25 Tatsuhiro Tsujikawa + + Added experimental built-in HTTP server. Currently, when a client + accesses to the server, it responds with the current download + progress. By default, it is disabled. To enable the server, give + --enable-http-server option. To change the default port number + for the server to listen to, use --http-server-listen-port option. + The response HTML is very simple and refreshes it self each 1 + second. Because of this refresh, you see flicker in normal web + browser such as Firefox. I recommend to use console-based browser + such as elinks, w3m. To connect to the server, run 'elinks + http://localhost:6800/' while running aria2. Please replace port + number '6800'(which is default) with your preference. + * src/DownloadEngineFactory.cc + * src/HttpHeader.cc + * src/HttpHeader.h + * src/HttpHeaderProcessor.cc + * src/HttpHeaderProcessor.h + * src/HttpListenCommand.cc + * src/HttpListenCommand.h + * src/HttpServer.cc + * src/HttpServer.h + * src/HttpServerCommand.cc + * src/HttpServerCommand.h + * src/HttpServerResponseCommand.cc + * src/HttpServerResponseCommand.h + * src/Makefile.am + * src/OptionHandlerFactory.cc + * src/Util.cc + * src/Util.h + * src/help_tags.h + * src/option_processing.cc + * src/prefs.cc + * src/prefs.h + * src/usage_text.h + * test/HttpHeaderProcessorTest.cc + * test/UtilTest.cc + 2009-01-24 Tatsuhiro Tsujikawa Removed. diff --git a/src/DownloadEngineFactory.cc b/src/DownloadEngineFactory.cc index 04c15fc3..4a97da44 100644 --- a/src/DownloadEngineFactory.cc +++ b/src/DownloadEngineFactory.cc @@ -63,6 +63,7 @@ #include "SelectEventPoll.h" #include "DlAbortEx.h" #include "FileAllocationEntry.h" +#include "HttpListenCommand.h" namespace aria2 { @@ -138,6 +139,15 @@ DownloadEngineFactory::newDownloadEngine(Option* op, stopSec)); } } + if(op->getAsBool(PREF_ENABLE_HTTP_SERVER)) { + HttpListenCommand* httpListenCommand = + new HttpListenCommand(e->newCUID(), e.get()); + if(httpListenCommand->bindPort(op->getAsInt(PREF_HTTP_SERVER_LISTEN_PORT))){ + e->addRoutineCommand(httpListenCommand); + } else { + delete httpListenCommand; + } + } return e; } diff --git a/src/HttpHeader.cc b/src/HttpHeader.cc index d7557e87..23ab0b27 100644 --- a/src/HttpHeader.cc +++ b/src/HttpHeader.cc @@ -193,6 +193,26 @@ void HttpHeader::setVersion(const std::string& version) _version = version; } +const std::string& HttpHeader::getMethod() const +{ + return _method; +} + +void HttpHeader::setMethod(const std::string& method) +{ + _method = method; +} + +const std::string& HttpHeader::getRequestPath() const +{ + return _requestPath; +} + +void HttpHeader::setRequestPath(const std::string& requestPath) +{ + _requestPath = requestPath; +} + void HttpHeader::fill(std::istream& in) { std::string line; diff --git a/src/HttpHeader.h b/src/HttpHeader.h index 7e5c43a4..96bb14d5 100644 --- a/src/HttpHeader.h +++ b/src/HttpHeader.h @@ -36,12 +36,14 @@ #define _D_HTTP_HEADER_H_ #include "common.h" -#include "SharedHandle.h" + #include #include #include #include +#include "SharedHandle.h" + namespace aria2 { class Range; @@ -56,6 +58,12 @@ private: // HTTP version, e.g. HTTP/1.1 std::string _version; + + // HTTP Method, e.g. GET, POST, etc + std::string _method; + + // Request Path + std::string _requestPath; public: HttpHeader() {} ~HttpHeader() {} @@ -77,6 +85,14 @@ public: void setVersion(const std::string& version); + const std::string& getMethod() const; + + void setMethod(const std::string& method); + + const std::string& getRequestPath() const; + + void setRequestPath(const std::string& requestPath); + void fill(std::istream& in); // Clears table. _responseStatus and _version are unchanged. diff --git a/src/HttpHeaderProcessor.cc b/src/HttpHeaderProcessor.cc index 9b2ef0e0..e5d2d46b 100644 --- a/src/HttpHeaderProcessor.cc +++ b/src/HttpHeaderProcessor.cc @@ -111,6 +111,31 @@ SharedHandle HttpHeaderProcessor::getHttpResponseHeader() return httpHeader; } +SharedHandle HttpHeaderProcessor::getHttpRequestHeader() +{ + // The minimum case of the first line is: + // GET / HTTP/1.x + // At least 14bytes before \r\n or \n. + std::string::size_type delimpos = std::string::npos; + if(((delimpos = _buf.find("\r\n")) == std::string::npos && + (delimpos = _buf.find("\n")) == std::string::npos) || + delimpos < 14) { + throw DlRetryEx(EX_NO_STATUS_HEADER); + } + std::deque firstLine; + Util::slice(firstLine, _buf.substr(0, delimpos), ' ', true); + if(firstLine.size() != 3) { + throw DlAbortEx("Malformed HTTP request header."); + } + SharedHandle httpHeader(new HttpHeader()); + httpHeader->setMethod(firstLine[0]); + httpHeader->setRequestPath(firstLine[1]); + httpHeader->setVersion(firstLine[2]); + std::istringstream strm(_buf.substr(14)); + httpHeader->fill(strm); + return httpHeader; +} + std::string HttpHeaderProcessor::getHeaderString() const { std::string::size_type delimpos = std::string::npos; diff --git a/src/HttpHeaderProcessor.h b/src/HttpHeaderProcessor.h index 1e0a703a..d8e8fddc 100644 --- a/src/HttpHeaderProcessor.h +++ b/src/HttpHeaderProcessor.h @@ -74,7 +74,9 @@ public: * Processes the recieved header as a http response header and returns * HttpHeader object. */ - SharedHandle getHttpResponseHeader(); + SharedHandle getHttpResponseHeader(); + + SharedHandle getHttpRequestHeader(); std::string getHeaderString() const; diff --git a/src/HttpListenCommand.cc b/src/HttpListenCommand.cc new file mode 100644 index 00000000..d16f6ce5 --- /dev/null +++ b/src/HttpListenCommand.cc @@ -0,0 +1,94 @@ +/* */ +#include "HttpListenCommand.h" +#include "DownloadEngine.h" +#include "RecoverableException.h" +#include "message.h" +#include "Logger.h" +#include "SocketCore.h" +#include "HttpServerCommand.h" +#include "CUIDCounter.h" +#include "RequestGroupMan.h" + +namespace aria2 { + +HttpListenCommand::HttpListenCommand(int32_t cuid, DownloadEngine* e): + Command(cuid),_e(e) {} + +HttpListenCommand::~HttpListenCommand() {} + +bool HttpListenCommand::execute() +{ + if(_e->_requestGroupMan->downloadFinished() || _e->isHaltRequested()) { + return true; + } + try { + if(_serverSocket->isReadable(0)) { + SharedHandle socket(_serverSocket->acceptConnection()); + HttpServerCommand* c = + new HttpServerCommand(_e->newCUID(), _e, socket); + c->setStatus(Command::STATUS_ONESHOT_REALTIME); + _e->setNoWait(true); + _e->commands.push_back(c); + } + } catch(RecoverableException& e) { + logger->debug(MSG_ACCEPT_FAILURE, _e, cuid); + } + _e->commands.push_back(this); + return false; +} + +bool HttpListenCommand::bindPort(uint16_t port) +{ + if(!_serverSocket.isNull()) { + _e->deleteSocketForReadCheck(_serverSocket, this); + } + _serverSocket.reset(new SocketCore()); + logger->info("CUID#%d - Setting up HttpListenCommand", cuid); + try { + _serverSocket->bind(port); + _serverSocket->beginListen(); + _serverSocket->setNonBlockingMode(); + logger->info(MSG_LISTENING_PORT, cuid, port); + _e->addSocketForReadCheck(_serverSocket, this); + return true; + } catch(RecoverableException& e) { + logger->error(MSG_BIND_FAILURE, e, cuid, port); + _serverSocket->closeConnection(); + } + return false; +} + +} // namespace aria2 diff --git a/src/HttpListenCommand.h b/src/HttpListenCommand.h new file mode 100644 index 00000000..fd165292 --- /dev/null +++ b/src/HttpListenCommand.h @@ -0,0 +1,62 @@ +/* */ +#ifndef _D_HTTP_LISTEN_COMMAND_H_ +#define _D_HTTP_LISTEN_COMMAND_H_ + +#include "Command.h" +#include "SharedHandle.h" + +namespace aria2 { + +class DownloadEngine; +class SocketCore; + +class HttpListenCommand : public Command { +private: + DownloadEngine* _e; + SharedHandle _serverSocket; +public: + HttpListenCommand(int32_t cuid, DownloadEngine* e); + + virtual ~HttpListenCommand(); + + virtual bool execute(); + + bool bindPort(uint16_t port); +}; + +} // namespace aria2 + +#endif // _D_HTTP_LISTEN_COMMAND_H_ diff --git a/src/HttpServer.cc b/src/HttpServer.cc new file mode 100644 index 00000000..14cf6192 --- /dev/null +++ b/src/HttpServer.cc @@ -0,0 +1,96 @@ +/* */ +#include "HttpServer.h" +#include "HttpHeader.h" +#include "SocketCore.h" +#include "HttpHeaderProcessor.h" +#include "DlAbortEx.h" +#include "message.h" +#include "Util.h" + +namespace aria2 { + +HttpServer::HttpServer(const SharedHandle& socket, + DownloadEngine* e): + _socket(socket), + _socketBuffer(socket), + _e(e), + _headerProcessor(new HttpHeaderProcessor()) +{} + +HttpServer::~HttpServer() {} + +SharedHandle HttpServer::receiveRequest() +{ + size_t size = 512; + unsigned char buf[size]; + _socket->peekData(buf, size); + if(size == 0 && !(_socket->wantRead() || _socket->wantWrite())) { + throw DlAbortEx(EX_EOF_FROM_PEER); + } + _headerProcessor->update(buf, size); + if(!_headerProcessor->eoh()) { + _socket->readData(buf, size); + return SharedHandle(); + } + size_t putbackDataLength = _headerProcessor->getPutBackDataLength(); + size -= putbackDataLength; + _socket->readData(buf, size); + + return _headerProcessor->getHttpRequestHeader(); +} + +void HttpServer::feedResponse(const std::string& text) +{ + std::string header = "HTTP/1.0 200 OK\r\n" + "Content-Type: text/html\r\n" + "Content-Length: "+Util::uitos(text.size())+"\r\n" + "Connection: close\r\n" + "\r\n"; + _socketBuffer.feedSendBuffer(header); + _socketBuffer.feedSendBuffer(text); +} + +ssize_t HttpServer::sendResponse() +{ + return _socketBuffer.send(); +} + +bool HttpServer::sendBufferIsEmpty() const +{ + return _socketBuffer.sendBufferIsEmpty(); +} + +} // namespace aria2 diff --git a/src/HttpServer.h b/src/HttpServer.h new file mode 100644 index 00000000..9b755f18 --- /dev/null +++ b/src/HttpServer.h @@ -0,0 +1,74 @@ +/* */ +#ifndef _D_HTTP_SERVER_H_ +#define _D_HTTP_SERVER_H_ + +#include "common.h" + +#include + +#include "SharedHandle.h" +#include "SocketBuffer.h" + +namespace aria2 { + +class SocketCore; +class HttpHeader; +class HttpHeaderProcessor; +class DownloadEngine; + +class HttpServer { +private: + SharedHandle _socket; + SocketBuffer _socketBuffer; + DownloadEngine* _e; + SharedHandle _headerProcessor; +public: + HttpServer(const SharedHandle& socket, DownloadEngine* e); + + ~HttpServer(); + + SharedHandle receiveRequest(); + + void feedResponse(const std::string& text); + + ssize_t sendResponse(); + + bool sendBufferIsEmpty() const; +}; + +} // namespace aria2 + +#endif // _D_HTTP_SERVER_H_ diff --git a/src/HttpServerCommand.cc b/src/HttpServerCommand.cc new file mode 100644 index 00000000..3670605b --- /dev/null +++ b/src/HttpServerCommand.cc @@ -0,0 +1,247 @@ +/* */ +#include "HttpServerCommand.h" + +#include +#include +#include +#include + +#include "SocketCore.h" +#include "DownloadEngine.h" +#include "HttpServer.h" +#include "HttpHeader.h" +#include "Logger.h" +#include "RequestGroup.h" +#include "RequestGroupMan.h" +#include "BtContext.h" +#include "Util.h" +#include "HttpServerResponseCommand.h" +#include "CheckIntegrityEntry.h" +#include "FileAllocationEntry.h" + +namespace aria2 { + +HttpServerCommand::HttpServerCommand(int32_t cuid, DownloadEngine* e, + const SharedHandle& socket): + Command(cuid), + _e(e), + _socket(socket), + _httpServer(new HttpServer(socket, e)) +{ + _e->addSocketForReadCheck(_socket, this); +} + +HttpServerCommand::~HttpServerCommand() +{ + _e->deleteSocketForReadCheck(_socket, this); +} + +class PrintSummaryHtml +{ +private: + std::ostream& _o; + +public: + PrintSummaryHtml(std::ostream& o):_o(o) {} + + void operator()(const SharedHandle& rg) + { + _o << "
getGID() << "\">" + << "[#" << rg->getGID() << "]" + << " FILE:" << "" + << Util::htmlEscape(rg->getFilePath()) << ""; +#ifdef ENABLE_BITTORRENT + SharedHandle btContext = + dynamic_pointer_cast(rg->getDownloadContext()); + if(!btContext.isNull()) { + _o << "
" << " Info Hash:" << btContext->getInfoHashAsString(); + } +#endif // ENABLE_BITTORRENT + _o << "
"; + + TransferStat stat = rg->calculateStat(); + unsigned int eta = 0; + if(rg->getTotalLength() > 0 && stat.getDownloadSpeed() > 0) { + eta = + (rg->getTotalLength()-rg->getCompletedLength())/stat.getDownloadSpeed(); + } +#ifdef ENABLE_BITTORRENT + if(!btContext.isNull() && rg->downloadFinished()) { + _o << "SEEDING" << "(" << "ratio:" + << std::fixed << std::setprecision(1) + << ((stat.getAllTimeUploadLength()*10)/rg->getCompletedLength())/10.0 + << ") "; + } +#endif // ENABLE_BITTORRENT + _o << Util::abbrevSize(rg->getCompletedLength()) + << "B" + << "/" + << Util::abbrevSize(rg->getTotalLength()) + << "B"; + if(rg->getTotalLength() > 0) { + _o << "(" + << 100*rg->getCompletedLength()/rg->getTotalLength() + << "%)"; + } + _o << " " + << "CN:" + << rg->getNumConnection(); + if(!rg->downloadFinished()) { + _o << " " + << "SPD:" + << std::fixed << std::setprecision(2) + << stat.getDownloadSpeed()/1024.0 << "KiB/s"; + } + if(stat.getSessionUploadLength() > 0) { + _o << " " + << "UP:" + << std::fixed << std::setprecision(2) + << stat.getUploadSpeed()/1024.0 << "KiB/s" + << "(" << Util::abbrevSize(stat.getAllTimeUploadLength()) << "B)"; + } + if(eta > 0) { + _o << " " + << "ETA:" + << Util::htmlEscape(Util::secfmt(eta)); + } + _o << "
" + << "
"; + } +}; + +static std::string createResponse(DownloadEngine* e) +{ + std::ostringstream strm; + const std::deque > groups = + e->_requestGroupMan->getRequestGroups(); + std::for_each(groups.begin(), groups.end(), PrintSummaryHtml(strm)); + + { + SharedHandle entry = + e->_fileAllocationMan->getPickedEntry(); + if(!entry.isNull()) { + strm << "
" + << "[FileAlloc:" + << "#" << entry->getRequestGroup()->getGID() << " " + << Util::abbrevSize(entry->getCurrentLength()) + << "B" + << "/" + << Util::abbrevSize(entry->getTotalLength()) + << "B" + << "("; + if(entry->getTotalLength() > 0) { + strm << 100*entry->getCurrentLength()/entry->getTotalLength(); + } else { + strm << "--"; + } + strm << "%)" + << "]"; + if(e->_fileAllocationMan->hasNext()) { + strm << "(" + << e->_fileAllocationMan->countEntryInQueue() + << "waiting...)"; + } + strm << "

"; + } + } +#ifdef ENABLE_MESSAGE_DIGEST + { + SharedHandle entry = + e->_checkIntegrityMan->getPickedEntry(); + if(!entry.isNull()) { + strm << "
" + << "[HashCheck:" + << "#" << entry->getRequestGroup()->getGID() << " " + << Util::abbrevSize(entry->getCurrentLength()) + << "B" + << "/" + << Util::abbrevSize(entry->getTotalLength()) + << "B" + << "(" + << 100*entry->getCurrentLength()/entry->getTotalLength() + << "%)" + << "]"; + if(e->_checkIntegrityMan->hasNext()) { + strm << "(" + << e->_checkIntegrityMan->countEntryInQueue() + << "waiting...)"; + } + strm << "

"; + } + } +#endif // ENABLE_MESSAGE_DIGEST + std::string body = + "" + "" + "" + "" + "aria2" + "" + "

aria2 - Download Progress

"+strm.str()+"" + ""; + return body; +} + +bool HttpServerCommand::execute() +{ + if(_socket->isReadable(0)) { + _timeout.reset(); + SharedHandle header = _httpServer->receiveRequest(); + if(header.isNull()) { + _e->commands.push_back(this); + return false; + } else { + _httpServer->feedResponse(createResponse(_e)); + Command* command = new HttpServerResponseCommand(cuid, _httpServer, _e, + _socket); + command->setStatus(Command::STATUS_ONESHOT_REALTIME); + _e->commands.push_back(command); + _e->setNoWait(true); + return true; + } + } else { + if(_timeout.elapsed(30)) { + logger->info("HTTP request timeout."); + return true; + } else { + _e->commands.push_back(this); + return false; + } + } +} + +} // namespace aria2 diff --git a/src/HttpServerCommand.h b/src/HttpServerCommand.h new file mode 100644 index 00000000..cfb42168 --- /dev/null +++ b/src/HttpServerCommand.h @@ -0,0 +1,65 @@ +/* */ +#ifndef _D_HTTP_SERVER_COMMAND_H_ +#define _D_HTTP_SERVER_COMMAND_H_ + +#include "Command.h" +#include "SharedHandle.h" +#include "TimeA2.h" + +namespace aria2 { + +class DownloadEngine; +class SocketCore; +class HttpServer; + +class HttpServerCommand : public Command { +private: + DownloadEngine* _e; + SharedHandle _socket; + SharedHandle _httpServer; + Time _timeout; +public: + HttpServerCommand(int32_t cuid, DownloadEngine* e, + const SharedHandle& socket); + + virtual ~HttpServerCommand(); + + virtual bool execute(); +}; + +} // namespace aria2 + +#endif // _D_HTTP_SERVER_COMMAND_H_ diff --git a/src/HttpServerResponseCommand.cc b/src/HttpServerResponseCommand.cc new file mode 100644 index 00000000..0578c7a8 --- /dev/null +++ b/src/HttpServerResponseCommand.cc @@ -0,0 +1,80 @@ +/* */ +#include "HttpServerResponseCommand.h" +#include "SocketCore.h" +#include "DownloadEngine.h" +#include "HttpServer.h" +#include "Logger.h" + +namespace aria2 { + +HttpServerResponseCommand::HttpServerResponseCommand +(int32_t cuid, + const SharedHandle& httpServer, + DownloadEngine* e, + const SharedHandle& socket): + Command(cuid), + _e(e), + _socket(socket), + _httpServer(httpServer) +{ + + _e->addSocketForWriteCheck(_socket, this); +} + +HttpServerResponseCommand::~HttpServerResponseCommand() +{ + _e->deleteSocketForWriteCheck(_socket, this); +} + +bool HttpServerResponseCommand::execute() +{ + _httpServer->sendResponse(); + if(_httpServer->sendBufferIsEmpty()) { + logger->info("CUID#%d - HttpServer: all response transmitted.", cuid); + return true; + } else { + if(_timeout.elapsed(10)) { + logger->info("CUID#%d - HttpServer: Timeout while trasmitting response.", + cuid); + return true; + } else { + _e->commands.push_back(this); + return true; + } + } +} + +} // namespace aria2 diff --git a/src/HttpServerResponseCommand.h b/src/HttpServerResponseCommand.h new file mode 100644 index 00000000..52523cf2 --- /dev/null +++ b/src/HttpServerResponseCommand.h @@ -0,0 +1,67 @@ +/* */ +#ifndef _D_HTTP_SERVER_RESPONSE_COMMAND_H_ +#define _D_HTTP_SERVER_RESPONSE_COMMAND_H_ + +#include "Command.h" +#include "SharedHandle.h" +#include "TimeA2.h" + +namespace aria2 { + +class DownloadEngine; +class SocketCore; +class HttpServer; + +class HttpServerResponseCommand : public Command { +private: + DownloadEngine* _e; + SharedHandle _socket; + SharedHandle _httpServer; + Time _timeout; +public: + HttpServerResponseCommand(int32_t cuid, + const SharedHandle& httpServer, + DownloadEngine* e, + const SharedHandle& socket); + + virtual ~HttpServerResponseCommand(); + + virtual bool execute(); +}; + +} // namespace aria2 + +#endif // _D_HTTP_SERVER_RESPONSE_COMMAND_H_ diff --git a/src/Makefile.am b/src/Makefile.am index 616009a8..152a08bb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -200,7 +200,11 @@ SRCS = Socket.h\ EventPoll.h\ SelectEventPoll.cc SelectEventPoll.h\ SequentialPicker.h\ - SequentialDispatcherCommand.h + SequentialDispatcherCommand.h\ + HttpListenCommand.cc HttpListenCommand.h\ + HttpServerCommand.cc HttpServerCommand.h\ + HttpServerResponseCommand.cc HttpServerResponseCommand.h\ + HttpServer.cc HttpServer.h if HAVE_EPOLL SRCS += EpollEventPoll.cc EpollEventPoll.h diff --git a/src/Makefile.in b/src/Makefile.in index 8c1ecc15..01adb93c 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -414,6 +414,9 @@ am__libaria2c_a_SOURCES_DIST = Socket.h SocketCore.cc SocketCore.h \ OptionHandlerException.h bencode.cc bencode.h URIResult.cc \ URIResult.h EventPoll.h SelectEventPoll.cc SelectEventPoll.h \ SequentialPicker.h SequentialDispatcherCommand.h \ + HttpListenCommand.cc HttpListenCommand.h HttpServerCommand.cc \ + HttpServerCommand.h HttpServerResponseCommand.cc \ + HttpServerResponseCommand.h HttpServer.cc HttpServer.h \ EpollEventPoll.cc EpollEventPoll.h TLSContext.h \ LibgnutlsTLSContext.cc LibgnutlsTLSContext.h \ LibsslTLSContext.cc LibsslTLSContext.h GZipDecoder.cc \ @@ -810,14 +813,16 @@ am__objects_22 = SocketCore.$(OBJEXT) Command.$(OBJEXT) \ NsCookieParser.$(OBJEXT) CookieStorage.$(OBJEXT) \ SocketBuffer.$(OBJEXT) OptionHandlerException.$(OBJEXT) \ bencode.$(OBJEXT) URIResult.$(OBJEXT) \ - SelectEventPoll.$(OBJEXT) $(am__objects_1) $(am__objects_2) \ - $(am__objects_3) $(am__objects_4) $(am__objects_5) \ - $(am__objects_6) $(am__objects_7) $(am__objects_8) \ - $(am__objects_9) $(am__objects_10) $(am__objects_11) \ - $(am__objects_12) $(am__objects_13) $(am__objects_14) \ - $(am__objects_15) $(am__objects_16) $(am__objects_17) \ - $(am__objects_18) $(am__objects_19) $(am__objects_20) \ - $(am__objects_21) + SelectEventPoll.$(OBJEXT) HttpListenCommand.$(OBJEXT) \ + HttpServerCommand.$(OBJEXT) \ + HttpServerResponseCommand.$(OBJEXT) HttpServer.$(OBJEXT) \ + $(am__objects_1) $(am__objects_2) $(am__objects_3) \ + $(am__objects_4) $(am__objects_5) $(am__objects_6) \ + $(am__objects_7) $(am__objects_8) $(am__objects_9) \ + $(am__objects_10) $(am__objects_11) $(am__objects_12) \ + $(am__objects_13) $(am__objects_14) $(am__objects_15) \ + $(am__objects_16) $(am__objects_17) $(am__objects_18) \ + $(am__objects_19) $(am__objects_20) $(am__objects_21) am_libaria2c_a_OBJECTS = $(am__objects_22) libaria2c_a_OBJECTS = $(am_libaria2c_a_OBJECTS) am__installdirs = "$(DESTDIR)$(bindir)" @@ -1143,6 +1148,9 @@ SRCS = Socket.h SocketCore.cc SocketCore.h BinaryStream.h Command.cc \ OptionHandlerException.h bencode.cc bencode.h URIResult.cc \ URIResult.h EventPoll.h SelectEventPoll.cc SelectEventPoll.h \ SequentialPicker.h SequentialDispatcherCommand.h \ + HttpListenCommand.cc HttpListenCommand.h HttpServerCommand.cc \ + HttpServerCommand.h HttpServerResponseCommand.cc \ + HttpServerResponseCommand.h HttpServer.cc HttpServer.h \ $(am__append_1) $(am__append_2) $(am__append_3) \ $(am__append_4) $(am__append_5) $(am__append_6) \ $(am__append_7) $(am__append_8) $(am__append_9) \ @@ -1406,12 +1414,16 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HttpHeader.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HttpHeaderProcessor.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HttpInitiateConnectionCommand.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HttpListenCommand.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HttpProxyRequestCommand.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HttpProxyResponseCommand.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HttpRequest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HttpRequestCommand.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HttpResponse.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HttpResponseCommand.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HttpServer.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HttpServerCommand.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HttpServerResponseCommand.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HttpSkipResponseCommand.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/InOrderURISelector.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/InitialMetalinkParserState.Po@am__quote@ diff --git a/src/OptionHandlerFactory.cc b/src/OptionHandlerFactory.cc index f662b5b7..4db13b09 100644 --- a/src/OptionHandlerFactory.cc +++ b/src/OptionHandlerFactory.cc @@ -150,6 +150,14 @@ OptionHandlers OptionHandlerFactory::createOptionHandlers() op->addTag(TAG_ADVANCED); handlers.push_back(op); } + { + SharedHandle op(new BooleanOptionHandler + (PREF_ENABLE_HTTP_SERVER, + TEXT_ENABLE_HTTP_SERVER, + V_FALSE)); + op->addTag(TAG_EXPERIMENTAL); + handlers.push_back(op); + } { std::string params[] = { #ifdef HAVE_EPOLL @@ -187,6 +195,15 @@ OptionHandlers OptionHandlerFactory::createOptionHandlers() op->addTag(TAG_BASIC); handlers.push_back(op); } + { + SharedHandle op(new NumberOptionHandler + (PREF_HTTP_SERVER_LISTEN_PORT, + TEXT_HTTP_SERVER_LISTEN_PORT, + "6800", + 1024, UINT16_MAX)); + op->addTag(TAG_EXPERIMENTAL); + handlers.push_back(op); + } { SharedHandle op(new DefaultOptionHandler (PREF_INPUT_FILE, @@ -1013,7 +1030,7 @@ OptionHandlers OptionHandlerFactory::createOptionHandlers() ("help", TEXT_HELP, TAG_BASIC, - StringFormat("%s,%s,%s,%s,%s,%s,%s,%s,all", + StringFormat("%s,%s,%s,%s,%s,%s,%s,%s,%s,all", TAG_BASIC, TAG_ADVANCED, TAG_HTTP, @@ -1021,6 +1038,7 @@ OptionHandlers OptionHandlerFactory::createOptionHandlers() TAG_FTP, TAG_METALINK, TAG_BITTORRENT, + TAG_EXPERIMENTAL, TAG_HELP).str())); op->addTag(TAG_BASIC); op->addTag(TAG_HELP); diff --git a/src/Util.cc b/src/Util.cc index ebe5c4fa..0eb59873 100644 --- a/src/Util.cc +++ b/src/Util.cc @@ -912,4 +912,26 @@ Util::getNumericNameInfo(const struct sockaddr* sockaddr, socklen_t len) return std::pair(host, atoi(service)); // TODO } +std::string Util::htmlEscape(const std::string& src) +{ + std::string dest; + for(std::string::const_iterator i = src.begin(); i != src.end(); ++i) { + char ch = *i; + if(ch == '<') { + dest += "<"; + } else if(ch == '>') { + dest += ">"; + } else if(ch == '&') { + dest += "&"; + } else if(ch == '\'') { + dest += "'"; + } else if(ch == '"') { + dest += """; + } else { + dest += ch; + } + } + return dest; +} + } // namespace aria2 diff --git a/src/Util.h b/src/Util.h index 002ddafd..de51f699 100644 --- a/src/Util.h +++ b/src/Util.h @@ -277,6 +277,8 @@ public: static std::pair getNumericNameInfo(const struct sockaddr* sockaddr, socklen_t len); + + static std::string htmlEscape(const std::string& src); }; } // namespace aria2 diff --git a/src/help_tags.h b/src/help_tags.h index 88993f86..11cbd195 100644 --- a/src/help_tags.h +++ b/src/help_tags.h @@ -42,6 +42,7 @@ #define TAG_FTP "ftp" #define TAG_METALINK "metalink" #define TAG_BITTORRENT "bittorrent" +#define TAG_EXPERIMENTAL "experimental" #define TAG_HELP "help" #endif // _D_HELP_TAGS_H_ diff --git a/src/option_processing.cc b/src/option_processing.cc index f3011968..0a810749 100644 --- a/src/option_processing.cc +++ b/src/option_processing.cc @@ -192,6 +192,8 @@ Option* option_processing(int argc, char* const argv[]) { PREF_NO_PROXY.c_str(), required_argument, &lopt, 235 }, { PREF_USE_HEAD.c_str(), optional_argument, &lopt, 236 }, { PREF_EVENT_POLL.c_str(), required_argument, &lopt, 237 }, + { PREF_HTTP_SERVER_LISTEN_PORT.c_str(), required_argument, &lopt, 238 }, + { PREF_ENABLE_HTTP_SERVER.c_str(), optional_argument, &lopt, 239 }, #if defined ENABLE_BITTORRENT || defined ENABLE_METALINK { PREF_SHOW_FILES.c_str(), no_argument, NULL, 'S' }, { PREF_SELECT_FILE.c_str(), required_argument, &lopt, 21 }, @@ -487,6 +489,13 @@ Option* option_processing(int argc, char* const argv[]) case 237: cmdstream << PREF_EVENT_POLL << "=" << optarg << "\n"; break; + case 238: + cmdstream << PREF_HTTP_SERVER_LISTEN_PORT << "=" << optarg << "\n"; + break; + case 239: + cmdstream << PREF_ENABLE_HTTP_SERVER << "=" << toBoolArg(optarg) + << "\n"; + break; } break; } diff --git a/src/prefs.cc b/src/prefs.cc index 710b3385..19e9a8ca 100644 --- a/src/prefs.cc +++ b/src/prefs.cc @@ -150,6 +150,10 @@ const std::string PREF_MAX_FILE_NOT_FOUND("max-file-not-found"); const std::string PREF_EVENT_POLL("event-poll"); const std::string V_EPOLL("epoll"); const std::string V_SELECT("select"); +// value: 1*digit +const std::string PREF_HTTP_SERVER_LISTEN_PORT("http-server-listen-port"); +// value: true | false +const std::string PREF_ENABLE_HTTP_SERVER("enable-http-server"); /** * FTP related preferences diff --git a/src/prefs.h b/src/prefs.h index c4ae3688..6eb7089e 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -154,6 +154,10 @@ extern const std::string PREF_MAX_FILE_NOT_FOUND; extern const std::string PREF_EVENT_POLL; extern const std::string V_EPOLL; extern const std::string V_SELECT; +// value: 1*digit +extern const std::string PREF_HTTP_SERVER_LISTEN_PORT; +// value: true | false +extern const std::string PREF_ENABLE_HTTP_SERVER; /** * FTP related preferences diff --git a/src/usage_text.h b/src/usage_text.h index 58d6b864..544d6a3d 100644 --- a/src/usage_text.h +++ b/src/usage_text.h @@ -462,3 +462,15 @@ _(" --use-head[=true|false] Use HEAD method for the first request to the HT " server.") #define TEXT_EVENT_POLL \ _(" --event-poll=POLL Specify the method for polling events.") +#define TEXT_HTTP_SERVER_LISTEN_PORT \ +_(" --http-server-listen-port=PORT Specify a port number for the built-in HTTP\n"\ + " Server to listen to.") +// Excluded from translation candidiates because it is subject to change. +#define TEXT_ENABLE_HTTP_SERVER \ + " --enable-http-server[=true|false] Enable the built-in HTTP server. Currently,\n"\ + " this is the experimental feature and it just\n"\ + " provides the current download progress. Use your\n"\ + " web browser(recommend to use console-based one,\n"\ + " such as elinks, w3m) to connect the server and see\n"\ + " what's what." + diff --git a/test/HttpHeaderProcessorTest.cc b/test/HttpHeaderProcessorTest.cc index 91759ee4..3bd06dda 100644 --- a/test/HttpHeaderProcessorTest.cc +++ b/test/HttpHeaderProcessorTest.cc @@ -20,6 +20,7 @@ class HttpHeaderProcessorTest:public CppUnit::TestFixture { CPPUNIT_TEST(testGetHttpResponseHeader_insufficientStatusLength); CPPUNIT_TEST(testBeyondLimit); CPPUNIT_TEST(testGetHeaderString); + CPPUNIT_TEST(testGetHttpRequestHeader); CPPUNIT_TEST_SUITE_END(); public: @@ -201,4 +202,22 @@ void HttpHeaderProcessorTest::testGetHeaderString() proc.getHeaderString()); } +void HttpHeaderProcessorTest::testGetHttpRequestHeader() +{ + HttpHeaderProcessor proc; + std::string request = "GET /index.html HTTP/1.1\r\n" + "Host: host\r\n" + "Connection: close\r\n" + "\r\n"; + + proc.update(request); + + SharedHandle httpHeader = proc.getHttpRequestHeader(); + CPPUNIT_ASSERT(!httpHeader.isNull()); + CPPUNIT_ASSERT_EQUAL(std::string("GET"), httpHeader->getMethod()); + CPPUNIT_ASSERT_EQUAL(std::string("/index.html"),httpHeader->getRequestPath()); + CPPUNIT_ASSERT_EQUAL(std::string("HTTP/1.1"), httpHeader->getVersion()); + CPPUNIT_ASSERT_EQUAL(std::string("close"),httpHeader->getFirst("Connection")); +} + } // namespace aria2 diff --git a/test/UtilTest.cc b/test/UtilTest.cc index 28ec89b1..3e5c27ba 100644 --- a/test/UtilTest.cc +++ b/test/UtilTest.cc @@ -51,6 +51,7 @@ class UtilTest:public CppUnit::TestFixture { CPPUNIT_TEST(testHttpGMT); CPPUNIT_TEST(testNtoh64); CPPUNIT_TEST(testUrlencode); + CPPUNIT_TEST(testHtmlEscape); CPPUNIT_TEST_SUITE_END(); private: @@ -92,6 +93,7 @@ public: void testHttpGMT(); void testNtoh64(); void testUrlencode(); + void testHtmlEscape(); }; @@ -722,4 +724,10 @@ void UtilTest::testUrlencode() CPPUNIT_ASSERT_EQUAL(std::string("1%5EA%20"), Util::urlencode("1^A ")); } +void UtilTest::testHtmlEscape() +{ + CPPUNIT_ASSERT_EQUAL(std::string("aria2<>"'util"), + Util::htmlEscape("aria2<>\"'util")); +} + } // namespace aria2