• Skip to content
  • Skip to link menu
KDE 3.5 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

kioslave

http.cc

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

kioslave

Skip menu "kioslave"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members

API Reference

Skip menu "API Reference"
  • dcop
  • DNSSD
  • interfaces
  • Kate
  • kconf_update
  • KDECore
  • KDED
  • kdefx
  • KDEsu
  • kdeui
  • KDocTools
  • KHTML
  • KImgIO
  • KInit
  • kio
  • kioslave
  • KJS
  • KNewStuff
  • KParts
  • KUtils
Generated for API Reference by doxygen 1.5.9
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal