http.cc
00001 /* 00002 Copyright (C) 2000-2003 Waldo Bastian <bastian@kde.org> 00003 Copyright (C) 2000-2002 George Staikos <staikos@kde.org> 00004 Copyright (C) 2000-2002 Dawit Alemayehu <adawit@kde.org> 00005 Copyright (C) 2001,2002 Hamish Rodda <rodda@kde.org> 00006 00007 This library is free software; you can redistribute it and/or 00008 modify it under the terms of the GNU Library General Public 00009 License (LGPL) as published by the Free Software Foundation; 00010 either version 2 of the License, or (at your option) any later 00011 version. 00012 00013 This library is distributed in the hope that it will be useful, 00014 but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 Library General Public License for more details. 00017 00018 You should have received a copy of the GNU Library General Public License 00019 along with this library; see the file COPYING.LIB. If not, write to 00020 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00021 Boston, MA 02110-1301, USA. 00022 */ 00023 00024 #include <config.h> 00025 00026 #include <errno.h> 00027 #include <fcntl.h> 00028 #include <utime.h> 00029 #include <stdlib.h> 00030 #include <signal.h> 00031 #include <sys/stat.h> 00032 #include <sys/socket.h> 00033 #include <netinet/in.h> // Required for AIX 00034 #include <netinet/tcp.h> 00035 #include <unistd.h> // must be explicitly included for MacOSX 00036 00037 /* 00038 #include <netdb.h> 00039 #include <sys/time.h> 00040 #include <sys/wait.h> 00041 */ 00042 00043 #include <qdom.h> 00044 #include <qfile.h> 00045 #include <qregexp.h> 00046 #include <qdatetime.h> 00047 #include <qstringlist.h> 00048 00049 #include <kurl.h> 00050 #include <kidna.h> 00051 #include <ksocks.h> 00052 #include <kdebug.h> 00053 #include <klocale.h> 00054 #include <kconfig.h> 00055 #include <kextsock.h> 00056 #include <kservice.h> 00057 #include <krfcdate.h> 00058 #include <kmdcodec.h> 00059 #include <kinstance.h> 00060 #include <kresolver.h> 00061 #include <kmimemagic.h> 00062 #include <dcopclient.h> 00063 #include <kdatastream.h> 00064 #include <kapplication.h> 00065 #include <kstandarddirs.h> 00066 #include <kstringhandler.h> 00067 #include <kremoteencoding.h> 00068 00069 #include "kio/ioslave_defaults.h" 00070 #include "kio/http_slave_defaults.h" 00071 00072 #include "httpfilter.h" 00073 #include "http.h" 00074 00075 #ifdef HAVE_LIBGSSAPI 00076 #ifdef GSSAPI_MIT 00077 #include <gssapi/gssapi.h> 00078 #else 00079 #include <gssapi.h> 00080 #endif /* GSSAPI_MIT */ 00081 00082 // Catch uncompatible crap (BR86019) 00083 #if defined(GSS_RFC_COMPLIANT_OIDS) && (GSS_RFC_COMPLIANT_OIDS == 0) 00084 #include <gssapi/gssapi_generic.h> 00085 #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name 00086 #endif 00087 00088 #endif /* HAVE_LIBGSSAPI */ 00089 00090 #include <misc/kntlm/kntlm.h> 00091 00092 using namespace KIO; 00093 00094 extern "C" { 00095 KDE_EXPORT int kdemain(int argc, char **argv); 00096 } 00097 00098 int kdemain( int argc, char **argv ) 00099 { 00100 KLocale::setMainCatalogue("kdelibs"); 00101 KInstance instance( "kio_http" ); 00102 ( void ) KGlobal::locale(); 00103 00104 if (argc != 4) 00105 { 00106 fprintf(stderr, "Usage: kio_http protocol domain-socket1 domain-socket2\n"); 00107 exit(-1); 00108 } 00109 00110 HTTPProtocol slave(argv[1], argv[2], argv[3]); 00111 slave.dispatchLoop(); 00112 return 0; 00113 } 00114 00115 /*********************************** Generic utility functions ********************/ 00116 00117 static char * trimLead (char *orig_string) 00118 { 00119 while (*orig_string == ' ') 00120 orig_string++; 00121 return orig_string; 00122 } 00123 00124 static bool isCrossDomainRequest( const QString& fqdn, const QString& originURL ) 00125 { 00126 if (originURL == "true") // Backwards compatibility 00127 return true; 00128 00129 KURL url ( originURL ); 00130 00131 // Document Origin domain 00132 QString a = url.host(); 00133 00134 // Current request domain 00135 QString b = fqdn; 00136 00137 if (a == b) 00138 return false; 00139 00140 QStringList l1 = QStringList::split('.', a); 00141 QStringList l2 = QStringList::split('.', b); 00142 00143 while(l1.count() > l2.count()) 00144 l1.pop_front(); 00145 00146 while(l2.count() > l1.count()) 00147 l2.pop_front(); 00148 00149 while(l2.count() >= 2) 00150 { 00151 if (l1 == l2) 00152 return false; 00153 00154 l1.pop_front(); 00155 l2.pop_front(); 00156 } 00157 00158 return true; 00159 } 00160 00161 /* 00162 Eliminates any custom header that could potentically alter the request 00163 */ 00164 static QString sanitizeCustomHTTPHeader(const QString& _header) 00165 { 00166 QString sanitizedHeaders; 00167 QStringList headers = QStringList::split(QRegExp("[\r\n]"), _header); 00168 00169 for(QStringList::Iterator it = headers.begin(); it != headers.end(); ++it) 00170 { 00171 QString header = (*it).lower(); 00172 // Do not allow Request line to be specified and ignore 00173 // the other HTTP headers. 00174 if (header.find(':') == -1 || 00175 header.startsWith("host") || 00176 header.startsWith("via")) 00177 continue; 00178 00179 sanitizedHeaders += (*it); 00180 sanitizedHeaders += "\r\n"; 00181 } 00182 00183 return sanitizedHeaders.stripWhiteSpace(); 00184 } 00185 00186 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"