• Skip to content
  • Skip to link menu
KDE 4.3 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    Copyright (C) 2008,2009 Andreas Hartmetz <ahartmetz@gmail.com>
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 
00031 #include <fcntl.h>
00032 #include <utime.h>
00033 #include <stdlib.h>
00034 #include <stdio.h>
00035 #include <sys/stat.h>
00036 #include <sys/time.h>
00037 #include <unistd.h> // must be explicitly included for MacOSX
00038 
00039 #include <QtXml/qdom.h>
00040 #include <QtCore/QFile>
00041 #include <QtCore/QRegExp>
00042 #include <QtCore/QDate>
00043 #include <QtDBus/QtDBus>
00044 #include <QtNetwork/QAuthenticator>
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 //string parsing helpers and HeaderTokenizer implementation
00091 #include "parsinghelpers.cpp"
00092 //authentication handlers
00093 #include "httpauthentication.cpp"
00094 
00095 using namespace KIO;
00096 
00097 extern "C" int KDE_EXPORT kdemain( int argc, char **argv )
00098 {
00099     QCoreApplication app( argc, argv ); // needed for QSocketNotifier
00100     KComponentData componentData( "kio_http", "kdelibs4" );
00101     (void) KGlobal::locale();
00102 
00103     if (argc != 4)
00104     {
00105         fprintf(stderr, "Usage: kio_http protocol domain-socket1 domain-socket2\n");
00106         exit(-1);
00107     }
00108 
00109     HTTPProtocol slave(argv[1], argv[2], argv[3]);
00110     slave.dispatchLoop();
00111     return 0;
00112 }
00113 
00114 /***********************************  Generic utility functions ********************/
00115 
00116 static bool isCrossDomainRequest( const QString& fqdn, const QString& originURL )
00117 {
00118   //TODO read the RFC
00119   if (originURL == "true") // Backwards compatibility
00120      return true;
00121 
00122   KUrl url ( originURL );
00123 
00124   // Document Origin domain
00125   QString a = url.host();
00126   // Current request domain
00127   QString b = fqdn;
00128 
00129   if (a == b)
00130     return false;
00131 
00132   QStringList la = a.split('.', QString::SkipEmptyParts);
00133   QStringList lb = b.split('.', QString::SkipEmptyParts);
00134 
00135   if (qMin(la.count(), lb.count()) < 2) {
00136       return true;  // better safe than sorry...
00137   }
00138 
00139   while(la.count() > 2)
00140       la.pop_front();
00141   while(lb.count() > 2)
00142       lb.pop_front();
00143 
00144   return la != lb;
00145 }
00146 
00147 /*
00148   Eliminates any custom header that could potentially alter the request
00149 */
00150 static QString sanitizeCustomHTTPHeader(const QString& _header)
00151 {
00152   QString sanitizedHeaders;
00153   const QStringList headers = _header.split(QRegExp("[\r\n]"));
00154 
00155   for(QStringList::ConstIterator it = headers.begin(); it != headers.end(); ++it)
00156   {
00157     QString header = (*it).toLower();
00158     // Do not allow Request line to be specified and ignore
00159     // the other HTTP headers.
00160     if (!header.contains(':') || header.startsWith("host") ||
00161         header.startsWith("proxy-authorization") ||
00162         header.startsWith("via"))
00163       continue;
00164 
00165     sanitizedHeaders += (*it);
00166     sanitizedHeaders += "\r\n";
00167   }
00168   sanitizedHeaders.chop(2);
00169 
00170   return sanitizedHeaders;
00171 }
00172 
00173 static bool isEncryptedHttpVariety(const QString &p)
00174 {
00175     return p == "https" || p == "webdavs";
00176 }
00177 
00178 static bool isValidProxy(const KUrl &u)
00179 {
00180     return u.isValid() && u.hasHost();
00181 }
00182 
00183 static bool isHttpProxy(const KUrl &u)
00184 {
00185     return isValidProxy(u) && u.protocol() == "http";
00186 }
00187 
00188 static QString methodString(HTTP_METHOD m)
00189 {
00190     switch(m) {
00191     case HTTP_GET:
00192         return"GET ";
00193     case HTTP_PUT:
00194         return "PUT ";
00195     case HTTP_POST:
00196         return "POST ";
00197     case HTTP_HEAD:
00198         return "HEAD ";
00199     case HTTP_DELETE:
00200         return "DELETE ";
00201     case HTTP_OPTIONS:
00202         return "OPTIONS ";
00203     case DAV_PROPFIND:
00204         return "PROPFIND ";
00205     case DAV_PROPPATCH:
00206         return "PROPPATCH ";
00207     case DAV_MKCOL:
00208         return "MKCOL ";
00209     case DAV_COPY:
00210         return "COPY ";
00211     case DAV_MOVE:
00212         return "MOVE ";
00213     case DAV_LOCK:
00214         return "LOCK ";
00215     case DAV_UNLOCK:
00216         return "UNLOCK ";
00217     case DAV_SEARCH:
00218         return "SEARCH ";
00219     case DAV_SUBSCRIBE:
00220         return "SUBSCRIBE ";
00221     case DAV_UNSUBSCRIBE:
00222         return "UNSUBSCRIBE ";
00223     case DAV_POLL:
00224         return "POLL ";
00225     default:
00226         Q_ASSERT(false);
00227         return QString();
00228     }
00229 }
00230 
00231 // KDE5 TODO (QT5) : use QString::htmlEscape or whatever https://qt.gitorious.org/qt/qtbase/merge_requests/56
00232 // ends up with.
00233 static QString htmlEscape(const QString &plain)
00234 {
00235     QString rich;
00236     rich.reserve(int(plain.length() * 1.1));
00237         for (int i = 0; i < plain.length(); ++i) {
00238         if (plain.at(i) == QLatin1Char('<'))
00239             rich += QLatin1String("&lt;");
00240         else if (plain.at(i) == QLatin1Char('>'))
00241             rich += QLatin1String("&gt;");
00242         else if (plain.at(i) == QLatin1Char('&'))
00243             rich += QLatin1String("&amp;");
00244         else if (plain.at(i) == QLatin1Char('"'))
00245             rich += QLatin1String("&quot;");
00246         else
00247             rich += plain.at(i);
00248     }
00249     rich.squeeze();
00250     return rich;
00251 }
00252 
00253 #define NO_SIZE ((KIO::filesize_t) -1)
00254 
00255 #ifdef HAVE_STRTOLL
00256 #define STRTOLL strtoll
00257 #else
00258 #define STRTOLL strtol
00259 #endif
00260 
00261 
00262 /************************************** HTTPProtocol **********************************************/
00263 
00264 
00265 HTTPProtocol::HTTPProtocol( const QByteArray &protocol, const QByteArray &pool,
00266                             const QByteArray &app )
00267     : TCPSlaveBase(protocol, pool, app, isEncryptedHttpVariety(protocol))
00268     , m_iSize(NO_SIZE)
00269     , m_isBusy(false)
00270     , m_isFirstRequest(false)
00271     , m_maxCacheAge(DEFAULT_MAX_CACHE_AGE)
00272     , m_maxCacheSize(DEFAULT_MAX_CACHE_SIZE/2)
00273     , m_protocol(protocol)
00274     , m_wwwAuth(0)
00275     , m_proxyAuth(0)
00276     , m_socketProxyAuth(0)
00277     , m_isError(false)
00278     , m_isLoadingErrorPage(false)
00279     , m_remoteRespTimeout(DEFAULT_RESPONSE_TIMEOUT)
00280 {
00281     reparseConfiguration();
00282     setBlocking(true);
00283     connect(socket(), SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)),
00284             this, SLOT(proxyAuthenticationForSocket(const QNetworkProxy &, QAuthenticator *)));
00285 }
00286 
00287 HTTPProtocol::~HTTPProtocol()
00288 {
00289   httpClose(false);
00290 }
00291 
00292 void HTTPProtocol::reparseConfiguration()
00293 {
00294     kDebug(7113);
00295 
00296     delete m_proxyAuth;
00297     delete m_wwwAuth;
00298     m_proxyAuth = 0;
00299     m_wwwAuth = 0;
00300     m_request.proxyUrl.clear(); //TODO revisit
00301 }
00302 
00303 void HTTPProtocol::resetConnectionSettings()
00304 {
00305   m_isEOF = false;
00306   m_isError = false;
00307   m_isLoadingErrorPage = false;
00308 }
00309 
00310 quint16 HTTPProtocol::defaultPort() const
00311 {
00312     return isEncryptedHttpVariety(m_protocol) ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT;
00313 }
00314 
00315 void HTTPProtocol::resetResponseParsing()
00316 {
00317   m_isRedirection = false;
00318   m_isChunked = false;
00319   m_iSize = NO_SIZE;
00320   clearUnreadBuffer();
00321 
00322   m_responseHeaders.clear();
00323   m_contentEncodings.clear();
00324   m_transferEncodings.clear();
00325   m_contentMD5.clear();
00326   m_mimeType.clear();
00327 
00328   setMetaData("request-id", m_request.id);
00329 }
00330 
00331 void HTTPProtocol::resetSessionSettings()
00332 {
00333   // Do not reset the URL on redirection if the proxy
00334   // URL, username or password has not changed!
00335   KUrl proxy ( config()->readEntry("UseProxy") );
00336   QNetworkProxy::ProxyType proxyType = QNetworkProxy::NoProxy;
00337 
00338 #if 0
00339   if ( m_proxyAuth.realm.isEmpty() || !proxy.isValid() ||
00340        m_request.proxyUrl.host() != proxy.host() ||
00341        m_request.proxyUrl.port() != proxy.port() ||
00342        (!proxy.user().isEmpty() && proxy.user() != m_request.proxyUrl.user()) ||
00343        (!proxy.pass().isEmpty() && proxy.pass() != m_request.proxyUrl.pass()) )
00344   {
00345     m_request.proxyUrl = proxy;
00346 
00347     kDebug(7113) << "Using proxy:" << m_request.useProxy()
00348                  << "URL: " << m_request.proxyUrl.url()
00349                  << "Realm: " << m_proxyAuth.realm;
00350   }
00351 #endif
00352     m_request.proxyUrl = proxy;
00353     kDebug(7113) << "Using proxy:" << isValidProxy(m_request.proxyUrl)
00354                  << "URL: " << m_request.proxyUrl.url();
00355                  //<< "Realm: " << m_proxyAuth.realm;
00356 
00357   if (isValidProxy(m_request.proxyUrl)) {
00358       if (m_request.proxyUrl.protocol() == "socks") {
00359           // Let Qt do SOCKS because it's already implemented there...
00360           proxyType = QNetworkProxy::Socks5Proxy;
00361       } else if (isAutoSsl()) {
00362           // and for HTTPS we use HTTP CONNECT on the proxy server, also implemented in Qt.
00363           // This is the usual way to handle SSL proxying.
00364           proxyType = QNetworkProxy::HttpProxy;
00365       }
00366       m_request.proxyUrl = proxy;
00367   } else {
00368       m_request.proxyUrl = KUrl();
00369   }
00370 
00371   QNetworkProxy appProxy(proxyType, m_request.proxyUrl.host(), m_request.proxyUrl.port(),
00372                          m_request.proxyUrl.user(), m_request.proxyUrl.pass());
00373   QNetworkProxy::setApplicationProxy(appProxy);
00374 
00375   if (isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) {
00376     m_request.isKeepAlive = config()->readEntry("PersistentProxyConnection", false);
00377     kDebug(7113) << "Enable Persistent Proxy Connection: "
00378                  << m_request.isKeepAlive;
00379   }
00380 
00381   m_request.useCookieJar = config()->readEntry("Cookies", false);
00382   m_request.cacheTag.useCache = config()->readEntry("UseCache", true);
00383   m_request.preferErrorPage = config()->readEntry("errorPage", true);
00384   m_request.doNotAuthenticate = config()->readEntry("no-auth", false);
00385   m_strCacheDir = config()->readPathEntry("CacheDir", QString());
00386   m_maxCacheAge = config()->readEntry("MaxCacheAge", DEFAULT_MAX_CACHE_AGE);
00387   m_request.windowId = config()->readEntry("window-id");
00388 
00389   kDebug(7113) << "Window Id =" << m_request.windowId;
00390   kDebug(7113) << "ssl_was_in_use ="
00391                << metaData ("ssl_was_in_use");
00392 
00393   m_request.referrer.clear();
00394   // RFC 2616: do not send the referrer if the referrer page was served using SSL and
00395   //           the current page does not use SSL.
00396   if ( config()->readEntry("SendReferrer", true) &&
00397        (isEncryptedHttpVariety(m_protocol) || metaData ("ssl_was_in_use") != "TRUE" ) )
00398   {
00399      KUrl refUrl(metaData("referrer"));
00400      if (refUrl.isValid()) {
00401         // Sanitize
00402         QString protocol = refUrl.protocol();
00403         if (protocol.startsWith("webdav")) {
00404            protocol.replace(0, 6, "http");
00405            refUrl.setProtocol(protocol);
00406         }
00407 
00408         if (protocol.startsWith("http")) {
00409            m_request.referrer = refUrl.toEncoded(QUrl::RemoveUserInfo | QUrl::RemoveFragment);
00410         }
00411      }
00412   }
00413 
00414   if (config()->readEntry("SendLanguageSettings", true)) {
00415       m_request.charsets = config()->readEntry( "Charsets", "iso-8859-1" );
00416       if (!m_request.charsets.isEmpty()) {
00417           m_request.charsets += DEFAULT_PARTIAL_CHARSET_HEADER;
00418       }
00419       m_request.languages = config()->readEntry( "Languages", DEFAULT_LANGUAGE_HEADER );
00420   } else {
00421       m_request.charsets.clear();
00422       m_request.languages.clear();
00423   }
00424 
00425   // Adjust the offset value based on the "resume" meta-data.
00426   QString resumeOffset = metaData("resume");
00427   if (!resumeOffset.isEmpty()) {
00428      m_request.offset = resumeOffset.toULongLong();
00429   } else {
00430      m_request.offset = 0;
00431   }
00432   // Same procedure for endoffset.
00433   QString resumeEndOffset = metaData("resume_until");
00434   if (!resumeEndOffset.isEmpty()) {
00435      m_request.endoffset = resumeEndOffset.toULongLong();
00436   } else {
00437      m_request.endoffset = 0;
00438   }
00439 
00440   m_request.disablePassDialog = config()->readEntry("DisablePassDlg", false);
00441   m_request.allowTransferCompression = config()->readEntry("AllowCompressedPage", true);
00442   m_request.id = metaData("request-id");
00443 
00444   // Store user agent for this host.
00445   if (config()->readEntry("SendUserAgent", true)) {
00446      m_request.userAgent = metaData("UserAgent");
00447   } else {
00448      m_request.userAgent.clear();
00449   }
00450 
00451   // Deal with cache cleaning.
00452   // TODO: Find a smarter way to deal with cleaning the
00453   // cache ?
00454   if (m_request.cacheTag.useCache) {
00455      cleanCache();
00456   }
00457 
00458   m_request.responseCode = 0;
00459   m_request.prevResponseCode = 0;
00460 
00461   delete m_wwwAuth;
00462   m_wwwAuth = 0;
00463   delete m_socketProxyAuth;
00464   m_socketProxyAuth = 0;
00465 
00466   // Obtain timeout values
00467   m_remoteRespTimeout = responseTimeout();
00468 
00469   // Bounce back the actual referrer sent
00470   setMetaData("referrer", m_request.referrer);
00471 
00472   // Follow HTTP/1.1 spec and enable keep-alive by default
00473   // unless the remote side tells us otherwise or we determine
00474   // the persistent link has been terminated by the remote end.
00475   m_request.isKeepAlive = true;
00476   m_request.keepAliveTimeout = 0;
00477 
00478   // A single request can require multiple exchanges with the remote
00479   // server due to authentication challenges or SSL tunneling.
00480   // m_isFirstRequest is a flag that indicates whether we are
00481   // still processing the first request. This is important because we
00482   // should not force a close of a keep-alive connection in the middle
00483   // of the first request.
00484   // m_isFirstRequest is set to "true" whenever a new connection is
00485   // made in httpOpenConnection()
00486   m_isFirstRequest = false;
00487 }
00488 
00489 void HTTPProtocol::setHost( const QString& host, quint16 port,
00490                             const QString& user, const QString& pass )
00491 {
00492   // Reset the webdav-capable flags for this host
00493   if ( m_request.url.host() != host )
00494     m_davHostOk = m_davHostUnsupported = false;
00495 
00496   m_request.url.setHost(host);
00497 
00498   // is it an IPv6 address?
00499   if (host.indexOf(':') == -1) {
00500       m_request.encoded_hostname = QUrl::toAce(host);
00501   } else  {
00502       int pos = host.indexOf('%');
00503       if (pos == -1)
00504         m_request.encoded_hostname = '[' + host + ']';
00505       else
00506         // don't send the scope-id in IPv6 addresses to the server
00507         m_request.encoded_hostname = '[' + host.left(pos) + ']';
00508   }
00509   m_request.url.setPort((port > 0 && port != defaultPort()) ? port : -1);
00510   m_request.url.setUser(user);
00511   m_request.url.setPass(pass);
00512 
00513   //TODO need to do anything about proxying?
00514 
00515   kDebug(7113) << "Hostname is now:" << m_request.url.host()
00516                << "(" << m_request.encoded_hostname << ")";
00517 }
00518 
00519 bool HTTPProtocol::maybeSetRequestUrl(const KUrl &u)
00520 {
00521   kDebug (7113) << u.url();
00522 
00523   m_request.url = u;
00524   m_request.url.setPort(u.port(defaultPort()) != defaultPort() ? u.port() : -1);
00525 
00526   if (u.host().isEmpty()) {
00527      error( KIO::ERR_UNKNOWN_HOST, i18n("No host specified."));
00528      return false;
00529   }
00530 
00531   if (u.path().isEmpty()) {
00532      KUrl newUrl(u);
00533      newUrl.setPath("/");
00534      redirection(newUrl);
00535      finished();
00536      return false;
00537   }
00538 
00539   return true;
00540 }
00541 
00542 void HTTPProtocol::proceedUntilResponseContent( bool dataInternal /* = false */ )
00543 {
00544   kDebug (7113);
00545   if (!(proceedUntilResponseHeader() && readBody(dataInternal))) {
00546       return;
00547   }
00548 
00549   httpClose(m_request.isKeepAlive);
00550 
00551   // if data is required internally, don't finish,
00552   // it is processed before we finish()
00553   if (!dataInternal) {
00554       if ((m_request.responseCode == 204) &&
00555           ((m_request.method == HTTP_GET) || (m_request.method == HTTP_POST))) {
00556           error(ERR_NO_CONTENT, "");
00557       } else {
00558           finished();
00559       }
00560   }
00561 }
00562 
00563 bool HTTPProtocol::proceedUntilResponseHeader()
00564 {
00565   kDebug (7113);
00566 
00567   // Retry the request until it succeeds or an unrecoverable error occurs.
00568   // Recoverable errors are, for example:
00569   // - Proxy or server authentication required: Ask for credentials and try again,
00570   //   this time with an authorization header in the request.
00571   // - Server-initiated timeout on keep-alive connection: Reconnect and try again
00572 
00573   while (true) {
00574       if (!sendQuery()) {
00575           return false;
00576       }
00577       if (readResponseHeader()) {
00578           // Success, finish the request.
00579 
00580           // Update our server connection state. Note that satisfying a request from cache
00581           // does not even touch the server connection, hence we should only update the server
00582           // connection state if not reading from cache.
00583           if (!m_request.cacheTag.readFromCache) {
00584               m_server.initFrom(m_request);
00585           }
00586           break;
00587       } else if (m_isError || m_isLoadingErrorPage) {
00588           // Unrecoverable error, abort everything.
00589           // Also, if we've just loaded an error page there is nothing more to do.
00590           // In that case we abort to avoid loops; some webservers manage to send 401 and
00591           // no authentication request. Or an auth request we don't understand.
00592           return false;
00593       }
00594 
00595       if (!m_request.isKeepAlive) {
00596           httpCloseConnection();
00597       }
00598 
00599       // update for the next go-around to have current information
00600       Q_ASSERT_X(!m_request.cacheTag.readFromCache, "proceedUntilResponseHeader()",
00601                  "retrying a request even though the result is cached?!");
00602       if (!m_request.cacheTag.readFromCache) {
00603           m_server.initFrom(m_request);
00604       }
00605   }
00606 
00607   // Do not save authorization if the current response code is
00608   // 4xx (client error) or 5xx (server error).
00609   kDebug(7113) << "Previous Response:" << m_request.prevResponseCode;
00610   kDebug(7113) << "Current Response:" << m_request.responseCode;
00611 
00612   setMetaData("responsecode", QString::number(m_request.responseCode));
00613   setMetaData("content-type", m_mimeType);
00614 
00615   // At this point sendBody() should have delivered any POST data.
00616   m_POSTbuf.clear();
00617 
00618   return true;
00619 }
00620 
00621 void HTTPProtocol::stat(const KUrl& url)
00622 {
00623   kDebug(7113) << url.url();
00624 
00625   if (!maybeSetRequestUrl(url))
00626       return;
00627   resetSessionSettings();
00628 
00629   if ( m_protocol != "webdav" && m_protocol != "webdavs" )
00630   {
00631     QString statSide = metaData(QString::fromLatin1("statSide"));
00632     if ( statSide != "source" )
00633     {
00634       // When uploading we assume the file doesn't exit
00635       error( ERR_DOES_NOT_EXIST, url.prettyUrl() );
00636       return;
00637     }
00638 
00639     // When downloading we assume it exists
00640     UDSEntry entry;
00641     entry.insert( KIO::UDSEntry::UDS_NAME, url.fileName() );
00642     entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG ); // a file
00643     entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH ); // readable by everybody
00644 
00645     statEntry( entry );
00646     finished();
00647     return;
00648   }
00649 
00650   davStatList( url );
00651 }
00652 
00653 void HTTPProtocol::listDir( const KUrl& url )
00654 {
00655   kDebug(7113) << url.url();
00656 
00657   if (!maybeSetRequestUrl(url))
00658     return;
00659   resetSessionSettings();
00660 
00661   davStatList( url, false );
00662 }
00663 
00664 void HTTPProtocol::davSetRequest( const QByteArray& requestXML )
00665 {
00666   // insert the document into the POST buffer, kill trailing zero byte
00667   m_POSTbuf = requestXML;
00668 }
00669 
00670 void HTTPProtocol::davStatList( const KUrl& url, bool stat )
00671 {
00672   UDSEntry entry;
00673 
00674   // check to make sure this host supports WebDAV
00675   if ( !davHostOk() )
00676     return;
00677 
00678   // Maybe it's a disguised SEARCH...
00679   QString query = metaData("davSearchQuery");
00680   if ( !query.isEmpty() )
00681   {
00682     QByteArray request = "<?xml version=\"1.0\"?>\r\n";
00683     request.append( "<D:searchrequest xmlns:D=\"DAV:\">\r\n" );
00684     request.append( query.toUtf8() );
00685     request.append( "</D:searchrequest>\r\n" );
00686 
00687     davSetRequest( request );
00688   } else {
00689     // We are only after certain features...
00690     QByteArray request;
00691     request = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
00692     "<D:propfind xmlns:D=\"DAV:\">";
00693 
00694     // insert additional XML request from the davRequestResponse metadata
00695     if ( hasMetaData( "davRequestResponse" ) )
00696       request += metaData( "davRequestResponse" ).toUtf8();
00697     else {
00698       // No special request, ask for default properties
00699       request += "<D:prop>"
00700       "<D:creationdate/>"
00701       "<D:getcontentlength/>"
00702       "<D:displayname/>"
00703       "<D:source/>"
00704       "<D:getcontentlanguage/>"
00705       "<D:getcontenttype/>"
00706       "<D:executable/>"
00707       "<D:getlastmodified/>"
00708       "<D:getetag/>"
00709       "<D:supportedlock/>"
00710       "<D:lockdiscovery/>"
00711       "<D:resourcetype/>"
00712       "</D:prop>";
00713     }
00714     request += "</D:propfind>";
00715 
00716     davSetRequest( request );
00717   }
00718 
00719   // WebDAV Stat or List...
00720   m_request.method = query.isEmpty() ? DAV_PROPFIND : DAV_SEARCH;
00721   m_request.url.setQuery(QString());
00722   m_request.cacheTag.policy = CC_Reload;
00723   m_request.davData.depth = stat ? 0 : 1;
00724   if (!stat)
00725      m_request.url.adjustPath(KUrl::AddTrailingSlash);
00726 
00727   proceedUntilResponseContent( true );
00728 
00729   // Has a redirection already been called? If so, we're done.
00730   if (m_isRedirection) {
00731     finished();
00732     return;
00733   }
00734 
00735   QDomDocument multiResponse;
00736   multiResponse.setContent( m_webDavDataBuf, true );
00737 
00738   bool hasResponse = false;
00739 
00740   for ( QDomNode n = multiResponse.documentElement().firstChild();
00741         !n.isNull(); n = n.nextSibling())
00742   {
00743     QDomElement thisResponse = n.toElement();
00744     if (thisResponse.isNull())
00745       continue;
00746 
00747     hasResponse = true;
00748 
00749     QDomElement href = thisResponse.namedItem( "href" ).toElement();
00750     if ( !href.isNull() )
00751     {
00752       entry.clear();
00753 
00754       QString urlStr = QUrl::fromPercentEncoding(href.text().toUtf8());
00755 #if 0 // qt4/kde4 say: it's all utf8...
00756       int encoding = remoteEncoding()->encodingMib();
00757       if ((encoding == 106) && (!KStringHandler::isUtf8(KUrl::decode_string(urlStr, 4).toLatin1())))
00758         encoding = 4; // Use latin1 if the file is not actually utf-8
00759 
00760       KUrl thisURL ( urlStr, encoding );
00761 #else
00762       KUrl thisURL( urlStr );
00763 #endif
00764 
00765       if ( thisURL.isValid() ) {
00766         QString name = thisURL.fileName();
00767 
00768         // base dir of a listDir(): name should be "."
00769         if ( !stat && thisURL.path(KUrl::AddTrailingSlash).length() == url.path(KUrl::AddTrailingSlash).length() )
00770           name = ".";
00771 
00772         entry.insert( KIO::UDSEntry::UDS_NAME, name.isEmpty() ? href.text() : name );
00773       }
00774 
00775       QDomNodeList propstats = thisResponse.elementsByTagName( "propstat" );
00776 
00777       davParsePropstats( propstats, entry );
00778 
00779       if ( stat )
00780       {
00781         // return an item
00782         statEntry( entry );
00783         finished();
00784         return;
00785       }
00786       else
00787       {
00788         listEntry( entry, false );
00789       }
00790     }
00791     else
00792     {
00793       kDebug(7113) << "Error: no URL contained in response to PROPFIND on" << url;
00794     }
00795   }
00796 
00797   if ( stat || !hasResponse )
00798   {
00799     error( ERR_DOES_NOT_EXIST, url.prettyUrl() );
00800   }
00801   else
00802   {
00803     listEntry( entry, true );
00804     finished();
00805   }
00806 }
00807 
00808 void HTTPProtocol::davGeneric( const KUrl& url, KIO::HTTP_METHOD method )
00809 {
00810   kDebug(7113) << url.url();
00811 
00812   if (!maybeSetRequestUrl(url))
00813     return;
00814   resetSessionSettings();
00815 
00816   // check to make sure this host supports WebDAV
00817   if ( !davHostOk() )
00818     return;
00819 
00820   // WebDAV method
00821   m_request.method = method;
00822   m_request.url.setQuery(QString());
00823   m_request.cacheTag.policy = CC_Reload;
00824 
00825   proceedUntilResponseContent( false );
00826 }
00827 
00828 int HTTPProtocol::codeFromResponse( const QString& response )
00829 {
00830   int firstSpace = response.indexOf( ' ' );
00831   int secondSpace = response.indexOf( ' ', firstSpace + 1 );
00832   return response.mid( firstSpace + 1, secondSpace - firstSpace - 1 ).toInt();
00833 }
00834 
00835 void HTTPProtocol::davParsePropstats( const QDomNodeList& propstats, UDSEntry& entry )
00836 {
00837   QString mimeType;
00838   bool foundExecutable = false;
00839   bool isDirectory = false;
00840   uint lockCount = 0;
00841   uint supportedLockCount = 0;
00842 
00843   for ( int i = 0; i < propstats.count(); i++)
00844   {
00845     QDomElement propstat = propstats.item(i).toElement();
00846 
00847     QDomElement status = propstat.namedItem( "status" ).toElement();
00848     if ( status.isNull() )
00849     {
00850       // error, no status code in this propstat
00851       kDebug(7113) << "Error, no status code in this propstat";
00852       return;
00853     }
00854 
00855     int code = codeFromResponse( status.text() );
00856 
00857     if ( code != 200 )
00858     {
00859       kDebug(7113) << "Warning: status code" << code << "(this may mean that some properties are unavailable";
00860       continue;
00861     }
00862 
00863     QDomElement prop = propstat.namedItem( "prop" ).toElement();
00864     if ( prop.isNull() )
00865     {
00866       kDebug(7113) << "Error: no prop segment in this propstat.";
00867       return;
00868     }
00869 
00870     if ( hasMetaData( "davRequestResponse" ) )
00871     {
00872       QDomDocument doc;
00873       doc.appendChild(prop);
00874       entry.insert( KIO::UDSEntry::UDS_XML_PROPERTIES, doc.toString() );
00875     }
00876 
00877     for ( QDomNode n = prop.firstChild(); !n.isNull(); n = n.nextSibling() )
00878     {
00879       QDomElement property = n.toElement();
00880       if (property.isNull())
00881         continue;
00882 
00883       if ( property.namespaceURI() != "DAV:" )
00884       {
00885         // break out - we're only interested in properties from the DAV namespace
00886         continue;
00887       }
00888 
00889       if ( property.tagName() == "creationdate" )
00890       {
00891         // Resource creation date. Should be is ISO 8601 format.
00892         entry.insert( KIO::UDSEntry::UDS_CREATION_TIME, parseDateTime( property.text(), property.attribute("dt") ) );
00893       }
00894       else if ( property.tagName() == "getcontentlength" )
00895       {
00896         // Content length (file size)
00897         entry.insert( KIO::UDSEntry::UDS_SIZE, property.text().toULong() );
00898       }
00899       else if ( property.tagName() == "displayname" )
00900       {
00901         // Name suitable for presentation to the user
00902         setMetaData( "davDisplayName", property.text() );
00903       }
00904       else if ( property.tagName() == "source" )
00905       {
00906         // Source template location
00907         QDomElement source = property.namedItem( "link" ).toElement()
00908                                       .namedItem( "dst" ).toElement();
00909         if ( !source.isNull() )
00910           setMetaData( "davSource", source.text() );
00911       }
00912       else if ( property.tagName() == "getcontentlanguage" )
00913       {
00914         // equiv. to Content-Language header on a GET
00915         setMetaData( "davContentLanguage", property.text() );
00916       }
00917       else if ( property.tagName() == "getcontenttype" )
00918       {
00919         // Content type (mime type)
00920         // This may require adjustments for other server-side webdav implementations
00921         // (tested with Apache + mod_dav 1.0.3)
00922         if ( property.text() == "httpd/unix-directory" )
00923         {
00924           isDirectory = true;
00925         }
00926         else
00927         {
00928       mimeType = property.text();
00929         }
00930       }
00931       else if ( property.tagName() == "executable" )
00932       {
00933         // File executable status
00934         if ( property.text() == "T" )
00935           foundExecutable = true;
00936 
00937       }
00938       else if ( property.tagName() == "getlastmodified" )
00939       {
00940         // Last modification date
00941         entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, parseDateTime( property.text(), property.attribute("dt") ) );
00942       }
00943       else if ( property.tagName() == "getetag" )
00944       {
00945         // Entity tag
00946         setMetaData( "davEntityTag", property.text() );
00947       }
00948       else if ( property.tagName() == "supportedlock" )
00949       {
00950         // Supported locking specifications
00951         for ( QDomNode n2 = property.firstChild(); !n2.isNull(); n2 = n2.nextSibling() )
00952         {
00953           QDomElement lockEntry = n2.toElement();
00954           if ( lockEntry.tagName() == "lockentry" )
00955           {
00956             QDomElement lockScope = lockEntry.namedItem( "lockscope" ).toElement();
00957             QDomElement lockType = lockEntry.namedItem( "locktype" ).toElement();
00958             if ( !lockScope.isNull() && !lockType.isNull() )
00959             {
00960               // Lock type was properly specified
00961               supportedLockCount++;
00962               QString scope = lockScope.firstChild().toElement().tagName();
00963               QString type = lockType.firstChild().toElement().tagName();
00964 
00965               setMetaData( QString("davSupportedLockScope%1").arg(supportedLockCount), scope );
00966               setMetaData( QString("davSupportedLockType%1").arg(supportedLockCount), type );
00967             }
00968           }
00969         }
00970       }
00971       else if ( property.tagName() == "lockdiscovery" )
00972       {
00973         // Lists the available locks
00974         davParseActiveLocks( property.elementsByTagName( "activelock" ), lockCount );
00975       }
00976       else if ( property.tagName() == "resourcetype" )
00977       {
00978         // Resource type. "Specifies the nature of the resource."
00979         if ( !property.namedItem( "collection" ).toElement().isNull() )
00980         {
00981           // This is a collection (directory)
00982           isDirectory = true;
00983         }
00984       }
00985       else
00986       {
00987         kDebug(7113) << "Found unknown webdav property: " << property.tagName();
00988       }
00989     }
00990   }
00991 
00992   setMetaData( "davLockCount", QString("%1").arg(lockCount) );
00993   setMetaData( "davSupportedLockCount", QString("%1").arg(supportedLockCount) );
00994 
00995   entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, isDirectory ? S_IFDIR : S_IFREG );
00996 
00997   if ( foundExecutable || isDirectory )
00998   {
00999     // File was executable, or is a directory.
01000     entry.insert( KIO::UDSEntry::UDS_ACCESS, 0700 );
01001   }
01002   else
01003   {
01004     entry.insert( KIO::UDSEntry::UDS_ACCESS, 0600 );
01005   }
01006 
01007   if ( !isDirectory && !mimeType.isEmpty() )
01008   {
01009     entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, mimeType );
01010   }
01011 }
01012 
01013 void HTTPProtocol::davParseActiveLocks( const QDomNodeList& activeLocks,
01014                                         uint& lockCount )
01015 {
01016   for ( int i = 0; i < activeLocks.count(); i++ )
01017   {
01018     QDomElement activeLock = activeLocks.item(i).toElement();
01019 
01020     lockCount++;
01021     // required
01022     QDomElement lockScope = activeLock.namedItem( "lockscope" ).toElement();
01023     QDomElement lockType = activeLock.namedItem( "locktype" ).toElement();
01024     QDomElement lockDepth = activeLock.namedItem( "depth" ).toElement();
01025     // optional
01026     QDomElement lockOwner = activeLock.namedItem( "owner" ).toElement();
01027     QDomElement lockTimeout = activeLock.namedItem( "timeout" ).toElement();
01028     QDomElement lockToken = activeLock.namedItem( "locktoken" ).toElement();
01029 
01030     if ( !lockScope.isNull() && !lockType.isNull() && !lockDepth.isNull() )
01031     {
01032       // lock was properly specified
01033       lockCount++;
01034       QString scope = lockScope.firstChild().toElement().tagName();
01035       QString type = lockType.firstChild().toElement().tagName();
01036       QString depth = lockDepth.text();
01037 
01038       setMetaData( QString("davLockScope%1").arg( lockCount ), scope );
01039       setMetaData( QString("davLockType%1").arg( lockCount ), type );
01040       setMetaData( QString("davLockDepth%1").arg( lockCount ), depth );
01041 
01042       if ( !lockOwner.isNull() )
01043         setMetaData( QString("davLockOwner%1").arg( lockCount ), lockOwner.text() );
01044 
01045       if ( !lockTimeout.isNull() )
01046         setMetaData( QString("davLockTimeout%1").arg( lockCount ), lockTimeout.text() );
01047 
01048       if ( !lockToken.isNull() )
01049       {
01050         QDomElement tokenVal = lockScope.namedItem( "href" ).toElement();
01051         if ( !tokenVal.isNull() )
01052           setMetaData( QString("davLockToken%1").arg( lockCount ), tokenVal.text() );
01053       }
01054     }
01055   }
01056 }
01057 
01058 long HTTPProtocol::parseDateTime( const QString& input, const QString& type )
01059 {
01060   if ( type == "dateTime.tz" )
01061   {
01062     return KDateTime::fromString( input, KDateTime::ISODate ).toTime_t();
01063   }
01064   else if ( type == "dateTime.rfc1123" )
01065   {
01066     return KDateTime::fromString( input, KDateTime::RFCDate ).toTime_t();
01067   }
01068 
01069   // format not advertised... try to parse anyway
01070   time_t time = KDateTime::fromString( input, KDateTime::RFCDate ).toTime_t();
01071   if ( time != 0 )
01072     return time;
01073 
01074   return KDateTime::fromString( input, KDateTime::ISODate ).toTime_t();
01075 }
01076 
01077 QString HTTPProtocol::davProcessLocks()
01078 {
01079   if ( hasMetaData( "davLockCount" ) )
01080   {
01081     QString response("If:");
01082     int numLocks;
01083     numLocks = metaData( "davLockCount" ).toInt();
01084     bool bracketsOpen = false;
01085     for ( int i = 0; i < numLocks; i++ )
01086     {
01087       if ( hasMetaData( QString("davLockToken%1").arg(i) ) )
01088       {
01089         if ( hasMetaData( QString("davLockURL%1").arg(i) ) )
01090         {
01091           if ( bracketsOpen )
01092           {
01093             response += ')';
01094             bracketsOpen = false;
01095           }
01096           response += " <" + metaData( QString("davLockURL%1").arg(i) ) + '>';
01097         }
01098 
01099         if ( !bracketsOpen )
01100         {
01101           response += " (";
01102           bracketsOpen = true;
01103         }
01104         else
01105         {
01106           response += ' ';
01107         }
01108 
01109         if ( hasMetaData( QString("davLockNot%1").arg(i) ) )
01110           response += "Not ";
01111 
01112         response += '<' + metaData( QString("davLockToken%1").arg(i) ) + '>';
01113       }
01114     }
01115 
01116     if ( bracketsOpen )
01117       response += ')';
01118 
01119     response += "\r\n";
01120     return response;
01121   }
01122 
01123   return QString();
01124 }
01125 
01126 bool HTTPProtocol::davHostOk()
01127 {
01128   // FIXME needs to be reworked. Switched off for now.
01129   return true;
01130 
01131   // cached?
01132   if ( m_davHostOk )
01133   {
01134     kDebug(7113) << "true";
01135     return true;
01136   }
01137   else if ( m_davHostUnsupported )
01138   {
01139     kDebug(7113) << " false";
01140     davError( -2 );
01141     return false;
01142   }
01143 
01144   m_request.method = HTTP_OPTIONS;
01145 
01146   // query the server's capabilities generally, not for a specific URL
01147   m_request.url.setPath("*");
01148   m_request.url.setQuery(QString());
01149   m_request.cacheTag.policy = CC_Reload;
01150 
01151   // clear davVersions variable, which holds the response to the DAV: header
01152   m_davCapabilities.clear();
01153 
01154   proceedUntilResponseHeader();
01155 
01156   if (m_davCapabilities.count())
01157   {
01158     for (int i = 0; i < m_davCapabilities.count(); i++)
01159     {
01160       bool ok;
01161       uint verNo = m_davCapabilities[i].toUInt(&ok);
01162       if (ok && verNo > 0 && verNo < 3)
01163       {
01164         m_davHostOk = true;
01165         kDebug(7113) << "Server supports DAV version" << verNo;
01166       }
01167     }
01168 
01169     if ( m_davHostOk )
01170       return true;
01171   }
01172 
01173   m_davHostUnsupported = true;
01174   davError( -2 );
01175   return false;
01176 }
01177 
01178 // This function is for closing proceedUntilResponseHeader(); requests
01179 // Required because there may or may not be further info expected
01180 void HTTPProtocol::davFinished()
01181 {
01182   // TODO: Check with the DAV extension developers
01183   httpClose(m_request.isKeepAlive);
01184   finished();
01185 }
01186 
01187 void HTTPProtocol::mkdir( const KUrl& url, int )
01188 {
01189   kDebug(7113) << url.url();
01190 
01191   if (!maybeSetRequestUrl(url))
01192     return;
01193   resetSessionSettings();
01194 
01195   m_request.method = DAV_MKCOL;
01196   m_request.url.setQuery(QString());
01197   m_request.cacheTag.policy = CC_Reload;
01198 
01199   proceedUntilResponseHeader();
01200 
01201   if ( m_request.responseCode == 201 )
01202     davFinished();
01203   else
01204     davError();
01205 }
01206 
01207 void HTTPProtocol::get( const KUrl& url )
01208 {
01209   kDebug(7113) << url.url();
01210 
01211   if (!maybeSetRequestUrl(url))
01212     return;
01213   resetSessionSettings();
01214 
01215   m_request.method = HTTP_GET;
01216 
01217   QString tmp(metaData("cache"));
01218   if (!tmp.isEmpty())
01219     m_request.cacheTag.policy = parseCacheControl(tmp);
01220   else
01221     m_request.cacheTag.policy = DEFAULT_CACHE_CONTROL;
01222 
01223   proceedUntilResponseContent();
01224 }
01225 
01226 void HTTPProtocol::put( const KUrl &url, int, KIO::JobFlags flags )
01227 {
01228   kDebug(7113) << url.url();
01229 
01230   if (!maybeSetRequestUrl(url))
01231     return;
01232   resetSessionSettings();
01233 
01234   // Webdav hosts are capable of observing overwrite == false
01235   if (!(flags & KIO::Overwrite) && m_protocol.startsWith("webdav")) {
01236     // check to make sure this host supports WebDAV
01237     if ( !davHostOk() )
01238       return;
01239 
01240     QByteArray request = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
01241     "<D:propfind xmlns:D=\"DAV:\"><D:prop>"
01242       "<D:creationdate/>"
01243       "<D:getcontentlength/>"
01244       "<D:displayname/>"
01245       "<D:resourcetype/>"
01246       "</D:prop></D:propfind>";
01247 
01248     davSetRequest( request );
01249 
01250     // WebDAV Stat or List...
01251     m_request.method = DAV_PROPFIND;
01252     m_request.url.setQuery(QString());
01253     m_request.cacheTag.policy = CC_Reload;
01254     m_request.davData.depth = 0;
01255 
01256     proceedUntilResponseContent(true);
01257 
01258     if (m_request.responseCode == 207) {
01259       error(ERR_FILE_ALREADY_EXIST, QString());
01260       return;
01261     }
01262 
01263     m_isError = false;
01264   }
01265 
01266   m_request.method = HTTP_PUT;
01267   m_request.url.setQuery(QString());
01268   m_request.cacheTag.policy = CC_Reload;
01269 
01270   proceedUntilResponseHeader();
01271 
01272   kDebug(7113) << "error = " << m_isError;
01273   if (m_isError)
01274     return;
01275 
01276   kDebug(7113) << "responseCode = " << m_request.responseCode;
01277 
01278   httpClose(false); // Always close connection.
01279 
01280   if ( (m_request.responseCode >= 200) && (m_request.responseCode < 300) )
01281     finished();
01282   else
01283     httpError();
01284 }
01285 
01286 void HTTPProtocol::copy( const KUrl& src, const KUrl& dest, int, KIO::JobFlags flags )
01287 {
01288   kDebug(7113) << src.url() << "->" << dest.url();
01289 
01290   if (!maybeSetRequestUrl(dest) || !maybeSetRequestUrl(src))
01291     return;
01292   resetSessionSettings();
01293 
01294   // destination has to be "http(s)://..."
01295   KUrl newDest = dest;
01296   if (newDest.protocol() == "webdavs")
01297     newDest.setProtocol("https");
01298   else
01299     newDest.setProtocol("http");
01300 
01301   m_request.method = DAV_COPY;
01302   m_request.davData.desturl = newDest.url();
01303   m_request.davData.overwrite = (flags & KIO::Overwrite);
01304   m_request.url.setQuery(QString());
01305   m_request.cacheTag.policy = CC_Reload;
01306 
01307   proceedUntilResponseHeader();
01308 
01309   // The server returns a HTTP/1.1 201 Created or 204 No Content on successful completion
01310   if ( m_request.responseCode == 201 || m_request.responseCode == 204 )
01311     davFinished();
01312   else
01313     davError();
01314 }
01315 
01316 void HTTPProtocol::rename( const KUrl& src, const KUrl& dest, KIO::JobFlags flags )
01317 {
01318   kDebug(7113) << src.url() << "->" << dest.url();
01319 
01320   if (!maybeSetRequestUrl(dest) || !maybeSetRequestUrl(src))
01321     return;
01322   resetSessionSettings();
01323 
01324   // destination has to be "http://..."
01325   KUrl newDest = dest;
01326   if (newDest.protocol() == "webdavs")
01327     newDest.setProtocol("https");
01328   else
01329     newDest.setProtocol("http");
01330 
01331   m_request.method = DAV_MOVE;
01332   m_request.davData.desturl = newDest.url();
01333   m_request.davData.overwrite = (flags & KIO::Overwrite);
01334   m_request.url.setQuery(QString());
01335   m_request.cacheTag.policy = CC_Reload;
01336 
01337   proceedUntilResponseHeader();
01338 
01339   if ( m_request.responseCode == 201 )
01340     davFinished();
01341   else
01342     davError();
01343 }
01344 
01345 void HTTPProtocol::del( const KUrl& url, bool )
01346 {
01347   kDebug(7113) << url.url();
01348 
01349   if (!maybeSetRequestUrl(url))
01350     return;
01351   resetSessionSettings();
01352 
01353   m_request.method = HTTP_DELETE;
01354   m_request.url.setQuery(QString());;
01355   m_request.cacheTag.policy = CC_Reload;
01356 
01357   proceedUntilResponseHeader();
01358 
01359   // The server returns a HTTP/1.1 200 Ok or HTTP/1.1 204 No Content
01360   // on successful completion
01361   if ( m_protocol.startsWith( "webdav" ) ) {
01362     if ( m_request.responseCode == 200 || m_request.responseCode == 204 )
01363       davFinished();
01364     else
01365       davError();
01366   } else {
01367     if ( m_request.responseCode == 200 || m_request.responseCode == 204 )
01368       finished();
01369     else
01370       error( ERR_SLAVE_DEFINED, i18n( "The resource cannot be deleted." ) );
01371   }
01372 }
01373 
01374 void HTTPProtocol::post( const KUrl& url )
01375 {
01376   kDebug(7113) << url.url();
01377 
01378   if (!maybeSetRequestUrl(url))
01379     return;
01380   resetSessionSettings();
01381 
01382   m_request.method = HTTP_POST;
01383   m_request.cacheTag.policy= CC_Reload;
01384 
01385   proceedUntilResponseContent();
01386 }
01387 
01388 void HTTPProtocol::davLock( const KUrl& url, const QString& scope,
01389                             const QString& type, const QString& owner )
01390 {
01391   kDebug(7113) << url.url();
01392 
01393   if (!maybeSetRequestUrl(url))
01394     return;
01395   resetSessionSettings();
01396 
01397   m_request.method = DAV_LOCK;
01398   m_request.url.setQuery(QString());
01399   m_request.cacheTag.policy= CC_Reload;
01400 
01401   /* Create appropriate lock XML request. */
01402   QDomDocument lockReq;
01403 
01404   QDomElement lockInfo = lockReq.createElementNS( "DAV:", "lockinfo" );
01405   lockReq.appendChild( lockInfo );
01406 
01407   QDomElement lockScope = lockReq.createElement( "lockscope" );
01408   lockInfo.appendChild( lockScope );
01409 
01410   lockScope.appendChild( lockReq.createElement( scope ) );
01411 
01412   QDomElement lockType = lockReq.createElement( "locktype" );
01413   lockInfo.appendChild( lockType );
01414 
01415   lockType.appendChild( lockReq.createElement( type ) );
01416 
01417   if ( !owner.isNull() ) {
01418     QDomElement ownerElement = lockReq.createElement( "owner" );
01419     lockReq.appendChild( ownerElement );
01420 
01421     QDomElement ownerHref = lockReq.createElement( "href" );
01422     ownerElement.appendChild( ownerHref );
01423 
01424     ownerHref.appendChild( lockReq.createTextNode( owner ) );
01425   }
01426 
01427   // insert the document into the POST buffer
01428   m_POSTbuf = lockReq.toByteArray();
01429 
01430   proceedUntilResponseContent( true );
01431 
01432   if ( m_request.responseCode == 200 ) {
01433     // success
01434     QDomDocument multiResponse;
01435     multiResponse.setContent( m_webDavDataBuf, true );
01436 
01437     QDomElement prop = multiResponse.documentElement().namedItem( "prop" ).toElement();
01438 
01439     QDomElement lockdiscovery = prop.namedItem( "lockdiscovery" ).toElement();
01440 
01441     uint lockCount = 0;
01442     davParseActiveLocks( lockdiscovery.elementsByTagName( "activelock" ), lockCount );
01443 
01444     setMetaData( "davLockCount", QString("%1").arg( lockCount ) );
01445 
01446     finished();
01447 
01448   } else
01449     davError();
01450 }
01451 
01452 void HTTPProtocol::davUnlock( const KUrl& url )
01453 {
01454   kDebug(7113) << url.url();
01455 
01456   if (!maybeSetRequestUrl(url))
01457     return;
01458   resetSessionSettings();
01459 
01460   m_request.method = DAV_UNLOCK;
01461   m_request.url.setQuery(QString());
01462   m_request.cacheTag.policy= CC_Reload;
01463 
01464   proceedUntilResponseContent( true );
01465 
01466   if ( m_request.responseCode == 200 )
01467     finished();
01468   else
01469     davError();
01470 }
01471 
01472 QString HTTPProtocol::davError( int code /* = -1 */, const QString &_url )
01473 {
01474   bool callError = false;
01475   if ( code == -1 ) {
01476     code = m_request.responseCode;
01477     callError = true;
01478   }
01479   if ( code == -2 ) {
01480     callError = true;
01481   }
01482 
01483   QString url = _url;
01484   if ( !url.isNull() )
01485     url = m_request.url.url();
01486 
01487   QString action, errorString;
01488   KIO::Error kError;
01489 
01490   // for 412 Precondition Failed
01491   QString ow = i18n( "Otherwise, the request would have succeeded." );
01492 
01493   switch ( m_request.method ) {
01494     case DAV_PROPFIND:
01495       action = i18nc( "request type", "retrieve property values" );
01496       break;
01497     case DAV_PROPPATCH:
01498       action = i18nc( "request type", "set property values" );
01499       break;
01500     case DAV_MKCOL:
01501       action = i18nc( "request type", "create the requested folder" );
01502       break;
01503     case DAV_COPY:
01504       action = i18nc( "request type", "copy the specified file or folder" );
01505       break;
01506     case DAV_MOVE:
01507       action = i18nc( "request type", "move the specified file or folder" );
01508       break;
01509     case DAV_SEARCH:
01510       action = i18nc( "request type", "search in the specified folder" );
01511       break;
01512     case DAV_LOCK:
01513       action = i18nc( "request type", "lock the specified file or folder" );
01514       break;
01515     case DAV_UNLOCK:
01516       action = i18nc( "request type", "unlock the specified file or folder" );
01517       break;
01518     case HTTP_DELETE:
01519       action = i18nc( "request type", "delete the specified file or folder" );
01520       break;
01521     case HTTP_OPTIONS:
01522       action = i18nc( "request type", "query the server's capabilities" );
01523       break;
01524     case HTTP_GET:
01525       action = i18nc( "request type", "retrieve the contents of the specified file or folder" );
01526       break;
01527     case HTTP_PUT:
01528     case HTTP_POST:
01529     case HTTP_HEAD:
01530     default:
01531       // this should not happen, this function is for webdav errors only
01532       Q_ASSERT(0);
01533   }
01534 
01535   // default error message if the following code fails
01536   kError = ERR_INTERNAL;
01537   errorString = i18nc("%1: code, %2: request type", "An unexpected error (%1) occurred "
01538                       "while attempting to %2.", code, action);
01539 
01540   switch ( code )
01541   {
01542     case -2:
01543       // internal error: OPTIONS request did not specify DAV compliance
01544       kError = ERR_UNSUPPORTED_PROTOCOL;
01545       errorString = i18n("The server does not support the WebDAV protocol.");
01546       break;
01547     case 207:
01548       // 207 Multi-status
01549     {
01550       // our error info is in the returned XML document.
01551       // retrieve the XML document
01552 
01553       // there was an error retrieving the XML document.
01554       // ironic, eh?
01555       if ( !readBody( true ) && m_isError )
01556         return QString();
01557 
01558       QStringList errors;
01559       QDomDocument multiResponse;
01560 
01561       multiResponse.setContent( m_webDavDataBuf, true );
01562 
01563       QDomElement multistatus = multiResponse.documentElement().namedItem( "multistatus" ).toElement();
01564 
01565       QDomNodeList responses = multistatus.elementsByTagName( "response" );
01566 
01567       for (int i = 0; i < responses.count(); i++)
01568       {
01569         int errCode;
01570         QString errUrl;
01571 
01572         QDomElement response = responses.item(i).toElement();
01573         QDomElement code = response.namedItem( "status" ).toElement();
01574 
01575         if ( !code.isNull() )
01576         {
01577           errCode = codeFromResponse( code.text() );
01578           QDomElement href = response.namedItem( "href" ).toElement();
01579           if ( !href.isNull() )
01580             errUrl = href.text();
01581           errors << davError( errCode, errUrl );
01582         }
01583       }
01584 
01585       //kError = ERR_SLAVE_DEFINED;
01586       errorString = i18nc( "%1: request type, %2: url",
01587                            "An error occurred while attempting to %1, %2. A "
01588                            "summary of the reasons is below.", action, url );
01589 
01590       errorString += "<ul>";
01591 
01592       for ( QStringList::const_iterator it = errors.constBegin(); it != errors.constEnd(); ++it )
01593         errorString += "<li>" + *it + "</li>";
01594 
01595       errorString += "</ul>";
01596     }
01597     case 403:
01598     case 500: // hack: Apache mod_dav returns this instead of 403 (!)
01599       // 403 Forbidden
01600       kError = ERR_ACCESS_DENIED;
01601       errorString = i18nc( "%1: request type", "Access was denied while attempting to %1.",  action );
01602       break;
01603     case 405:
01604       // 405 Method Not Allowed
01605       if ( m_request.method == DAV_MKCOL )
01606       {
01607         kError = ERR_DIR_ALREADY_EXIST;
01608         errorString = i18n("The specified folder already exists.");
01609       }
01610       break;
01611     case 409:
01612       // 409 Conflict
01613       kError = ERR_ACCESS_DENIED;
01614       errorString = i18n("A resource cannot be created at the destination "
01615                   "until one or more intermediate collections (folders) "
01616                   "have been created.");
01617       break;
01618     case 412:
01619       // 412 Precondition failed
01620       if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE )
01621       {
01622         kError = ERR_ACCESS_DENIED;
01623         errorString = i18n("The server was unable to maintain the liveness of "
01624                            "the properties listed in the propertybehavior XML "
01625                            "element or you attempted to overwrite a file while "
01626                            "requesting that files are not overwritten. %1",
01627                              ow );
01628 
01629       }
01630       else if ( m_request.method == DAV_LOCK )
01631       {
01632         kError = ERR_ACCESS_DENIED;
01633         errorString = i18n("The requested lock could not be granted. %1",  ow );
01634       }
01635       break;
01636     case 415:
01637       // 415 Unsupported Media Type
01638       kError = ERR_ACCESS_DENIED;
01639       errorString = i18n("The server does not support the request type of the body.");
01640       break;
01641     case 423:
01642       // 423 Locked
01643       kError = ERR_ACCESS_DENIED;
01644       errorString = i18nc( "%1: request type", "Unable to %1 because the resource is locked.",  action );
01645       break;
01646     case 425:
01647       // 424 Failed Dependency
01648       errorString = i18n("This action was prevented by another error.");
01649       break;
01650     case 502:
01651       // 502 Bad Gateway
01652       if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE )
01653       {
01654         kError = ERR_WRITE_ACCESS_DENIED;
01655         errorString = i18nc( "%1: request type", "Unable to %1 because the destination server refuses "
01656                            "to accept the file or folder.",  action );
01657       }
01658       break;
01659     case 507:
01660       // 507 Insufficient Storage
01661       kError = ERR_DISK_FULL;
01662       errorString = i18n("The destination resource does not have sufficient space "
01663                          "to record the state of the resource after the execution "
01664                          "of this method.");
01665       break;
01666   }
01667 
01668   // if ( kError != ERR_SLAVE_DEFINED )
01669   //errorString += " (" + url + ')';
01670 
01671   if ( callError )
01672     error( ERR_SLAVE_DEFINED, errorString );
01673 
01674   return errorString;
01675 }
01676 
01677 void HTTPProtocol::httpError()
01678 {
01679   QString action, errorString;
01680   KIO::Error kError;
01681 
01682   switch ( m_request.method ) {
01683     case HTTP_PUT:
01684       action = i18nc("request type", "upload %1", m_request.url.prettyUrl());
01685       break;
01686     default:
01687       // this should not happen, this function is for http errors only
01688       // ### WTF, what about HTTP_GET?
01689       Q_ASSERT(0);
01690   }
01691 
01692   // default error message if the following code fails
01693   kError = ERR_INTERNAL;
01694   errorString = i18nc("%1: response code, %2: request type",
01695                       "An unexpected error (%1) occurred while attempting to %2.",
01696                        m_request.responseCode, action);
01697 
01698   switch ( m_request.responseCode )
01699   {
01700     case 403:
01701     case 405:
01702     case 500: // hack: Apache mod_dav returns this instead of 403 (!)
01703       // 403 Forbidden
01704       // 405 Method Not Allowed
01705       kError = ERR_ACCESS_DENIED;
01706       errorString = i18nc( "%1: request type", "Access was denied while attempting to %1.",  action );
01707       break;
01708     case 409:
01709       // 409 Conflict
01710       kError = ERR_ACCESS_DENIED;
01711       errorString = i18n("A resource cannot be created at the destination "
01712                   "until one or more intermediate collections (folders) "
01713                   "have been created.");
01714       break;
01715     case 423:
01716       // 423 Locked
01717       kError = ERR_ACCESS_DENIED;
01718       errorString = i18nc( "%1: request type", "Unable to %1 because the resource is locked.",  action );
01719       break;
01720     case 502:
01721       // 502 Bad Gateway
01722       kError = ERR_WRITE_ACCESS_DENIED;
01723       errorString = i18nc( "%1: request type", "Unable to %1 because the destination server refuses "
01724                          "to accept the file or folder.",  action );
01725       break;
01726     case 507:
01727       // 507 Insufficient Storage
01728       kError = ERR_DISK_FULL;
01729       errorString = i18n("The destination resource does not have sufficient space "
01730                          "to record the state of the resource after the execution "
01731                          "of this method.");
01732       break;
01733   }
01734 
01735   // if ( kError != ERR_SLAVE_DEFINED )
01736   //errorString += " (" + url + ')';
01737 
01738   error( ERR_SLAVE_DEFINED, errorString );
01739 }
01740 
01741 void HTTPProtocol::setLoadingErrorPage()
01742 {
01743     if (m_isLoadingErrorPage) {
01744         kWarning(7113) << "called twice during one request, something is probably wrong.";
01745     }
01746     m_isLoadingErrorPage = true;
01747     SlaveBase::errorPage();
01748 }
01749 
01750 bool HTTPProtocol::isOffline(const KUrl &url)
01751 {
01752   const int NetWorkStatusUnknown = 1;
01753   const int NetWorkStatusOnline = 8;
01754 
01755   QDBusReply<int> reply =
01756     QDBusInterface( "org.kde.kded", "/modules/networkstatus", "org.kde.NetworkStatusModule" ).
01757     call( "status", url.url() );
01758 
01759   if ( reply.isValid() )
01760   {
01761      int result = reply;
01762      kDebug(7113) << "networkstatus status = " << result;
01763      return (result != NetWorkStatusUnknown) && (result != NetWorkStatusOnline);
01764   }
01765   kDebug(7113) << "networkstatus <unreachable>";
01766   return false; // On error, assume we are online
01767 }
01768 
01769 void HTTPProtocol::multiGet(const QByteArray &data)
01770 {
01771     QDataStream stream(data);
01772     quint32 n;
01773     stream >> n;
01774 
01775     kDebug(7113) << n;
01776 
01777     HTTPRequest saveRequest;
01778     if (m_isBusy)
01779         saveRequest = m_request;
01780 
01781     resetSessionSettings();
01782 
01783     for (unsigned i = 0; i < n; i++) {
01784         KUrl url;
01785         stream >> url >> mIncomingMetaData;
01786 
01787         if (!maybeSetRequestUrl(url))
01788             continue;
01789 
01790         //### should maybe call resetSessionSettings() if the server/domain is
01791         //    different from the last request!
01792 
01793         kDebug(7113) << url.url();
01794 
01795         m_request.method = HTTP_GET;
01796         m_request.isKeepAlive = true;   //readResponseHeader clears it if necessary
01797 
01798         QString tmp = metaData("cache");
01799         if (!tmp.isEmpty())
01800             m_request.cacheTag.policy= parseCacheControl(tmp);
01801         else
01802             m_request.cacheTag.policy= DEFAULT_CACHE_CONTROL;
01803 
01804         m_requestQueue.append(m_request);
01805     }
01806 
01807     if (m_isBusy)
01808         m_request = saveRequest;
01809 #if 0
01810     if (!m_isBusy) {
01811         m_isBusy = true;
01812         QMutableListIterator<HTTPRequest> it(m_requestQueue);
01813         while (it.hasNext()) {
01814             m_request = it.next();
01815             it.remove();
01816             proceedUntilResponseContent();
01817         }
01818         m_isBusy = false;
01819     }
01820 #endif
01821     if (!m_isBusy) {
01822         m_isBusy = true;
01823         QMutableListIterator<HTTPRequest> it(m_requestQueue);
01824         // send the requests
01825         while (it.hasNext()) {
01826             m_request = it.next();
01827             sendQuery();
01828             // save the request state so we can pick it up again in the collection phase
01829             it.setValue(m_request);
01830             kDebug(7113) << "check one: isKeepAlive =" << m_request.isKeepAlive;
01831             if (!m_request.cacheTag.readFromCache) {
01832                 m_server.initFrom(m_request);
01833             }
01834         }
01835         // collect the responses
01836         //### for the moment we use a hack: instead of saving and restoring request-id
01837         //    we just count up like ParallelGetJobs does.
01838         int requestId = 0;
01839         foreach (const HTTPRequest &r, m_requestQueue) {
01840             m_request = r;
01841             kDebug(7113) << "check two: isKeepAlive =" << m_request.isKeepAlive;
01842             setMetaData("request-id", QString::number(requestId++));
01843             sendAndKeepMetaData();
01844             if (!(readResponseHeader() && readBody())) {
01845                 return;
01846             }
01847             // the "next job" signal for ParallelGetJob is data of size zero which
01848             // readBody() sends without our intervention.
01849             kDebug(7113) << "check three: isKeepAlive =" << m_request.isKeepAlive;
01850             httpClose(m_request.isKeepAlive);  //actually keep-alive is mandatory for pipelining
01851         }
01852 
01853         finished();
01854         m_requestQueue.clear();
01855         m_isBusy = false;
01856     }
01857 }
01858 
01859 ssize_t HTTPProtocol::write (const void *_buf, size_t nbytes)
01860 {
01861   size_t sent = 0;
01862   const char* buf = static_cast<const char*>(_buf);
01863   while (sent < nbytes)
01864   {
01865     int n = TCPSlaveBase::write(buf + sent, nbytes - sent);
01866 
01867     if (n < 0) {
01868       // some error occurred
01869       return -1;
01870     }
01871 
01872     sent += n;
01873   }
01874 
01875   return sent;
01876 }
01877 
01878 void HTTPProtocol::clearUnreadBuffer()
01879 {
01880     m_unreadBuf.clear();
01881 }
01882 
01883 // Note: the implementation of unread/readBuffered assumes that unread will only
01884 // be used when there is extra data we don't want to handle, and not to wait for more data.
01885 void HTTPProtocol::unread(char *buf, size_t size)
01886 {
01887     // implement LIFO (stack) semantics
01888     const int newSize = m_unreadBuf.size() + size;
01889     m_unreadBuf.resize(newSize);
01890     for (size_t i = 0; i < size; i++) {
01891         m_unreadBuf.data()[newSize - i - 1] = buf[i];
01892     }
01893     if (size) {
01894         //hey, we still have data, closed connection or not!
01895         m_isEOF = false;
01896     }
01897 }
01898 
01899 size_t HTTPProtocol::readBuffered(char *buf, size_t size)
01900 {
01901     size_t bytesRead = 0;
01902     if (!m_unreadBuf.isEmpty()) {
01903         const int bufSize = m_unreadBuf.size();
01904         bytesRead = qMin((int)size, bufSize);
01905 
01906         for (size_t i = 0; i < bytesRead; i++) {
01907             buf[i] = m_unreadBuf.constData()[bufSize - i - 1];
01908         }
01909         m_unreadBuf.truncate(bufSize - bytesRead);
01910 
01911         // if we have an unread buffer, return here, since we may already have enough data to
01912         // complete the response, so we don't want to wait for more.
01913         return bytesRead;
01914     }
01915     if (bytesRead < size) {
01916         int rawRead = TCPSlaveBase::read(buf + bytesRead, size - bytesRead);
01917         if (rawRead < 1) {
01918             m_isEOF = true;
01919             return bytesRead;
01920         }
01921         bytesRead += rawRead;
01922     }
01923     return bytesRead;
01924 }
01925 
01926 //### this method will detect an n*(\r\n) sequence if it crosses invocations.
01927 //    it will look (n*2 - 1) bytes before start at most and never before buf, naturally.
01928 //    supported number of newlines are one and two, in line with HTTP syntax.
01929 // return true if numNewlines newlines were found.
01930 bool HTTPProtocol::readDelimitedText(char *buf, int *idx, int end, int numNewlines)
01931 {
01932     Q_ASSERT(numNewlines >=1 && numNewlines <= 2);
01933     char mybuf[64]; //somewhere close to the usual line length to avoid unread()ing too much
01934     int pos = *idx;
01935     while (pos < end && !m_isEOF) {
01936         int step = qMin((int)sizeof(mybuf), end - pos);
01937         if (m_isChunked) {
01938             //we might be reading the end of the very last chunk after which there is no data.
01939             //don't try to read any more bytes than there are because it causes stalls
01940             //(yes, it shouldn't stall but it does)
01941             step = 1;
01942         }
01943         size_t bufferFill = readBuffered(mybuf, step);
01944 
01945         for (size_t i = 0; i < bufferFill ; i++, pos++) {
01946             // we copy the data from mybuf to buf immediately and look for the newlines in buf.
01947             // that way we don't miss newlines split over several invocations of this method.
01948             buf[pos] = mybuf[i];
01949 
01950             // did we just copy one or two times the (usually) \r\n delimiter?
01951             // until we find even more broken webservers in the wild let's assume that they either
01952             // send \r\n (RFC compliant) or \n (broken) as delimiter...
01953             if (buf[pos] == '\n') {
01954                 bool found = numNewlines == 1;
01955                 if (!found) {   // looking for two newlines
01956                     found = ((pos >= 1 && buf[pos - 1] == '\n') ||
01957                              (pos >= 3 && buf[pos - 3] == '\r' && buf[pos - 2] == '\n' &&
01958                                           buf[pos - 1] == '\r'));
01959                 }
01960                 if (found) {
01961                     i++;    // unread bytes *after* CRLF
01962                     unread(&mybuf[i], bufferFill - i);
01963                     *idx = pos + 1;
01964                     return true;
01965                 }
01966             }
01967         }
01968     }
01969     *idx = pos;
01970     return false;
01971 }
01972 
01973 
01974 bool HTTPProtocol::httpShouldCloseConnection()
01975 {
01976   kDebug(7113) << "Keep Alive:" << m_request.isKeepAlive << "First:" << m_isFirstRequest;
01977 
01978   if (m_isFirstRequest || !isConnected()) {
01979       return false;
01980   }
01981 
01982   if (m_request.method != HTTP_GET && m_request.method != HTTP_POST) {
01983       return true;
01984   }
01985 
01986   if (m_request.proxyUrl != m_server.proxyUrl) {
01987       return true;
01988   }
01989 
01990   // TODO compare current proxy state against proxy needs of next request,
01991   // *when* we actually have variable proxy settings!
01992 
01993   if (isValidProxy(m_request.proxyUrl))  {
01994       if (m_request.proxyUrl != m_server.proxyUrl ||
01995           m_request.proxyUrl.user() != m_server.proxyUrl.user() ||
01996           m_request.proxyUrl.pass() != m_server.proxyUrl.pass()) {
01997           return true;
01998       }
01999   } else {
02000       if (m_request.url.host() != m_server.url.host() ||
02001           m_request.url.port() != m_server.url.port() ||
02002           m_request.url.user() != m_server.url.user() ||
02003           m_request.url.pass() != m_server.url.pass()) {
02004           return true;
02005       }
02006   }
02007   return false;
02008 }
02009 
02010 bool HTTPProtocol::httpOpenConnection()
02011 {
02012   kDebug(7113);
02013   m_server.clear();
02014 
02015   // Only save proxy auth information after proxy authentication has
02016   // actually taken place, which will set up exactly this connection.
02017   disconnect(socket(), SIGNAL(connected()),
02018              this, SLOT(saveProxyAuthenticationForSocket()));
02019 
02020   clearUnreadBuffer();
02021 
02022   bool connectOk = false;
02023   if (isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) {
02024       connectOk = connectToHost(m_request.proxyUrl.protocol(), m_request.proxyUrl.host(), m_request.proxyUrl.port());
02025   } else {
02026       connectOk = connectToHost(m_protocol, m_request.url.host(), m_request.url.port(defaultPort()));
02027   }
02028 
02029   if (!connectOk) {
02030       return false;
02031   }
02032 
02033 #if 0                           // QTcpSocket doesn't support this
02034   // Set our special socket option!!
02035   socket().setNoDelay(true);
02036 #endif
02037 
02038   m_isFirstRequest = true;
02039   m_server.initFrom(m_request);
02040   connected();
02041   return true;
02042 }
02043 
02044 bool HTTPProtocol::satisfyRequestFromCache(bool *success)
02045 {
02046     m_request.cacheTag.gzs = 0;
02047     m_request.cacheTag.readFromCache = false;
02048     m_request.cacheTag.writeToCache = false;
02049     m_request.cacheTag.isExpired = false;
02050     m_request.cacheTag.expireDate = 0;
02051     m_request.cacheTag.creationDate = 0;
02052 
02053     if (m_request.cacheTag.useCache) {
02054 
02055         m_request.cacheTag.gzs = checkCacheEntry();
02056         bool bCacheOnly = (m_request.cacheTag.policy == KIO::CC_CacheOnly);
02057         bool bOffline = isOffline(isValidProxy(m_request.proxyUrl) ? m_request.proxyUrl : m_request.url);
02058 
02059         if (bOffline && m_request.cacheTag.policy != KIO::CC_Reload) {
02060             m_request.cacheTag.policy= KIO::CC_CacheOnly;
02061         }
02062 
02063         if (m_request.cacheTag.policy == CC_Reload && m_request.cacheTag.gzs) {
02064             gzclose(m_request.cacheTag.gzs);
02065             m_request.cacheTag.gzs = 0;
02066         }
02067         if (m_request.cacheTag.policy == KIO::CC_CacheOnly ||
02068             m_request.cacheTag.policy == KIO::CC_Cache) {
02069             m_request.cacheTag.isExpired = false;
02070         }
02071 
02072         m_request.cacheTag.writeToCache = true;
02073 
02074         if (m_request.cacheTag.gzs && !m_request.cacheTag.isExpired) {
02075             // Cache entry is OK. Cache hit.
02076             m_request.cacheTag.readFromCache = true;
02077             *success = true;
02078             return true;
02079         } else if (!m_request.cacheTag.gzs) {
02080             // Cache miss.
02081             m_request.cacheTag.isExpired = false;
02082         } else {
02083             // Conditional cache hit. (Validate)
02084         }
02085 
02086         if (bCacheOnly) {
02087             error(ERR_DOES_NOT_EXIST, m_request.url.url());
02088             *success = false;
02089             return true;
02090         }
02091         if (bOffline) {
02092             error(ERR_COULD_NOT_CONNECT, m_request.url.url());
02093             *success = false;
02094             return true;
02095         }
02096     }
02097     *success = true;   //whatever
02098     return false;
02099 }
02100 
02101 QString HTTPProtocol::formatRequestUri() const
02102 {
02103     // Only specify protocol, host and port when they are not already clear, i.e. when
02104     // we handle HTTP proxying ourself and the proxy server needs to know them.
02105     // Sending protocol/host/port in other cases confuses some servers, and it's not their fault.
02106     if (isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) {
02107         KUrl u;
02108 
02109         QString protocol = m_protocol;
02110         if (protocol.startsWith("webdav")) {
02111             protocol.replace(0, strlen("webdav"), "http");
02112         }
02113         u.setProtocol(protocol);
02114 
02115         u.setHost(m_request.url.host());
02116         // if the URL contained the default port it should have been stripped earlier
02117         Q_ASSERT(m_request.url.port() != defaultPort());
02118         u.setPort(m_request.url.port());
02119         u.setEncodedPathAndQuery(m_request.url.encodedPathAndQuery(
02120                                     KUrl::LeaveTrailingSlash, KUrl::AvoidEmptyPath));
02121         return u.url();
02122     } else {
02123         return m_request.url.encodedPathAndQuery(KUrl::LeaveTrailingSlash, KUrl::AvoidEmptyPath);
02124     }
02125 }
02126 
02142 bool HTTPProtocol::sendQuery()
02143 {
02144   kDebug(7113);
02145 
02146   // Cannot have an https request without autoSsl!  This can
02147   // only happen if  the current installation does not support SSL...
02148   if (isEncryptedHttpVariety(m_protocol) && !isAutoSsl()) {
02149     error(ERR_UNSUPPORTED_PROTOCOL, m_protocol);
02150     return false;
02151   }
02152 
02153   bool cacheHasPage = false;
02154   if (satisfyRequestFromCache(&cacheHasPage)) {
02155     return cacheHasPage;
02156   }
02157 
02158   QString header;
02159 
02160   bool hasBodyData = false;
02161   bool hasDavData = false;
02162 
02163   {
02164     header = methodString(m_request.method);
02165     QString davHeader;
02166 
02167     // Fill in some values depending on the HTTP method to guide further processing
02168     switch (m_request.method)
02169     {
02170     case HTTP_GET:
02171     case HTTP_HEAD:
02172         break;
02173     case HTTP_PUT:
02174     case HTTP_POST:
02175         hasBodyData = true;
02176         m_request.cacheTag.writeToCache = false; // Do not put any result in the cache
02177         break;
02178     case HTTP_DELETE:
02179     case HTTP_OPTIONS:
02180         m_request.cacheTag.writeToCache = false; // Do not put any result in the cache
02181         break;
02182     case DAV_PROPFIND:
02183         hasDavData = true;
02184         davHeader = "Depth: ";
02185         if ( hasMetaData( "davDepth" ) )
02186         {
02187           kDebug(7113) << "Reading DAV depth from metadata: " << metaData( "davDepth" );
02188           davHeader += metaData( "davDepth" );
02189         }
02190         else
02191         {
02192           if ( m_request.davData.depth == 2 )
02193             davHeader += "infinity";
02194           else
02195             davHeader += QString("%1").arg( m_request.davData.depth );
02196         }
02197         davHeader += "\r\n";
02198         m_request.cacheTag.writeToCache = false; // Do not put any result in the cache
02199         break;
02200     case DAV_PROPPATCH:
02201         hasDavData = true;
02202         m_request.cacheTag.writeToCache = false; // Do not put any result in the cache
02203         break;
02204     case DAV_MKCOL:
02205         m_request.cacheTag.writeToCache = false; // Do not put any result in the cache
02206         break;
02207     case DAV_COPY:
02208     case DAV_MOVE:
02209         davHeader = "Destination: " + m_request.davData.desturl;
02210         // infinity depth means copy recursively
02211         // (optional for copy -> but is the desired action)
02212         davHeader += "\r\nDepth: infinity\r\nOverwrite: ";
02213         davHeader += m_request.davData.overwrite ? "T" : "F";
02214         davHeader += "\r\n";
02215         m_request.cacheTag.writeToCache = false; // Do not put any result in the cache
02216         break;
02217     case DAV_LOCK:
02218         davHeader = "Timeout: ";
02219         {
02220           uint timeout = 0;
02221           if ( hasMetaData( "davTimeout" ) )
02222             timeout = metaData( "davTimeout" ).toUInt();
02223           if ( timeout == 0 )
02224             davHeader += "Infinite";
02225           else
02226             davHeader += QString("Seconds-%1").arg(timeout);
02227         }
02228         davHeader += "\r\n";
02229         m_request.cacheTag.writeToCache = false; // Do not put any result in the cache
02230         hasDavData = true;
02231         break;
02232     case DAV_UNLOCK:
02233         davHeader = "Lock-token: " + metaData("davLockToken") + "\r\n";
02234         m_request.cacheTag.writeToCache = false; // Do not put any result in the cache
02235         break;
02236     case DAV_SEARCH:
02237         hasDavData = true;
02238         /* fall through */
02239     case DAV_SUBSCRIBE:
02240     case DAV_UNSUBSCRIBE:
02241     case DAV_POLL:
02242         m_request.cacheTag.writeToCache = false;
02243         break;
02244     default:
02245         error (ERR_UNSUPPORTED_ACTION, QString());
02246         return false;
02247     }
02248     // DAV_POLL; DAV_NOTIFY
02249 
02250     header += formatRequestUri() + " HTTP/1.1\r\n"; /* start header */
02251 
02252     /* support for virtual hosts and required by HTTP 1.1 */
02253     header += "Host: " + m_request.encoded_hostname;
02254     if (m_request.url.port(defaultPort()) != defaultPort()) {
02255       header += QString(":%1").arg(m_request.url.port());
02256     }
02257     header += "\r\n";
02258 
02259     // Support old HTTP/1.0 style keep-alive header for compatibility
02260     // purposes as well as performance improvements while giving end
02261     // users the ability to disable this feature for proxy servers that
02262     // don't support it, e.g. junkbuster proxy server.
02263     if (isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) {
02264         header += "Proxy-Connection: ";
02265     } else {
02266         header += "Connection: ";
02267     }
02268     if (m_request.isKeepAlive) {
02269         header += "Keep-Alive\r\n";
02270     } else {
02271         header += "close\r\n";
02272     }
02273 
02274     if (!m_request.userAgent.isEmpty())
02275     {
02276         header += "User-Agent: ";
02277         header += m_request.userAgent;
02278         header += "\r\n";
02279     }
02280 
02281     if (!m_request.referrer.isEmpty())
02282     {
02283         header += "Referer: "; //Don't try to correct spelling!
02284         header += m_request.referrer;
02285         header += "\r\n";
02286     }
02287 
02288     if ( m_request.endoffset > m_request.offset )
02289     {
02290         header += QString("Range: bytes=%1-%2\r\n").arg(KIO::number(m_request.offset))
02291                          .arg(KIO::number(m_request.endoffset));
02292         kDebug(7103) << "kio_http : Range = " << KIO::number(m_request.offset) <<
02293                         " - "  << KIO::number(m_request.endoffset);
02294     }
02295     else if ( m_request.offset > 0 && m_request.endoffset == 0 )
02296     {
02297         header += QString("Range: bytes=%1-\r\n").arg(KIO::number(m_request.offset));
02298         kDebug(7103) << "kio_http : Range = " << KIO::number(m_request.offset);
02299     }
02300 
02301     if ( m_request.cacheTag.policy== CC_Reload )
02302     {
02303       /* No caching for reload */
02304       header += "Pragma: no-cache\r\n"; /* for HTTP/1.0 caches */
02305       header += "Cache-control: no-cache\r\n"; /* for HTTP >=1.1 caches */
02306     }
02307 
02308     if (m_request.cacheTag.isExpired)
02309     {
02310       /* conditional get */
02311       if (!m_request.cacheTag.etag.isEmpty())
02312         header += "If-None-Match: "+m_request.cacheTag.etag+"\r\n";
02313       if (!m_request.cacheTag.lastModified.isEmpty())
02314         header += "If-Modified-Since: "+m_request.cacheTag.lastModified+"\r\n";
02315     }
02316 
02317     header += "Accept: ";
02318     QString acceptHeader = metaData("accept");
02319     if (!acceptHeader.isEmpty())
02320       header += acceptHeader;
02321     else
02322       header += DEFAULT_ACCEPT_HEADER;
02323     header += "\r\n";
02324 
02325     if (m_request.allowTransferCompression)
02326       header += "Accept-Encoding: x-gzip, x-deflate, gzip, deflate\r\n";
02327 
02328     if (!m_request.charsets.isEmpty())
02329       header += "Accept-Charset: " + m_request.charsets + "\r\n";
02330 
02331     if (!m_request.languages.isEmpty())
02332       header += "Accept-Language: " + m_request.languages + "\r\n";
02333 
02334     QString cookieStr;
02335     QString cookieMode = metaData("cookies").toLower();
02336     if (cookieMode == "none")
02337     {
02338       m_request.cookieMode = HTTPRequest::CookiesNone;
02339     }
02340     else if (cookieMode == "manual")
02341     {
02342       m_request.cookieMode = HTTPRequest::CookiesManual;
02343       cookieStr = metaData("setcookies");
02344     }
02345     else
02346     {
02347       m_request.cookieMode = HTTPRequest::CookiesAuto;
02348       if (m_request.useCookieJar)
02349         cookieStr = findCookies(m_request.url.url());
02350     }
02351 
02352     if (!cookieStr.isEmpty())
02353       header += cookieStr + "\r\n";
02354 
02355     QString customHeader = metaData( "customHTTPHeader" );
02356     if (!customHeader.isEmpty())
02357     {
02358       header += sanitizeCustomHTTPHeader(customHeader);
02359       header += "\r\n";
02360     }
02361 
02362     QString contentType = metaData("content-type");
02363     if ((m_request.method == HTTP_POST || m_request.method == HTTP_PUT)
02364     && !contentType.isEmpty())
02365     {
02366       header += contentType;
02367       header += "\r\n";
02368     }
02369 
02370     // Remember that at least one failed (with 401 or 407) request/response
02371     // roundtrip is necessary for the server to tell us that it requires
02372     // authentication.
02373     // We proactively add authentication headers if we have cached credentials
02374     // to avoid the extra roundtrip where possible.
02375     // (TODO: implement this caching)
02376     header += authenticationHeader();
02377 
02378     if ( m_protocol == "webdav" || m_protocol == "webdavs" )
02379     {
02380       header += davProcessLocks();
02381 
02382       // add extra webdav headers, if supplied
02383       davHeader += metaData("davHeader");
02384 
02385       // Set content type of webdav data
02386       if (hasDavData)
02387         davHeader += "Content-Type: text/xml; charset=utf-8\r\n";
02388 
02389       // add extra header elements for WebDAV
02390       header += davHeader;
02391     }
02392   }
02393 
02394   kDebug(7103) << "============ Sending Header:";
02395   foreach (const QString &s, header.split("\r\n", QString::SkipEmptyParts)) {
02396     kDebug(7103) << s;
02397   }
02398 
02399   // End the header iff there is no payload data. If we do have payload data
02400   // sendBody() will add another field to the header, Content-Length.
02401   if (!hasBodyData && !hasDavData)
02402     header += "\r\n";
02403 
02404   // Check the reusability of the current connection.
02405   if (httpShouldCloseConnection()) {
02406     httpCloseConnection();
02407   }
02408 
02409   // Now that we have our formatted header, let's send it!
02410   // Create a new connection to the remote machine if we do
02411   // not already have one...
02412   // NB: the !m_socketProxyAuth condition is a workaround for a proxied Qt socket sometimes
02413   // looking disconnected after receiving the initial 407 response.
02414   // I guess the Qt socket fails to hide the effect of  proxy-connection: close after receiving
02415   // the 407 header.
02416   if ((!isConnected() && !m_socketProxyAuth))
02417   {
02418     if (!httpOpenConnection())
02419     {
02420        kDebug(7113) << "Couldn't connect, oopsie!";
02421        return false;
02422     }
02423   }
02424 
02425   // Clear out per-connection settings...
02426   resetConnectionSettings();
02427 
02428 
02429   // Send the data to the remote machine...
02430   ssize_t written = write(header.toLatin1(), header.length());
02431   bool sendOk = (written == (ssize_t) header.length());
02432   if (!sendOk)
02433   {
02434     kDebug(7113) << "Connection broken! (" << m_request.url.host() << ")"
02435                  << "  -- intended to write" << header.length()
02436                  << "bytes but wrote" << (int)written << ".";
02437 
02438     // The server might have closed the connection due to a timeout, or maybe
02439     // some transport problem arose while the connection was idle.
02440     if (m_request.isKeepAlive)
02441     {
02442        httpCloseConnection();
02443        return true; // Try again
02444     }
02445 
02446     kDebug(7113) << "sendOk == false. Connection broken !"
02447                  << "  -- intended to write" << header.length()
02448                  << "bytes but wrote" << (int)written << ".";
02449     error( ERR_CONNECTION_BROKEN, m_request.url.host() );
02450     return false;
02451   }
02452   else
02453     kDebug(7113) << "sent it!";
02454 
02455   bool res = true;
02456   if (hasBodyData || hasDavData)
02457     res = sendBody();
02458 
02459   infoMessage(i18n("%1 contacted. Waiting for reply...", m_request.url.host()));
02460 
02461   return res;
02462 }
02463 
02464 void HTTPProtocol::forwardHttpResponseHeader()
02465 {
02466   // Send the response header if it was requested
02467   if ( config()->readEntry("PropagateHttpHeader", false) )
02468   {
02469     setMetaData("HTTP-Headers", m_responseHeaders.join("\n"));
02470     sendMetaData();
02471   }
02472 }
02473 
02474 bool HTTPProtocol::readHeaderFromCache() {
02475     m_responseHeaders.clear();
02476 
02477     // Read header from cache...
02478     static const int bufSize = 8192;
02479     char buffer[bufSize + 1];
02480     if (!gzgets(m_request.cacheTag.gzs, buffer, bufSize)) {
02481         // Error, delete cache entry
02482         kDebug(7113) << "Could not access cache to obtain mimetype!";
02483         error( ERR_CONNECTION_BROKEN, m_request.url.host() );
02484         return false;
02485     }
02486 
02487     m_mimeType = QString::fromLatin1(buffer).trimmed();
02488 
02489     kDebug(7113) << "cached data mimetype: " << m_mimeType;
02490 
02491     // read http-headers, first the response code
02492     if (!gzgets(m_request.cacheTag.gzs, buffer, bufSize)) {
02493         // Error, delete cache entry
02494         kDebug(7113) << "Could not access cached data! ";
02495         error( ERR_CONNECTION_BROKEN, m_request.url.host() );
02496         return false;
02497     }
02498     m_responseHeaders << buffer;
02499     // then the headers
02500     while(true) {
02501         if (!gzgets(m_request.cacheTag.gzs, buffer, bufSize)) {
02502             // Error, delete cache entry
02503             kDebug(7113) << "Could not access cached data!";
02504             error( ERR_CONNECTION_BROKEN, m_request.url.host() );
02505             return false;
02506         }
02507         m_responseHeaders << buffer;
02508         QString header = QString::fromLatin1(buffer).trimmed().toLower();
02509         if (header.isEmpty()) {
02510             break;
02511         }
02512         if (header.startsWith("content-type: ")) {
02513             int pos = header.indexOf("charset=");
02514             if (pos != -1) {
02515                 QString charset = header.mid(pos+8);
02516                 m_request.cacheTag.charset = charset;
02517                 setMetaData("charset", charset);
02518             }
02519         } else if (header.startsWith("content-language: ")) {
02520             QString language = header.mid(18);
02521             setMetaData("content-language", language);
02522         } else if (header.startsWith("content-disposition:")) {
02523             parseContentDisposition(header.mid(20));
02524         }
02525     }
02526     forwardHttpResponseHeader();
02527 
02528     if (!m_request.cacheTag.lastModified.isEmpty())
02529         setMetaData("modified", m_request.cacheTag.lastModified);
02530 
02531     setMetaData("expire-date", QString::number(m_request.cacheTag.expireDate));
02532     setMetaData("cache-creation-date", QString::number(m_request.cacheTag.creationDate));
02533 
02534     mimeType(m_mimeType);
02535     return true;
02536 }
02537 
02538 void HTTPProtocol::fixupResponseMimetype()
02539 {
02540     // Convert some common mimetypes to standard mimetypes
02541     if (m_mimeType == "application/x-targz")
02542         m_mimeType = QString::fromLatin1("application/x-compressed-tar");
02543     else if (m_mimeType == "image/x-png")
02544         m_mimeType = QString::fromLatin1("image/png");
02545     else if (m_mimeType == "audio/x-mp3" || m_mimeType == "audio/x-mpeg" || m_mimeType == "audio/mp3")
02546         m_mimeType = QString::fromLatin1("audio/mpeg");
02547     else if (m_mimeType == "audio/microsoft-wave")
02548         m_mimeType = QString::fromLatin1("audio/x-wav");
02549 
02550     // Crypto ones....
02551     else if (m_mimeType == "application/pkix-cert" ||
02552              m_mimeType == "application/binary-certificate") {
02553         m_mimeType = QString::fromLatin1("application/x-x509-ca-cert");
02554     }
02555 
02556     // Prefer application/x-compressed-tar or x-gzpostscript over application/x-gzip.
02557     else if (m_mimeType == "application/x-gzip") {
02558         if ((m_request.url.path().endsWith(".tar.gz")) ||
02559             (m_request.url.path().endsWith(".tar")))
02560             m_mimeType = QString::fromLatin1("application/x-compressed-tar");
02561         if ((m_request.url.path().endsWith(".ps.gz")))
02562             m_mimeType = QString::fromLatin1("application/x-gzpostscript");
02563     }
02564 
02565     // Some webservers say "text/plain" when they mean "application/x-bzip"
02566     else if ((m_mimeType == "text/plain") || (m_mimeType == "application/octet-stream")) {
02567         QString ext = m_request.url.path().right(4).toUpper();
02568         if (ext == ".BZ2")
02569             m_mimeType = QString::fromLatin1("application/x-bzip");
02570         else if (ext == ".PEM")
02571             m_mimeType = QString::fromLatin1("application/x-x509-ca-cert");
02572         else if (ext == ".SWF")
02573             m_mimeType = QString::fromLatin1("application/x-shockwave-flash");
02574         else if (ext == ".PLS")
02575             m_mimeType = QString::fromLatin1("audio/x-scpls");
02576         else if (ext == ".WMV")
02577             m_mimeType = QString::fromLatin1("video/x-ms-wmv");
02578     }
02579 }
02580 
02581 
02588 bool HTTPProtocol::readResponseHeader()
02589 {
02590     resetResponseParsing();
02591 try_again:
02592     kDebug(7113);
02593 
02594     if (m_request.cacheTag.readFromCache) {
02595         return readHeaderFromCache();
02596     }
02597 
02598     // QStrings to force deep copy from "volatile" QByteArray that TokenIterator supplies.
02599     // One generally has to be very careful with those!
02600     QString locationStr; // In case we get a redirect.
02601     QByteArray cookieStr; // In case we get a cookie.
02602 
02603     QString mediaValue;
02604     QString mediaAttribute;
02605 
02606     QStringList upgradeOffers;
02607 
02608     bool upgradeRequired = false;   // Server demands that we upgrade to something
02609                                     // This is also true if we ask to upgrade and
02610                                     // the server accepts, since we are now
02611                                     // committed to doing so
02612     bool canUpgrade = false;        // The server offered an upgrade
02613 
02614 
02615     m_request.cacheTag.etag.clear();
02616     m_request.cacheTag.lastModified.clear();
02617     m_request.cacheTag.charset.clear();
02618     m_responseHeaders.clear();
02619 
02620     time_t dateHeader = 0;
02621     time_t expireDate = 0; // 0 = no info, 1 = already expired, > 1 = actual date
02622     int currentAge = 0;
02623     int maxAge = -1; // -1 = no max age, 0 already expired, > 0 = actual time
02624     static const int maxHeaderSize = 128 * 1024;
02625 
02626     char buffer[maxHeaderSize];
02627     bool cont = false;
02628     bool cacheValidated = false; // Revalidation was successful
02629     bool mayCache = true;
02630     bool hasCacheDirective = false;
02631     bool bCanResume = false;
02632 
02633     if (!isConnected()) {
02634         kDebug(7113) << "No connection.";
02635         return false; // Reestablish connection and try again
02636     }
02637 
02638     if (!waitForResponse(m_remoteRespTimeout)) {
02639         // No response error
02640         error(ERR_SERVER_TIMEOUT , m_request.url.host());
02641         return false;
02642     }
02643 
02644     int bufPos = 0;
02645     bool foundDelimiter = readDelimitedText(buffer, &bufPos, maxHeaderSize, 1);
02646     if (!foundDelimiter && bufPos < maxHeaderSize) {
02647         kDebug(7113) << "EOF while waiting for header start.";
02648         if (m_request.isKeepAlive) {
02649             // Try to reestablish connection.
02650             httpCloseConnection();
02651             return false; // Reestablish connection and try again.
02652         }
02653 
02654         if (m_request.method == HTTP_HEAD) {
02655             // HACK
02656             // Some web-servers fail to respond properly to a HEAD request.
02657             // We compensate for their failure to properly implement the HTTP standard
02658             // by assuming that they will be sending html.
02659             kDebug(7113) << "HEAD -> returned mimetype: " << DEFAULT_MIME_TYPE;
02660             mimeType(QString::fromLatin1(DEFAULT_MIME_TYPE));
02661             return true;
02662         }
02663 
02664         kDebug(7113) << "Connection broken !";
02665         error( ERR_CONNECTION_BROKEN, m_request.url.host() );
02666         return false;
02667     }
02668     if (!foundDelimiter) {
02669         //### buffer too small for first line of header(!)
02670         Q_ASSERT(0);
02671     }
02672 
02673     kDebug(7103) << "============ Received Status Response:";
02674     kDebug(7103) << QByteArray(buffer, bufPos);
02675 
02676     HTTP_REV httpRev = HTTP_None;
02677     int headerSize = 0;
02678 
02679     int idx = 0;
02680 
02681     if (idx != bufPos && buffer[idx] == '<') {
02682         kDebug(7103) << "No valid HTTP header found! Document starts with XML/HTML tag";
02683         // document starts with a tag, assume HTML instead of text/plain
02684         m_mimeType = "text/html";
02685         // put string back
02686         unread(buffer, bufPos);
02687         goto endParsing;
02688     }
02689 
02690     // "HTTP/1.1" or similar
02691     if (consume(buffer, &idx, bufPos, "ICY ")) {
02692         httpRev = SHOUTCAST;
02693         m_request.isKeepAlive = false;
02694     } else if (consume(buffer, &idx, bufPos, "HTTP/")) {
02695         if (consume(buffer, &idx, bufPos, "1.0")) {
02696             httpRev = HTTP_10;
02697             m_request.isKeepAlive = false;
02698         } else if (consume(buffer, &idx, bufPos, "1.1")) {
02699             httpRev = HTTP_11;
02700         }
02701     }
02702 
02703     if (httpRev == HTTP_None && bufPos != 0) {
02704         // Remote server does not seem to speak HTTP at all
02705         // Put the crap back into the buffer and hope for the best
02706         kDebug(7113) << "DO NOT WANT." << bufPos;
02707         unread(buffer, bufPos);
02708         if (m_request.responseCode) {
02709             m_request.prevResponseCode = m_request.responseCode;
02710         }
02711         m_request.responseCode = 200; // Fake it
02712         httpRev = HTTP_Unknown;
02713         m_request.isKeepAlive = false;
02714         goto endParsing; //### ### correct?
02715     }
02716 
02717     // response code //### maybe wrong if we need several iterations for this response...
02718     //### also, do multiple iterations (cf. try_again) to parse one header work w/ pipelining?
02719     if (m_request.responseCode) {
02720         m_request.prevResponseCode = m_request.responseCode;
02721     }
02722     skipSpace(buffer, &idx, bufPos);
02723     //TODO saner handling of invalid response code strings
02724     if (idx != bufPos) {
02725         m_request.responseCode = atoi(&buffer[idx]);
02726     } else {
02727         m_request.responseCode = 200;
02728     }
02729     // move idx to start of (yet to be fetched) next line, skipping the "OK"
02730     idx = bufPos;
02731     // (don't bother parsing the "OK", what do we do if it isn't there anyway?)
02732 
02733     // immediately act on most response codes...
02734 
02735     if (m_request.responseCode >= 500 && m_request.responseCode <= 599) {
02736         // Server side errors
02737 
02738         if (m_request.method == HTTP_HEAD) {
02739             ; // Ignore error
02740         } else {
02741             if (m_request.preferErrorPage) {
02742                 setLoadingErrorPage();
02743             } else {
02744                 error(ERR_INTERNAL_SERVER, m_request.url.url());
02745                 return false;
02746             }
02747         }
02748         m_request.cacheTag.writeToCache = false; // Don't put in cache
02749         mayCache = false;
02750     } else if (m_request.responseCode == 401 || m_request.responseCode == 407) {
02751         // Unauthorized access
02752         m_request.cacheTag.writeToCache = false; // Don't put in cache
02753         mayCache = false;
02754     } else if (m_request.responseCode == 416) {
02755         // Range not supported
02756         m_request.offset = 0;
02757         return false; // Try again.
02758     } else if (m_request.responseCode == 426) {
02759         // Upgrade Required
02760         upgradeRequired = true;
02761     } else if (m_request.responseCode >= 400 && m_request.responseCode <= 499) {
02762         // Any other client errors
02763         // Tell that we will only get an error page here.
02764         if (m_request.preferErrorPage) {
02765             setLoadingErrorPage();
02766         } else {
02767             error(ERR_DOES_NOT_EXIST, m_request.url.url());
02768             return false;
02769         }
02770         m_request.cacheTag.writeToCache = false; // Don't put in cache
02771         mayCache = false;
02772     } else if (m_request.responseCode == 307) {
02773         // 307 Temporary Redirect
02774         m_request.cacheTag.writeToCache = false; // Don't put in cache
02775         mayCache = false;
02776     } else if (m_request.responseCode == 304) {
02777         // 304 Not Modified
02778         // The value in our cache is still valid.
02779         cacheValidated = true;
02780 
02781     } else if (m_request.responseCode >= 301 && m_request.responseCode<= 303) {
02782         // 301 Moved permanently
02783         if (m_request.responseCode == 301) {
02784             setMetaData("permanent-redirect", "true");
02785         }
02786         // 302 Found (temporary location)
02787         // 303 See Other
02788         if (m_request.method != HTTP_HEAD && m_request.method != HTTP_GET) {
02789 #if 0
02790             // Reset the POST buffer to avoid a double submit
02791             // on redirection
02792             if (m_request.method == HTTP_POST) {
02793                 m_POSTbuf.resize(0);
02794             }
02795 #endif
02796 
02797             // NOTE: This is wrong according to RFC 2616.  However,
02798             // because most other existing user agent implementations
02799             // treat a 301/302 response as a 303 response and preform
02800             // a GET action regardless of what the previous method was,
02801             // many servers have simply adapted to this way of doing
02802             // things!!  Thus, we are forced to do the same thing or we
02803             // won't be able to retrieve these pages correctly!! See RFC
02804             // 2616 sections 10.3.[2/3/4/8]
02805             m_request.method = HTTP_GET; // Force a GET
02806         }
02807         m_request.cacheTag.writeToCache = false; // Don't put in cache
02808         mayCache = false;
02809     } else if ( m_request.responseCode == 207 ) {
02810         // Multi-status (for WebDav)
02811 
02812     } else if (m_request.responseCode == 204) {
02813         // No content
02814 
02815         // error(ERR_NO_CONTENT, i18n("Data have been successfully sent."));
02816         // Short circuit and do nothing!
02817 
02818         // The original handling here was wrong, this is not an error: eg. in the
02819         // example of a 204 No Content response to a PUT completing.
02820         // m_isError = true;
02821         // return false;
02822     } else if (m_request.responseCode == 206) {
02823         if (m_request.offset) {
02824             bCanResume = true;
02825         }
02826     } else if (m_request.responseCode == 102) {
02827         // Processing (for WebDAV)
02828         /***
02829          * This status code is given when the server expects the
02830          * command to take significant time to complete. So, inform
02831          * the user.
02832          */
02833         infoMessage( i18n( "Server processing request, please wait..." ) );
02834         cont = true;
02835     } else if (m_request.responseCode == 100) {
02836         // We got 'Continue' - ignore it
02837         cont = true;
02838     }
02839 
02840 
02841     {
02842         const bool wasAuthError = m_request.prevResponseCode == 401 || m_request.prevResponseCode == 407;
02843         const bool isAuthError = m_request.responseCode == 401 || m_request.responseCode == 407;
02844         // Not the same authorization error as before and no generic error?
02845         // -> save the successful credentials.
02846         if (wasAuthError && (m_request.responseCode < 400 ||
02847                              (isAuthError && m_request.responseCode != m_request.prevResponseCode))) {
02848             KIO::AuthInfo authi;
02849             KAbstractHttpAuthentication *auth;
02850             if (m_request.prevResponseCode == 401) {
02851                 auth = m_wwwAuth;
02852             } else {
02853                 auth = m_proxyAuth;
02854             }
02855             Q_ASSERT(auth);
02856             if (auth) {
02857                 auth->fillKioAuthInfo(&authi);
02858                 cacheAuthentication(authi);
02859             }
02860         }
02861     }
02862 
02863     // done with the first line; now tokenize the other lines
02864 
02865   endParsing: //### if we goto here nothing good comes out of it. rethink.
02866 
02867     // TODO review use of STRTOLL vs. QByteArray::toInt()
02868 
02869     foundDelimiter = readDelimitedText(buffer, &bufPos, maxHeaderSize, 2);
02870     kDebug(7113) << " -- full response:" << QByteArray(buffer, bufPos);
02871     Q_ASSERT(foundDelimiter);
02872 
02873     //NOTE because tokenizer will overwrite newlines in case of line continuations in the header
02874     //     unread(buffer, bufSize) will not generally work anymore. we don't need it either.
02875     //     either we have a http response line -> try to parse the header, fail if it doesn't work
02876     //     or we have garbage -> fail.
02877     HeaderTokenizer tokenizer(buffer);
02878     headerSize = tokenizer.tokenize(idx, sizeof(buffer));
02879 
02880     // Note that not receiving "accept-ranges" means that all bets are off
02881     // wrt the server supporting ranges.
02882     TokenIterator tIt = tokenizer.iterator("accept-ranges");
02883     if (tIt.hasNext() && tIt.next().toLower().startsWith("none")) {
02884         bCanResume = false;
02885     }
02886 
02887     tIt = tokenizer.iterator("keep-alive");
02888     while (tIt.hasNext()) {
02889         if (tIt.next().startsWith("timeout=")) {
02890             m_request.keepAliveTimeout = tIt.current().mid(strlen("timeout=")).trimmed().toInt();
02891         }
02892     }
02893 
02894     tIt = tokenizer.iterator("cache-control");
02895     while (tIt.hasNext()) {
02896         QByteArray cacheStr = tIt.next().toLower();
02897         if (cacheStr.startsWith("no-cache") || cacheStr.startsWith("no-store")) {
02898             // Don't put in cache
02899             m_request.cacheTag.writeToCache = false;
02900             mayCache = false;
02901             hasCacheDirective = true;
02902         } else if (cacheStr.startsWith("max-age=")) {
02903             QByteArray age = cacheStr.mid(strlen("max-age=")).trimmed();
02904             if (!age.isEmpty()) {
02905                 maxAge = STRTOLL(age.constData(), 0, 10);
02906                 hasCacheDirective = true;
02907             }
02908         }
02909     }
02910 
02911     // get the size of our data
02912     tIt = tokenizer.iterator("content-length");
02913     if (tIt.hasNext()) {
02914         m_iSize = STRTOLL(tIt.next().constData(), 0, 10);
02915     }
02916 
02917     tIt = tokenizer.iterator("content-location");
02918     if (tIt.hasNext()) {
02919         setMetaData("content-location", QString::fromLatin1(tIt.next().trimmed()));
02920     }
02921 
02922     // which type of data do we have?
02923     tIt = tokenizer.iterator("content-type");
02924     if (tIt.hasNext()) {
02925         QList<QByteArray> l = tIt.next().split(';');
02926         if (!l.isEmpty()) {
02927             // Assign the mime-type.
02928             m_mimeType = QString::fromLatin1(l.first().trimmed().toLower());
02929             kDebug(7113) << "Content-type: " << m_mimeType;
02930             l.removeFirst();
02931         }
02932 
02933         // If we still have text, then it means we have a mime-type with a
02934         // parameter (eg: charset=iso-8851) ; so let's get that...
02935         foreach (const QByteArray &statement, l) {
02936             QList<QByteArray> parts = statement.split('=');
02937             if (parts.count() != 2) {
02938                 continue;
02939             }
02940             mediaAttribute = parts[0].trimmed().toLower();
02941             mediaValue = parts[1].trimmed();
02942             if (mediaValue.length() && (mediaValue[0] == '"') &&
02943                 (mediaValue[mediaValue.length() - 1] == '"')) {
02944                 mediaValue = mediaValue.mid(1, mediaValue.length() - 2);
02945             }
02946             kDebug (7113) << "Encoding-type: " << mediaAttribute
02947                           << "=" << mediaValue;
02948 
02949             if (mediaAttribute == "charset") {
02950                 mediaValue = mediaValue.toLower();
02951                 m_request.cacheTag.charset = mediaValue;
02952                 setMetaData("charset", mediaValue);
02953             } else {
02954                 setMetaData("media-" + mediaAttribute, mediaValue);
02955             }
02956         }
02957     }
02958 
02959     // Date
02960     tIt = tokenizer.iterator("date");
02961     if (tIt.hasNext()) {
02962         dateHeader = KDateTime::fromString(tIt.next(), KDateTime::RFCDate).toTime_t();
02963     }
02964 
02965     // Cache management
02966     tIt = tokenizer.iterator("etag");
02967     if (tIt.hasNext()) {
02968         //note QByteArray -> QString conversion will make a deep copy; we want one.
02969         m_request.cacheTag.etag = QString(tIt.next());
02970     }
02971 
02972     tIt = tokenizer.iterator("expires");
02973     if (tIt.hasNext()) {
02974         expireDate = KDateTime::fromString(tIt.next(), KDateTime::RFCDate).toTime_t();
02975         if (!expireDate) {
02976             expireDate = 1; // Already expired
02977         }
02978     }
02979 
02980     tIt = tokenizer.iterator("last-modified");
02981     if (tIt.hasNext()) {
02982         m_request.cacheTag.lastModified = QString(tIt.next());
02983     }
02984 
02985     // whoops.. we received a warning
02986     tIt = tokenizer.iterator("warning");
02987     if (tIt.hasNext()) {
02988         //Don't use warning() here, no need to bother the user.
02989         //Those warnings are mostly about caches.
02990         infoMessage(tIt.next());
02991     }
02992 
02993     // Cache management (HTTP 1.0)
02994     tIt = tokenizer.iterator("pragma");
02995     while (tIt.hasNext()) {
02996         if (tIt.next().toLower().startsWith("no-cache")) {
02997             m_request.cacheTag.writeToCache = false; // Don't put in cache
02998             mayCache = false;
02999             hasCacheDirective = true;
03000         }
03001     }
03002 
03003     // The deprecated Refresh Response
03004     tIt = tokenizer.iterator("refresh");
03005     if (tIt.hasNext()) {
03006         mayCache = false;  // Do not cache page as it defeats purpose of Refresh tag!
03007         setMetaData("http-refresh", QString::fromLatin1(tIt.next().trimmed()));
03008     }
03009 
03010     // In fact we should do redirection only if we have a redirection response code (300 range)
03011     tIt = tokenizer.iterator("location");
03012     if (tIt.hasNext() && m_request.responseCode > 299 && m_request.responseCode < 400) {
03013         locationStr = QString::fromUtf8(tIt.next().trimmed());
03014     }
03015 
03016     // Harvest cookies (mmm, cookie fields!)
03017     tIt = tokenizer.iterator("set-cookie");
03018     while (tIt.hasNext()) {
03019         cookieStr += "Set-Cookie: ";
03020         cookieStr += tIt.next();
03021         cookieStr += '\n';
03022     }
03023 
03024     tIt = tokenizer.iterator("upgrade");
03025     if (tIt.hasNext()) {
03026         // Now we have to check to see what is offered for the upgrade
03027         QString offered = QString::fromLatin1(tIt.next());
03028         upgradeOffers = offered.split(QRegExp("[ \n,\r\t]"), QString::SkipEmptyParts);
03029     }
03030 
03031     // content?
03032     tIt = tokenizer.iterator("content-encoding");
03033     while (tIt.hasNext()) {
03034         // This is so wrong !!  No wonder kio_http is stripping the
03035         // gzip encoding from downloaded files.  This solves multiple
03036         // bug reports and caitoo's problem with downloads when such a
03037         // header is encountered...
03038 
03039         // A quote from RFC 2616:
03040         // " When present, its (Content-Encoding) value indicates what additional
03041         // content have been applied to the entity body, and thus what decoding
03042         // mechanism must be applied to obtain the media-type referenced by the
03043         // Content-Type header field.  Content-Encoding is primarily used to allow
03044         // a document to be compressed without loosing the identity of its underlying
03045         // media type.  Simply put if it is specified, this is the actual mime-type
03046         // we should use when we pull the resource !!!
03047         addEncoding(tIt.next(), m_contentEncodings);
03048     }
03049     // Refer to RFC 2616 sec 15.5/19.5.1 and RFC 2183
03050     tIt = tokenizer.iterator("content-disposition");
03051     if (tIt.hasNext()) {
03052         parseContentDisposition(QString::fromLatin1(tIt.next()));
03053     }
03054     tIt = tokenizer.iterator("content-language");
03055     if (tIt.hasNext()) {
03056         QString language = QString::fromLatin1(tIt.next().trimmed());
03057         if (!language.isEmpty()) {
03058             setMetaData("content-language", language);
03059         }
03060     }
03061 
03062     tIt = tokenizer.iterator("proxy-connection");
03063     if (tIt.hasNext() && isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) {
03064         QByteArray pc = tIt.next().toLower();
03065         if (pc.startsWith("close")) {
03066             m_request.isKeepAlive = false;
03067         } else if (pc.startsWith("keep-alive")) {
03068             m_request.isKeepAlive = true;
03069         }
03070     }
03071 
03072     tIt = tokenizer.iterator("link");
03073     if (tIt.hasNext()) {
03074         // We only support Link: <url>; rel="type"   so far
03075         QStringList link = QString::fromLatin1(tIt.next()).split(';', QString::SkipEmptyParts);
03076         if (link.count() == 2) {
03077             QString rel = link[1].trimmed();
03078             if (rel.startsWith("rel=\"")) {
03079                 rel = rel.mid(5, rel.length() - 6);
03080                 if (rel.toLower() == "pageservices") {
03081                     //### the remove() part looks fishy!
03082                     QString url = link[0].remove(QRegExp("[<>]")).trimmed();
03083                     setMetaData("PageServices", url);
03084                 }
03085             }
03086         }
03087     }
03088 
03089     tIt = tokenizer.iterator("p3p");
03090     if (tIt.hasNext()) {
03091         // P3P privacy policy information
03092         QStringList policyrefs, compact;
03093         while (tIt.hasNext()) {
03094             QStringList policy = QString::fromLatin1(tIt.next().simplified())
03095                                  .split('=', QString::SkipEmptyParts);
03096             if (policy.count() == 2) {
03097                 if (policy[0].toLower() == "policyref") {
03098                     policyrefs << policy[1].remove(QRegExp("[\"\']")).trimmed();
03099                 } else if (policy[0].toLower() == "cp") {
03100                     // We convert to cp\ncp\ncp\n[...]\ncp to be consistent with
03101                     // other metadata sent in strings.  This could be a bit more
03102                     // efficient but I'm going for correctness right now.
03103                     const QString s = policy[1].remove(QRegExp("[\"\']"));
03104                     const QStringList cps = s.split(' ', QString::SkipEmptyParts);
03105                     compact << cps;
03106                 }
03107             }
03108         }
03109         if (!policyrefs.isEmpty()) {
03110             setMetaData("PrivacyPolicy", policyrefs.join("\n"));
03111         }
03112         if (!compact.isEmpty()) {
03113             setMetaData("PrivacyCompactPolicy", compact.join("\n"));
03114         }
03115     }
03116 
03117     // continue only if we know that we're at least HTTP/1.0
03118     if (httpRev == HTTP_11 || httpRev == HTTP_10) {
03119         // let them tell us if we should stay alive or not
03120         tIt = tokenizer.iterator("connection");
03121         while (tIt.hasNext()) {
03122             QByteArray connection = tIt.next().toLower();
03123             if (!(isHttpProxy(m_request.proxyUrl) && !isAutoSsl())) {
03124                 if (connection.startsWith("close")) {
03125                     m_request.isKeepAlive = false;
03126                 } else if (connection.startsWith("keep-alive")) {
03127                     m_request.isKeepAlive = true;
03128                 }
03129             }
03130             if (connection.startsWith("upgrade")) {
03131                 if (m_request.responseCode == 101) {
03132                     // Ok, an upgrade was accepted, now we must do it
03133                     upgradeRequired = true;
03134                 } else if (upgradeRequired) {  // 426
03135                     // Nothing to do since we did it above already
03136                 } else {
03137                     // Just an offer to upgrade - no need to take it
03138                     canUpgrade = true;
03139                 }
03140             }
03141         }
03142         // what kind of encoding do we have?  transfer?
03143         tIt = tokenizer.iterator("transfer-encoding");
03144         while (tIt.hasNext()) {
03145             // If multiple encodings have been applied to an entity, the
03146             // transfer-codings MUST be listed in the order in which they
03147             // were applied.
03148             addEncoding(tIt.next().trimmed(), m_transferEncodings);
03149         }
03150 
03151         // md5 signature
03152         tIt = tokenizer.iterator("content-md5");
03153         if (tIt.hasNext()) {
03154             m_contentMD5 = QString::fromLatin1(tIt.next().trimmed());
03155         }
03156 
03157         // *** Responses to the HTTP OPTIONS method follow
03158         // WebDAV capabilities
03159         tIt = tokenizer.iterator("dav");
03160         while (tIt.hasNext()) {
03161             m_davCapabilities << QString::fromLatin1(tIt.next());
03162         }
03163         // *** Responses to the HTTP OPTIONS method finished
03164     }
03165 
03166 
03167     // Now process the HTTP/1.1 upgrade
03168     foreach (const QString &opt, upgradeOffers) {
03169         if (opt == "TLS/1.0") {
03170             if (!startSsl() && upgradeRequired) {
03171                 error(ERR_UPGRADE_REQUIRED, opt);
03172                 return false;
03173             }
03174         } else if (opt == "HTTP/1.1") {
03175             httpRev = HTTP_11;
03176         } else if (upgradeRequired) {
03177             // we are told to do an upgrade we don't understand
03178             error(ERR_UPGRADE_REQUIRED, opt);
03179             return false;
03180         }
03181     }
03182 
03183   // Fixup expire date for clock drift.
03184   if (expireDate && (expireDate <= dateHeader))
03185     expireDate = 1; // Already expired.
03186 
03187   // Convert max-age into expireDate (overriding previous set expireDate)
03188   if (maxAge == 0)
03189     expireDate = 1; // Already expired.
03190   else if (maxAge > 0)
03191   {
03192     if (currentAge)
03193       maxAge -= currentAge;
03194     if (maxAge <=0)
03195       maxAge = 0;
03196     expireDate = time(0) + maxAge;
03197   }
03198 
03199   if (!expireDate)
03200   {
03201     time_t lastModifiedDate = 0;
03202     if (!m_request.cacheTag.lastModified.isEmpty())
03203        lastModifiedDate = KDateTime::fromString(m_request.cacheTag.lastModified, KDateTime::RFCDate).toTime_t();
03204 
03205     if (lastModifiedDate)
03206     {
03207        long diff = static_cast<long>(difftime(dateHeader, lastModifiedDate));
03208        if (diff < 0)
03209           expireDate = time(0) + 1;
03210        else
03211           expireDate = time(0) + (diff / 10);
03212     }
03213     else
03214     {
03215        expireDate = time(0) + DEFAULT_CACHE_EXPIRE;
03216     }
03217   }
03218 
03219   // DONE receiving the header!
03220   if (!cookieStr.isEmpty())
03221   {
03222     if ((m_request.cookieMode == HTTPRequest::CookiesAuto) && m_request.useCookieJar)
03223     {
03224       // Give cookies to the cookiejar.
03225       QString domain = config()->readEntry("cross-domain");
03226       if (!domain.isEmpty() && isCrossDomainRequest(m_request.url.host(), domain))
03227          cookieStr = "Cross-Domain\n" + cookieStr;
03228       addCookies( m_request.url.url(), cookieStr );
03229     }
03230     else if (m_request.cookieMode == HTTPRequest::CookiesManual)
03231     {
03232       // Pass cookie to application
03233       setMetaData("setcookies", cookieStr);
03234     }
03235   }
03236 
03237   if (m_request.cacheTag.isExpired)
03238   {
03239     m_request.cacheTag.isExpired = false; // Reset just in case.
03240     if (cacheValidated)
03241     {
03242       // Yippie, we can use the cached version.
03243       // Update the cache with new "Expire" headers.
03244       gzclose(m_request.cacheTag.gzs);
03245       m_request.cacheTag.gzs = 0;
03246       updateExpireDate( expireDate, true );
03247       m_request.cacheTag.gzs = checkCacheEntry( ); // Re-read cache entry
03248 
03249       if (m_request.cacheTag.gzs)
03250       {
03251           m_request.cacheTag.readFromCache = true;
03252           goto try_again; // Read header again, but now from cache.
03253        }
03254        else
03255        {
03256           // Where did our cache entry go???
03257        }
03258      }
03259      else
03260      {
03261        // Validation failed. Close cache.
03262        gzclose(m_request.cacheTag.gzs);
03263        m_request.cacheTag.gzs = 0;
03264      }
03265   }
03266 
03267   // We need to reread the header if we got a '100 Continue' or '102 Processing'
03268   if ( cont )
03269   {
03270     kDebug(7113) << "cont; returning to mark try_again";
03271     goto try_again;
03272   }
03273 
03274   // Do not do a keep-alive connection if the size of the
03275   // response is not known and the response is not Chunked.
03276   if (!m_isChunked && (m_iSize == NO_SIZE)) {
03277     m_request.isKeepAlive = false;
03278   }
03279 
03280   if ( m_request.responseCode == 204 )
03281   {
03282     return true;
03283   }
03284 
03285     // TODO cache the proxy auth data (not doing this means a small performance regression for now)
03286 
03287     // we may need to send (Proxy or WWW) authorization data
03288     bool authRequiresAnotherRoundtrip = false;
03289     if (!m_request.doNotAuthenticate && (m_request.responseCode == 401 ||
03290                                          m_request.responseCode == 407)) {
03291         authRequiresAnotherRoundtrip = true;
03292 
03293         KAbstractHttpAuthentication **auth = &m_wwwAuth;
03294         tIt = tokenizer.iterator("www-authenticate");
03295         KUrl resource = m_request.url;
03296         if (m_request.responseCode == 407) {
03297             // make sure that the 407 header hasn't escaped a lower layer when it shouldn't.
03298             // this may break proxy chains which were never tested anyway, and AFAIK they are
03299             // rare to nonexistent in the wild.
03300             Q_ASSERT(QNetworkProxy::applicationProxy().type() == QNetworkProxy::NoProxy);
03301 
03302             auth = &m_proxyAuth;
03303             tIt = tokenizer.iterator("proxy-authenticate");
03304             resource = m_request.proxyUrl;
03305         }
03306 
03307         // Workaround brain dead frameworks/sites that violate the spec and
03308         // incorrectly return a 401 without the required WWW-Authenticate
03309         // header field when they should actually be returning a 200.
03310         // See BR #215736.
03311         QList<QByteArray> authTokens = tIt.all();
03312         if (authTokens.isEmpty()) {
03313             m_request.responseCode = 200; // Change back the response code...
03314             m_request.cacheTag.writeToCache = true;
03315             mayCache = true;
03316         } else {
03317             kDebug(7113) << "parsing authentication request; response code =" << m_request.responseCode;
03318 
03319             QByteArray bestOffer = KAbstractHttpAuthentication::bestOffer(authTokens);
03320             if (*auth) {
03321                 if (!bestOffer.toLower().startsWith((*auth)->scheme().toLower())) {
03322                     // huh, the strongest authentication scheme offered has changed.
03323                     kDebug(7113) << "deleting old auth class, scheme mismatch.";
03324                     delete *auth;
03325                     *auth = 0;
03326                 }
03327             }
03328             kDebug(7113) << "strongest authentication scheme offered is" << bestOffer;
03329             if (!(*auth)) {
03330                 *auth = KAbstractHttpAuthentication::newAuth(bestOffer);
03331             }
03332             kDebug(7113) << "pointer to auth class is now" << *auth;
03333             if (!(*auth)) {
03334                 if (m_request.preferErrorPage) {
03335                     setLoadingErrorPage();
03336                 } else {
03337                     error(ERR_UNSUPPORTED_ACTION, "Unknown Authorization method!");
03338                     return false;
03339                 }
03340             }
03341 
03342             // *auth may still be null due to setLoadingErrorPage().
03343 
03344             if (*auth) {
03345                 // remove trailing space from the method string, or digest auth will fail
03346                 QByteArray requestMethod = methodString(m_request.method).toLatin1().trimmed();
03347                 (*auth)->setChallenge(bestOffer, resource, requestMethod);
03348 
03349                 QString username;
03350                 QString password;
03351                 if ((*auth)->needCredentials()) {
03352                     // use credentials supplied by the application if available
03353                     if (!m_request.url.user().isEmpty() && !m_request.url.pass().isEmpty()) {
03354                         username = m_request.url.user();
03355                         password = m_request.url.pass();
03356                         // don't try this password any more
03357                         m_request.url.setPass(QString());
03358                     } else {
03359                         // try to get credentials from kpasswdserver's cache, then try asking the user.
03360                         KIO::AuthInfo authi;
03361                         fillPromptInfo(&authi);
03362                         bool obtained = checkCachedAuthentication(authi);
03363                         const bool probablyWrong = m_request.responseCode == m_request.prevResponseCode;
03364                         if (!obtained || probablyWrong) {
03365                             QString msg = (m_request.responseCode == 401) ?
03366                                             i18n("Authentication Failed.") :
03367                                             i18n("Proxy Authentication Failed.");
03368                             obtained = openPasswordDialog(authi, msg);
03369                             if (!obtained) {
03370                                 kDebug(7103) << "looks like the user canceled"
03371                                              << (m_request.responseCode == 401 ? "WWW" : "proxy")
03372                                              << "authentication.";
03373                                 kDebug(7113) << "obtained =" << obtained << "probablyWrong =" << probablyWrong
03374                                              << "authInfo username =" << authi.username
03375                                              << "authInfo realm =" << authi.realmValue;
03376                                 error(ERR_USER_CANCELED, resource.host());
03377                                 return false;
03378                             }
03379                         }
03380                         if (!obtained) {
03381                             kDebug(7103) << "could not obtain authentication credentials from cache or user!";
03382                         }
03383                         username = authi.username;
03384                         password = authi.password;
03385                     }
03386                 }
03387                 (*auth)->generateResponse(username, password);
03388 
03389                 kDebug(7113) << "auth state: isError" << (*auth)->isError()
03390                              << "needCredentials" << (*auth)->needCredentials()
03391                              << "forceKeepAlive" << (*auth)->forceKeepAlive()
03392                              << "forceDisconnect" << (*auth)->forceDisconnect()
03393                              << "headerFragment" << (*auth)->headerFragment();
03394 
03395                 if ((*auth)->isError()) {
03396                     if (m_request.preferErrorPage) {
03397                         setLoadingErrorPage();
03398                     } else {
03399                         error(ERR_UNSUPPORTED_ACTION, "Authorization failed!");
03400                         return false;
03401                     }
03402                     //### return false; ?
03403                 } else if ((*auth)->forceKeepAlive()) {
03404                     //### think this through for proxied / not proxied
03405                     m_request.isKeepAlive = true;
03406                 } else if ((*auth)->forceDisconnect()) {
03407                     //### think this through for proxied / not proxied
03408                     m_request.isKeepAlive = false;
03409                     httpCloseConnection();
03410                 }
03411             }
03412 
03413             if (m_request.isKeepAlive) {
03414                 // Important: trash data until the next response header starts.
03415                 readBody(true);
03416             }
03417         }
03418     }
03419 
03420   // We need to do a redirect
03421   if (!locationStr.isEmpty())
03422   {
03423     KUrl u(m_request.url, locationStr);
03424     if(!u.isValid())
03425     {
03426       error(ERR_MALFORMED_URL, u.url());
03427       return false;
03428     }
03429     if ((u.protocol() != "http") && (u.protocol() != "https") &&
03430         (u.protocol() != "webdav") && (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_isRedirection = 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.cacheTag.writeToCache = 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_mimeType.startsWith("text/") &&
03473       (m_mimeType != "text/css") &&
03474       (m_mimeType != "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() || m_wwwAuth)
03481      {
03482         m_request.cacheTag.writeToCache = 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_contentEncodings.isEmpty() && m_contentEncodings.last() == "gzip")
03493   {
03494      if (m_mimeType == "application/x-tar")
03495      {
03496         m_contentEncodings.removeLast();
03497         m_mimeType = QString::fromLatin1("application/x-compressed-tar");
03498      }
03499      else if (m_mimeType == "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_contentEncodings.removeLast();
03504         m_mimeType = QString::fromLatin1("application/x-gzpostscript");
03505      }
03506      else if ( (m_request.allowTransferCompression &&
03507                 m_mimeType == "text/html")
03508                 ||
03509                (m_request.allowTransferCompression &&
03510                 m_mimeType != "application/x-compressed-tar" &&
03511                 m_mimeType != "application/x-tgz" && // deprecated name
03512                 m_mimeType != "application/x-targz" && // deprecated name
03513                 m_mimeType != "application/x-gzip" &&
03514                 !m_request.url.path().endsWith(QLatin1String(".gz")))
03515                 )
03516      {
03517         // Unzip!
03518      }
03519      else
03520      {
03521         m_contentEncodings.removeLast();
03522         m_mimeType = 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_contentEncodings.isEmpty() && m_contentEncodings.last() == "bzip2")
03533   {
03534      m_contentEncodings.removeLast();
03535      m_mimeType = QString::fromLatin1("application/x-bzip");
03536   }
03537 
03538   // Correct some common incorrect pseudo-mimetypes
03539   fixupResponseMimetype();
03540 
03541   if (!m_request.cacheTag.lastModified.isEmpty())
03542     setMetaData("modified", m_request.cacheTag.lastModified);
03543 
03544   if (!mayCache)
03545   {
03546     setMetaData("no-cache", "true");
03547     setMetaData("expire-date", "1"); // Expired
03548   }
03549   else
03550   {
03551     QString tmp;
03552     tmp.setNum(expireDate);
03553     setMetaData("expire-date", tmp);
03554     tmp.setNum(time(0)); // Cache entry will be created shortly.
03555     setMetaData("cache-creation-date", tmp);
03556   }
03557 
03558   // Let the app know about the mime-type iff this is not
03559   // a redirection and the mime-type string is not empty.
03560   if (locationStr.isEmpty() &&
03561       (!m_mimeType.isEmpty() || m_request.method == HTTP_HEAD) &&
03562       (m_isLoadingErrorPage || (m_request.responseCode != 401 && m_request.responseCode != 407)))
03563   {
03564     kDebug(7113) << "Emitting mimetype " << m_mimeType;
03565     mimeType( m_mimeType );
03566   }
03567 
03568   if (config()->readEntry("PropagateHttpHeader", false) ||
03569       (m_request.cacheTag.useCache && m_request.cacheTag.writeToCache)) {
03570       // store header lines if they will be used; note that the tokenizer removing
03571       // line continuation special cases is probably more good than bad.
03572       int nextLinePos = 0;
03573       int prevLinePos = 0;
03574       bool haveMore = true;
03575       while (haveMore) {
03576           haveMore = nextLine(buffer, &nextLinePos, bufPos);
03577           int prevLineEnd = nextLinePos;
03578           while (buffer[prevLineEnd - 1] == '\r' || buffer[prevLineEnd - 1] == '\n') {
03579               prevLineEnd--;
03580           }
03581 
03582           m_responseHeaders.append(QString::fromLatin1(&buffer[prevLinePos],
03583                                                        prevLineEnd - prevLinePos));
03584           prevLinePos = nextLinePos;
03585       }
03586   }
03587 
03588   // Do not move send response header before any redirection as it seems
03589   // to screw up some sites. See BR# 150904.
03590   forwardHttpResponseHeader();
03591 
03592   if (m_request.method == HTTP_HEAD)
03593      return true;
03594 
03595   // Do we want to cache this request?
03596   if (m_request.cacheTag.useCache)
03597   {
03598     QFile::remove(m_request.cacheTag.file);
03599     if ( m_request.cacheTag.writeToCache && !m_mimeType.isEmpty() )
03600     {
03601       kDebug(7113) << "Cache, adding" << m_request.url.url();
03602       createCacheEntry(m_mimeType, expireDate); // Create a cache entry
03603       if (!m_request.cacheTag.gzs)
03604       {
03605         m_request.cacheTag.writeToCache = false; // Error creating cache entry.
03606         kDebug(7113) << "Error creating cache entry for " << m_request.url.url()<<"!\n";
03607       }
03608       m_request.cacheTag.expireDate = expireDate;
03609       m_maxCacheSize = config()->readEntry("MaxCacheSize", DEFAULT_MAX_CACHE_SIZE) / 2;
03610     }
03611   }
03612 
03613   return !authRequiresAnotherRoundtrip; // return true if no more credentials need to be sent
03614 }
03615 
03616 static void skipLWS(const QString &str, int &pos)
03617 {
03618     while (pos < str.length() && (str[pos] == ' ' || str[pos] == '\t'))
03619         ++pos;
03620 }
03621 
03622 // Extracts token-like input until terminator char or EOL.. Also skips over the terminator.
03623 // We don't try to be strict or anything..
03624 static QString extractUntil(const QString &str, unsigned char term, int &pos)
03625 {
03626     QString out;
03627     skipLWS(str, pos);
03628     while (pos < str.length() && (str[pos] != term)) {
03629         out += str[pos];
03630         ++pos;
03631     }
03632 
03633     if (pos < str.length()) // Stopped due to finding term
03634         ++pos;
03635 
03636     // Remove trailing linear whitespace...
03637     while (out.endsWith(' ') || out.endsWith('\t'))
03638         out.chop(1);
03639 
03640     return out;
03641 }
03642 
03643 // As above, but also handles quotes..
03644 static QString extractMaybeQuotedUntil(const QString &str, unsigned char term, int &pos)
03645 {
03646     skipLWS(str, pos);
03647 
03648     // Are we quoted?
03649     if (pos < str.length() && str[pos] == '"') {
03650         QString out;
03651 
03652         // Skip the quote...
03653         ++pos;
03654 
03655         // Parse until trailing quote...
03656         while (pos < str.length()) {
03657             if (str[pos] == '\\' && pos + 1 < str.length()) {
03658                 // quoted-pair = "\" CHAR
03659                 out += str[pos + 1];
03660                 pos += 2; // Skip both...
03661             } else if (str[pos] == '"') {
03662                 ++pos;
03663                 break;
03664             }  else {
03665                 out += str[pos];
03666                 ++pos;
03667             }
03668         }
03669 
03670         // Skip until term..
03671         while (pos < str.length() && (str[pos] != term))
03672             ++pos;
03673 
03674         if (pos < str.length()) // Stopped due to finding term
03675             ++pos;
03676 
03677         return out;
03678     } else {
03679         return extractUntil(str, term, pos);
03680     }
03681 }
03682 
03683 void HTTPProtocol::parseContentDisposition(const QString &disposition)
03684 {
03685     kDebug(7113) << "disposition: " << disposition;
03686     QString strDisposition;
03687     QString strFilename;
03688 
03689     int pos = 0;
03690 
03691     strDisposition = extractUntil(disposition, ';', pos);
03692 
03693     while (pos < disposition.length()) {
03694         QString key = extractUntil(disposition, '=', pos);
03695         QString val = extractMaybeQuotedUntil(disposition, ';', pos);
03696         if (key == "filename")
03697             strFilename = val;
03698     }
03699 
03700     // Content-Dispostion is not allowed to dictate directory
03701     // path, thus we extract the filename only.
03702     if ( !strFilename.isEmpty() )
03703     {
03704         int pos = strFilename.lastIndexOf( '/' );
03705 
03706         if( pos > -1 )
03707             strFilename = strFilename.mid(pos+1);
03708 
03709         kDebug(7113) << "Content-Disposition: filename=" << strFilename;
03710     }
03711     setMetaData("content-disposition-type", strDisposition);
03712     if (!strFilename.isEmpty())
03713         setMetaData("content-disposition-filename", KCodecs::decodeRFC2047String(strFilename));
03714 }
03715 
03716 void HTTPProtocol::addEncoding(const QString &_encoding, QStringList &encs)
03717 {
03718   QString encoding = _encoding.trimmed().toLower();
03719   // Identity is the same as no encoding
03720   if (encoding == "identity") {
03721     return;
03722   } else if (encoding == "8bit") {
03723     // Strange encoding returned by http://linac.ikp.physik.tu-darmstadt.de
03724     return;
03725   } else if (encoding == "chunked") {
03726     m_isChunked = true;
03727     // Anyone know of a better way to handle unknown sizes possibly/ideally with unsigned ints?
03728     //if ( m_cmd != CMD_COPY )
03729       m_iSize = NO_SIZE;
03730   } else if ((encoding == "x-gzip") || (encoding == "gzip")) {
03731     encs.append(QString::fromLatin1("gzip"));
03732   } else if ((encoding == "x-bzip2") || (encoding == "bzip2")) {
03733     encs.append(QString::fromLatin1("bzip2")); // Not yet supported!
03734   } else if ((encoding == "x-deflate") || (encoding == "deflate")) {
03735     encs.append(QString::fromLatin1("deflate"));
03736   } else {
03737     kDebug(7113) << "Unknown encoding encountered.  "
03738                  << "Please write code. Encoding =" << encoding;
03739   }
03740 }
03741 
03742 bool HTTPProtocol::sendBody()
03743 {
03744   infoMessage( i18n( "Requesting data to send" ) );
03745 
03746   int readFromApp = -1;
03747 
03748   // m_POSTbuf will NOT be empty iff authentication was required before posting
03749   // the data OR a re-connect is requested from ::readResponseHeader because the
03750   // connection was lost for some reason.
03751   if (m_POSTbuf.isEmpty())
03752   {
03753     kDebug(7113) << "POST'ing live data...";
03754 
03755     QByteArray buffer;
03756 
03757     do {
03758       m_POSTbuf.append(buffer);
03759       buffer.clear();
03760       dataReq(); // Request for data
03761       readFromApp = readData(buffer);
03762     } while (readFromApp > 0);
03763   }
03764   else
03765   {
03766     kDebug(7113) << "POST'ing saved data...";
03767     readFromApp = 0;
03768   }
03769 
03770   if (readFromApp < 0)
03771   {
03772     error(ERR_ABORTED, m_request.url.host());
03773     return false;
03774   }
03775 
03776   infoMessage(i18n("Sending data to %1" ,  m_request.url.host()));
03777 
03778   QString cLength = QString("Content-Length: %1\r\n\r\n").arg(m_POSTbuf.size());
03779   kDebug( 7113 ) << cLength;
03780 
03781   // Send the content length...
03782   bool sendOk = (write(cLength.toLatin1(), cLength.length()) == (ssize_t) cLength.length());
03783   if (!sendOk)
03784   {
03785     kDebug( 7113 ) << "Connection broken when sending "
03786                     << "content length: (" << m_request.url.host() << ")";
03787     error( ERR_CONNECTION_BROKEN, m_request.url.host() );
03788     return false;
03789   }
03790 
03791   // Send the data...
03792   // kDebug( 7113 ) << "POST DATA: " << QCString(m_POSTbuf);
03793   sendOk = (write(m_POSTbuf.data(), m_POSTbuf.size()) == (ssize_t) m_POSTbuf.size());
03794   if (!sendOk)
03795   {
03796     kDebug(7113) << "Connection broken when sending message body: ("
03797                   << m_request.url.host() << ")";
03798     error( ERR_CONNECTION_BROKEN, m_request.url.host() );
03799     return false;
03800   }
03801 
03802   return true;
03803 }
03804 
03805 void HTTPProtocol::httpClose( bool keepAlive )
03806 {
03807   kDebug(7113) << "keepAlive =" << keepAlive;
03808 
03809   if (m_request.cacheTag.gzs)
03810   {
03811      gzclose(m_request.cacheTag.gzs);
03812      m_request.cacheTag.gzs = 0;
03813      if (m_request.cacheTag.writeToCache)
03814      {
03815         QString filename = m_request.cacheTag.file + ".new";
03816         QFile::remove( filename );
03817      }
03818   }
03819 
03820   // Only allow persistent connections for GET requests.
03821   // NOTE: we might even want to narrow this down to non-form
03822   // based submit requests which will require a meta-data from
03823   // khtml.
03824   if (keepAlive) {
03825     if (!m_request.keepAliveTimeout)
03826        m_request.keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT;
03827     else if (m_request.keepAliveTimeout > 2*DEFAULT_KEEP_ALIVE_TIMEOUT)
03828        m_request.keepAliveTimeout = 2*DEFAULT_KEEP_ALIVE_TIMEOUT;
03829 
03830     kDebug(7113) << "keep alive (" << m_request.keepAliveTimeout << ")";
03831     QByteArray data;
03832     QDataStream stream( &data, QIODevice::WriteOnly );
03833     stream << int(99); // special: Close connection
03834     setTimeoutSpecialCommand(m_request.keepAliveTimeout, data);
03835 
03836     return;
03837   }
03838 
03839   httpCloseConnection();
03840 }
03841 
03842 void HTTPProtocol::closeConnection()
03843 {
03844   kDebug(7113);
03845   httpCloseConnection();
03846 }
03847 
03848 void HTTPProtocol::httpCloseConnection()
03849 {
03850   kDebug(7113);
03851   m_request.isKeepAlive = false;
03852   m_server.clear();
03853   disconnectFromHost();
03854   clearUnreadBuffer();
03855   setTimeoutSpecialCommand(-1); // Cancel any connection timeout
03856 }
03857 
03858 void HTTPProtocol::slave_status()
03859 {
03860   kDebug(7113);
03861 
03862   if ( !isConnected() )
03863      httpCloseConnection();
03864 
03865   slaveStatus( m_server.url.host(), isConnected() );
03866 }
03867 
03868 void HTTPProtocol::mimetype( const KUrl& url )
03869 {
03870   kDebug(7113) << url.url();
03871 
03872   if (!maybeSetRequestUrl(url))
03873     return;
03874   resetSessionSettings();
03875 
03876   m_request.method = HTTP_HEAD;
03877   m_request.cacheTag.policy= CC_Cache;
03878 
03879   proceedUntilResponseHeader();
03880   httpClose(m_request.isKeepAlive);
03881   finished();
03882 
03883   kDebug(7113) << "http: mimetype = " << m_mimeType;
03884 }
03885 
03886 void HTTPProtocol::special( const QByteArray &data )
03887 {
03888   kDebug(7113);
03889 
03890   int tmp;
03891   QDataStream stream(data);
03892 
03893   stream >> tmp;
03894   switch (tmp) {
03895     case 1: // HTTP POST
03896     {
03897       KUrl url;
03898       stream >> url;
03899       post( url );
03900       break;
03901     }
03902     case 2: // cache_update
03903     {
03904       KUrl url;
03905       bool no_cache;
03906       qlonglong expireDate;
03907       stream >> url >> no_cache >> expireDate;
03908       cacheUpdate( url, no_cache, time_t(expireDate) );
03909       break;
03910     }
03911     case 5: // WebDAV lock
03912     {
03913       KUrl url;
03914       QString scope, type, owner;
03915       stream >> url >> scope >> type >> owner;
03916       davLock( url, scope, type, owner );
03917       break;
03918     }
03919     case 6: // WebDAV unlock
03920     {
03921       KUrl url;
03922       stream >> url;
03923       davUnlock( url );
03924       break;
03925     }
03926     case 7: // Generic WebDAV
03927     {
03928       KUrl url;
03929       int method;
03930       stream >> url >> method;
03931       davGeneric( url, (KIO::HTTP_METHOD) method );
03932       break;
03933     }
03934     case 99: // Close Connection
03935     {
03936       httpCloseConnection();
03937       break;
03938     }
03939     default:
03940       // Some command we don't understand.
03941       // Just ignore it, it may come from some future version of KDE.
03942       break;
03943   }
03944 }
03945 
03949 int HTTPProtocol::readChunked()
03950 {
03951   if ((m_iBytesLeft == 0) || (m_iBytesLeft == NO_SIZE))
03952   {
03953      // discard CRLF from previous chunk, if any, and read size of next chunk
03954 
03955      int bufPos = 0;
03956      m_receiveBuf.resize(4096);
03957 
03958      bool foundCrLf = readDelimitedText(m_receiveBuf.data(), &bufPos, m_receiveBuf.size(), 1);
03959 
03960      if (foundCrLf && bufPos == 2) {
03961          // The previous read gave us the CRLF from the previous chunk. As bufPos includes
03962          // the trailing CRLF it has to be > 2 to possibly include the next chunksize.
03963          bufPos = 0;
03964          foundCrLf = readDelimitedText(m_receiveBuf.data(), &bufPos, m_receiveBuf.size(), 1);
03965      }
03966      if (!foundCrLf) {
03967          kDebug(7113) << "Failed to read chunk header.";
03968          return -1;
03969      }
03970      Q_ASSERT(bufPos > 2);
03971 
03972      long long nextChunkSize = STRTOLL(m_receiveBuf.data(), 0, 16);
03973      if (nextChunkSize < 0)
03974      {
03975         kDebug(7113) << "Negative chunk size";
03976         return -1;
03977      }
03978      m_iBytesLeft = nextChunkSize;
03979 
03980      kDebug(7113) << "Chunk size = " << m_iBytesLeft << " bytes";
03981 
03982      if (m_iBytesLeft == 0)
03983      {
03984        // Last chunk; read and discard chunk trailer.
03985        // The last trailer line ends with CRLF and is followed by another CRLF
03986        // so we have CRLFCRLF like at the end of a standard HTTP header.
03987        // Do not miss a CRLFCRLF spread over two of our 4K blocks: keep three previous bytes.
03988        //NOTE the CRLF after the chunksize also counts if there is no trailer. Copy it over.
03989        char trash[4096];
03990        trash[0] = m_receiveBuf.constData()[bufPos - 2];
03991        trash[1] = m_receiveBuf.constData()[bufPos - 1];
03992        int trashBufPos = 2;
03993        bool done = false;
03994        while (!done && !m_isEOF) {
03995            if (trashBufPos > 3) {
03996                // shift everything but the last three bytes out of the buffer
03997                for (int i = 0; i < 3; i++) {
03998                    trash[i] = trash[trashBufPos - 3 + i];
03999                }
04000                trashBufPos = 3;
04001            }
04002            done = readDelimitedText(trash, &trashBufPos, 4096, 2);
04003        }
04004        if (m_isEOF && !done) {
04005            kDebug(7113) << "Failed to read chunk trailer.";
04006            return -1;
04007        }
04008 
04009        return 0;
04010      }
04011   }
04012 
04013   int bytesReceived = readLimited();
04014   if (!m_iBytesLeft) {
04015      m_iBytesLeft = NO_SIZE; // Don't stop, continue with next chunk
04016   }
04017   return bytesReceived;
04018 }
04019 
04020 int HTTPProtocol::readLimited()
04021 {
04022   if (!m_iBytesLeft)
04023     return 0;
04024 
04025   m_receiveBuf.resize(4096);
04026 
04027   int bytesToReceive;
04028   if (m_iBytesLeft > KIO::filesize_t(m_receiveBuf.size()))
04029      bytesToReceive = m_receiveBuf.size();
04030   else
04031      bytesToReceive = m_iBytesLeft;
04032 
04033   int bytesReceived = readBuffered(m_receiveBuf.data(), bytesToReceive);
04034 
04035   if (bytesReceived <= 0)
04036      return -1; // Error: connection lost
04037 
04038   m_iBytesLeft -= bytesReceived;
04039   return bytesReceived;
04040 }
04041 
04042 int HTTPProtocol::readUnlimited()
04043 {
04044   if (m_request.isKeepAlive)
04045   {
04046      kDebug(7113) << "Unbounded datastream on a Keep-alive connection!";
04047      m_request.isKeepAlive = false;
04048   }
04049 
04050   m_receiveBuf.resize(4096);
04051 
04052   int result = readBuffered(m_receiveBuf.data(), m_receiveBuf.size());
04053   if (result > 0)
04054      return result;
04055 
04056   m_isEOF = true;
04057   m_iBytesLeft = 0;
04058   return 0;
04059 }
04060 
04061 void HTTPProtocol::slotData(const QByteArray &_d)
04062 {
04063    if (!_d.size())
04064    {
04065       m_isEOD = true;
04066       return;
04067    }
04068 
04069    if (m_iContentLeft != NO_SIZE)
04070    {
04071       if (m_iContentLeft >= KIO::filesize_t(_d.size()))
04072          m_iContentLeft -= _d.size();
04073       else
04074          m_iContentLeft = NO_SIZE;
04075    }
04076 
04077    QByteArray d = _d;
04078    if ( !m_dataInternal )
04079    {
04080       // If a broken server does not send the mime-type,
04081       // we try to id it from the content before dealing
04082       // with the content itself.
04083       if ( m_mimeType.isEmpty() && !m_isRedirection &&
04084            !( m_request.responseCode >= 300 && m_request.responseCode <=399) )
04085       {
04086         kDebug(7113) << "Determining mime-type from content...";
04087         int old_size = m_mimeTypeBuffer.size();
04088         m_mimeTypeBuffer.resize( old_size + d.size() );
04089         memcpy( m_mimeTypeBuffer.data() + old_size, d.data(), d.size() );
04090         if ( (m_iBytesLeft != NO_SIZE) && (m_iBytesLeft > 0)
04091              && (m_mimeTypeBuffer.size() < 1024) )
04092         {
04093           m_cpMimeBuffer = true;
04094           return;   // Do not send up the data since we do not yet know its mimetype!
04095         }
04096 
04097         kDebug(7113) << "Mimetype buffer size: " << m_mimeTypeBuffer.size();
04098 
04099         KMimeType::Ptr mime = KMimeType::findByNameAndContent(m_request.url.fileName(), m_mimeTypeBuffer);
04100         if( mime && !mime->isDefault() )
04101         {
04102           m_mimeType = mime->name();
04103           kDebug(7113) << "Mimetype from content: " << m_mimeType;
04104         }
04105 
04106         if ( m_mimeType.isEmpty() )
04107         {
04108           m_mimeType = QString::fromLatin1( DEFAULT_MIME_TYPE );
04109           kDebug(7113) << "Using default mimetype: " <<  m_mimeType;
04110         }
04111 
04112         if ( m_request.cacheTag.writeToCache )
04113         {
04114           createCacheEntry( m_mimeType, m_request.cacheTag.expireDate );
04115           if (!m_request.cacheTag.gzs)
04116             m_request.cacheTag.writeToCache = false;
04117         }
04118 
04119         if ( m_cpMimeBuffer )
04120         {
04121           d.resize(0);
04122           d.resize(m_mimeTypeBuffer.size());
04123           memcpy( d.data(), m_mimeTypeBuffer.data(),
04124                   d.size() );
04125         }
04126         mimeType(m_mimeType);
04127         m_mimeTypeBuffer.resize(0);
04128       }
04129 
04130       data( d );
04131       if (m_request.cacheTag.writeToCache && m_request.cacheTag.gzs)
04132          writeCacheEntry(d.data(), d.size());
04133    }
04134    else
04135    {
04136       uint old_size = m_webDavDataBuf.size();
04137       m_webDavDataBuf.resize (old_size + d.size());
04138       memcpy (m_webDavDataBuf.data() + old_size, d.data(), d.size());
04139    }
04140 }
04141 
04151 bool HTTPProtocol::readBody( bool dataInternal /* = false */ )
04152 {
04153   if (m_request.responseCode == 204)
04154      return true;
04155 
04156   m_isEOD = false;
04157   // Note that when dataInternal is true, we are going to:
04158   // 1) save the body data to a member variable, m_webDavDataBuf
04159   // 2) _not_ advertise the data, speed, size, etc., through the
04160   //    corresponding functions.
04161   // This is used for returning data to WebDAV.
04162   m_dataInternal = dataInternal;
04163   if (dataInternal) {
04164     m_webDavDataBuf.clear();
04165   }
04166 
04167   // Check if we need to decode the data.
04168   // If we are in copy mode, then use only transfer decoding.
04169   bool useMD5 = !m_contentMD5.isEmpty();
04170 
04171   // Deal with the size of the file.
04172   KIO::filesize_t sz = m_request.offset;
04173   if ( sz )
04174     m_iSize += sz;
04175 
04176   // Update the application with total size except when
04177   // it is compressed, or when the data is to be handled
04178   // internally (webDAV).  If compressed we have to wait
04179   // until we uncompress to find out the actual data size
04180   if ( !dataInternal ) {
04181     if ( (m_iSize > 0) && (m_iSize != NO_SIZE)) {
04182        totalSize(m_iSize);
04183        infoMessage(i18n("Retrieving %1 from %2...", KIO::convertSize(m_iSize),
04184                    m_request.url.host()));
04185     } else {
04186        totalSize (0);
04187     }
04188   } else {
04189     infoMessage( i18n( "Retrieving from %1..." ,  m_request.url.host() ) );
04190   }
04191 
04192   if (m_request.cacheTag.readFromCache)
04193   {
04194     kDebug(7113) << "read data from cache!";
04195     m_request.cacheTag.writeToCache = false;
04196 
04197     char buffer[ MAX_IPC_SIZE ];
04198 
04199     m_iContentLeft = NO_SIZE;
04200 
04201     // Jippie! It's already in the cache :-)
04202     while (!gzeof(m_request.cacheTag.gzs))
04203     {
04204       int nbytes = gzread( m_request.cacheTag.gzs, buffer, MAX_IPC_SIZE);
04205 
04206       if (nbytes > 0)
04207       {
04208         slotData( QByteArray::fromRawData( buffer, nbytes ) );
04209         sz += nbytes;
04210       }
04211       else if (!gzeof( m_request.cacheTag.gzs ) || nbytes < 0)
04212       {
04213         // Error reading compressed data
04214         int errnum;
04215         const char *errString = gzerror( m_request.cacheTag.gzs, &errnum );
04216         kError(7113) << "zlib error decompressing cached data:" << errString;
04217 
04218         // Not super-accurate error code, but it is what's used below for
04219         // the same error.
04220         error( ERR_CONNECTION_BROKEN, m_request.url.host() );
04221         return false;
04222       }
04223       // Only way neither branch handled is nbytes == 0 but no error, so loop
04224     }
04225 
04226     m_receiveBuf.resize( 0 );
04227 
04228     if ( !dataInternal )
04229     {
04230       processedSize( sz );
04231       data( QByteArray() );
04232     }
04233 
04234     return true;
04235   }
04236 
04237 
04238   if (m_iSize != NO_SIZE)
04239     m_iBytesLeft = m_iSize - sz;
04240   else
04241     m_iBytesLeft = NO_SIZE;
04242 
04243   m_iContentLeft = m_iBytesLeft;
04244 
04245   if (m_isChunked)
04246     m_iBytesLeft = NO_SIZE;
04247 
04248   kDebug(7113) << "retrieve data."<<KIO::number(m_iBytesLeft)<<"left.";
04249 
04250   // Main incoming loop...  Gather everything while we can...
04251   m_cpMimeBuffer = false;
04252   m_mimeTypeBuffer.resize(0);
04253   struct timeval last_tv;
04254   gettimeofday( &last_tv, 0L );
04255 
04256   HTTPFilterChain chain;
04257 
04258   QObject::connect(&chain, SIGNAL(output(const QByteArray &)),
04259           this, SLOT(slotData(const QByteArray &)));
04260   QObject::connect(&chain, SIGNAL(error(const QString &)),
04261           this, SLOT(slotFilterError(const QString &)));
04262 
04263    // decode all of the transfer encodings
04264   while (!m_transferEncodings.isEmpty())
04265   {
04266     QString enc = m_transferEncodings.takeLast();
04267     if ( enc == "gzip" )
04268       chain.addFilter(new HTTPFilterGZip);
04269     else if ( enc == "deflate" )
04270       chain.addFilter(new HTTPFilterDeflate);
04271   }
04272 
04273   // From HTTP 1.1 Draft 6:
04274   // The MD5 digest is computed based on the content of the entity-body,
04275   // including any content-coding that has been applied, but not including
04276   // any transfer-encoding applied to the message-body. If the message is
04277   // received with a transfer-encoding, that encoding MUST be removed
04278   // prior to checking the Content-MD5 value against the received entity.
04279   HTTPFilterMD5 *md5Filter = 0;
04280   if ( useMD5 )
04281   {
04282      md5Filter = new HTTPFilterMD5;
04283      chain.addFilter(md5Filter);
04284   }
04285 
04286   // now decode all of the content encodings
04287   // -- Why ?? We are not
04288   // -- a proxy server, be a client side implementation!!  The applications
04289   // -- are capable of determinig how to extract the encoded implementation.
04290   // WB: That's a misunderstanding. We are free to remove the encoding.
04291   // WB: Some braindead www-servers however, give .tgz files an encoding
04292   // WB: of "gzip" (or even "x-gzip") and a content-type of "applications/tar"
04293   // WB: They shouldn't do that. We can work around that though...
04294   while (!m_contentEncodings.isEmpty())
04295   {
04296     QString enc = m_contentEncodings.takeLast();
04297     if ( enc == "gzip" )
04298       chain.addFilter(new HTTPFilterGZip);
04299     else if ( enc == "deflate" )
04300       chain.addFilter(new HTTPFilterDeflate);
04301   }
04302 
04303   while (!m_isEOF)
04304   {
04305     int bytesReceived;
04306 
04307     if (m_isChunked)
04308        bytesReceived = readChunked();
04309     else if (m_iSize != NO_SIZE)
04310        bytesReceived = readLimited();
04311     else
04312        bytesReceived = readUnlimited();
04313 
04314     // make sure that this wasn't an error, first
04315     // kDebug(7113) << "bytesReceived:"
04316     //              << (int) bytesReceived << " m_iSize:" << (int) m_iSize << " Chunked:"
04317     //              << m_isChunked << " BytesLeft:"<< (int) m_iBytesLeft;
04318     if (bytesReceived == -1)
04319     {
04320       if (m_iContentLeft == 0)
04321       {
04322          // gzip'ed data sometimes reports a too long content-length.
04323          // (The length of the unzipped data)
04324          m_iBytesLeft = 0;
04325          break;
04326       }
04327       // Oh well... log an error and bug out
04328       kDebug(7113) << "bytesReceived==-1 sz=" << (int)sz
04329                     << " Connection broken !";
04330       error(ERR_CONNECTION_BROKEN, m_request.url.host());
04331       return false;
04332     }
04333 
04334     // I guess that nbytes == 0 isn't an error.. but we certainly
04335     // won't work with it!
04336     if (bytesReceived > 0)
04337     {
04338       // Important: truncate the buffer to the actual size received!
04339       // Otherwise garbage will be passed to the app
04340       m_receiveBuf.truncate( bytesReceived );
04341 
04342       chain.slotInput(m_receiveBuf);
04343 
04344       if (m_isError)
04345          return false;
04346 
04347       sz += bytesReceived;
04348       if (!dataInternal)
04349         processedSize( sz );
04350     }
04351     m_receiveBuf.resize(0); // res
04352 
04353     if (m_iBytesLeft && m_isEOD && !m_isChunked)
04354     {
04355       // gzip'ed data sometimes reports a too long content-length.
04356       // (The length of the unzipped data)
04357       m_iBytesLeft = 0;
04358     }
04359 
04360     if (m_iBytesLeft == 0)
04361     {
04362       kDebug(7113) << "EOD received! Left = "<< KIO::number(m_iBytesLeft);
04363       break;
04364     }
04365   }
04366   chain.slotInput(QByteArray()); // Flush chain.
04367 
04368   if ( useMD5 )
04369   {
04370     QString calculatedMD5 = md5Filter->md5();
04371 
04372     if ( m_contentMD5 != calculatedMD5 )
04373       kWarning(7113) << "MD5 checksum MISMATCH! Expected: "
04374                      << calculatedMD5 << ", Got: " << m_contentMD5;
04375   }
04376 
04377   // Close cache entry
04378   if (m_iBytesLeft == 0)
04379   {
04380      if (m_request.cacheTag.writeToCache && m_request.cacheTag.gzs)
04381         closeCacheEntry();
04382   }
04383 
04384   if (sz <= 1)
04385   {
04386     if (m_request.responseCode >= 500 && m_request.responseCode <= 599) {
04387       error(ERR_INTERNAL_SERVER, m_request.url.host());
04388       return false;
04389     } else if (m_request.responseCode >= 400 && m_request.responseCode <= 499 && m_request.responseCode != 401 && m_request.responseCode != 407) {
04390       error(ERR_DOES_NOT_EXIST, m_request.url.host());
04391       return false;
04392     }
04393   }
04394 
04395   if (!dataInternal)
04396     data( QByteArray() );
04397   return true;
04398 }
04399 
04400 void HTTPProtocol::slotFilterError(const QString &text)
04401 {
04402     error(KIO::ERR_SLAVE_DEFINED, text);
04403 }
04404 
04405 void HTTPProtocol::error( int _err, const QString &_text )
04406 {
04407   httpClose(false);
04408 
04409   if (!m_request.id.isEmpty())
04410   {
04411     forwardHttpResponseHeader();
04412     sendMetaData();
04413   }
04414 
04415   // It's over, we don't need it anymore
04416   m_POSTbuf.clear();
04417 
04418   SlaveBase::error( _err, _text );
04419   m_isError = true;
04420 }
04421 
04422 
04423 void HTTPProtocol::addCookies( const QString &url, const QByteArray &cookieHeader )
04424 {
04425    qlonglong windowId = m_request.windowId.toLongLong();
04426    QDBusInterface kcookiejar( "org.kde.kded", "/modules/kcookiejar", "org.kde.KCookieServer" );
04427    (void)kcookiejar.call( QDBus::NoBlock, "addCookies", url,
04428                            cookieHeader, windowId );
04429 }
04430 
04431 QString HTTPProtocol::findCookies( const QString &url)
04432 {
04433   qlonglong windowId = m_request.windowId.toLongLong();
04434   QDBusInterface kcookiejar( "org.kde.kded", "/modules/kcookiejar", "org.kde.KCookieServer" );
04435   QDBusReply<QString> reply = kcookiejar.call( "findCookies", url, windowId );
04436 
04437   if ( !reply.isValid() )
04438   {
04439      kWarning(7113) << "Can't communicate with kded_kcookiejar!";
04440      return QString();
04441   }
04442   return reply;
04443 }
04444 
04445 /******************************* CACHING CODE ****************************/
04446 
04447 
04448 void HTTPProtocol::cacheUpdate( const KUrl& url, bool no_cache, time_t expireDate)
04449 {
04450   if (!maybeSetRequestUrl(url))
04451       return;
04452 
04453   // Make sure we read in the cache info.
04454   resetSessionSettings();
04455 
04456   m_request.cacheTag.policy= CC_Reload;
04457 
04458   if (no_cache)
04459   {
04460      m_request.cacheTag.gzs = checkCacheEntry( );
04461      if (m_request.cacheTag.gzs)
04462      {
04463        gzclose(m_request.cacheTag.gzs);
04464        m_request.cacheTag.gzs = 0;
04465        QFile::remove( m_request.cacheTag.file );
04466      }
04467   }
04468   else
04469   {
04470      updateExpireDate( expireDate );
04471   }
04472   finished();
04473 }
04474 
04475 // !START SYNC!
04476 // The following code should be kept in sync
04477 // with the code in http_cache_cleaner.cpp
04478 
04479 gzFile HTTPProtocol::checkCacheEntry( bool readWrite)
04480 {
04481    const QChar separator = '_';
04482 
04483    QString CEF = m_request.url.path();
04484 
04485    int p = CEF.indexOf('/');
04486 
04487    while(p != -1)
04488    {
04489       CEF[p] = separator;
04490       p = CEF.indexOf('/', p);
04491    }
04492 
04493    QString host = m_request.url.host().toLower();
04494    CEF = host + CEF + '_';
04495 
04496    QString dir = m_strCacheDir;
04497    if (dir[dir.length()-1] != '/')
04498       dir += '/';
04499 
04500    int l = host.length();
04501    for(int i = 0; i < l; i++)
04502    {
04503       if (host[i].isLetter() && (host[i] != 'w'))
04504       {
04505          dir += host[i];
04506          break;
04507       }
04508    }
04509    if (dir[dir.length()-1] == '/')
04510       dir += '0';
04511 
04512    unsigned long hash = 0x00000000;
04513    QByteArray u = m_request.url.url().toLatin1();
04514    for(int i = u.length(); i--;)
04515    {
04516       hash = (hash * 12211 + u.at(i)) % 2147483563;
04517    }
04518 
04519    QString hashString;
04520    hashString.sprintf("%08lx", hash);
04521 
04522    CEF = CEF + hashString;
04523 
04524    CEF = dir + '/' + CEF;
04525 
04526    m_request.cacheTag.file = CEF;
04527 
04528    const char *mode = (readWrite ? "r+b" : "rb");
04529 
04530    gzFile fs = gzopen( QFile::encodeName(CEF), mode); // Open for reading and writing
04531    if (!fs)
04532       return 0;
04533 
04534    char buffer[401];
04535    bool ok = true;
04536 
04537   // CacheRevision
04538   if (ok && (!gzgets(fs, buffer, 400)))
04539       ok = false;
04540    if (ok && (strcmp(buffer, CACHE_REVISION) != 0))
04541       ok = false;
04542 
04543    time_t date;
04544    time_t currentDate = time(0);
04545 
04546    // URL
04547    if (ok && (!gzgets(fs, buffer, 400)))
04548       ok = false;
04549    if (ok)
04550    {
04551       int l = strlen(buffer);
04552       if (l>0)
04553          buffer[l-1] = 0; // Strip newline
04554       if (m_request.url.url() != buffer)
04555       {
04556          ok = false; // Hash collision
04557       }
04558    }
04559 
04560    // Creation Date
04561    if (ok && (!gzgets(fs, buffer, 400)))
04562       ok = false;
04563    if (ok)
04564    {
04565       date = (time_t) strtoul(buffer, 0, 10);
04566       m_request.cacheTag.creationDate = date;
04567       if (m_maxCacheAge && (difftime(currentDate, date) > m_maxCacheAge))
04568       {
04569          m_request.cacheTag.isExpired = true;
04570          m_request.cacheTag.expireDate = currentDate;
04571       }
04572    }
04573 
04574    // Expiration Date
04575    m_request.cacheTag.expireDateOffset = gztell(fs);
04576    if (ok && (!gzgets(fs, buffer, 400)))
04577       ok = false;
04578    if (ok)
04579    {
04580       if (m_request.cacheTag.policy== CC_Verify)
04581       {
04582          date = (time_t) strtoul(buffer, 0, 10);
04583          // After the expire date we need to revalidate.
04584          if (!date || difftime(currentDate, date) >= 0)
04585             m_request.cacheTag.isExpired = true;
04586          m_request.cacheTag.expireDate = date;
04587       }
04588       else if (m_request.cacheTag.policy== CC_Refresh)
04589       {
04590          m_request.cacheTag.isExpired = true;
04591          m_request.cacheTag.expireDate = currentDate;
04592       }
04593    }
04594 
04595    // ETag
04596    if (ok && (!gzgets(fs, buffer, 400)))
04597       ok = false;
04598    if (ok)
04599    {
04600       m_request.cacheTag.etag = QString(buffer).trimmed();
04601    }
04602 
04603    // Last-Modified
04604    if (ok && (!gzgets(fs, buffer, 400)))
04605       ok = false;
04606    if (ok)
04607    {
04608       m_request.cacheTag.bytesCached=0;
04609       m_request.cacheTag.lastModified = QString(buffer).trimmed();
04610 //    }
04611 
04612 //    if (ok)
04613 //    {
04614 
04615       //write hit frequency data
04616       int freq=0;
04617       FILE* hitdata = fopen( QFile::encodeName(CEF+"_freq"), "r+");
04618          if (hitdata)
04619          {
04620              freq=fgetc(hitdata);
04621              if (freq!=EOF)
04622                 freq+=fgetc(hitdata)<<8;
04623              else
04624                 freq=0;
04625             KDE_fseek(hitdata,0,SEEK_SET);
04626          }
04627          if (hitdata||(hitdata=fopen(QFile::encodeName(CEF+"_freq"), "w")))
04628          {
04629              fputc(++freq,hitdata);
04630              fputc(freq>>8,hitdata);
04631              fclose(hitdata);
04632          }
04633 
04634       return fs;
04635    }
04636 
04637    gzclose(fs);
04638    QFile::remove( CEF );
04639    return 0;
04640 }
04641 
04642 void HTTPProtocol::updateExpireDate(time_t expireDate, bool updateCreationDate)
04643 {
04644     bool ok = true;
04645 
04646     gzFile fs = checkCacheEntry(true);
04647     if (fs)
04648     {
04649         QString date;
04650         char buffer[401];
04651         time_t creationDate;
04652 
04653         gzseek(fs, 0, SEEK_SET);
04654         if (ok && !gzgets(fs, buffer, 400))
04655             ok = false;
04656         if (ok && !gzgets(fs, buffer, 400))
04657             ok = false;
04658         long cacheCreationDateOffset = gztell(fs);
04659         if (ok && !gzgets(fs, buffer, 400))
04660             ok = false;
04661         creationDate = strtoul(buffer, 0, 10);
04662         if (!creationDate)
04663             ok = false;
04664 
04665         if (updateCreationDate)
04666         {
04667            if (!ok || gzseek(fs, cacheCreationDateOffset, SEEK_SET))
04668               return;
04669            QString date;
04670            date.setNum( time(0) );
04671            date = date.leftJustified(16);
04672            gzputs(fs, date.toLatin1());      // Creation date
04673            gzputc(fs, '\n');
04674         }
04675 
04676         if (expireDate > (30 * 365 * 24 * 60 * 60))
04677         {
04678             // expire date is a really a big number, it can't be
04679             // a relative date.
04680             date.setNum( expireDate );
04681         }
04682         else
04683         {
04684             // expireDate before 2000. those values must be
04685             // interpreted as relative expiration dates from
04686             // <META http-equiv="Expires"> tags.
04687             // so we have to scan the creation time and add
04688             // it to the expiryDate
04689             date.setNum( creationDate + expireDate );
04690         }
04691         date = date.leftJustified(16);
04692         if (!ok || gzseek(fs, m_request.cacheTag.expireDateOffset, SEEK_SET))
04693             return;
04694         gzputs(fs, date.toLatin1());      // Expire date
04695         gzseek(fs, 0, SEEK_END);
04696         gzclose(fs);
04697     }
04698 }
04699 
04700 void HTTPProtocol::createCacheEntry( const QString &mimetype, time_t expireDate)
04701 {
04702    QString dir = m_request.cacheTag.file;
04703    int p = dir.lastIndexOf('/');
04704    if (p == -1) return; // Error.
04705    dir.truncate(p);
04706 
04707    // Create file
04708    KDE::mkdir( dir, 0700 );
04709 
04710    QString filename = m_request.cacheTag.file + ".new";  // Create a new cache entryexpireDate
04711 
04712 //   kDebug( 7103 ) <<  "creating new cache entry: " << filename;
04713 
04714    m_request.cacheTag.gzs = gzopen( QFile::encodeName(filename), "wb");
04715    if (!m_request.cacheTag.gzs)
04716    {
04717       kWarning(7113) << "opening" << filename << "failed.";
04718       return; // Error.
04719    }
04720 
04721    gzputs(m_request.cacheTag.gzs, CACHE_REVISION);    // Revision
04722 
04723    gzputs(m_request.cacheTag.gzs, m_request.url.url().toLatin1());  // Url
04724    gzputc(m_request.cacheTag.gzs, '\n');
04725 
04726    QString date;
04727    m_request.cacheTag.creationDate = time(0);
04728    date.setNum( m_request.cacheTag.creationDate );
04729    date = date.leftJustified(16);
04730    gzputs(m_request.cacheTag.gzs, date.toLatin1());      // Creation date
04731    gzputc(m_request.cacheTag.gzs, '\n');
04732 
04733    date.setNum( expireDate );
04734    date = date.leftJustified(16);
04735    gzputs(m_request.cacheTag.gzs, date.toLatin1());      // Expire date
04736    gzputc(m_request.cacheTag.gzs, '\n');
04737 
04738    if (!m_request.cacheTag.etag.isEmpty())
04739       gzputs(m_request.cacheTag.gzs, m_request.cacheTag.etag.toLatin1());    //ETag
04740    gzputc(m_request.cacheTag.gzs, '\n');
04741 
04742    if (!m_request.cacheTag.lastModified.isEmpty())
04743       gzputs(m_request.cacheTag.gzs, m_request.cacheTag.lastModified.toLatin1());    // Last modified
04744    gzputc(m_request.cacheTag.gzs, '\n');
04745 
04746    gzputs(m_request.cacheTag.gzs, mimetype.toLatin1());  // Mimetype
04747    gzputc(m_request.cacheTag.gzs, '\n');
04748 
04749    gzputs(m_request.cacheTag.gzs, m_responseHeaders.join("\n").toLatin1());
04750    gzputc(m_request.cacheTag.gzs, '\n');
04751 
04752    gzputc(m_request.cacheTag.gzs, '\n');
04753 
04754    return;
04755 }
04756 // The above code should be kept in sync
04757 // with the code in http_cache_cleaner.cpp
04758 // !END SYNC!
04759 
04760 void HTTPProtocol::writeCacheEntry( const char *buffer, int nbytes)
04761 {
04762    // gzwrite's second argument has type void *const in 1.1.4 and
04763    // const void * in 1.2.3, so we futz buffer to a plain void * and
04764    // let the compiler figure it out from there.
04765    if (gzwrite(m_request.cacheTag.gzs, const_cast<void *>(static_cast<const void *>(buffer)), nbytes) == 0)
04766    {
04767       kWarning(7113) << "writeCacheEntry: writing " << nbytes << " bytes failed.";
04768       gzclose(m_request.cacheTag.gzs);
04769       m_request.cacheTag.gzs = 0;
04770       QString filename = m_request.cacheTag.file + ".new";
04771       QFile::remove( filename );
04772       return;
04773    }
04774    m_request.cacheTag.bytesCached+=nbytes;
04775    if ( m_request.cacheTag.bytesCached>>10 > m_maxCacheSize )
04776    {
04777       kDebug(7113) << "writeCacheEntry: File size reaches " << (m_request.cacheTag.bytesCached>>10)
04778                     << "Kb, exceeds cache limits. (" << m_maxCacheSize << "Kb)";
04779       gzclose(m_request.cacheTag.gzs);
04780       m_request.cacheTag.gzs = 0;
04781       QString filename = m_request.cacheTag.file + ".new";
04782       QFile::remove( filename );
04783       return;
04784    }
04785 }
04786 
04787 void HTTPProtocol::closeCacheEntry()
04788 {
04789    QString filename = m_request.cacheTag.file + ".new";
04790    int result = gzclose( m_request.cacheTag.gzs);
04791    m_request.cacheTag.gzs = 0;
04792    if (result == 0)
04793    {
04794       if (KDE::rename( filename, m_request.cacheTag.file) == 0)
04795          return; // Success
04796       kWarning(7113) << "closeCacheEntry: error renaming "
04797                       << "cache entry. (" << filename << " -> " << m_request.cacheTag.file
04798                       << ")";
04799    }
04800 
04801    kWarning(7113) << "closeCacheEntry: error closing cache "
04802                    << "entry. (" << filename<< ")";
04803 }
04804 
04805 void HTTPProtocol::cleanCache()
04806 {
04807    const time_t maxAge = DEFAULT_CLEAN_CACHE_INTERVAL; // 30 Minutes.
04808    bool doClean = false;
04809    QString cleanFile = m_strCacheDir;
04810    if (cleanFile[cleanFile.length()-1] != '/')
04811       cleanFile += '/';
04812    cleanFile += "cleaned";
04813 
04814    KDE_struct_stat stat_buf;
04815 
04816    int result = KDE::stat(cleanFile, &stat_buf);
04817    if (result == -1)
04818    {
04819       int fd = KDE::open( cleanFile, O_WRONLY|O_CREAT|O_TRUNC, 0600);
04820       if (fd != -1)
04821       {
04822          doClean = true;
04823          ::close(fd);
04824       }
04825    }
04826    else
04827    {
04828       time_t age = (time_t) difftime( time(0), stat_buf.st_mtime );
04829       if (age > maxAge) //
04830         doClean = true;
04831    }
04832    if (doClean)
04833    {
04834       // Touch file.
04835       KDE::utime(cleanFile, 0);
04836       KToolInvocation::startServiceByDesktopPath("http_cache_cleaner.desktop");
04837    }
04838 }
04839 
04840 
04841 
04842 //**************************  AUTHENTICATION CODE ********************/
04843 
04844 
04845 void HTTPProtocol::fillPromptInfo(AuthInfo *inf)
04846 {
04847   AuthInfo &info = *inf;    //no use rewriting everything below
04848 
04849   info.keepPassword = true; // Prompt the user for persistence as well.
04850   info.verifyPath = false;
04851 
04852   if ( m_request.responseCode == 401 )
04853   {
04854     // TODO sort out the data flow of the password
04855     info.url = m_request.url;
04856     if ( !m_server.url.user().isEmpty() )
04857       info.username = m_server.url.user();
04858     info.prompt = i18n( "You need to supply a username and a "
04859                         "password to access this site." );
04860     Q_ASSERT(m_wwwAuth);
04861     if (m_wwwAuth)
04862     {
04863       info.realmValue = m_wwwAuth->realm();
04864       //TODO info.digestInfo = m_wwwAuth.authorization;
04865       info.commentLabel = i18n("Site:");
04866       info.comment = i18n("<b>%1</b> at <b>%2</b>", htmlEscape(info.realmValue), m_request.url.host());
04867     }
04868   }
04869   else if ( m_request.responseCode == 407 )
04870   {
04871     info.url = m_request.proxyUrl;
04872     info.username = m_request.proxyUrl.user();
04873     info.prompt = i18n( "You need to supply a username and a password for "
04874                         "the proxy server listed below before you are allowed "
04875                         "to access any sites." );
04876     Q_ASSERT(m_proxyAuth);
04877     if (m_proxyAuth)
04878     {
04879       info.realmValue = m_proxyAuth->realm();
04880       //TODO info.digestInfo = m_proxyAuth.authorization;
04881       info.commentLabel = i18n("Proxy:");
04882       info.comment = i18n("<b>%1</b> at <b>%2</b>", htmlEscape(info.realmValue), m_request.proxyUrl.host());
04883     }
04884   }
04885 }
04886 
04887 
04888 QString HTTPProtocol::authenticationHeader()
04889 {
04890     QString ret;
04891     // the authentication classes don't know if they are for proxy or webserver authentication...
04892     if (m_wwwAuth && !m_wwwAuth->isError()) {
04893         ret += "Authorization: ";
04894         ret += m_wwwAuth->headerFragment();
04895     }
04896     if (m_proxyAuth && !m_proxyAuth->isError()) {
04897         ret += "Proxy-Authorization: ";
04898         ret += m_proxyAuth->headerFragment();
04899     }
04900     return ret;
04901 }
04902 
04903 
04904 void HTTPProtocol::proxyAuthenticationForSocket(const QNetworkProxy &proxy, QAuthenticator *authenticator)
04905 {
04906     Q_UNUSED(proxy);
04907     kDebug(7113) << "Authenticator received -- realm: " << authenticator->realm() << "user:"
04908                  << authenticator->user();
04909 
04910     AuthInfo info;
04911     Q_ASSERT(proxy.hostName() == m_request.proxyUrl.host() && proxy.port() == m_request.proxyUrl.port());
04912     info.url = m_request.proxyUrl;
04913     info.realmValue = authenticator->realm();
04914     info.verifyPath = true;    //### whatever
04915     info.username = authenticator->user();
04916 
04917     const bool haveCachedCredentials = checkCachedAuthentication(info);
04918 
04919     // if m_socketProxyAuth is a valid pointer then authentication has been attempted before,
04920     // and it was not successful. see below and saveProxyAuthenticationForSocket().
04921     if (!haveCachedCredentials || m_socketProxyAuth) {
04922         // Save authentication info if the connection succeeds. We need to disconnect
04923         // this after saving the auth data (or an error) so we won't save garbage afterwards!
04924         connect(socket(), SIGNAL(connected()),
04925                 this, SLOT(saveProxyAuthenticationForSocket()));
04926         //### fillPromptInfo(&info);
04927         info.prompt = i18n("You need to supply a username and a password for "
04928                            "the proxy server listed below before you are allowed "
04929                            "to access any sites.");
04930         info.keepPassword = true;
04931         info.commentLabel = i18n("Proxy:");
04932         info.comment = i18n("<b>%1</b> at <b>%2</b>", htmlEscape(info.realmValue), m_request.proxyUrl.host());
04933         const bool dataEntered = openPasswordDialog(info, i18n("Proxy Authentication Failed."));
04934         if (!dataEntered) {
04935             kDebug(7103) << "looks like the user canceled proxy authentication.";
04936             error(ERR_USER_CANCELED, m_request.proxyUrl.host());
04937         }
04938     }
04939     authenticator->setUser(info.username);
04940     authenticator->setPassword(info.password);
04941 
04942     if (m_socketProxyAuth) {
04943         *m_socketProxyAuth = *authenticator;
04944     } else {
04945         m_socketProxyAuth = new QAuthenticator(*authenticator);
04946     }
04947 
04948     m_request.proxyUrl.setUser(info.username);
04949     m_request.proxyUrl.setPassword(info.password);
04950 }
04951 
04952 void HTTPProtocol::saveProxyAuthenticationForSocket()
04953 {
04954     kDebug(7113) << "Saving authenticator";
04955     disconnect(socket(), SIGNAL(connected()),
04956                this, SLOT(saveProxyAuthenticationForSocket()));
04957     Q_ASSERT(m_socketProxyAuth);
04958     if (m_socketProxyAuth) {
04959         kDebug(7113) << "-- realm: " << m_socketProxyAuth->realm() << "user:"
04960                      << m_socketProxyAuth->user();
04961         KIO::AuthInfo a;
04962         a.verifyPath = true;
04963         a.url = m_request.proxyUrl;
04964         a.realmValue = m_socketProxyAuth->realm();
04965         a.username = m_socketProxyAuth->user();
04966         a.password = m_socketProxyAuth->password();
04967         cacheAuthentication(a);
04968     }
04969     delete m_socketProxyAuth;
04970     m_socketProxyAuth = 0;
04971 }
04972 
04973 #include "http.moc"

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
  • KPty
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.6.1
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