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

KIOSlave

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • 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.3
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