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