// Under MIT licence from http://www.mindcontrol.org/~hplus/http-get.html #if defined(WIN32) #include #include #include #else #include #include #endif #include #include #include #include #include "HTTP.h" #include "PortableSockets.h" #if !defined(WIN32) static int strnicmp( char const * a, char const * b, int n) { return strncasecmp( a, b, n ); } #endif namespace { struct Chunk { Chunk() { next_ = 0; size_ = 0; } Chunk * next_; size_t size_; char data_[ 30000 ]; }; class HTTPQuery : public I_HTTPRequest { public: HTTPQuery() { head_ = 0; curRd_ = 0; curWr_ = 0; curOffset_ = 0; toRead_ = 0; complete_ = false; gotLength_ = false; socket_ = BAD_SOCKET_FD; } ~HTTPQuery() { if (socket_ != BAD_SOCKET_FD) { ::closesocket( socket_ ); } Chunk * ch = head_; while( ch != 0) { Chunk * d = ch; ch = ch->next_; ::free( d ); } } void setQuery( char const * host, unsigned short port, char const * url) { if (strlen( url ) > 1536 || strlen( host ) > 256) { return; } struct hostent * hent = gethostbyname( host ); if (hent == 0) { complete_ = true; return; } addr_.sin_family = AF_INET; addr_.sin_addr = *(in_addr *)hent->h_addr_list[0]; addr_.sin_port = htons( port ); socket_ = ::socket( AF_INET, SOCK_STREAM, getprotobyname("tcp")->p_proto ); if (socket_ == BAD_SOCKET_FD) { complete_ = true; return; } int r; r = ::connect( socket_, (sockaddr *)&addr_, sizeof( addr_ ) ); if (r < 0) { complete_ = true; return; } MAKE_SOCKET_NONBLOCKING( socket_, r ); if (r < 0) { complete_ = true; return; } char buf[2048]; sprintf( buf, "GET %s HTTP/1.0\r\nUser-Agent: Dolphin2.0\r\nAccept: */*\r\nHost: %s\r\nConnection: close\r\n\r\n", url, host ); r = ::send( socket_, buf, int(strlen( buf )), NONBLOCK_MSG_SEND ); if (r != (int)strlen( buf )) { complete_ = true; return; } } void dispose() { delete this; } void step() { if (!complete_) { if (!curWr_ || (curWr_->size_ == sizeof( curWr_->data_ ))) { Chunk * c = new Chunk; if (!head_) { head_ = c; curWr_ = c; } else { curWr_->next_ = c; curWr_ = c; } } assert( curWr_ && (curWr_->size_ < sizeof( curWr_->data_ )) ); int r = ::recv( socket_, &curWr_->data_[curWr_->size_], int(sizeof(curWr_->data_)-curWr_->size_), NONBLOCK_MSG_SEND ); if (r > 0) { curWr_->size_ += r; assert( curWr_->size_ <= sizeof( curWr_->data_ ) ); if (gotLength_) { if (toRead_ <= size_t(r)) { toRead_ = 0; complete_ = true; } else { toRead_ -= r; } } if (!gotLength_) { char const * end = &head_->data_[head_->size_]; char const * ptr = &head_->data_[1]; while( ptr < end-1) { if (ptr[-1] == '\n') { if (!strnicmp( ptr, "content-length:", 15 )) { ptr += 15; toRead_ = strtol( ptr, (char **)&ptr, 10 ); gotLength_ = true; } else if (ptr[0] == '\r' && ptr[1] == '\n') { size_t haveRead = end-ptr-2; if (haveRead > toRead_) { toRead_ = 0; } else { toRead_ -= haveRead; } if (toRead_ == 0) { complete_ = true; } break; } } ++ptr; } } } else if (r < 0) { if (!SOCKET_WOULDBLOCK_ERROR( SOCKET_ERRNO )) { complete_ = true; } } } } bool complete() { step(); return complete_; } void rewind() { curRd_ = head_; curOffset_ = 0; } size_t read( void * ptr, size_t size) { step(); if (!head_) { return 0; } if (!curRd_) { curRd_ = head_; assert( curOffset_ == 0 ); } size_t copied = 0; while( size > 0) { assert( curRd_->size_ <= sizeof( curRd_->data_ ) ); size_t toCopy = curRd_->size_ - curOffset_; if (toCopy > size) { toCopy = size; } memcpy( ptr, &curRd_->data_[curOffset_], toCopy ); curOffset_ += toCopy; assert( curOffset_ <= sizeof(curRd_->data_) ); ptr = ((char *)ptr)+toCopy; size -= toCopy; copied += toCopy; if (curOffset_ == curRd_->size_) { if (curRd_->next_ != 0) { curRd_ = curRd_->next_; curOffset_ = 0; } else { break; } } } return copied; } Chunk * head_; Chunk * curRd_; Chunk * curWr_; size_t curOffset_; size_t toRead_; bool complete_; bool gotLength_; SOCKET socket_; sockaddr_in addr_; }; }; I_HTTPRequest * NewHTTPRequest( char const * url ) { static bool socketsInited; if (!socketsInited) { socketsInited = true; INIT_SOCKET_LIBRARY(); } if (strncmp( url, "http://", 7 )) { return 0; } url += 7; char const * path = strchr( url, '/' ); if (!path) { return 0; } char name[ 256 ]; if (path-url > 255) { return 0; } strncpy( name, url, path-url ); name[path-url] = 0; char * port = strrchr( name, ':' ); unsigned short iport = 80; if (port) { *port = 0; iport = (unsigned short)( strtol( port+1, &port, 10 ) ); } HTTPQuery * q = new HTTPQuery; q->setQuery( name, iport, path ); return q; } // TODO(ector): // Currently extremely bad implementation - busy waits! std::string HTTPDownloadText(const char *url) { I_HTTPRequest *r = NewHTTPRequest(url); int timeout = 10; std::string text = ""; time_t t; t = 0; char buf[4096]; while (true) { r->step(); size_t rd = r->read(buf, 4096); if (rd > 0) { buf[rd] = 0; text += buf; } else { if (r->complete()) goto next; if (!t) { time(&t); } else { time_t t2; time(&t2); if (t2 > t + timeout) { fprintf( stderr, "timeout\n"); goto next; } } } } next: r->dispose(); return text; } void UnittestMyNetwork() { I_HTTPRequest * r = NewHTTPRequest( "http://www.cisco.com/" ); char buf[1024]; while( !r->complete()) { r->step(); while( r->read( buf, sizeof( buf ) ) ) ; } char buf2[100000]; r->rewind(); while( r->read( buf2, sizeof( buf2 ) ) ) ; r->dispose(); }