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