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