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

KIOSlave

Skip menu "KIOSlave"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • 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
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.7
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