• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

KIOSlave

http.cpp

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

KIOSlave

Skip menu "KIOSlave"
  • Main Page
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • KIO
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.6
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal