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

KIOSlave

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

kdelibs

Skip menu "kdelibs"
  • 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
Generated for kdelibs by doxygen 1.7.6.1
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal