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