Main Page | Class List | File List | Class Members | Related Pages

httprequest.cpp

00001 /***************************************************************************
00002                            httprequest.cpp
00003                            -------------------
00004     class                : GetRequest
00005     begin                : 2004-05-15 - 2005-01-11
00006     copyright            : (C) 2004-2005 by Dominik Haumann
00007     email                : dhdev (at) gmx (dot) de
00008 
00009     dependency           : POSIX socket implementation and C++ (STL)
00010  ***************************************************************************/
00011 
00012 /***************************************************************************
00013  * This library is free software; you can redistribute it and/or
00014  * modify it under the terms of the GNU Library General Public
00015  * License version 2 as published by the Free Software Foundation.
00016  *
00017  * This library is distributed in the hope that it will be useful,
00018  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00019  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00020  * Library General Public License for more details.
00021  *
00022  * You should have received a copy of the GNU Library General Public License
00023  * along with this library; see the file COPYING.LIB.  If not, write to
00024  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00025  * Boston, MA 02111-1307, USA.
00026  ***************************************************************************/
00027 
00028 //BEGIN includes
00029 #include <sstream>
00030 
00031 #include <stdio.h>
00032 #include <errno.h>
00033 #include <string.h>
00034 
00035 #include <stdlib.h>
00036 
00037 #ifndef WIN32
00038 // unix/linux header files
00039 //#include <tcpd.h> // sockaddr_in
00040 #include <unistd.h>
00041 #include <arpa/inet.h>
00042 #include <sys/types.h>
00043 #include <sys/socket.h>
00044 #include <netdb.h>
00045 #define closesocket(s) close(s)
00046 #endif
00047 
00048 #include "httprequest.h"
00049 
00050 // debug
00051 #include <iostream>
00052 //END includes
00053 
00054 namespace HTTP {
00055 
00056 //BEGIN GetRequest
00057 GetRequest::GetRequest()
00058 {
00059     m_useragent = "";
00060 }
00061 
00062 GetRequest::~GetRequest() { }
00063 
00064 void GetRequest::setUserAgent(const std::string& ua)
00065 {
00066     m_useragent = ua;
00067 }
00068 
00069 std::string GetRequest::getUserAgent() const
00070 {
00071     return m_useragent;
00072 }
00073 
00074 void GetRequest::addHeader(const std::string& key, const std::string& value)
00075 {
00076     m_additionalHeader[key] = value;
00077 }
00078 
00079 void GetRequest::addHeader(const std::string& key, int value)
00080 {
00081     // int to string
00082     std::string str;
00083     std::stringstream ss;
00084     ss << value;
00085     ss >> str;
00086 
00087     addHeader(key, str);
00088 }
00089 
00090 void GetRequest::clearHeaders()
00091 {
00092     m_additionalHeader.clear();
00093 }
00094 
00095 void GetRequest::reset()
00096 {
00097     m_useragent = "";
00098     clearHeaders();
00099 }
00100 
00101 bool GetRequest::connect(const std::string& host, int port)
00102 {
00103     m_success = true;
00104     m_host = host;
00105     unsigned long addr;
00106     struct hostent *host_info;
00107 
00108 #ifdef WIN32
00109     // initialize TCP for Windows ("winsock")
00110     short wVersionRequested;
00111     WSADATA wsaData;
00112     wVersionRequested = MAKEWORD (1, 1);
00113     if (WSAStartup (wVersionRequested, &wsaData) != 0) {
00114         setError("HTTP request error: failed to init windows sockets\n");
00115         m_success = false;
00116         return false;
00117     }
00118 #endif
00119 
00120     // create socket
00121     sock = socket( PF_INET, SOCK_STREAM, 0);
00122     if (sock < 0) {
00123         setError("HTTP request error: failed to create socket.");
00124         m_success = false;
00125         return false;
00126     }
00127 
00128     // create socked address of the server
00129     // it is: type, IP-Address and port number
00130     memset(&server, 0, sizeof (server));
00131     if ((addr = inet_addr(host.c_str())) != INADDR_NONE) {
00132         // host is a numeric IP address
00133         memcpy((char *)&server.sin_addr, &addr, sizeof(addr));
00134     } else {
00135         // convert servername to IP address
00136         host_info = gethostbyname(host.c_str());
00137         if (host_info == 0L) {
00138             setError("HTTP request error: unknown server: " + host + "\n");
00139             m_success = false;
00140             return false;
00141         }
00142         memcpy((char *)&server.sin_addr, host_info->h_addr, host_info->h_length);
00143     }
00144 
00145     server.sin_family = AF_INET;
00146     server.sin_port = htons(port);
00147 
00148     return true;
00149 }
00150 
00151 bool GetRequest::request(const std::string& page)
00152 {
00153     m_success = true;
00154     m_header = "";
00155     m_data = "";
00156     m_rawdata = "";
00157     m_rawdata.reserve( 1048576 ); // reserve 1 MB
00158 
00159     setError("HTTP request: no error occured.");
00160 
00161     char buffer[8192];
00162     // connect to server
00163     if ( ::connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0 ) {
00164         setError("HTTP request error: cannot connect to server");
00165         m_success = false;
00166         return false;
00167     }
00168 
00169     // create and send HTTP GET request
00170     std::string cmd = "GET /" + page + " HTTP/1.1\r\n"
00171                       "Host: " + m_host;
00172 
00173     if ( m_useragent.size() > 0 ) {
00174         cmd += "\r\nUser-Agent: " + m_useragent;
00175     }
00176 
00177     std::map <std::string, std::string>::iterator it = m_additionalHeader.begin();
00178     while ( it != m_additionalHeader.end() ) {
00179         cmd += "\r\n" + it->first + ": " + it->second;
00180         it++;
00181     }
00182 
00183     // add empty line
00184     cmd += "\n\n";
00185 
00186     sprintf(buffer, cmd.c_str());
00187 
00188     send(sock, buffer, strlen(buffer), 0);
00189 
00190     return true;
00191 }
00192 
00193 bool GetRequest::process( )
00194 {
00195     int count;
00196     char buffer[8192];
00197 
00198     // get server answer
00199     count = recv(sock, buffer, sizeof(buffer), 0);
00200 
00201     // NOTE: use append, because this way we support binary data!
00202     m_rawdata.append(buffer, count);
00203 
00204     // check if we have reached the end of the http-stream
00205     if (count == 0 || isTransferFinished())
00206     {
00207         // close connection and socket
00208         closesocket(sock);
00209 
00210         // split data from response
00211         m_success = splitData();
00212 
00213         return false;
00214     }
00215 
00216     return true;
00217 }
00218 
00219 bool GetRequest::success() const
00220 {
00221     return m_success;
00222 }
00223 
00224 //BEGIN getter
00225 std::string GetRequest::getHeader() const
00226 {
00227     return m_header;
00228 }
00229 
00230 std::string GetRequest::getHeaderAsString(const std::string& key) const
00231 {
00232     if (m_header.size() > 0) {
00233         unsigned int pos = m_header.find("\r\n" + key + ": ", 0);
00234         if (pos == std::string::npos) return "";
00235 
00236         pos += 4 + key.size();
00237 
00238         unsigned int end = m_rawdata.find("\r\n", pos);
00239         if (end == std::string::npos) return "";
00240 
00241         return m_header.substr(pos, end - pos);
00242     }
00243 
00244     return "";
00245 }
00246 
00247 int GetRequest::getHeaderAsInt(const std::string& key) const
00248 {
00249     std::string str = getHeaderAsString(key);
00250 
00251     if (str.size() == 0) {
00252         return 0;
00253     } else {
00254         int value;
00255         std::stringstream ss;
00256         ss << str;
00257         ss >> value;
00258         return value;
00259     }
00260 }
00261 
00262 std::string GetRequest::getData() const
00263 {
00264     return m_data;
00265 }
00266 
00267 std::string GetRequest::getFooter() const
00268 {
00269     return m_footer;
00270 }
00271 
00272 std::string GetRequest::getRaw() const
00273 {
00274     return m_rawdata;
00275 }
00276 
00277 unsigned int GetRequest::getHeaderSize() const
00278 {
00279     return m_header.size();
00280 }
00281 
00282 unsigned int GetRequest::getDataSize() const
00283 {
00284     return m_data.size();
00285 }
00286 
00287 unsigned int GetRequest::getFooterSize() const
00288 {
00289     return m_footer.size();
00290 }
00291 
00292 unsigned int GetRequest::getRawSize() const
00293 {
00294     return m_rawdata.size();
00295 }
00296 
00297 std::string GetRequest::getError() const
00298 {
00299     return m_errorMessage;
00300 }
00301 //END getter
00302 
00303 void GetRequest::setError(std::string message)
00304 {
00305     m_errorMessage = message;
00306 }
00307 
00308 bool GetRequest::isChunkedTransfer() const
00309 {
00310     return ( m_rawdata.find("\r\nContent-Length: ", 0) == std::string::npos );
00311 }
00312 
00313 bool GetRequest::isTransferFinished() const
00314 {
00315     if ( !isChunkedTransfer() ) return false;
00316 
00317     // now find the first empty line, as this marks is the end of the header \r\n\r\n
00318     unsigned int pos = m_rawdata.find("\r\n\r\n", 0);
00319     if (pos == std::string::npos) return false;
00320     pos += 4;
00321 
00322     unsigned int size;
00323 
00324     // now enter the loop and step through all the chunks, until
00325     // the last chunk is marked with 0. If we find that, all data
00326     // was transferred and we return true.
00327     while ( (size = findSize(pos, m_rawdata)) != std::string::npos ) {
00328         if ( size == 0 ) return true;
00329 
00330         // move pos over the chunked-info-line
00331         pos = m_rawdata.find("\r\n", pos);
00332         if (pos == std::string::npos) return false;
00333         pos += 2;
00334         pos += size + 2;
00335     }
00336 
00337     return false;
00338 }
00339 
00340 unsigned int GetRequest::getContentLength() const
00341 {
00342     unsigned int pos = m_rawdata.find("\r\nContent-Length: ", 0) + 18; // 18 == strlen("\r\nContent-Length: ");
00343     unsigned int end = m_rawdata.find("\r\n", pos);
00344 
00345     std::string tmp = m_rawdata.substr(pos, end - pos);
00346 
00347     return atoi( tmp.c_str() );
00348 }
00349 
00350 bool GetRequest::postProcessData()
00351 {
00352     // in detail, the chunked BNF
00353     //     Chunked-Body   = *chunk
00354     //                       last-chunk
00355     //                       trailer
00356     //                       CRLF
00357 
00358     // chunk          = chunk-size [ chunk-extension ] CRLF
00359     //                  chunk-data CRLF
00360     // chunk-size     = 1*HEX
00361     // last-chunk     = 1*("0") [ chunk-extension ] CRLF
00362 
00363     // chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
00364     // chunk-ext-name = token
00365     // chunk-ext-val  = token | quoted-string
00366     // chunk-data     = chunk-size(OCTET)
00367     // trailer        = *(entity-header CRLF)
00368 
00369     unsigned int size, pos, prev;
00370 
00371     size = findSize(0, m_data);
00372     if (size == std::string::npos) {
00373         m_success = false;
00374         return false;
00375     }
00376 
00377     // remove the first line
00378     pos = m_data.find("\r\n", 0);
00379     if (pos == std::string::npos) {
00380         m_success = false;
00381         return false;
00382     }
00383 
00384     pos += 2;
00385 
00386     m_data = m_data.erase(0, pos);
00387     prev = pos = size;
00388 
00389     // delete crlf from first chunk
00390     m_data = m_data.erase(pos, 2);
00391 
00392     while (true) {
00393         size = findSize(pos, m_data);
00394 
00395         if (size == std::string::npos) {
00396             m_success = false;
00397             return false;
00398         } else if (size == 0) {
00399             // last chunk
00400             m_data = m_data.erase(pos, std::string::npos);
00401             m_success = true;
00402             return true;
00403         }
00404 
00405         // remove the line
00406         pos = m_data.find("\r\n", pos);
00407         if (pos == std::string::npos) {
00408             m_success = false;
00409             return false;
00410         }
00411 
00412         pos += 2;
00413         m_data = m_data.erase(prev, pos - prev);
00414         pos = prev + size;
00415         prev = pos;
00416 
00417         // delete crlf from this chunk
00418         m_data = m_data.erase(pos, 2);
00419     }
00420 
00421     // never happens
00422     return true;
00423 }
00424 
00425 unsigned int GetRequest::findSize(unsigned int n, const std::string& source) const
00426 {
00427     unsigned int end, crlf, semicolon, size;
00428 
00429     crlf = source.find("\r\n", n);
00430     if (crlf == std::string::npos)
00431         return std::string::npos;
00432 
00433     semicolon = source.find(";", n);
00434     if (semicolon == std::string::npos)
00435         semicolon = crlf;
00436 
00437     if (semicolon < crlf)
00438         end = semicolon;
00439     else
00440         end = crlf;
00441 
00442     std::stringstream ss;
00443     ss << source.substr(n, end - n);
00444     ss.setf(std::ios_base::hex, std::ios_base::basefield); // hex is the format
00445     ss >> size;
00446 
00447 //    std::cout << "findSize(" << n << ") == " << size << " (" << source.substr(n, end - n) << ")" << std::endl;
00448 
00449     return size;
00450 }
00451 
00452 bool GetRequest::splitData()
00453 {
00454     // info: http://www.jmarshall.com/easy/http/
00455     //
00456     // if we know the size, we can copy the amount of data.
00457     // otherwise we use the chuked transfer.
00458     //
00459     // chunked transfer:
00460     // Part A
00461     //   HTTP/1.1 200 OK
00462     //   Date: Fri, 31 Dec 1999 23:59:59 GMT
00463     //   Content-Type: text/plain
00464     //   Transfer-Encoding: chunked
00465     //
00466     // Part B
00467     //   1a; ignore-stuff-here
00468     // part C
00469     //   abcdefghijklmnopqrstuvwxyz
00470     //   10
00471     //   1234567890abcdef
00472     // Part D
00473     //   \r\n0\r\n
00474     //   some-footer: some-value
00475     //   another-footer: another-value
00476     //   [blank line here]
00477 
00478 
00479     if ( !isChunkedTransfer() ) {
00480 
00481         // get the exact length of data
00482         unsigned int size = getContentLength();
00483 
00484         unsigned int pos = m_rawdata.find("\r\n\r\n", 0);
00485         if (pos == std::string::npos) {
00486             setError("HTTP request error: no header information available");
00487             m_success = false;
00488             return false;
00489         }
00490 
00491         pos += 4;
00492         // split header
00493         m_header = m_rawdata.substr(0, pos);
00494         // remove header from m_data
00495         m_data = m_rawdata.substr(pos, size);
00496         m_footer = ""; // :)
00497 
00498     } else {
00499 
00500         // face part A: extract the header
00501         // find empty line
00502         unsigned int pos = m_rawdata.find("\r\n\r\n", 0);
00503         if (pos == std::string::npos) {
00504             setError("HTTP request error: no valid header found (chunked)");
00505             m_success = false;
00506             return false;
00507         } else {
00508             m_header = m_rawdata.substr(0, pos + 2);
00509         }
00510         pos += 4;
00511 
00512         // face part D: extract footer
00513         unsigned int end = m_rawdata.rfind("\r\n0", std::string::npos);
00514         if (end == std::string::npos) {
00515                 setError("HTTP request error: no valid footer found (chunked)");
00516                 m_success = false;
00517                 return false;
00518             } else {
00519                 // ha! we got footer
00520                 end += 2;
00521                 end = m_rawdata.find("\r\n", end);
00522                 if (end == std::string::npos) {
00523                     setError("HTTP request error: no valid footer found (chunked*)");
00524                     m_success = false;
00525                     return false;
00526                 }
00527                 end += 2;
00528                 m_footer = m_rawdata.substr(end, std::string::npos);
00529             }
00530 
00531         // copy data part to m_data
00532         m_data = m_rawdata.substr(pos, end - pos);
00533 
00534         // face part B and C
00535         // chunked means that we have many chunks with size/body.
00536         // what we do now, is to remove all sizes, eg. \r\nsize\r\n
00537         if (!postProcessData()) {
00538             setError("HTTP request error: the data part seems to be corrupted (chunked)");
00539             m_success = false;
00540             return false;
00541         }
00542     }
00543 
00544     return true;
00545 }
00546 //END GetRequest
00547 
00548 }
00549 
00550 // kate: space-indent on; indent-width 4; replace-tabs off;

Generated on Sun Jan 16 18:20:26 2005 for WDSMap by  doxygen 1.3.9.1