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

KDE's Doxygen guidelines are available online.

KIOSlave

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

kdelibs-4.9.5 API Reference

Skip menu "kdelibs-4.9.5 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal