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