http.cc

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 
00007    This library is free software; you can redistribute it and/or
00008    modify it under the terms of the GNU Library General Public
00009    License (LGPL) as published by the Free Software Foundation;
00010    either version 2 of the License, or (at your option) any later
00011    version.
00012 
00013    This library is distributed in the hope that it will be useful,
00014    but WITHOUT ANY WARRANTY; without even the implied warranty of
00015    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016    Library General Public License for more details.
00017 
00018    You should have received a copy of the GNU Library General Public License
00019    along with this library; see the file COPYING.LIB.  If not, write to
00020    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00021    Boston, MA 02110-1301, USA.
00022 */
00023 
00024 #include <config.h>
00025 
00026 #include <errno.h>
00027 #include <fcntl.h>
00028 #include <utime.h>
00029 #include <stdlib.h>
00030 #include <signal.h>
00031 #include <sys/stat.h>
00032 #include <sys/socket.h>
00033 #include <netinet/in.h>  // Required for AIX
00034 #include <netinet/tcp.h>
00035 #include <unistd.h> // must be explicitly included for MacOSX
00036 
00037 /*
00038 #include <netdb.h>
00039 #include <sys/time.h>
00040 #include <sys/wait.h>
00041 */
00042 
00043 #include <qdom.h>
00044 #include <qfile.h>
00045 #include <qregexp.h>
00046 #include <qdatetime.h>
00047 #include <qstringlist.h>
00048 
00049 #include <kurl.h>
00050 #include <kidna.h>
00051 #include <ksocks.h>
00052 #include <kdebug.h>
00053 #include <klocale.h>
00054 #include <kconfig.h>
00055 #include <kextsock.h>
00056 #include <kservice.h>
00057 #include <krfcdate.h>
00058 #include <kmdcodec.h>
00059 #include <kinstance.h>
00060 #include <kresolver.h>
00061 #include <kmimemagic.h>
00062 #include <dcopclient.h>
00063 #include <kdatastream.h>
00064 #include <kapplication.h>
00065 #include <kstandarddirs.h>
00066 #include <kstringhandler.h>
00067 #include <kremoteencoding.h>
00068 
00069 #include "kio/ioslave_defaults.h"
00070 #include "kio/http_slave_defaults.h"
00071 
00072 #include "httpfilter.h"
00073 #include "http.h"
00074 
00075 #ifdef HAVE_LIBGSSAPI
00076 #ifdef GSSAPI_MIT
00077 #include <gssapi/gssapi.h>
00078 #else
00079 #include <gssapi.h>
00080 #endif /* GSSAPI_MIT */
00081 
00082 // Catch uncompatible crap (BR86019)
00083 #if defined(GSS_RFC_COMPLIANT_OIDS) && (GSS_RFC_COMPLIANT_OIDS == 0)
00084 #include <gssapi/gssapi_generic.h>
00085 #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
00086 #endif
00087 
00088 #endif /* HAVE_LIBGSSAPI */
00089 
00090 #include <misc/kntlm/kntlm.h>
00091 
00092 using namespace KIO;
00093 
00094 extern "C" {
00095   KDE_EXPORT int kdemain(int argc, char **argv);
00096 }
00097 
00098 int kdemain( int argc, char **argv )
00099 {
00100   KLocale::setMainCatalogue("kdelibs");
00101   KInstance instance( "kio_http" );
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 char * trimLead (char *orig_string)
00118 {
00119   while (*orig_string == ' ')
00120     orig_string++;
00121   return orig_string;
00122 }
00123 
00124 static bool isCrossDomainRequest( const QString& fqdn, const QString& originURL )
00125 {
00126   if (originURL == "true") // Backwards compatibility
00127      return true;
00128 
00129   KURL url ( originURL );
00130 
00131   // Document Origin domain
00132   QString a = url.host();
00133 
00134   // Current request domain
00135   QString b = fqdn;
00136 
00137   if (a == b)
00138     return false;
00139 
00140   QStringList l1 = QStringList::split('.', a);
00141   QStringList l2 = QStringList::split('.', b);
00142 
00143   while(l1.count() > l2.count())
00144       l1.pop_front();
00145 
00146   while(l2.count() > l1.count())
00147       l2.pop_front();
00148 
00149   while(l2.count() >= 2)
00150   {
00151       if (l1 == l2)
00152           return false;
00153 
00154       l1.pop_front();
00155       l2.pop_front();
00156   }
00157 
00158   return true;
00159 }
00160 
00161 /*
00162   Eliminates any custom header that could potentically alter the request
00163 */
00164 static QString sanitizeCustomHTTPHeader(const QString& _header)
00165 {
00166   QString sanitizedHeaders;
00167   QStringList headers = QStringList::split(QRegExp("[\r\n]"), _header);
00168 
00169   for(QStringList::Iterator it = headers.begin(); it != headers.end(); ++it)
00170   {
00171     QString header = (*it).lower();
00172     // Do not allow Request line to be specified and ignore
00173     // the other HTTP headers.
00174     if (header.find(':') == -1 ||
00175         header.startsWith("host") ||
00176         header.startsWith("via"))
00177       continue;
00178 
00179     sanitizedHeaders += (*it);
00180     sanitizedHeaders += "\r\n";
00181   }
00182 
00183   return sanitizedHeaders.stripWhiteSpace();
00184 }
00185 
00186 static QString htmlEscape(const QString &plain)
00187 {
00188     QString rich;
00189     rich.reserve(uint(plain.length() * 1.1));
00190     for (uint i = 0; i < plain.length(); ++i) {
00191         if (plain.at(i) == '<')
00192             rich += "&lt;";
00193         else if (plain.at(i) == '>')
00194             rich += "&gt;";
00195         else if (plain.at(i) == '&')
00196             rich += "&amp;";
00197         else if (plain.at(i) == '"')
00198             rich += "&quot;";
00199         else
00200             rich += plain.at(i);
00201     }
00202     rich.squeeze();
00203     return rich;
00204 }
00205 
00206 
00207 #define NO_SIZE     ((KIO::filesize_t) -1)
00208 
00209 #ifdef HAVE_STRTOLL
00210 #define STRTOLL strtoll
00211 #else
00212 #define STRTOLL strtol
00213 #endif
00214 
00215 
00216 /************************************** HTTPProtocol **********************************************/
00217 
00218 HTTPProtocol::HTTPProtocol( const QCString &protocol, const QCString &pool,
00219                             const QCString &app )
00220              :TCPSlaveBase( 0, protocol , pool, app,
00221                             (protocol == "https" || protocol == "webdavs") )
00222 {
00223   m_requestQueue.setAutoDelete(true);
00224 
00225   m_bBusy = false;
00226   m_bFirstRequest = false;
00227   m_bProxyAuthValid = false;
00228 
00229   m_iSize = NO_SIZE;
00230   m_lineBufUnget = 0;
00231 
00232   m_protocol = protocol;
00233 
00234   m_maxCacheAge = DEFAULT_MAX_CACHE_AGE;
00235   m_maxCacheSize = DEFAULT_MAX_CACHE_SIZE / 2;
00236   m_remoteConnTimeout = DEFAULT_CONNECT_TIMEOUT;
00237   m_remoteRespTimeout = DEFAULT_RESPONSE_TIMEOUT;
00238   m_proxyConnTimeout = DEFAULT_PROXY_CONNECT_TIMEOUT;
00239 
00240   m_pid = getpid();
00241 
00242   setMultipleAuthCaching( true );
00243   reparseConfiguration();
00244 }
00245 
00246 HTTPProtocol::~HTTPProtocol()
00247 {
00248   httpClose(false);
00249 }
00250 
00251 void HTTPProtocol::reparseConfiguration()
00252 {
00253   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::reparseConfiguration" << endl;
00254 
00255   m_strProxyRealm = QString::null;
00256   m_strProxyAuthorization = QString::null;
00257   ProxyAuthentication = AUTH_None;
00258   m_bUseProxy = false;
00259 
00260   if (m_protocol == "https" || m_protocol == "webdavs")
00261     m_iDefaultPort = DEFAULT_HTTPS_PORT;
00262   else if (m_protocol == "ftp")
00263     m_iDefaultPort = DEFAULT_FTP_PORT;
00264   else
00265     m_iDefaultPort = DEFAULT_HTTP_PORT;
00266 }
00267 
00268 void HTTPProtocol::resetConnectionSettings()
00269 {
00270   m_bEOF = false;
00271   m_bError = false;
00272   m_lineCount = 0;
00273   m_iWWWAuthCount = 0;
00274   m_lineCountUnget = 0;
00275   m_iProxyAuthCount = 0;
00276 
00277 }
00278 
00279 void HTTPProtocol::resetResponseSettings()
00280 {
00281   m_bRedirect = false;
00282   m_redirectLocation = KURL();
00283   m_bChunked = false;
00284   m_iSize = NO_SIZE;
00285 
00286   m_responseHeader.clear();
00287   m_qContentEncodings.clear();
00288   m_qTransferEncodings.clear();
00289   m_sContentMD5 = QString::null;
00290   m_strMimeType = QString::null;
00291 
00292   setMetaData("request-id", m_request.id);
00293 }
00294 
00295 void HTTPProtocol::resetSessionSettings()
00296 {
00297   // Do not reset the URL on redirection if the proxy
00298   // URL, username or password has not changed!
00299   KURL proxy ( config()->readEntry("UseProxy") );
00300 
00301   if ( m_strProxyRealm.isEmpty() || !proxy.isValid() ||
00302        m_proxyURL.host() != proxy.host() ||
00303        (!proxy.user().isNull() && proxy.user() != m_proxyURL.user()) ||
00304        (!proxy.pass().isNull() && proxy.pass() != m_proxyURL.pass()) )
00305   {
00306     m_bProxyAuthValid = false;
00307     m_proxyURL = proxy;
00308     m_bUseProxy = m_proxyURL.isValid();
00309 
00310     kdDebug(7113) << "(" << m_pid << ") Using proxy: " << m_bUseProxy <<
00311                                               " URL: " << m_proxyURL.url() <<
00312                                             " Realm: " << m_strProxyRealm << endl;
00313   }
00314 
00315   m_bPersistentProxyConnection = config()->readBoolEntry("PersistentProxyConnection", false);
00316   kdDebug(7113) << "(" << m_pid << ") Enable Persistent Proxy Connection: "
00317                 << m_bPersistentProxyConnection << endl;
00318 
00319   m_request.bUseCookiejar = config()->readBoolEntry("Cookies");
00320   m_request.bUseCache = config()->readBoolEntry("UseCache", true);
00321   m_request.bErrorPage = config()->readBoolEntry("errorPage", true);
00322   m_request.bNoAuth = config()->readBoolEntry("no-auth");
00323   m_strCacheDir = config()->readPathEntry("CacheDir");
00324   m_maxCacheAge = config()->readNumEntry("MaxCacheAge", DEFAULT_MAX_CACHE_AGE);
00325   m_request.window = config()->readEntry("window-id");
00326 
00327   kdDebug(7113) << "(" << m_pid << ") Window Id = " << m_request.window << endl;
00328   kdDebug(7113) << "(" << m_pid << ") ssl_was_in_use = "
00329                 << metaData ("ssl_was_in_use") << endl;
00330 
00331   m_request.referrer = QString::null;
00332   if ( config()->readBoolEntry("SendReferrer", true) &&
00333        (m_protocol == "https" || m_protocol == "webdavs" ||
00334         metaData ("ssl_was_in_use") != "TRUE" ) )
00335   {
00336      KURL referrerURL ( metaData("referrer") );
00337      if (referrerURL.isValid())
00338      {
00339         // Sanitize
00340         QString protocol = referrerURL.protocol();
00341         if (protocol.startsWith("webdav"))
00342         {
00343            protocol.replace(0, 6, "http");
00344            referrerURL.setProtocol(protocol);
00345         }
00346 
00347         if (protocol.startsWith("http"))
00348         {
00349            referrerURL.setRef(QString::null);
00350            referrerURL.setUser(QString::null);
00351            referrerURL.setPass(QString::null);
00352            m_request.referrer = referrerURL.url();
00353         }
00354      }
00355   }
00356 
00357   if ( config()->readBoolEntry("SendLanguageSettings", true) )
00358   {
00359       m_request.charsets = config()->readEntry( "Charsets", "iso-8859-1" );
00360 
00361       if ( !m_request.charsets.isEmpty() )
00362           m_request.charsets += DEFAULT_PARTIAL_CHARSET_HEADER;
00363 
00364       m_request.languages = config()->readEntry( "Languages", DEFAULT_LANGUAGE_HEADER );
00365   }
00366   else
00367   {
00368       m_request.charsets = QString::null;
00369       m_request.languages = QString::null;
00370   }
00371 
00372   // Adjust the offset value based on the "resume" meta-data.
00373   QString resumeOffset = metaData("resume");
00374   if ( !resumeOffset.isEmpty() )
00375      m_request.offset = resumeOffset.toInt(); // TODO: Convert to 64 bit
00376   else
00377      m_request.offset = 0;
00378 
00379   m_request.disablePassDlg = config()->readBoolEntry("DisablePassDlg", false);
00380   m_request.allowCompressedPage = config()->readBoolEntry("AllowCompressedPage", true);
00381   m_request.id = metaData("request-id");
00382 
00383   // Store user agent for this host.
00384   if ( config()->readBoolEntry("SendUserAgent", true) )
00385      m_request.userAgent = metaData("UserAgent");
00386   else
00387      m_request.userAgent = QString::null;
00388 
00389   // Deal with cache cleaning.
00390   // TODO: Find a smarter way to deal with cleaning the
00391   // cache ?
00392   if ( m_request.bUseCache )
00393     cleanCache();
00394 
00395   // Deal with HTTP tunneling
00396   if ( m_bIsSSL && m_bUseProxy && m_proxyURL.protocol() != "https" &&
00397        m_proxyURL.protocol() != "webdavs")
00398   {
00399     m_bNeedTunnel = true;
00400     setRealHost( m_request.hostname );
00401     kdDebug(7113) << "(" << m_pid << ") SSL tunnel: Setting real hostname to: "
00402                   << m_request.hostname << endl;
00403   }
00404   else
00405   {
00406     m_bNeedTunnel = false;
00407     setRealHost( QString::null);
00408   }
00409 
00410   m_responseCode = 0;
00411   m_prevResponseCode = 0;
00412 
00413   m_strRealm = QString::null;
00414   m_strAuthorization = QString::null;
00415   Authentication = AUTH_None;
00416 
00417   // Obtain the proxy and remote server timeout values
00418   m_proxyConnTimeout = proxyConnectTimeout();
00419   m_remoteConnTimeout = connectTimeout();
00420   m_remoteRespTimeout = responseTimeout();
00421 
00422   // Set the SSL meta-data here...
00423   setSSLMetaData();
00424 
00425   // Bounce back the actual referrer sent
00426   setMetaData("referrer", m_request.referrer);
00427 
00428   // Follow HTTP/1.1 spec and enable keep-alive by default
00429   // unless the remote side tells us otherwise or we determine
00430   // the persistent link has been terminated by the remote end.
00431   m_bKeepAlive = true;
00432   m_keepAliveTimeout = 0;
00433   m_bUnauthorized = false;
00434 
00435   // A single request can require multiple exchanges with the remote
00436   // server due to authentication challenges or SSL tunneling.
00437   // m_bFirstRequest is a flag that indicates whether we are
00438   // still processing the first request. This is important because we
00439   // should not force a close of a keep-alive connection in the middle
00440   // of the first request.
00441   // m_bFirstRequest is set to "true" whenever a new connection is
00442   // made in httpOpenConnection()
00443   m_bFirstRequest = false;
00444 }
00445 
00446 void HTTPProtocol::setHost( const QString& host, int port,
00447                             const QString& user, const QString& pass )
00448 {
00449   // Reset the webdav-capable flags for this host
00450   if ( m_request.hostname != host )
00451     m_davHostOk = m_davHostUnsupported = false;
00452 
00453   // is it an IPv6 address?
00454   if (host.find(':') == -1)
00455     {
00456       m_request.hostname = host;
00457       m_request.encoded_hostname = KIDNA::toAscii(host);
00458     }
00459   else
00460     {
00461       m_request.hostname = host;
00462       int pos = host.find('%');
00463       if (pos == -1)
00464     m_request.encoded_hostname = '[' + host + ']';
00465       else
00466     // don't send the scope-id in IPv6 addresses to the server
00467     m_request.encoded_hostname = '[' + host.left(pos) + ']';
00468     }
00469   m_request.port = (port == 0) ? m_iDefaultPort : port;
00470   m_request.user = user;
00471   m_request.passwd = pass;
00472 
00473   m_bIsTunneled = false;
00474 
00475   kdDebug(7113) << "(" << m_pid << ") Hostname is now: " << m_request.hostname <<
00476     " (" << m_request.encoded_hostname << ")" <<endl;
00477 }
00478 
00479 bool HTTPProtocol::checkRequestURL( const KURL& u )
00480 {
00481   kdDebug (7113) << "(" << m_pid << ") HTTPProtocol::checkRequestURL:  " << u.url() << endl;
00482 
00483   m_request.url = u;
00484 
00485   if (m_request.hostname.isEmpty())
00486   {
00487      error( KIO::ERR_UNKNOWN_HOST, i18n("No host specified."));
00488      return false;
00489   }
00490 
00491   if (u.path().isEmpty())
00492   {
00493      KURL newUrl(u);
00494      newUrl.setPath("/");
00495      redirection(newUrl);
00496      finished();
00497      return false;
00498   }
00499 
00500   if ( m_protocol != u.protocol().latin1() )
00501   {
00502     short unsigned int oldDefaultPort = m_iDefaultPort;
00503     m_protocol = u.protocol().latin1();
00504     reparseConfiguration();
00505     if ( m_iDefaultPort != oldDefaultPort &&
00506          m_request.port == oldDefaultPort )
00507         m_request.port = m_iDefaultPort;
00508   }
00509 
00510   resetSessionSettings();
00511   return true;
00512 }
00513 
00514 void HTTPProtocol::retrieveContent( bool dataInternal /* = false */ )
00515 {
00516   kdDebug (7113) << "(" << m_pid << ") HTTPProtocol::retrieveContent " << endl;
00517   if ( !retrieveHeader( false ) )
00518   {
00519     if ( m_bError )
00520       return;
00521   }
00522   else
00523   {
00524     if ( !readBody( dataInternal ) && m_bError )
00525       return;
00526   }
00527 
00528   httpClose(m_bKeepAlive);
00529 
00530   // if data is required internally, don't finish,
00531   // it is processed before we finish()
00532   if ( !dataInternal )
00533   {
00534     if ((m_responseCode == 204) &&
00535         ((m_request.method == HTTP_GET) || (m_request.method == HTTP_POST)))
00536        error(ERR_NO_CONTENT, "");
00537     else
00538        finished();
00539   }
00540 }
00541 
00542 bool HTTPProtocol::retrieveHeader( bool close_connection )
00543 {
00544   kdDebug (7113) << "(" << m_pid << ") HTTPProtocol::retrieveHeader " << endl;
00545   while ( 1 )
00546   {
00547     if (!httpOpen())
00548       return false;
00549 
00550     resetResponseSettings();
00551     if (!readHeader())
00552     {
00553       if ( m_bError )
00554         return false;
00555 
00556       if (m_bIsTunneled)
00557       {
00558         kdDebug(7113) << "(" << m_pid << ") Re-establishing SSL tunnel..." << endl;
00559         httpCloseConnection();
00560       }
00561     }
00562     else
00563     {
00564       // Do not save authorization if the current response code is
00565       // 4xx (client error) or 5xx (server error).
00566       kdDebug(7113) << "(" << m_pid << ") Previous Response: "
00567                     << m_prevResponseCode << endl;
00568       kdDebug(7113) << "(" << m_pid << ") Current Response: "
00569                     << m_responseCode << endl;
00570 
00571       if (isSSLTunnelEnabled() &&  m_bIsSSL && !m_bUnauthorized && !m_bError)
00572       {
00573         // If there is no error, disable tunneling
00574         if ( m_responseCode < 400 )
00575         {
00576           kdDebug(7113) << "(" << m_pid << ") Unset tunneling flag!" << endl;
00577           setEnableSSLTunnel( false );
00578           m_bIsTunneled = true;
00579           // Reset the CONNECT response code...
00580           m_responseCode = m_prevResponseCode;
00581           continue;
00582         }
00583         else
00584         {
00585           if ( !m_request.bErrorPage )
00586           {
00587             kdDebug(7113) << "(" << m_pid << ") Sending an error message!" << endl;
00588             error( ERR_UNKNOWN_PROXY_HOST, m_proxyURL.host() );
00589             return false;
00590           }
00591 
00592           kdDebug(7113) << "(" << m_pid << ") Sending an error page!" << endl;
00593         }
00594       }
00595 
00596       if (m_responseCode < 400 && (m_prevResponseCode == 401 ||
00597           m_prevResponseCode == 407))
00598         saveAuthorization();
00599       break;
00600     }
00601   }
00602 
00603   // Clear of the temporary POST buffer if it is not empty...
00604   if (!m_bufPOST.isEmpty())
00605   {
00606     m_bufPOST.resize(0);
00607     kdDebug(7113) << "(" << m_pid << ") HTTP::retreiveHeader: Cleared POST "
00608                      "buffer..." << endl;
00609   }
00610 
00611   if ( close_connection )
00612   {
00613     httpClose(m_bKeepAlive);
00614     finished();
00615   }
00616 
00617   return true;
00618 }
00619 
00620 void HTTPProtocol::stat(const KURL& url)
00621 {
00622   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::stat " << url.prettyURL()
00623                 << endl;
00624 
00625   if ( !checkRequestURL( url ) )
00626       return;
00627 
00628   if ( m_protocol != "webdav" && m_protocol != "webdavs" )
00629   {
00630     QString statSide = metaData(QString::fromLatin1("statSide"));
00631     if ( statSide != "source" )
00632     {
00633       // When uploading we assume the file doesn't exit
00634       error( ERR_DOES_NOT_EXIST, url.prettyURL() );
00635       return;
00636     }
00637 
00638     // When downloading we assume it exists
00639     UDSEntry entry;
00640     UDSAtom atom;
00641     atom.m_uds = KIO::UDS_NAME;
00642     atom.m_str = url.fileName();
00643     entry.append( atom );
00644 
00645     atom.m_uds = KIO::UDS_FILE_TYPE;
00646     atom.m_long = S_IFREG; // a file
00647     entry.append( atom );
00648 
00649     atom.m_uds = KIO::UDS_ACCESS;
00650     atom.m_long = S_IRUSR | S_IRGRP | S_IROTH; // readable by everybody
00651     entry.append( atom );
00652 
00653     statEntry( entry );
00654     finished();
00655     return;
00656   }
00657 
00658   davStatList( url );
00659 }
00660 
00661 void HTTPProtocol::listDir( const KURL& url )
00662 {
00663   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::listDir " << url.url()
00664                 << endl;
00665 
00666   if ( !checkRequestURL( url ) )
00667     return;
00668 
00669   if (!url.protocol().startsWith("webdav")) {
00670     error(ERR_UNSUPPORTED_ACTION, url.prettyURL());
00671     return;
00672   }
00673 
00674   davStatList( url, false );
00675 }
00676 
00677 void HTTPProtocol::davSetRequest( const QCString& requestXML )
00678 {
00679   // insert the document into the POST buffer, kill trailing zero byte
00680   m_bufPOST = requestXML;
00681 
00682   if (m_bufPOST.size())
00683     m_bufPOST.truncate( m_bufPOST.size() - 1 );
00684 }
00685 
00686 void HTTPProtocol::davStatList( const KURL& url, bool stat )
00687 {
00688   UDSEntry entry;
00689   UDSAtom atom;
00690 
00691   // check to make sure this host supports WebDAV
00692   if ( !davHostOk() )
00693     return;
00694 
00695   // Maybe it's a disguised SEARCH...
00696   QString query = metaData("davSearchQuery");
00697   if ( !query.isEmpty() )
00698   {
00699     QCString request = "<?xml version=\"1.0\"?>\r\n";
00700     request.append( "<D:searchrequest xmlns:D=\"DAV:\">\r\n" );
00701     request.append( query.utf8() );
00702     request.append( "</D:searchrequest>\r\n" );
00703 
00704     davSetRequest( request );
00705   } else {
00706     // We are only after certain features...
00707     QCString request;
00708     request = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
00709     "<D:propfind xmlns:D=\"DAV:\">";
00710 
00711     // insert additional XML request from the davRequestResponse metadata
00712     if ( hasMetaData( "davRequestResponse" ) )
00713       request += metaData( "davRequestResponse" ).utf8();
00714     else {
00715       // No special request, ask for default properties
00716       request += "<D:prop>"
00717       "<D:creationdate/>"
00718       "<D:getcontentlength/>"
00719       "<D:displayname/>"
00720       "<D:source/>"
00721       "<D:getcontentlanguage/>"
00722       "<D:getcontenttype/>"
00723       "<D:executable/>"
00724       "<D:getlastmodified/>"
00725       "<D:getetag/>"
00726       "<D:supportedlock/>"
00727       "<D:lockdiscovery/>"
00728       "<D:resourcetype/>"
00729       "</D:prop>";
00730     }
00731     request += "</D:propfind>";
00732 
00733     davSetRequest( request );
00734   }
00735 
00736   // WebDAV Stat or List...
00737   m_request.method = query.isEmpty() ? DAV_PROPFIND : DAV_SEARCH;
00738   m_request.query = QString::null;
00739   m_request.cache = CC_Reload;
00740   m_request.doProxy = m_bUseProxy;
00741   m_request.davData.depth = stat ? 0 : 1;
00742   if (!stat)
00743      m_request.url.adjustPath(+1);
00744 
00745   retrieveContent( true );
00746 
00747   // Has a redirection already been called? If so, we're done.
00748   if (m_bRedirect) {
00749     finished();
00750     return;
00751   }
00752 
00753   QDomDocument multiResponse;
00754   multiResponse.setContent( m_bufWebDavData, true );
00755 
00756   bool hasResponse = false;
00757 
00758   for ( QDomNode n = multiResponse.documentElement().firstChild();
00759         !n.isNull(); n = n.nextSibling())
00760   {
00761     QDomElement thisResponse = n.toElement();
00762     if (thisResponse.isNull())
00763       continue;
00764 
00765     hasResponse = true;
00766 
00767     QDomElement href = thisResponse.namedItem( "href" ).toElement();
00768     if ( !href.isNull() )
00769     {
00770       entry.clear();
00771 
00772       QString urlStr = href.text();
00773       int encoding = remoteEncoding()->encodingMib();
00774       if ((encoding == 106) && (!KStringHandler::isUtf8(KURL::decode_string(urlStr, 4).latin1())))
00775         encoding = 4; // Use latin1 if the file is not actually utf-8
00776 
00777       KURL thisURL ( urlStr, encoding );
00778 
00779       atom.m_uds = KIO::UDS_NAME;
00780 
00781       if ( thisURL.isValid() ) {
00782         // don't list the base dir of a listDir()
00783         if ( !stat && thisURL.path(+1).length() == url.path(+1).length() )
00784           continue;
00785 
00786         atom.m_str = thisURL.fileName();
00787       } else {
00788         // This is a relative URL.
00789         atom.m_str = href.text();
00790       }
00791 
00792       entry.append( atom );
00793 
00794       QDomNodeList propstats = thisResponse.elementsByTagName( "propstat" );
00795 
00796       davParsePropstats( propstats, entry );
00797 
00798       if ( stat )
00799       {
00800         // return an item
00801         statEntry( entry );
00802         finished();
00803         return;
00804       }
00805       else
00806       {
00807         listEntry( entry, false );
00808       }
00809     }
00810     else
00811     {
00812       kdDebug(7113) << "Error: no URL contained in response to PROPFIND on "
00813                     << url.prettyURL() << endl;
00814     }
00815   }
00816 
00817   if ( stat || !hasResponse )
00818   {
00819     error( ERR_DOES_NOT_EXIST, url.prettyURL() );
00820   }
00821   else
00822   {
00823     listEntry( entry, true );
00824     finished();
00825   }
00826 }
00827 
00828 void HTTPProtocol::davGeneric( const KURL& url, KIO::HTTP_METHOD method )
00829 {
00830   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::davGeneric " << url.url()
00831                 << endl;
00832 
00833   if ( !checkRequestURL( url ) )
00834     return;
00835 
00836   // check to make sure this host supports WebDAV
00837   if ( !davHostOk() )
00838     return;
00839 
00840   // WebDAV method
00841   m_request.method = method;
00842   m_request.query = QString::null;
00843   m_request.cache = CC_Reload;
00844   m_request.doProxy = m_bUseProxy;
00845 
00846   retrieveContent( false );
00847 }
00848 
00849 int HTTPProtocol::codeFromResponse( const QString& response )
00850 {
00851   int firstSpace = response.find( ' ' );
00852   int secondSpace = response.find( ' ', firstSpace + 1 );
00853   return response.mid( firstSpace + 1, secondSpace - firstSpace - 1 ).toInt();
00854 }
00855 
00856 void HTTPProtocol::davParsePropstats( const QDomNodeList& propstats, UDSEntry& entry )
00857 {
00858   QString mimeType;
00859   UDSAtom atom;
00860   bool foundExecutable = false;
00861   bool isDirectory = false;
00862   uint lockCount = 0;
00863   uint supportedLockCount = 0;
00864 
00865   for ( uint i = 0; i < propstats.count(); i++)
00866   {
00867     QDomElement propstat = propstats.item(i).toElement();
00868 
00869     QDomElement status = propstat.namedItem( "status" ).toElement();
00870     if ( status.isNull() )
00871     {
00872       // error, no status code in this propstat
00873       kdDebug(7113) << "Error, no status code in this propstat" << endl;
00874       return;
00875     }
00876 
00877     int code = codeFromResponse( status.text() );
00878 
00879     if ( code != 200 )
00880     {
00881       kdDebug(7113) << "Warning: status code " << code << " (this may mean that some properties are unavailable" << endl;
00882       continue;
00883     }
00884 
00885     QDomElement prop = propstat.namedItem( "prop" ).toElement();
00886     if ( prop.isNull() )
00887     {
00888       kdDebug(7113) << "Error: no prop segment in this propstat." << endl;
00889       return;
00890     }
00891 
00892     if ( hasMetaData( "davRequestResponse" ) )
00893     {
00894       atom.m_uds = KIO::UDS_XML_PROPERTIES;
00895       QDomDocument doc;
00896       doc.appendChild(prop);
00897       atom.m_str = doc.toString();
00898       entry.append( atom );
00899     }
00900 
00901     for ( QDomNode n = prop.firstChild(); !n.isNull(); n = n.nextSibling() )
00902     {
00903       QDomElement property = n.toElement();
00904       if (property.isNull())
00905         continue;
00906 
00907       if ( property.namespaceURI() != "DAV:" )
00908       {
00909         // break out - we're only interested in properties from the DAV namespace
00910         continue;
00911       }
00912 
00913       if ( property.tagName() == "creationdate" )
00914       {
00915         // Resource creation date. Should be is ISO 8601 format.
00916         atom.m_uds = KIO::UDS_CREATION_TIME;
00917         atom.m_long = parseDateTime( property.text(), property.attribute("dt") );
00918         entry.append( atom );
00919       }
00920       else if ( property.tagName() == "getcontentlength" )
00921       {
00922         // Content length (file size)
00923         atom.m_uds = KIO::UDS_SIZE;
00924         atom.m_long = property.text().toULong();
00925         entry.append( atom );
00926       }
00927       else if ( property.tagName() == "displayname" )
00928       {
00929         // Name suitable for presentation to the user
00930         setMetaData( "davDisplayName", property.text() );
00931       }
00932       else if ( property.tagName() == "source" )
00933       {
00934         // Source template location
00935         QDomElement source = property.namedItem( "link" ).toElement()
00936                                       .namedItem( "dst" ).toElement();
00937         if ( !source.isNull() )
00938           setMetaData( "davSource", source.text() );
00939       }
00940       else if ( property.tagName() == "getcontentlanguage" )
00941       {
00942         // equiv. to Content-Language header on a GET
00943         setMetaData( "davContentLanguage", property.text() );
00944       }
00945       else if ( property.tagName() == "getcontenttype" )
00946       {
00947         // Content type (mime type)
00948         // This may require adjustments for other server-side webdav implementations
00949         // (tested with Apache + mod_dav 1.0.3)
00950         if ( property.text() == "httpd/unix-directory" )
00951         {
00952           isDirectory = true;
00953         }
00954         else
00955         {
00956       mimeType = property.text();
00957         }
00958       }
00959       else if ( property.tagName() == "executable" )
00960       {
00961         // File executable status
00962         if ( property.text() == "T" )
00963           foundExecutable = true;
00964 
00965       }
00966       else if ( property.tagName() == "getlastmodified" )
00967       {
00968         // Last modification date
00969         atom.m_uds = KIO::UDS_MODIFICATION_TIME;
00970         atom.m_long = parseDateTime( property.text(), property.attribute("dt") );
00971         entry.append( atom );
00972 
00973       }
00974       else if ( property.tagName() == "getetag" )
00975       {
00976         // Entity tag
00977         setMetaData( "davEntityTag", property.text() );
00978       }
00979       else if ( property.tagName() == "supportedlock" )
00980       {
00981         // Supported locking specifications
00982         for ( QDomNode n2 = property.firstChild(); !n2.isNull(); n2 = n2.nextSibling() )
00983         {
00984           QDomElement lockEntry = n2.toElement();
00985           if ( lockEntry.tagName() == "lockentry" )
00986           {
00987             QDomElement lockScope = lockEntry.namedItem( "lockscope" ).toElement();
00988             QDomElement lockType = lockEntry.namedItem( "locktype" ).toElement();
00989             if ( !lockScope.isNull() && !lockType.isNull() )
00990             {
00991               // Lock type was properly specified
00992               supportedLockCount++;
00993               QString scope = lockScope.firstChild().toElement().tagName();
00994               QString type = lockType.firstChild().toElement().tagName();
00995 
00996               setMetaData( QString("davSupportedLockScope%1").arg(supportedLockCount), scope );
00997               setMetaData( QString("davSupportedLockType%1").arg(supportedLockCount), type );
00998             }
00999           }
01000         }
01001       }
01002       else if ( property.tagName() == "lockdiscovery" )
01003       {
01004         // Lists the available locks
01005         davParseActiveLocks( property.elementsByTagName( "activelock" ), lockCount );
01006       }
01007       else if ( property.tagName() == "resourcetype" )
01008       {
01009         // Resource type. "Specifies the nature of the resource."
01010         if ( !property.namedItem( "collection" ).toElement().isNull() )
01011         {
01012           // This is a collection (directory)
01013           isDirectory = true;
01014         }
01015       }
01016       else
01017       {
01018         kdDebug(7113) << "Found unknown webdav property: " << property.tagName() << endl;
01019       }
01020     }
01021   }
01022 
01023   setMetaData( "davLockCount", QString("%1").arg(lockCount) );
01024   setMetaData( "davSupportedLockCount", QString("%1").arg(supportedLockCount) );
01025 
01026   atom.m_uds = KIO::UDS_FILE_TYPE;
01027   atom.m_long = isDirectory ? S_IFDIR : S_IFREG;
01028   entry.append( atom );
01029 
01030   if ( foundExecutable || isDirectory )
01031   {
01032     // File was executable, or is a directory.
01033     atom.m_uds = KIO::UDS_ACCESS;
01034     atom.m_long = 0700;
01035     entry.append(atom);
01036   }
01037   else
01038   {
01039     atom.m_uds = KIO::UDS_ACCESS;
01040     atom.m_long = 0600;
01041     entry.append(atom);
01042   }
01043 
01044   if ( !isDirectory && !mimeType.isEmpty() )
01045   {
01046     atom.m_uds = KIO::UDS_MIME_TYPE;
01047     atom.m_str = mimeType;
01048     entry.append( atom );
01049   }
01050 }
01051 
01052 void HTTPProtocol::davParseActiveLocks( const QDomNodeList& activeLocks,
01053                                         uint& lockCount )
01054 {
01055   for ( uint i = 0; i < activeLocks.count(); i++ )
01056   {
01057     QDomElement activeLock = activeLocks.item(i).toElement();
01058 
01059     lockCount++;
01060     // required
01061     QDomElement lockScope = activeLock.namedItem( "lockscope" ).toElement();
01062     QDomElement lockType = activeLock.namedItem( "locktype" ).toElement();
01063     QDomElement lockDepth = activeLock.namedItem( "depth" ).toElement();
01064     // optional
01065     QDomElement lockOwner = activeLock.namedItem( "owner" ).toElement();
01066     QDomElement lockTimeout = activeLock.namedItem( "timeout" ).toElement();
01067     QDomElement lockToken = activeLock.namedItem( "locktoken" ).toElement();
01068 
01069     if ( !lockScope.isNull() && !lockType.isNull() && !lockDepth.isNull() )
01070     {
01071       // lock was properly specified
01072       lockCount++;
01073       QString scope = lockScope.firstChild().toElement().tagName();
01074       QString type = lockType.firstChild().toElement().tagName();
01075       QString depth = lockDepth.text();
01076 
01077       setMetaData( QString("davLockScope%1").arg( lockCount ), scope );
01078       setMetaData( QString("davLockType%1").arg( lockCount ), type );
01079       setMetaData( QString("davLockDepth%1").arg( lockCount ), depth );
01080 
01081       if ( !lockOwner.isNull() )
01082         setMetaData( QString("davLockOwner%1").arg( lockCount ), lockOwner.text() );
01083 
01084       if ( !lockTimeout.isNull() )
01085         setMetaData( QString("davLockTimeout%1").arg( lockCount ), lockTimeout.text() );
01086 
01087       if ( !lockToken.isNull() )
01088       {
01089         QDomElement tokenVal = lockScope.namedItem( "href" ).toElement();
01090         if ( !tokenVal.isNull() )
01091           setMetaData( QString("davLockToken%1").arg( lockCount ), tokenVal.text() );
01092       }
01093     }
01094   }
01095 }
01096 
01097 long HTTPProtocol::parseDateTime( const QString& input, const QString& type )
01098 {
01099   if ( type == "dateTime.tz" )
01100   {
01101     return KRFCDate::parseDateISO8601( input );
01102   }
01103   else if ( type == "dateTime.rfc1123" )
01104   {
01105     return KRFCDate::parseDate( input );
01106   }
01107 
01108   // format not advertised... try to parse anyway
01109   time_t time = KRFCDate::parseDate( input );
01110   if ( time != 0 )
01111     return time;
01112 
01113   return KRFCDate::parseDateISO8601( input );
01114 }
01115 
01116 QString HTTPProtocol::davProcessLocks()
01117 {
01118   if ( hasMetaData( "davLockCount" ) )
01119   {
01120     QString response("If:");
01121     int numLocks;
01122     numLocks = metaData( "davLockCount" ).toInt();
01123     bool bracketsOpen = false;
01124     for ( int i = 0; i < numLocks; i++ )
01125     {
01126       if ( hasMetaData( QString("davLockToken%1").arg(i) ) )
01127       {
01128         if ( hasMetaData( QString("davLockURL%1").arg(i) ) )
01129         {
01130           if ( bracketsOpen )
01131           {
01132             response += ")";
01133             bracketsOpen = false;
01134           }
01135           response += " <" + metaData( QString("davLockURL%1").arg(i) ) + ">";
01136         }
01137 
01138         if ( !bracketsOpen )
01139         {
01140           response += " (";
01141           bracketsOpen = true;
01142         }
01143         else
01144         {
01145           response += " ";
01146         }
01147 
01148         if ( hasMetaData( QString("davLockNot%1").arg(i) ) )
01149           response += "Not ";
01150 
01151         response += "<" + metaData( QString("davLockToken%1").arg(i) ) + ">";
01152       }
01153     }
01154 
01155     if ( bracketsOpen )
01156       response += ")";
01157 
01158     response += "\r\n";
01159     return response;
01160   }
01161 
01162   return QString::null;
01163 }
01164 
01165 bool HTTPProtocol::davHostOk()
01166 {
01167   // FIXME needs to be reworked. Switched off for now.
01168   return true;
01169 
01170   // cached?
01171   if ( m_davHostOk )
01172   {
01173     kdDebug(7113) << "(" << m_pid << ") " << k_funcinfo << " true" << endl;
01174     return true;
01175   }
01176   else if ( m_davHostUnsupported )
01177   {
01178     kdDebug(7113) << "(" << m_pid << ") " << k_funcinfo << " false" << endl;
01179     davError( -2 );
01180     return false;
01181   }
01182 
01183   m_request.method = HTTP_OPTIONS;
01184 
01185   // query the server's capabilities generally, not for a specific URL
01186   m_request.path = "*";
01187   m_request.query = QString::null;
01188   m_request.cache = CC_Reload;
01189   m_request.doProxy = m_bUseProxy;
01190 
01191   // clear davVersions variable, which holds the response to the DAV: header
01192   m_davCapabilities.clear();
01193 
01194   retrieveHeader(false);
01195 
01196   if (m_davCapabilities.count())
01197   {
01198     for (uint i = 0; i < m_davCapabilities.count(); i++)
01199     {
01200       bool ok;
01201       uint verNo = m_davCapabilities[i].toUInt(&ok);
01202       if (ok && verNo > 0 && verNo < 3)
01203       {
01204         m_davHostOk = true;
01205         kdDebug(7113) << "Server supports DAV version " << verNo << "." << endl;
01206       }
01207     }
01208 
01209     if ( m_davHostOk )
01210       return true;
01211   }
01212 
01213   m_davHostUnsupported = true;
01214   davError( -2 );
01215   return false;
01216 }
01217 
01218 // This function is for closing retrieveHeader( false ); requests
01219 // Required because there may or may not be further info expected
01220 void HTTPProtocol::davFinished()
01221 {
01222   // TODO: Check with the DAV extension developers
01223   httpClose(m_bKeepAlive);
01224   finished();
01225 }
01226 
01227 void HTTPProtocol::mkdir( const KURL& url, int )
01228 {
01229   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::mkdir " << url.url()
01230                 << endl;
01231 
01232   if ( !checkRequestURL( url ) )
01233     return;
01234 
01235   m_request.method = DAV_MKCOL;
01236   m_request.path = url.path();
01237   m_request.query = QString::null;
01238   m_request.cache = CC_Reload;
01239   m_request.doProxy = m_bUseProxy;
01240 
01241   retrieveHeader( false );
01242 
01243   if ( m_responseCode == 201 )
01244     davFinished();
01245   else
01246     davError();
01247 }
01248 
01249 void HTTPProtocol::get( const KURL& url )
01250 {
01251   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::get " << url.url()
01252                 << endl;
01253 
01254   if ( !checkRequestURL( url ) )
01255     return;
01256 
01257   m_request.method = HTTP_GET;
01258   m_request.path = url.path();
01259   m_request.query = url.query();
01260 
01261   QString tmp = metaData("cache");
01262   if (!tmp.isEmpty())
01263     m_request.cache = parseCacheControl(tmp);
01264   else
01265     m_request.cache = DEFAULT_CACHE_CONTROL;
01266 
01267   m_request.passwd = url.pass();
01268   m_request.user = url.user();
01269   m_request.doProxy = m_bUseProxy;
01270 
01271   retrieveContent();
01272 }
01273 
01274 void HTTPProtocol::put( const KURL &url, int, bool overwrite, bool)
01275 {
01276   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::put " << url.prettyURL()
01277                 << endl;
01278 
01279   if ( !checkRequestURL( url ) )
01280     return;
01281 
01282   // Webdav hosts are capable of observing overwrite == false
01283   if (!overwrite && m_protocol.left(6) == "webdav") {
01284     // check to make sure this host supports WebDAV
01285     if ( !davHostOk() )
01286       return;
01287 
01288     QCString request;
01289     request = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
01290     "<D:propfind xmlns:D=\"DAV:\"><D:prop>"
01291       "<D:creationdate/>"
01292       "<D:getcontentlength/>"
01293       "<D:displayname/>"
01294       "<D:resourcetype/>"
01295       "</D:prop></D:propfind>";
01296 
01297     davSetRequest( request );
01298 
01299     // WebDAV Stat or List...
01300     m_request.method = DAV_PROPFIND;
01301     m_request.query = QString::null;
01302     m_request.cache = CC_Reload;
01303     m_request.doProxy = m_bUseProxy;
01304     m_request.davData.depth = 0;
01305 
01306     retrieveContent(true);
01307 
01308     if (m_responseCode == 207) {
01309       error(ERR_FILE_ALREADY_EXIST, QString::null);
01310       return;
01311     }
01312 
01313     m_bError = false;
01314   }
01315 
01316   m_request.method = HTTP_PUT;
01317   m_request.path = url.path();
01318   m_request.query = QString::null;
01319   m_request.cache = CC_Reload;
01320   m_request.doProxy = m_bUseProxy;
01321 
01322   retrieveHeader( false );
01323 
01324   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::put error = " << m_bError << endl;
01325   if (m_bError)
01326     return;
01327 
01328   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::put responseCode = " << m_responseCode << endl;
01329 
01330   httpClose(false); // Always close connection.
01331 
01332   if ( (m_responseCode >= 200) && (m_responseCode < 300) )
01333     finished();
01334   else
01335     httpError();
01336 }
01337 
01338 void HTTPProtocol::copy( const KURL& src, const KURL& dest, int, bool overwrite )
01339 {
01340   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::copy " << src.prettyURL()
01341                 << " -> " << dest.prettyURL() << endl;
01342 
01343   if ( !checkRequestURL( dest ) || !checkRequestURL( src ) )
01344     return;
01345 
01346   // destination has to be "http(s)://..."
01347   KURL newDest = dest;
01348   if (newDest.protocol() == "webdavs")
01349     newDest.setProtocol("https");
01350   else
01351     newDest.setProtocol("http");
01352 
01353   m_request.method = DAV_COPY;
01354   m_request.path = src.path();
01355   m_request.davData.desturl = newDest.url();
01356   m_request.davData.overwrite = overwrite;
01357   m_request.query = QString::null;
01358   m_request.cache = CC_Reload;
01359   m_request.doProxy = m_bUseProxy;
01360 
01361   retrieveHeader( false );
01362 
01363   // The server returns a HTTP/1.1 201 Created or 204 No Content on successful completion
01364   if ( m_responseCode == 201 || m_responseCode == 204 )
01365     davFinished();
01366   else
01367     davError();
01368 }
01369 
01370 void HTTPProtocol::rename( const KURL& src, const KURL& dest, bool overwrite )
01371 {
01372   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::rename " << src.prettyURL()
01373                 << " -> " << dest.prettyURL() << endl;
01374 
01375   if ( !checkRequestURL( dest ) || !checkRequestURL( src ) )
01376     return;
01377 
01378   // destination has to be "http://..."
01379   KURL newDest = dest;
01380   if (newDest.protocol() == "webdavs")
01381     newDest.setProtocol("https");
01382   else
01383     newDest.setProtocol("http");
01384 
01385   m_request.method = DAV_MOVE;
01386   m_request.path = src.path();
01387   m_request.davData.desturl = newDest.url();
01388   m_request.davData.overwrite = overwrite;
01389   m_request.query = QString::null;
01390   m_request.cache = CC_Reload;
01391   m_request.doProxy = m_bUseProxy;
01392 
01393   retrieveHeader( false );
01394 
01395   if ( m_responseCode == 301 )
01396   {
01397     // Work around strict Apache-2 WebDAV implementation which refuses to cooperate
01398     // with webdav://host/directory, instead requiring webdav://host/directory/
01399     // (strangely enough it accepts Destination: without a trailing slash)
01400 
01401     if (m_redirectLocation.protocol() == "https")
01402       m_redirectLocation.setProtocol("webdavs");
01403     else
01404       m_redirectLocation.setProtocol("webdav");
01405 
01406     if ( !checkRequestURL( m_redirectLocation ) )
01407       return;
01408 
01409     m_request.method = DAV_MOVE;
01410     m_request.path = m_redirectLocation.path();
01411     m_request.davData.desturl = newDest.url();
01412     m_request.davData.overwrite = overwrite;
01413     m_request.query = QString::null;
01414     m_request.cache = CC_Reload;
01415     m_request.doProxy = m_bUseProxy;
01416 
01417     retrieveHeader( false );
01418   }
01419 
01420   if ( m_responseCode == 201 )
01421     davFinished();
01422   else
01423     davError();
01424 }
01425 
01426 void HTTPProtocol::del( const KURL& url, bool )
01427 {
01428   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::del " << url.prettyURL()
01429                 << endl;
01430 
01431   if ( !checkRequestURL( url ) )
01432     return;
01433 
01434   m_request.method = HTTP_DELETE;
01435   m_request.path = url.path();
01436   m_request.query = QString::null;
01437   m_request.cache = CC_Reload;
01438   m_request.doProxy = m_bUseProxy;
01439 
01440   retrieveHeader( false );
01441 
01442   // The server returns a HTTP/1.1 200 Ok or HTTP/1.1 204 No Content
01443   // on successful completion
01444   if ( m_responseCode == 200 || m_responseCode == 204 )
01445     davFinished();
01446   else
01447     davError();
01448 }
01449 
01450 void HTTPProtocol::post( const KURL& url )
01451 {
01452   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::post "
01453                 << url.prettyURL() << endl;
01454 
01455   if ( !checkRequestURL( url ) )
01456     return;
01457 
01458   m_request.method = HTTP_POST;
01459   m_request.path = url.path();
01460   m_request.query = url.query();
01461   m_request.cache = CC_Reload;
01462   m_request.doProxy = m_bUseProxy;
01463 
01464   retrieveContent();
01465 }
01466 
01467 void HTTPProtocol::davLock( const KURL& url, const QString& scope,
01468                             const QString& type, const QString& owner )
01469 {
01470   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::davLock "
01471                 << url.prettyURL() << endl;
01472 
01473   if ( !checkRequestURL( url ) )
01474     return;
01475 
01476   m_request.method = DAV_LOCK;
01477   m_request.path = url.path();
01478   m_request.query = QString::null;
01479   m_request.cache = CC_Reload;
01480   m_request.doProxy = m_bUseProxy;
01481 
01482   /* Create appropriate lock XML request. */
01483   QDomDocument lockReq;
01484 
01485   QDomElement lockInfo = lockReq.createElementNS( "DAV:", "lockinfo" );
01486   lockReq.appendChild( lockInfo );
01487 
01488   QDomElement lockScope = lockReq.createElement( "lockscope" );
01489   lockInfo.appendChild( lockScope );
01490 
01491   lockScope.appendChild( lockReq.createElement( scope ) );
01492 
01493   QDomElement lockType = lockReq.createElement( "locktype" );
01494   lockInfo.appendChild( lockType );
01495 
01496   lockType.appendChild( lockReq.createElement( type ) );
01497 
01498   if ( !owner.isNull() ) {
01499     QDomElement ownerElement = lockReq.createElement( "owner" );
01500     lockReq.appendChild( ownerElement );
01501 
01502     QDomElement ownerHref = lockReq.createElement( "href" );
01503     ownerElement.appendChild( ownerHref );
01504 
01505     ownerHref.appendChild( lockReq.createTextNode( owner ) );
01506   }
01507 
01508   // insert the document into the POST buffer
01509   m_bufPOST = lockReq.toCString();
01510 
01511   retrieveContent( true );
01512 
01513   if ( m_responseCode == 200 ) {
01514     // success
01515     QDomDocument multiResponse;
01516     multiResponse.setContent( m_bufWebDavData, true );
01517 
01518     QDomElement prop = multiResponse.documentElement().namedItem( "prop" ).toElement();
01519 
01520     QDomElement lockdiscovery = prop.namedItem( "lockdiscovery" ).toElement();
01521 
01522     uint lockCount = 0;
01523     davParseActiveLocks( lockdiscovery.elementsByTagName( "activelock" ), lockCount );
01524 
01525     setMetaData( "davLockCount", QString("%1").arg( lockCount ) );
01526 
01527     finished();
01528 
01529   } else
01530     davError();
01531 }
01532 
01533 void HTTPProtocol::davUnlock( const KURL& url )
01534 {
01535   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::davUnlock "
01536                 << url.prettyURL() << endl;
01537 
01538   if ( !checkRequestURL( url ) )
01539     return;
01540 
01541   m_request.method = DAV_UNLOCK;
01542   m_request.path = url.path();
01543   m_request.query = QString::null;
01544   m_request.cache = CC_Reload;
01545   m_request.doProxy = m_bUseProxy;
01546 
01547   retrieveContent( true );
01548 
01549   if ( m_responseCode == 200 )
01550     finished();
01551   else
01552     davError();
01553 }
01554 
01555 QString HTTPProtocol::davError( int code /* = -1 */, QString url )
01556 {
01557   bool callError = false;
01558   if ( code == -1 ) {
01559     code = m_responseCode;
01560     callError = true;
01561   }
01562   if ( code == -2 ) {
01563     callError = true;
01564   }
01565 
01566   if ( !url.isNull() )
01567     url = m_request.url.url();
01568 
01569   QString action, errorString;
01570   KIO::Error kError;
01571 
01572   // for 412 Precondition Failed
01573   QString ow = i18n( "Otherwise, the request would have succeeded." );
01574 
01575   switch ( m_request.method ) {
01576     case DAV_PROPFIND:
01577       action = i18n( "retrieve property values" );
01578       break;
01579     case DAV_PROPPATCH:
01580       action = i18n( "set property values" );
01581       break;
01582     case DAV_MKCOL:
01583       action = i18n( "create the requested folder" );
01584       break;
01585     case DAV_COPY:
01586       action = i18n( "copy the specified file or folder" );
01587       break;
01588     case DAV_MOVE:
01589       action = i18n( "move the specified file or folder" );
01590       break;
01591     case DAV_SEARCH:
01592       action = i18n( "search in the specified folder" );
01593       break;
01594     case DAV_LOCK:
01595       action = i18n( "lock the specified file or folder" );
01596       break;
01597     case DAV_UNLOCK:
01598       action = i18n( "unlock the specified file or folder" );
01599       break;
01600     case HTTP_DELETE:
01601       action = i18n( "delete the specified file or folder" );
01602       break;
01603     case HTTP_OPTIONS:
01604       action = i18n( "query the server's capabilities" );
01605       break;
01606     case HTTP_GET:
01607       action = i18n( "retrieve the contents of the specified file or folder" );
01608       break;
01609     case HTTP_PUT:
01610     case HTTP_POST:
01611     case HTTP_HEAD:
01612     default:
01613       // this should not happen, this function is for webdav errors only
01614       Q_ASSERT(0);
01615   }
01616 
01617   // default error message if the following code fails
01618   kError = ERR_INTERNAL;
01619   errorString = i18n("An unexpected error (%1) occurred while attempting to %2.")
01620                       .arg( code ).arg( action );
01621 
01622   switch ( code )
01623   {
01624     case -2:
01625       // internal error: OPTIONS request did not specify DAV compliance
01626       kError = ERR_UNSUPPORTED_PROTOCOL;
01627       errorString = i18n("The server does not support the WebDAV protocol.");
01628       break;
01629     case 207:
01630       // 207 Multi-status
01631     {
01632       // our error info is in the returned XML document.
01633       // retrieve the XML document
01634 
01635       // there was an error retrieving the XML document.
01636       // ironic, eh?
01637       if ( !readBody( true ) && m_bError )
01638         return QString::null;
01639 
01640       QStringList errors;
01641       QDomDocument multiResponse;
01642 
01643       multiResponse.setContent( m_bufWebDavData, true );
01644 
01645       QDomElement multistatus = multiResponse.documentElement().namedItem( "multistatus" ).toElement();
01646 
01647       QDomNodeList responses = multistatus.elementsByTagName( "response" );
01648 
01649       for (uint i = 0; i < responses.count(); i++)
01650       {
01651         int errCode;
01652         QString errUrl;
01653 
01654         QDomElement response = responses.item(i).toElement();
01655         QDomElement code = response.namedItem( "status" ).toElement();
01656 
01657         if ( !code.isNull() )
01658         {
01659           errCode = codeFromResponse( code.text() );
01660           QDomElement href = response.namedItem( "href" ).toElement();
01661           if ( !href.isNull() )
01662             errUrl = href.text();
01663           errors << davError( errCode, errUrl );
01664         }
01665       }
01666 
01667       //kError = ERR_SLAVE_DEFINED;
01668       errorString = i18n("An error occurred while attempting to %1, %2. A "
01669                          "summary of the reasons is below.<ul>").arg( action ).arg( url );
01670 
01671       for ( QStringList::Iterator it = errors.begin(); it != errors.end(); ++it )
01672         errorString += "<li>" + *it + "</li>";
01673 
01674       errorString += "</ul>";
01675     }
01676     case 403:
01677     case 500: // hack: Apache mod_dav returns this instead of 403 (!)
01678       // 403 Forbidden
01679       kError = ERR_ACCESS_DENIED;
01680       errorString = i18n("Access was denied while attempting to %1.").arg( action );
01681       break;
01682     case 405:
01683       // 405 Method Not Allowed
01684       if ( m_request.method == DAV_MKCOL )
01685       {
01686         kError = ERR_DIR_ALREADY_EXIST;
01687         errorString = i18n("The specified folder already exists.");
01688       }
01689       break;
01690     case 409:
01691       // 409 Conflict
01692       kError = ERR_ACCESS_DENIED;
01693       errorString = i18n("A resource cannot be created at the destination "
01694                   "until one or more intermediate collections (folders) "
01695                   "have been created.");
01696       break;
01697     case 412:
01698       // 412 Precondition failed
01699       if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE )
01700       {
01701         kError = ERR_ACCESS_DENIED;
01702         errorString = i18n("The server was unable to maintain the liveness of "
01703                            "the properties listed in the propertybehavior XML "
01704                            "element or you attempted to overwrite a file while "
01705                            "requesting that files are not overwritten. %1")
01706                            .arg( ow );
01707 
01708       }
01709       else if ( m_request.method == DAV_LOCK )
01710       {
01711         kError = ERR_ACCESS_DENIED;
01712         errorString = i18n("The requested lock could not be granted. %1").arg( ow );
01713       }
01714       break;
01715     case 415:
01716       // 415 Unsupported Media Type
01717       kError = ERR_ACCESS_DENIED;
01718       errorString = i18n("The server does not support the request type of the body.");
01719       break;
01720     case 423:
01721       // 423 Locked
01722       kError = ERR_ACCESS_DENIED;
01723       errorString = i18n("Unable to %1 because the resource is locked.").arg( action );
01724       break;
01725     case 425:
01726       // 424 Failed Dependency
01727       errorString = i18n("This action was prevented by another error.");
01728       break;
01729     case 502:
01730       // 502 Bad Gateway
01731       if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE )
01732       {
01733         kError = ERR_WRITE_ACCESS_DENIED;
01734         errorString = i18n("Unable to %1 because the destination server refuses "
01735                            "to accept the file or folder.").arg( action );
01736       }
01737       break;
01738     case 507:
01739       // 507 Insufficient Storage
01740       kError = ERR_DISK_FULL;
01741       errorString = i18n("The destination resource does not have sufficient space "
01742                          "to record the state of the resource after the execution "
01743                          "of this method.");
01744       break;
01745   }
01746 
01747   // if ( kError != ERR_SLAVE_DEFINED )
01748   //errorString += " (" + url + ")";
01749 
01750   if ( callError )
01751     error( ERR_SLAVE_DEFINED, errorString );
01752 
01753   return errorString;
01754 }
01755 
01756 void HTTPProtocol::httpError()
01757 {
01758   QString action, errorString;
01759   KIO::Error kError;
01760 
01761   switch ( m_request.method ) {
01762     case HTTP_PUT:
01763       action = i18n( "upload %1" ).arg(m_request.url.prettyURL());
01764       break;
01765     default:
01766       // this should not happen, this function is for http errors only
01767       Q_ASSERT(0);
01768   }
01769 
01770   // default error message if the following code fails
01771   kError = ERR_INTERNAL;
01772   errorString = i18n("An unexpected error (%1) occurred while attempting to %2.")
01773                       .arg( m_responseCode ).arg( action );
01774 
01775   switch ( m_responseCode )
01776   {
01777     case 403:
01778     case 405:
01779     case 500: // hack: Apache mod_dav returns this instead of 403 (!)
01780       // 403 Forbidden
01781       // 405 Method Not Allowed
01782       kError = ERR_ACCESS_DENIED;
01783       errorString = i18n("Access was denied while attempting to %1.").arg( action );
01784       break;
01785     case 409:
01786       // 409 Conflict
01787       kError = ERR_ACCESS_DENIED;
01788       errorString = i18n("A resource cannot be created at the destination "
01789                   "until one or more intermediate collections (folders) "
01790                   "have been created.");
01791       break;
01792     case 423:
01793       // 423 Locked
01794       kError = ERR_ACCESS_DENIED;
01795       errorString = i18n("Unable to %1 because the resource is locked.").arg( action );
01796       break;
01797     case 502:
01798       // 502 Bad Gateway
01799       kError = ERR_WRITE_ACCESS_DENIED;
01800       errorString = i18n("Unable to %1 because the destination server refuses "
01801                          "to accept the file or folder.").arg( action );
01802       break;
01803     case 507:
01804       // 507 Insufficient Storage
01805       kError = ERR_DISK_FULL;
01806       errorString = i18n("The destination resource does not have sufficient space "
01807                          "to record the state of the resource after the execution "
01808                          "of this method.");
01809       break;
01810   }
01811 
01812   // if ( kError != ERR_SLAVE_DEFINED )
01813   //errorString += " (" + url + ")";
01814 
01815   error( ERR_SLAVE_DEFINED, errorString );
01816 }
01817 
01818 bool HTTPProtocol::isOffline(const KURL &url)
01819 {
01820   const int NetWorkStatusUnknown = 1;
01821   const int NetWorkStatusOnline = 8;
01822   QCString replyType;
01823   QByteArray params;
01824   QByteArray reply;
01825 
01826   QDataStream stream(params, IO_WriteOnly);
01827   stream << url.url();
01828 
01829   if ( dcopClient()->call( "kded", "networkstatus", "status(QString)",
01830                            params, replyType, reply ) && (replyType == "int") )
01831   {
01832      int result;
01833      QDataStream stream2( reply, IO_ReadOnly );
01834      stream2 >> result;
01835      kdDebug(7113) << "(" << m_pid << ") networkstatus status = " << result << endl;
01836      return (result != NetWorkStatusUnknown) && (result != NetWorkStatusOnline);
01837   }
01838   kdDebug(7113) << "(" << m_pid << ") networkstatus <unreachable>" << endl;
01839   return false; // On error, assume we are online
01840 }
01841 
01842 void HTTPProtocol::multiGet(const QByteArray &data)
01843 {
01844   QDataStream stream(data, IO_ReadOnly);
01845   Q_UINT32 n;
01846   stream >> n;
01847 
01848   kdDebug(7113) << "(" << m_pid << ") HTTPProtcool::multiGet n = " << n << endl;
01849 
01850   HTTPRequest saveRequest;
01851   if (m_bBusy)
01852      saveRequest = m_request;
01853 
01854 //  m_requestQueue.clear();
01855   for(unsigned i = 0; i < n; i++)
01856   {
01857      KURL url;
01858      stream >> url >> mIncomingMetaData;
01859 
01860      if ( !checkRequestURL( url ) )
01861         continue;
01862 
01863      kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::multi_get " << url.url() << endl;
01864 
01865      m_request.method = HTTP_GET;
01866      m_request.path = url.path();
01867      m_request.query = url.query();
01868      QString tmp = metaData("cache");
01869      if (!tmp.isEmpty())
01870         m_request.cache = parseCacheControl(tmp);
01871      else
01872         m_request.cache = DEFAULT_CACHE_CONTROL;
01873 
01874      m_request.passwd = url.pass();
01875      m_request.user = url.user();
01876      m_request.doProxy = m_bUseProxy;
01877 
01878      HTTPRequest *newRequest = new HTTPRequest(m_request);
01879      m_requestQueue.append(newRequest);
01880   }
01881 
01882   if (m_bBusy)
01883      m_request = saveRequest;
01884 
01885   if (!m_bBusy)
01886   {
01887      m_bBusy = true;
01888      while(!m_requestQueue.isEmpty())
01889      {
01890         HTTPRequest *request = m_requestQueue.take(0);
01891         m_request = *request;
01892         delete request;
01893         retrieveContent();
01894      }
01895      m_bBusy = false;
01896   }
01897 }
01898 
01899 ssize_t HTTPProtocol::write (const void *_buf, size_t nbytes)
01900 {
01901   int bytes_sent = 0;
01902   const char* buf = static_cast<const char*>(_buf);
01903   while ( nbytes > 0 )
01904   {
01905     int n = TCPSlaveBase::write(buf, nbytes);
01906 
01907     if ( n <= 0 )
01908     {
01909       // remote side closed connection ?
01910       if ( n == 0 )
01911         break;
01912       // a valid exception(s) occurred, let's retry...
01913       if (n < 0 && ((errno == EINTR) || (errno == EAGAIN)))
01914         continue;
01915       // some other error occurred ?
01916       return -1;
01917     }
01918 
01919     nbytes -= n;
01920     buf += n;
01921     bytes_sent += n;
01922   }
01923 
01924   return bytes_sent;
01925 }
01926 
01927 void HTTPProtocol::setRewindMarker()
01928 {
01929   m_rewindCount = 0;
01930 }
01931 
01932 void HTTPProtocol::rewind()
01933 {
01934   m_linePtrUnget = m_rewindBuf,
01935   m_lineCountUnget = m_rewindCount;
01936   m_rewindCount = 0;
01937 }
01938 
01939 
01940 char *HTTPProtocol::gets (char *s, int size)
01941 {
01942   int len=0;
01943   char *buf=s;
01944   char mybuf[2]={0,0};
01945 
01946   while (len < size)
01947   {
01948     read(mybuf, 1);
01949     if (m_bEOF)
01950       break;
01951 
01952     if (m_rewindCount < sizeof(m_rewindBuf))
01953        m_rewindBuf[m_rewindCount++] = *mybuf;
01954 
01955     if (*mybuf == '\r') // Ignore!
01956       continue;
01957 
01958     if ((*mybuf == '\n') || !*mybuf)
01959       break;
01960 
01961     *buf++ = *mybuf;
01962     len++;
01963   }
01964 
01965   *buf=0;
01966   return s;
01967 }
01968 
01969 ssize_t HTTPProtocol::read (void *b, size_t nbytes)
01970 {
01971   ssize_t ret = 0;
01972 
01973   if (m_lineCountUnget > 0)
01974   {
01975     ret = ( nbytes < m_lineCountUnget ? nbytes : m_lineCountUnget );
01976     m_lineCountUnget -= ret;
01977     memcpy(b, m_linePtrUnget, ret);
01978     m_linePtrUnget += ret;
01979 
01980     return ret;
01981   }
01982 
01983   if (m_lineCount > 0)
01984   {
01985     ret = ( nbytes < m_lineCount ? nbytes : m_lineCount );
01986     m_lineCount -= ret;
01987     memcpy(b, m_linePtr, ret);
01988     m_linePtr += ret;
01989     return ret;
01990   }
01991 
01992   if (nbytes == 1)
01993   {
01994     ret = read(m_lineBuf, 1024); // Read into buffer
01995     m_linePtr = m_lineBuf;
01996     if (ret <= 0)
01997     {
01998       m_lineCount = 0;
01999       return ret;
02000     }
02001     m_lineCount = ret;
02002     return read(b, 1); // Read from buffer
02003   }
02004 
02005   do
02006   {
02007     ret = TCPSlaveBase::read( b, nbytes);
02008     if (ret == 0)
02009       m_bEOF = true;
02010 
02011   } while ((ret == -1) && (errno == EAGAIN || errno == EINTR));
02012 
02013   return ret;
02014 }
02015 
02016 void HTTPProtocol::httpCheckConnection()
02017 {
02018   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpCheckConnection: " <<
02019                                    " Socket status: " << m_iSock <<
02020                                       " Keep Alive: " << m_bKeepAlive <<
02021                                            " First: " << m_bFirstRequest << endl;
02022 
02023   if ( !m_bFirstRequest && (m_iSock != -1) )
02024   {
02025      bool closeDown = false;
02026      if ( !isConnectionValid())
02027      {
02028         kdDebug(7113) << "(" << m_pid << ") Connection lost!" << endl;
02029         closeDown = true;
02030      }
02031      else if ( m_request.method != HTTP_GET )
02032      {
02033         closeDown = true;
02034      }
02035      else if ( !m_state.doProxy && !m_request.doProxy )
02036      {
02037         if (m_state.hostname != m_request.hostname ||
02038             m_state.port != m_request.port ||
02039             m_state.user != m_request.user ||
02040             m_state.passwd != m_request.passwd)
02041           closeDown = true;
02042      }
02043      else
02044      {
02045         // Keep the connection to the proxy.
02046         if ( !(m_request.doProxy && m_state.doProxy) )
02047           closeDown = true;
02048      }
02049 
02050      if (closeDown)
02051         httpCloseConnection();
02052   }
02053 
02054   // Let's update our current state
02055   m_state.hostname = m_request.hostname;
02056   m_state.encoded_hostname = m_request.encoded_hostname;
02057   m_state.port = m_request.port;
02058   m_state.user = m_request.user;
02059   m_state.passwd = m_request.passwd;
02060   m_state.doProxy = m_request.doProxy;
02061 }
02062 
02063 bool HTTPProtocol::httpOpenConnection()
02064 {
02065   int errCode;
02066   QString errMsg;
02067 
02068   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpenConnection" << endl;
02069 
02070   setBlockConnection( true );
02071   // kio_http uses its own proxying:
02072   KSocks::self()->disableSocks();
02073 
02074   if ( m_state.doProxy )
02075   {
02076     QString proxy_host = m_proxyURL.host();
02077     int proxy_port = m_proxyURL.port();
02078 
02079     kdDebug(7113) << "(" << m_pid << ") Connecting to proxy server: "
02080                   << proxy_host << ", port: " << proxy_port << endl;
02081 
02082     infoMessage( i18n("Connecting to %1...").arg(m_state.hostname) );
02083 
02084     setConnectTimeout( m_proxyConnTimeout );
02085 
02086     if ( !connectToHost(proxy_host, proxy_port, false) )
02087     {
02088       if (userAborted()) {
02089         error(ERR_NO_CONTENT, "");
02090         return false;
02091       }
02092 
02093       switch ( connectResult() )
02094       {
02095         case IO_LookupError:
02096           errMsg = proxy_host;
02097           errCode = ERR_UNKNOWN_PROXY_HOST;
02098           break;
02099         case IO_TimeOutError:
02100           errMsg = i18n("Proxy %1 at port %2").arg(proxy_host).arg(proxy_port);
02101           errCode = ERR_SERVER_TIMEOUT;
02102           break;
02103         default:
02104           errMsg = i18n("Proxy %1 at port %2").arg(proxy_host).arg(proxy_port);
02105           errCode = ERR_COULD_NOT_CONNECT;
02106       }
02107       error( errCode, errMsg );
02108       return false;
02109     }
02110   }
02111   else
02112   {
02113     // Apparently we don't want a proxy.  let's just connect directly
02114     setConnectTimeout(m_remoteConnTimeout);
02115 
02116     if ( !connectToHost(m_state.hostname, m_state.port, false ) )
02117     {
02118       if (userAborted()) {
02119         error(ERR_NO_CONTENT, "");
02120         return false;
02121       }
02122 
02123       switch ( connectResult() )
02124       {
02125         case IO_LookupError:
02126           errMsg = m_state.hostname;
02127           errCode = ERR_UNKNOWN_HOST;
02128           break;
02129         case IO_TimeOutError:
02130           errMsg = i18n("Connection was to %1 at port %2").arg(m_state.hostname).arg(m_state.port);
02131           errCode = ERR_SERVER_TIMEOUT;
02132           break;
02133         default:
02134           errCode = ERR_COULD_NOT_CONNECT;
02135           if (m_state.port != m_iDefaultPort)
02136             errMsg = i18n("%1 (port %2)").arg(m_state.hostname).arg(m_state.port);
02137           else
02138             errMsg = m_state.hostname;
02139       }
02140       error( errCode, errMsg );
02141       return false;
02142     }
02143   }
02144 
02145   // Set our special socket option!!
02146   int on = 1;
02147   (void) setsockopt( m_iSock, IPPROTO_TCP, TCP_NODELAY, (char*)&on, sizeof(on) );
02148 
02149   m_bFirstRequest = true;
02150 
02151   connected();
02152   return true;
02153 }
02154 
02155 
02178 bool HTTPProtocol::httpOpen()
02179 {
02180   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpen" << endl;
02181 
02182   // Cannot have an https request without the m_bIsSSL being set!  This can
02183   // only happen if TCPSlaveBase::InitializeSSL() function failed in which it
02184   // means the current installation does not support SSL...
02185   if ( (m_protocol == "https" || m_protocol == "webdavs") && !m_bIsSSL )
02186   {
02187     error( ERR_UNSUPPORTED_PROTOCOL, m_protocol );
02188     return false;
02189   }
02190 
02191   m_request.fcache = 0;
02192   m_request.bCachedRead = false;
02193   m_request.bCachedWrite = false;
02194   m_request.bMustRevalidate = false;
02195   m_request.expireDate = 0;
02196   m_request.creationDate = 0;
02197 
02198   if (m_request.bUseCache)
02199   {
02200      m_request.fcache = checkCacheEntry( );
02201 
02202      bool bCacheOnly = (m_request.cache == KIO::CC_CacheOnly);
02203      bool bOffline = isOffline(m_request.doProxy ? m_proxyURL : m_request.url);
02204      if (bOffline && (m_request.cache != KIO::CC_Reload))
02205         m_request.cache = KIO::CC_CacheOnly;
02206 
02207      if (m_request.cache == CC_Reload && m_request.fcache)
02208      {
02209         if (m_request.fcache)
02210           fclose(m_request.fcache);
02211         m_request.fcache = 0;
02212      }
02213      if ((m_request.cache == KIO::CC_CacheOnly) || (m_request.cache == KIO::CC_Cache))
02214         m_request.bMustRevalidate = false;
02215 
02216      m_request.bCachedWrite = true;
02217 
02218      if (m_request.fcache && !m_request.bMustRevalidate)
02219      {
02220         // Cache entry is OK.
02221         m_request.bCachedRead = true; // Cache hit.
02222         return true;
02223      }
02224      else if (!m_request.fcache)
02225      {
02226         m_request.bMustRevalidate = false; // Cache miss
02227      }
02228      else
02229      {
02230         // Conditional cache hit. (Validate)
02231      }
02232 
02233      if (bCacheOnly)
02234      {
02235         error( ERR_DOES_NOT_EXIST, m_request.url.url() );
02236         return false;
02237      }
02238      if (bOffline)
02239      {
02240         error( ERR_COULD_NOT_CONNECT, m_request.url.url() );
02241         return false;
02242      }
02243   }
02244 
02245   QString header;
02246   QString davHeader;
02247 
02248   bool moreData = false;
02249   bool davData = false;
02250 
02251   // Clear out per-connection settings...
02252   resetConnectionSettings ();
02253 
02254   // Check the validity of the current connection, if one exists.
02255   httpCheckConnection();
02256 
02257   if ( !m_bIsTunneled && m_bNeedTunnel )
02258   {
02259     setEnableSSLTunnel( true );
02260     // We send a HTTP 1.0 header since some proxies refuse HTTP 1.1 and we don't
02261     // need any HTTP 1.1 capabilities for CONNECT - Waba
02262     header = QString("CONNECT %1:%2 HTTP/1.0"
02263                      "\r\n").arg( m_request.encoded_hostname).arg(m_request.port);
02264 
02265     // Identify who you are to the proxy server!
02266     if (!m_request.userAgent.isEmpty())
02267         header += "User-Agent: " + m_request.userAgent + "\r\n";
02268 
02269     /* Add hostname information */
02270     header += "Host: " + m_state.encoded_hostname;
02271 
02272     if (m_state.port != m_iDefaultPort)
02273       header += QString(":%1").arg(m_state.port);
02274     header += "\r\n";
02275 
02276     header += proxyAuthenticationHeader();
02277   }
02278   else
02279   {
02280     // Determine if this is a POST or GET method
02281     switch (m_request.method)
02282     {
02283     case HTTP_GET:
02284         header = "GET ";
02285         break;
02286     case HTTP_PUT:
02287         header = "PUT ";
02288         moreData = true;
02289         m_request.bCachedWrite = false; // Do not put any result in the cache
02290         break;
02291     case HTTP_POST:
02292         header = "POST ";
02293         moreData = true;
02294         m_request.bCachedWrite = false; // Do not put any result in the cache
02295         break;
02296     case HTTP_HEAD:
02297         header = "HEAD ";
02298         break;
02299     case HTTP_DELETE:
02300         header = "DELETE ";
02301         m_request.bCachedWrite = false; // Do not put any result in the cache
02302         break;
02303     case HTTP_OPTIONS:
02304         header = "OPTIONS ";
02305         m_request.bCachedWrite = false; // Do not put any result in the cache
02306         break;
02307     case DAV_PROPFIND:
02308         header = "PROPFIND ";
02309         davData = true;
02310         davHeader = "Depth: ";
02311         if ( hasMetaData( "davDepth" ) )
02312         {
02313           kdDebug(7113) << "Reading DAV depth from metadata: " << metaData( "davDepth" ) << endl;
02314           davHeader += metaData( "davDepth" );
02315         }
02316         else
02317         {
02318           if ( m_request.davData.depth == 2 )
02319             davHeader += "infinity";
02320           else
02321             davHeader += QString("%1").arg( m_request.davData.depth );
02322         }
02323         davHeader += "\r\n";
02324         m_request.bCachedWrite = false; // Do not put any result in the cache
02325         break;
02326     case DAV_PROPPATCH:
02327         header = "PROPPATCH ";
02328         davData = true;
02329         m_request.bCachedWrite = false; // Do not put any result in the cache
02330         break;
02331     case DAV_MKCOL:
02332         header = "MKCOL ";
02333         m_request.bCachedWrite = false; // Do not put any result in the cache
02334         break;
02335     case DAV_COPY:
02336     case DAV_MOVE:
02337         header = ( m_request.method == DAV_COPY ) ? "COPY " : "MOVE ";
02338         davHeader = "Destination: " + m_request.davData.desturl;
02339         // infinity depth means copy recursively
02340         // (optional for copy -> but is the desired action)
02341         davHeader += "\r\nDepth: infinity\r\nOverwrite: ";
02342         davHeader += m_request.davData.overwrite ? "T" : "F";
02343         davHeader += "\r\n";
02344         m_request.bCachedWrite = false; // Do not put any result in the cache
02345         break;
02346     case DAV_LOCK:
02347         header = "LOCK ";
02348         davHeader = "Timeout: ";
02349         {
02350           uint timeout = 0;
02351           if ( hasMetaData( "davTimeout" ) )
02352             timeout = metaData( "davTimeout" ).toUInt();
02353           if ( timeout == 0 )
02354             davHeader += "Infinite";
02355           else
02356             davHeader += QString("Seconds-%1").arg(timeout);
02357         }
02358         davHeader += "\r\n";
02359         m_request.bCachedWrite = false; // Do not put any result in the cache
02360         davData = true;
02361         break;
02362     case DAV_UNLOCK:
02363         header = "UNLOCK ";
02364         davHeader = "Lock-token: " + metaData("davLockToken") + "\r\n";
02365         m_request.bCachedWrite = false; // Do not put any result in the cache
02366         break;
02367     case DAV_SEARCH:
02368         header = "SEARCH ";
02369         davData = true;
02370         m_request.bCachedWrite = false;
02371         break;
02372     case DAV_SUBSCRIBE:
02373         header = "SUBSCRIBE ";
02374         m_request.bCachedWrite = false;
02375         break;
02376     case DAV_UNSUBSCRIBE:
02377         header = "UNSUBSCRIBE ";
02378         m_request.bCachedWrite = false;
02379         break;
02380     case DAV_POLL:
02381         header = "POLL ";
02382         m_request.bCachedWrite = false;
02383         break;
02384     default:
02385         error (ERR_UNSUPPORTED_ACTION, QString::null);
02386         return false;
02387     }
02388     // DAV_POLL; DAV_NOTIFY
02389 
02390     // format the URI
02391     if (m_state.doProxy && !m_bIsTunneled)
02392     {
02393       KURL u;
02394 
02395       if (m_protocol == "webdav")
02396          u.setProtocol( "http" );
02397       else if (m_protocol == "webdavs" )
02398          u.setProtocol( "https" );
02399       else
02400          u.setProtocol( m_protocol );
02401 
02402       // For all protocols other than the once handled by this io-slave
02403       // append the username.  This fixes a long standing bug of ftp io-slave
02404       // logging in anonymously in proxied connections even when the username
02405       // is explicitly specified.
02406       if (m_protocol != "http" && m_protocol != "https" &&
02407           !m_state.user.isEmpty())
02408         u.setUser (m_state.user);
02409 
02410       u.setHost( m_state.hostname );
02411       if (m_state.port != m_iDefaultPort)
02412          u.setPort( m_state.port );
02413       u.setEncodedPathAndQuery( m_request.url.encodedPathAndQuery(0,true) );
02414       header += u.url();
02415     }
02416     else
02417     {
02418       header += m_request.url.encodedPathAndQuery(0, true);
02419     }
02420 
02421     header += " HTTP/1.1\r\n"; /* start header */
02422 
02423     if (!m_request.userAgent.isEmpty())
02424     {
02425         header += "User-Agent: ";
02426         header += m_request.userAgent;
02427         header += "\r\n";
02428     }
02429 
02430     if (!m_request.referrer.isEmpty())
02431     {
02432         header += "Referer: "; //Don't try to correct spelling!
02433         header += m_request.referrer;
02434         header += "\r\n";
02435     }
02436 
02437     if ( m_request.offset > 0 )
02438     {
02439       header += QString("Range: bytes=%1-\r\n").arg(KIO::number(m_request.offset));
02440       kdDebug(7103) << "kio_http : Range = " << KIO::number(m_request.offset) << endl;
02441     }
02442 
02443     if ( m_request.cache == CC_Reload )
02444     {
02445       /* No caching for reload */
02446       header += "Pragma: no-cache\r\n"; /* for HTTP/1.0 caches */
02447       header += "Cache-control: no-cache\r\n"; /* for HTTP >=1.1 caches */
02448     }
02449 
02450     if (m_request.bMustRevalidate)
02451     {
02452       /* conditional get */
02453       if (!m_request.etag.isEmpty())
02454         header += "If-None-Match: "+m_request.etag+"\r\n";
02455       if (!m_request.lastModified.isEmpty())
02456         header += "If-Modified-Since: "+m_request.lastModified+"\r\n";
02457     }
02458 
02459     header += "Accept: ";
02460     QString acceptHeader = metaData("accept");
02461     if (!acceptHeader.isEmpty())
02462       header += acceptHeader;
02463     else
02464       header += DEFAULT_ACCEPT_HEADER;
02465     header += "\r\n";
02466 
02467 #ifdef DO_GZIP
02468     if (m_request.allowCompressedPage)
02469       header += "Accept-Encoding: x-gzip, x-deflate, gzip, deflate\r\n";
02470 #endif
02471 
02472     if (!m_request.charsets.isEmpty())
02473       header += "Accept-Charset: " + m_request.charsets + "\r\n";
02474 
02475     if (!m_request.languages.isEmpty())
02476       header += "Accept-Language: " + m_request.languages + "\r\n";
02477 
02478 
02479     /* support for virtual hosts and required by HTTP 1.1 */
02480     header += "Host: " + m_state.encoded_hostname;
02481 
02482     if (m_state.port != m_iDefaultPort)
02483       header += QString(":%1").arg(m_state.port);
02484     header += "\r\n";
02485 
02486     QString cookieStr;
02487     QString cookieMode = metaData("cookies").lower();
02488     if (cookieMode == "none")
02489     {
02490       m_request.cookieMode = HTTPRequest::CookiesNone;
02491     }
02492     else if (cookieMode == "manual")
02493     {
02494       m_request.cookieMode = HTTPRequest::CookiesManual;
02495       cookieStr = metaData("setcookies");
02496     }
02497     else
02498     {
02499       m_request.cookieMode = HTTPRequest::CookiesAuto;
02500       if (m_request.bUseCookiejar)
02501         cookieStr = findCookies( m_request.url.url());
02502     }
02503 
02504     if (!cookieStr.isEmpty())
02505       header += cookieStr + "\r\n";
02506 
02507     QString customHeader = metaData( "customHTTPHeader" );
02508     if (!customHeader.isEmpty())
02509     {
02510       header += sanitizeCustomHTTPHeader(customHeader);
02511       header += "\r\n";
02512     }
02513 
02514     if (m_request.method == HTTP_POST)
02515     {
02516       header += metaData("content-type");
02517       header += "\r\n";
02518     }
02519 
02520     // Only check for a cached copy if the previous
02521     // response was NOT a 401 or 407.
02522     // no caching for Negotiate auth.
02523     if ( !m_request.bNoAuth && m_responseCode != 401 && m_responseCode != 407 && Authentication != AUTH_Negotiate )
02524     {
02525       kdDebug(7113) << "(" << m_pid << ") Calling checkCachedAuthentication " << endl;
02526       AuthInfo info;
02527       info.url = m_request.url;
02528       info.verifyPath = true;
02529       if ( !m_request.user.isEmpty() )
02530         info.username = m_request.user;
02531       if ( checkCachedAuthentication( info ) && !info.digestInfo.isEmpty() )
02532       {
02533         Authentication = info.digestInfo.startsWith("Basic") ? AUTH_Basic : info.digestInfo.startsWith("NTLM") ? AUTH_NTLM : info.digestInfo.startsWith("Negotiate") ? AUTH_Negotiate : AUTH_Digest ;
02534         m_state.user   = info.username;
02535         m_state.passwd = info.password;
02536         m_strRealm = info.realmValue;
02537         if ( Authentication != AUTH_NTLM && Authentication != AUTH_Negotiate ) // don't use the cached challenge
02538           m_strAuthorization = info.digestInfo;
02539       }
02540     }
02541     else
02542     {
02543       kdDebug(7113) << "(" << m_pid << ") Not calling checkCachedAuthentication " << endl;
02544     }
02545 
02546     switch ( Authentication )
02547     {
02548       case AUTH_Basic:
02549           header += createBasicAuth();
02550           break;
02551       case AUTH_Digest:
02552           header += createDigestAuth();
02553           break;
02554 #ifdef HAVE_LIBGSSAPI
02555       case AUTH_Negotiate:
02556           header += createNegotiateAuth();
02557           break;
02558 #endif
02559       case AUTH_NTLM:
02560           header += createNTLMAuth();
02561           break;
02562       case AUTH_None:
02563       default:
02564           break;
02565     }
02566 
02567     /********* Only for debugging purpose *********/
02568     if ( Authentication != AUTH_None )
02569     {
02570       kdDebug(7113) << "(" << m_pid << ") Using Authentication: " << endl;
02571       kdDebug(7113) << "(" << m_pid << ")   HOST= " << m_state.hostname << endl;
02572       kdDebug(7113) << "(" << m_pid << ")   PORT= " << m_state.port << endl;
02573       kdDebug(7113) << "(" << m_pid << ")   USER= " << m_state.user << endl;
02574       kdDebug(7113) << "(" << m_pid << ")   PASSWORD= [protected]" << endl;
02575       kdDebug(7113) << "(" << m_pid << ")   REALM= " << m_strRealm << endl;
02576       kdDebug(7113) << "(" << m_pid << ")   EXTRA= " << m_strAuthorization << endl;
02577     }
02578 
02579     // Do we need to authorize to the proxy server ?
02580     if ( m_state.doProxy && !m_bIsTunneled )
02581       header += proxyAuthenticationHeader();
02582 
02583     // Support old HTTP/1.0 style keep-alive header for compatability
02584     // purposes as well as performance improvements while giving end
02585     // users the ability to disable this feature proxy servers that
02586     // don't not support such feature, e.g. junkbuster proxy server.
02587     if (!m_bUseProxy || m_bPersistentProxyConnection || m_bIsTunneled)
02588       header += "Connection: Keep-Alive\r\n";
02589     else
02590       header += "Connection: close\r\n";
02591 
02592     if ( m_protocol == "webdav" || m_protocol == "webdavs" )
02593     {
02594       header += davProcessLocks();
02595 
02596       // add extra webdav headers, if supplied
02597       QString davExtraHeader = metaData("davHeader");
02598       if ( !davExtraHeader.isEmpty() )
02599         davHeader += davExtraHeader;
02600 
02601       // Set content type of webdav data
02602       if (davData)
02603         davHeader += "Content-Type: text/xml; charset=utf-8\r\n";
02604 
02605       // add extra header elements for WebDAV
02606       if ( !davHeader.isNull() )
02607         header += davHeader;
02608     }
02609   }
02610 
02611   kdDebug(7103) << "(" << m_pid << ") ============ Sending Header:" << endl;
02612 
02613   QStringList headerOutput = QStringList::split("\r\n", header);
02614   QStringList::Iterator it = headerOutput.begin();
02615 
02616   for (; it != headerOutput.end(); it++)
02617     kdDebug(7103) << "(" << m_pid << ") " << (*it) << endl;
02618 
02619   if ( !moreData && !davData)
02620     header += "\r\n";  /* end header */
02621 
02622   // Now that we have our formatted header, let's send it!
02623   // Create a new connection to the remote machine if we do
02624   // not already have one...
02625   if ( m_iSock == -1)
02626   {
02627     if (!httpOpenConnection())
02628        return false;
02629   }
02630 
02631   // Send the data to the remote machine...
02632   bool sendOk = (write(header.latin1(), header.length()) == (ssize_t) header.length());
02633   if (!sendOk)
02634   {
02635     kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpen: "
02636                      "Connection broken! (" << m_state.hostname << ")" << endl;
02637 
02638     // With a Keep-Alive connection this can happen.
02639     // Just reestablish the connection.
02640     if (m_bKeepAlive)
02641     {
02642        httpCloseConnection();
02643        return true; // Try again
02644     }
02645 
02646     if (!sendOk)
02647     {
02648        kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpen: sendOk==false."
02649                         " Connnection broken !" << endl;
02650        error( ERR_CONNECTION_BROKEN, m_state.hostname );
02651        return false;
02652     }
02653   }
02654 
02655   bool res = true;
02656 
02657   if ( moreData || davData )
02658     res = sendBody();
02659 
02660   infoMessage(i18n("%1 contacted. Waiting for reply...").arg(m_request.hostname));
02661 
02662   return res;
02663 }
02664 
02665 void HTTPProtocol::forwardHttpResponseHeader()
02666 {
02667   // Send the response header if it was requested
02668   if ( config()->readBoolEntry("PropagateHttpHeader", false) )
02669   {
02670     setMetaData("HTTP-Headers", m_responseHeader.join("\n"));
02671     sendMetaData();
02672   }
02673   m_responseHeader.clear();
02674 }
02675 
02682 bool HTTPProtocol::readHeader()
02683 {
02684 try_again:
02685   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader" << endl;
02686 
02687   // Check
02688   if (m_request.bCachedRead)
02689   {
02690      m_responseHeader << "HTTP-CACHE";
02691      // Read header from cache...
02692      char buffer[4097];
02693      if (!fgets(buffer, 4096, m_request.fcache) )
02694      {
02695         // Error, delete cache entry
02696         kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: "
02697                       << "Could not access cache to obtain mimetype!" << endl;
02698         error( ERR_CONNECTION_BROKEN, m_state.hostname );
02699         return false;
02700      }
02701 
02702      m_strMimeType = QString::fromUtf8( buffer).stripWhiteSpace();
02703 
02704      kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: cached "
02705                    << "data mimetype: " << m_strMimeType << endl;
02706 
02707      if (!fgets(buffer, 4096, m_request.fcache) )
02708      {
02709         // Error, delete cache entry
02710         kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: "
02711                       << "Could not access cached data! " << endl;
02712         error( ERR_CONNECTION_BROKEN, m_state.hostname );
02713         return false;
02714      }
02715 
02716      m_request.strCharset = QString::fromUtf8( buffer).stripWhiteSpace().lower();
02717      setMetaData("charset", m_request.strCharset);
02718      if (!m_request.lastModified.isEmpty())
02719          setMetaData("modified", m_request.lastModified);
02720      QString tmp;
02721      tmp.setNum(m_request.expireDate);
02722      setMetaData("expire-date", tmp);
02723      tmp.setNum(m_request.creationDate);
02724      setMetaData("cache-creation-date", tmp);
02725      mimeType(m_strMimeType);
02726      forwardHttpResponseHeader();
02727      return true;
02728   }
02729 
02730   QCString locationStr; // In case we get a redirect.
02731   QCString cookieStr; // In case we get a cookie.
02732 
02733   QString dispositionType; // In case we get a Content-Disposition type
02734   QString dispositionFilename; // In case we get a Content-Disposition filename
02735 
02736   QString mediaValue;
02737   QString mediaAttribute;
02738 
02739   QStringList upgradeOffers;
02740 
02741   bool upgradeRequired = false;   // Server demands that we upgrade to something
02742                                   // This is also true if we ask to upgrade and
02743                                   // the server accepts, since we are now
02744                                   // committed to doing so
02745   bool canUpgrade = false;        // The server offered an upgrade
02746 
02747 
02748   m_request.etag = QString::null;
02749   m_request.lastModified = QString::null;
02750   m_request.strCharset = QString::null;
02751 
02752   time_t dateHeader = 0;
02753   time_t expireDate = 0; // 0 = no info, 1 = already expired, > 1 = actual date
02754   int currentAge = 0;
02755   int maxAge = -1; // -1 = no max age, 0 already expired, > 0 = actual time
02756   int maxHeaderSize = 64*1024; // 64Kb to catch DOS-attacks
02757 
02758   // read in 8192 bytes at a time (HTTP cookies can be quite large.)
02759   int len = 0;
02760   char buffer[8193];
02761   bool cont = false;
02762   bool cacheValidated = false; // Revalidation was successful
02763   bool mayCache = true;
02764   bool hasCacheDirective = false;
02765   bool bCanResume = false;
02766 
02767   if (m_iSock == -1)
02768   {
02769      kdDebug(7113) << "HTTPProtocol::readHeader: No connection." << endl;
02770      return false; // Restablish connection and try again
02771   }
02772 
02773   if (!waitForResponse(m_remoteRespTimeout))
02774   {
02775      // No response error
02776      error( ERR_SERVER_TIMEOUT , m_state.hostname );
02777      return false;
02778   }
02779 
02780   setRewindMarker();
02781 
02782   gets(buffer, sizeof(buffer)-1);
02783 
02784   if (m_bEOF || *buffer == '\0')
02785   {
02786     kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: "
02787                   << "EOF while waiting for header start." << endl;
02788     if (m_bKeepAlive) // Try to reestablish connection.
02789     {
02790       httpCloseConnection();
02791       return false; // Reestablish connection and try again.
02792     }
02793 
02794     if (m_request.method == HTTP_HEAD)
02795     {
02796       // HACK
02797       // Some web-servers fail to respond properly to a HEAD request.
02798       // We compensate for their failure to properly implement the HTTP standard
02799       // by assuming that they will be sending html.
02800       kdDebug(7113) << "(" << m_pid << ") HTTPPreadHeader: HEAD -> returned "
02801                     << "mimetype: " << DEFAULT_MIME_TYPE << endl;
02802       mimeType(QString::fromLatin1(DEFAULT_MIME_TYPE));
02803       return true;
02804     }
02805 
02806     kdDebug(7113) << "HTTPProtocol::readHeader: Connection broken !" << endl;
02807     error( ERR_CONNECTION_BROKEN, m_state.hostname );
02808     return false;
02809   }
02810 
02811   kdDebug(7103) << "(" << m_pid << ") ============ Received Response:"<< endl;
02812 
02813   bool noHeader = true;
02814   HTTP_REV httpRev = HTTP_None;
02815   int headerSize = 0;
02816 
02817   do
02818   {
02819     // strip off \r and \n if we have them
02820     len = strlen(buffer);
02821 
02822     while(len && (buffer[len-1] == '\n' || buffer[len-1] == '\r'))
02823       buffer[--len] = 0;
02824 
02825     // if there was only a newline then continue
02826     if (!len)
02827     {
02828       kdDebug(7103) << "(" << m_pid << ") --empty--" << endl;
02829       continue;
02830     }
02831 
02832     headerSize += len;
02833 
02834     // We have a response header.  This flag is a work around for
02835     // servers that append a "\r\n" before the beginning of the HEADER
02836     // response!!!  It only catches x number of \r\n being placed at the
02837     // top of the reponse...
02838     noHeader = false;
02839 
02840     kdDebug(7103) << "(" << m_pid << ") \"" << buffer << "\"" << endl;
02841 
02842     // Save broken servers from damnation!!
02843     char* buf = buffer;
02844     while( *buf == ' ' )
02845         buf++;
02846 
02847 
02848     if (buf[0] == '<')
02849     {
02850       // We get XML / HTTP without a proper header
02851       // put string back
02852       kdDebug(7103) << "kio_http: No valid HTTP header found! Document starts with XML/HTML tag" << endl;
02853 
02854       // Document starts with a tag, assume html instead of text/plain
02855       m_strMimeType = "text/html";
02856 
02857       rewind();
02858       break;
02859     }
02860 
02861     // Store the the headers so they can be passed to the
02862     // calling application later
02863     m_responseHeader << QString::fromLatin1(buf);
02864 
02865     if ((strncasecmp(buf, "HTTP", 4) == 0) ||
02866         (strncasecmp(buf, "ICY ", 4) == 0)) // Shoutcast support
02867     {
02868       if (strncasecmp(buf, "ICY ", 4) == 0)
02869       {
02870         // Shoutcast support
02871         httpRev = SHOUTCAST;
02872         m_bKeepAlive = false;
02873       }
02874       else if (strncmp((buf + 5), "1.0",3) == 0)
02875       {
02876         httpRev = HTTP_10;
02877         // For 1.0 servers, the server itself has to explicitly
02878         // tell us whether it supports persistent connection or
02879         // not.  By default, we assume it does not, but we do
02880         // send the old style header "Connection: Keep-Alive" to
02881         // inform it that we support persistence.
02882         m_bKeepAlive = false;
02883       }
02884       else if (strncmp((buf + 5), "1.1",3) == 0)
02885       {
02886         httpRev = HTTP_11;
02887       }
02888       else
02889       {
02890         httpRev = HTTP_Unknown;
02891       }
02892 
02893       if (m_responseCode)
02894         m_prevResponseCode = m_responseCode;
02895 
02896       const char* rptr = buf;
02897       while ( *rptr && *rptr > ' ' )
02898           ++rptr;
02899       m_responseCode = atoi(rptr);
02900 
02901       // server side errors
02902       if (m_responseCode >= 500 && m_responseCode <= 599)
02903       {
02904         if (m_request.method == HTTP_HEAD)
02905         {
02906            ; // Ignore error
02907         }
02908         else
02909         {
02910            if (m_request.bErrorPage)
02911               errorPage();
02912            else
02913            {
02914               error(ERR_INTERNAL_SERVER, m_request.url.url());
02915               return false;
02916            }
02917         }
02918         m_request.bCachedWrite = false; // Don't put in cache
02919         mayCache = false;
02920       }
02921       // Unauthorized access
02922       else if (m_responseCode == 401 || m_responseCode == 407)
02923       {
02924         // Double authorization requests, i.e. a proxy auth
02925         // request followed immediately by a regular auth request.
02926         if ( m_prevResponseCode != m_responseCode &&
02927             (m_prevResponseCode == 401 || m_prevResponseCode == 407) )
02928           saveAuthorization();
02929 
02930         m_bUnauthorized = true;
02931         m_request.bCachedWrite = false; // Don't put in cache
02932         mayCache = false;
02933       }
02934       //
02935       else if (m_responseCode == 416) // Range not supported
02936       {
02937         m_request.offset = 0;
02938         httpCloseConnection();
02939         return false; // Try again.
02940       }
02941       // Upgrade Required
02942       else if (m_responseCode == 426)
02943       {
02944         upgradeRequired = true;
02945       }
02946       // Any other client errors
02947       else if (m_responseCode >= 400 && m_responseCode <= 499)
02948       {
02949         // Tell that we will only get an error page here.
02950         if (m_request.bErrorPage)
02951           errorPage();
02952         else
02953         {
02954           error(ERR_DOES_NOT_EXIST, m_request.url.url());
02955           return false;
02956         }
02957         m_request.bCachedWrite = false; // Don't put in cache
02958         mayCache = false;
02959       }
02960       else if (m_responseCode == 307)
02961       {
02962         // 307 Temporary Redirect
02963         m_request.bCachedWrite = false; // Don't put in cache
02964         mayCache = false;
02965       }
02966       else if (m_responseCode == 304)
02967       {
02968         // 304 Not Modified
02969         // The value in our cache is still valid.
02970         cacheValidated = true;
02971       }
02972       else if (m_responseCode >= 301 && m_responseCode<= 303)
02973       {
02974         // 301 Moved permanently
02975         if (m_responseCode == 301)
02976            setMetaData("permanent-redirect", "true");
02977 
02978         // 302 Found (temporary location)
02979         // 303 See Other
02980         if (m_request.method != HTTP_HEAD && m_request.method != HTTP_GET)
02981         {
02982 #if 0
02983            // Reset the POST buffer to avoid a double submit
02984            // on redirection
02985            if (m_request.method == HTTP_POST)
02986               m_bufPOST.resize(0);
02987 #endif
02988 
02989            // NOTE: This is wrong according to RFC 2616.  However,
02990            // because most other existing user agent implementations
02991            // treat a 301/302 response as a 303 response and preform
02992            // a GET action regardless of what the previous method was,
02993            // many servers have simply adapted to this way of doing
02994            // things!!  Thus, we are forced to do the same thing or we
02995            // won't be able to retrieve these pages correctly!! See RFC
02996            // 2616 sections 10.3.[2/3/4/8]
02997            m_request.method = HTTP_GET; // Force a GET
02998         }
02999         m_request.bCachedWrite = false; // Don't put in cache
03000         mayCache = false;
03001       }
03002       else if ( m_responseCode == 207 ) // Multi-status (for WebDav)
03003       {
03004 
03005       }
03006       else if ( m_responseCode == 204 ) // No content
03007       {
03008         // error(ERR_NO_CONTENT, i18n("Data have been successfully sent."));
03009         // Short circuit and do nothing!
03010 
03011         // The original handling here was wrong, this is not an error: eg. in the
03012         // example of a 204 No Content response to a PUT completing.
03013         // m_bError = true;
03014         // return false;
03015       }
03016       else if ( m_responseCode == 206 )
03017       {
03018         if ( m_request.offset )
03019           bCanResume = true;
03020       }
03021       else if (m_responseCode == 102) // Processing (for WebDAV)
03022       {
03023         /***
03024          * This status code is given when the server expects the
03025          * command to take significant time to complete. So, inform
03026          * the user.
03027          */
03028         infoMessage( i18n( "Server processing request, please wait..." ) );
03029         cont = true;
03030       }
03031       else if (m_responseCode == 100)
03032       {
03033         // We got 'Continue' - ignore it
03034         cont = true;
03035       }
03036     }
03037 
03038     // are we allowd to resume?  this will tell us
03039     else if (strncasecmp(buf, "Accept-Ranges:", 14) == 0) {
03040       if (strncasecmp(trimLead(buf + 14), "none", 4) == 0)
03041             bCanResume = false;
03042     }
03043     // Keep Alive
03044     else if (strncasecmp(buf, "Keep-Alive:", 11) == 0) {
03045       QStringList options = QStringList::split(',',
03046                                      QString::fromLatin1(trimLead(buf+11)));
03047       for(QStringList::ConstIterator it = options.begin();
03048           it != options.end();
03049           it++)
03050       {
03051          QString option = (*it).stripWhiteSpace().lower();
03052          if (option.startsWith("timeout="))
03053          {
03054             m_keepAliveTimeout = option.mid(8).toInt();
03055          }
03056       }
03057     }
03058 
03059     // Cache control
03060     else if (strncasecmp(buf, "Cache-Control:", 14) == 0) {
03061       QStringList cacheControls = QStringList::split(',',
03062                                      QString::fromLatin1(trimLead(buf+14)));
03063       for(QStringList::ConstIterator it = cacheControls.begin();
03064           it != cacheControls.end();
03065           it++)
03066       {
03067          QString cacheControl = (*it).stripWhiteSpace();
03068          if (strncasecmp(cacheControl.latin1(), "no-cache", 8) == 0)
03069          {
03070             m_request.bCachedWrite = false; // Don't put in cache
03071             mayCache = false;
03072          }
03073          else if (strncasecmp(cacheControl.latin1(), "no-store", 8) == 0)
03074          {
03075             m_request.bCachedWrite = false; // Don't put in cache
03076             mayCache = false;
03077          }
03078          else if (strncasecmp(cacheControl.latin1(), "max-age=", 8) == 0)
03079          {
03080             QString age = cacheControl.mid(8).stripWhiteSpace();
03081             if (!age.isNull())
03082               maxAge = STRTOLL(age.latin1(), 0, 10);
03083          }
03084       }
03085       hasCacheDirective = true;
03086     }
03087 
03088     // get the size of our data
03089     else if (strncasecmp(buf, "Content-length:", 15) == 0) {
03090       char* len = trimLead(buf + 15);
03091       if (len)
03092         m_iSize = STRTOLL(len, 0, 10);
03093     }
03094 
03095     else if (strncasecmp(buf, "Content-location:", 17) == 0) {
03096       setMetaData ("content-location",
03097                    QString::fromLatin1(trimLead(buf+17)).stripWhiteSpace());
03098     }
03099 
03100     // what type of data do we have?
03101     else if (strncasecmp(buf, "Content-type:", 13) == 0) {
03102       char *start = trimLead(buf + 13);
03103       char *pos = start;
03104 
03105       // Increment until we encounter ";" or the end of the buffer
03106       while ( *pos && *pos != ';' )  pos++;
03107 
03108       // Assign the mime-type.
03109       m_strMimeType = QString::fromLatin1(start, pos-start).stripWhiteSpace().lower();
03110       kdDebug(7113) << "(" << m_pid << ") Content-type: " << m_strMimeType << endl;
03111 
03112       // If we still have text, then it means we have a mime-type with a
03113       // parameter (eg: charset=iso-8851) ; so let's get that...
03114       while (*pos)
03115       {
03116         start = ++pos;
03117         while ( *pos && *pos != '=' )  pos++;
03118 
03119     char *end = pos;
03120     while ( *end && *end != ';' )  end++;
03121 
03122         if (*pos)
03123         {
03124           mediaAttribute = QString::fromLatin1(start, pos-start).stripWhiteSpace().lower();
03125           mediaValue = QString::fromLatin1(pos+1, end-pos-1).stripWhiteSpace();
03126       pos = end;
03127           if (mediaValue.length() &&
03128               (mediaValue[0] == '"') &&
03129               (mediaValue[mediaValue.length()-1] == '"'))
03130              mediaValue = mediaValue.mid(1, mediaValue.length()-2);
03131 
03132           kdDebug (7113) << "(" << m_pid << ") Media-Parameter Attribute: "
03133                          << mediaAttribute << endl;
03134           kdDebug (7113) << "(" << m_pid << ") Media-Parameter Value: "
03135                          << mediaValue << endl;
03136 
03137           if ( mediaAttribute == "charset")
03138           {
03139             mediaValue = mediaValue.lower();
03140             m_request.strCharset = mediaValue;
03141           }
03142           else
03143           {
03144             setMetaData("media-"+mediaAttribute, mediaValue);
03145           }
03146         }
03147       }
03148     }
03149 
03150     // Date
03151     else if (strncasecmp(buf, "Date:", 5) == 0) {
03152       dateHeader = KRFCDate::parseDate(trimLead(buf+5));
03153     }
03154 
03155     // Cache management
03156     else if (strncasecmp(buf, "ETag:", 5) == 0) {
03157       m_request.etag = trimLead(buf+5);
03158     }
03159 
03160     // Cache management
03161     else if (strncasecmp(buf, "Expires:", 8) == 0) {
03162       expireDate = KRFCDate::parseDate(trimLead(buf+8));
03163       if (!expireDate)
03164         expireDate = 1; // Already expired
03165     }
03166 
03167     // Cache management
03168     else if (strncasecmp(buf, "Last-Modified:", 14) == 0) {
03169       m_request.lastModified = (QString::fromLatin1(trimLead(buf+14))).stripWhiteSpace();
03170     }
03171 
03172     // whoops.. we received a warning
03173     else if (strncasecmp(buf, "Warning:", 8) == 0) {
03174       //Don't use warning() here, no need to bother the user.
03175       //Those warnings are mostly about caches.
03176       infoMessage(trimLead(buf + 8));
03177     }
03178 
03179     // Cache management (HTTP 1.0)
03180     else if (strncasecmp(buf, "Pragma:", 7) == 0) {
03181       QCString pragma = QCString(trimLead(buf+7)).stripWhiteSpace().lower();
03182       if (pragma == "no-cache")
03183       {
03184          m_request.bCachedWrite = false; // Don't put in cache
03185          mayCache = false;
03186          hasCacheDirective = true;
03187       }
03188     }
03189 
03190     // The deprecated Refresh Response
03191     else if (strncasecmp(buf,"Refresh:", 8) == 0) {
03192       mayCache = false;  // Do not cache page as it defeats purpose of Refresh tag!
03193       setMetaData( "http-refresh", QString::fromLatin1(trimLead(buf+8)).stripWhiteSpace() );
03194     }
03195 
03196     // In fact we should do redirection only if we got redirection code
03197     else if (strncasecmp(buf, "Location:", 9) == 0) {
03198       // Redirect only for 3xx status code, will ya! Thanks, pal!
03199       if ( m_responseCode > 299 && m_responseCode < 400 )
03200         locationStr = QCString(trimLead(buf+9)).stripWhiteSpace();
03201     }
03202 
03203     // Check for cookies
03204     else if (strncasecmp(buf, "Set-Cookie", 10) == 0) {
03205       cookieStr += buf;
03206       cookieStr += '\n';
03207     }
03208 
03209     // check for direct authentication
03210     else if (strncasecmp(buf, "WWW-Authenticate:", 17) == 0) {
03211       configAuth(trimLead(buf + 17), false);
03212     }
03213 
03214     // check for proxy-based authentication
03215     else if (strncasecmp(buf, "Proxy-Authenticate:", 19) == 0) {
03216       configAuth(trimLead(buf + 19), true);
03217     }
03218 
03219     else if (strncasecmp(buf, "Upgrade:", 8) == 0) {
03220        // Now we have to check to see what is offered for the upgrade
03221        QString offered = &(buf[8]);
03222        upgradeOffers = QStringList::split(QRegExp("[ \n,\r\t]"), offered);
03223     }
03224 
03225     // content?
03226     else if (strncasecmp(buf, "Content-Encoding:", 17) == 0) {
03227       // This is so wrong !!  No wonder kio_http is stripping the
03228       // gzip encoding from downloaded files.  This solves multiple
03229       // bug reports and caitoo's problem with downloads when such a
03230       // header is encountered...
03231 
03232       // A quote from RFC 2616:
03233       // " When present, its (Content-Encoding) value indicates what additional
03234       // content have been applied to the entity body, and thus what decoding
03235       // mechanism must be applied to obtain the media-type referenced by the
03236       // Content-Type header field.  Content-Encoding is primarily used to allow
03237       // a document to be compressed without loosing the identity of its underlying
03238       // media type.  Simply put if it is specified, this is the actual mime-type
03239       // we should use when we pull the resource !!!
03240       addEncoding(trimLead(buf + 17), m_qContentEncodings);
03241     }
03242     // Refer to RFC 2616 sec 15.5/19.5.1 and RFC 2183
03243     else if(strncasecmp(buf, "Content-Disposition:", 20) == 0) {
03244       char* dispositionBuf = trimLead(buf + 20);
03245       while ( *dispositionBuf )
03246       {
03247         if ( strncasecmp( dispositionBuf, "filename", 8 ) == 0 )
03248         {
03249           dispositionBuf += 8;
03250 
03251           while ( *dispositionBuf == ' ' || *dispositionBuf == '=' )
03252             dispositionBuf++;
03253 
03254           char* bufStart = dispositionBuf;
03255 
03256           while ( *dispositionBuf && *dispositionBuf != ';' )
03257             dispositionBuf++;
03258 
03259           if ( dispositionBuf > bufStart )
03260           {
03261             // Skip any leading quotes...
03262             while ( *bufStart == '"' )
03263               bufStart++;
03264 
03265             // Skip any trailing quotes as well as white spaces...
03266             while ( *(dispositionBuf-1) == ' ' || *(dispositionBuf-1) == '"')
03267               dispositionBuf--;
03268 
03269             if ( dispositionBuf > bufStart )
03270               dispositionFilename = QString::fromLatin1( bufStart, dispositionBuf-bufStart );
03271 
03272             break;
03273           }
03274         }
03275         else
03276         {
03277           char *bufStart = dispositionBuf;
03278 
03279           while ( *dispositionBuf && *dispositionBuf != ';' )
03280             dispositionBuf++;
03281 
03282           if ( dispositionBuf > bufStart )
03283             dispositionType = QString::fromLatin1( bufStart, dispositionBuf-bufStart ).stripWhiteSpace();
03284 
03285           while ( *dispositionBuf == ';' || *dispositionBuf == ' ' )
03286             dispositionBuf++;
03287         }
03288       }
03289 
03290       // Content-Dispostion is not allowed to dictate directory
03291       // path, thus we extract the filename only.
03292       if ( !dispositionFilename.isEmpty() )
03293       {
03294         int pos = dispositionFilename.findRev( '/' );
03295 
03296         if( pos > -1 )
03297           dispositionFilename = dispositionFilename.mid(pos+1);
03298 
03299         kdDebug(7113) << "(" << m_pid << ") Content-Disposition: filename="
03300                       << dispositionFilename<< endl;
03301       }
03302     }
03303     else if(strncasecmp(buf, "Content-Language:", 17) == 0) {
03304       QString language = QString::fromLatin1(trimLead(buf+17)).stripWhiteSpace();
03305       if (!language.isEmpty())
03306         setMetaData("content-language", language);
03307     }
03308     else if (strncasecmp(buf, "Proxy-Connection:", 17) == 0)
03309     {
03310       if (strncasecmp(trimLead(buf + 17), "Close", 5) == 0)
03311         m_bKeepAlive = false;
03312       else if (strncasecmp(trimLead(buf + 17), "Keep-Alive", 10)==0)
03313         m_bKeepAlive = true;
03314     }
03315     else if (strncasecmp(buf, "Link:", 5) == 0) {
03316       // We only support Link: <url>; rel="type"   so far
03317       QStringList link = QStringList::split(";", QString(buf)
03318                                                  .replace(QRegExp("^Link:[ ]*"),
03319                                                           ""));
03320       if (link.count() == 2) {
03321         QString rel = link[1].stripWhiteSpace();
03322         if (rel.startsWith("rel=\"")) {
03323           rel = rel.mid(5, rel.length() - 6);
03324           if (rel.lower() == "pageservices") {
03325             QString url = link[0].replace(QRegExp("[<>]"),"").stripWhiteSpace();
03326             setMetaData("PageServices", url);
03327           }
03328         }
03329       }
03330     }
03331     else if (strncasecmp(buf, "P3P:", 4) == 0) {
03332       QString p3pstr = buf;
03333       p3pstr = p3pstr.mid(4).simplifyWhiteSpace();
03334       QStringList policyrefs, compact;
03335       QStringList policyfields = QStringList::split(QRegExp(",[ ]*"), p3pstr);
03336       for (QStringList::Iterator it = policyfields.begin();
03337                                   it != policyfields.end();
03338                                                       ++it) {
03339          QStringList policy = QStringList::split("=", *it);
03340 
03341          if (policy.count() == 2) {
03342             if (policy[0].lower() == "policyref") {
03343                policyrefs << policy[1].replace(QRegExp("[\"\']"), "")
03344                                       .stripWhiteSpace();
03345             } else if (policy[0].lower() == "cp") {
03346                // We convert to cp\ncp\ncp\n[...]\ncp to be consistent with
03347                // other metadata sent in strings.  This could be a bit more
03348                // efficient but I'm going for correctness right now.
03349                QStringList cps = QStringList::split(" ",
03350                                         policy[1].replace(QRegExp("[\"\']"), "")
03351                                                  .simplifyWhiteSpace());
03352 
03353                for (QStringList::Iterator j = cps.begin(); j != cps.end(); ++j)
03354                  compact << *j;
03355             }
03356          }
03357       }
03358 
03359       if (!policyrefs.isEmpty())
03360          setMetaData("PrivacyPolicy", policyrefs.join("\n"));
03361 
03362       if (!compact.isEmpty())
03363          setMetaData("PrivacyCompactPolicy", compact.join("\n"));
03364     }
03365     // let them tell us if we should stay alive or not
03366     else if (strncasecmp(buf, "Connection:", 11) == 0)
03367     {
03368         if (strncasecmp(trimLead(buf + 11), "Close", 5) == 0)
03369           m_bKeepAlive = false;
03370         else if (strncasecmp(trimLead(buf + 11), "Keep-Alive", 10)==0)
03371           m_bKeepAlive = true;
03372         else if (strncasecmp(trimLead(buf + 11), "Upgrade", 7)==0)
03373         {
03374           if (m_responseCode == 101) {
03375             // Ok, an upgrade was accepted, now we must do it
03376             upgradeRequired = true;
03377           } else if (upgradeRequired) {  // 426
03378             // Nothing to do since we did it above already
03379           } else {
03380             // Just an offer to upgrade - no need to take it
03381             canUpgrade = true;
03382           }
03383         }
03384     }
03385     // continue only if we know that we're HTTP/1.1
03386     else if ( httpRev == HTTP_11) {
03387       // what kind of encoding do we have?  transfer?
03388       if (strncasecmp(buf, "Transfer-Encoding:", 18) == 0) {
03389         // If multiple encodings have been applied to an entity, the
03390         // transfer-codings MUST be listed in the order in which they
03391         // were applied.
03392         addEncoding(trimLead(buf + 18), m_qTransferEncodings);
03393       }
03394 
03395       // md5 signature
03396       else if (strncasecmp(buf, "Content-MD5:", 12) == 0) {
03397         m_sContentMD5 = QString::fromLatin1(trimLead(buf + 12));
03398       }
03399 
03400       // *** Responses to the HTTP OPTIONS method follow
03401       // WebDAV capabilities
03402       else if (strncasecmp(buf, "DAV:", 4) == 0) {
03403         if (m_davCapabilities.isEmpty()) {
03404           m_davCapabilities << QString::fromLatin1(trimLead(buf + 4));
03405         }
03406         else {
03407           m_davCapabilities << QString::fromLatin1(trimLead(buf + 4));
03408         }
03409       }
03410       // *** Responses to the HTTP OPTIONS method finished
03411     }
03412     else if ((httpRev == HTTP_None) && (strlen(buf) != 0))
03413     {
03414       // Remote server does not seem to speak HTTP at all
03415       // Put the crap back into the buffer and hope for the best
03416       rewind();
03417       if (m_responseCode)
03418         m_prevResponseCode = m_responseCode;
03419 
03420       m_responseCode = 200; // Fake it
03421       httpRev = HTTP_Unknown;
03422       m_bKeepAlive = false;
03423       break;
03424     }
03425     setRewindMarker();
03426 
03427     // Clear out our buffer for further use.
03428     memset(buffer, 0, sizeof(buffer));
03429 
03430   } while (!m_bEOF && (len || noHeader) && (headerSize < maxHeaderSize) && (gets(buffer, sizeof(buffer)-1)));
03431 
03432   // Now process the HTTP/1.1 upgrade
03433   QStringList::Iterator opt = upgradeOffers.begin();
03434   for( ; opt != upgradeOffers.end(); ++opt) {
03435      if (*opt == "TLS/1.0") {
03436         if(upgradeRequired) {
03437            if (!startTLS() && !usingTLS()) {
03438               error(ERR_UPGRADE_REQUIRED, *opt);
03439               return false;
03440            }
03441         }
03442      } else if (*opt == "HTTP/1.1") {
03443         httpRev = HTTP_11;
03444      } else {
03445         // unknown
03446         if (upgradeRequired) {
03447            error(ERR_UPGRADE_REQUIRED, *opt);
03448            return false;
03449         }
03450      }
03451   }
03452 
03453   setMetaData("charset", m_request.strCharset);
03454 
03455   // If we do not support the requested authentication method...
03456   if ( (m_responseCode == 401 && Authentication == AUTH_None) ||
03457        (m_responseCode == 407 && ProxyAuthentication == AUTH_None) )
03458   {
03459     m_bUnauthorized = false;
03460     if (m_request.bErrorPage)
03461       errorPage();
03462     else
03463     {
03464       error( ERR_UNSUPPORTED_ACTION, "Unknown Authorization method!" );
03465       return false;
03466     }
03467   }
03468 
03469   // Fixup expire date for clock drift.
03470   if (expireDate && (expireDate <= dateHeader))
03471     expireDate = 1; // Already expired.
03472 
03473   // Convert max-age into expireDate (overriding previous set expireDate)
03474   if (maxAge == 0)
03475     expireDate = 1; // Already expired.
03476   else if (maxAge > 0)
03477   {
03478     if (currentAge)
03479       maxAge -= currentAge;
03480     if (maxAge <=0)
03481       maxAge = 0;
03482     expireDate = time(0) + maxAge;
03483   }
03484 
03485   if (!expireDate)
03486   {
03487     time_t lastModifiedDate = 0;
03488     if (!m_request.lastModified.isEmpty())
03489        lastModifiedDate = KRFCDate::parseDate(m_request.lastModified);
03490 
03491     if (lastModifiedDate)
03492     {
03493        long diff = static_cast<long>(difftime(dateHeader, lastModifiedDate));
03494        if (diff < 0)
03495           expireDate = time(0) + 1;
03496        else
03497           expireDate = time(0) + (diff / 10);
03498     }
03499     else
03500     {
03501        expireDate = time(0) + DEFAULT_CACHE_EXPIRE;
03502     }
03503   }
03504 
03505   // DONE receiving the header!
03506   if (!cookieStr.isEmpty())
03507   {
03508     if ((m_request.cookieMode == HTTPRequest::CookiesAuto) && m_request.bUseCookiejar)
03509     {
03510       // Give cookies to the cookiejar.
03511       QString domain = config()->readEntry("cross-domain");
03512       if (!domain.isEmpty() && isCrossDomainRequest(m_request.url.host(), domain))
03513          cookieStr = "Cross-Domain\n" + cookieStr;
03514       addCookies( m_request.url.url(), cookieStr );
03515     }
03516     else if (m_request.cookieMode == HTTPRequest::CookiesManual)
03517     {
03518       // Pass cookie to application
03519       setMetaData("setcookies", cookieStr);
03520     }
03521   }
03522 
03523   if (m_request.bMustRevalidate)
03524   {
03525     m_request.bMustRevalidate = false; // Reset just in case.
03526     if (cacheValidated)
03527     {
03528       // Yippie, we can use the cached version.
03529       // Update the cache with new "Expire" headers.
03530       fclose(m_request.fcache);
03531       m_request.fcache = 0;
03532       updateExpireDate( expireDate, true );
03533       m_request.fcache = checkCacheEntry( ); // Re-read cache entry
03534 
03535       if (m_request.fcache)
03536       {
03537           m_request.bCachedRead = true;
03538           goto try_again; // Read header again, but now from cache.
03539        }
03540        else
03541        {
03542           // Where did our cache entry go???
03543        }
03544      }
03545      else
03546      {
03547        // Validation failed. Close cache.
03548        fclose(m_request.fcache);
03549        m_request.fcache = 0;
03550      }
03551   }
03552 
03553   // We need to reread the header if we got a '100 Continue' or '102 Processing'
03554   if ( cont )
03555   {
03556     goto try_again;
03557   }
03558 
03559   // Do not do a keep-alive connection if the size of the
03560   // response is not known and the response is not Chunked.
03561   if (!m_bChunked && (m_iSize == NO_SIZE))
03562     m_bKeepAlive = false;
03563 
03564   if ( m_responseCode == 204 )
03565   {
03566     return true;
03567   }
03568 
03569   // We need to try to login again if we failed earlier
03570   if ( m_bUnauthorized )
03571   {
03572     if ( (m_responseCode == 401) ||
03573          (m_bUseProxy && (m_responseCode == 407))
03574        )
03575     {
03576         if ( getAuthorization() )
03577         {
03578            // for NTLM Authentication we have to keep the connection open!
03579            if ( Authentication == AUTH_NTLM && m_strAuthorization.length() > 4 )
03580            {
03581              m_bKeepAlive = true;
03582              readBody( true );
03583            }
03584            else if (ProxyAuthentication == AUTH_NTLM && m_strProxyAuthorization.length() > 4)
03585            {
03586             readBody( true );
03587            }
03588            else
03589              httpCloseConnection();
03590            return false; // Try again.
03591         }
03592 
03593         if (m_bError)
03594            return false; // Error out
03595 
03596         // Show error page...
03597     }
03598     m_bUnauthorized = false;
03599   }
03600 
03601   // We need to do a redirect
03602   if (!locationStr.isEmpty())
03603   {
03604     KURL u(m_request.url, locationStr);
03605     if(!u.isValid())
03606     {
03607       error(ERR_MALFORMED_URL, u.url());
03608       return false;
03609     }
03610     if ((u.protocol() != "http") && (u.protocol() != "https") &&
03611        (u.protocol() != "ftp") && (u.protocol() != "webdav") &&
03612        (u.protocol() != "webdavs"))
03613     {
03614       redirection(u);
03615       error(ERR_ACCESS_DENIED, u.url());
03616       return false;
03617     }
03618 
03619     // preserve #ref: (bug 124654)
03620     // if we were at http://host/resource1#ref, we sent a GET for "/resource1"
03621     // if we got redirected to http://host/resource2, then we have to re-add
03622     // the fragment:
03623     if (m_request.url.hasRef() && !u.hasRef() &&
03624         (m_request.url.host() == u.host()) &&
03625         (m_request.url.protocol() == u.protocol()))
03626       u.setRef(m_request.url.ref());
03627 
03628     m_bRedirect = true;
03629     m_redirectLocation = u;
03630 
03631     if (!m_request.id.isEmpty())
03632     {
03633        sendMetaData();
03634     }
03635 
03636     kdDebug(7113) << "(" << m_pid << ") request.url: " << m_request.url.url()
03637                   << endl << "LocationStr: " << locationStr.data() << endl;
03638 
03639     kdDebug(7113) << "(" << m_pid << ") Requesting redirection to: " << u.url()
03640                   << endl;
03641 
03642     // If we're redirected to a http:// url, remember that we're doing webdav...
03643     if (m_protocol == "webdav" || m_protocol == "webdavs")
03644       u.setProtocol(m_protocol);
03645 
03646     redirection(u);
03647     m_request.bCachedWrite = false; // Turn off caching on re-direction (DA)
03648     mayCache = false;
03649   }
03650 
03651   // Inform the job that we can indeed resume...
03652   if ( bCanResume && m_request.offset )
03653     canResume();
03654   else
03655     m_request.offset = 0;
03656 
03657   // We don't cache certain text objects
03658   if (m_strMimeType.startsWith("text/") &&
03659       (m_strMimeType != "text/css") &&
03660       (m_strMimeType != "text/x-javascript") &&
03661       !hasCacheDirective)
03662   {
03663      // Do not cache secure pages or pages
03664      // originating from password protected sites
03665      // unless the webserver explicitly allows it.
03666      if ( m_bIsSSL || (Authentication != AUTH_None) )
03667      {
03668         m_request.bCachedWrite = false;
03669         mayCache = false;
03670      }
03671   }
03672 
03673   // WABA: Correct for tgz files with a gzip-encoding.
03674   // They really shouldn't put gzip in the Content-Encoding field!
03675   // Web-servers really shouldn't do this: They let Content-Size refer
03676   // to the size of the tgz file, not to the size of the tar file,
03677   // while the Content-Type refers to "tar" instead of "tgz".
03678   if (m_qContentEncodings.last() == "gzip")
03679   {
03680      if (m_strMimeType == "application/x-tar")
03681      {
03682         m_qContentEncodings.remove(m_qContentEncodings.fromLast());
03683         m_strMimeType = QString::fromLatin1("application/x-tgz");
03684      }
03685      else if (m_strMimeType == "application/postscript")
03686      {
03687         // LEONB: Adding another exception for psgz files.
03688         // Could we use the mimelnk files instead of hardcoding all this?
03689         m_qContentEncodings.remove(m_qContentEncodings.fromLast());
03690         m_strMimeType = QString::fromLatin1("application/x-gzpostscript");
03691      }
03692      else if ( m_request.allowCompressedPage &&
03693                m_strMimeType != "application/x-tgz" &&
03694                m_strMimeType != "application/x-targz" &&
03695                m_strMimeType != "application/x-gzip" &&
03696                m_request.url.path().right(6) == ".ps.gz" )
03697      {
03698         m_qContentEncodings.remove(m_qContentEncodings.fromLast());
03699         m_strMimeType = QString::fromLatin1("application/x-gzpostscript");
03700      }
03701      else if ( (m_request.allowCompressedPage &&
03702                 m_strMimeType == "text/html")
03703                 ||
03704                (m_request.allowCompressedPage &&
03705                 m_strMimeType != "application/x-tgz" &&
03706                 m_strMimeType != "application/x-targz" &&
03707                 m_strMimeType != "application/x-gzip" &&
03708                 m_request.url.path().right(3) != ".gz")
03709                 )
03710      {
03711         // Unzip!
03712      }
03713      else
03714      {
03715         m_qContentEncodings.remove(m_qContentEncodings.fromLast());
03716         m_strMimeType = QString::fromLatin1("application/x-gzip");
03717      }
03718   }
03719 
03720   // We can't handle "bzip2" encoding (yet). So if we get something with
03721   // bzip2 encoding, we change the mimetype to "application/x-bzip2".
03722   // Note for future changes: some web-servers send both "bzip2" as
03723   //   encoding and "application/x-bzip2" as mimetype. That is wrong.
03724   //   currently that doesn't bother us, because we remove the encoding
03725   //   and set the mimetype to x-bzip2 anyway.
03726   if (m_qContentEncodings.last() == "bzip2")
03727   {
03728      m_qContentEncodings.remove(m_qContentEncodings.fromLast());
03729      m_strMimeType = QString::fromLatin1("application/x-bzip2");
03730   }
03731 
03732   // Convert some common mimetypes to standard KDE mimetypes
03733   if (m_strMimeType == "application/x-targz")
03734      m_strMimeType = QString::fromLatin1("application/x-tgz");
03735   else if (m_strMimeType == "application/zip")
03736      m_strMimeType = QString::fromLatin1("application/x-zip");
03737   else if (m_strMimeType == "image/x-png")
03738      m_strMimeType = QString::fromLatin1("image/png");
03739   else if (m_strMimeType == "image/bmp")
03740      m_strMimeType = QString::fromLatin1("image/x-bmp");
03741   else if (m_strMimeType == "audio/mpeg" || m_strMimeType == "audio/x-mpeg" || m_strMimeType == "audio/mp3")
03742      m_strMimeType = QString::fromLatin1("audio/x-mp3");
03743   else if (m_strMimeType == "audio/microsoft-wave")
03744      m_strMimeType = QString::fromLatin1("audio/x-wav");
03745   else if (m_strMimeType == "audio/midi")
03746      m_strMimeType = QString::fromLatin1("audio/x-midi");
03747   else if (m_strMimeType == "image/x-xpixmap")
03748      m_strMimeType = QString::fromLatin1("image/x-xpm");
03749   else if (m_strMimeType == "application/rtf")
03750      m_strMimeType = QString::fromLatin1("text/rtf");
03751 
03752   // Crypto ones....
03753   else if (m_strMimeType == "application/pkix-cert" ||
03754            m_strMimeType == "application/binary-certificate")
03755   {
03756      m_strMimeType = QString::fromLatin1("application/x-x509-ca-cert");
03757   }
03758 
03759   // Prefer application/x-tgz or x-gzpostscript over application/x-gzip.
03760   else if (m_strMimeType == "application/x-gzip")
03761   {
03762      if ((m_request.url.path().right(7) == ".tar.gz") ||
03763          (m_request.url.path().right(4) == ".tar"))
03764         m_strMimeType = QString::fromLatin1("application/x-tgz");
03765      if ((m_request.url.path().right(6) == ".ps.gz"))
03766         m_strMimeType = QString::fromLatin1("application/x-gzpostscript");
03767   }
03768 
03769   // Some webservers say "text/plain" when they mean "application/x-bzip2"
03770   else if ((m_strMimeType == "text/plain") || (m_strMimeType == "application/octet-stream"))
03771   {
03772      QString ext = m_request.url.path().right(4).upper();
03773      if (ext == ".BZ2")
03774         m_strMimeType = QString::fromLatin1("application/x-bzip2");
03775      else if (ext == ".PEM")
03776         m_strMimeType = QString::fromLatin1("application/x-x509-ca-cert");
03777      else if (ext == ".SWF")
03778         m_strMimeType = QString::fromLatin1("application/x-shockwave-flash");
03779      else if (ext == ".PLS")
03780         m_strMimeType = QString::fromLatin1("audio/x-scpls");
03781      else if (ext == ".WMV")
03782         m_strMimeType = QString::fromLatin1("video/x-ms-wmv");
03783   }
03784 
03785 #if 0
03786   // Even if we can't rely on content-length, it seems that we should
03787   // never get more data than content-length. Maybe less, if the
03788   // content-length refers to the unzipped data.
03789   if (!m_qContentEncodings.isEmpty())
03790   {
03791      // If we still have content encoding we can't rely on the Content-Length.
03792      m_iSize = NO_SIZE;
03793   }
03794 #endif
03795 
03796   if( !dispositionType.isEmpty() )
03797   {
03798     kdDebug(7113) << "(" << m_pid << ") Setting Content-Disposition type to: "
03799                   << dispositionType << endl;
03800     setMetaData("content-disposition-type", dispositionType);
03801   }
03802   if( !dispositionFilename.isEmpty() )
03803   {
03804     kdDebug(7113) << "(" << m_pid << ") Setting Content-Disposition filename to: "
03805                   << dispositionFilename << endl;
03806     // ### KDE4:  setting content-disposition to filename for pre 3.5.2 compatability
03807     setMetaData("content-disposition", dispositionFilename);
03808     setMetaData("content-disposition-filename", dispositionFilename);
03809   }
03810 
03811   if (!m_request.lastModified.isEmpty())
03812     setMetaData("modified", m_request.lastModified);
03813 
03814   if (!mayCache)
03815   {
03816     setMetaData("no-cache", "true");
03817     setMetaData("expire-date", "1"); // Expired
03818   }
03819   else
03820   {
03821     QString tmp;
03822     tmp.setNum(expireDate);
03823     setMetaData("expire-date", tmp);
03824     tmp.setNum(time(0)); // Cache entry will be created shortly.
03825     setMetaData("cache-creation-date", tmp);
03826   }
03827 
03828   // Let the app know about the mime-type iff this is not
03829   // a redirection and the mime-type string is not empty.
03830   if (locationStr.isEmpty() && (!m_strMimeType.isEmpty() ||
03831       m_request.method == HTTP_HEAD))
03832   {
03833     kdDebug(7113) << "(" << m_pid << ") Emitting mimetype " << m_strMimeType << endl;
03834     mimeType( m_strMimeType );
03835   }
03836 
03837   // Do not move send response header before any redirection as it seems
03838   // to screw up some sites. See BR# 150904.
03839   forwardHttpResponseHeader();
03840 
03841   if (m_request.method == HTTP_HEAD)
03842      return true;
03843 
03844   // Do we want to cache this request?
03845   if (m_request.bUseCache)
03846   {
03847      ::unlink( QFile::encodeName(m_request.cef));
03848      if ( m_request.bCachedWrite && !m_strMimeType.isEmpty() )
03849      {
03850         // Check...
03851         createCacheEntry(m_strMimeType, expireDate); // Create a cache entry
03852         if (!m_request.fcache)
03853         {
03854           m_request.bCachedWrite = false; // Error creating cache entry.
03855           kdDebug(7113) << "(" << m_pid << ") Error creating cache entry for " << m_request.url.url()<<"!\n";
03856         }
03857         m_request.expireDate = expireDate;
03858         m_maxCacheSize = config()->readNumEntry("MaxCacheSize", DEFAULT_MAX_CACHE_SIZE) / 2;
03859      }
03860   }
03861 
03862   if (m_request.bCachedWrite && !m_strMimeType.isEmpty())
03863     kdDebug(7113) << "(" << m_pid << ") Cache, adding \"" << m_request.url.url() << "\"" << endl;
03864   else if (m_request.bCachedWrite && m_strMimeType.isEmpty())
03865     kdDebug(7113) << "(" << m_pid << ") Cache, pending \"" << m_request.url.url() << "\"" << endl;
03866   else
03867     kdDebug(7113) << "(" << m_pid << ") Cache, not adding \"" << m_request.url.url() << "\"" << endl;
03868   return true;
03869 }
03870 
03871 
03872 void HTTPProtocol::addEncoding(QString encoding, QStringList &encs)
03873 {
03874   encoding = encoding.stripWhiteSpace().lower();
03875   // Identity is the same as no encoding
03876   if (encoding == "identity") {
03877     return;
03878   } else if (encoding == "8bit") {
03879     // Strange encoding returned by http://linac.ikp.physik.tu-darmstadt.de
03880     return;
03881   } else if (encoding == "chunked") {
03882     m_bChunked = true;
03883     // Anyone know of a better way to handle unknown sizes possibly/ideally with unsigned ints?
03884     //if ( m_cmd != CMD_COPY )
03885       m_iSize = NO_SIZE;
03886   } else if ((encoding == "x-gzip") || (encoding == "gzip")) {
03887     encs.append(QString::fromLatin1("gzip"));
03888   } else if ((encoding == "x-bzip2") || (encoding == "bzip2")) {
03889     encs.append(QString::fromLatin1("bzip2")); // Not yet supported!
03890   } else if ((encoding == "x-deflate") || (encoding == "deflate")) {
03891     encs.append(QString::fromLatin1("deflate"));
03892   } else {
03893     kdDebug(7113) << "(" << m_pid << ") Unknown encoding encountered.  "
03894                     << "Please write code. Encoding = \"" << encoding
03895                     << "\"" << endl;
03896   }
03897 }
03898 
03899 bool HTTPProtocol::sendBody()
03900 {
03901   int result=-1;
03902   int length=0;
03903 
03904   infoMessage( i18n( "Requesting data to send" ) );
03905 
03906   // m_bufPOST will NOT be empty iff authentication was required before posting
03907   // the data OR a re-connect is requested from ::readHeader because the
03908   // connection was lost for some reason.
03909   if ( !m_bufPOST.isNull() )
03910   {
03911     kdDebug(7113) << "(" << m_pid << ") POST'ing saved data..." << endl;
03912 
03913     result = 0;
03914     length = m_bufPOST.size();
03915   }
03916   else
03917   {
03918     kdDebug(7113) << "(" << m_pid << ") POST'ing live data..." << endl;
03919 
03920     QByteArray buffer;
03921     int old_size;
03922 
03923     m_bufPOST.resize(0);
03924     do
03925     {
03926       dataReq(); // Request for data
03927       result = readData( buffer );
03928       if ( result > 0 )
03929       {
03930         length += result;
03931         old_size = m_bufPOST.size();
03932         m_bufPOST.resize( old_size+result );
03933         memcpy( m_bufPOST.data()+ old_size, buffer.data(), buffer.size() );
03934         buffer.resize(0);
03935       }
03936     } while ( result > 0 );
03937   }
03938 
03939   if ( result < 0 )
03940   {
03941     error( ERR_ABORTED, m_request.hostname );
03942     return false;
03943   }
03944 
03945   infoMessage( i18n( "Sending data to %1" ).arg( m_request.hostname ) );
03946 
03947   QString size = QString ("Content-Length: %1\r\n\r\n").arg(length);
03948   kdDebug( 7113 ) << "(" << m_pid << ")" << size << endl;
03949 
03950   // Send the content length...
03951   bool sendOk = (write(size.latin1(), size.length()) == (ssize_t) size.length());
03952   if (!sendOk)
03953   {
03954     kdDebug( 7113 ) << "(" << m_pid << ") Connection broken when sending "
03955                     << "content length: (" << m_state.hostname << ")" << endl;
03956     error( ERR_CONNECTION_BROKEN, m_state.hostname );
03957     return false;
03958   }
03959 
03960   // Send the data...
03961   // kdDebug( 7113 ) << "(" << m_pid << ") POST DATA: " << QCString(m_bufPOST) << endl;
03962   sendOk = (write(m_bufPOST.data(), m_bufPOST.size()) == (ssize_t) m_bufPOST.size());
03963   if (!sendOk)
03964   {
03965     kdDebug(7113) << "(" << m_pid << ") Connection broken when sending message body: ("
03966                   << m_state.hostname << ")" << endl;
03967     error( ERR_CONNECTION_BROKEN, m_state.hostname );
03968     return false;
03969   }
03970 
03971   return true;
03972 }
03973 
03974 void HTTPProtocol::httpClose( bool keepAlive )
03975 {
03976   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpClose" << endl;
03977 
03978   if (m_request.fcache)
03979   {
03980      fclose(m_request.fcache);
03981      m_request.fcache = 0;
03982      if (m_request.bCachedWrite)
03983      {
03984         QString filename = m_request.cef + ".new";
03985         ::unlink( QFile::encodeName(filename) );
03986      }
03987   }
03988 
03989   // Only allow persistent connections for GET requests.
03990   // NOTE: we might even want to narrow this down to non-form
03991   // based submit requests which will require a meta-data from
03992   // khtml.
03993   if (keepAlive && (!m_bUseProxy ||
03994       m_bPersistentProxyConnection || m_bIsTunneled))
03995   {
03996     if (!m_keepAliveTimeout)
03997        m_keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT;
03998     else if (m_keepAliveTimeout > 2*DEFAULT_KEEP_ALIVE_TIMEOUT)
03999        m_keepAliveTimeout = 2*DEFAULT_KEEP_ALIVE_TIMEOUT;
04000 
04001     kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpClose: keep alive (" << m_keepAliveTimeout << ")" << endl;
04002     QByteArray data;
04003     QDataStream stream( data, IO_WriteOnly );
04004     stream << int(99); // special: Close connection
04005     setTimeoutSpecialCommand(m_keepAliveTimeout, data);
04006     return;
04007   }
04008 
04009   httpCloseConnection();
04010 }
04011 
04012 void HTTPProtocol::closeConnection()
04013 {
04014   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::closeConnection" << endl;
04015   httpCloseConnection ();
04016 }
04017 
04018 void HTTPProtocol::httpCloseConnection ()
04019 {
04020   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpCloseConnection" << endl;
04021   m_bIsTunneled = false;
04022   m_bKeepAlive = false;
04023   closeDescriptor();
04024   setTimeoutSpecialCommand(-1); // Cancel any connection timeout
04025 }
04026 
04027 void HTTPProtocol::slave_status()
04028 {
04029   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::slave_status" << endl;
04030 
04031   if ( m_iSock != -1 && !isConnectionValid() )
04032      httpCloseConnection();
04033 
04034   slaveStatus( m_state.hostname, (m_iSock != -1) );
04035 }
04036 
04037 void HTTPProtocol::mimetype( const KURL& url )
04038 {
04039   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::mimetype: "
04040                 << url.prettyURL() << endl;
04041 
04042   if ( !checkRequestURL( url ) )
04043     return;
04044 
04045   m_request.method = HTTP_HEAD;
04046   m_request.path = url.path();
04047   m_request.query = url.query();
04048   m_request.cache = CC_Cache;
04049   m_request.doProxy = m_bUseProxy;
04050 
04051   retrieveHeader();
04052 
04053   kdDebug(7113) << "(" << m_pid << ") http: mimetype = " << m_strMimeType
04054                 << endl;
04055 }
04056 
04057 void HTTPProtocol::special( const QByteArray &data )
04058 {
04059   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::special" << endl;
04060 
04061   int tmp;
04062   QDataStream stream(data, IO_ReadOnly);
04063 
04064   stream >> tmp;
04065   switch (tmp) {
04066     case 1: // HTTP POST
04067     {
04068       KURL url;
04069       stream >> url;
04070       post( url );
04071       break;
04072     }
04073     case 2: // cache_update
04074     {
04075       KURL url;
04076       bool no_cache;
04077       time_t expireDate;
04078       stream >> url >> no_cache >> expireDate;
04079       cacheUpdate( url, no_cache, expireDate );
04080       break;
04081     }
04082     case 5: // WebDAV lock
04083     {
04084       KURL url;
04085       QString scope, type, owner;
04086       stream >> url >> scope >> type >> owner;
04087       davLock( url, scope, type, owner );
04088       break;
04089     }
04090     case 6: // WebDAV unlock
04091     {
04092       KURL url;
04093       stream >> url;
04094       davUnlock( url );
04095       break;
04096     }
04097     case 7: // Generic WebDAV
04098     {
04099       KURL url;
04100       int method;
04101       stream >> url >> method;
04102       davGeneric( url, (KIO::HTTP_METHOD) method );
04103       break;
04104     }
04105     case 99: // Close Connection
04106     {
04107       httpCloseConnection();
04108       break;
04109     }
04110     default:
04111       // Some command we don't understand.
04112       // Just ignore it, it may come from some future version of KDE.
04113       break;
04114   }
04115 }
04116 
04120 int HTTPProtocol::readChunked()
04121 {
04122   if ((m_iBytesLeft == 0) || (m_iBytesLeft == NO_SIZE))
04123   {
04124      setRewindMarker();
04125 
04126      m_bufReceive.resize(4096);
04127 
04128      if (!gets(m_bufReceive.data(), m_bufReceive.size()-1))
04129      {
04130        kdDebug(7113) << "(" << m_pid << ") gets() failure on Chunk header" << endl;
04131        return -1;
04132      }
04133      // We could have got the CRLF of the previous chunk.
04134      // If so, try again.
04135      if (m_bufReceive[0] == '\0')
04136      {
04137         if (!gets(m_bufReceive.data(), m_bufReceive.size()-1))
04138         {
04139            kdDebug(7113) << "(" << m_pid << ") gets() failure on Chunk header" << endl;
04140            return -1;
04141         }
04142      }
04143 
04144      // m_bEOF is set to true when read called from gets returns 0. For chunked reading 0
04145      // means end of chunked transfer and not error. See RFC 2615 section 3.6.1
04146      #if 0
04147      if (m_bEOF)
04148      {
04149         kdDebug(7113) << "(" << m_pid << ") EOF on Chunk header" << endl;
04150         return -1;
04151      }
04152      #endif
04153 
04154      long long trunkSize = STRTOLL(m_bufReceive.data(), 0, 16);
04155      if (trunkSize < 0)
04156      {
04157         kdDebug(7113) << "(" << m_pid << ") Negative chunk size" << endl;
04158         return -1;
04159      }
04160      m_iBytesLeft = trunkSize;
04161 
04162      // kdDebug(7113) << "(" << m_pid << ") Chunk size = " << m_iBytesLeft << " bytes" << endl;
04163 
04164      if (m_iBytesLeft == 0)
04165      {
04166        // Last chunk.
04167        // Skip trailers.
04168        do {
04169          // Skip trailer of last chunk.
04170          if (!gets(m_bufReceive.data(), m_bufReceive.size()-1))
04171          {
04172            kdDebug(7113) << "(" << m_pid << ") gets() failure on Chunk trailer" << endl;
04173            return -1;
04174          }
04175          // kdDebug(7113) << "(" << m_pid << ") Chunk trailer = \"" << m_bufReceive.data() << "\"" << endl;
04176        }
04177        while (strlen(m_bufReceive.data()) != 0);
04178 
04179        return 0;
04180      }
04181   }
04182 
04183   int bytesReceived = readLimited();
04184   if (!m_iBytesLeft)
04185      m_iBytesLeft = NO_SIZE; // Don't stop, continue with next chunk
04186 
04187   // kdDebug(7113) << "(" << m_pid << ") readChunked: BytesReceived=" << bytesReceived << endl;
04188   return bytesReceived;
04189 }
04190 
04191 int HTTPProtocol::readLimited()
04192 {
04193   if (!m_iBytesLeft)
04194     return 0;
04195 
04196   m_bufReceive.resize(4096);
04197 
04198   int bytesReceived;
04199   int bytesToReceive;
04200 
04201   if (m_iBytesLeft > m_bufReceive.size())
04202      bytesToReceive = m_bufReceive.size();
04203   else
04204      bytesToReceive = m_iBytesLeft;
04205 
04206   bytesReceived = read(m_bufReceive.data(), bytesToReceive);
04207 
04208   if (bytesReceived <= 0)
04209      return -1; // Error: connection lost
04210 
04211   m_iBytesLeft -= bytesReceived;
04212   return bytesReceived;
04213 }
04214 
04215 int HTTPProtocol::readUnlimited()
04216 {
04217   if (m_bKeepAlive)
04218   {
04219      kdDebug(7113) << "(" << m_pid << ") Unbounded datastream on a Keep "
04220                      << "alive connection!" << endl;
04221      m_bKeepAlive = false;
04222   }
04223 
04224   m_bufReceive.resize(4096);
04225 
04226   int result = read(m_bufReceive.data(), m_bufReceive.size());
04227   if (result > 0)
04228      return result;
04229 
04230   m_bEOF = true;
04231   m_iBytesLeft = 0;
04232   return 0;
04233 }
04234 
04235 void HTTPProtocol::slotData(const QByteArray &_d)
04236 {
04237    if (!_d.size())
04238    {
04239       m_bEOD = true;
04240       return;
04241    }
04242 
04243    if (m_iContentLeft != NO_SIZE)
04244    {
04245       if (m_iContentLeft >= _d.size())
04246          m_iContentLeft -= _d.size();
04247       else
04248          m_iContentLeft = NO_SIZE;
04249    }
04250 
04251    QByteArray d = _d;
04252    if ( !m_dataInternal )
04253    {
04254       // If a broken server does not send the mime-type,
04255       // we try to id it from the content before dealing
04256       // with the content itself.
04257       if ( m_strMimeType.isEmpty() && !m_bRedirect &&
04258            !( m_responseCode >= 300 && m_responseCode <=399) )
04259       {
04260         kdDebug(7113) << "(" << m_pid << ") Determining mime-type from content..." << endl;
04261         int old_size = m_mimeTypeBuffer.size();
04262         m_mimeTypeBuffer.resize( old_size + d.size() );
04263         memcpy( m_mimeTypeBuffer.data() + old_size, d.data(), d.size() );
04264         if ( (m_iBytesLeft != NO_SIZE) && (m_iBytesLeft > 0)
04265              && (m_mimeTypeBuffer.size() < 1024) )
04266         {
04267           m_cpMimeBuffer = true;
04268           return;   // Do not send up the data since we do not yet know its mimetype!
04269         }
04270 
04271         kdDebug(7113) << "(" << m_pid << ") Mimetype buffer size: " << m_mimeTypeBuffer.size()
04272                       << endl;
04273 
04274         KMimeMagicResult *result;
04275         result = KMimeMagic::self()->findBufferFileType( m_mimeTypeBuffer,
04276                                                          m_request.url.fileName() );
04277         if( result )
04278         {
04279           m_strMimeType = result->mimeType();
04280           kdDebug(7113) << "(" << m_pid << ") Mimetype from content: "
04281                         << m_strMimeType << endl;
04282         }
04283 
04284         if ( m_strMimeType.isEmpty() )
04285         {
04286           m_strMimeType = QString::fromLatin1( DEFAULT_MIME_TYPE );
04287           kdDebug(7113) << "(" << m_pid << ") Using default mimetype: "
04288                         <<  m_strMimeType << endl;
04289         }
04290 
04291         if ( m_request.bCachedWrite )
04292         {
04293           createCacheEntry( m_strMimeType, m_request.expireDate );
04294           if (!m_request.fcache)
04295             m_request.bCachedWrite = false;
04296         }
04297 
04298         if ( m_cpMimeBuffer )
04299         {
04300           // Do not make any assumption about the state of the QByteArray we received.
04301           // Fix the crash described by BR# 130104.
04302           d.detach();
04303           d.resize(0);
04304           d.resize(m_mimeTypeBuffer.size());
04305           memcpy( d.data(), m_mimeTypeBuffer.data(),
04306                   d.size() );
04307         }
04308         mimeType(m_strMimeType);
04309         m_mimeTypeBuffer.resize(0);
04310       }
04311 
04312       data( d );
04313       if (m_request.bCachedWrite && m_request.fcache)
04314          writeCacheEntry(d.data(), d.size());
04315    }
04316    else
04317    {
04318       uint old_size = m_bufWebDavData.size();
04319       m_bufWebDavData.resize (old_size + d.size());
04320       memcpy (m_bufWebDavData.data() + old_size, d.data(), d.size());
04321    }
04322 }
04323 
04333 bool HTTPProtocol::readBody( bool dataInternal /* = false */ )
04334 {
04335   if (m_responseCode == 204)
04336      return true;
04337 
04338   m_bEOD = false;
04339   // Note that when dataInternal is true, we are going to:
04340   // 1) save the body data to a member variable, m_bufWebDavData
04341   // 2) _not_ advertise the data, speed, size, etc., through the
04342   //    corresponding functions.
04343   // This is used for returning data to WebDAV.
04344   m_dataInternal = dataInternal;
04345   if ( dataInternal )
04346     m_bufWebDavData.resize (0);
04347 
04348   // Check if we need to decode the data.
04349   // If we are in copy mode, then use only transfer decoding.
04350   bool useMD5 = !m_sContentMD5.isEmpty();
04351 
04352   // Deal with the size of the file.
04353   KIO::filesize_t sz = m_request.offset;
04354   if ( sz )
04355     m_iSize += sz;
04356 
04357   // Update the application with total size except when
04358   // it is compressed, or when the data is to be handled
04359   // internally (webDAV).  If compressed we have to wait
04360   // until we uncompress to find out the actual data size
04361   if ( !dataInternal ) {
04362     if ( (m_iSize > 0) && (m_iSize != NO_SIZE)) {
04363        totalSize(m_iSize);
04364        infoMessage( i18n( "Retrieving %1 from %2...").arg(KIO::convertSize(m_iSize))
04365          .arg( m_request.hostname ) );
04366     }
04367     else
04368     {
04369        totalSize ( 0 );
04370     }
04371   }
04372   else
04373     infoMessage( i18n( "Retrieving from %1..." ).arg( m_request.hostname ) );
04374 
04375   if (m_request.bCachedRead)
04376   {
04377   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readBody: read data from cache!" << endl;
04378     m_request.bCachedWrite = false;
04379 
04380     char buffer[ MAX_IPC_SIZE ];
04381 
04382     m_iContentLeft = NO_SIZE;
04383 
04384     // Jippie! It's already in the cache :-)
04385     while (!feof(m_request.fcache) && !ferror(m_request.fcache))
04386     {
04387       int nbytes = fread( buffer, 1, MAX_IPC_SIZE, m_request.fcache);
04388 
04389       if (nbytes > 0)
04390       {
04391         m_bufReceive.setRawData( buffer, nbytes);
04392         slotData( m_bufReceive );
04393         m_bufReceive.resetRawData( buffer, nbytes );
04394         sz += nbytes;
04395       }
04396     }
04397 
04398     m_bufReceive.resize( 0 );
04399 
04400     if ( !dataInternal )
04401     {
04402       processedSize( sz );
04403       data( QByteArray() );
04404     }
04405 
04406     return true;
04407   }
04408 
04409 
04410   if (m_iSize != NO_SIZE)
04411     m_iBytesLeft = m_iSize - sz;
04412   else
04413     m_iBytesLeft = NO_SIZE;
04414 
04415   m_iContentLeft = m_iBytesLeft;
04416 
04417   if (m_bChunked)
04418     m_iBytesLeft = NO_SIZE;
04419 
04420   kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readBody: retrieve data. "
04421                 << KIO::number(m_iBytesLeft) << " left." << endl;
04422 
04423   // Main incoming loop...  Gather everything while we can...
04424   m_cpMimeBuffer = false;
04425   m_mimeTypeBuffer.resize(0);
04426   struct timeval last_tv;
04427   gettimeofday( &last_tv, 0L );
04428 
04429   HTTPFilterChain chain;
04430 
04431   QObject::connect(&chain, SIGNAL(output(const QByteArray &)),
04432           this, SLOT(slotData(const QByteArray &)));
04433   QObject::connect(&chain, SIGNAL(error(int, const QString &)),
04434           this, SLOT(error(int, const QString &)));
04435 
04436    // decode all of the transfer encodings
04437   while (!m_qTransferEncodings.isEmpty())
04438   {
04439     QString enc = m_qTransferEncodings.last();
04440     m_qTransferEncodings.remove(m_qTransferEncodings.fromLast());
04441     if ( enc == "gzip" )
04442       chain.addFilter(new HTTPFilterGZip);
04443     else if ( enc == "deflate" )
04444       chain.addFilter(new HTTPFilterDeflate);
04445   }
04446 
04447   // From HTTP 1.1 Draft 6:
04448   // The MD5 digest is computed based on the content of the entity-body,
04449   // including any content-coding that has been applied, but not including
04450   // any transfer-encoding applied to the message-body. If the message is
04451   // received with a transfer-encoding, that encoding MUST be removed
04452   // prior to checking the Content-MD5 value against the received entity.
04453   HTTPFilterMD5 *md5Filter = 0;
04454   if ( useMD5 )
04455   {
04456      md5Filter = new HTTPFilterMD5;
04457      chain.addFilter(md5Filter);
04458   }
04459 
04460   // now decode all of the content encodings
04461   // -- Why ?? We are not
04462   // -- a proxy server, be a client side implementation!!  The applications
04463   // -- are capable of determinig how to extract the encoded implementation.
04464   // WB: That's a misunderstanding. We are free to remove the encoding.
04465   // WB: Some braindead www-servers however, give .tgz files an encoding
04466   // WB: of "gzip" (or even "x-gzip") and a content-type of "applications/tar"
04467   // WB: They shouldn't do that. We can work around that though...
04468   while (!m_qContentEncodings.isEmpty())
04469   {
04470     QString enc = m_qContentEncodings.last();
04471     m_qContentEncodings.remove(m_qContentEncodings.fromLast());
04472     if ( enc == "gzip" )
04473       chain.addFilter(new HTTPFilterGZip);
04474     else if ( enc == "deflate" )
04475       chain.addFilter(new HTTPFilterDeflate);
04476   }
04477 
04478   while (!m_bEOF)
04479   {
04480     int bytesReceived;
04481 
04482     if (m_bChunked)
04483        bytesReceived = readChunked();
04484     else if (m_iSize != NO_SIZE)
04485        bytesReceived = readLimited();
04486     else
04487        bytesReceived = readUnlimited();
04488 
04489     // make sure that this wasn't an error, first
04490     // kdDebug(7113) << "(" << (int) m_pid << ") readBody: bytesReceived: "
04491     //              << (int) bytesReceived << " m_iSize: " << (int) m_iSize << " Chunked: "
04492     //              << (int) m_bChunked << " BytesLeft: "<< (int) m_iBytesLeft << endl;
04493     if (bytesReceived == -1)
04494     {
04495       if (m_iContentLeft == 0)
04496       {
04497          // gzip'ed data sometimes reports a too long content-length.
04498          // (The length of the unzipped data)
04499          m_iBytesLeft = 0;
04500          break;
04501       }
04502       // Oh well... log an error and bug out
04503       kdDebug(7113) << "(" << m_pid << ") readBody: bytesReceived==-1 sz=" << (int)sz
04504                     << " Connnection broken !" << endl;
04505       error(ERR_CONNECTION_BROKEN, m_state.hostname);
04506       return false;
04507     }
04508 
04509     // I guess that nbytes == 0 isn't an error.. but we certainly
04510     // won't work with it!
04511     if (bytesReceived > 0)
04512     {
04513       // Important: truncate the buffer to the actual size received!
04514       // Otherwise garbage will be passed to the app
04515       m_bufReceive.truncate( bytesReceived );
04516 
04517       chain.slotInput(m_bufReceive);
04518 
04519       if (m_bError)
04520          return false;
04521 
04522       sz += bytesReceived;
04523       if (!dataInternal)
04524         processedSize( sz );
04525     }
04526     m_bufReceive.resize(0); // res
04527 
04528     if (m_iBytesLeft && m_bEOD && !m_bChunked)
04529     {
04530       // gzip'ed data sometimes reports a too long content-length.
04531       // (The length of the unzipped data)
04532       m_iBytesLeft = 0;
04533     }
04534 
04535     if (m_iBytesLeft == 0)
04536     {
04537       kdDebug(7113) << "("<<m_pid<<") EOD received! Left = "<< KIO::number(m_iBytesLeft) << endl;
04538       break;
04539     }
04540   }
04541   chain.slotInput(QByteArray()); // Flush chain.
04542 
04543   if ( useMD5 )
04544   {
04545     QString calculatedMD5 = md5Filter->md5();
04546 
04547     if ( m_sContentMD5 == calculatedMD5 )
04548       kdDebug(7113) << "(" << m_pid << ") MD5 checksum MATCHED!!" << endl;
04549     else
04550       kdDebug(7113) << "(" << m_pid << ") MD5 checksum MISMATCH! Expected: "
04551                     << calculatedMD5 << ", Got: " << m_sContentMD5 << endl;
04552   }
04553 
04554   // Close cache entry
04555   if (m_iBytesLeft == 0)
04556   {
04557      if (m_request.bCachedWrite && m_request.fcache)
04558         closeCacheEntry();
04559      else if (m_request.bCachedWrite)
04560         kdDebug(7113) << "(" << m_pid << ") no cache file!\n";
04561   }
04562   else
04563   {
04564     kdDebug(7113) << "(" << m_pid << ") still "<< KIO::number(m_iBytesLeft)
04565                   << " bytes left! can't close cache entry!\n";
04566   }
04567 
04568   if (sz <= 1)
04569   {
04570     /* kdDebug(7113) << "(" << m_pid << ") readBody: sz = " << KIO::number(sz)
04571                      << ", responseCode =" << m_responseCode << endl; */
04572     if (m_responseCode >= 500 && m_responseCode <= 599)
04573       error(ERR_INTERNAL_SERVER, m_state.hostname);
04574     else if (m_responseCode >= 400 && m_responseCode <= 499)
04575       error(ERR_DOES_NOT_EXIST, m_state.hostname);
04576   }
04577 
04578   if (!dataInternal)
04579     data( QByteArray() );
04580 
04581   return true;
04582 }
04583 
04584 
04585 void HTTPProtocol::error( int _err, const QString &_text )
04586 {
04587   httpClose(false);
04588 
04589   if (!m_request.id.isEmpty())
04590   {
04591     forwardHttpResponseHeader();
04592     sendMetaData();
04593   }
04594 
04595   // Clear of the temporary POST buffer if it is not empty...
04596   if (!m_bufPOST.isEmpty())
04597   {
04598     m_bufPOST.resize(0);
04599     kdDebug(7113) << "(" << m_pid << ") HTTP::retreiveHeader: Cleared POST "
04600                      "buffer..." << endl;
04601   }
04602 
04603   SlaveBase::error( _err, _text );
04604   m_bError = true;
04605 }
04606 
04607 
04608 void HTTPProtocol::addCookies( const QString &url, const QCString &cookieHeader )
04609 {
04610    long windowId = m_request.window.toLong();
04611    QByteArray params;
04612    QDataStream stream(params, IO_WriteOnly);
04613    stream << url << cookieHeader << windowId;
04614 
04615    kdDebug(7113) << "(" << m_pid << ") " << cookieHeader << endl;
04616    kdDebug(7113) << "(" << m_pid << ") " << "Window ID: "
04617                  << windowId << ", for host = " << url << endl;
04618 
04619    if ( !dcopClient()->send( "kded", "kcookiejar", "addCookies(QString,QCString,long int)", params ) )
04620    {
04621       kdWarning(7113) << "(" << m_pid << ") Can't communicate with kded_kcookiejar!" << endl;
04622    }
04623 }
04624 
04625 QString HTTPProtocol::findCookies( const QString &url)
04626 {
04627   QCString replyType;
04628   QByteArray params;
04629   QByteArray reply;
04630   QString result;
04631 
04632   long windowId = m_request.window.toLong();
04633   result = QString::null;
04634   QDataStream stream(params, IO_WriteOnly);
04635   stream << url << windowId;
04636 
04637   if ( !dcopClient()->call( "kded", "kcookiejar", "findCookies(QString,long int)",
04638                             params, replyType, reply ) )
04639   {
04640      kdWarning(7113) << "(" << m_pid << ") Can't communicate with kded_kcookiejar!" << endl;
04641      return result;
04642   }
04643   if ( replyType == "QString" )
04644   {
04645      QDataStream stream2( reply, IO_ReadOnly );
04646      stream2 >> result;
04647   }
04648   else
04649   {
04650      kdError(7113) << "(" << m_pid << ") DCOP function findCookies(...) returns "
04651                           << replyType << ", expected QString" << endl;
04652   }
04653   return result;
04654 }
04655 
04656 /******************************* CACHING CODE ****************************/
04657 
04658 
04659 void HTTPProtocol::cacheUpdate( const KURL& url, bool no_cache, time_t expireDate)
04660 {
04661   if ( !checkRequestURL( url ) )
04662       return;
04663 
04664   m_request.path = url.path();
04665   m_request.query = url.query();
04666   m_request.cache = CC_Reload;
04667   m_request.doProxy = m_bUseProxy;
04668 
04669   if (no_cache)
04670   {
04671      m_request.fcache = checkCacheEntry( );
04672      if (m_request.fcache)
04673      {
04674        fclose(m_request.fcache);
04675        m_request.fcache = 0;
04676        ::unlink( QFile::encodeName(m_request.cef) );
04677      }
04678   }
04679   else
04680   {
04681      updateExpireDate( expireDate );
04682   }
04683   finished();
04684 }
04685 
04686 // !START SYNC!
04687 // The following code should be kept in sync
04688 // with the code in http_cache_cleaner.cpp
04689 
04690 FILE* HTTPProtocol::checkCacheEntry( bool readWrite)
04691 {
04692    const QChar separator = '_';
04693 
04694    QString CEF = m_request.path;
04695 
04696    int p = CEF.find('/');
04697 
04698    while(p != -1)
04699    {
04700       CEF[p] = separator;
04701       p = CEF.find('/', p);
04702    }
04703 
04704    QString host = m_request.hostname.lower();
04705    CEF = host + CEF + '_';
04706 
04707    QString dir = m_strCacheDir;
04708    if (dir[dir.length()-1] != '/')
04709       dir += "/";
04710 
04711    int l = host.length();
04712    for(int i = 0; i < l; i++)
04713    {
04714       if (host[i].isLetter() && (host[i] != 'w'))
04715       {
04716          dir += host[i];
04717          break;
04718       }
04719    }
04720    if (dir[dir.length()-1] == '/')
04721       dir += "0";
04722 
04723    unsigned long hash = 0x00000000;
04724    QCString u = m_request.url.url().latin1();
04725    for(int i = u.length(); i--;)
04726    {
04727       hash = (hash * 12211 + u[i]) % 2147483563;
04728    }
04729 
04730    QString hashString;
04731    hashString.sprintf("%08lx", hash);
04732 
04733    CEF = CEF + hashString;
04734 
04735    CEF = dir + "/" + CEF;
04736 
04737    m_request.cef = CEF;
04738 
04739    const char *mode = (readWrite ? "r+" : "r");
04740 
04741    FILE *fs = fopen( QFile::encodeName(CEF), mode); // Open for reading and writing
04742    if (!fs)
04743       return 0;
04744 
04745    char buffer[401];
04746    bool ok = true;
04747 
04748   // CacheRevision
04749   if (ok && (!fgets(buffer, 400, fs)))
04750       ok = false;
04751    if (ok && (strcmp(buffer, CACHE_REVISION) != 0))
04752       ok = false;
04753 
04754    time_t date;
04755    time_t currentDate = time(0);
04756 
04757    // URL
04758    if (ok && (!fgets(buffer, 400, fs)))
04759       ok = false;
04760    if (ok)
04761    {
04762       int l = strlen(buffer);
04763       if (l>0)
04764          buffer[l-1] = 0; // Strip newline
04765       if (m_request.url.url() != buffer)
04766       {
04767          ok = false; // Hash collision
04768       }
04769    }
04770 
04771    // Creation Date
04772    if (ok && (!fgets(buffer, 400, fs)))
04773       ok = false;
04774    if (ok)
04775    {
04776       date = (time_t) strtoul(buffer, 0, 10);
04777       m_request.creationDate = date;
04778       if (m_maxCacheAge && (difftime(currentDate, date) > m_maxCacheAge))
04779       {
04780          m_request.bMustRevalidate = true;
04781          m_request.expireDate = currentDate;
04782       }
04783    }
04784 
04785    // Expiration Date
04786    m_request.cacheExpireDateOffset = ftell(fs);
04787    if (ok && (!fgets(buffer, 400, fs)))
04788       ok = false;
04789    if (ok)
04790    {
04791       if (m_request.cache == CC_Verify)
04792       {
04793          date = (time_t) strtoul(buffer, 0, 10);
04794          // After the expire date we need to revalidate.
04795          if (!date || difftime(currentDate, date) >= 0)
04796             m_request.bMustRevalidate = true;
04797          m_request.expireDate = date;
04798       }
04799       else if (m_request.cache == CC_Refresh)
04800       {
04801          m_request.bMustRevalidate = true;
04802          m_request.expireDate = currentDate;
04803       }
04804    }
04805 
04806    // ETag
04807    if (ok && (!fgets(buffer, 400, fs)))
04808       ok = false;
04809    if (ok)
04810    {
04811       m_request.etag = QString(buffer).stripWhiteSpace();
04812    }
04813 
04814    // Last-Modified
04815    if (ok && (!fgets(buffer, 400, fs)))
04816       ok = false;
04817    if (ok)
04818    {
04819       m_request.lastModified = QString(buffer).stripWhiteSpace();
04820    }
04821 
04822    if (ok)
04823       return fs;
04824 
04825    fclose(fs);
04826    unlink( QFile::encodeName(CEF));
04827    return 0;
04828 }
04829 
04830 void HTTPProtocol::updateExpireDate(time_t expireDate, bool updateCreationDate)
04831 {
04832     bool ok = true;
04833 
04834     FILE *fs = checkCacheEntry(true);
04835     if (fs)
04836     {
04837         QString date;
04838         char buffer[401];
04839         time_t creationDate;
04840 
04841         fseek(fs, 0, SEEK_SET);
04842         if (ok && !fgets(buffer, 400, fs))
04843             ok = false;
04844         if (ok && !fgets(buffer, 400, fs))
04845             ok = false;
04846         long cacheCreationDateOffset = ftell(fs);
04847         if (ok && !fgets(buffer, 400, fs))
04848             ok = false;
04849         creationDate = strtoul(buffer, 0, 10);
04850         if (!creationDate)
04851             ok = false;
04852 
04853         if (updateCreationDate)
04854         {
04855            if (!ok || fseek(fs, cacheCreationDateOffset, SEEK_SET))
04856               return;
04857            QString date;
04858            date.setNum( time(0) );
04859            date = date.leftJustify(16);
04860            fputs(date.latin1(), fs);      // Creation date
04861            fputc('\n', fs);
04862         }
04863 
04864         if (expireDate>(30*365*24*60*60))
04865         {
04866             // expire date is a really a big number, it can't be
04867             // a relative date.
04868             date.setNum( expireDate );
04869         }
04870         else
04871         {
04872             // expireDate before 2000. those values must be
04873             // interpreted as relative expiration dates from
04874             // <META http-equiv="Expires"> tags.
04875             // so we have to scan the creation time and add
04876             // it to the expiryDate
04877             date.setNum( creationDate + expireDate );
04878         }
04879         date = date.leftJustify(16);
04880         if (!ok || fseek(fs, m_request.cacheExpireDateOffset, SEEK_SET))
04881             return;
04882         fputs(date.latin1(), fs);      // Expire date
04883         fseek(fs, 0, SEEK_END);
04884         fclose(fs);
04885     }
04886 }
04887 
04888 void HTTPProtocol::createCacheEntry( const QString &mimetype, time_t expireDate)
04889 {
04890    QString dir = m_request.cef;
04891    int p = dir.findRev('/');
04892    if (p == -1) return; // Error.
04893    dir.truncate(p);
04894 
04895    // Create file
04896    (void) ::mkdir( QFile::encodeName(dir), 0700 );
04897 
04898    QString filename = m_request.cef + ".new";  // Create a new cache entryexpireDate
04899 
04900 //   kdDebug( 7103 ) <<  "creating new cache entry: " << filename << endl;
04901 
04902    m_request.fcache = fopen( QFile::encodeName(filename), "w");
04903    if (!m_request.fcache)
04904    {
04905       kdWarning(7113) << "(" << m_pid << ")createCacheEntry: opening " << filename << " failed." << endl;
04906       return; // Error.
04907    }
04908 
04909    fputs(CACHE_REVISION, m_request.fcache);    // Revision
04910 
04911    fputs(m_request.url.url().latin1(), m_request.fcache);  // Url
04912    fputc('\n', m_request.fcache);
04913 
04914    QString date;
04915    m_request.creationDate = time(0);
04916    date.setNum( m_request.creationDate );
04917    date = date.leftJustify(16);
04918    fputs(date.latin1(), m_request.fcache);      // Creation date
04919    fputc('\n', m_request.fcache);
04920 
04921    date.setNum( expireDate );
04922    date = date.leftJustify(16);
04923    fputs(date.latin1(), m_request.fcache);      // Expire date
04924    fputc('\n', m_request.fcache);
04925 
04926    if (!m_request.etag.isEmpty())
04927       fputs(m_request.etag.latin1(), m_request.fcache);    //ETag
04928    fputc('\n', m_request.fcache);
04929 
04930    if (!m_request.lastModified.isEmpty())
04931       fputs(m_request.lastModified.latin1(), m_request.fcache);    // Last modified
04932    fputc('\n', m_request.fcache);
04933 
04934    fputs(mimetype.latin1(), m_request.fcache);  // Mimetype
04935    fputc('\n', m_request.fcache);
04936 
04937    if (!m_request.strCharset.isEmpty())
04938       fputs(m_request.strCharset.latin1(), m_request.fcache);    // Charset
04939    fputc('\n', m_request.fcache);
04940 
04941    return;
04942 }
04943 // The above code should be kept in sync
04944 // with the code in http_cache_cleaner.cpp
04945 // !END SYNC!
04946 
04947 void HTTPProtocol::writeCacheEntry( const char *buffer, int nbytes)
04948 {
04949    if (fwrite( buffer, nbytes, 1, m_request.fcache) != 1)
04950    {
04951       kdWarning(7113) << "(" << m_pid << ") writeCacheEntry: writing " << nbytes << " bytes failed." << endl;
04952       fclose(m_request.fcache);
04953       m_request.fcache = 0;
04954       QString filename = m_request.cef + ".new";
04955       ::unlink( QFile::encodeName(filename) );
04956       return;
04957    }
04958    long file_pos = ftell( m_request.fcache ) / 1024;
04959    if ( file_pos > m_maxCacheSize )
04960    {
04961       kdDebug(7113) << "writeCacheEntry: File size reaches " << file_pos
04962                     << "Kb, exceeds cache limits. (" << m_maxCacheSize << "Kb)" << endl;
04963       fclose(m_request.fcache);
04964       m_request.fcache = 0;
04965       QString filename = m_request.cef + ".new";
04966       ::unlink( QFile::encodeName(filename) );
04967       return;
04968    }
04969 }
04970 
04971 void HTTPProtocol::closeCacheEntry()
04972 {
04973    QString filename = m_request.cef + ".new";
04974    int result = fclose( m_request.fcache);
04975    m_request.fcache = 0;
04976    if (result == 0)
04977    {
04978       if (::rename( QFile::encodeName(filename), QFile::encodeName(m_request.cef)) == 0)
04979          return; // Success
04980 
04981       kdWarning(7113) << "(" << m_pid << ") closeCacheEntry: error renaming "
04982                       << "cache entry. (" << filename << " -> " << m_request.cef
04983                       << ")" << endl;
04984    }
04985 
04986    kdWarning(7113) << "(" << m_pid << ") closeCacheEntry: error closing cache "
04987                    << "entry. (" << filename<< ")" << endl;
04988 }
04989 
04990 void HTTPProtocol::cleanCache()
04991 {
04992    const time_t maxAge = DEFAULT_CLEAN_CACHE_INTERVAL; // 30 Minutes.
04993    bool doClean = false;
04994    QString cleanFile = m_strCacheDir;
04995    if (cleanFile[cleanFile.length()-1] != '/')
04996       cleanFile += "/";
04997    cleanFile += "cleaned";
04998 
04999    struct stat stat_buf;
05000 
05001    int result = ::stat(QFile::encodeName(cleanFile), &stat_buf);
05002    if (result == -1)
05003    {
05004       int fd = creat( QFile::encodeName(cleanFile), 0600);
05005       if (fd != -1)
05006       {
05007          doClean = true;
05008          ::close(fd);
05009       }
05010    }
05011    else
05012    {
05013       time_t age = (time_t) difftime( time(0), stat_buf.st_mtime );
05014       if (age > maxAge) //
05015         doClean = true;
05016    }
05017    if (doClean)
05018    {
05019       // Touch file.
05020       utime(QFile::encodeName(cleanFile), 0);
05021       KApplication::startServiceByDesktopPath("http_cache_cleaner.desktop");
05022    }
05023 }
05024 
05025 
05026 
05027 //**************************  AUTHENTICATION CODE ********************/
05028 
05029 
05030 void HTTPProtocol::configAuth( char *p, bool isForProxy )
05031 {
05032   HTTP_AUTH f = AUTH_None;
05033   const char *strAuth = p;
05034 
05035   if ( strncasecmp( p, "Basic", 5 ) == 0 )
05036   {
05037     f = AUTH_Basic;
05038     p += 5;
05039     strAuth = "Basic"; // Correct for upper-case variations.
05040   }
05041   else if ( strncasecmp (p, "Digest", 6) == 0 )
05042   {
05043     f = AUTH_Digest;
05044     memcpy((void *)p, "Digest", 6); // Correct for upper-case variations.
05045     p += 6;
05046   }
05047   else if (strncasecmp( p, "MBS_PWD_COOKIE", 14 ) == 0)
05048   {
05049     // Found on http://www.webscription.net/baen/default.asp
05050     f = AUTH_Basic;
05051     p += 14;
05052     strAuth = "Basic";
05053   }
05054 #ifdef HAVE_LIBGSSAPI
05055   else if ( strncasecmp( p, "Negotiate", 9 ) == 0 )
05056   {
05057     // if we get two 401 in a row let's assume for now that
05058     // Negotiate isn't working and ignore it
05059     if ( !isForProxy && !(m_responseCode == 401 && m_prevResponseCode == 401) )
05060     {
05061       f = AUTH_Negotiate;
05062       memcpy((void *)p, "Negotiate", 9); // Correct for upper-case variations.
05063       p += 9;
05064     };
05065   }
05066 #endif
05067   else if ( strncasecmp( p, "NTLM", 4 ) == 0 )
05068   {
05069     f = AUTH_NTLM;
05070     memcpy((void *)p, "NTLM", 4); // Correct for upper-case variations.
05071     p += 4;
05072     m_strRealm = "NTLM"; // set a dummy realm
05073   }
05074   else
05075   {
05076     kdWarning(7113) << "(" << m_pid << ") Unsupported or invalid authorization "
05077                     << "type requested" << endl;
05078     if (isForProxy)
05079       kdWarning(7113) << "(" << m_pid << ") Proxy URL: " << m_proxyURL << endl;
05080     else
05081       kdWarning(7113) << "(" << m_pid << ") URL: " << m_request.url << endl;
05082     kdWarning(7113) << "(" << m_pid << ") Request Authorization: " << p << endl;
05083   }
05084 
05085   /*
05086      This check ensures the following:
05087      1.) Rejection of any unknown/unsupported authentication schemes
05088      2.) Usage of the strongest possible authentication schemes if
05089          and when multiple Proxy-Authenticate or WWW-Authenticate
05090          header field is sent.
05091   */
05092   if (isForProxy)
05093   {
05094     if ((f == AUTH_None) ||
05095         ((m_iProxyAuthCount > 0) && (f < ProxyAuthentication)))
05096     {
05097       // Since I purposefully made the Proxy-Authentication settings
05098       // persistent to reduce the number of round-trips to kdesud we
05099       // have to take special care when an unknown/unsupported auth-
05100       // scheme is received. This check accomplishes just that...
05101       if ( m_iProxyAuthCount == 0)
05102         ProxyAuthentication = f;
05103       kdDebug(7113) << "(" << m_pid << ") Rejected proxy auth method: " << f << endl;
05104       return;
05105     }
05106     m_iProxyAuthCount++;
05107     kdDebug(7113) << "(" << m_pid << ") Accepted proxy auth method: " << f << endl;
05108   }
05109   else
05110   {
05111     if ((f == AUTH_None) ||
05112         ((m_iWWWAuthCount > 0) && (f < Authentication)))
05113     {
05114       kdDebug(7113) << "(" << m_pid << ") Rejected auth method: " << f << endl;
05115       return;
05116     }
05117     m_iWWWAuthCount++;
05118     kdDebug(7113) << "(" << m_pid << ") Accepted auth method: " << f << endl;
05119   }
05120 
05121 
05122   while (*p)
05123   {
05124     int i = 0;
05125     while( (*p == ' ') || (*p == ',') || (*p == '\t') ) { p++; }
05126     if ( strncasecmp( p, "realm=", 6 ) == 0 )
05127     {
05128       //for sites like lib.homelinux.org
05129       QTextCodec* oldCodec=QTextCodec::codecForCStrings();
05130       if (KGlobal::locale()->language().contains("ru"))
05131         QTextCodec::setCodecForCStrings(QTextCodec::codecForName("CP1251"));
05132 
05133       p += 6;
05134       if (*p == '"') p++;
05135       while( p[i] && p[i] != '"' ) i++;
05136       if( isForProxy )
05137         m_strProxyRealm = QString::fromAscii( p, i );
05138       else
05139         m_strRealm = QString::fromAscii( p, i );
05140 
05141       QTextCodec::setCodecForCStrings(oldCodec);
05142 
05143       if (!p[i]) break;
05144     }
05145     p+=(i+1);
05146   }
05147 
05148   if( isForProxy )
05149   {
05150     ProxyAuthentication = f;
05151     m_strProxyAuthorization = QString::fromLatin1( strAuth );
05152   }
05153   else
05154   {
05155     Authentication = f;
05156     m_strAuthorization = QString::fromLatin1( strAuth );
05157   }
05158 }
05159 
05160 
05161 bool HTTPProtocol::retryPrompt()
05162 {
05163   QString prompt;
05164   switch ( m_responseCode )
05165   {
05166     case 401:
05167       prompt = i18n("Authentication Failed.");
05168       break;
05169     case 407:
05170       prompt = i18n("Proxy Authentication Failed.");
05171       break;
05172     default:
05173       break;
05174   }
05175   prompt += i18n("  Do you want to retry?");
05176   return (messageBox(QuestionYesNo, prompt, i18n("Authentication")) == 3);
05177 }
05178 
05179 void HTTPProtocol::promptInfo( AuthInfo& info )
05180 {
05181   if ( m_responseCode == 401 )
05182   {
05183     info.url = m_request.url;
05184     if ( !m_state.user.isEmpty() )
05185       info.username = m_state.user;
05186     info.readOnly = !m_request.url.user().isEmpty();
05187     info.prompt = i18n( "You need to supply a username and a "
05188                         "password to access this site." );
05189     info.keepPassword = true; // Prompt the user for persistence as well.
05190     if ( !m_strRealm.isEmpty() )
05191     {
05192       info.realmValue = m_strRealm;
05193       info.verifyPath = false;
05194       info.digestInfo = m_strAuthorization;
05195       info.commentLabel = i18n( "Site:" );
05196       info.comment = i18n("<b>%1</b> at <b>%2</b>").arg( htmlEscape(m_strRealm) ).arg( m_request.hostname );
05197     }
05198   }
05199   else if ( m_responseCode == 407 )
05200   {
05201     info.url = m_proxyURL;
05202     info.username = m_proxyURL.user();
05203     info.prompt = i18n( "You need to supply a username and a password for "
05204                         "the proxy server listed below before you are allowed "
05205                         "to access any sites." );
05206     info.keepPassword = true;
05207     if ( !m_strProxyRealm.isEmpty() )
05208     {
05209       info.realmValue = m_strProxyRealm;
05210       info.verifyPath = false;
05211       info.digestInfo = m_strProxyAuthorization;
05212       info.commentLabel = i18n( "Proxy:" );
05213       info.comment = i18n("<b>%1</b> at <b>%2</b>").arg( htmlEscape(m_strProxyRealm) ).arg( m_proxyURL.host() );
05214     }
05215   }
05216 }
05217 
05218 bool HTTPProtocol::getAuthorization()
05219 {
05220   AuthInfo info;
05221   bool result = false;
05222 
05223   kdDebug (7113) << "(" << m_pid << ") HTTPProtocol::getAuthorization: "
05224                  << "Current Response: " << m_responseCode << ", "
05225                  << "Previous Response: " << m_prevResponseCode << ", "
05226                  << "Authentication: " << Authentication << ", "
05227                  << "ProxyAuthentication: " << ProxyAuthentication << endl;
05228 
05229   if (m_request.bNoAuth)
05230   {
05231      if (m_request.bErrorPage)
05232         errorPage();
05233      else
05234         error( ERR_COULD_NOT_LOGIN, i18n("Authentication needed for %1 but authentication is disabled.").arg(m_request.hostname));
05235      return false;
05236   }
05237 
05238   bool repeatFailure = (m_prevResponseCode == m_responseCode);
05239 
05240   QString errorMsg;
05241 
05242   if (repeatFailure)
05243   {
05244     bool prompt = true;
05245     if ( Authentication == AUTH_Digest || ProxyAuthentication == AUTH_Digest )
05246     {
05247       bool isStaleNonce = false;
05248       QString auth = ( m_responseCode == 401 ) ? m_strAuthorization : m_strProxyAuthorization;
05249       int pos = auth.find("stale", 0, false);
05250       if ( pos != -1 )
05251       {
05252         pos += 5;
05253         int len = auth.length();
05254         while( pos < len && (auth[pos] == ' ' || auth[pos] == '=') ) pos++;
05255         if ( pos < len && auth.find("true", pos, false) != -1 )
05256         {
05257           isStaleNonce = true;
05258           kdDebug(7113) << "(" << m_pid << ") Stale nonce value. "
05259                         << "Will retry using same info..." << endl;
05260         }
05261       }
05262       if ( isStaleNonce )
05263       {
05264         prompt = false;
05265         result = true;
05266         if ( m_responseCode == 401 )
05267         {
05268           info.username = m_request.user;
05269           info.password = m_request.passwd;
05270           info.realmValue = m_strRealm;
05271           info.digestInfo = m_strAuthorization;
05272         }
05273         else if ( m_responseCode == 407 )
05274         {
05275           info.username = m_proxyURL.user();
05276           info.password = m_proxyURL.pass();
05277           info.realmValue = m_strProxyRealm;
05278           info.digestInfo = m_strProxyAuthorization;
05279         }
05280       }
05281     }
05282 
05283     if ( Authentication == AUTH_NTLM || ProxyAuthentication == AUTH_NTLM )
05284     {
05285       QString auth = ( m_responseCode == 401 ) ? m_strAuthorization : m_strProxyAuthorization;
05286       kdDebug(7113) << "auth: " << auth << endl;
05287       if ( auth.length() > 4 )
05288       {
05289         prompt = false;
05290         result = true;
05291         kdDebug(7113) << "(" << m_pid << ") NTLM auth second phase, "
05292                       << "sending response..." << endl;
05293         if ( m_responseCode == 401 )
05294         {
05295           info.username = m_request.user;
05296           info.password = m_request.passwd;
05297           info.realmValue = m_strRealm;
05298           info.digestInfo = m_strAuthorization;
05299         }
05300         else if ( m_responseCode == 407 )
05301         {
05302           info.username = m_proxyURL.user();
05303           info.password = m_proxyURL.pass();
05304           info.realmValue = m_strProxyRealm;
05305           info.digestInfo = m_strProxyAuthorization;
05306         }
05307       }
05308     }
05309 
05310     if ( prompt )
05311     {
05312       switch ( m_responseCode )
05313       {
05314         case 401:
05315           errorMsg = i18n("Authentication Failed.");
05316           break;
05317         case 407:
05318           errorMsg = i18n("Proxy Authentication Failed.");
05319           break;
05320         default:
05321           break;
05322       }
05323     }
05324   }
05325   else
05326   {
05327     // At this point we know more details, so use it to find
05328     // out if we have a cached version and avoid a re-prompt!
05329     // We also do not use verify path unlike the pre-emptive
05330     // requests because we already know the realm value...
05331 
05332     if (m_bProxyAuthValid)
05333     {
05334       // Reset cached proxy auth
05335       m_bProxyAuthValid = false;
05336       KURL proxy ( config()->readEntry("UseProxy") );
05337       m_proxyURL.setUser(proxy.user());
05338       m_proxyURL.setPass(proxy.pass());
05339     }
05340 
05341     info.verifyPath = false;
05342     if ( m_responseCode == 407 )
05343     {
05344       info.url = m_proxyURL;
05345       info.username = m_proxyURL.user();
05346       info.password = m_proxyURL.pass();
05347       info.realmValue = m_strProxyRealm;
05348       info.digestInfo = m_strProxyAuthorization;
05349     }
05350     else
05351     {
05352       info.url = m_request.url;
05353       info.username = m_request.user;
05354       info.password = m_request.passwd;
05355       info.realmValue = m_strRealm;
05356       info.digestInfo = m_strAuthorization;
05357     }
05358 
05359     // If either username or password is not supplied
05360     // with the request, check the password cache.
05361     if ( info.username.isNull() ||
05362          info.password.isNull() )
05363       result = checkCachedAuthentication( info );
05364 
05365     if ( Authentication == AUTH_Digest )
05366     {
05367       QString auth;
05368 
05369       if (m_responseCode == 401)
05370         auth = m_strAuthorization;
05371       else
05372         auth = m_strProxyAuthorization;
05373 
05374       int pos = auth.find("stale", 0, false);
05375       if ( pos != -1 )
05376       {
05377         pos += 5;
05378         int len = auth.length();
05379         while( pos < len && (auth[pos] == ' ' || auth[pos] == '=') ) pos++;
05380         if ( pos < len && auth.find("true", pos, false) != -1 )
05381         {
05382           info.digestInfo = (m_responseCode == 401) ? m_strAuthorization : m_strProxyAuthorization;
05383           kdDebug(7113) << "(" << m_pid << ") Just a stale nonce value! "
05384                         << "Retrying using the new nonce sent..." << endl;
05385         }
05386       }
05387     }
05388   }
05389 
05390   if (!result )
05391   {
05392     // Do not prompt if the username & password
05393     // is already supplied and the login attempt
05394     // did not fail before.
05395     if ( !repeatFailure &&
05396          !info.username.isNull() &&
05397          !info.password.isNull() )
05398       result = true;
05399     else
05400     {
05401       if (Authentication == AUTH_Negotiate)
05402       {
05403         if (!repeatFailure)
05404           result = true;
05405       }
05406       else if ( m_request.disablePassDlg == false )
05407       {
05408         kdDebug( 7113 ) << "(" << m_pid << ") Prompting the user for authorization..." << endl;
05409         promptInfo( info );
05410         result = openPassDlg( info, errorMsg );
05411       }
05412     }
05413   }
05414 
05415   if ( result )
05416   {
05417     switch (m_responseCode)
05418     {
05419       case 401: // Request-Authentication
05420         m_request.user = info.username;
05421         m_request.passwd = info.password;
05422         m_strRealm = info.realmValue;
05423         m_strAuthorization = info.digestInfo;
05424         break;
05425       case 407: // Proxy-Authentication
05426         m_proxyURL.setUser( info.username );
05427         m_proxyURL.setPass( info.password );
05428         m_strProxyRealm = info.realmValue;
05429         m_strProxyAuthorization = info.digestInfo;
05430         break;
05431       default:
05432         break;
05433     }
05434     return true;
05435   }
05436 
05437   if (m_request.bErrorPage)
05438      errorPage();
05439   else
05440      error( ERR_USER_CANCELED, QString::null );
05441   return false;
05442 }
05443 
05444 void HTTPProtocol::saveAuthorization()
05445 {
05446   AuthInfo info;
05447   if ( m_prevResponseCode == 407 )
05448   {
05449     if (!m_bUseProxy)
05450        return;
05451     m_bProxyAuthValid = true;
05452     info.url = m_proxyURL;
05453     info.username = m_proxyURL.user();
05454     info.password = m_proxyURL.pass();
05455     info.realmValue = m_strProxyRealm;
05456     info.digestInfo = m_strProxyAuthorization;
05457     cacheAuthentication( info );
05458   }
05459   else
05460   {
05461     info.url = m_request.url;
05462     info.username = m_request.user;
05463     info.password = m_request.passwd;
05464     info.realmValue = m_strRealm;
05465     info.digestInfo = m_strAuthorization;
05466     cacheAuthentication( info );
05467   }
05468 }
05469 
05470 #ifdef HAVE_LIBGSSAPI
05471 QCString HTTPProtocol::gssError( int major_status, int minor_status )
05472 {
05473   OM_uint32 new_status;
05474   OM_uint32 msg_ctx = 0;
05475   gss_buffer_desc major_string;
05476   gss_buffer_desc minor_string;
05477   OM_uint32 ret;
05478   QCString errorstr;
05479 
05480   errorstr = "";
05481 
05482   do {
05483     ret = gss_display_status(&new_status, major_status, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &major_string);
05484     errorstr += (const char *)major_string.value;
05485     errorstr += " ";
05486     ret = gss_display_status(&new_status, minor_status, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &minor_string);
05487     errorstr += (const char *)minor_string.value;
05488     errorstr += " ";
05489   } while (!GSS_ERROR(ret) && msg_ctx != 0);
05490 
05491   return errorstr;
05492 }
05493 
05494 QString HTTPProtocol::createNegotiateAuth()
05495 {
05496   QString auth;
05497   QCString servicename;
05498   QByteArray input;
05499   OM_uint32 major_status, minor_status;
05500   OM_uint32 req_flags = 0;
05501   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
05502   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
05503   gss_name_t server;
05504   gss_ctx_id_t ctx;
05505   gss_OID mech_oid;
05506   static gss_OID_desc krb5_oid_desc = {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"};
05507   static gss_OID_desc spnego_oid_desc = {6, (void *) "\x2b\x06\x01\x05\x05\x02"};
05508   int found = 0;
05509   unsigned int i;
05510   gss_OID_set mech_set;
05511   gss_OID tmp_oid;
05512 
05513   ctx = GSS_C_NO_CONTEXT;
05514   mech_oid = &krb5_oid_desc;
05515 
05516   // see whether we can use the SPNEGO mechanism
05517   major_status = gss_indicate_mechs(&minor_status, &mech_set);
05518   if (GSS_ERROR(major_status)) {
05519     kdDebug(7113) << "(" << m_pid << ") gss_indicate_mechs failed: " << gssError(major_status, minor_status) << endl;
05520   } else {
05521     for (i=0; i<mech_set->count && !found; i++) {
05522       tmp_oid = &mech_set->elements[i];
05523       if (tmp_oid->length == spnego_oid_desc.length &&
05524         !memcmp(tmp_oid->elements, spnego_oid_desc.elements, tmp_oid->length)) {
05525         kdDebug(7113) << "(" << m_pid << ") createNegotiateAuth: found SPNEGO mech" << endl;
05526         found = 1;
05527         mech_oid = &spnego_oid_desc;
05528         break;
05529       }
05530     }
05531     gss_release_oid_set(&minor_status, &mech_set);
05532   }
05533 
05534   // the service name is "HTTP/f.q.d.n"
05535   servicename = "HTTP@";
05536   servicename += m_state.hostname.ascii();
05537 
05538   input_token.value = (void *)servicename.data();
05539   input_token.length = servicename.length() + 1;
05540 
05541   major_status = gss_import_name(&minor_status, &input_token,
05542                                  GSS_C_NT_HOSTBASED_SERVICE, &server);
05543 
05544   input_token.value = NULL;
05545   input_token.length = 0;
05546 
05547   if (GSS_ERROR(major_status)) {
05548     kdDebug(7113) << "(" << m_pid << ") gss_import_name failed: " << gssError(major_status, minor_status) << endl;
05549     // reset the auth string so that subsequent methods aren't confused
05550     m_strAuthorization = QString::null;
05551     return QString::null;
05552   }
05553 
05554   major_status = gss_init_sec_context(&minor_status, GSS_C_NO_CREDENTIAL,
05555                                       &ctx, server, mech_oid,
05556                                       req_flags, GSS_C_INDEFINITE,
05557                                       GSS_C_NO_CHANNEL_BINDINGS,
05558                                       GSS_C_NO_BUFFER, NULL, &output_token,
05559                                       NULL, NULL);
05560 
05561 
05562   if (GSS_ERROR(major_status) || (output_token.length == 0)) {
05563     kdDebug(7113) << "(" << m_pid << ") gss_init_sec_context failed: " << gssError(major_status, minor_status) << endl;
05564     gss_release_name(&minor_status, &server);
05565     if (ctx != GSS_C_NO_CONTEXT) {
05566       gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER);
05567       ctx = GSS_C_NO_CONTEXT;
05568     }
05569     // reset the auth string so that subsequent methods aren't confused
05570     m_strAuthorization = QString::null;
05571     return QString::null;
05572   }
05573 
05574   input.duplicate((const char *)output_token.value, output_token.length);
05575   auth = "Authorization: Negotiate ";
05576   auth += KCodecs::base64Encode( input );
05577   auth += "\r\n";
05578 
05579   // free everything
05580   gss_release_name(&minor_status, &server);
05581   if (ctx != GSS_C_NO_CONTEXT) {
05582     gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER);
05583     ctx = GSS_C_NO_CONTEXT;
05584   }
05585   gss_release_buffer(&minor_status, &output_token);
05586 
05587   return auth;
05588 }
05589 #else
05590 
05591 // Dummy
05592 QCString HTTPProtocol::gssError( int, int )
05593 {
05594   return "";
05595 }
05596 
05597 // Dummy
05598 QString HTTPProtocol::createNegotiateAuth()
05599 {
05600   return QString::null;
05601 }
05602 #endif
05603 
05604 QString HTTPProtocol::createNTLMAuth( bool isForProxy )
05605 {
05606   uint len;
05607   QString auth, user, domain, passwd;
05608   QCString strauth;
05609   QByteArray buf;
05610 
05611   if ( isForProxy )
05612   {
05613     auth = "Proxy-Connection: Keep-Alive\r\n";
05614     auth += "Proxy-Authorization: NTLM ";
05615     user = m_proxyURL.user();
05616     passwd = m_proxyURL.pass();
05617     strauth = m_strProxyAuthorization.latin1();
05618     len = m_strProxyAuthorization.length();
05619   }
05620   else
05621   {
05622     auth = "Authorization: NTLM ";
05623     user = m_state.user;
05624     passwd = m_state.passwd;
05625     strauth = m_strAuthorization.latin1();
05626     len = m_strAuthorization.length();
05627   }
05628   if ( user.contains('\\') ) {
05629     domain = user.section( '\\', 0, 0);
05630     user = user.section( '\\', 1 );
05631   }
05632 
05633   kdDebug(7113) << "(" << m_pid << ") NTLM length: " << len << endl;
05634   if ( user.isEmpty() || passwd.isEmpty() || len < 4 )
05635     return QString::null;
05636 
05637   if ( len > 4 )
05638   {
05639     // create a response
05640     QByteArray challenge;
05641     KCodecs::base64Decode( strauth.right( len - 5 ), challenge );
05642     KNTLM::getAuth( buf, challenge, user, passwd, domain,
05643             KNetwork::KResolver::localHostName(), false, false );
05644   }
05645   else
05646   {
05647     KNTLM::getNegotiate( buf );
05648   }
05649 
05650   // remove the challenge to prevent reuse
05651   if ( isForProxy )
05652     m_strProxyAuthorization = "NTLM";
05653   else
05654     m_strAuthorization = "NTLM";
05655 
05656   auth += KCodecs::base64Encode( buf );
05657   auth += "\r\n";
05658 
05659   return auth;
05660 }
05661 
05662 QString HTTPProtocol::createBasicAuth( bool isForProxy )
05663 {
05664   QString auth;
05665   QCString user, passwd;
05666   if ( isForProxy )
05667   {
05668     auth = "Proxy-Authorization: Basic ";
05669     user = m_proxyURL.user().latin1();
05670     passwd = m_proxyURL.pass().latin1();
05671   }
05672   else
05673   {
05674     auth = "Authorization: Basic ";
05675     user = m_state.user.latin1();
05676     passwd = m_state.passwd.latin1();
05677   }
05678 
05679   if ( user.isEmpty() )
05680     user = "";
05681   if ( passwd.isEmpty() )
05682     passwd = "";
05683 
05684   user += ':';
05685   user += passwd;
05686   auth += KCodecs::base64Encode( user );
05687   auth += "\r\n";
05688 
05689   return auth;
05690 }
05691 
05692 void HTTPProtocol::calculateResponse( DigestAuthInfo& info, QCString& Response )
05693 {
05694   KMD5 md;
05695   QCString HA1;
05696   QCString HA2;
05697 
05698   // Calculate H(A1)
05699   QCString authStr = info.username;
05700   authStr += ':';
05701   authStr += info.realm;
05702   authStr += ':';
05703   authStr += info.password;
05704   md.update( authStr );
05705 
05706   if ( info.algorithm.lower() == "md5-sess" )
05707   {
05708     authStr = md.hexDigest();
05709     authStr += ':';
05710     authStr += info.nonce;
05711     authStr += ':';
05712     authStr += info.cnonce;
05713     md.reset();
05714     md.update( authStr );
05715   }
05716   HA1 = md.hexDigest();
05717 
05718   kdDebug(7113) << "(" << m_pid << ") calculateResponse(): A1 => " << HA1 << endl;
05719 
05720   // Calcualte H(A2)
05721   authStr = info.method;
05722   authStr += ':';
05723   authStr += m_request.url.encodedPathAndQuery(0, true).latin1();
05724   if ( info.qop == "auth-int" )
05725   {
05726     authStr += ':';
05727     authStr += info.entityBody;
05728   }
05729   md.reset();
05730   md.update( authStr );
05731   HA2 = md.hexDigest();
05732 
05733   kdDebug(7113) << "(" << m_pid << ") calculateResponse(): A2 => "
05734                 << HA2 << endl;
05735 
05736   // Calcualte the response.
05737   authStr = HA1;
05738   authStr += ':';
05739   authStr += info.nonce;
05740   authStr += ':';
05741   if ( !info.qop.isEmpty() )
05742   {
05743     authStr += info.nc;
05744     authStr += ':';
05745     authStr += info.cnonce;
05746     authStr += ':';
05747     authStr += info.qop;
05748     authStr += ':';
05749   }
05750   authStr += HA2;
05751   md.reset();
05752   md.update( authStr );
05753   Response = md.hexDigest();
05754 
05755   kdDebug(7113) << "(" << m_pid << ") calculateResponse(): Response => "
05756                 << Response << endl;
05757 }
05758 
05759 QString HTTPProtocol::createDigestAuth ( bool isForProxy )
05760 {
05761   const char *p;
05762 
05763   QString auth;
05764   QCString opaque;
05765   QCString Response;
05766 
05767   DigestAuthInfo info;
05768 
05769   opaque = "";
05770   if ( isForProxy )
05771   {
05772     auth = "Proxy-Authorization: Digest ";
05773     info.username = m_proxyURL.user().latin1();
05774     info.password = m_proxyURL.pass().latin1();
05775     p = m_strProxyAuthorization.latin1();
05776   }
05777   else
05778   {
05779     auth = "Authorization: Digest ";
05780     info.username = m_state.user.latin1();
05781     info.password = m_state.passwd.latin1();
05782     p = m_strAuthorization.latin1();
05783   }
05784   if (!p || !*p)
05785     return QString::null;
05786 
05787   p += 6; // Skip "Digest"
05788 
05789   if ( info.username.isEmpty() || info.password.isEmpty() || !p )
05790     return QString::null;
05791 
05792   // info.entityBody = p;  // FIXME: send digest of data for POST action ??
05793   info.realm = "";
05794   info.algorithm = "MD5";
05795   info.nonce = "";
05796   info.qop = "";
05797 
05798   // cnonce is recommended to contain about 64 bits of entropy
05799   info.cnonce = KApplication::randomString(16).latin1();
05800 
05801   // HACK: Should be fixed according to RFC 2617 section 3.2.2
05802   info.nc = "00000001";
05803 
05804   // Set the method used...
05805   switch ( m_request.method )
05806   {
05807     case HTTP_GET:
05808         info.method = "GET";
05809         break;
05810     case HTTP_PUT:
05811         info.method = "PUT";
05812         break;
05813     case HTTP_POST:
05814         info.method = "POST";
05815         break;
05816     case HTTP_HEAD:
05817         info.method = "HEAD";
05818         break;
05819     case HTTP_DELETE:
05820         info.method = "DELETE";
05821         break;
05822     case DAV_PROPFIND:
05823         info.method = "PROPFIND";
05824         break;
05825     case DAV_PROPPATCH:
05826         info.method = "PROPPATCH";
05827         break;
05828     case DAV_MKCOL:
05829         info.method = "MKCOL";
05830         break;
05831     case DAV_COPY:
05832         info.method = "COPY";
05833         break;
05834     case DAV_MOVE:
05835         info.method = "MOVE";
05836         break;
05837     case DAV_LOCK:
05838         info.method = "LOCK";
05839         break;
05840     case DAV_UNLOCK:
05841         info.method = "UNLOCK";
05842         break;
05843     case DAV_SEARCH:
05844         info.method = "SEARCH";
05845         break;
05846     case DAV_SUBSCRIBE:
05847         info.method = "SUBSCRIBE";
05848         break;
05849     case DAV_UNSUBSCRIBE:
05850         info.method = "UNSUBSCRIBE";
05851         break;
05852     case DAV_POLL:
05853         info.method = "POLL";
05854         break;
05855     default:
05856         error( ERR_UNSUPPORTED_ACTION, i18n("Unsupported method: authentication will fail. Please submit a bug report."));
05857         break;
05858   }
05859 
05860   // Parse the Digest response....
05861   while (*p)
05862   {
05863     int i = 0;
05864     while ( (*p == ' ') || (*p == ',') || (*p == '\t')) { p++; }
05865     if (strncasecmp(p, "realm=", 6 )==0)
05866     {
05867       p+=6;
05868       while ( *p == '"' ) p++;  // Go past any number of " mark(s) first
05869       while ( p[i] != '"' ) i++;  // Read everything until the last " mark
05870       info.realm = QCString( p, i+1 );
05871     }
05872     else if (strncasecmp(p, "algorith=", 9)==0)
05873     {
05874       p+=9;
05875       while ( *p == '"' ) p++;  // Go past any number of " mark(s) first
05876       while ( ( p[i] != '"' ) && ( p[i] != ',' ) && ( p[i] != '\0' ) ) i++;
05877       info.algorithm = QCString(p, i+1);
05878     }
05879     else if (strncasecmp(p, "algorithm=", 10)==0)
05880     {
05881       p+=10;
05882       while ( *p == '"' ) p++;  // Go past any " mark(s) first
05883       while ( ( p[i] != '"' ) && ( p[i] != ',' ) && ( p[i] != '\0' ) ) i++;
05884       info.algorithm = QCString(p,i+1);
05885     }
05886     else if (strncasecmp(p, "domain=", 7)==0)
05887     {
05888       p+=7;
05889       while ( *p == '"' ) p++;  // Go past any " mark(s) first
05890       while ( p[i] != '"' ) i++;  // Read everything until the last " mark
05891       int pos;
05892       int idx = 0;
05893       QCString uri = QCString(p,i+1);
05894       do
05895       {
05896         pos = uri.find( ' ', idx );
05897         if ( pos != -1 )
05898         {
05899           KURL u (m_request.url, uri.mid(idx, pos-idx));
05900           if (u.isValid ())
05901             info.digestURI.append( u.url().latin1() );
05902         }
05903         else
05904         {
05905           KURL u (m_request.url, uri.mid(idx, uri.length()-idx));
05906           if (u.isValid ())
05907             info.digestURI.append( u.url().latin1() );
05908         }
05909         idx = pos+1;
05910       } while ( pos != -1 );
05911     }
05912     else if (strncasecmp(p, "nonce=", 6)==0)
05913     {
05914       p+=6;
05915       while ( *p == '"' ) p++;  // Go past any " mark(s) first
05916       while ( p[i] != '"' ) i++;  // Read everything until the last " mark
05917       info.nonce = QCString(p,i+1);
05918     }
05919     else if (strncasecmp(p, "opaque=", 7)==0)
05920     {
05921       p+=7;
05922       while ( *p == '"' ) p++;  // Go past any " mark(s) first
05923       while ( p[i] != '"' ) i++;  // Read everything until the last " mark
05924       opaque = QCString(p,i+1);
05925     }
05926     else if (strncasecmp(p, "qop=", 4)==0)
05927     {
05928       p+=4;
05929       while ( *p == '"' ) p++;  // Go past any " mark(s) first
05930       while ( p[i] != '"' ) i++;  // Read everything until the last " mark
05931       info.qop = QCString(p,i+1);
05932     }
05933     p+=(i+1);
05934   }
05935 
05936   if (info.realm.isEmpty() || info.nonce.isEmpty())
05937     return QString::null;
05938 
05939   // If the "domain" attribute was not specified and the current response code
05940   // is authentication needed, add the current request url to the list over which
05941   // this credential can be automatically applied.
05942   if (info.digestURI.isEmpty() && (m_responseCode == 401 || m_responseCode == 407))
05943     info.digestURI.append (m_request.url.url().latin1());
05944   else
05945   {
05946     // Verify whether or not we should send a cached credential to the
05947     // server based on the stored "domain" attribute...
05948     bool send = true;
05949 
05950     // Determine the path of the request url...
05951     QString requestPath = m_request.url.directory(false, false);
05952     if (requestPath.isEmpty())
05953       requestPath = "/";
05954 
05955     int count = info.digestURI.count();
05956 
05957     for (int i = 0; i < count; i++ )
05958     {
05959       KURL u ( info.digestURI.at(i) );
05960 
05961       send &= (m_request.url.protocol().lower() == u.protocol().lower());
05962       send &= (m_request.hostname.lower() == u.host().lower());
05963 
05964       if (m_request.port > 0 && u.port() > 0)
05965         send &= (m_request.port == u.port());
05966 
05967       QString digestPath = u.directory (false, false);
05968       if (digestPath.isEmpty())
05969         digestPath = "/";
05970 
05971       send &= (requestPath.startsWith(digestPath));
05972 
05973       if (send)
05974         break;
05975     }
05976 
05977     kdDebug(7113) << "(" << m_pid << ") createDigestAuth(): passed digest "
05978                      "authentication credential test: " << send << endl;
05979 
05980     if (!send)
05981       return QString::null;
05982   }
05983 
05984   kdDebug(7113) << "(" << m_pid << ") RESULT OF PARSING:" << endl;
05985   kdDebug(7113) << "(" << m_pid << ")   algorithm: " << info.algorithm << endl;
05986   kdDebug(7113) << "(" << m_pid << ")   realm:     " << info.realm << endl;
05987   kdDebug(7113) << "(" << m_pid << ")   nonce:     " << info.nonce << endl;
05988   kdDebug(7113) << "(" << m_pid << ")   opaque:    " << opaque << endl;
05989   kdDebug(7113) << "(" << m_pid << ")   qop:       " << info.qop << endl;
05990 
05991   // Calculate the response...
05992   calculateResponse( info, Response );
05993 
05994   auth += "username=\"";
05995   auth += info.username;
05996 
05997   auth += "\", realm=\"";
05998   auth += info.realm;
05999   auth += "\"";
06000 
06001   auth += ", nonce=\"";
06002   auth += info.nonce;
06003 
06004   auth += "\", uri=\"";
06005   auth += m_request.url.encodedPathAndQuery(0, true);
06006 
06007   auth += "\", algorithm=\"";
06008   auth += info.algorithm;
06009   auth +="\"";
06010 
06011   if ( !info.qop.isEmpty() )
06012   {
06013     auth += ", qop=\"";
06014     auth += info.qop;
06015     auth += "\", cnonce=\"";
06016     auth += info.cnonce;
06017     auth += "\", nc=";
06018     auth += info.nc;
06019   }
06020 
06021   auth += ", response=\"";
06022   auth += Response;
06023   if ( !opaque.isEmpty() )
06024   {
06025     auth += "\", opaque=\"";
06026     auth += opaque;
06027   }
06028   auth += "\"\r\n";
06029 
06030   return auth;
06031 }
06032 
06033 QString HTTPProtocol::proxyAuthenticationHeader()
06034 {
06035   QString header;
06036 
06037   // We keep proxy authentication locally until they are changed.
06038   // Thus, no need to check with the password manager for every
06039   // connection.
06040   if ( m_strProxyRealm.isEmpty() )
06041   {
06042     AuthInfo info;
06043     info.url = m_proxyURL;
06044     info.username = m_proxyURL.user();
06045     info.password = m_proxyURL.pass();
06046     info.verifyPath = true;
06047 
06048     // If the proxy URL already contains username
06049     // and password simply attempt to retrieve it
06050     // without prompting the user...
06051     if ( !info.username.isNull() && !info.password.isNull() )
06052     {
06053       if( m_strProxyAuthorization.isEmpty() )
06054         ProxyAuthentication = AUTH_None;
06055       else if( m_strProxyAuthorization.startsWith("Basic") )
06056         ProxyAuthentication = AUTH_Basic;
06057       else if( m_strProxyAuthorization.startsWith("NTLM") )
06058         ProxyAuthentication = AUTH_NTLM;
06059       else
06060         ProxyAuthentication = AUTH_Digest;
06061     }
06062     else
06063     {
06064       if ( checkCachedAuthentication(info) && !info.digestInfo.isEmpty() )
06065       {
06066         m_proxyURL.setUser( info.username );
06067         m_proxyURL.setPass( info.password );
06068         m_strProxyRealm = info.realmValue;
06069         m_strProxyAuthorization = info.digestInfo;
06070         if( m_strProxyAuthorization.startsWith("Basic") )
06071           ProxyAuthentication = AUTH_Basic;
06072         else if( m_strProxyAuthorization.startsWith("NTLM") )
06073           ProxyAuthentication = AUTH_NTLM;
06074         else
06075           ProxyAuthentication = AUTH_Digest;
06076       }
06077       else
06078       {
06079         ProxyAuthentication = AUTH_None;
06080       }
06081     }
06082   }
06083 
06084   /********* Only for debugging purpose... *********/
06085   if ( ProxyAuthentication != AUTH_None )
06086   {
06087     kdDebug(7113) << "(" << m_pid << ") Using Proxy Authentication: " << endl;
06088     kdDebug(7113) << "(" << m_pid << ")   HOST= " << m_proxyURL.host() << endl;
06089     kdDebug(7113) << "(" << m_pid << ")   PORT= " << m_proxyURL.port() << endl;
06090     kdDebug(7113) << "(" << m_pid << ")   USER= " << m_proxyURL.user() << endl;
06091     kdDebug(7113) << "(" << m_pid << ")   PASSWORD= [protected]" << endl;
06092     kdDebug(7113) << "(" << m_pid << ")   REALM= " << m_strProxyRealm << endl;
06093     kdDebug(7113) << "(" << m_pid << ")   EXTRA= " << m_strProxyAuthorization << endl;
06094   }
06095 
06096   switch ( ProxyAuthentication )
06097   {
06098     case AUTH_Basic:
06099       header += createBasicAuth( true );
06100       break;
06101     case AUTH_Digest:
06102       header += createDigestAuth( true );
06103       break;
06104     case AUTH_NTLM:
06105       if ( m_bFirstRequest ) header += createNTLMAuth( true );
06106       break;
06107     case AUTH_None:
06108     default:
06109       break;
06110   }
06111 
06112   return header;
06113 }
06114 
06115 #include "http.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys