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