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

Skip menu "kdelibs-4.8.3 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