http.cc

00001 /*
00002    Copyright (C) 2000-2003 Waldo Bastian <bastian@kde.org>
00003    Copyright (C) 2000-2002 George Staikos <staikos@kde.org>
00004    Copyright (C) 2000-2002 Dawit Alemayehu <adawit@kde.org>
00005    Copyright (C) 2001,2002 Hamish Rodda <rodda@kde.org>
00006 
00007    This library is free software; you can redistribute it and/or
00008    modify it under the terms of the GNU Library General Public
00009    License (LGPL) as published by the Free Software Foundation;
00010    either version 2 of the License, or (at your option) any later
00011    version.
00012 
00013    This library is distributed in the hope that it will be useful,
00014    but WITHOUT ANY WARRANTY; without even the implied warranty of
00015    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016    Library General Public License for more details.
00017 
00018    You should have received a copy of the GNU Library General Public License
00019    along with this library; see the file COPYING.LIB.  If not, write to
00020    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00021    Boston, MA 02110-1301, USA.
00022 */
00023 
00024 #include <config.h>
00025 
00026 #include <errno.h>
00027 #include <fcntl.h>
00028 #include <utime.h>
00029 #include <stdlib.h>
00030 #include <signal.h>
00031 #include <sys/stat.h>
00032 #include <sys/socket.h>
00033 #include <netinet/in.h>  // Required for AIX
00034 #include <netinet/tcp.h>
00035 #include <unistd.h> // must be explicitly included for MacOSX
00036 
00037 /*
00038 #include <netdb.h>
00039 #include <sys/time.h>
00040 #include <sys/wait.h>
00041 */
00042 
00043 #include <qdom.h>
00044 #include <qfile.h>
00045 #include <qregexp.h>
00046 #include <qdatetime.h>
00047 #include <qstringlist.h>
00048 
00049 #include <kurl.h>
00050 #include <kidna.h>
00051 #include <ksocks.h>
00052 #include <kdebug.h>
00053 #include <klocale.h>
00054 #include <kconfig.h>
00055 #include <kextsock.h>
00056 #include <kservice.h>
00057 #include <krfcdate.h>
00058 #include <kmdcodec.h>
00059 #include <kinstance.h>
00060 #include <kresolver.h>
00061 #include <kmimemagic.h>
00062 #include <dcopclient.h>
00063 #include <kdatastream.h>
00064 #include <kapplication.h>
00065 #include <kstandarddirs.h>
00066 #include <kstringhandler.h>
00067 #include <kremoteencoding.h>
00068 
00069 #include "kio/ioslave_defaults.h"
00070 #include "kio/http_slave_defaults.h"
00071 
00072 #include "httpfilter.h"
00073 #include "http.h"
00074 
00075 #ifdef HAVE_LIBGSSAPI
00076 #ifdef GSSAPI_MIT
00077 #include <gssapi/gssapi.h>
00078 #else
00079 #include <gssapi.h>
00080 #endif /* GSSAPI_MIT */
00081 
00082 // Catch uncompatible crap (BR86019)
00083 #if defined(GSS_RFC_COMPLIANT_OIDS) && (GSS_RFC_COMPLIANT_OIDS == 0)
00084 #include <gssapi/gssapi_generic.h>
00085 #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
00086 #endif
00087 
00088 #endif /* HAVE_LIBGSSAPI */
00089 
00090 #include <misc/kntlm/kntlm.h>
00091 
00092 using namespace KIO;
00093 
00094 extern "C" {
00095   KDE_EXPORT int kdemain(int argc, char **argv);
00096 }
00097 
00098 int kdemain( int argc, char **argv )
00099 {
00100   KLocale::setMainCatalogue("kdelibs");
00101   KInstance instance( "kio_http" );
00102   ( void ) KGlobal::locale();
00103 
00104   if (argc != 4)
00105   {
00106      fprintf(stderr, "Usage: kio_http protocol domain-socket1 domain-socket2\n");
00107      exit(-1);
00108   }
00109 
00110   HTTPProtocol slave(argv[1], argv[2], argv[3]);
00111   slave.dispatchLoop();
00112   return 0;
00113 }
00114 
00115 /***********************************  Generic utility functions ********************/
00116 
00117 static char * trimLead (char *orig_string)
00118 {
00119   while (*orig_string == ' ')
00120     orig_string++;
00121   return orig_string;
00122 }
00123 
00124 static bool isCrossDomainRequest( const QString& fqdn, const QString& originURL )
00125 {
00126   if (originURL == "true") // Backwards compatibility
00127      return true;
00128 
00129   KURL url ( originURL );
00130 
00131   // Document Origin domain
00132   QString a = url.host();
00133 
00134   // Current request domain
00135   QString b = fqdn;
00136 
00137   if (a == b)
00138     return false;
00139 
00140   QStringList l1 = QStringList::split('.', a);
00141   QStringList l2 = QStringList::split('.', b);
00142 
00143   while(l1.count() > l2.count())
00144       l1.pop_front();
00145 
00146   while(l2.count() > l1.count())
00147       l2.pop_front();
00148 
00149   while(l2.count() >= 2)
00150   {
00151       if (l1 == l2)
00152           return false;
00153 
00154       l1.pop_front();
00155       l2.pop_front();
00156   }
00157 
00158   return true;
00159 }
00160 
00161 /*
00162   Eliminates any custom header that could potentically alter the request
00163 */
00164 static QString sanitizeCustomHTTPHeader(const QString& _header)
00165 {
00166   QString sanitizedHeaders;
00167   QStringList headers = QStringList::split("\r\n", _header);
00168 
00169   for(QStringList::Iterator it = headers.begin(); it != headers.end(); ++it)
00170   {
00171     QString header = (*it).lower();
00172     // Do not allow Request line to be specified and ignore
00173     // the other HTTP headers.
00174     if (header.find(':') == -1 ||
00175         header.startsWith("host") ||
00176         header.startsWith("via"))
00177       continue;
00178 
00179     sanitizedHeaders += (*it);
00180     sanitizedHeaders += "\r\n";
00181   }
00182 
00183   return sanitizedHeaders.stripWhiteSpace();
00184 }
00185 
00186 
00187 #define NO_SIZE     ((KIO::filesize_t) -1)
00188 
00189 #ifdef HAVE_STRTOLL
00190 #define STRTOLL strtoll
00191 #else
00192 #define STRTOLL strtol
00193 #endif
00194 
00195 
00196 /************************************** HTTPProtocol **********************************************/
00197 
00198 HTTPProtocol::HTTPProtocol( const QCString &protocol, const QCString &pool,
00199                             const QCString &app )
00200              :TCPSlaveBase( 0, protocol , pool, app,
00201                             (protocol == "https" || protocol == "webdavs") )
00202 {
00203   m_requestQueue.setAutoDelete(true);
00204 
00205   m_bBusy = false;
00206   m_bFirstRequest = false;
00207   m_bProxyAuthValid = false;
00208 
00209   m_iSize = NO_SIZE;
00210   m_lineBufUnget = 0;
00211 
00212   m_protocol = protocol;
00213 
00214   m_maxCacheAge = DEFAULT_MAX_CACHE_AGE;
00215   m_maxCacheSize = DEFAULT_MAX_CACHE_SIZE / 2;
00216   m_remoteConnTimeout = DEFAULT_CONNECT_TIMEOUT;
00217   m_remoteRespTimeout = DEFAULT_RESPONSE_TIMEOUT;
00218   m_proxyConnTimeout = DEFAULT_PROXY_CONNECT_TIMEOUT;
00219 
00220   m_pid = getpid();
00221 
00222   setMultipleAuthCaching( true );
00223   reparseConfiguration();
00224 }
00225 
00226 HTTPProtocol::~HTTPProtocol()
00227 {
00228   httpClose(false);
00229 }
00230 
00231 void HTTPProtocol::reparseConfiguration()
00232 {
00233   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::reparseConfiguration" << endl;
00234 
00235   m_strProxyRealm = QString::null;
00236   m_strProxyAuthorization = QString::null;
00237   ProxyAuthentication = AUTH_None;
00238   m_bUseProxy = false;
00239 
00240   if (m_protocol == "https" || m_protocol == "webdavs")
00241     m_iDefaultPort = DEFAULT_HTTPS_PORT;
00242   else if (m_protocol == "ftp")
00243     m_iDefaultPort = DEFAULT_FTP_PORT;
00244   else
00245     m_iDefaultPort = DEFAULT_HTTP_PORT;
00246 }
00247 
00248 void HTTPProtocol::resetConnectionSettings()
00249 {
00250   m_bEOF = false;
00251   m_bError = false;
00252   m_lineCount = 0;
00253   m_iWWWAuthCount = 0;
00254   m_lineCountUnget = 0;
00255   m_iProxyAuthCount = 0;
00256 
00257 }
00258 
00259 void HTTPProtocol::resetResponseSettings()
00260 {
00261   m_bRedirect = false;
00262   m_redirectLocation = KURL();
00263   m_bChunked = false;
00264   m_iSize = NO_SIZE;
00265 
00266   m_responseHeader.clear();
00267   m_qContentEncodings.clear();
00268   m_qTransferEncodings.clear();
00269   m_sContentMD5 = QString::null;
00270   m_strMimeType = QString::null;
00271 
00272   setMetaData("request-id", m_request.id);
00273 }
00274 
00275 void HTTPProtocol::resetSessionSettings()
00276 {
00277   // Do not reset the URL on redirection if the proxy
00278   // URL, username or password has not changed!
00279   KURL proxy ( config()->readEntry("UseProxy") );
00280 
00281   if ( m_strProxyRealm.isEmpty() || !proxy.isValid() ||
00282        m_proxyURL.host() != proxy.host() ||
00283        (!proxy.user().isNull() && proxy.user() != m_proxyURL.user()) ||
00284        (!proxy.pass().isNull() && proxy.pass() != m_proxyURL.pass()) )
00285   {
00286     m_bProxyAuthValid = false;
00287     m_proxyURL = proxy;
00288     m_bUseProxy = m_proxyURL.isValid();
00289 
00290     kdDebug(7113) << "(" << m_pid << ") Using proxy: " << m_bUseProxy <<
00291                                               " URL: " << m_proxyURL.url() <<
00292                                             " Realm: " << m_strProxyRealm << endl;
00293   }
00294 
00295   m_bPersistentProxyConnection = config()->readBoolEntry("PersistentProxyConnection", false);
00296   kdDebug(7113) << "(" << m_pid << ") Enable Persistent Proxy Connection: "
00297                 << m_bPersistentProxyConnection << endl;
00298 
00299   m_request.bUseCookiejar = config()->readBoolEntry("Cookies");
00300   m_request.bUseCache = config()->readBoolEntry("UseCache", true);
00301   m_request.bErrorPage = config()->readBoolEntry("errorPage", true);
00302   m_request.bNoAuth = config()->readBoolEntry("no-auth");
00303   m_strCacheDir = config()->readPathEntry("CacheDir");
00304   m_maxCacheAge = config()->readNumEntry("MaxCacheAge", DEFAULT_MAX_CACHE_AGE);
00305   m_request.window = config()->readEntry("window-id");
00306 
00307   kdDebug(7113) << "(" << m_pid << ") Window Id = " << m_request.window << endl;
00308   kdDebug(7113) << "(" << m_pid << ") ssl_was_in_use = "
00309                 << metaData ("ssl_was_in_use") << endl;
00310 
00311   m_request.referrer = QString::null;
00312   if ( config()->readBoolEntry("SendReferrer", true) &&
00313        (m_protocol == "https" || m_protocol == "webdavs" ||
00314         metaData ("ssl_was_in_use") != "TRUE" ) )
00315   {
00316      KURL referrerURL ( metaData("referrer") );
00317      if (referrerURL.isValid())
00318      {
00319         // Sanitize
00320         QString protocol = referrerURL.protocol();
00321         if (protocol.startsWith("webdav"))
00322         {
00323            protocol.replace(0, 6, "http");
00324            referrerURL.setProtocol(protocol);
00325         }
00326 
00327         if (protocol.startsWith("http"))
00328         {
00329            referrerURL.setRef(QString::null);
00330            referrerURL.setUser(QString::null);
00331            referrerURL.setPass(QString::null);
00332            m_request.referrer = referrerURL.url();
00333         }
00334      }
00335   }
00336 
00337   if ( config()->readBoolEntry("SendLanguageSettings", true) )
00338   {
00339       m_request.charsets = config()->readEntry( "Charsets", "iso-8859-1" );
00340 
00341       if ( !m_request.charsets.isEmpty() )
00342           m_request.charsets += DEFAULT_PARTIAL_CHARSET_HEADER;
00343 
00344       m_request.languages = config()->readEntry( "Languages", DEFAULT_LANGUAGE_HEADER );
00345   }
00346   else
00347   {
00348       m_request.charsets = QString::null;
00349       m_request.languages = QString::null;
00350   }
00351 
00352   // Adjust the offset value based on the "resume" meta-data.
00353   QString resumeOffset = metaData("resume");
00354   if ( !resumeOffset.isEmpty() )
00355      m_request.offset = resumeOffset.toInt(); // TODO: Convert to 64 bit
00356   else
00357      m_request.offset = 0;
00358 
00359   m_request.disablePassDlg = config()->readBoolEntry("DisablePassDlg", false);
00360   m_request.allowCompressedPage = config()->readBoolEntry("AllowCompressedPage", true);
00361   m_request.id = metaData("request-id");
00362 
00363   // Store user agent for this host.
00364   if ( config()->readBoolEntry("SendUserAgent", true) )
00365      m_request.userAgent = metaData("UserAgent");
00366   else
00367      m_request.userAgent = QString::null;
00368 
00369   // Deal with cache cleaning.
00370   // TODO: Find a smarter way to deal with cleaning the
00371   // cache ?
00372   if ( m_request.bUseCache )
00373     cleanCache();
00374 
00375   // Deal with HTTP tunneling
00376   if ( m_bIsSSL && m_bUseProxy && m_proxyURL.protocol() != "https" &&
00377        m_proxyURL.protocol() != "webdavs")
00378   {
00379     m_bNeedTunnel = true;
00380     setRealHost( m_request.hostname );
00381     kdDebug(7113) << "(" << m_pid << ") SSL tunnel: Setting real hostname to: "
00382                   << m_request.hostname << endl;
00383   }
00384   else
00385   {
00386     m_bNeedTunnel = false;
00387     setRealHost( QString::null);
00388   }
00389 
00390   m_responseCode = 0;
00391   m_prevResponseCode = 0;
00392 
00393   m_strRealm = QString::null;
00394   m_strAuthorization = QString::null;
00395   Authentication = AUTH_None;
00396 
00397   // Obtain the proxy and remote server timeout values
00398   m_proxyConnTimeout = proxyConnectTimeout();
00399   m_remoteConnTimeout = connectTimeout();
00400   m_remoteRespTimeout = responseTimeout();
00401 
00402   // Bounce back the actual referrer sent
00403   setMetaData("referrer", m_request.referrer);
00404 
00405   // Set the SSL meta-data here...
00406   setSSLMetaData();
00407 
00408   // Follow HTTP/1.1 spec and enable keep-alive by default
00409   // unless the remote side tells us otherwise or we determine
00410   // the persistent link has been terminated by the remote end.
00411   m_bKeepAlive = true;
00412   m_keepAliveTimeout = 0;
00413   m_bUnauthorized = false;
00414 
00415   // A single request can require multiple exchanges with the remote
00416   // server due to authentication challenges or SSL tunneling.
00417   // m_bFirstRequest is a flag that indicates whether we are
00418   // still processing the first request. This is important because we
00419   // should not force a close of a keep-alive connection in the middle
00420   // of the first request.
00421   // m_bFirstRequest is set to "true" whenever a new connection is
00422   // made in httpOpenConnection()
00423   m_bFirstRequest = false;
00424 }
00425 
00426 void HTTPProtocol::setHost( const QString& host, int port,
00427                             const QString& user, const QString& pass )
00428 {
00429   // Reset the webdav-capable flags for this host
00430   if ( m_request.hostname != host )
00431     m_davHostOk = m_davHostUnsupported = false;
00432 
00433   // is it an IPv6 address?
00434   if (host.find(':') == -1)
00435     {
00436       m_request.hostname = host;
00437       m_request.encoded_hostname = KIDNA::toAscii(host);
00438     }
00439   else
00440     {
00441       m_request.hostname = host;
00442       int pos = host.find('%');
00443       if (pos == -1)
00444     m_request.encoded_hostname = '[' + host + ']';
00445       else
00446     // don't send the scope-id in IPv6 addresses to the server
00447     m_request.encoded_hostname = '[' + host.left(pos) + ']';
00448     }
00449   m_request.port = (port == 0) ? m_iDefaultPort : port;
00450   m_request.user = user;
00451   m_request.passwd = pass;
00452 
00453   m_bIsTunneled = false;
00454 
00455   kdDebug(7113) << "(" << m_pid << ") Hostname is now: " << m_request.hostname <<
00456     " (" << m_request.encoded_hostname << ")" <<endl;
00457 }
00458 
00459 bool HTTPProtocol::checkRequestURL( const KURL& u )
00460 {
00461   kdDebug (7113) << "(" << m_pid << ") HTTPProtocol::checkRequestURL:  " << u.url() << endl;
00462 
00463   m_request.url = u;
00464 
00465   if (m_request.hostname.isEmpty())
00466   {
00467      error( KIO::ERR_UNKNOWN_HOST, i18n("No host specified."));
00468      return false;
00469   }
00470 
00471   if (u.path().isEmpty())
00472   {
00473      KURL newUrl(u);
00474      newUrl.setPath("/");
00475      redirection(newUrl);
00476      finished();
00477      return false;
00478   }
00479 
00480   if ( m_protocol != u.protocol().latin1() )
00481   {
00482     short unsigned int oldDefaultPort = m_iDefaultPort;
00483     m_protocol = u.protocol().latin1();
00484     reparseConfiguration();
00485     if ( m_iDefaultPort != oldDefaultPort &&
00486          m_request.port == oldDefaultPort )
00487         m_request.port = m_iDefaultPort;
00488   }
00489 
00490   resetSessionSettings();
00491   return true;
00492 }
00493 
00494 void HTTPProtocol::retrieveContent( bool dataInternal /* = false */ )
00495 {
00496   kdDebug (7113) << "(" << m_pid << ") HTTPProtocol::retrieveContent " << endl;
00497   if ( !retrieveHeader( false ) )
00498   {
00499     if ( m_bError )
00500       return;
00501   }
00502   else
00503   {
00504     if ( !readBody( dataInternal ) && m_bError )
00505       return;
00506   }
00507 
00508   httpClose(m_bKeepAlive);
00509 
00510   // if data is required internally, don't finish,
00511   // it is processed before we finish()
00512   if ( !dataInternal )
00513   {
00514     if ((m_responseCode == 204) &&
00515         ((m_request.method == HTTP_GET) || (m_request.method == HTTP_POST)))
00516        error(ERR_NO_CONTENT, "");
00517     else
00518        finished();
00519   }
00520 }
00521 
00522 bool HTTPProtocol::retrieveHeader( bool close_connection )
00523 {
00524   kdDebug (7113) << "(" << m_pid << ") HTTPProtocol::retrieveHeader " << endl;
00525   while ( 1 )
00526   {
00527     if (!httpOpen())
00528       return false;
00529 
00530     resetResponseSettings();
00531     if (!readHeader())
00532     {
00533       if ( m_bError )
00534         return false;
00535 
00536       if (m_bIsTunneled)
00537       {
00538         kdDebug(7113) << "(" << m_pid << ") Re-establishing SSL tunnel..." << endl;
00539         httpCloseConnection();
00540       }
00541     }
00542     else
00543     {
00544       // Do not save authorization if the current response code is
00545       // 4xx (client error) or 5xx (server error).
00546       kdDebug(7113) << "(" << m_pid << ") Previous Response: "
00547                     << m_prevResponseCode << endl;
00548       kdDebug(7113) << "(" << m_pid << ") Current Response: "
00549                     << m_responseCode << endl;
00550 
00551       if (isSSLTunnelEnabled() &&  m_bIsSSL && !m_bUnauthorized && !m_bError)
00552       {
00553         // If there is no error, disable tunneling
00554         if ( m_responseCode < 400 )
00555         {
00556           kdDebug(7113) << "(" << m_pid << ") Unset tunneling flag!" << endl;
00557           setEnableSSLTunnel( false );
00558           m_bIsTunneled = true;
00559           // Reset the CONNECT response code...
00560           m_responseCode = m_prevResponseCode;
00561           continue;
00562         }
00563         else
00564         {
00565           if ( !m_request.bErrorPage )
00566           {
00567             kdDebug(7113) << "(" << m_pid << ") Sending an error message!" << endl;
00568             error( ERR_UNKNOWN_PROXY_HOST, m_proxyURL.host() );
00569             return false;
00570           }
00571 
00572           kdDebug(7113) << "(" << m_pid << ") Sending an error page!" << endl;
00573         }
00574       }
00575 
00576       if (m_responseCode < 400 && (m_prevResponseCode == 401 ||
00577           m_prevResponseCode == 407))
00578         saveAuthorization();
00579       break;
00580     }
00581   }
00582 
00583   // Clear of the temporary POST buffer if it is not empty...
00584   if (!m_bufPOST.isEmpty())
00585   {
00586     m_bufPOST.resize(0);
00587     kdDebug(7113) << "(" << m_pid << ") HTTP::retreiveHeader: Cleared POST "
00588                      "buffer..." << endl;
00589   }
00590 
00591   if ( close_connection )
00592   {
00593     httpClose(m_bKeepAlive);
00594     finished();
00595   }
00596 
00597   return true;
00598 }
00599 
00600 void HTTPProtocol::stat(const KURL& url)
00601 {
00602   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::stat " << url.prettyURL()
00603                 << endl;
00604 
00605   if ( !checkRequestURL( url ) )
00606       return;
00607 
00608   if ( m_protocol != "webdav" && m_protocol != "webdavs" )
00609   {
00610     QString statSide = metaData(QString::fromLatin1("statSide"));
00611     if ( statSide != "source" )
00612     {
00613       // When uploading we assume the file doesn't exit
00614       error( ERR_DOES_NOT_EXIST, url.prettyURL() );
00615       return;
00616     }
00617 
00618     // When downloading we assume it exists
00619     UDSEntry entry;
00620     UDSAtom atom;
00621     atom.m_uds = KIO::UDS_NAME;
00622     atom.m_str = url.fileName();
00623     entry.append( atom );
00624 
00625     atom.m_uds = KIO::UDS_FILE_TYPE;
00626     atom.m_long = S_IFREG; // a file
00627     entry.append( atom );
00628 
00629     atom.m_uds = KIO::UDS_ACCESS;
00630     atom.m_long = S_IRUSR | S_IRGRP | S_IROTH; // readable by everybody
00631     entry.append( atom );
00632 
00633     statEntry( entry );
00634     finished();
00635     return;
00636   }
00637 
00638   davStatList( url );
00639 }
00640 
00641 void HTTPProtocol::listDir( const KURL& url )
00642 {
00643   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::listDir " << url.url()
00644                 << endl;
00645 
00646   if ( !checkRequestURL( url ) )
00647     return;
00648 
00649   davStatList( url, false );
00650 }
00651 
00652 void HTTPProtocol::davSetRequest( const QCString& requestXML )
00653 {
00654   // insert the document into the POST buffer, kill trailing zero byte
00655   m_bufPOST = requestXML;
00656 
00657   if (m_bufPOST.size())
00658     m_bufPOST.truncate( m_bufPOST.size() - 1 );
00659 }
00660 
00661 void HTTPProtocol::davStatList( const KURL& url, bool stat )
00662 {
00663   UDSEntry entry;
00664   UDSAtom atom;
00665 
00666   // check to make sure this host supports WebDAV
00667   if ( !davHostOk() )
00668     return;
00669 
00670   // Maybe it's a disguised SEARCH...
00671   QString query = metaData("davSearchQuery");
00672   if ( !query.isEmpty() )
00673   {
00674     QCString request = "<?xml version=\"1.0\"?>\r\n";
00675     request.append( "<D:searchrequest xmlns:D=\"DAV:\">\r\n" );
00676     request.append( query.utf8() );
00677     request.append( "</D:searchrequest>\r\n" );
00678 
00679     davSetRequest( request );
00680   } else {
00681     // We are only after certain features...
00682     QCString request;
00683     request = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
00684     "<D:propfind xmlns:D=\"DAV:\">";
00685 
00686     // insert additional XML request from the davRequestResponse metadata
00687     if ( hasMetaData( "davRequestResponse" ) )
00688       request += metaData( "davRequestResponse" ).utf8();
00689     else {
00690       // No special request, ask for default properties
00691       request += "<D:prop>"
00692       "<D:creationdate/>"
00693       "<D:getcontentlength/>"
00694       "<D:displayname/>"
00695       "<D:source/>"
00696       "<D:getcontentlanguage/>"
00697       "<D:getcontenttype/>"
00698       "<D:executable/>"
00699       "<D:getlastmodified/>"
00700       "<D:getetag/>"
00701       "<D:supportedlock/>"
00702       "<D:lockdiscovery/>"
00703       "<D:resourcetype/>"
00704       "</D:prop>";
00705     }
00706     request += "</D:propfind>";
00707 
00708     davSetRequest( request );
00709   }
00710 
00711   // WebDAV Stat or List...
00712   m_request.method = query.isEmpty() ? DAV_PROPFIND : DAV_SEARCH;
00713   m_request.query = QString::null;
00714   m_request.cache = CC_Reload;
00715   m_request.doProxy = m_bUseProxy;
00716   m_request.davData.depth = stat ? 0 : 1;
00717   if (!stat)
00718      m_request.url.adjustPath(+1);
00719 
00720   retrieveContent( true );
00721 
00722   // Has a redirection already been called? If so, we're done.
00723   if (m_bRedirect) {
00724     finished();
00725     return;
00726   }
00727 
00728   QDomDocument multiResponse;
00729   multiResponse.setContent( m_bufWebDavData, true );
00730 
00731   bool hasResponse = false;
00732 
00733   for ( QDomNode n = multiResponse.documentElement().firstChild();
00734         !n.isNull(); n = n.nextSibling())
00735   {
00736     QDomElement thisResponse = n.toElement();
00737     if (thisResponse.isNull())
00738       continue;
00739 
00740     hasResponse = true;
00741 
00742     QDomElement href = thisResponse.namedItem( "href" ).toElement();
00743     if ( !href.isNull() )
00744     {
00745       entry.clear();
00746 
00747       QString urlStr = href.text();
00748       int encoding = remoteEncoding()->encodingMib();
00749       if ((encoding == 106) && (!KStringHandler::isUtf8(KURL::decode_string(urlStr, 4).latin1())))
00750         encoding = 4; // Use latin1 if the file is not actually utf-8
00751 
00752       KURL thisURL ( urlStr, encoding );
00753 
00754       atom.m_uds = KIO::UDS_NAME;
00755 
00756       if ( thisURL.isValid() ) {
00757         // don't list the base dir of a listDir()
00758         if ( !stat && thisURL.path(+1).length() == url.path(+1).length() )
00759           continue;
00760 
00761         atom.m_str = thisURL.fileName();
00762       } else {
00763         // This is a relative URL.
00764         atom.m_str = href.text();
00765       }
00766 
00767       entry.append( atom );
00768 
00769       QDomNodeList propstats = thisResponse.elementsByTagName( "propstat" );
00770 
00771       davParsePropstats( propstats, entry );
00772 
00773       if ( stat )
00774       {
00775         // return an item
00776         statEntry( entry );
00777         finished();
00778         return;
00779       }
00780       else
00781       {
00782         listEntry( entry, false );
00783       }
00784     }
00785     else
00786     {
00787       kdDebug(7113) << "Error: no URL contained in response to PROPFIND on "
00788                     << url.prettyURL() << endl;
00789     }
00790   }
00791 
00792   if ( stat || !hasResponse )
00793   {
00794     error( ERR_DOES_NOT_EXIST, url.prettyURL() );
00795   }
00796   else
00797   {
00798     listEntry( entry, true );
00799     finished();
00800   }
00801 }
00802 
00803 void HTTPProtocol::davGeneric( const KURL& url, KIO::HTTP_METHOD method )
00804 {
00805   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::davGeneric " << url.url()
00806                 << endl;
00807 
00808   if ( !checkRequestURL( url ) )
00809     return;
00810 
00811   // check to make sure this host supports WebDAV
00812   if ( !davHostOk() )
00813     return;
00814 
00815   // WebDAV method
00816   m_request.method = method;
00817   m_request.query = QString::null;
00818   m_request.cache = CC_Reload;
00819   m_request.doProxy = m_bUseProxy;
00820 
00821   retrieveContent( false );
00822 }
00823 
00824 int HTTPProtocol::codeFromResponse( const QString& response )
00825 {
00826   int firstSpace = response.find( ' ' );
00827   int secondSpace = response.find( ' ', firstSpace + 1 );
00828   return response.mid( firstSpace + 1, secondSpace - firstSpace - 1 ).toInt();
00829 }
00830 
00831 void HTTPProtocol::davParsePropstats( const QDomNodeList& propstats, UDSEntry& entry )
00832 {
00833   QString mimeType;
00834   UDSAtom atom;
00835   bool foundExecutable = false;
00836   bool isDirectory = false;
00837   uint lockCount = 0;
00838   uint supportedLockCount = 0;
00839 
00840   for ( uint i = 0; i < propstats.count(); i++)
00841   {
00842     QDomElement propstat = propstats.item(i).toElement();
00843 
00844     QDomElement status = propstat.namedItem( "status" ).toElement();
00845     if ( status.isNull() )
00846     {
00847       // error, no status code in this propstat
00848       kdDebug(7113) << "Error, no status code in this propstat" << endl;
00849       return;
00850     }
00851 
00852     int code = codeFromResponse( status.text() );
00853 
00854     if ( code != 200 )
00855     {
00856       kdDebug(7113) << "Warning: status code " << code << " (this may mean that some properties are unavailable" << endl;
00857       continue;
00858     }
00859 
00860     QDomElement prop = propstat.namedItem( "prop" ).toElement();
00861     if ( prop.isNull() )
00862     {
00863       kdDebug(7113) << "Error: no prop segment in this propstat." << endl;
00864       return;
00865     }
00866 
00867     if ( hasMetaData( "davRequestResponse" ) )
00868     {
00869       atom.m_uds = KIO::UDS_XML_PROPERTIES;
00870       QDomDocument doc;
00871       doc.appendChild(prop);
00872       atom.m_str = doc.toString();
00873       entry.append( atom );
00874     }
00875 
00876     for ( QDomNode n = prop.firstChild(); !n.isNull(); n = n.nextSibling() )
00877     {
00878       QDomElement property = n.toElement();
00879       if (property.isNull())
00880         continue;
00881 
00882       if ( property.namespaceURI() != "DAV:" )
00883       {
00884         // break out - we're only interested in properties from the DAV namespace
00885         continue;
00886       }
00887 
00888       if ( property.tagName() == "creationdate" )
00889       {
00890         // Resource creation date. Should be is ISO 8601 format.
00891         atom.m_uds = KIO::UDS_CREATION_TIME;
00892         atom.m_long = parseDateTime( property.text(), property.attribute("dt") );
00893         entry.append( atom );
00894       }
00895       else if ( property.tagName() == "getcontentlength" )
00896       {
00897         // Content length (file size)
00898         atom.m_uds = KIO::UDS_SIZE;
00899         atom.m_long = property.text().toULong();
00900         entry.append( atom );
00901       }
00902       else if ( property.tagName() == "displayname" )
00903       {
00904         // Name suitable for presentation to the user
00905         setMetaData( "davDisplayName", property.text() );
00906       }
00907       else if ( property.tagName() == "source" )
00908       {
00909         // Source template location
00910         QDomElement source = property.namedItem( "link" ).toElement()
00911                                       .namedItem( "dst" ).toElement();
00912         if ( !source.isNull() )
00913           setMetaData( "davSource", source.text() );
00914       }
00915       else if ( property.tagName() == "getcontentlanguage" )
00916       {
00917         // equiv. to Content-Language header on a GET
00918         setMetaData( "davContentLanguage", property.text() );
00919       }
00920       else if ( property.tagName() == "getcontenttype" )
00921       {
00922         // Content type (mime type)
00923         // This may require adjustments for other server-side webdav implementations
00924         // (tested with Apache + mod_dav 1.0.3)
00925         if ( property.text() == "httpd/unix-directory" )
00926         {
00927           isDirectory = true;
00928         }
00929         else
00930         {
00931       mimeType = property.text();
00932         }
00933       }
00934       else if ( property.tagName() == "executable" )
00935       {
00936         // File executable status
00937         if ( property.text() == "T" )
00938           foundExecutable = true;
00939 
00940       }
00941       else if ( property.tagName() == "getlastmodified" )
00942       {
00943         // Last modification date
00944         atom.m_uds = KIO::UDS_MODIFICATION_TIME;
00945         atom.m_long = parseDateTime( property.text(), property.attribute("dt") );
00946         entry.append( atom );
00947 
00948       }
00949       else if ( property.tagName() == "getetag" )
00950       {
00951         // Entity tag
00952         setMetaData( "davEntityTag", property.text() );
00953       }
00954       else if ( property.tagName() == "supportedlock" )
00955       {
00956         // Supported locking specifications
00957         for ( QDomNode n2 = property.firstChild(); !n2.isNull(); n2 = n2.nextSibling() )
00958         {
00959           QDomElement lockEntry = n2.toElement();
00960           if ( lockEntry.tagName() == "lockentry" )
00961           {
00962             QDomElement lockScope = lockEntry.namedItem( "lockscope" ).toElement();
00963             QDomElement lockType = lockEntry.namedItem( "locktype" ).toElement();
00964             if ( !lockScope.isNull() && !lockType.isNull() )
00965             {
00966               // Lock type was properly specified
00967               supportedLockCount++;
00968               QString scope = lockScope.firstChild().toElement().tagName();
00969               QString type = lockType.firstChild().toElement().tagName();
00970 
00971               setMetaData( QString("davSupportedLockScope%1").arg(supportedLockCount), scope );
00972               setMetaData( QString("davSupportedLockType%1").arg(supportedLockCount), type );
00973             }
00974           }
00975         }
00976       }
00977       else if ( property.tagName() == "lockdiscovery" )
00978       {
00979         // Lists the available locks
00980         davParseActiveLocks( property.elementsByTagName( "activelock" ), lockCount );
00981       }
00982       else if ( property.tagName() == "resourcetype" )
00983       {
00984         // Resource type. "Specifies the nature of the resource."
00985         if ( !property.namedItem( "collection" ).toElement().isNull() )
00986         {
00987           // This is a collection (directory)
00988           isDirectory = true;
00989         }
00990       }
00991       else
00992       {
00993         kdDebug(7113) << "Found unknown webdav property: " << property.tagName() << endl;
00994       }
00995     }
00996   }
00997 
00998   setMetaData( "davLockCount", QString("%1").arg(lockCount) );
00999   setMetaData( "davSupportedLockCount", QString("%1").arg(supportedLockCount) );
01000 
01001   atom.m_uds = KIO::UDS_FILE_TYPE;
01002   atom.m_long = isDirectory ? S_IFDIR : S_IFREG;
01003   entry.append( atom );
01004 
01005   if ( foundExecutable || isDirectory )
01006   {
01007     // File was executable, or is a directory.
01008     atom.m_uds = KIO::UDS_ACCESS;
01009     atom.m_long = 0700;
01010     entry.append(atom);
01011   }
01012   else
01013   {
01014     atom.m_uds = KIO::UDS_ACCESS;
01015     atom.m_long = 0600;
01016     entry.append(atom);
01017   }
01018 
01019   if ( !isDirectory && !mimeType.isEmpty() )
01020   {
01021     atom.m_uds = KIO::UDS_MIME_TYPE;
01022     atom.m_str = mimeType;
01023     entry.append( atom );
01024   }
01025 }
01026 
01027 void HTTPProtocol::davParseActiveLocks( const QDomNodeList& activeLocks,
01028                                         uint& lockCount )
01029 {
01030   for ( uint i = 0; i < activeLocks.count(); i++ )
01031   {
01032     QDomElement activeLock = activeLocks.item(i).toElement();
01033 
01034     lockCount++;
01035     // required
01036     QDomElement lockScope = activeLock.namedItem( "lockscope" ).toElement();
01037     QDomElement lockType = activeLock.namedItem( "locktype" ).toElement();
01038     QDomElement lockDepth = activeLock.namedItem( "depth" ).toElement();
01039     // optional
01040     QDomElement lockOwner = activeLock.namedItem( "owner" ).toElement();
01041     QDomElement lockTimeout = activeLock.namedItem( "timeout" ).toElement();
01042     QDomElement lockToken = activeLock.namedItem( "locktoken" ).toElement();
01043 
01044     if ( !lockScope.isNull() && !lockType.isNull() && !lockDepth.isNull() )
01045     {
01046       // lock was properly specified
01047       lockCount++;
01048       QString scope = lockScope.firstChild().toElement().tagName();
01049       QString type = lockType.firstChild().toElement().tagName();
01050       QString depth = lockDepth.text();
01051 
01052       setMetaData( QString("davLockScope%1").arg( lockCount ), scope );
01053       setMetaData( QString("davLockType%1").arg( lockCount ), type );
01054       setMetaData( QString("davLockDepth%1").arg( lockCount ), depth );
01055 
01056       if ( !lockOwner.isNull() )
01057         setMetaData( QString("davLockOwner%1").arg( lockCount ), lockOwner.text() );
01058 
01059       if ( !lockTimeout.isNull() )
01060         setMetaData( QString("davLockTimeout%1").arg( lockCount ), lockTimeout.text() );
01061 
01062       if ( !lockToken.isNull() )
01063       {
01064         QDomElement tokenVal = lockScope.namedItem( "href" ).toElement();
01065         if ( !tokenVal.isNull() )
01066           setMetaData( QString("davLockToken%1").arg( lockCount ), tokenVal.text() );
01067       }
01068     }
01069   }
01070 }
01071 
01072 long HTTPProtocol::parseDateTime( const QString& input, const QString& type )
01073 {
01074   if ( type == "dateTime.tz" )
01075   {
01076     return KRFCDate::parseDateISO8601( input );
01077   }
01078   else if ( type == "dateTime.rfc1123" )
01079   {
01080     return KRFCDate::parseDate( input );
01081   }
01082 
01083   // format not advertised... try to parse anyway
01084   time_t time = KRFCDate::parseDate( input );
01085   if ( time != 0 )
01086     return time;
01087 
01088   return KRFCDate::parseDateISO8601( input );
01089 }
01090 
01091 QString HTTPProtocol::davProcessLocks()
01092 {
01093   if ( hasMetaData( "davLockCount" ) )
01094   {
01095     QString response("If:");
01096     int numLocks;
01097     numLocks = metaData( "davLockCount" ).toInt();
01098     bool bracketsOpen = false;
01099     for ( int i = 0; i < numLocks; i++ )
01100     {
01101       if ( hasMetaData( QString("davLockToken%1").arg(i) ) )
01102       {
01103         if ( hasMetaData( QString("davLockURL%1").arg(i) ) )
01104         {
01105           if ( bracketsOpen )
01106           {
01107             response += ")";
01108             bracketsOpen = false;
01109           }
01110           response += " <" + metaData( QString("davLockURL%1").arg(i) ) + ">";
01111         }
01112 
01113         if ( !bracketsOpen )
01114         {
01115           response += " (";
01116           bracketsOpen = true;
01117         }
01118         else
01119         {
01120           response += " ";
01121         }
01122 
01123         if ( hasMetaData( QString("davLockNot%1").arg(i) ) )
01124           response += "Not ";
01125 
01126         response += "<" + metaData( QString("davLockToken%1").arg(i) ) + ">";
01127       }
01128     }
01129 
01130     if ( bracketsOpen )
01131       response += ")";
01132 
01133     response += "\r\n";
01134     return response;
01135   }
01136 
01137   return QString::null;
01138 }
01139 
01140 bool HTTPProtocol::davHostOk()
01141 {
01142   // FIXME needs to be reworked. Switched off for now.
01143   return true;
01144 
01145   // cached?
01146   if ( m_davHostOk )
01147   {
01148     kdDebug(7113) << "(" << m_pid << ") " << k_funcinfo << " true" << endl;
01149     return true;
01150   }
01151   else if ( m_davHostUnsupported )
01152   {
01153     kdDebug(7113) << "(" << m_pid << ") " << k_funcinfo << " false" << endl;
01154     davError( -2 );
01155     return false;
01156   }
01157 
01158   m_request.method = HTTP_OPTIONS;
01159 
01160   // query the server's capabilities generally, not for a specific URL
01161   m_request.path = "*";
01162   m_request.query = QString::null;
01163   m_request.cache = CC_Reload;
01164   m_request.doProxy = m_bUseProxy;
01165 
01166   // clear davVersions variable, which holds the response to the DAV: header
01167   m_davCapabilities.clear();
01168 
01169   retrieveHeader(false);
01170 
01171   if (m_davCapabilities.count())
01172   {
01173     for (uint i = 0; i < m_davCapabilities.count(); i++)
01174     {
01175       bool ok;
01176       uint verNo = m_davCapabilities[i].toUInt(&ok);
01177       if (ok && verNo > 0 && verNo < 3)
01178       {
01179         m_davHostOk = true;
01180         kdDebug(7113) << "Server supports DAV version " << verNo << "." << endl;
01181       }
01182     }
01183 
01184     if ( m_davHostOk )
01185       return true;
01186   }
01187 
01188   m_davHostUnsupported = true;
01189   davError( -2 );
01190   return false;
01191 }
01192 
01193 // This function is for closing retrieveHeader( false ); requests
01194 // Required because there may or may not be further info expected
01195 void HTTPProtocol::davFinished()
01196 {
01197   // TODO: Check with the DAV extension developers
01198   httpClose(m_bKeepAlive);
01199   finished();
01200 }
01201 
01202 void HTTPProtocol::mkdir( const KURL& url, int )
01203 {
01204   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::mkdir " << url.url()
01205                 << endl;
01206 
01207   if ( !checkRequestURL( url ) )
01208     return;
01209 
01210   m_request.method = DAV_MKCOL;
01211   m_request.path = url.path();
01212   m_request.query = QString::null;
01213   m_request.cache = CC_Reload;
01214   m_request.doProxy = m_bUseProxy;
01215 
01216   retrieveHeader( false );
01217 
01218   if ( m_responseCode == 201 )
01219     davFinished();
01220   else
01221     davError();
01222 }
01223 
01224 void HTTPProtocol::get( const KURL& url )
01225 {
01226   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::get " << url.url()
01227                 << endl;
01228 
01229   if ( !checkRequestURL( url ) )
01230     return;
01231 
01232   m_request.method = HTTP_GET;
01233   m_request.path = url.path();
01234   m_request.query = url.query();
01235 
01236   QString tmp = metaData("cache");
01237   if (!tmp.isEmpty())
01238     m_request.cache = parseCacheControl(tmp);
01239   else
01240     m_request.cache = DEFAULT_CACHE_CONTROL;
01241 
01242   m_request.passwd = url.pass();
01243   m_request.user = url.user();
01244   m_request.doProxy = m_bUseProxy;
01245 
01246   retrieveContent();
01247 }
01248 
01249 void HTTPProtocol::put( const KURL &url, int, bool overwrite, bool)
01250 {
01251   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::put " << url.prettyURL()
01252                 << endl;
01253 
01254   if ( !checkRequestURL( url ) )
01255     return;
01256 
01257   // Webdav hosts are capable of observing overwrite == false
01258   if (!overwrite && m_protocol.left(6) == "webdav") {
01259     // check to make sure this host supports WebDAV
01260     if ( !davHostOk() )
01261       return;
01262 
01263     QCString request;
01264     request = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
01265     "<D:propfind xmlns:D=\"DAV:\"><D:prop>"
01266       "<D:creationdate/>"
01267       "<D:getcontentlength/>"
01268       "<D:displayname/>"
01269       "<D:resourcetype/>"
01270       "</D:prop></D:propfind>";
01271 
01272     davSetRequest( request );
01273 
01274     // WebDAV Stat or List...
01275     m_request.method = DAV_PROPFIND;
01276     m_request.query = QString::null;
01277     m_request.cache = CC_Reload;
01278     m_request.doProxy = m_bUseProxy;
01279     m_request.davData.depth = 0;
01280 
01281     retrieveContent(true);
01282 
01283     if (m_responseCode == 207) {
01284       error(ERR_FILE_ALREADY_EXIST, QString::null);
01285       return;
01286     }
01287 
01288     m_bError = false;
01289   }
01290 
01291   m_request.method = HTTP_PUT;
01292   m_request.path = url.path();
01293   m_request.query = QString::null;
01294   m_request.cache = CC_Reload;
01295   m_request.doProxy = m_bUseProxy;
01296 
01297   retrieveHeader( false );
01298 
01299   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::put error = " << m_bError << endl;
01300   if (m_bError)
01301     return;
01302 
01303   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::put responseCode = " << m_responseCode << endl;
01304 
01305   httpClose(false); // Always close connection.
01306 
01307   if ( (m_responseCode >= 200) && (m_responseCode < 300) )
01308     finished();
01309   else
01310     httpError();
01311 }
01312 
01313 void HTTPProtocol::copy( const KURL& src, const KURL& dest, int, bool overwrite )
01314 {
01315   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::copy " << src.prettyURL()
01316                 << " -> " << dest.prettyURL() << endl;
01317 
01318   if ( !checkRequestURL( dest ) || !checkRequestURL( src ) )
01319     return;
01320 
01321   // destination has to be "http(s)://..."
01322   KURL newDest = dest;
01323   if (newDest.protocol() == "webdavs")
01324     newDest.setProtocol("https");
01325   else
01326     newDest.setProtocol("http");
01327 
01328   m_request.method = DAV_COPY;
01329   m_request.path = src.path();
01330   m_request.davData.desturl = newDest.url();
01331   m_request.davData.overwrite = overwrite;
01332   m_request.query = QString::null;
01333   m_request.cache = CC_Reload;
01334   m_request.doProxy = m_bUseProxy;
01335 
01336   retrieveHeader( false );
01337 
01338   // The server returns a HTTP/1.1 201 Created or 204 No Content on successful completion
01339   if ( m_responseCode == 201 || m_responseCode == 204 )
01340     davFinished();
01341   else
01342     davError();
01343 }
01344 
01345 void HTTPProtocol::rename( const KURL& src, const KURL& dest, bool overwrite )
01346 {
01347   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::rename " << src.prettyURL()
01348                 << " -> " << dest.prettyURL() << endl;
01349 
01350   if ( !checkRequestURL( dest ) || !checkRequestURL( src ) )
01351     return;
01352 
01353   // destination has to be "http://..."
01354   KURL newDest = dest;
01355   if (newDest.protocol() == "webdavs")
01356     newDest.setProtocol("https");
01357   else
01358     newDest.setProtocol("http");
01359 
01360   m_request.method = DAV_MOVE;
01361   m_request.path = src.path();
01362   m_request.davData.desturl = newDest.url();
01363   m_request.davData.overwrite = overwrite;
01364   m_request.query = QString::null;
01365   m_request.cache = CC_Reload;
01366   m_request.doProxy = m_bUseProxy;
01367 
01368   retrieveHeader( false );
01369 
01370   if ( m_responseCode == 301 )
01371   {
01372     // Work around strict Apache-2 WebDAV implementation which refuses to cooperate
01373     // with webdav://host/directory, instead requiring webdav://host/directory/
01374     // (strangely enough it accepts Destination: without a trailing slash)
01375 
01376     if (m_redirectLocation.protocol() == "https")
01377       m_redirectLocation.setProtocol("webdavs");
01378     else
01379       m_redirectLocation.setProtocol("webdav");
01380 
01381     if ( !checkRequestURL( m_redirectLocation ) )
01382       return;
01383 
01384     m_request.method = DAV_MOVE;
01385     m_request.path = m_redirectLocation.path();
01386     m_request.davData.desturl = newDest.url();
01387     m_request.davData.overwrite = overwrite;
01388     m_request.query = QString::null;
01389     m_request.cache = CC_Reload;
01390     m_request.doProxy = m_bUseProxy;
01391 
01392     retrieveHeader( false );
01393   }
01394 
01395   if ( m_responseCode == 201 )
01396     davFinished();
01397   else
01398     davError();
01399 }
01400 
01401 void HTTPProtocol::del( const KURL& url, bool )
01402 {
01403   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::del " << url.prettyURL()
01404                 << endl;
01405 
01406   if ( !checkRequestURL( url ) )
01407     return;
01408 
01409   m_request.method = HTTP_DELETE;
01410   m_request.path = url.path();
01411   m_request.query = QString::null;
01412   m_request.cache = CC_Reload;
01413   m_request.doProxy = m_bUseProxy;
01414 
01415   retrieveHeader( false );
01416 
01417   // The server returns a HTTP/1.1 200 Ok or HTTP/1.1 204 No Content
01418   // on successful completion
01419   if ( m_responseCode == 200 || m_responseCode == 204 )
01420     davFinished();
01421   else
01422     davError();
01423 }
01424 
01425 void HTTPProtocol::post( const KURL& url )
01426 {
01427   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::post "
01428                 << url.prettyURL() << endl;
01429 
01430   if ( !checkRequestURL( url ) )
01431     return;
01432 
01433   m_request.method = HTTP_POST;
01434   m_request.path = url.path();
01435   m_request.query = url.query();
01436   m_request.cache = CC_Reload;
01437   m_request.doProxy = m_bUseProxy;
01438 
01439   retrieveContent();
01440 }
01441 
01442 void HTTPProtocol::davLock( const KURL& url, const QString& scope,
01443                             const QString& type, const QString& owner )
01444 {
01445   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::davLock "
01446                 << url.prettyURL() << endl;
01447 
01448   if ( !checkRequestURL( url ) )
01449     return;
01450 
01451   m_request.method = DAV_LOCK;
01452   m_request.path = url.path();
01453   m_request.query = QString::null;
01454   m_request.cache = CC_Reload;
01455   m_request.doProxy = m_bUseProxy;
01456 
01457   /* Create appropriate lock XML request. */
01458   QDomDocument lockReq;
01459 
01460   QDomElement lockInfo = lockReq.createElementNS( "DAV:", "lockinfo" );
01461   lockReq.appendChild( lockInfo );
01462 
01463   QDomElement lockScope = lockReq.createElement( "lockscope" );
01464   lockInfo.appendChild( lockScope );
01465 
01466   lockScope.appendChild( lockReq.createElement( scope ) );
01467 
01468   QDomElement lockType = lockReq.createElement( "locktype" );
01469   lockInfo.appendChild( lockType );
01470 
01471   lockType.appendChild( lockReq.createElement( type ) );
01472 
01473   if ( !owner.isNull() ) {
01474     QDomElement ownerElement = lockReq.createElement( "owner" );
01475     lockReq.appendChild( ownerElement );
01476 
01477     QDomElement ownerHref = lockReq.createElement( "href" );
01478     ownerElement.appendChild( ownerHref );
01479 
01480     ownerHref.appendChild( lockReq.createTextNode( owner ) );
01481   }
01482 
01483   // insert the document into the POST buffer
01484   m_bufPOST = lockReq.toCString();
01485 
01486   retrieveContent( true );
01487 
01488   if ( m_responseCode == 200 ) {
01489     // success
01490     QDomDocument multiResponse;
01491     multiResponse.setContent( m_bufWebDavData, true );
01492 
01493     QDomElement prop = multiResponse.documentElement().namedItem( "prop" ).toElement();
01494 
01495     QDomElement lockdiscovery = prop.namedItem( "lockdiscovery" ).toElement();
01496 
01497     uint lockCount = 0;
01498     davParseActiveLocks( lockdiscovery.elementsByTagName( "activelock" ), lockCount );
01499 
01500     setMetaData( "davLockCount", QString("%1").arg( lockCount ) );
01501 
01502     finished();
01503 
01504   } else
01505     davError();
01506 }
01507 
01508 void HTTPProtocol::davUnlock( const KURL& url )
01509 {
01510   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::davUnlock "
01511                 << url.prettyURL() << endl;
01512 
01513   if ( !checkRequestURL( url ) )
01514     return;
01515 
01516   m_request.method = DAV_UNLOCK;
01517   m_request.path = url.path();
01518   m_request.query = QString::null;
01519   m_request.cache = CC_Reload;
01520   m_request.doProxy = m_bUseProxy;
01521 
01522   retrieveContent( true );
01523 
01524   if ( m_responseCode == 200 )
01525     finished();
01526   else
01527     davError();
01528 }
01529 
01530 QString HTTPProtocol::davError( int code /* = -1 */, QString url )
01531 {
01532   bool callError = false;
01533   if ( code == -1 ) {
01534     code = m_responseCode;
01535     callError = true;
01536   }
01537   if ( code == -2 ) {
01538     callError = true;
01539   }
01540 
01541   if ( !url.isNull() )
01542     url = m_request.url.url();
01543 
01544   QString action, errorString;
01545   KIO::Error kError;
01546 
01547   // for 412 Precondition Failed
01548   QString ow = i18n( "Otherwise, the request would have succeeded." );
01549 
01550   switch ( m_request.method ) {
01551     case DAV_PROPFIND:
01552       action = i18n( "retrieve property values" );
01553       break;
01554     case DAV_PROPPATCH:
01555       action = i18n( "set property values" );
01556       break;
01557     case DAV_MKCOL:
01558       action = i18n( "create the requested folder" );
01559       break;
01560     case DAV_COPY:
01561       action = i18n( "copy the specified file or folder" );
01562       break;
01563     case DAV_MOVE:
01564       action = i18n( "move the specified file or folder" );
01565       break;
01566     case DAV_SEARCH:
01567       action = i18n( "search in the specified folder" );
01568       break;
01569     case DAV_LOCK:
01570       action = i18n( "lock the specified file or folder" );
01571       break;
01572     case DAV_UNLOCK:
01573       action = i18n( "unlock the specified file or folder" );
01574       break;
01575     case HTTP_DELETE:
01576       action = i18n( "delete the specified file or folder" );
01577       break;
01578     case HTTP_OPTIONS:
01579       action = i18n( "query the server's capabilities" );
01580       break;
01581     case HTTP_GET:
01582       action = i18n( "retrieve the contents of the specified file or folder" );
01583       break;
01584     case HTTP_PUT:
01585     case HTTP_POST:
01586     case HTTP_HEAD:
01587     default:
01588       // this should not happen, this function is for webdav errors only
01589       Q_ASSERT(0);
01590   }
01591 
01592   // default error message if the following code fails
01593   kError = ERR_INTERNAL;
01594   errorString = i18n("An unexpected error (%1) occurred while attempting to %2.")
01595                       .arg( code ).arg( action );
01596 
01597   switch ( code )
01598   {
01599     case -2:
01600       // internal error: OPTIONS request did not specify DAV compliance
01601       kError = ERR_UNSUPPORTED_PROTOCOL;
01602       errorString = i18n("The server does not support the WebDAV protocol.");
01603       break;
01604     case 207:
01605       // 207 Multi-status
01606     {
01607       // our error info is in the returned XML document.
01608       // retrieve the XML document
01609 
01610       // there was an error retrieving the XML document.
01611       // ironic, eh?
01612       if ( !readBody( true ) && m_bError )
01613         return QString::null;
01614 
01615       QStringList errors;
01616       QDomDocument multiResponse;
01617 
01618       multiResponse.setContent( m_bufWebDavData, true );
01619 
01620       QDomElement multistatus = multiResponse.documentElement().namedItem( "multistatus" ).toElement();
01621 
01622       QDomNodeList responses = multistatus.elementsByTagName( "response" );
01623 
01624       for (uint i = 0; i < responses.count(); i++)
01625       {
01626         int errCode;
01627         QString errUrl;
01628 
01629         QDomElement response = responses.item(i).toElement();
01630         QDomElement code = response.namedItem( "status" ).toElement();
01631 
01632         if ( !code.isNull() )
01633         {
01634           errCode = codeFromResponse( code.text() );
01635           QDomElement href = response.namedItem( "href" ).toElement();
01636           if ( !href.isNull() )
01637             errUrl = href.text();
01638           errors << davError( errCode, errUrl );
01639         }
01640       }
01641 
01642       //kError = ERR_SLAVE_DEFINED;
01643       errorString = i18n("An error occurred while attempting to %1, %2. A "
01644                          "summary of the reasons is below.<ul>").arg( action ).arg( url );
01645 
01646       for ( QStringList::Iterator it = errors.begin(); it != errors.end(); ++it )
01647         errorString += "<li>" + *it + "</li>";
01648 
01649       errorString += "</ul>";
01650     }
01651     case 403:
01652     case 500: // hack: Apache mod_dav returns this instead of 403 (!)
01653       // 403 Forbidden
01654       kError = ERR_ACCESS_DENIED;
01655       errorString = i18n("Access was denied while attempting to %1.").arg( action );
01656       break;
01657     case 405:
01658       // 405 Method Not Allowed
01659       if ( m_request.method == DAV_MKCOL )
01660       {
01661         kError = ERR_DIR_ALREADY_EXIST;
01662         errorString = i18n("The specified folder already exists.");
01663       }
01664       break;
01665     case 409:
01666       // 409 Conflict
01667       kError = ERR_ACCESS_DENIED;
01668       errorString = i18n("A resource cannot be created at the destination "
01669                   "until one or more intermediate collections (folders) "
01670                   "have been created.");
01671       break;
01672     case 412:
01673       // 412 Precondition failed
01674       if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE )
01675       {
01676         kError = ERR_ACCESS_DENIED;
01677         errorString = i18n("The server was unable to maintain the liveness of "
01678                            "the properties listed in the propertybehavior XML "
01679                            "element or you attempted to overwrite a file while "
01680                            "requesting that files are not overwritten. %1")
01681                            .arg( ow );
01682 
01683       }
01684       else if ( m_request.method == DAV_LOCK )
01685       {
01686         kError = ERR_ACCESS_DENIED;
01687         errorString = i18n("The requested lock could not be granted. %1").arg( ow );
01688       }
01689       break;
01690     case 415:
01691       // 415 Unsupported Media Type
01692       kError = ERR_ACCESS_DENIED;
01693       errorString = i18n("The server does not support the request type of the body.");
01694       break;
01695     case 423:
01696       // 423 Locked
01697       kError = ERR_ACCESS_DENIED;
01698       errorString = i18n("Unable to %1 because the resource is locked.").arg( action );
01699       break;
01700     case 425:
01701       // 424 Failed Dependency
01702       errorString = i18n("This action was prevented by another error.");
01703       break;
01704     case 502:
01705       // 502 Bad Gateway
01706       if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE )
01707       {
01708         kError = ERR_WRITE_ACCESS_DENIED;
01709         errorString = i18n("Unable to %1 because the destination server refuses "
01710                            "to accept the file or folder.").arg( action );
01711       }
01712       break;
01713     case 507:
01714       // 507 Insufficient Storage
01715       kError = ERR_DISK_FULL;
01716       errorString = i18n("The destination resource does not have sufficient space "
01717                          "to record the state of the resource after the execution "
01718                          "of this method.");
01719       break;
01720   }
01721 
01722   // if ( kError != ERR_SLAVE_DEFINED )
01723   //errorString += " (" + url + ")";
01724 
01725   if ( callError )
01726     error( ERR_SLAVE_DEFINED, errorString );
01727 
01728   return errorString;
01729 }
01730 
01731 void HTTPProtocol::httpError()
01732 {
01733   QString action, errorString;
01734   KIO::Error kError;
01735 
01736   switch ( m_request.method ) {
01737     case HTTP_PUT:
01738       action = i18n( "upload %1" ).arg(m_request.url.prettyURL());
01739       break;
01740     default:
01741       // this should not happen, this function is for http errors only
01742       Q_ASSERT(0);
01743   }
01744 
01745   // default error message if the following code fails
01746   kError = ERR_INTERNAL;
01747   errorString = i18n("An unexpected error (%1) occurred while attempting to %2.")
01748                       .arg( m_responseCode ).arg( action );
01749 
01750   switch ( m_responseCode )
01751   {
01752     case 403:
01753     case 405:
01754     case 500: // hack: Apache mod_dav returns this instead of 403 (!)
01755       // 403 Forbidden
01756       // 405 Method Not Allowed
01757       kError = ERR_ACCESS_DENIED;
01758       errorString = i18n("Access was denied while attempting to %1.").arg( action );
01759       break;
01760     case 409:
01761       // 409 Conflict
01762       kError = ERR_ACCESS_DENIED;
01763       errorString = i18n("A resource cannot be created at the destination "
01764                   "until one or more intermediate collections (folders) "
01765                   "have been created.");
01766       break;
01767     case 423:
01768       // 423 Locked
01769       kError = ERR_ACCESS_DENIED;
01770       errorString = i18n("Unable to %1 because the resource is locked.").arg( action );
01771       break;
01772     case 502:
01773       // 502 Bad Gateway
01774       kError = ERR_WRITE_ACCESS_DENIED;
01775       errorString = i18n("Unable to %1 because the destination server refuses "
01776                          "to accept the file or folder.").arg( action );
01777       break;
01778     case 507:
01779       // 507 Insufficient Storage
01780       kError = ERR_DISK_FULL;
01781       errorString = i18n("The destination resource does not have sufficient space "
01782                          "to record the state of the resource after the execution "
01783                          "of this method.");
01784       break;
01785   }
01786 
01787   // if ( kError != ERR_SLAVE_DEFINED )
01788   //errorString += " (" + url + ")";
01789 
01790   error( ERR_SLAVE_DEFINED, errorString );
01791 }
01792 
01793 bool HTTPProtocol::isOffline(const KURL &url)
01794 {
01795   const int NetWorkStatusUnknown = 1;
01796   const int NetWorkStatusOnline = 8;
01797   QCString replyType;
01798   QByteArray params;
01799   QByteArray reply;
01800 
01801   QDataStream stream(params, IO_WriteOnly);
01802   stream << url.url();
01803 
01804   if ( dcopClient()->call( "kded", "networkstatus", "status(QString)",
01805                            params, replyType, reply ) && (replyType == "int") )
01806   {
01807      int result;
01808      QDataStream stream2( reply, IO_ReadOnly );
01809      stream2 >> result;
01810      kdDebug(7113) << "(" << m_pid << ") networkstatus status = " << result << endl;
01811      return (result != NetWorkStatusUnknown) && (result != NetWorkStatusOnline);
01812   }
01813   kdDebug(7113) << "(" << m_pid << ") networkstatus <unreachable>" << endl;
01814   return false; // On error, assume we are online
01815 }
01816 
01817 void HTTPProtocol::multiGet(const QByteArray &data)
01818 {
01819   QDataStream stream(data, IO_ReadOnly);
01820   Q_UINT32 n;
01821   stream >> n;
01822 
01823   kdDebug(7113) << "(" << m_pid << ") HTTPProtcool::multiGet n = " << n << endl;
01824 
01825   HTTPRequest saveRequest;
01826   if (m_bBusy)
01827      saveRequest = m_request;
01828 
01829 //  m_requestQueue.clear();
01830   for(unsigned i = 0; i < n; i++)
01831   {
01832      KURL url;
01833      stream >> url >> mIncomingMetaData;
01834 
01835      if ( !checkRequestURL( url ) )
01836         continue;
01837 
01838      kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::multi_get " << url.url() << endl;
01839 
01840      m_request.method = HTTP_GET;
01841      m_request.path = url.path();
01842      m_request.query = url.query();
01843      QString tmp = metaData("cache");
01844      if (!tmp.isEmpty())
01845         m_request.cache = parseCacheControl(tmp);
01846      else
01847         m_request.cache = DEFAULT_CACHE_CONTROL;
01848 
01849      m_request.passwd = url.pass();
01850      m_request.user = url.user();
01851      m_request.doProxy = m_bUseProxy;
01852 
01853      HTTPRequest *newRequest = new HTTPRequest(m_request);
01854      m_requestQueue.append(newRequest);
01855   }
01856 
01857   if (m_bBusy)
01858      m_request = saveRequest;
01859 
01860   if (!m_bBusy)
01861   {
01862      m_bBusy = true;
01863      while(!m_requestQueue.isEmpty())
01864      {
01865         HTTPRequest *request = m_requestQueue.take(0);
01866         m_request = *request;
01867         delete request;
01868         retrieveContent();
01869      }
01870      m_bBusy = false;
01871   }
01872 }
01873 
01874 ssize_t HTTPProtocol::write (const void *_buf, size_t nbytes)
01875 {
01876   int bytes_sent = 0;
01877   const char* buf = static_cast<const char*>(_buf);
01878   while ( nbytes > 0 )
01879   {
01880     int n = TCPSlaveBase::write(buf, nbytes);
01881 
01882     if ( n <= 0 )
01883     {
01884       // remote side closed connection ?
01885       if ( n == 0 )
01886         break;
01887       // a valid exception(s) occurred, let's retry...
01888       if (n < 0 && ((errno == EINTR) || (errno == EAGAIN)))
01889         continue;
01890       // some other error occurred ?
01891       return -1;
01892     }
01893 
01894     nbytes -= n;
01895     buf += n;
01896     bytes_sent += n;
01897   }
01898 
01899   return bytes_sent;
01900 }
01901 
01902 void HTTPProtocol::setRewindMarker()
01903 {
01904   m_rewindCount = 0;
01905 }
01906 
01907 void HTTPProtocol::rewind()
01908 {
01909   m_linePtrUnget = m_rewindBuf,
01910   m_lineCountUnget = m_rewindCount;
01911   m_rewindCount = 0;
01912 }
01913 
01914 
01915 char *HTTPProtocol::gets (char *s, int size)
01916 {
01917   int len=0;
01918   char *buf=s;
01919   char mybuf[2]={0,0};
01920 
01921   while (len < size)
01922   {
01923     read(mybuf, 1);
01924     if (m_bEOF)
01925       break;
01926 
01927     if (m_rewindCount < sizeof(m_rewindBuf))
01928        m_rewindBuf[m_rewindCount++] = *mybuf;
01929 
01930     if (*mybuf == '\r') // Ignore!
01931       continue;
01932 
01933     if ((*mybuf == '\n') || !*mybuf)
01934       break;
01935 
01936     *buf++ = *mybuf;
01937     len++;
01938   }
01939 
01940   *buf=0;
01941   return s;
01942 }
01943 
01944 ssize_t HTTPProtocol::read (void *b, size_t nbytes)
01945 {
01946   ssize_t ret = 0;
01947 
01948   if (m_lineCountUnget > 0)
01949   {
01950     ret = ( nbytes < m_lineCountUnget ? nbytes : m_lineCountUnget );
01951     m_lineCountUnget -= ret;
01952     memcpy(b, m_linePtrUnget, ret);
01953     m_linePtrUnget += ret;
01954 
01955     return ret;
01956   }
01957 
01958   if (m_lineCount > 0)
01959   {
01960     ret = ( nbytes < m_lineCount ? nbytes : m_lineCount );
01961     m_lineCount -= ret;
01962     memcpy(b, m_linePtr, ret);
01963     m_linePtr += ret;
01964     return ret;
01965   }
01966 
01967   if (nbytes == 1)
01968   {
01969     ret = read(m_lineBuf, 1024); // Read into buffer
01970     m_linePtr = m_lineBuf;
01971     if (ret <= 0)
01972     {
01973       m_lineCount = 0;
01974       return ret;
01975     }
01976     m_lineCount = ret;
01977     return read(b, 1); // Read from buffer
01978   }
01979 
01980   do
01981   {
01982     ret = TCPSlaveBase::read( b, nbytes);
01983     if (ret == 0)
01984       m_bEOF = true;
01985 
01986   } while ((ret == -1) && (errno == EAGAIN || errno == EINTR));
01987 
01988   return ret;
01989 }
01990 
01991 void HTTPProtocol::httpCheckConnection()
01992 {
01993   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpCheckConnection: " <<
01994                                    " Socket status: " << m_iSock <<
01995                                       " Keep Alive: " << m_bKeepAlive <<
01996                                            " First: " << m_bFirstRequest << endl;
01997 
01998   if ( !m_bFirstRequest && (m_iSock != -1) )
01999   {
02000      bool closeDown = false;
02001      if ( !isConnectionValid())
02002      {
02003         kdDebug(7113) << "(" << m_pid << ") Connection lost!" << endl;
02004         closeDown = true;
02005      }
02006      else if ( m_request.method != HTTP_GET )
02007      {
02008         closeDown = true;
02009      }
02010      else if ( !m_state.doProxy && !m_request.doProxy )
02011      {
02012         if (m_state.hostname != m_request.hostname ||
02013             m_state.port != m_request.port ||
02014             m_state.user != m_request.user ||
02015             m_state.passwd != m_request.passwd)
02016           closeDown = true;
02017      }
02018      else
02019      {
02020         // Keep the connection to the proxy.
02021         if ( !(m_request.doProxy && m_state.doProxy) )
02022           closeDown = true;
02023      }
02024 
02025      if (closeDown)
02026         httpCloseConnection();
02027   }
02028 
02029   // Let's update our current state
02030   m_state.hostname = m_request.hostname;
02031   m_state.encoded_hostname = m_request.encoded_hostname;
02032   m_state.port = m_request.port;
02033   m_state.user = m_request.user;
02034   m_state.passwd = m_request.passwd;
02035   m_state.doProxy = m_request.doProxy;
02036 }
02037 
02038 bool HTTPProtocol::httpOpenConnection()
02039 {
02040   int errCode;
02041   QString errMsg;
02042 
02043   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpenConnection" << endl;
02044 
02045   setBlockConnection( true );
02046   // kio_http uses its own proxying:
02047   KSocks::self()->disableSocks();
02048 
02049   if ( m_state.doProxy )
02050   {
02051     QString proxy_host = m_proxyURL.host();
02052     int proxy_port = m_proxyURL.port();
02053 
02054     kdDebug(7113) << "(" << m_pid << ") Connecting to proxy server: "
02055                   << proxy_host << ", port: " << proxy_port << endl;
02056 
02057     infoMessage( i18n("Connecting to %1...").arg(m_state.hostname) );
02058 
02059     setConnectTimeout( m_proxyConnTimeout );
02060 
02061     if ( !connectToHost(proxy_host, proxy_port, false) )
02062     {
02063       if (userAborted()) {
02064         error(ERR_NO_CONTENT, "");
02065         return false;
02066       }
02067 
02068       switch ( connectResult() )
02069       {
02070         case IO_LookupError:
02071           errMsg = proxy_host;
02072           errCode = ERR_UNKNOWN_PROXY_HOST;
02073           break;
02074         case IO_TimeOutError:
02075           errMsg = i18n("Proxy %1 at port %2").arg(proxy_host).arg(proxy_port);
02076           errCode = ERR_SERVER_TIMEOUT;
02077           break;
02078         default:
02079           errMsg = i18n("Proxy %1 at port %2").arg(proxy_host).arg(proxy_port);
02080           errCode = ERR_COULD_NOT_CONNECT;
02081       }
02082       error( errCode, errMsg );
02083       return false;
02084     }
02085   }
02086   else
02087   {
02088     // Apparently we don't want a proxy.  let's just connect directly
02089     setConnectTimeout(m_remoteConnTimeout);
02090 
02091     if ( !connectToHost(m_state.hostname, m_state.port, false ) )
02092     {
02093       if (userAborted()) {
02094         error(ERR_NO_CONTENT, "");
02095         return false;
02096       }
02097 
02098       switch ( connectResult() )
02099       {
02100         case IO_LookupError:
02101           errMsg = m_state.hostname;
02102           errCode = ERR_UNKNOWN_HOST;
02103           break;
02104         case IO_TimeOutError:
02105           errMsg = i18n("Connection was to %1 at port %2").arg(m_state.hostname).arg(m_state.port);
02106           errCode = ERR_SERVER_TIMEOUT;
02107           break;
02108         default:
02109           errCode = ERR_COULD_NOT_CONNECT;
02110           if (m_state.port != m_iDefaultPort)
02111             errMsg = i18n("%1 (port %2)").arg(m_state.hostname).arg(m_state.port);
02112           else
02113             errMsg = m_state.hostname;
02114       }
02115       error( errCode, errMsg );
02116       return false;
02117     }
02118   }
02119 
02120   // Set our special socket option!!
02121   int on = 1;
02122   (void) setsockopt( m_iSock, IPPROTO_TCP, TCP_NODELAY, (char*)&on, sizeof(on) );
02123 
02124   m_bFirstRequest = true;
02125 
02126   connected();
02127   return true;
02128 }
02129 
02130 
02153 bool HTTPProtocol::httpOpen()
02154 {
02155   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpen" << endl;
02156 
02157   // Cannot have an https request without the m_bIsSSL being set!  This can
02158   // only happen if TCPSlaveBase::InitializeSSL() function failed in which it
02159   // means the current installation does not support SSL...
02160   if ( (m_protocol == "https" || m_protocol == "webdavs") && !m_bIsSSL )
02161   {
02162     error( ERR_UNSUPPORTED_PROTOCOL, m_protocol );
02163     return false;
02164   }
02165 
02166   m_request.fcache = 0;
02167   m_request.bCachedRead = false;
02168   m_request.bCachedWrite = false;
02169   m_request.bMustRevalidate = false;
02170   m_request.expireDate = 0;
02171   m_request.creationDate = 0;
02172 
02173   if (m_request.bUseCache)
02174   {
02175      m_request.fcache = checkCacheEntry( );
02176 
02177      bool bCacheOnly = (m_request.cache == KIO::CC_CacheOnly);
02178      bool bOffline = isOffline(m_request.doProxy ? m_proxyURL : m_request.url);
02179      if (bOffline && (m_request.cache != KIO::CC_Reload))
02180         m_request.cache = KIO::CC_CacheOnly;
02181 
02182      if (m_request.cache == CC_Reload && m_request.fcache)
02183      {
02184         if (m_request.fcache)
02185           fclose(m_request.fcache);
02186         m_request.fcache = 0;
02187      }
02188      if ((m_request.cache == KIO::CC_CacheOnly) || (m_request.cache == KIO::CC_Cache))
02189         m_request.bMustRevalidate = false;
02190 
02191      m_request.bCachedWrite = true;
02192 
02193      if (m_request.fcache && !m_request.bMustRevalidate)
02194      {
02195         // Cache entry is OK.
02196         m_request.bCachedRead = true; // Cache hit.
02197         return true;
02198      }
02199      else if (!m_request.fcache)
02200      {
02201         m_request.bMustRevalidate = false; // Cache miss
02202      }
02203      else
02204      {
02205         // Conditional cache hit. (Validate)
02206      }
02207 
02208      if (bCacheOnly)
02209      {
02210         error( ERR_DOES_NOT_EXIST, m_request.url.url() );
02211         return false;
02212      }
02213      if (bOffline)
02214      {
02215         error( ERR_COULD_NOT_CONNECT, m_request.url.url() );
02216         return false;
02217      }
02218   }
02219 
02220   QString header;
02221   QString davHeader;
02222 
02223   bool moreData = false;
02224   bool davData = false;
02225 
02226   // Clear out per-connection settings...
02227   resetConnectionSettings ();
02228 
02229   // Check the validity of the current connection, if one exists.
02230   httpCheckConnection();
02231 
02232   if ( !m_bIsTunneled && m_bNeedTunnel )
02233   {
02234     setEnableSSLTunnel( true );
02235     // We send a HTTP 1.0 header since some proxies refuse HTTP 1.1 and we don't
02236     // need any HTTP 1.1 capabilities for CONNECT - Waba
02237     header = QString("CONNECT %1:%2 HTTP/1.0"
02238                      "\r\n").arg( m_request.encoded_hostname).arg(m_request.port);
02239 
02240     // Identify who you are to the proxy server!
02241     if (!m_request.userAgent.isEmpty())
02242         header += "User-Agent: " + m_request.userAgent + "\r\n";
02243 
02244     /* Add hostname information */
02245     header += "Host: " + m_state.encoded_hostname;
02246 
02247     if (m_state.port != m_iDefaultPort)
02248       header += QString(":%1").arg(m_state.port);
02249     header += "\r\n";
02250 
02251     header += proxyAuthenticationHeader();
02252   }
02253   else
02254   {
02255     // Determine if this is a POST or GET method
02256     switch (m_request.method)
02257     {
02258     case HTTP_GET:
02259         header = "GET ";
02260         break;
02261     case HTTP_PUT:
02262         header = "PUT ";
02263         moreData = true;
02264         m_request.bCachedWrite = false; // Do not put any result in the cache
02265         break;
02266     case HTTP_POST:
02267         header = "POST ";
02268         moreData = true;
02269         m_request.bCachedWrite = false; // Do not put any result in the cache
02270         break;
02271     case HTTP_HEAD:
02272         header = "HEAD ";
02273         break;
02274     case HTTP_DELETE:
02275         header = "DELETE ";
02276         m_request.bCachedWrite = false; // Do not put any result in the cache
02277         break;
02278     case HTTP_OPTIONS:
02279         header = "OPTIONS ";
02280         m_request.bCachedWrite = false; // Do not put any result in the cache
02281         break;
02282     case DAV_PROPFIND:
02283         header = "PROPFIND ";
02284         davData = true;
02285         davHeader = "Depth: ";
02286         if ( hasMetaData( "davDepth" ) )
02287         {
02288           kdDebug(7113) << "Reading DAV depth from metadata: " << metaData( "davDepth" ) << endl;
02289           davHeader += metaData( "davDepth" );
02290         }
02291         else
02292         {
02293           if ( m_request.davData.depth == 2 )
02294             davHeader += "infinity";
02295           else
02296             davHeader += QString("%1").arg( m_request.davData.depth );
02297         }
02298         davHeader += "\r\n";
02299         m_request.bCachedWrite = false; // Do not put any result in the cache
02300         break;
02301     case DAV_PROPPATCH:
02302         header = "PROPPATCH ";
02303         davData = true;
02304         m_request.bCachedWrite = false; // Do not put any result in the cache
02305         break;
02306     case DAV_MKCOL:
02307         header = "MKCOL ";
02308         m_request.bCachedWrite = false; // Do not put any result in the cache
02309         break;
02310     case DAV_COPY:
02311     case DAV_MOVE:
02312         header = ( m_request.method == DAV_COPY ) ? "COPY " : "MOVE ";
02313         davHeader = "Destination: " + m_request.davData.desturl;
02314         // infinity depth means copy recursively
02315         // (optional for copy -> but is the desired action)
02316         davHeader += "\r\nDepth: infinity\r\nOverwrite: ";
02317         davHeader += m_request.davData.overwrite ? "T" : "F";
02318         davHeader += "\r\n";
02319         m_request.bCachedWrite = false; // Do not put any result in the cache
02320         break;
02321     case DAV_LOCK:
02322         header = "LOCK ";
02323         davHeader = "Timeout: ";
02324         {
02325           uint timeout = 0;
02326           if ( hasMetaData( "davTimeout" ) )
02327             timeout = metaData( "davTimeout" ).toUInt();
02328           if ( timeout == 0 )
02329             davHeader += "Infinite";
02330           else
02331             davHeader += QString("Seconds-%1").arg(timeout);
02332         }
02333         davHeader += "\r\n";
02334         m_request.bCachedWrite = false; // Do not put any result in the cache
02335         davData = true;
02336         break;
02337     case DAV_UNLOCK:
02338         header = "UNLOCK ";
02339         davHeader = "Lock-token: " + metaData("davLockToken") + "\r\n";
02340         m_request.bCachedWrite = false; // Do not put any result in the cache
02341         break;
02342     case DAV_SEARCH:
02343         header = "SEARCH ";
02344         davData = true;
02345         m_request.bCachedWrite = false;
02346         break;
02347     case DAV_SUBSCRIBE:
02348         header = "SUBSCRIBE ";
02349         m_request.bCachedWrite = false;
02350         break;
02351     case DAV_UNSUBSCRIBE:
02352         header = "UNSUBSCRIBE ";
02353         m_request.bCachedWrite = false;
02354         break;
02355     case DAV_POLL:
02356         header = "POLL ";
02357         m_request.bCachedWrite = false;
02358         break;
02359     default:
02360         error (ERR_UNSUPPORTED_ACTION, QString::null);
02361         return false;
02362     }
02363     // DAV_POLL; DAV_NOTIFY
02364 
02365     // format the URI
02366     if (m_state.doProxy && !m_bIsTunneled)
02367     {
02368       KURL u;
02369 
02370       if (m_protocol == "webdav")
02371          u.setProtocol( "http" );
02372       else if (m_protocol == "webdavs" )
02373          u.setProtocol( "https" );
02374       else
02375          u.setProtocol( m_protocol );
02376 
02377       // For all protocols other than the once handled by this io-slave
02378       // append the username.  This fixes a long standing bug of ftp io-slave
02379       // logging in anonymously in proxied connections even when the username
02380       // is explicitly specified.
02381       if (m_protocol != "http" && m_protocol != "https" &&
02382           !m_state.user.isEmpty())
02383         u.setUser (m_state.user);
02384 
02385       u.setHost( m_state.hostname );
02386       if (m_state.port != m_iDefaultPort)
02387          u.setPort( m_state.port );
02388       u.setEncodedPathAndQuery( m_request.url.encodedPathAndQuery(0,true) );
02389       header += u.url();
02390     }
02391     else
02392     {
02393       header += m_request.url.encodedPathAndQuery(0, true);
02394     }
02395 
02396     header += " HTTP/1.1\r\n"; /* start header */
02397 
02398     if (!m_request.userAgent.isEmpty())
02399     {
02400         header += "User-Agent: ";
02401         header += m_request.userAgent;
02402         header += "\r\n";
02403     }
02404 
02405     if (!m_request.referrer.isEmpty())
02406     {
02407         header += "Referer: "; //Don't try to correct spelling!
02408         header += m_request.referrer;
02409         header += "\r\n";
02410     }
02411 
02412     if ( m_request.offset > 0 )
02413     {
02414       header += QString("Range: bytes=%1-\r\n").arg(KIO::number(m_request.offset));
02415       kdDebug(7103) << "kio_http : Range = " << KIO::number(m_request.offset) << endl;
02416     }
02417 
02418     if ( m_request.cache == CC_Reload )
02419     {
02420       /* No caching for reload */
02421       header += "Pragma: no-cache\r\n"; /* for HTTP/1.0 caches */
02422       header += "Cache-control: no-cache\r\n"; /* for HTTP >=1.1 caches */
02423     }
02424 
02425     if (m_request.bMustRevalidate)
02426     {
02427       /* conditional get */
02428       if (!m_request.etag.isEmpty())
02429         header += "If-None-Match: "+m_request.etag+"\r\n";
02430       if (!m_request.lastModified.isEmpty())
02431         header += "If-Modified-Since: "+m_request.lastModified+"\r\n";
02432     }
02433 
02434     header += "Accept: ";
02435     QString acceptHeader = metaData("accept");
02436     if (!acceptHeader.isEmpty())
02437       header += acceptHeader;
02438     else
02439       header += DEFAULT_ACCEPT_HEADER;
02440     header += "\r\n";
02441 
02442 #ifdef DO_GZIP
02443     if (m_request.allowCompressedPage)
02444       header += "Accept-Encoding: x-gzip, x-deflate, gzip, deflate\r\n";
02445 #endif
02446 
02447     if (!m_request.charsets.isEmpty())
02448       header += "Accept-Charset: " + m_request.charsets + "\r\n";
02449 
02450     if (!m_request.languages.isEmpty())
02451       header += "Accept-Language: " + m_request.languages + "\r\n";
02452 
02453 
02454     /* support for virtual hosts and required by HTTP 1.1 */
02455     header += "Host: " + m_state.encoded_hostname;
02456 
02457     if (m_state.port != m_iDefaultPort)
02458       header += QString(":%1").arg(m_state.port);
02459     header += "\r\n";
02460 
02461     QString cookieStr;
02462     QString cookieMode = metaData("cookies").lower();
02463     if (cookieMode == "none")
02464     {
02465       m_request.cookieMode = HTTPRequest::CookiesNone;
02466     }
02467     else if (cookieMode == "manual")
02468     {
02469       m_request.cookieMode = HTTPRequest::CookiesManual;
02470       cookieStr = metaData("setcookies");
02471     }
02472     else
02473     {
02474       m_request.cookieMode = HTTPRequest::CookiesAuto;
02475       if (m_request.bUseCookiejar)
02476         cookieStr = findCookies( m_request.url.url());
02477     }
02478 
02479     if (!cookieStr.isEmpty())
02480       header += cookieStr + "\r\n";
02481 
02482     QString customHeader = metaData( "customHTTPHeader" );
02483     if (!customHeader.isEmpty())
02484     {
02485       header += sanitizeCustomHTTPHeader(customHeader);
02486       header += "\r\n";
02487     }
02488 
02489     if (m_request.method == HTTP_POST)
02490     {
02491       header += metaData("content-type");
02492       header += "\r\n";
02493     }
02494 
02495     // Only check for a cached copy if the previous
02496     // response was NOT a 401 or 407.
02497     // no caching for Negotiate auth.
02498     if ( !m_request.bNoAuth && m_responseCode != 401 && m_responseCode != 407 && Authentication != AUTH_Negotiate )
02499     {
02500       kdDebug(7113) << "(" << m_pid << ") Calling checkCachedAuthentication " << endl;
02501       AuthInfo info;
02502       info.url = m_request.url;
02503       info.verifyPath = true;
02504       if ( !m_request.user.isEmpty() )
02505         info.username = m_request.user;
02506       if ( checkCachedAuthentication( info ) && !info.digestInfo.isEmpty() )
02507       {
02508         Authentication = info.digestInfo.startsWith("Basic") ? AUTH_Basic : info.digestInfo.startsWith("NTLM") ? AUTH_NTLM : info.digestInfo.startsWith("Negotiate") ? AUTH_Negotiate : AUTH_Digest ;
02509         m_state.user   = info.username;
02510         m_state.passwd = info.password;
02511         m_strRealm = info.realmValue;
02512         if ( Authentication != AUTH_NTLM && Authentication != AUTH_Negotiate ) // don't use the cached challenge
02513           m_strAuthorization = info.digestInfo;
02514       }
02515     }
02516     else
02517     {
02518       kdDebug(7113) << "(" << m_pid << ") Not calling checkCachedAuthentication " << endl;
02519     }
02520 
02521     switch ( Authentication )
02522     {
02523       case AUTH_Basic:
02524           header += createBasicAuth();
02525           break;
02526       case AUTH_Digest:
02527           header += createDigestAuth();
02528           break;
02529 #ifdef HAVE_LIBGSSAPI
02530       case AUTH_Negotiate:
02531           header += createNegotiateAuth();
02532           break;
02533 #endif
02534       case AUTH_NTLM:
02535           header += createNTLMAuth();
02536           break;
02537       case AUTH_None:
02538       default:
02539           break;
02540     }
02541 
02542     /********* Only for debugging purpose *********/
02543     if ( Authentication != AUTH_None )
02544     {
02545       kdDebug(7113) << "(" << m_pid << ") Using Authentication: " << endl;
02546       kdDebug(7113) << "(" << m_pid << ")   HOST= " << m_state.hostname << endl;
02547       kdDebug(7113) << "(" << m_pid << ")   PORT= " << m_state.port << endl;
02548       kdDebug(7113) << "(" << m_pid << ")   USER= " << m_state.user << endl;
02549       kdDebug(7113) << "(" << m_pid << ")   PASSWORD= [protected]" << endl;
02550       kdDebug(7113) << "(" << m_pid << ")   REALM= " << m_strRealm << endl;
02551       kdDebug(7113) << "(" << m_pid << ")   EXTRA= " << m_strAuthorization << endl;
02552     }
02553 
02554     // Do we need to authorize to the proxy server ?
02555     if ( m_state.doProxy && !m_bIsTunneled )
02556       header += proxyAuthenticationHeader();
02557 
02558     // Support old HTTP/1.0 style keep-alive header for compatability
02559     // purposes as well as performance improvements while giving end
02560     // users the ability to disable this feature proxy servers that
02561     // don't not support such feature, e.g. junkbuster proxy server.
02562     if (!m_bUseProxy || m_bPersistentProxyConnection || m_bIsTunneled)
02563       header += "Connection: Keep-Alive\r\n";
02564     else
02565       header += "Connection: close\r\n";
02566 
02567     if ( m_protocol == "webdav" || m_protocol == "webdavs" )
02568     {
02569       header += davProcessLocks();
02570 
02571       // add extra webdav headers, if supplied
02572       QString davExtraHeader = metaData("davHeader");
02573       if ( !davExtraHeader.isEmpty() )
02574         davHeader += davExtraHeader;
02575 
02576       // Set content type of webdav data
02577       if (davData)
02578         davHeader += "Content-Type: text/xml; charset=utf-8\r\n";
02579 
02580       // add extra header elements for WebDAV
02581       if ( !davHeader.isNull() )
02582         header += davHeader;
02583     }
02584   }
02585 
02586   kdDebug(7103) << "(" << m_pid << ") ============ Sending Header:" << endl;
02587 
02588   QStringList headerOutput = QStringList::split("\r\n", header);
02589   QStringList::Iterator it = headerOutput.begin();
02590 
02591   for (; it != headerOutput.end(); it++)
02592     kdDebug(7103) << "(" << m_pid << ") " << (*it) << endl;
02593 
02594   if ( !moreData && !davData)
02595     header += "\r\n";  /* end header */
02596 
02597   // Now that we have our formatted header, let's send it!
02598   // Create a new connection to the remote machine if we do
02599   // not already have one...
02600   if ( m_iSock == -1)
02601   {
02602     if (!httpOpenConnection())
02603        return false;
02604   }
02605 
02606   // Send the data to the remote machine...
02607   bool sendOk = (write(header.latin1(), header.length()) == (ssize_t) header.length());
02608   if (!sendOk)
02609   {
02610     kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpen: "
02611                      "Connection broken! (" << m_state.hostname << ")" << endl;
02612 
02613     // With a Keep-Alive connection this can happen.
02614     // Just reestablish the connection.
02615     if (m_bKeepAlive)
02616     {
02617        httpCloseConnection();
02618        return true; // Try again
02619     }
02620 
02621     if (!sendOk)
02622     {
02623        kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpen: sendOk==false."
02624                         " Connnection broken !" << endl;
02625        error( ERR_CONNECTION_BROKEN, m_state.hostname );
02626        return false;
02627     }
02628   }
02629 
02630   bool res = true;
02631 
02632   if ( moreData || davData )
02633     res = sendBody();
02634 
02635   infoMessage(i18n("%1 contacted. Waiting for reply...").arg(m_request.hostname));
02636 
02637   return res;
02638 }
02639 
02640 void HTTPProtocol::forwardHttpResponseHeader()
02641 {
02642   // Send the response header if it was requested
02643   if ( config()->readBoolEntry("PropagateHttpHeader", false) )
02644   {
02645     setMetaData("HTTP-Headers", m_responseHeader.join("\n"));
02646     sendMetaData();
02647   }
02648   m_responseHeader.clear();
02649 }
02650 
02657 bool HTTPProtocol::readHeader()
02658 {
02659 try_again:
02660   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader" << endl;
02661 
02662   // Check
02663   if (m_request.bCachedRead)
02664   {
02665      m_responseHeader << "HTTP-CACHE";
02666      // Read header from cache...
02667      char buffer[4097];
02668      if (!fgets(buffer, 4096, m_request.fcache) )
02669      {
02670         // Error, delete cache entry
02671         kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: "
02672                       << "Could not access cache to obtain mimetype!" << endl;
02673         error( ERR_CONNECTION_BROKEN, m_state.hostname );
02674         return false;
02675      }
02676 
02677      m_strMimeType = QString::fromUtf8( buffer).stripWhiteSpace();
02678 
02679      kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: cached "
02680                    << "data mimetype: " << m_strMimeType << endl;
02681 
02682      if (!fgets(buffer, 4096, m_request.fcache) )
02683      {
02684         // Error, delete cache entry
02685         kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: "
02686                       << "Could not access cached data! " << endl;
02687         error( ERR_CONNECTION_BROKEN, m_state.hostname );
02688         return false;
02689      }
02690 
02691      m_request.strCharset = QString::fromUtf8( buffer).stripWhiteSpace().lower();
02692      setMetaData("charset", m_request.strCharset);
02693      if (!m_request.lastModified.isEmpty())
02694          setMetaData("modified", m_request.lastModified);
02695      QString tmp;
02696      tmp.setNum(m_request.expireDate);
02697      setMetaData("expire-date", tmp);
02698      tmp.setNum(m_request.creationDate);
02699      setMetaData("cache-creation-date", tmp);
02700      mimeType(m_strMimeType);
02701      forwardHttpResponseHeader();
02702      return true;
02703   }
02704 
02705   QCString locationStr; // In case we get a redirect.
02706   QCString cookieStr; // In case we get a cookie.
02707 
02708   QString disposition; // Incase we get a Content-Disposition
02709   QString mediaValue;
02710   QString mediaAttribute;
02711 
02712   QStringList upgradeOffers;
02713 
02714   bool upgradeRequired = false;   // Server demands that we upgrade to something
02715                                   // This is also true if we ask to upgrade and
02716                                   // the server accepts, since we are now
02717                                   // committed to doing so
02718   bool canUpgrade = false;        // The server offered an upgrade
02719 
02720 
02721   m_request.etag = QString::null;
02722   m_request.lastModified = QString::null;
02723   m_request.strCharset = QString::null;
02724 
02725   time_t dateHeader = 0;
02726   time_t expireDate = 0; // 0 = no info, 1 = already expired, > 1 = actual date
02727   int currentAge = 0;
02728   int maxAge = -1; // -1 = no max age, 0 already expired, > 0 = actual time
02729   int maxHeaderSize = 64*1024; // 64Kb to catch DOS-attacks
02730 
02731   // read in 4096 bytes at a time (HTTP cookies can be quite large.)
02732   int len = 0;
02733   char buffer[4097];
02734   bool cont = false;
02735   bool cacheValidated = false; // Revalidation was successful
02736   bool mayCache = true;
02737   bool hasCacheDirective = false;
02738   bool bCanResume = false;
02739 
02740   if (m_iSock == -1)
02741   {
02742      kdDebug(7113) << "HTTPProtocol::readHeader: No connection." << endl;
02743      return false; // Restablish connection and try again
02744   }
02745 
02746   if (!waitForResponse(m_remoteRespTimeout))
02747   {
02748      // No response error
02749      error( ERR_SERVER_TIMEOUT , m_state.hostname );
02750      return false;
02751   }
02752 
02753   setRewindMarker();
02754 
02755   gets(buffer, sizeof(buffer)-1);
02756 
02757   if (m_bEOF || *buffer == '\0')
02758   {
02759     kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: "
02760                   << "EOF while waiting for header start." << endl;
02761     if (m_bKeepAlive) // Try to reestablish connection.
02762     {
02763       httpCloseConnection();
02764       return false; // Reestablish connection and try again.
02765     }
02766 
02767     if (m_request.method == HTTP_HEAD)
02768     {
02769       // HACK
02770       // Some web-servers fail to respond properly to a HEAD request.
02771       // We compensate for their failure to properly implement the HTTP standard
02772       // by assuming that they will be sending html.
02773       kdDebug(7113) << "(" << m_pid << ") HTTPPreadHeader: HEAD -> returned "
02774                     << "mimetype: " << DEFAULT_MIME_TYPE << endl;
02775       mimeType(QString::fromLatin1(DEFAULT_MIME_TYPE));
02776       return true;
02777     }
02778 
02779     kdDebug(7113) << "HTTPProtocol::readHeader: Connection broken !" << endl;
02780     error( ERR_CONNECTION_BROKEN, m_state.hostname );
02781     return false;
02782   }
02783 
02784   kdDebug(7103) << "(" << m_pid << ") ============ Received Response:"<< endl;
02785 
02786   bool noHeader = true;
02787   HTTP_REV httpRev = HTTP_None;
02788   int headerSize = 0;
02789 
02790   do
02791   {
02792     // strip off \r and \n if we have them
02793     len = strlen(buffer);
02794 
02795     while(len && (buffer[len-1] == '\n' || buffer[len-1] == '\r'))
02796       buffer[--len] = 0;
02797 
02798     // if there was only a newline then continue
02799     if (!len)
02800     {
02801       kdDebug(7103) << "(" << m_pid << ") --empty--" << endl;
02802       continue;
02803     }
02804 
02805     headerSize += len;
02806 
02807     // We have a response header.  This flag is a work around for
02808     // servers that append a "\r\n" before the beginning of the HEADER
02809     // response!!!  It only catches x number of \r\n being placed at the
02810     // top of the reponse...
02811     noHeader = false;
02812 
02813     kdDebug(7103) << "(" << m_pid << ") \"" << buffer << "\"" << endl;
02814 
02815     // Save broken servers from damnation!!
02816     char* buf = buffer;
02817     while( *buf == ' ' )
02818         buf++;
02819 
02820 
02821     if (buf[0] == '<')
02822     {
02823       // We get XML / HTTP without a proper header
02824       // put string back
02825       kdDebug(7103) << "kio_http: No valid HTTP header found! Document starts with XML/HTML tag" << endl;
02826 
02827       // Document starts with a tag, assume html instead of text/plain
02828       m_strMimeType = "text/html";
02829 
02830       rewind();
02831       break;
02832     }
02833 
02834     // Store the the headers so they can be passed to the
02835     // calling application later
02836     m_responseHeader << QString::fromLatin1(buf);
02837 
02838     if ((strncasecmp(buf, "HTTP", 4) == 0) ||
02839         (strncasecmp(buf, "ICY ", 4) == 0)) // Shoutcast support
02840     {
02841       if (strncasecmp(buf, "ICY ", 4) == 0)
02842       {
02843         // Shoutcast support
02844         httpRev = SHOUTCAST;
02845         m_bKeepAlive = false;
02846       }
02847       else if (strncmp((buf + 5), "1.0",3) == 0)
02848       {
02849         httpRev = HTTP_10;
02850         // For 1.0 servers, the server itself has to explicitly
02851         // tell us whether it supports persistent connection or
02852         // not.  By default, we assume it does not, but we do
02853         // send the old style header "Connection: Keep-Alive" to
02854         // inform it that we support persistence.
02855         m_bKeepAlive = false;
02856       }
02857       else if (strncmp((buf + 5), "1.1",3) == 0)
02858       {
02859         httpRev = HTTP_11;
02860       }
02861       else
02862       {
02863         httpRev = HTTP_Unknown;
02864       }
02865 
02866       if (m_responseCode)
02867         m_prevResponseCode = m_responseCode;
02868 
02869       const char* rptr = buf;
02870       while ( *rptr && *rptr > ' ' )
02871           ++rptr;
02872       m_responseCode = atoi(rptr);
02873 
02874       // server side errors
02875       if (m_responseCode >= 500 && m_responseCode <= 599)
02876       {
02877         if (m_request.method == HTTP_HEAD)
02878         {
02879            ; // Ignore error
02880         }
02881         else
02882         {
02883            if (m_request.bErrorPage)
02884               errorPage();
02885            else
02886            {
02887               error(ERR_INTERNAL_SERVER, m_request.url.url());
02888               return false;
02889            }
02890         }
02891         m_request.bCachedWrite = false; // Don't put in cache
02892         mayCache = false;
02893       }
02894       // Unauthorized access
02895       else if (m_responseCode == 401 || m_responseCode == 407)
02896       {
02897         // Double authorization requests, i.e. a proxy auth
02898         // request followed immediately by a regular auth request.
02899         if ( m_prevResponseCode != m_responseCode &&
02900             (m_prevResponseCode == 401 || m_prevResponseCode == 407) )
02901           saveAuthorization();
02902 
02903         m_bUnauthorized = true;
02904         m_request.bCachedWrite = false; // Don't put in cache
02905         mayCache = false;
02906       }
02907       //
02908       else if (m_responseCode == 416) // Range not supported
02909       {
02910         m_request.offset = 0;
02911         httpCloseConnection();
02912         return false; // Try again.
02913       }
02914       // Upgrade Required
02915       else if (m_responseCode == 426)
02916       {
02917         upgradeRequired = true;
02918       }
02919       // Any other client errors
02920       else if (m_responseCode >= 400 && m_responseCode <= 499)
02921       {
02922         // Tell that we will only get an error page here.
02923         if (m_request.bErrorPage)
02924           errorPage();
02925         else
02926         {
02927           error(ERR_DOES_NOT_EXIST, m_request.url.url());
02928           return false;
02929         }
02930         m_request.bCachedWrite = false; // Don't put in cache
02931         mayCache = false;
02932       }
02933       else if (m_responseCode == 307)
02934       {
02935         // 307 Temporary Redirect
02936         m_request.bCachedWrite = false; // Don't put in cache
02937         mayCache = false;
02938       }
02939       else if (m_responseCode == 304)
02940       {
02941         // 304 Not Modified
02942         // The value in our cache is still valid.
02943         cacheValidated = true;
02944       }
02945       else if (m_responseCode >= 301 && m_responseCode<= 303)
02946       {
02947         // 301 Moved permanently
02948         if (m_responseCode == 301)
02949            setMetaData("permanent-redirect", "true");
02950 
02951         // 302 Found (temporary location)
02952         // 303 See Other
02953         if (m_request.method != HTTP_HEAD && m_request.method != HTTP_GET)
02954         {
02955 #if 0
02956            // Reset the POST buffer to avoid a double submit
02957            // on redirection
02958            if (m_request.method == HTTP_POST)
02959               m_bufPOST.resize(0);
02960 #endif
02961 
02962            // NOTE: This is wrong according to RFC 2616.  However,
02963            // because most other existing user agent implementations
02964            // treat a 301/302 response as a 303 response and preform
02965            // a GET action regardless of what the previous method was,
02966            // many servers have simply adapted to this way of doing
02967            // things!!  Thus, we are forced to do the same thing or we
02968            // won't be able to retrieve these pages correctly!! See RFC
02969            // 2616 sections 10.3.[2/3/4/8]
02970            m_request.method = HTTP_GET; // Force a GET
02971         }
02972         m_request.bCachedWrite = false; // Don't put in cache
02973         mayCache = false;
02974       }
02975       else if ( m_responseCode == 207 ) // Multi-status (for WebDav)
02976       {
02977 
02978       }
02979       else if ( m_responseCode == 204 ) // No content
02980       {
02981         // error(ERR_NO_CONTENT, i18n("Data have been successfully sent."));
02982         // Short circuit and do nothing!
02983 
02984         // The original handling here was wrong, this is not an error: eg. in the
02985         // example of a 204 No Content response to a PUT completing.
02986         // m_bError = true;
02987         // return false;
02988       }
02989       else if ( m_responseCode == 206 )
02990       {
02991         if ( m_request.offset )
02992           bCanResume = true;
02993       }
02994       else if (m_responseCode == 102) // Processing (for WebDAV)
02995       {
02996         /***
02997          * This status code is given when the server expects the
02998          * command to take significant time to complete. So, inform
02999          * the user.
03000          */
03001         infoMessage( i18n( "Server processing request, please wait..." ) );
03002         cont = true;
03003       }
03004       else if (m_responseCode == 100)
03005       {
03006         // We got 'Continue' - ignore it
03007         cont = true;
03008       }
03009     }
03010 
03011     // are we allowd to resume?  this will tell us
03012     else if (strncasecmp(buf, "Accept-Ranges:", 14) == 0) {
03013       if (strncasecmp(trimLead(buf + 14), "none", 4) == 0)
03014             bCanResume = false;
03015     }
03016     // Keep Alive
03017     else if (strncasecmp(buf, "Keep-Alive:", 11) == 0) {
03018       QStringList options = QStringList::split(',',
03019                                      QString::fromLatin1(trimLead(buf+11)));
03020       for(QStringList::ConstIterator it = options.begin();
03021           it != options.end();
03022           it++)
03023       {
03024          QString option = (*it).stripWhiteSpace().lower();
03025          if (option.startsWith("timeout="))
03026          {
03027             m_keepAliveTimeout = option.mid(8).toInt();
03028          }
03029       }
03030     }
03031 
03032     // Cache control
03033     else if (strncasecmp(buf, "Cache-Control:", 14) == 0) {
03034       QStringList cacheControls = QStringList::split(',',
03035                                      QString::fromLatin1(trimLead(buf+14)));
03036       for(QStringList::ConstIterator it = cacheControls.begin();
03037           it != cacheControls.end();
03038           it++)
03039       {
03040          QString cacheControl = (*it).stripWhiteSpace();
03041          if (strncasecmp(cacheControl.latin1(), "no-cache", 8) == 0)
03042          {
03043             m_request.bCachedWrite = false; // Don't put in cache
03044             mayCache = false;
03045          }
03046          else if (strncasecmp(cacheControl.latin1(), "no-store", 8) == 0)
03047          {
03048             m_request.bCachedWrite = false; // Don't put in cache
03049             mayCache = false;
03050          }
03051          else if (strncasecmp(cacheControl.latin1(), "max-age=", 8) == 0)
03052          {
03053             QString age = cacheControl.mid(8).stripWhiteSpace();
03054             if (!age.isNull())
03055               maxAge = STRTOLL(age.latin1(), 0, 10);
03056          }
03057       }
03058       hasCacheDirective = true;
03059     }
03060 
03061     // get the size of our data
03062     else if (strncasecmp(buf, "Content-length:", 15) == 0) {
03063       char* len = trimLead(buf + 15);
03064       if (len)
03065         m_iSize = STRTOLL(len, 0, 10);
03066     }
03067 
03068     else if (strncasecmp(buf, "Content-location:", 17) == 0) {
03069       setMetaData ("content-location",
03070                    QString::fromLatin1(trimLead(buf+17)).stripWhiteSpace());
03071     }
03072 
03073     // what type of data do we have?
03074     else if (strncasecmp(buf, "Content-type:", 13) == 0) {
03075       char *start = trimLead(buf + 13);
03076       char *pos = start;
03077 
03078       // Increment until we encounter ";" or the end of the buffer
03079       while ( *pos && *pos != ';' )  pos++;
03080 
03081       // Assign the mime-type.
03082       m_strMimeType = QString::fromLatin1(start, pos-start).stripWhiteSpace().lower();
03083       kdDebug(7113) << "(" << m_pid << ") Content-type: " << m_strMimeType << endl;
03084 
03085       // If we still have text, then it means we have a mime-type with a
03086       // parameter (eg: charset=iso-8851) ; so let's get that...
03087       while (*pos)
03088       {
03089         start = ++pos;
03090         while ( *pos && *pos != '=' )  pos++;
03091 
03092     char *end = pos;
03093     while ( *end && *end != ';' )  end++;
03094 
03095         if (*pos)
03096         {
03097           mediaAttribute = QString::fromLatin1(start, pos-start).stripWhiteSpace().lower();
03098           mediaValue = QString::fromLatin1(pos+1, end-pos-1).stripWhiteSpace();
03099       pos = end;
03100           if (mediaValue.length() &&
03101               (mediaValue[0] == '"') &&
03102               (mediaValue[mediaValue.length()-1] == '"'))
03103              mediaValue = mediaValue.mid(1, mediaValue.length()-2);
03104 
03105           kdDebug (7113) << "(" << m_pid << ") Media-Parameter Attribute: "
03106                          << mediaAttribute << endl;
03107           kdDebug (7113) << "(" << m_pid << ") Media-Parameter Value: "
03108                          << mediaValue << endl;
03109 
03110           if ( mediaAttribute == "charset")
03111           {
03112             mediaValue = mediaValue.lower();
03113             m_request.strCharset = mediaValue;
03114           }
03115           else
03116           {
03117             setMetaData("media-"+mediaAttribute, mediaValue);
03118           }
03119         }
03120       }
03121     }
03122 
03123     // Date
03124     else if (strncasecmp(buf, "Date:", 5) == 0) {
03125       dateHeader = KRFCDate::parseDate(trimLead(buf+5));
03126     }
03127 
03128     // Cache management
03129     else if (strncasecmp(buf, "ETag:", 5) == 0) {
03130       m_request.etag = trimLead(buf+5);
03131     }
03132 
03133     // Cache management
03134     else if (strncasecmp(buf, "Expires:", 8) == 0) {
03135       expireDate = KRFCDate::parseDate(trimLead(buf+8));
03136       if (!expireDate)
03137         expireDate = 1; // Already expired
03138     }
03139 
03140     // Cache management
03141     else if (strncasecmp(buf, "Last-Modified:", 14) == 0) {
03142       m_request.lastModified = (QString::fromLatin1(trimLead(buf+14))).stripWhiteSpace();
03143     }
03144 
03145     // whoops.. we received a warning
03146     else if (strncasecmp(buf, "Warning:", 8) == 0) {
03147       //Don't use warning() here, no need to bother the user.
03148       //Those warnings are mostly about caches.
03149       infoMessage(trimLead(buf + 8));
03150     }
03151 
03152     // Cache management (HTTP 1.0)
03153     else if (strncasecmp(buf, "Pragma:", 7) == 0) {
03154       QCString pragma = QCString(trimLead(buf+7)).stripWhiteSpace().lower();
03155       if (pragma == "no-cache")
03156       {
03157          m_request.bCachedWrite = false; // Don't put in cache
03158          mayCache = false;
03159          hasCacheDirective = true;
03160       }
03161     }
03162 
03163     // The deprecated Refresh Response
03164     else if (strncasecmp(buf,"Refresh:", 8) == 0) {
03165       mayCache = false;  // Do not cache page as it defeats purpose of Refresh tag!
03166       setMetaData( "http-refresh", QString::fromLatin1(trimLead(buf+8)).stripWhiteSpace() );
03167     }
03168 
03169     // In fact we should do redirection only if we got redirection code
03170     else if (strncasecmp(buf, "Location:", 9) == 0) {
03171       // Redirect only for 3xx status code, will ya! Thanks, pal!
03172       if ( m_responseCode > 299 && m_responseCode < 400 )
03173         locationStr = QCString(trimLead(buf+9)).stripWhiteSpace();
03174     }
03175 
03176     // Check for cookies
03177     else if (strncasecmp(buf, "Set-Cookie", 10) == 0) {
03178       cookieStr += buf;
03179       cookieStr += '\n';
03180     }
03181 
03182     // check for direct authentication
03183     else if (strncasecmp(buf, "WWW-Authenticate:", 17) == 0) {
03184       configAuth(trimLead(buf + 17), false);
03185     }
03186 
03187     // check for proxy-based authentication
03188     else if (strncasecmp(buf, "Proxy-Authenticate:", 19) == 0) {
03189       configAuth(trimLead(buf + 19), true);
03190     }
03191 
03192     else if (strncasecmp(buf, "Upgrade:", 8) == 0) {
03193        // Now we have to check to see what is offered for the upgrade
03194        QString offered = &(buf[8]);
03195        upgradeOffers = QStringList::split(QRegExp("[ \n,\r\t]"), offered);
03196     }
03197 
03198     // content?
03199     else if (strncasecmp(buf, "Content-Encoding:", 17) == 0) {
03200       // This is so wrong !!  No wonder kio_http is stripping the
03201       // gzip encoding from downloaded files.  This solves multiple
03202       // bug reports and caitoo's problem with downloads when such a
03203       // header is encountered...
03204 
03205       // A quote from RFC 2616:
03206       // " When present, its (Content-Encoding) value indicates what additional
03207       // content have been applied to the entity body, and thus what decoding
03208       // mechanism must be applied to obtain the media-type referenced by the
03209       // Content-Type header field.  Content-Encoding is primarily used to allow
03210       // a document to be compressed without loosing the identity of its underlying
03211       // media type.  Simply put if it is specified, this is the actual mime-type
03212       // we should use when we pull the resource !!!
03213       addEncoding(trimLead(buf + 17), m_qContentEncodings);
03214     }
03215     // Refer to RFC 2616 sec 15.5/19.5.1 and RFC 2183
03216     else if(strncasecmp(buf, "Content-Disposition:", 20) == 0) {
03217       char* dispositionBuf = trimLead(buf + 20);
03218       while ( *dispositionBuf )
03219       {
03220         if ( strncasecmp( dispositionBuf, "filename", 8 ) == 0 )
03221         {
03222           dispositionBuf += 8;
03223 
03224           while ( *dispositionBuf == ' ' || *dispositionBuf == '=' )
03225             dispositionBuf++;
03226 
03227           char* bufStart = dispositionBuf;
03228 
03229           while ( *dispositionBuf && *dispositionBuf != ';' )
03230             dispositionBuf++;
03231 
03232           if ( dispositionBuf > bufStart )
03233           {
03234             // Skip any leading quotes...
03235             while ( *bufStart == '"' )
03236               bufStart++;
03237 
03238             // Skip any trailing quotes as well as white spaces...
03239             while ( *(dispositionBuf-1) == ' ' || *(dispositionBuf-1) == '"')
03240               dispositionBuf--;
03241 
03242             if ( dispositionBuf > bufStart )
03243               disposition = QString::fromLatin1( bufStart, dispositionBuf-bufStart );
03244 
03245             break;
03246           }
03247         }
03248         else
03249         {
03250           char *bufStart = dispositionBuf;
03251 
03252           while ( *dispositionBuf && *dispositionBuf != ';' )
03253             dispositionBuf++;
03254 
03255           if ( dispositionBuf > bufStart )
03256             disposition = QString::fromLatin1( bufStart, dispositionBuf-bufStart ).stripWhiteSpace();
03257 
03258           while ( *dispositionBuf == ';' || *dispositionBuf == ' ' )
03259             dispositionBuf++;
03260         }
03261       }
03262 
03263       // Content-Dispostion is not allowed to dictate directory
03264       // path, thus we extract the filename only.
03265       if ( !disposition.isEmpty() )
03266       {
03267         int pos = disposition.findRev( '/' );
03268 
03269         if( pos > -1 )
03270           disposition = disposition.mid(pos+1);
03271 
03272         kdDebug(7113) << "(" << m_pid << ") Content-Disposition: "
03273                       << disposition<< endl;
03274       }
03275     }
03276     else if (strncasecmp(buf, "Proxy-Connection:", 17) == 0)
03277     {
03278       if (strncasecmp(trimLead(buf + 17), "Close", 5) == 0)
03279         m_bKeepAlive = false;
03280       else if (strncasecmp(trimLead(buf + 17), "Keep-Alive", 10)==0)
03281         m_bKeepAlive = true;
03282     }
03283     else if (strncasecmp(buf, "Link:", 5) == 0) {
03284       // We only support Link: <url>; rel="type"   so far
03285       QStringList link = QStringList::split(";", QString(buf)
03286                                                  .replace(QRegExp("^Link:[ ]*"),
03287                                                           ""));
03288       if (link.count() == 2) {
03289         QString rel = link[1].stripWhiteSpace();
03290         if (rel.startsWith("rel=\"")) {
03291           rel = rel.mid(5, rel.length() - 6);
03292           if (rel.lower() == "pageservices") {
03293             QString url = link[0].replace(QRegExp("[<>]"),"").stripWhiteSpace();
03294             setMetaData("PageServices", url);
03295           }
03296         }
03297       }
03298     }
03299     else if (strncasecmp(buf, "P3P:", 4) == 0) {
03300       QString p3pstr = buf;
03301       p3pstr = p3pstr.mid(4).simplifyWhiteSpace();
03302       QStringList policyrefs, compact;
03303       QStringList policyfields = QStringList::split(QRegExp(",[ ]*"), p3pstr);
03304       for (QStringList::Iterator it = policyfields.begin();
03305                                   it != policyfields.end();
03306                                                       ++it) {
03307          QStringList policy = QStringList::split("=", *it);
03308 
03309          if (policy.count() == 2) {
03310             if (policy[0].lower() == "policyref") {
03311                policyrefs << policy[1].replace(QRegExp("[\"\']"), "")
03312                                       .stripWhiteSpace();
03313             } else if (policy[0].lower() == "cp") {
03314                // We convert to cp\ncp\ncp\n[...]\ncp to be consistent with
03315                // other metadata sent in strings.  This could be a bit more
03316                // efficient but I'm going for correctness right now.
03317                QStringList cps = QStringList::split(" ",
03318                                         policy[1].replace(QRegExp("[\"\']"), "")
03319                                                  .simplifyWhiteSpace());
03320 
03321                for (QStringList::Iterator j = cps.begin(); j != cps.end(); ++j)
03322                  compact << *j;
03323             }
03324          }
03325       }
03326 
03327       if (!policyrefs.isEmpty())
03328          setMetaData("PrivacyPolicy", policyrefs.join("\n"));
03329 
03330       if (!compact.isEmpty())
03331          setMetaData("PrivacyCompactPolicy", compact.join("\n"));
03332     }
03333 
03334     // continue only if we know that we're HTTP/1.1
03335     else if (httpRev == HTTP_11) {
03336       // let them tell us if we should stay alive or not
03337       if (strncasecmp(buf, "Connection:", 11) == 0)
03338       {
03339         if (strncasecmp(trimLead(buf + 11), "Close", 5) == 0)
03340           m_bKeepAlive = false;
03341         else if (strncasecmp(trimLead(buf + 11), "Keep-Alive", 10)==0)
03342           m_bKeepAlive = true;
03343         else if (strncasecmp(trimLead(buf + 11), "Upgrade", 7)==0)
03344         {
03345           if (m_responseCode == 101) {
03346             // Ok, an upgrade was accepted, now we must do it
03347             upgradeRequired = true;
03348           } else if (upgradeRequired) {  // 426
03349             // Nothing to do since we did it above already
03350           } else {
03351             // Just an offer to upgrade - no need to take it
03352             canUpgrade = true;
03353           }
03354         }
03355 
03356       }
03357       // what kind of encoding do we have?  transfer?
03358       else if (strncasecmp(buf, "Transfer-Encoding:", 18) == 0) {
03359         // If multiple encodings have been applied to an entity, the
03360         // transfer-codings MUST be listed in the order in which they
03361         // were applied.
03362         addEncoding(trimLead(buf + 18), m_qTransferEncodings);
03363       }
03364 
03365       // md5 signature
03366       else if (strncasecmp(buf, "Content-MD5:", 12) == 0) {
03367         m_sContentMD5 = QString::fromLatin1(trimLead(buf + 12));
03368       }
03369 
03370       // *** Responses to the HTTP OPTIONS method follow
03371       // WebDAV capabilities
03372       else if (strncasecmp(buf, "DAV:", 4) == 0) {
03373         if (m_davCapabilities.isEmpty()) {
03374           m_davCapabilities << QString::fromLatin1(trimLead(buf + 4));
03375         }
03376         else {
03377           m_davCapabilities << QString::fromLatin1(trimLead(buf + 4));
03378         }
03379       }
03380       // *** Responses to the HTTP OPTIONS method finished
03381     }
03382     else if ((httpRev == HTTP_None) && (strlen(buf) != 0))
03383     {
03384       // Remote server does not seem to speak HTTP at all
03385       // Put the crap back into the buffer and hope for the best
03386       rewind();
03387       if (m_responseCode)
03388         m_prevResponseCode = m_responseCode;
03389 
03390       m_responseCode = 200; // Fake it
03391       httpRev = HTTP_Unknown;
03392       m_bKeepAlive = false;
03393       break;
03394     }
03395     setRewindMarker();
03396 
03397     // Clear out our buffer for further use.
03398     memset(buffer, 0, sizeof(buffer));
03399 
03400   } while (!m_bEOF && (len || noHeader) && (headerSize < maxHeaderSize) && (gets(buffer, sizeof(buffer)-1)));
03401 
03402 
03403   // Now process the HTTP/1.1 upgrade
03404   QStringList::Iterator opt = upgradeOffers.begin();
03405   for( ; opt != upgradeOffers.end(); ++opt) {
03406      if (*opt == "TLS/1.0") {
03407         if(upgradeRequired) {
03408            if (!startTLS() && !usingTLS()) {
03409               error(ERR_UPGRADE_REQUIRED, *opt);
03410               return false;
03411            }
03412         }
03413      } else if (*opt == "HTTP/1.1") {
03414         httpRev = HTTP_11;
03415      } else {
03416         // unknown
03417         if (upgradeRequired) {
03418            error(ERR_UPGRADE_REQUIRED, *opt);
03419            return false;
03420         }
03421      }
03422   }
03423 
03424   setMetaData("charset", m_request.strCharset);
03425 
03426   // If we do not support the requested authentication method...
03427   if ( (m_responseCode == 401 && Authentication == AUTH_None) ||
03428        (m_responseCode == 407 && ProxyAuthentication == AUTH_None) )
03429   {
03430     m_bUnauthorized = false;
03431     if (m_request.bErrorPage)
03432       errorPage();
03433     else
03434     {
03435       error( ERR_UNSUPPORTED_ACTION, "Unknown Authorization method!" );
03436       return false;
03437     }
03438   }
03439 
03440   // Fixup expire date for clock drift.
03441   if (expireDate && (expireDate <= dateHeader))
03442     expireDate = 1; // Already expired.
03443 
03444   // Convert max-age into expireDate (overriding previous set expireDate)
03445   if (maxAge == 0)
03446     expireDate = 1; // Already expired.
03447   else if (maxAge > 0)
03448   {
03449     if (currentAge)
03450       maxAge -= currentAge;
03451     if (maxAge <=0)
03452       maxAge = 0;
03453     expireDate = time(0) + maxAge;
03454   }
03455 
03456   if (!expireDate)
03457   {
03458     time_t lastModifiedDate = 0;
03459     if (!m_request.lastModified.isEmpty())
03460        lastModifiedDate = KRFCDate::parseDate(m_request.lastModified);
03461 
03462     if (lastModifiedDate)
03463     {
03464        long diff = static_cast<long>(difftime(dateHeader, lastModifiedDate));
03465        if (diff < 0)
03466           expireDate = time(0) + 1;
03467        else
03468           expireDate = time(0) + (diff / 10);
03469     }
03470     else
03471     {
03472        expireDate = time(0) + DEFAULT_CACHE_EXPIRE;
03473     }
03474   }
03475 
03476   // DONE receiving the header!
03477   if (!cookieStr.isEmpty())
03478   {
03479     if ((m_request.cookieMode == HTTPRequest::CookiesAuto) && m_request.bUseCookiejar)
03480     {
03481       // Give cookies to the cookiejar.
03482       QString domain = config()->readEntry("cross-domain");
03483       if (!domain.isEmpty() && isCrossDomainRequest(m_request.url.host(), domain))
03484          cookieStr = "Cross-Domain\n" + cookieStr;
03485       addCookies( m_request.url.url(), cookieStr );
03486     }
03487     else if (m_request.cookieMode == HTTPRequest::CookiesManual)
03488     {
03489       // Pass cookie to application
03490       setMetaData("setcookies", cookieStr);
03491     }
03492   }
03493 
03494   if (m_request.bMustRevalidate)
03495   {
03496     m_request.bMustRevalidate = false; // Reset just in case.
03497     if (cacheValidated)
03498     {
03499       // Yippie, we can use the cached version.
03500       // Update the cache with new "Expire" headers.
03501       fclose(m_request.fcache);
03502       m_request.fcache = 0;
03503       updateExpireDate( expireDate, true );
03504       m_request.fcache = checkCacheEntry( ); // Re-read cache entry
03505 
03506       if (m_request.fcache)
03507       {
03508           m_request.bCachedRead = true;
03509           goto try_again; // Read header again, but now from cache.
03510        }
03511        else
03512        {
03513           // Where did our cache entry go???
03514        }
03515      }
03516      else
03517      {
03518        // Validation failed. Close cache.
03519        fclose(m_request.fcache);
03520        m_request.fcache = 0;
03521      }
03522   }
03523 
03524   // We need to reread the header if we got a '100 Continue' or '102 Processing'
03525   if ( cont )
03526   {
03527     goto try_again;
03528   }
03529 
03530   // Do not do a keep-alive connection if the size of the
03531   // response is not known and the response is not Chunked.
03532   if (!m_bChunked && (m_iSize == NO_SIZE))
03533     m_bKeepAlive = false;
03534 
03535   if ( m_responseCode == 204 )
03536   {
03537     return true;
03538   }
03539 
03540   // We need to try to login again if we failed earlier
03541   if ( m_bUnauthorized )
03542   {
03543     if ( (m_responseCode == 401) ||
03544          (m_bUseProxy && (m_responseCode == 407))
03545        )
03546     {
03547         if ( getAuthorization() )
03548         {
03549            // for NTLM Authentication we have to keep the connection open!
03550            if ( Authentication == AUTH_NTLM && m_strAuthorization.length() > 4 )
03551            {
03552              m_bKeepAlive = true;
03553              readBody( true );
03554            }
03555            else if (ProxyAuthentication == AUTH_NTLM && m_strProxyAuthorization.length() > 4)
03556            {
03557             readBody( true );
03558            }
03559            else
03560              httpCloseConnection();
03561            return false; // Try again.
03562         }
03563 
03564         if (m_bError)
03565            return false; // Error out
03566 
03567         // Show error page...
03568     }
03569     m_bUnauthorized = false;
03570   }
03571 
03572   // We need to do a redirect
03573   if (!locationStr.isEmpty())
03574   {
03575     KURL u(m_request.url, locationStr);
03576     if(!u.isValid())
03577     {
03578       error(ERR_MALFORMED_URL, u.url());
03579       return false;
03580     }
03581     if ((u.protocol() != "http") && (u.protocol() != "https") &&
03582        (u.protocol() != "ftp") && (u.protocol() != "webdav") &&
03583        (u.protocol() != "webdavs"))
03584     {
03585       redirection(u);
03586       error(ERR_ACCESS_DENIED, u.url());
03587       return false;
03588     }
03589     m_bRedirect = true;
03590     m_redirectLocation = u;
03591 
03592     if (!m_request.id.isEmpty())
03593     {
03594        sendMetaData();
03595     }
03596 
03597     kdDebug(7113) << "(" << m_pid << ") request.url: " << m_request.url.url()
03598                   << endl << "LocationStr: " << locationStr.data() << endl;
03599 
03600     kdDebug(7113) << "(" << m_pid << ") Requesting redirection to: " << u.url()
03601                   << endl;
03602 
03603     // If we're redirected to a http:// url, remember that we're doing webdav...
03604     if (m_protocol == "webdav" || m_protocol == "webdavs")
03605       u.setProtocol(m_protocol);
03606 
03607     redirection(u);
03608     m_request.bCachedWrite = false; // Turn off caching on re-direction (DA)
03609     mayCache = false;
03610   }
03611 
03612   // Inform the job that we can indeed resume...
03613   if ( bCanResume && m_request.offset )
03614     canResume();
03615   else
03616     m_request.offset = 0;
03617 
03618   // We don't cache certain text objects
03619   if (m_strMimeType.startsWith("text/") &&
03620       (m_strMimeType != "text/css") &&
03621       (m_strMimeType != "text/x-javascript") &&
03622       !hasCacheDirective)
03623   {
03624      // Do not cache secure pages or pages
03625      // originating from password protected sites
03626      // unless the webserver explicitly allows it.
03627      if ( m_bIsSSL || (Authentication != AUTH_None) )
03628      {
03629         m_request.bCachedWrite = false;
03630         mayCache = false;
03631      }
03632   }
03633 
03634   // WABA: Correct for tgz files with a gzip-encoding.
03635   // They really shouldn't put gzip in the Content-Encoding field!
03636   // Web-servers really shouldn't do this: They let Content-Size refer
03637   // to the size of the tgz file, not to the size of the tar file,
03638   // while the Content-Type refers to "tar" instead of "tgz".
03639   if (m_qContentEncodings.last() == "gzip")
03640   {
03641      if (m_strMimeType == "application/x-tar")
03642      {
03643         m_qContentEncodings.remove(m_qContentEncodings.fromLast());
03644         m_strMimeType = QString::fromLatin1("application/x-tgz");
03645      }
03646      else if (m_strMimeType == "application/postscript")
03647      {
03648         // LEONB: Adding another exception for psgz files.
03649         // Could we use the mimelnk files instead of hardcoding all this?
03650         m_qContentEncodings.remove(m_qContentEncodings.fromLast());
03651         m_strMimeType = QString::fromLatin1("application/x-gzpostscript");
03652      }
03653      else if ( (m_request.allowCompressedPage &&
03654                 m_strMimeType == "text/html")
03655                 ||
03656                (m_request.allowCompressedPage &&
03657                 m_strMimeType != "application/x-tgz" &&
03658                 m_strMimeType != "application/x-targz" &&
03659                 m_strMimeType != "application/x-gzip" &&
03660                 m_request.url.path().right(3) != ".gz")
03661                 )
03662      {
03663         // Unzip!
03664      }
03665      else
03666      {
03667         m_qContentEncodings.remove(m_qContentEncodings.fromLast());
03668         m_strMimeType = QString::fromLatin1("application/x-gzip");
03669      }
03670   }
03671 
03672   // We can't handle "bzip2" encoding (yet). So if we get something with
03673   // bzip2 encoding, we change the mimetype to "application/x-bzip2".
03674   // Note for future changes: some web-servers send both "bzip2" as
03675   //   encoding and "application/x-bzip2" as mimetype. That is wrong.
03676   //   currently that doesn't bother us, because we remove the encoding
03677   //   and set the mimetype to x-bzip2 anyway.
03678   if (m_qContentEncodings.last() == "bzip2")
03679   {
03680      m_qContentEncodings.remove(m_qContentEncodings.fromLast());
03681      m_strMimeType = QString::fromLatin1("application/x-bzip2");
03682   }
03683 
03684   // Convert some common mimetypes to standard KDE mimetypes
03685   if (m_strMimeType == "application/x-targz")
03686      m_strMimeType = QString::fromLatin1("application/x-tgz");
03687   else if (m_strMimeType == "application/zip")
03688      m_strMimeType = QString::fromLatin1("application/x-zip");
03689   else if (m_strMimeType == "image/x-png")
03690      m_strMimeType = QString::fromLatin1("image/png");
03691   else if (m_strMimeType == "image/bmp")
03692      m_strMimeType = QString::fromLatin1("image/x-bmp");
03693   else if (m_strMimeType == "audio/mpeg" || m_strMimeType == "audio/x-mpeg" || m_strMimeType == "audio/mp3")
03694      m_strMimeType = QString::fromLatin1("audio/x-mp3");
03695   else if (m_strMimeType == "audio/microsoft-wave")
03696      m_strMimeType = QString::fromLatin1("audio/x-wav");
03697   else if (m_strMimeType == "audio/midi")
03698      m_strMimeType = QString::fromLatin1("audio/x-midi");
03699   else if (m_strMimeType == "image/x-xpixmap")
03700      m_strMimeType = QString::fromLatin1("image/x-xpm");
03701   else if (m_strMimeType == "application/rtf")
03702      m_strMimeType = QString::fromLatin1("text/rtf");
03703 
03704   // Crypto ones....
03705   else if (m_strMimeType == "application/pkix-cert" ||
03706            m_strMimeType == "application/binary-certificate")
03707   {
03708      m_strMimeType = QString::fromLatin1("application/x-x509-ca-cert");
03709   }
03710 
03711   // Prefer application/x-tgz or x-gzpostscript over application/x-gzip.
03712   else if (m_strMimeType == "application/x-gzip")
03713   {
03714      if ((m_request.url.path().right(7) == ".tar.gz") ||
03715          (m_request.url.path().right(4) == ".tar"))
03716         m_strMimeType = QString::fromLatin1("application/x-tgz");
03717      if ((m_request.url.path().right(6) == ".ps.gz"))
03718         m_strMimeType = QString::fromLatin1("application/x-gzpostscript");
03719   }
03720 
03721   // Some webservers say "text/plain" when they mean "application/x-bzip2"
03722   else if ((m_strMimeType == "text/plain") || (m_strMimeType == "application/octet-stream"))
03723   {
03724      QString ext = m_request.url.path().right(4).upper();
03725      if (ext == ".BZ2")
03726         m_strMimeType = QString::fromLatin1("application/x-bzip2");
03727      else if (ext == ".PEM")
03728         m_strMimeType = QString::fromLatin1("application/x-x509-ca-cert");
03729      else if (ext == ".SWF")
03730         m_strMimeType = QString::fromLatin1("application/x-shockwave-flash");
03731      else if (ext == ".PLS")
03732         m_strMimeType = QString::fromLatin1("audio/x-scpls");
03733      else if (ext == ".WMV")
03734         m_strMimeType = QString::fromLatin1("video/x-ms-wmv");
03735   }
03736 
03737 #if 0
03738   // Even if we can't rely on content-length, it seems that we should
03739   // never get more data than content-length. Maybe less, if the
03740   // content-length refers to the unzipped data.
03741   if (!m_qContentEncodings.isEmpty())
03742   {
03743      // If we still have content encoding we can't rely on the Content-Length.
03744      m_iSize = NO_SIZE;
03745   }
03746 #endif
03747 
03748   if( !disposition.isEmpty() )
03749   {
03750     kdDebug(7113) << "(" << m_pid << ") Setting Content-Disposition metadata to: "
03751                   << disposition << endl;
03752     setMetaData("content-disposition", disposition);
03753   }
03754 
03755   if (!m_request.lastModified.isEmpty())
03756     setMetaData("modified", m_request.lastModified);
03757 
03758   if (!mayCache)
03759   {
03760     setMetaData("no-cache", "true");
03761     setMetaData("expire-date", "1"); // Expired
03762   }
03763   else
03764   {
03765     QString tmp;
03766     tmp.setNum(expireDate);
03767     setMetaData("expire-date", tmp);
03768     tmp.setNum(time(0)); // Cache entry will be created shortly.
03769     setMetaData("cache-creation-date", tmp);
03770   }
03771 
03772   // Let the app know about the mime-type iff this is not
03773   // a redirection and the mime-type string is not empty.
03774   if (locationStr.isEmpty() && (!m_strMimeType.isEmpty() ||
03775       m_request.method == HTTP_HEAD))
03776   {
03777     kdDebug(7113) << "(" << m_pid << ") Emitting mimetype " << m_strMimeType << endl;
03778     mimeType( m_strMimeType );
03779   }
03780 
03781   forwardHttpResponseHeader();
03782 
03783   if (m_request.method == HTTP_HEAD)
03784      return true;
03785 
03786   // Do we want to cache this request?
03787   if (m_request.bUseCache)
03788   {
03789      ::unlink( QFile::encodeName(m_request.cef));
03790      if ( m_request.bCachedWrite && !m_strMimeType.isEmpty() )
03791      {
03792         // Check...
03793         createCacheEntry(m_strMimeType, expireDate); // Create a cache entry
03794         if (!m_request.fcache)
03795         {
03796         m_request.bCachedWrite = false; // Error creating cache entry.
03797         kdDebug(7113) << "(" << m_pid << ") Error creating cache entry for " << m_request.url.url()<<"!\n";
03798         }
03799         m_request.expireDate = expireDate;
03800         m_maxCacheSize = config()->readNumEntry("MaxCacheSize", DEFAULT_MAX_CACHE_SIZE) / 2;
03801      }
03802   }
03803 
03804   if (m_request.bCachedWrite && !m_strMimeType.isEmpty())
03805     kdDebug(7113) << "(" << m_pid << ") Cache, adding \"" << m_request.url.url() << "\"" << endl;
03806   else if (m_request.bCachedWrite && m_strMimeType.isEmpty())
03807     kdDebug(7113) << "(" << m_pid << ") Cache, pending \"" << m_request.url.url() << "\"" << endl;
03808   else
03809     kdDebug(7113) << "(" << m_pid << ") Cache, not adding \"" << m_request.url.url() << "\"" << endl;
03810   return true;
03811 }
03812 
03813 
03814 void HTTPProtocol::addEncoding(QString encoding, QStringList &encs)
03815 {
03816   encoding = encoding.stripWhiteSpace().lower();
03817   // Identity is the same as no encoding
03818   if (encoding == "identity") {
03819     return;
03820   } else if (encoding == "8bit") {
03821     // Strange encoding returned by http://linac.ikp.physik.tu-darmstadt.de
03822     return;
03823   } else if (encoding == "chunked") {
03824     m_bChunked = true;
03825     // Anyone know of a better way to handle unknown sizes possibly/ideally with unsigned ints?
03826     //if ( m_cmd != CMD_COPY )
03827       m_iSize = NO_SIZE;
03828   } else if ((encoding == "x-gzip") || (encoding == "gzip")) {
03829     encs.append(QString::fromLatin1("gzip"));
03830   } else if ((encoding == "x-bzip2") || (encoding == "bzip2")) {
03831     encs.append(QString::fromLatin1("bzip2")); // Not yet supported!
03832   } else if ((encoding == "x-deflate") || (encoding == "deflate")) {
03833     encs.append(QString::fromLatin1("deflate"));
03834   } else {
03835     kdDebug(7113) << "(" << m_pid << ") Unknown encoding encountered.  "
03836                     << "Please write code. Encoding = \"" << encoding
03837                     << "\"" << endl;
03838   }
03839 }
03840 
03841 bool HTTPProtocol::sendBody()
03842 {
03843   int result=-1;
03844   int length=0;
03845 
03846   infoMessage( i18n( "Requesting data to send" ) );
03847 
03848   // m_bufPOST will NOT be empty iff authentication was required before posting
03849   // the data OR a re-connect is requested from ::readHeader because the
03850   // connection was lost for some reason.
03851   if ( !m_bufPOST.isNull() )
03852   {
03853     kdDebug(7113) << "(" << m_pid << ") POST'ing saved data..." << endl;
03854 
03855     result = 0;
03856     length = m_bufPOST.size();
03857   }
03858   else
03859   {
03860     kdDebug(7113) << "(" << m_pid << ") POST'ing live data..." << endl;
03861 
03862     QByteArray buffer;
03863     int old_size;
03864 
03865     m_bufPOST.resize(0);
03866     do
03867     {
03868       dataReq(); // Request for data
03869       result = readData( buffer );
03870       if ( result > 0 )
03871       {
03872         length += result;
03873         old_size = m_bufPOST.size();
03874         m_bufPOST.resize( old_size+result );
03875         memcpy( m_bufPOST.data()+ old_size, buffer.data(), buffer.size() );
03876         buffer.resize(0);
03877       }
03878     } while ( result > 0 );
03879   }
03880 
03881   if ( result < 0 )
03882   {
03883     error( ERR_ABORTED, m_request.hostname );
03884     return false;
03885   }
03886 
03887   infoMessage( i18n( "Sending data to %1" ).arg( m_request.hostname ) );
03888 
03889   QString size = QString ("Content-Length: %1\r\n\r\n").arg(length);
03890   kdDebug( 7113 ) << "(" << m_pid << ")" << size << endl;
03891 
03892   // Send the content length...
03893   bool sendOk = (write(size.latin1(), size.length()) == (ssize_t) size.length());
03894   if (!sendOk)
03895   {
03896     kdDebug( 7113 ) << "(" << m_pid << ") Connection broken when sending "
03897                     << "content length: (" << m_state.hostname << ")" << endl;
03898     error( ERR_CONNECTION_BROKEN, m_state.hostname );
03899     return false;
03900   }
03901 
03902   // Send the data...
03903   // kdDebug( 7113 ) << "(" << m_pid << ") POST DATA: " << QCString(m_bufPOST) << endl;
03904   sendOk = (write(m_bufPOST.data(), m_bufPOST.size()) == (ssize_t) m_bufPOST.size());
03905   if (!sendOk)
03906   {
03907     kdDebug(7113) << "(" << m_pid << ") Connection broken when sending message body: ("
03908                   << m_state.hostname << ")" << endl;
03909     error( ERR_CONNECTION_BROKEN, m_state.hostname );
03910     return false;
03911   }
03912 
03913   return true;
03914 }
03915 
03916 void HTTPProtocol::httpClose( bool keepAlive )
03917 {
03918   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpClose" << endl;
03919 
03920   if (m_request.fcache)
03921   {
03922      fclose(m_request.fcache);
03923      m_request.fcache = 0;
03924      if (m_request.bCachedWrite)
03925      {
03926         QString filename = m_request.cef + ".new";
03927         ::unlink( QFile::encodeName(filename) );
03928      }
03929   }
03930 
03931   // Only allow persistent connections for GET requests.
03932   // NOTE: we might even want to narrow this down to non-form
03933   // based submit requests which will require a meta-data from
03934   // khtml.
03935   if (keepAlive && (!m_bUseProxy ||
03936       m_bPersistentProxyConnection || m_bIsTunneled))
03937   {
03938     if (!m_keepAliveTimeout)
03939        m_keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT;
03940     else if (m_keepAliveTimeout > 2*DEFAULT_KEEP_ALIVE_TIMEOUT)
03941        m_keepAliveTimeout = 2*DEFAULT_KEEP_ALIVE_TIMEOUT;
03942 
03943     kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpClose: keep alive (" << m_keepAliveTimeout << ")" << endl;
03944     QByteArray data;
03945     QDataStream stream( data, IO_WriteOnly );
03946     stream << int(99); // special: Close connection
03947     setTimeoutSpecialCommand(m_keepAliveTimeout, data);
03948     return;
03949   }
03950 
03951   httpCloseConnection();
03952 }
03953 
03954 void HTTPProtocol::closeConnection()
03955 {
03956   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::closeConnection" << endl;
03957   httpCloseConnection ();
03958 }
03959 
03960 void HTTPProtocol::httpCloseConnection ()
03961 {
03962   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpCloseConnection" << endl;
03963   m_bIsTunneled = false;
03964   m_bKeepAlive = false;
03965   closeDescriptor();
03966   setTimeoutSpecialCommand(-1); // Cancel any connection timeout
03967 }
03968 
03969 void HTTPProtocol::slave_status()
03970 {
03971   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::slave_status" << endl;
03972 
03973   if ( m_iSock != -1 && !isConnectionValid() )
03974      httpCloseConnection();
03975 
03976   slaveStatus( m_state.hostname, (m_iSock != -1) );
03977 }
03978 
03979 void HTTPProtocol::mimetype( const KURL& url )
03980 {
03981   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::mimetype: "
03982                 << url.prettyURL() << endl;
03983 
03984   if ( !checkRequestURL( url ) )
03985     return;
03986 
03987   m_request.method = HTTP_HEAD;
03988   m_request.path = url.path();
03989   m_request.query = url.query();
03990   m_request.cache = CC_Cache;
03991   m_request.doProxy = m_bUseProxy;
03992 
03993   retrieveHeader();
03994 
03995   kdDebug(7113) << "(" << m_pid << ") http: mimetype = " << m_strMimeType
03996                 << endl;
03997 }
03998 
03999 void HTTPProtocol::special( const QByteArray &data )
04000 {
04001   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::special" << endl;
04002 
04003   int tmp;
04004   QDataStream stream(data, IO_ReadOnly);
04005 
04006   stream >> tmp;
04007   switch (tmp) {
04008     case 1: // HTTP POST
04009     {
04010       KURL url;
04011       stream >> url;
04012       post( url );
04013       break;
04014     }
04015     case 2: // cache_update
04016     {
04017       KURL url;
04018       bool no_cache;
04019       time_t expireDate;
04020       stream >> url >> no_cache >> expireDate;
04021       cacheUpdate( url, no_cache, expireDate );
04022       break;
04023     }
04024     case 5: // WebDAV lock
04025     {
04026       KURL url;
04027       QString scope, type, owner;
04028       stream >> url >> scope >> type >> owner;
04029       davLock( url, scope, type, owner );
04030       break;
04031     }
04032     case 6: // WebDAV unlock
04033     {
04034       KURL url;
04035       stream >> url;
04036       davUnlock( url );
04037       break;
04038     }
04039     case 7: // Generic WebDAV
04040     {
04041       KURL url;
04042       int method;
04043       stream >> url >> method;
04044       davGeneric( url, (KIO::HTTP_METHOD) method );
04045       break;
04046     }
04047     case 99: // Close Connection
04048     {
04049       httpCloseConnection();
04050       break;
04051     }
04052     default:
04053       // Some command we don't understand.
04054       // Just ignore it, it may come from some future version of KDE.
04055       break;
04056   }
04057 }
04058 
04062 int HTTPProtocol::readChunked()
04063 {
04064   if ((m_iBytesLeft == 0) || (m_iBytesLeft == NO_SIZE))
04065   {
04066      setRewindMarker();
04067 
04068      m_bufReceive.resize(4096);
04069 
04070      if (!gets(m_bufReceive.data(), m_bufReceive.size()-1))
04071      {
04072        kdDebug(7113) << "(" << m_pid << ") gets() failure on Chunk header" << endl;
04073        return -1;
04074      }
04075      // We could have got the CRLF of the previous chunk.
04076      // If so, try again.
04077      if (m_bufReceive[0] == '\0')
04078      {
04079         if (!gets(m_bufReceive.data(), m_bufReceive.size()-1))
04080         {
04081            kdDebug(7113) << "(" << m_pid << ") gets() failure on Chunk header" << endl;
04082            return -1;
04083         }
04084      }
04085      if (m_bEOF)
04086      {
04087         kdDebug(7113) << "(" << m_pid << ") EOF on Chunk header" << endl;
04088         return -1;
04089      }
04090 
04091      long long trunkSize = STRTOLL(m_bufReceive.data(), 0, 16);
04092      if (trunkSize < 0)
04093      {
04094         kdDebug(7113) << "(" << m_pid << ") Negative chunk size" << endl;
04095         return -1;
04096      }
04097      m_iBytesLeft = trunkSize;
04098 
04099      // kdDebug(7113) << "(" << m_pid << ") Chunk size = " << m_iBytesLeft << " bytes" << endl;
04100 
04101      if (m_iBytesLeft == 0)
04102      {
04103        // Last chunk.
04104        // Skip trailers.
04105        do {
04106          // Skip trailer of last chunk.
04107          if (!gets(m_bufReceive.data(), m_bufReceive.size()-1))
04108          {
04109            kdDebug(7113) << "(" << m_pid << ") gets() failure on Chunk trailer" << endl;
04110            return -1;
04111          }
04112          // kdDebug(7113) << "(" << m_pid << ") Chunk trailer = \"" << m_bufReceive.data() << "\"" << endl;
04113        }
04114        while (strlen(m_bufReceive.data()) != 0);
04115 
04116        return 0;
04117      }
04118   }
04119 
04120   int bytesReceived = readLimited();
04121   if (!m_iBytesLeft)
04122      m_iBytesLeft = NO_SIZE; // Don't stop, continue with next chunk
04123   return bytesReceived;
04124 }
04125 
04126 int HTTPProtocol::readLimited()
04127 {
04128   if (!m_iBytesLeft)
04129     return 0;
04130 
04131   m_bufReceive.resize(4096);
04132 
04133   int bytesReceived;
04134   int bytesToReceive;
04135 
04136   if (m_iBytesLeft > m_bufReceive.size())
04137      bytesToReceive = m_bufReceive.size();
04138   else
04139      bytesToReceive = m_iBytesLeft;
04140 
04141   bytesReceived = read(m_bufReceive.data(), bytesToReceive);
04142 
04143   if (bytesReceived <= 0)
04144      return -1; // Error: connection lost
04145 
04146   m_iBytesLeft -= bytesReceived;
04147   return bytesReceived;
04148 }
04149 
04150 int HTTPProtocol::readUnlimited()
04151 {
04152   if (m_bKeepAlive)
04153   {
04154      kdDebug(7113) << "(" << m_pid << ") Unbounded datastream on a Keep "
04155                      << "alive connection!" << endl;
04156      m_bKeepAlive = false;
04157   }
04158 
04159   m_bufReceive.resize(4096);
04160 
04161   int result = read(m_bufReceive.data(), m_bufReceive.size());
04162   if (result > 0)
04163      return result;
04164 
04165   m_bEOF = true;
04166   m_iBytesLeft = 0;
04167   return 0;
04168 }
04169 
04170 void HTTPProtocol::slotData(const QByteArray &_d)
04171 {
04172    if (!_d.size())
04173    {
04174       m_bEOD = true;
04175       return;
04176    }
04177 
04178    if (m_iContentLeft != NO_SIZE)
04179    {
04180       if (m_iContentLeft >= _d.size())
04181          m_iContentLeft -= _d.size();
04182       else
04183          m_iContentLeft = NO_SIZE;
04184    }
04185 
04186    QByteArray d = _d;
04187    if ( !m_dataInternal )
04188    {
04189       // If a broken server does not send the mime-type,
04190       // we try to id it from the content before dealing
04191       // with the content itself.
04192       if ( m_strMimeType.isEmpty() && !m_bRedirect &&
04193            !( m_responseCode >= 300 && m_responseCode <=399) )
04194       {
04195         kdDebug(7113) << "(" << m_pid << ") Determining mime-type from content..." << endl;
04196         int old_size = m_mimeTypeBuffer.size();
04197         m_mimeTypeBuffer.resize( old_size + d.size() );
04198         memcpy( m_mimeTypeBuffer.data() + old_size, d.data(), d.size() );
04199         if ( (m_iBytesLeft != NO_SIZE) && (m_iBytesLeft > 0)
04200              && (m_mimeTypeBuffer.size() < 1024) )
04201         {
04202           m_cpMimeBuffer = true;
04203           return;   // Do not send up the data since we do not yet know its mimetype!
04204         }
04205 
04206         kdDebug(7113) << "(" << m_pid << ") Mimetype buffer size: " << m_mimeTypeBuffer.size()
04207                       << endl;
04208 
04209         KMimeMagicResult *result;
04210         result = KMimeMagic::self()->findBufferFileType( m_mimeTypeBuffer,
04211                                                          m_request.url.fileName() );
04212         if( result )
04213         {
04214           m_strMimeType = result->mimeType();
04215           kdDebug(7113) << "(" << m_pid << ") Mimetype from content: "
04216                         << m_strMimeType << endl;
04217         }
04218 
04219         if ( m_strMimeType.isEmpty() )
04220         {
04221           m_strMimeType = QString::fromLatin1( DEFAULT_MIME_TYPE );
04222           kdDebug(7113) << "(" << m_pid << ") Using default mimetype: "
04223                         <<  m_strMimeType << endl;
04224         }
04225 
04226         if ( m_request.bCachedWrite )
04227         {
04228           createCacheEntry( m_strMimeType, m_request.expireDate );
04229           if (!m_request.fcache)
04230             m_request.bCachedWrite = false;
04231         }
04232 
04233         if ( m_cpMimeBuffer )
04234         {
04235           d.resize(0);
04236           d.resize(m_mimeTypeBuffer.size());
04237           memcpy( d.data(), m_mimeTypeBuffer.data(),
04238                   d.size() );
04239         }
04240         mimeType(m_strMimeType);
04241         m_mimeTypeBuffer.resize(0);
04242       }
04243 
04244       data( d );
04245       if (m_request.bCachedWrite && m_request.fcache)
04246          writeCacheEntry(d.data(), d.size());
04247    }
04248    else
04249    {
04250       uint old_size = m_bufWebDavData.size();
04251       m_bufWebDavData.resize (old_size + d.size());
04252       memcpy (m_bufWebDavData.data() + old_size, d.data(), d.size());
04253    }
04254 }
04255 
04265 bool HTTPProtocol::readBody( bool dataInternal /* = false */ )
04266 {
04267   if (m_responseCode == 204)
04268      return true;
04269 
04270   m_bEOD = false;
04271   // Note that when dataInternal is true, we are going to:
04272   // 1) save the body data to a member variable, m_bufWebDavData
04273   // 2) _not_ advertise the data, speed, size, etc., through the
04274   //    corresponding functions.
04275   // This is used for returning data to WebDAV.
04276   m_dataInternal = dataInternal;
04277   if ( dataInternal )
04278     m_bufWebDavData.resize (0);
04279 
04280   // Check if we need to decode the data.
04281   // If we are in copy mode, then use only transfer decoding.
04282   bool useMD5 = !m_sContentMD5.isEmpty();
04283 
04284   // Deal with the size of the file.
04285   KIO::filesize_t sz = m_request.offset;
04286   if ( sz )
04287     m_iSize += sz;
04288 
04289   // Update the application with total size except when
04290   // it is compressed, or when the data is to be handled
04291   // internally (webDAV).  If compressed we have to wait
04292   // until we uncompress to find out the actual data size
04293   if ( !dataInternal ) {
04294     if ( (m_iSize > 0) && (m_iSize != NO_SIZE)) {
04295        totalSize(m_iSize);
04296        infoMessage( i18n( "Retrieving %1 from %2...").arg(KIO::convertSize(m_iSize))
04297          .arg( m_request.hostname ) );
04298     }
04299     else
04300     {
04301        totalSize ( 0 );
04302     }
04303   }
04304   else
04305     infoMessage( i18n( "Retrieving from %1..." ).arg( m_request.hostname ) );
04306 
04307   if (m_request.bCachedRead)
04308   {
04309   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readBody: read data from cache!" << endl;
04310     m_request.bCachedWrite = false;
04311 
04312     char buffer[ MAX_IPC_SIZE ];
04313 
04314     m_iContentLeft = NO_SIZE;
04315 
04316     // Jippie! It's already in the cache :-)
04317     while (!feof(m_request.fcache) && !ferror(m_request.fcache))
04318     {
04319       int nbytes = fread( buffer, 1, MAX_IPC_SIZE, m_request.fcache);
04320 
04321       if (nbytes > 0)
04322       {
04323         m_bufReceive.setRawData( buffer, nbytes);
04324         slotData( m_bufReceive );
04325         m_bufReceive.resetRawData( buffer, nbytes );
04326         sz += nbytes;
04327       }
04328     }
04329 
04330     m_bufReceive.resize( 0 );
04331 
04332     if ( !dataInternal )
04333     {
04334       processedSize( sz );
04335       data( QByteArray() );
04336     }
04337 
04338     return true;
04339   }
04340 
04341 
04342   if (m_iSize != NO_SIZE)
04343     m_iBytesLeft = m_iSize - sz;
04344   else
04345     m_iBytesLeft = NO_SIZE;
04346 
04347   m_iContentLeft = m_iBytesLeft;
04348 
04349   if (m_bChunked)
04350     m_iBytesLeft = NO_SIZE;
04351 
04352   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readBody: retrieve data. "<<KIO::number(m_iBytesLeft)<<" left." << endl;
04353 
04354   // Main incoming loop...  Gather everything while we can...
04355   m_cpMimeBuffer = false;
04356   m_mimeTypeBuffer.resize(0);
04357   struct timeval last_tv;
04358   gettimeofday( &last_tv, 0L );
04359 
04360   HTTPFilterChain chain;
04361 
04362   QObject::connect(&chain, SIGNAL(output(const QByteArray &)),
04363           this, SLOT(slotData(const QByteArray &)));
04364   QObject::connect(&chain, SIGNAL(error(int, const QString &)),
04365           this, SLOT(error(int, const QString &)));
04366 
04367    // decode all of the transfer encodings
04368   while (!m_qTransferEncodings.isEmpty())
04369   {
04370     QString enc = m_qTransferEncodings.last();
04371     m_qTransferEncodings.remove(m_qTransferEncodings.fromLast());
04372     if ( enc == "gzip" )
04373       chain.addFilter(new HTTPFilterGZip);
04374     else if ( enc == "deflate" )
04375       chain.addFilter(new HTTPFilterDeflate);
04376   }
04377 
04378   // From HTTP 1.1 Draft 6:
04379   // The MD5 digest is computed based on the content of the entity-body,
04380   // including any content-coding that has been applied, but not including
04381   // any transfer-encoding applied to the message-body. If the message is
04382   // received with a transfer-encoding, that encoding MUST be removed
04383   // prior to checking the Content-MD5 value against the received entity.
04384   HTTPFilterMD5 *md5Filter = 0;
04385   if ( useMD5 )
04386   {
04387      md5Filter = new HTTPFilterMD5;
04388      chain.addFilter(md5Filter);
04389   }
04390 
04391   // now decode all of the content encodings
04392   // -- Why ?? We are not
04393   // -- a proxy server, be a client side implementation!!  The applications
04394   // -- are capable of determinig how to extract the encoded implementation.
04395   // WB: That's a misunderstanding. We are free to remove the encoding.
04396   // WB: Some braindead www-servers however, give .tgz files an encoding
04397   // WB: of "gzip" (or even "x-gzip") and a content-type of "applications/tar"
04398   // WB: They shouldn't do that. We can work around that though...
04399   while (!m_qContentEncodings.isEmpty())
04400   {
04401     QString enc = m_qContentEncodings.last();
04402     m_qContentEncodings.remove(m_qContentEncodings.fromLast());
04403     if ( enc == "gzip" )
04404       chain.addFilter(new HTTPFilterGZip);
04405     else if ( enc == "deflate" )
04406       chain.addFilter(new HTTPFilterDeflate);
04407   }
04408 
04409   while (!m_bEOF)
04410   {
04411     int bytesReceived;
04412 
04413     if (m_bChunked)
04414        bytesReceived = readChunked();
04415     else if (m_iSize != NO_SIZE)
04416        bytesReceived = readLimited();
04417     else
04418        bytesReceived = readUnlimited();
04419 
04420     // make sure that this wasn't an error, first
04421     // kdDebug(7113) << "(" << (int) m_pid << ") readBody: bytesReceived: "
04422     //              << (int) bytesReceived << " m_iSize: " << (int) m_iSize << " Chunked: "
04423     //              << (int) m_bChunked << " BytesLeft: "<< (int) m_iBytesLeft << endl;
04424     if (bytesReceived == -1)
04425     {
04426       if (m_iContentLeft == 0)
04427       {
04428          // gzip'ed data sometimes reports a too long content-length.
04429          // (The length of the unzipped data)
04430          m_iBytesLeft = 0;
04431          break;
04432       }
04433       // Oh well... log an error and bug out
04434       kdDebug(7113) << "(" << m_pid << ") readBody: bytesReceived==-1 sz=" << (int)sz
04435                     << " Connnection broken !" << endl;
04436       error(ERR_CONNECTION_BROKEN, m_state.hostname);
04437       return false;
04438     }
04439 
04440     // I guess that nbytes == 0 isn't an error.. but we certainly
04441     // won't work with it!
04442     if (bytesReceived > 0)
04443     {
04444       // Important: truncate the buffer to the actual size received!
04445       // Otherwise garbage will be passed to the app
04446       m_bufReceive.truncate( bytesReceived );
04447 
04448       chain.slotInput(m_bufReceive);
04449 
04450       if (m_bError)
04451          return false;
04452 
04453       sz += bytesReceived;
04454       if (!dataInternal)
04455         processedSize( sz );
04456     }
04457     m_bufReceive.resize(0); // res
04458 
04459     if (m_iBytesLeft && m_bEOD && !m_bChunked)
04460     {
04461       // gzip'ed data sometimes reports a too long content-length.
04462       // (The length of the unzipped data)
04463       m_iBytesLeft = 0;
04464     }
04465 
04466     if (m_iBytesLeft == 0)
04467     {
04468       kdDebug(7113) << "("<<m_pid<<") EOD received! Left = "<< KIO::number(m_iBytesLeft) << endl;
04469       break;
04470     }
04471   }
04472   chain.slotInput(QByteArray()); // Flush chain.
04473 
04474   if ( useMD5 )
04475   {
04476     QString calculatedMD5 = md5Filter->md5();
04477 
04478     if ( m_sContentMD5 == calculatedMD5 )
04479       kdDebug(7113) << "(" << m_pid << ") MD5 checksum MATCHED!!" << endl;
04480     else
04481       kdDebug(7113) << "(" << m_pid << ") MD5 checksum MISMATCH! Expected: "
04482                     << calculatedMD5 << ", Got: " << m_sContentMD5 << endl;
04483   }
04484 
04485   // Close cache entry
04486   if (m_iBytesLeft == 0)
04487   {
04488      if (m_request.bCachedWrite && m_request.fcache)
04489         closeCacheEntry();
04490      else if (m_request.bCachedWrite) kdDebug(7113) << "(" << m_pid << ") no cache file!\n";
04491   }
04492   else kdDebug(7113) << "(" << m_pid << ") still "<< KIO::number(m_iBytesLeft) <<" bytes left! can't close cache entry!\n";
04493 
04494   if (!dataInternal)
04495     data( QByteArray() );
04496   return true;
04497 }
04498 
04499 
04500 void HTTPProtocol::error( int _err, const QString &_text )
04501 {
04502   httpClose(false);
04503 
04504   if (!m_request.id.isEmpty())
04505   {
04506     forwardHttpResponseHeader();
04507     sendMetaData();
04508   }
04509 
04510   // Clear of the temporary POST buffer if it is not empty...
04511   if (!m_bufPOST.isEmpty())
04512   {
04513     m_bufPOST.resize(0);
04514     kdDebug(7113) << "(" << m_pid << ") HTTP::retreiveHeader: Cleared POST "
04515                      "buffer..." << endl;
04516   }
04517 
04518   SlaveBase::error( _err, _text );
04519   m_bError = true;
04520 }
04521 
04522 
04523 void HTTPProtocol::addCookies( const QString &url, const QCString &cookieHeader )
04524 {
04525    long windowId = m_request.window.toLong();
04526    QByteArray params;
04527    QDataStream stream(params, IO_WriteOnly);
04528    stream << url << cookieHeader << windowId;
04529 
04530    kdDebug(7113) << "(" << m_pid << ") " << cookieHeader << endl;
04531    kdDebug(7113) << "(" << m_pid << ") " << "Window ID: "
04532                  << windowId << ", for host = " << url << endl;
04533 
04534    if ( !dcopClient()->send( "kded", "kcookiejar", "addCookies(QString,QCString,long int)", params ) )
04535    {
04536       kdWarning(7113) << "(" << m_pid << ") Can't communicate with kded_kcookiejar!" << endl;
04537    }
04538 }
04539 
04540 QString HTTPProtocol::findCookies( const QString &url)
04541 {
04542   QCString replyType;
04543   QByteArray params;
04544   QByteArray reply;
04545   QString result;
04546 
04547   long windowId = m_request.window.toLong();
04548   result = QString::null;
04549   QDataStream stream(params, IO_WriteOnly);
04550   stream << url << windowId;
04551 
04552   if ( !dcopClient()->call( "kded", "kcookiejar", "findCookies(QString,long int)",
04553                             params, replyType, reply ) )
04554   {
04555      kdWarning(7113) << "(" << m_pid << ") Can't communicate with kded_kcookiejar!" << endl;
04556      return result;
04557   }
04558   if ( replyType == "QString" )
04559   {
04560      QDataStream stream2( reply, IO_ReadOnly );
04561      stream2 >> result;
04562   }
04563   else
04564   {
04565      kdError(7113) << "(" << m_pid << ") DCOP function findCookies(...) returns "
04566                           << replyType << ", expected QString" << endl;
04567   }
04568   return result;
04569 }
04570 
04571 /******************************* CACHING CODE ****************************/
04572 
04573 
04574 void HTTPProtocol::cacheUpdate( const KURL& url, bool no_cache, time_t expireDate)
04575 {
04576   if ( !checkRequestURL( url ) )
04577       return;
04578 
04579   m_request.path = url.path();
04580   m_request.query = url.query();
04581   m_request.cache = CC_Reload;
04582   m_request.doProxy = m_bUseProxy;
04583 
04584   if (no_cache)
04585   {
04586      m_request.fcache = checkCacheEntry( );
04587      if (m_request.fcache)
04588      {
04589        fclose(m_request.fcache);
04590        m_request.fcache = 0;
04591        ::unlink( QFile::encodeName(m_request.cef) );
04592      }
04593   }
04594   else
04595   {
04596      updateExpireDate( expireDate );
04597   }
04598   finished();
04599 }
04600 
04601 // !START SYNC!
04602 // The following code should be kept in sync
04603 // with the code in http_cache_cleaner.cpp
04604 
04605 FILE* HTTPProtocol::checkCacheEntry( bool readWrite)
04606 {
04607    const QChar separator = '_';
04608 
04609    QString CEF = m_request.path;
04610 
04611    int p = CEF.find('/');
04612 
04613    while(p != -1)
04614    {
04615       CEF[p] = separator;
04616       p = CEF.find('/', p);
04617    }
04618 
04619    QString host = m_request.hostname.lower();
04620    CEF = host + CEF + '_';
04621 
04622    QString dir = m_strCacheDir;
04623    if (dir[dir.length()-1] != '/')
04624       dir += "/";
04625 
04626    int l = host.length();
04627    for(int i = 0; i < l; i++)
04628    {
04629       if (host[i].isLetter() && (host[i] != 'w'))
04630       {
04631          dir += host[i];
04632          break;
04633       }
04634    }
04635    if (dir[dir.length()-1] == '/')
04636       dir += "0";
04637 
04638    unsigned long hash = 0x00000000;
04639    QCString u = m_request.url.url().latin1();
04640    for(int i = u.length(); i--;)
04641    {
04642       hash = (hash * 12211 + u[i]) % 2147483563;
04643    }
04644 
04645    QString hashString;
04646    hashString.sprintf("%08lx", hash);
04647 
04648    CEF = CEF + hashString;
04649 
04650    CEF = dir + "/" + CEF;
04651 
04652    m_request.cef = CEF;
04653 
04654    const char *mode = (readWrite ? "r+" : "r");
04655 
04656    FILE *fs = fopen( QFile::encodeName(CEF), mode); // Open for reading and writing
04657    if (!fs)
04658       return 0;
04659 
04660    char buffer[401];
04661    bool ok = true;
04662 
04663   // CacheRevision
04664   if (ok && (!fgets(buffer, 400, fs)))
04665       ok = false;
04666    if (ok && (strcmp(buffer, CACHE_REVISION) != 0))
04667       ok = false;
04668 
04669    time_t date;
04670    time_t currentDate = time(0);
04671 
04672    // URL
04673    if (ok && (!fgets(buffer, 400, fs)))
04674       ok = false;
04675    if (ok)
04676    {
04677       int l = strlen(buffer);
04678       if (l>0)
04679          buffer[l-1] = 0; // Strip newline
04680       if (m_request.url.url() != buffer)
04681       {
04682          ok = false; // Hash collision
04683       }
04684    }
04685 
04686    // Creation Date
04687    if (ok && (!fgets(buffer, 400, fs)))
04688       ok = false;
04689    if (ok)
04690    {
04691       date = (time_t) strtoul(buffer, 0, 10);
04692       m_request.creationDate = date;
04693       if (m_maxCacheAge && (difftime(currentDate, date) > m_maxCacheAge))
04694       {
04695          m_request.bMustRevalidate = true;
04696          m_request.expireDate = currentDate;
04697       }
04698    }
04699 
04700    // Expiration Date
04701    m_request.cacheExpireDateOffset = ftell(fs);
04702    if (ok && (!fgets(buffer, 400, fs)))
04703       ok = false;
04704    if (ok)
04705    {
04706       if (m_request.cache == CC_Verify)
04707       {
04708          date = (time_t) strtoul(buffer, 0, 10);
04709          // After the expire date we need to revalidate.
04710          if (!date || difftime(currentDate, date) >= 0)
04711             m_request.bMustRevalidate = true;
04712          m_request.expireDate = date;
04713       }
04714       else if (m_request.cache == CC_Refresh)
04715       {
04716          m_request.bMustRevalidate = true;
04717          m_request.expireDate = currentDate;
04718       }
04719    }
04720 
04721    // ETag
04722    if (ok && (!fgets(buffer, 400, fs)))
04723       ok = false;
04724    if (ok)
04725    {
04726       m_request.etag = QString(buffer).stripWhiteSpace();
04727    }
04728 
04729    // Last-Modified
04730    if (ok && (!fgets(buffer, 400, fs)))
04731       ok = false;
04732    if (ok)
04733    {
04734       m_request.lastModified = QString(buffer).stripWhiteSpace();
04735    }
04736 
04737    if (ok)
04738       return fs;
04739 
04740    fclose(fs);
04741    unlink( QFile::encodeName(CEF));
04742    return 0;
04743 }
04744 
04745 void HTTPProtocol::updateExpireDate(time_t expireDate, bool updateCreationDate)
04746 {
04747     bool ok = true;
04748 
04749     FILE *fs = checkCacheEntry(true);
04750     if (fs)
04751     {
04752         QString date;
04753         char buffer[401];
04754         time_t creationDate;
04755 
04756         fseek(fs, 0, SEEK_SET);
04757         if (ok && !fgets(buffer, 400, fs))
04758             ok = false;
04759         if (ok && !fgets(buffer, 400, fs))
04760             ok = false;
04761         long cacheCreationDateOffset = ftell(fs);
04762         if (ok && !fgets(buffer, 400, fs))
04763             ok = false;
04764         creationDate = strtoul(buffer, 0, 10);
04765         if (!creationDate)
04766             ok = false;
04767 
04768         if (updateCreationDate)
04769         {
04770            if (!ok || fseek(fs, cacheCreationDateOffset, SEEK_SET))
04771               return;
04772            QString date;
04773            date.setNum( time(0) );
04774            date = date.leftJustify(16);
04775            fputs(date.latin1(), fs);      // Creation date
04776            fputc('\n', fs);
04777         }
04778 
04779         if (expireDate>(30*365*24*60*60))
04780         {
04781             // expire date is a really a big number, it can't be
04782             // a relative date.
04783             date.setNum( expireDate );
04784         }
04785         else
04786         {
04787             // expireDate before 2000. those values must be
04788             // interpreted as relative expiration dates from
04789             // <META http-equiv="Expires"> tags.
04790             // so we have to scan the creation time and add
04791             // it to the expiryDate
04792             date.setNum( creationDate + expireDate );
04793         }
04794         date = date.leftJustify(16);
04795         if (!ok || fseek(fs, m_request.cacheExpireDateOffset, SEEK_SET))
04796             return;
04797         fputs(date.latin1(), fs);      // Expire date
04798         fseek(fs, 0, SEEK_END);
04799         fclose(fs);
04800     }
04801 }
04802 
04803 void HTTPProtocol::createCacheEntry( const QString &mimetype, time_t expireDate)
04804 {
04805    QString dir = m_request.cef;
04806    int p = dir.findRev('/');
04807    if (p == -1) return; // Error.
04808    dir.truncate(p);
04809 
04810    // Create file
04811    (void) ::mkdir( QFile::encodeName(dir), 0700 );
04812 
04813    QString filename = m_request.cef + ".new";  // Create a new cache entryexpireDate
04814 
04815 //   kdDebug( 7103 ) <<  "creating new cache entry: " << filename << endl;
04816 
04817    m_request.fcache = fopen( QFile::encodeName(filename), "w");
04818    if (!m_request.fcache)
04819    {
04820       kdWarning(7113) << "(" << m_pid << ")createCacheEntry: opening " << filename << " failed." << endl;
04821       return; // Error.
04822    }
04823 
04824    fputs(CACHE_REVISION, m_request.fcache);    // Revision
04825 
04826    fputs(m_request.url.url().latin1(), m_request.fcache);  // Url
04827    fputc('\n', m_request.fcache);
04828 
04829    QString date;
04830    m_request.creationDate = time(0);
04831    date.setNum( m_request.creationDate );
04832    date = date.leftJustify(16);
04833    fputs(date.latin1(), m_request.fcache);      // Creation date
04834    fputc('\n', m_request.fcache);
04835 
04836    date.setNum( expireDate );
04837    date = date.leftJustify(16);
04838    fputs(date.latin1(), m_request.fcache);      // Expire date
04839    fputc('\n', m_request.fcache);
04840 
04841    if (!m_request.etag.isEmpty())
04842       fputs(m_request.etag.latin1(), m_request.fcache);    //ETag
04843    fputc('\n', m_request.fcache);
04844 
04845    if (!m_request.lastModified.isEmpty())
04846       fputs(m_request.lastModified.latin1(), m_request.fcache);    // Last modified
04847    fputc('\n', m_request.fcache);
04848 
04849    fputs(mimetype.latin1(), m_request.fcache);  // Mimetype
04850    fputc('\n', m_request.fcache);
04851 
04852    if (!m_request.strCharset.isEmpty())
04853       fputs(m_request.strCharset.latin1(), m_request.fcache);    // Charset
04854    fputc('\n', m_request.fcache);
04855 
04856    return;
04857 }
04858 // The above code should be kept in sync
04859 // with the code in http_cache_cleaner.cpp
04860 // !END SYNC!
04861 
04862 void HTTPProtocol::writeCacheEntry( const char *buffer, int nbytes)
04863 {
04864    if (fwrite( buffer, nbytes, 1, m_request.fcache) != 1)
04865    {
04866       kdWarning(7113) << "(" << m_pid << ") writeCacheEntry: writing " << nbytes << " bytes failed." << endl;
04867       fclose(m_request.fcache);
04868       m_request.fcache = 0;
04869       QString filename = m_request.cef + ".new";
04870       ::unlink( QFile::encodeName(filename) );
04871       return;
04872    }
04873    long file_pos = ftell( m_request.fcache ) / 1024;
04874    if ( file_pos > m_maxCacheSize )
04875    {
04876       kdDebug(7113) << "writeCacheEntry: File size reaches " << file_pos
04877                     << "Kb, exceeds cache limits. (" << m_maxCacheSize << "Kb)" << endl;
04878       fclose(m_request.fcache);
04879       m_request.fcache = 0;
04880       QString filename = m_request.cef + ".new";
04881       ::unlink( QFile::encodeName(filename) );
04882       return;
04883    }
04884 }
04885 
04886 void HTTPProtocol::closeCacheEntry()
04887 {
04888    QString filename = m_request.cef + ".new";
04889    int result = fclose( m_request.fcache);
04890    m_request.fcache = 0;
04891    if (result == 0)
04892    {
04893       if (::rename( QFile::encodeName(filename), QFile::encodeName(m_request.cef)) == 0)
04894          return; // Success
04895 
04896       kdWarning(7113) << "(" << m_pid << ") closeCacheEntry: error renaming "
04897                       << "cache entry. (" << filename << " -> " << m_request.cef
04898                       << ")" << endl;
04899    }
04900 
04901    kdWarning(7113) << "(" << m_pid << ") closeCacheEntry: error closing cache "
04902                    << "entry. (" << filename<< ")" << endl;
04903 }
04904 
04905 void HTTPProtocol::cleanCache()
04906 {
04907    const time_t maxAge = DEFAULT_CLEAN_CACHE_INTERVAL; // 30 Minutes.
04908    bool doClean = false;
04909    QString cleanFile = m_strCacheDir;
04910    if (cleanFile[cleanFile.length()-1] != '/')
04911       cleanFile += "/";
04912    cleanFile += "cleaned";
04913 
04914    struct stat stat_buf;
04915 
04916    int result = ::stat(QFile::encodeName(cleanFile), &stat_buf);
04917    if (result == -1)
04918    {
04919       int fd = creat( QFile::encodeName(cleanFile), 0600);
04920       if (fd != -1)
04921       {
04922          doClean = true;
04923          ::close(fd);
04924       }
04925    }
04926    else
04927    {
04928       time_t age = (time_t) difftime( time(0), stat_buf.st_mtime );
04929       if (age > maxAge) //
04930         doClean = true;
04931    }
04932    if (doClean)
04933    {
04934       // Touch file.
04935       utime(QFile::encodeName(cleanFile), 0);
04936       KApplication::startServiceByDesktopPath("http_cache_cleaner.desktop");
04937    }
04938 }
04939 
04940 
04941 
04942 //**************************  AUTHENTICATION CODE ********************/
04943 
04944 
04945 void HTTPProtocol::configAuth( char *p, bool isForProxy )
04946 {
04947   HTTP_AUTH f = AUTH_None;
04948   const char *strAuth = p;
04949 
04950   if ( strncasecmp( p, "Basic", 5 ) == 0 )
04951   {
04952     f = AUTH_Basic;
04953     p += 5;
04954     strAuth = "Basic"; // Correct for upper-case variations.
04955   }
04956   else if ( strncasecmp (p, "Digest", 6) == 0 )
04957   {
04958     f = AUTH_Digest;
04959     memcpy((void *)p, "Digest", 6); // Correct for upper-case variations.
04960     p += 6;
04961   }
04962   else if (strncasecmp( p, "MBS_PWD_COOKIE", 14 ) == 0)
04963   {
04964     // Found on http://www.webscription.net/baen/default.asp
04965     f = AUTH_Basic;
04966     p += 14;
04967     strAuth = "Basic";
04968   }
04969 #ifdef HAVE_LIBGSSAPI
04970   else if ( strncasecmp( p, "Negotiate", 9 ) == 0 )
04971   {
04972     // if we get two 401 in a row let's assume for now that
04973     // Negotiate isn't working and ignore it
04974     if ( !isForProxy && !(m_responseCode == 401 && m_prevResponseCode == 401) )
04975     {
04976       f = AUTH_Negotiate;
04977       memcpy((void *)p, "Negotiate", 9); // Correct for upper-case variations.
04978       p += 9;
04979     };
04980   }
04981 #endif
04982   else if ( strncasecmp( p, "NTLM", 4 ) == 0 )
04983   {
04984     f = AUTH_NTLM;
04985     memcpy((void *)p, "NTLM", 4); // Correct for upper-case variations.
04986     p += 4;
04987     m_strRealm = "NTLM"; // set a dummy realm
04988   }
04989   else
04990   {
04991     kdWarning(7113) << "(" << m_pid << ") Unsupported or invalid authorization "
04992                     << "type requested" << endl;
04993     if (isForProxy)
04994       kdWarning(7113) << "(" << m_pid << ") Proxy URL: " << m_proxyURL << endl;
04995     else
04996       kdWarning(7113) << "(" << m_pid << ") URL: " << m_request.url << endl;
04997     kdWarning(7113) << "(" << m_pid << ") Request Authorization: " << p << endl;
04998   }
04999 
05000   /*
05001      This check ensures the following:
05002      1.) Rejection of any unknown/unsupported authentication schemes
05003      2.) Usage of the strongest possible authentication schemes if
05004          and when multiple Proxy-Authenticate or WWW-Authenticate
05005          header field is sent.
05006   */
05007   if (isForProxy)
05008   {
05009     if ((f == AUTH_None) ||
05010         ((m_iProxyAuthCount > 0) && (f < ProxyAuthentication)))
05011     {
05012       // Since I purposefully made the Proxy-Authentication settings
05013       // persistent to reduce the number of round-trips to kdesud we
05014       // have to take special care when an unknown/unsupported auth-
05015       // scheme is received. This check accomplishes just that...
05016       if ( m_iProxyAuthCount == 0)
05017         ProxyAuthentication = f;
05018       kdDebug(7113) << "(" << m_pid << ") Rejected proxy auth method: " << f << endl;
05019       return;
05020     }
05021     m_iProxyAuthCount++;
05022     kdDebug(7113) << "(" << m_pid << ") Accepted proxy auth method: " << f << endl;
05023   }
05024   else
05025   {
05026     if ((f == AUTH_None) ||
05027         ((m_iWWWAuthCount > 0) && (f < Authentication)))
05028     {
05029       kdDebug(7113) << "(" << m_pid << ") Rejected auth method: " << f << endl;
05030       return;
05031     }
05032     m_iWWWAuthCount++;
05033     kdDebug(7113) << "(" << m_pid << ") Accepted auth method: " << f << endl;
05034   }
05035 
05036 
05037   while (*p)
05038   {
05039     int i = 0;
05040     while( (*p == ' ') || (*p == ',') || (*p == '\t') ) { p++; }
05041     if ( strncasecmp( p, "realm=", 6 ) == 0 )
05042     {
05043       p += 6;
05044       if (*p == '"') p++;
05045       while( p[i] && p[i] != '"' ) i++;
05046       if( isForProxy )
05047         m_strProxyRealm = QString::fromLatin1( p, i );
05048       else
05049         m_strRealm = QString::fromLatin1( p, i );
05050       if (!p[i]) break;
05051     }
05052     p+=(i+1);
05053   }
05054 
05055   if( isForProxy )
05056   {
05057     ProxyAuthentication = f;
05058     m_strProxyAuthorization = QString::fromLatin1( strAuth );
05059   }
05060   else
05061   {
05062     Authentication = f;
05063     m_strAuthorization = QString::fromLatin1( strAuth );
05064   }
05065 }
05066 
05067 
05068 bool HTTPProtocol::retryPrompt()
05069 {
05070   QString prompt;
05071   switch ( m_responseCode )
05072   {
05073     case 401:
05074       prompt = i18n("Authentication Failed.");
05075       break;
05076     case 407:
05077       prompt = i18n("Proxy Authentication Failed.");
05078       break;
05079     default:
05080       break;
05081   }
05082   prompt += i18n("  Do you want to retry?");
05083   return (messageBox(QuestionYesNo, prompt, i18n("Authentication")) == 3);
05084 }
05085 
05086 void HTTPProtocol::promptInfo( AuthInfo& info )
05087 {
05088   if ( m_responseCode == 401 )
05089   {
05090     info.url = m_request.url;
05091     if ( !m_state.user.isEmpty() )
05092       info.username = m_state.user;
05093     info.readOnly = !m_request.url.user().isEmpty();
05094     info.prompt = i18n( "You need to supply a username and a "
05095                         "password to access this site." );
05096     info.keepPassword = true; // Prompt the user for persistence as well.
05097     if ( !m_strRealm.isEmpty() )
05098     {
05099       info.realmValue = m_strRealm;
05100       info.verifyPath = false;
05101       info.digestInfo = m_strAuthorization;
05102       info.commentLabel = i18n( "Site:" );
05103       info.comment = i18n("<b>%1</b> at <b>%2</b>").arg( m_strRealm ).arg( m_request.hostname );
05104     }
05105   }
05106   else if ( m_responseCode == 407 )
05107   {
05108     info.url = m_proxyURL;
05109     info.username = m_proxyURL.user();
05110     info.prompt = i18n( "You need to supply a username and a password for "
05111                         "the proxy server listed below before you are allowed "
05112                         "to access any sites." );
05113     info.keepPassword = true;
05114     if ( !m_strProxyRealm.isEmpty() )
05115     {
05116       info.realmValue = m_strProxyRealm;
05117       info.verifyPath = false;
05118       info.digestInfo = m_strProxyAuthorization;
05119       info.commentLabel = i18n( "Proxy:" );
05120       info.comment = i18n("<b>%1</b> at <b>%2</b>").arg( m_strProxyRealm ).arg( m_proxyURL.host() );
05121     }
05122   }
05123 }
05124 
05125 bool HTTPProtocol::getAuthorization()
05126 {
05127   AuthInfo info;
05128   bool result = false;
05129 
05130   kdDebug (7113) << "(" << m_pid << ") HTTPProtocol::getAuthorization: "
05131                  << "Current Response: " << m_responseCode << ", "
05132                  << "Previous Response: " << m_prevResponseCode << ", "
05133                  << "Authentication: " << Authentication << ", "
05134                  << "ProxyAuthentication: " << ProxyAuthentication << endl;
05135 
05136   if (m_request.bNoAuth)
05137   {
05138      if (m_request.bErrorPage)
05139         errorPage();
05140      else
05141         error( ERR_COULD_NOT_LOGIN, i18n("Authentication needed for %1 but authentication is disabled.").arg(m_request.hostname));
05142      return false;
05143   }
05144 
05145   bool repeatFailure = (m_prevResponseCode == m_responseCode);
05146 
05147   QString errorMsg;
05148 
05149   if (repeatFailure)
05150   {
05151     bool prompt = true;
05152     if ( Authentication == AUTH_Digest || ProxyAuthentication == AUTH_Digest )
05153     {
05154       bool isStaleNonce = false;
05155       QString auth = ( m_responseCode == 401 ) ? m_strAuthorization : m_strProxyAuthorization;
05156       int pos = auth.find("stale", 0, false);
05157       if ( pos != -1 )
05158       {
05159         pos += 5;
05160         int len = auth.length();
05161         while( pos < len && (auth[pos] == ' ' || auth[pos] == '=') ) pos++;
05162         if ( pos < len && auth.find("true", pos, false) != -1 )
05163         {
05164           isStaleNonce = true;
05165           kdDebug(7113) << "(" << m_pid << ") Stale nonce value. "
05166                         << "Will retry using same info..." << endl;
05167         }
05168       }
05169       if ( isStaleNonce )
05170       {
05171         prompt = false;
05172         result = true;
05173         if ( m_responseCode == 401 )
05174         {
05175           info.username = m_request.user;
05176           info.password = m_request.passwd;
05177           info.realmValue = m_strRealm;
05178           info.digestInfo = m_strAuthorization;
05179         }
05180         else if ( m_responseCode == 407 )
05181         {
05182           info.username = m_proxyURL.user();
05183           info.password = m_proxyURL.pass();
05184           info.realmValue = m_strProxyRealm;
05185           info.digestInfo = m_strProxyAuthorization;
05186         }
05187       }
05188     }
05189 
05190     if ( Authentication == AUTH_NTLM || ProxyAuthentication == AUTH_NTLM )
05191     {
05192       QString auth = ( m_responseCode == 401 ) ? m_strAuthorization : m_strProxyAuthorization;
05193       kdDebug(7113) << "auth: " << auth << endl;
05194       if ( auth.length() > 4 )
05195       {
05196         prompt = false;
05197         result = true;
05198         kdDebug(7113) << "(" << m_pid << ") NTLM auth second phase, "
05199                       << "sending response..." << endl;
05200         if ( m_responseCode == 401 )
05201         {
05202           info.username = m_request.user;
05203           info.password = m_request.passwd;
05204           info.realmValue = m_strRealm;
05205           info.digestInfo = m_strAuthorization;
05206         }
05207         else if ( m_responseCode == 407 )
05208         {
05209           info.username = m_proxyURL.user();
05210           info.password = m_proxyURL.pass();
05211           info.realmValue = m_strProxyRealm;
05212           info.digestInfo = m_strProxyAuthorization;
05213         }
05214       }
05215     }
05216 
05217     if ( prompt )
05218     {
05219       switch ( m_responseCode )
05220       {
05221         case 401:
05222           errorMsg = i18n("Authentication Failed.");
05223           break;
05224         case 407:
05225           errorMsg = i18n("Proxy Authentication Failed.");
05226           break;
05227         default:
05228           break;
05229       }
05230     }
05231   }
05232   else
05233   {
05234     // At this point we know more details, so use it to find
05235     // out if we have a cached version and avoid a re-prompt!
05236     // We also do not use verify path unlike the pre-emptive
05237     // requests because we already know the realm value...
05238 
05239     if (m_bProxyAuthValid)
05240     {
05241       // Reset cached proxy auth
05242       m_bProxyAuthValid = false;
05243       KURL proxy ( config()->readEntry("UseProxy") );
05244       m_proxyURL.setUser(proxy.user());
05245       m_proxyURL.setPass(proxy.pass());
05246     }
05247 
05248     info.verifyPath = false;
05249     if ( m_responseCode == 407 )
05250     {
05251       info.url = m_proxyURL;
05252       info.username = m_proxyURL.user();
05253       info.password = m_proxyURL.pass();
05254       info.realmValue = m_strProxyRealm;
05255       info.digestInfo = m_strProxyAuthorization;
05256     }
05257     else
05258     {
05259       info.url = m_request.url;
05260       info.username = m_request.user;
05261       info.password = m_request.passwd;
05262       info.realmValue = m_strRealm;
05263       info.digestInfo = m_strAuthorization;
05264     }
05265 
05266     // If either username or password is not supplied
05267     // with the request, check the password cache.
05268     if ( info.username.isNull() ||
05269          info.password.isNull() )
05270       result = checkCachedAuthentication( info );
05271 
05272     if ( Authentication == AUTH_Digest )
05273     {
05274       QString auth;
05275 
05276       if (m_responseCode == 401)
05277         auth = m_strAuthorization;
05278       else
05279         auth = m_strProxyAuthorization;
05280 
05281       int pos = auth.find("stale", 0, false);
05282       if ( pos != -1 )
05283       {
05284         pos += 5;
05285         int len = auth.length();
05286         while( pos < len && (auth[pos] == ' ' || auth[pos] == '=') ) pos++;
05287         if ( pos < len && auth.find("true", pos, false) != -1 )
05288         {
05289           info.digestInfo = (m_responseCode == 401) ? m_strAuthorization : m_strProxyAuthorization;
05290           kdDebug(7113) << "(" << m_pid << ") Just a stale nonce value! "
05291                         << "Retrying using the new nonce sent..." << endl;
05292         }
05293       }
05294     }
05295   }
05296 
05297   if (!result )
05298   {
05299     // Do not prompt if the username & password
05300     // is already supplied and the login attempt
05301     // did not fail before.
05302     if ( !repeatFailure &&
05303          !info.username.isNull() &&
05304          !info.password.isNull() )
05305       result = true;
05306     else
05307     {
05308       if (Authentication == AUTH_Negotiate)
05309       {
05310         if (!repeatFailure)
05311           result = true;
05312       }
05313       else if ( m_request.disablePassDlg == false )
05314       {
05315         kdDebug( 7113 ) << "(" << m_pid << ") Prompting the user for authorization..." << endl;
05316         promptInfo( info );
05317         result = openPassDlg( info, errorMsg );
05318       }
05319     }
05320   }
05321 
05322   if ( result )
05323   {
05324     switch (m_responseCode)
05325     {
05326       case 401: // Request-Authentication
05327         m_request.user = info.username;
05328         m_request.passwd = info.password;
05329         m_strRealm = info.realmValue;
05330         m_strAuthorization = info.digestInfo;
05331         break;
05332       case 407: // Proxy-Authentication
05333         m_proxyURL.setUser( info.username );
05334         m_proxyURL.setPass( info.password );
05335         m_strProxyRealm = info.realmValue;
05336         m_strProxyAuthorization = info.digestInfo;
05337         break;
05338       default:
05339         break;
05340     }
05341     return true;
05342   }
05343 
05344   if (m_request.bErrorPage)
05345      errorPage();
05346   else
05347      error( ERR_USER_CANCELED, QString::null );
05348   return false;
05349 }
05350 
05351 void HTTPProtocol::saveAuthorization()
05352 {
05353   AuthInfo info;
05354   if ( m_prevResponseCode == 407 )
05355   {
05356     if (!m_bUseProxy)
05357        return;
05358     m_bProxyAuthValid = true;
05359     info.url = m_proxyURL;
05360     info.username = m_proxyURL.user();
05361     info.password = m_proxyURL.pass();
05362     info.realmValue = m_strProxyRealm;
05363     info.digestInfo = m_strProxyAuthorization;
05364     cacheAuthentication( info );
05365   }
05366   else
05367   {
05368     info.url = m_request.url;
05369     info.username = m_request.user;
05370     info.password = m_request.passwd;
05371     info.realmValue = m_strRealm;
05372     info.digestInfo = m_strAuthorization;
05373     cacheAuthentication( info );
05374   }
05375 }
05376 
05377 #ifdef HAVE_LIBGSSAPI
05378 QCString HTTPProtocol::gssError( int major_status, int minor_status )
05379 {
05380   OM_uint32 new_status;
05381   OM_uint32 msg_ctx = 0;
05382   gss_buffer_desc major_string;
05383   gss_buffer_desc minor_string;
05384   OM_uint32 ret;
05385   QCString errorstr;
05386 
05387   errorstr = "";
05388 
05389   do {
05390     ret = gss_display_status(&new_status, major_status, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &major_string);
05391     errorstr += (const char *)major_string.value;
05392     errorstr += " ";
05393     ret = gss_display_status(&new_status, minor_status, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &minor_string);
05394     errorstr += (const char *)minor_string.value;
05395     errorstr += " ";
05396   } while (!GSS_ERROR(ret) && msg_ctx != 0);
05397 
05398   return errorstr;
05399 }
05400 
05401 QString HTTPProtocol::createNegotiateAuth()
05402 {
05403   QString auth;
05404   QCString servicename;
05405   QByteArray input;
05406   OM_uint32 major_status, minor_status;
05407   OM_uint32 req_flags = 0;
05408   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
05409   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
05410   gss_name_t server;
05411   gss_ctx_id_t ctx;
05412   gss_OID mech_oid;
05413   static gss_OID_desc krb5_oid_desc = {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"};
05414   static gss_OID_desc spnego_oid_desc = {6, (void *) "\x2b\x06\x01\x05\x05\x02"};
05415   int found = 0;
05416   unsigned int i;
05417   gss_OID_set mech_set;
05418   gss_OID tmp_oid;
05419 
05420   ctx = GSS_C_NO_CONTEXT;
05421   mech_oid = &krb5_oid_desc;
05422 
05423   // see whether we can use the SPNEGO mechanism
05424   major_status = gss_indicate_mechs(&minor_status, &mech_set);
05425   if (GSS_ERROR(major_status)) {
05426     kdDebug(7113) << "(" << m_pid << ") gss_indicate_mechs failed: " << gssError(major_status, minor_status) << endl;
05427   } else {
05428     for (i=0; i<mech_set->count && !found; i++) {
05429       tmp_oid = &mech_set->elements[i];
05430       if (tmp_oid->length == spnego_oid_desc.length &&
05431         !memcmp(tmp_oid->elements, spnego_oid_desc.elements, tmp_oid->length)) {
05432         kdDebug(7113) << "(" << m_pid << ") createNegotiateAuth: found SPNEGO mech" << endl;
05433         found = 1;
05434         mech_oid = &spnego_oid_desc;
05435         break;
05436       }
05437     }
05438     gss_release_oid_set(&minor_status, &mech_set);
05439   }
05440 
05441   // the service name is "HTTP/f.q.d.n"
05442   servicename = "HTTP@";
05443   servicename += m_state.hostname.ascii();
05444 
05445   input_token.value = (void *)servicename.data();
05446   input_token.length = servicename.length() + 1;
05447 
05448   major_status = gss_import_name(&minor_status, &input_token,
05449                                  GSS_C_NT_HOSTBASED_SERVICE, &server);
05450 
05451   input_token.value = NULL;
05452   input_token.length = 0;
05453 
05454   if (GSS_ERROR(major_status)) {
05455     kdDebug(7113) << "(" << m_pid << ") gss_import_name failed: " << gssError(major_status, minor_status) << endl;
05456     // reset the auth string so that subsequent methods aren't confused
05457     m_strAuthorization = QString::null;
05458     return QString::null;
05459   }
05460 
05461   major_status = gss_init_sec_context(&minor_status, GSS_C_NO_CREDENTIAL,
05462                                       &ctx, server, mech_oid,
05463                                       req_flags, GSS_C_INDEFINITE,
05464                                       GSS_C_NO_CHANNEL_BINDINGS,
05465                                       GSS_C_NO_BUFFER, NULL, &output_token,
05466                                       NULL, NULL);
05467 
05468 
05469   if (GSS_ERROR(major_status) || (output_token.length == 0)) {
05470     kdDebug(7113) << "(" << m_pid << ") gss_init_sec_context failed: " << gssError(major_status, minor_status) << endl;
05471     gss_release_name(&minor_status, &server);
05472     if (ctx != GSS_C_NO_CONTEXT) {
05473       gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER);
05474       ctx = GSS_C_NO_CONTEXT;
05475     }
05476     // reset the auth string so that subsequent methods aren't confused
05477     m_strAuthorization = QString::null;
05478     return QString::null;
05479   }
05480 
05481   input.duplicate((const char *)output_token.value, output_token.length);
05482   auth = "Authorization: Negotiate ";
05483   auth += KCodecs::base64Encode( input );
05484   auth += "\r\n";
05485 
05486   // free everything
05487   gss_release_name(&minor_status, &server);
05488   if (ctx != GSS_C_NO_CONTEXT) {
05489     gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER);
05490     ctx = GSS_C_NO_CONTEXT;
05491   }
05492   gss_release_buffer(&minor_status, &output_token);
05493 
05494   return auth;
05495 }
05496 #else
05497 
05498 // Dummy
05499 QCString HTTPProtocol::gssError( int, int )
05500 {
05501   return "";
05502 }
05503 
05504 // Dummy
05505 QString HTTPProtocol::createNegotiateAuth()
05506 {
05507   return QString::null;
05508 }
05509 #endif
05510 
05511 QString HTTPProtocol::createNTLMAuth( bool isForProxy )
05512 {
05513   uint len;
05514   QString auth, user, domain, passwd;
05515   QCString strauth;
05516   QByteArray buf;
05517 
05518   if ( isForProxy )
05519   {
05520     auth = "Proxy-Connection: Keep-Alive\r\n";
05521     auth += "Proxy-Authorization: NTLM ";
05522     user = m_proxyURL.user();
05523     passwd = m_proxyURL.pass();
05524     strauth = m_strProxyAuthorization.latin1();
05525     len = m_strProxyAuthorization.length();
05526   }
05527   else
05528   {
05529     auth = "Authorization: NTLM ";
05530     user = m_state.user;
05531     passwd = m_state.passwd;
05532     strauth = m_strAuthorization.latin1();
05533     len = m_strAuthorization.length();
05534   }
05535   if ( user.contains('\\') ) {
05536     domain = user.section( '\\', 0, 0);
05537     user = user.section( '\\', 1 );
05538   }
05539 
05540   kdDebug(7113) << "(" << m_pid << ") NTLM length: " << len << endl;
05541   if ( user.isEmpty() || passwd.isEmpty() || len < 4 )
05542     return QString::null;
05543 
05544   if ( len > 4 )
05545   {
05546     // create a response
05547     QByteArray challenge;
05548     KCodecs::base64Decode( strauth.right( len - 5 ), challenge );
05549     KNTLM::getAuth( buf, challenge, user, passwd, domain,
05550             KNetwork::KResolver::localHostName(), false, false );
05551   }
05552   else
05553   {
05554     KNTLM::getNegotiate( buf );
05555   }
05556 
05557   // remove the challenge to prevent reuse
05558   if ( isForProxy )
05559     m_strProxyAuthorization = "NTLM";
05560   else
05561     m_strAuthorization = "NTLM";
05562 
05563   auth += KCodecs::base64Encode( buf );
05564   auth += "\r\n";
05565 
05566   return auth;
05567 }
05568 
05569 QString HTTPProtocol::createBasicAuth( bool isForProxy )
05570 {
05571   QString auth;
05572   QCString user, passwd;
05573   if ( isForProxy )
05574   {
05575     auth = "Proxy-Authorization: Basic ";
05576     user = m_proxyURL.user().latin1();
05577     passwd = m_proxyURL.pass().latin1();
05578   }
05579   else
05580   {
05581     auth = "Authorization: Basic ";
05582     user = m_state.user.latin1();
05583     passwd = m_state.passwd.latin1();
05584   }
05585 
05586   if ( user.isEmpty() )
05587     user = "";
05588   if ( passwd.isEmpty() )
05589     passwd = "";
05590 
05591   user += ':';
05592   user += passwd;
05593   auth += KCodecs::base64Encode( user );
05594   auth += "\r\n";
05595 
05596   return auth;
05597 }
05598 
05599 void HTTPProtocol::calculateResponse( DigestAuthInfo& info, QCString& Response )
05600 {
05601   KMD5 md;
05602   QCString HA1;
05603   QCString HA2;
05604 
05605   // Calculate H(A1)
05606   QCString authStr = info.username;
05607   authStr += ':';
05608   authStr += info.realm;
05609   authStr += ':';
05610   authStr += info.password;
05611   md.update( authStr );
05612 
05613   if ( info.algorithm.lower() == "md5-sess" )
05614   {
05615     authStr = md.hexDigest();
05616     authStr += ':';
05617     authStr += info.nonce;
05618     authStr += ':';
05619     authStr += info.cnonce;
05620     md.reset();
05621     md.update( authStr );
05622   }
05623   HA1 = md.hexDigest();
05624 
05625   kdDebug(7113) << "(" << m_pid << ") calculateResponse(): A1 => " << HA1 << endl;
05626 
05627   // Calcualte H(A2)
05628   authStr = info.method;
05629   authStr += ':';
05630   authStr += m_request.url.encodedPathAndQuery(0, true).latin1();
05631   if ( info.qop == "auth-int" )
05632   {
05633     authStr += ':';
05634     authStr += info.entityBody;
05635   }
05636   md.reset();
05637   md.update( authStr );
05638   HA2 = md.hexDigest();
05639 
05640   kdDebug(7113) << "(" << m_pid << ") calculateResponse(): A2 => "
05641                 << HA2 << endl;
05642 
05643   // Calcualte the response.
05644   authStr = HA1;
05645   authStr += ':';
05646   authStr += info.nonce;
05647   authStr += ':';
05648   if ( !info.qop.isEmpty() )
05649   {
05650     authStr += info.nc;
05651     authStr += ':';
05652     authStr += info.cnonce;
05653     authStr += ':';
05654     authStr += info.qop;
05655     authStr += ':';
05656   }
05657   authStr += HA2;
05658   md.reset();
05659   md.update( authStr );
05660   Response = md.hexDigest();
05661 
05662   kdDebug(7113) << "(" << m_pid << ") calculateResponse(): Response => "
05663                 << Response << endl;
05664 }
05665 
05666 QString HTTPProtocol::createDigestAuth ( bool isForProxy )
05667 {
05668   const char *p;
05669 
05670   QString auth;
05671   QCString opaque;
05672   QCString Response;
05673 
05674   DigestAuthInfo info;
05675 
05676   opaque = "";
05677   if ( isForProxy )
05678   {
05679     auth = "Proxy-Authorization: Digest ";
05680     info.username = m_proxyURL.user().latin1();
05681     info.password = m_proxyURL.pass().latin1();
05682     p = m_strProxyAuthorization.latin1();
05683   }
05684   else
05685   {
05686     auth = "Authorization: Digest ";
05687     info.username = m_state.user.latin1();
05688     info.password = m_state.passwd.latin1();
05689     p = m_strAuthorization.latin1();
05690   }
05691   if (!p || !*p)
05692     return QString::null;
05693 
05694   p += 6; // Skip "Digest"
05695 
05696   if ( info.username.isEmpty() || info.password.isEmpty() || !p )
05697     return QString::null;
05698 
05699   // info.entityBody = p;  // FIXME: send digest of data for POST action ??
05700   info.realm = "";
05701   info.algorithm = "MD5";
05702   info.nonce = "";
05703   info.qop = "";
05704 
05705   // cnonce is recommended to contain about 64 bits of entropy
05706   info.cnonce = KApplication::randomString(16).latin1();
05707 
05708   // HACK: Should be fixed according to RFC 2617 section 3.2.2
05709   info.nc = "00000001";
05710 
05711   // Set the method used...
05712   switch ( m_request.method )
05713   {
05714     case HTTP_GET:
05715         info.method = "GET";
05716         break;
05717     case HTTP_PUT:
05718         info.method = "PUT";
05719         break;
05720     case HTTP_POST:
05721         info.method = "POST";
05722         break;
05723     case HTTP_HEAD:
05724         info.method = "HEAD";
05725         break;
05726     case HTTP_DELETE:
05727         info.method = "DELETE";
05728         break;
05729     case DAV_PROPFIND:
05730         info.method = "PROPFIND";
05731         break;
05732     case DAV_PROPPATCH:
05733         info.method = "PROPPATCH";
05734         break;
05735     case DAV_MKCOL:
05736         info.method = "MKCOL";
05737         break;
05738     case DAV_COPY:
05739         info.method = "COPY";
05740         break;
05741     case DAV_MOVE:
05742         info.method = "MOVE";
05743         break;
05744     case DAV_LOCK:
05745         info.method = "LOCK";
05746         break;
05747     case DAV_UNLOCK:
05748         info.method = "UNLOCK";
05749         break;
05750     case DAV_SEARCH:
05751         info.method = "SEARCH";
05752         break;
05753     case DAV_SUBSCRIBE:
05754         info.method = "SUBSCRIBE";
05755         break;
05756     case DAV_UNSUBSCRIBE:
05757         info.method = "UNSUBSCRIBE";
05758         break;
05759     case DAV_POLL:
05760         info.method = "POLL";
05761         break;
05762     default:
05763         error( ERR_UNSUPPORTED_ACTION, i18n("Unsupported method: authentication will fail. Please submit a bug report."));
05764         break;
05765   }
05766 
05767   // Parse the Digest response....
05768   while (*p)
05769   {
05770     int i = 0;
05771     while ( (*p == ' ') || (*p == ',') || (*p == '\t')) { p++; }
05772     if (strncasecmp(p, "realm=", 6 )==0)
05773     {
05774       p+=6;
05775       while ( *p == '"' ) p++;  // Go past any number of " mark(s) first
05776       while ( p[i] != '"' ) i++;  // Read everything until the last " mark
05777       info.realm = QCString( p, i+1 );
05778     }
05779     else if (strncasecmp(p, "algorith=", 9)==0)
05780     {
05781       p+=9;
05782       while ( *p == '"' ) p++;  // Go past any number of " mark(s) first
05783       while ( ( p[i] != '"' ) && ( p[i] != ',' ) && ( p[i] != '\0' ) ) i++;
05784       info.algorithm = QCString(p, i+1);
05785     }
05786     else if (strncasecmp(p, "algorithm=", 10)==0)
05787     {
05788       p+=10;
05789       while ( *p == '"' ) p++;  // Go past any " mark(s) first
05790       while ( ( p[i] != '"' ) && ( p[i] != ',' ) && ( p[i] != '\0' ) ) i++;
05791       info.algorithm = QCString(p,i+1);
05792     }
05793     else if (strncasecmp(p, "domain=", 7)==0)
05794     {
05795       p+=7;
05796       while ( *p == '"' ) p++;  // Go past any " mark(s) first
05797       while ( p[i] != '"' ) i++;  // Read everything until the last " mark
05798       int pos;
05799       int idx = 0;
05800       QCString uri = QCString(p,i+1);
05801       do
05802       {
05803         pos = uri.find( ' ', idx );
05804         if ( pos != -1 )
05805         {
05806           KURL u (m_request.url, uri.mid(idx, pos-idx));
05807           if (u.isValid ())
05808             info.digestURI.append( u.url().latin1() );
05809         }
05810         else
05811         {
05812           KURL u (m_request.url, uri.mid(idx, uri.length()-idx));
05813           if (u.isValid ())
05814             info.digestURI.append( u.url().latin1() );
05815         }
05816         idx = pos+1;
05817       } while ( pos != -1 );
05818     }
05819     else if (strncasecmp(p, "nonce=", 6)==0)
05820     {
05821       p+=6;
05822       while ( *p == '"' ) p++;  // Go past any " mark(s) first
05823       while ( p[i] != '"' ) i++;  // Read everything until the last " mark
05824       info.nonce = QCString(p,i+1);
05825     }
05826     else if (strncasecmp(p, "opaque=", 7)==0)
05827     {
05828       p+=7;
05829       while ( *p == '"' ) p++;  // Go past any " mark(s) first
05830       while ( p[i] != '"' ) i++;  // Read everything until the last " mark
05831       opaque = QCString(p,i+1);
05832     }
05833     else if (strncasecmp(p, "qop=", 4)==0)
05834     {
05835       p+=4;
05836       while ( *p == '"' ) p++;  // Go past any " mark(s) first
05837       while ( p[i] != '"' ) i++;  // Read everything until the last " mark
05838       info.qop = QCString(p,i+1);
05839     }
05840     p+=(i+1);
05841   }
05842 
05843   if (info.realm.isEmpty() || info.nonce.isEmpty())
05844     return QString::null;
05845 
05846   // If the "domain" attribute was not specified and the current response code
05847   // is authentication needed, add the current request url to the list over which
05848   // this credential can be automatically applied.
05849   if (info.digestURI.isEmpty() && (m_responseCode == 401 || m_responseCode == 407))
05850     info.digestURI.append (m_request.url.url().latin1());
05851   else
05852   {
05853     // Verify whether or not we should send a cached credential to the
05854     // server based on the stored "domain" attribute...
05855     bool send = true;
05856 
05857     // Determine the path of the request url...
05858     QString requestPath = m_request.url.directory(false, false);
05859     if (requestPath.isEmpty())
05860       requestPath = "/";
05861 
05862     int count = info.digestURI.count();
05863 
05864     for (int i = 0; i < count; i++ )
05865     {
05866       KURL u ( info.digestURI.at(i) );
05867 
05868       send &= (m_request.url.protocol().lower() == u.protocol().lower());
05869       send &= (m_request.hostname.lower() == u.host().lower());
05870 
05871       if (m_request.port > 0 && u.port() > 0)
05872         send &= (m_request.port == u.port());
05873 
05874       QString digestPath = u.directory (false, false);
05875       if (digestPath.isEmpty())
05876         digestPath = "/";
05877 
05878       send &= (requestPath.startsWith(digestPath));
05879 
05880       if (send)
05881         break;
05882     }
05883 
05884     kdDebug(7113) << "(" << m_pid << ") createDigestAuth(): passed digest "
05885                      "authentication credential test: " << send << endl;
05886 
05887     if (!send)
05888       return QString::null;
05889   }
05890 
05891   kdDebug(7113) << "(" << m_pid << ") RESULT OF PARSING:" << endl;
05892   kdDebug(7113) << "(" << m_pid << ")   algorithm: " << info.algorithm << endl;
05893   kdDebug(7113) << "(" << m_pid << ")   realm:     " << info.realm << endl;
05894   kdDebug(7113) << "(" << m_pid << ")   nonce:     " << info.nonce << endl;
05895   kdDebug(7113) << "(" << m_pid << ")   opaque:    " << opaque << endl;
05896   kdDebug(7113) << "(" << m_pid << ")   qop:       " << info.qop << endl;
05897 
05898   // Calculate the response...
05899   calculateResponse( info, Response );
05900 
05901   auth += "username=\"";
05902   auth += info.username;
05903 
05904   auth += "\", realm=\"";
05905   auth += info.realm;
05906   auth += "\"";
05907 
05908   auth += ", nonce=\"";
05909   auth += info.nonce;
05910 
05911   auth += "\", uri=\"";
05912   auth += m_request.url.encodedPathAndQuery(0, true);
05913 
05914   auth += "\", algorithm=\"";
05915   auth += info.algorithm;
05916   auth +="\"";
05917 
05918   if ( !info.qop.isEmpty() )
05919   {
05920     auth += ", qop=\"";
05921     auth += info.qop;
05922     auth += "\", cnonce=\"";
05923     auth += info.cnonce;
05924     auth += "\", nc=";
05925     auth += info.nc;
05926   }
05927 
05928   auth += ", response=\"";
05929   auth += Response;
05930   if ( !opaque.isEmpty() )
05931   {
05932     auth += "\", opaque=\"";
05933     auth += opaque;
05934   }
05935   auth += "\"\r\n";
05936 
05937   return auth;
05938 }
05939 
05940 QString HTTPProtocol::proxyAuthenticationHeader()
05941 {
05942   QString header;
05943 
05944   // We keep proxy authentication locally until they are changed.
05945   // Thus, no need to check with the password manager for every
05946   // connection.
05947   if ( m_strProxyRealm.isEmpty() )
05948   {
05949     AuthInfo info;
05950     info.url = m_proxyURL;
05951     info.username = m_proxyURL.user();
05952     info.password = m_proxyURL.pass();
05953     info.verifyPath = true;
05954 
05955     // If the proxy URL already contains username
05956     // and password simply attempt to retrieve it
05957     // without prompting the user...
05958     if ( !info.username.isNull() && !info.password.isNull() )
05959     {
05960       if( m_strProxyAuthorization.isEmpty() )
05961         ProxyAuthentication = AUTH_None;
05962       else if( m_strProxyAuthorization.startsWith("Basic") )
05963         ProxyAuthentication = AUTH_Basic;
05964       else if( m_strProxyAuthorization.startsWith("NTLM") )
05965         ProxyAuthentication = AUTH_NTLM;
05966       else
05967         ProxyAuthentication = AUTH_Digest;
05968     }
05969     else
05970     {
05971       if ( checkCachedAuthentication(info) && !info.digestInfo.isEmpty() )
05972       {
05973         m_proxyURL.setUser( info.username );
05974         m_proxyURL.setPass( info.password );
05975         m_strProxyRealm = info.realmValue;
05976         m_strProxyAuthorization = info.digestInfo;
05977         if( m_strProxyAuthorization.startsWith("Basic") )
05978           ProxyAuthentication = AUTH_Basic;
05979         else if( m_strProxyAuthorization.startsWith("NTLM") )
05980           ProxyAuthentication = AUTH_NTLM;
05981         else
05982           ProxyAuthentication = AUTH_Digest;
05983       }
05984       else
05985       {
05986         ProxyAuthentication = AUTH_None;
05987       }
05988     }
05989   }
05990 
05991   /********* Only for debugging purpose... *********/
05992   if ( ProxyAuthentication != AUTH_None )
05993   {
05994     kdDebug(7113) << "(" << m_pid << ") Using Proxy Authentication: " << endl;
05995     kdDebug(7113) << "(" << m_pid << ")   HOST= " << m_proxyURL.host() << endl;
05996     kdDebug(7113) << "(" << m_pid << ")   PORT= " << m_proxyURL.port() << endl;
05997     kdDebug(7113) << "(" << m_pid << ")   USER= " << m_proxyURL.user() << endl;
05998     kdDebug(7113) << "(" << m_pid << ")   PASSWORD= [protected]" << endl;
05999     kdDebug(7113) << "(" << m_pid << ")   REALM= " << m_strProxyRealm << endl;
06000     kdDebug(7113) << "(" << m_pid << ")   EXTRA= " << m_strProxyAuthorization << endl;
06001   }
06002 
06003   switch ( ProxyAuthentication )
06004   {
06005     case AUTH_Basic:
06006       header += createBasicAuth( true );
06007       break;
06008     case AUTH_Digest:
06009       header += createDigestAuth( true );
06010       break;
06011     case AUTH_NTLM:
06012       if ( m_bFirstRequest ) header += createNTLMAuth( true );
06013       break;
06014     case AUTH_None:
06015     default:
06016       break;
06017   }
06018 
06019   return header;
06020 }
06021 
06022 #include "http.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys