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) 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 } else { 00444 // Follow HTTP/1.1 spec and enable keep-alive by default 00445 // unless the remote side tells us otherwise or we determine 00446 // the persistent link has been terminated by the remote end. 00447 m_request.isKeepAlive = true; 00448 } 00449 m_request.keepAliveTimeout = 0; 00450 00451 m_request.redirectUrl = KUrl(); 00452 m_request.useCookieJar = config()->readEntry("Cookies", false); 00453 m_request.cacheTag.useCache = config()->readEntry("UseCache", true); 00454 m_request.preferErrorPage = config()->readEntry("errorPage", true); 00455 m_request.doNotAuthenticate = config()->readEntry("no-auth", false); 00456 m_strCacheDir = config()->readPathEntry("CacheDir", QString()); 00457 m_maxCacheAge = config()->readEntry("MaxCacheAge", DEFAULT_MAX_CACHE_AGE); 00458 m_request.windowId = config()->readEntry("window-id"); 00459 00460 m_request.methodStringOverride = metaData(QLatin1String("CustomHTTPMethod")); 00461 00462 kDebug(7113) << "Window Id =" << m_request.windowId; 00463 kDebug(7113) << "ssl_was_in_use =" << metaData(QLatin1String("ssl_was_in_use")); 00464 00465 m_request.referrer.clear(); 00466 // RFC 2616: do not send the referrer if the referrer page was served using SSL and 00467 // the current page does not use SSL. 00468 if ( config()->readEntry("SendReferrer", true) && 00469 (isEncryptedHttpVariety(m_protocol) || metaData(QLatin1String("ssl_was_in_use")) != QLatin1String("TRUE") ) ) 00470 { 00471 KUrl refUrl(metaData(QLatin1String("referrer"))); 00472 if (refUrl.isValid()) { 00473 // Sanitize 00474 QString protocol = refUrl.protocol(); 00475 if (protocol.startsWith(QLatin1String("webdav"))) { 00476 protocol.replace(0, 6, QLatin1String("http")); 00477 refUrl.setProtocol(protocol); 00478 } 00479 00480 if (protocol.startsWith(QLatin1String("http"))) { 00481 m_request.referrer = toQString(refUrl.toEncoded(QUrl::RemoveUserInfo | QUrl::RemoveFragment)); 00482 } 00483 } 00484 } 00485 00486 if (config()->readEntry("SendLanguageSettings", true)) { 00487 m_request.charsets = config()->readEntry( "Charsets", "iso-8859-1" ); 00488 if (!m_request.charsets.isEmpty()) { 00489 m_request.charsets += QLatin1String(DEFAULT_PARTIAL_CHARSET_HEADER); 00490 } 00491 m_request.languages = config()->readEntry( "Languages", DEFAULT_LANGUAGE_HEADER ); 00492 } else { 00493 m_request.charsets.clear(); 00494 m_request.languages.clear(); 00495 } 00496 00497 // Adjust the offset value based on the "resume" meta-data. 00498 QString resumeOffset = metaData(QLatin1String("resume")); 00499 if (!resumeOffset.isEmpty()) { 00500 m_request.offset = resumeOffset.toULongLong(); 00501 } else { 00502 m_request.offset = 0; 00503 } 00504 // Same procedure for endoffset. 00505 QString resumeEndOffset = metaData(QLatin1String("resume_until")); 00506 if (!resumeEndOffset.isEmpty()) { 00507 m_request.endoffset = resumeEndOffset.toULongLong(); 00508 } else { 00509 m_request.endoffset = 0; 00510 } 00511 00512 m_request.disablePassDialog = config()->readEntry("DisablePassDlg", false); 00513 m_request.allowTransferCompression = config()->readEntry("AllowCompressedPage", true); 00514 m_request.id = metaData(QLatin1String("request-id")); 00515 00516 // Store user agent for this host. 00517 if (config()->readEntry("SendUserAgent", true)) { 00518 m_request.userAgent = metaData(QLatin1String("UserAgent")); 00519 } else { 00520 m_request.userAgent.clear(); 00521 } 00522 00523 m_request.cacheTag.etag.clear(); 00524 // -1 is also the value returned by KDateTime::toTime_t() from an invalid instance. 00525 m_request.cacheTag.servedDate = -1; 00526 m_request.cacheTag.lastModifiedDate = -1; 00527 m_request.cacheTag.expireDate = -1; 00528 00529 m_request.responseCode = 0; 00530 m_request.prevResponseCode = 0; 00531 00532 delete m_wwwAuth; 00533 m_wwwAuth = 0; 00534 delete m_socketProxyAuth; 00535 m_socketProxyAuth = 0; 00536 00537 // Obtain timeout values 00538 m_remoteRespTimeout = responseTimeout(); 00539 00540 // Bounce back the actual referrer sent 00541 setMetaData(QLatin1String("referrer"), m_request.referrer); 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 (isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) { 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 = m_request.url.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 const int index = statement.indexOf('='); 03031 if (index <= 0) { 03032 mediaAttribute = toQString(statement.mid(0, index)); 03033 } else { 03034 mediaAttribute = toQString(statement.mid(0, index)); 03035 mediaValue = toQString(statement.mid(index+1)); 03036 } 03037 mediaAttribute = mediaAttribute.trimmed(); 03038 mediaValue = mediaValue.trimmed(); 03039 03040 if (mediaValue.startsWith(QLatin1Char('"'))) { 03041 mediaValue.remove(QLatin1Char('"')); 03042 } 03043 03044 if (mediaValue.endsWith(QLatin1Char('"'))) { 03045 mediaValue.truncate(mediaValue.length()-1); 03046 } 03047 03048 kDebug (7113) << "Encoding-type:" << mediaAttribute << "=" << mediaValue; 03049 03050 if (mediaAttribute == QLatin1String("charset")) { 03051 mediaValue = mediaValue.toLower(); 03052 m_request.cacheTag.charset = mediaValue; 03053 setMetaData(QLatin1String("charset"), mediaValue); 03054 } else { 03055 setMetaData(QLatin1String("media-") + mediaAttribute, mediaValue); 03056 } 03057 } 03058 } 03059 03060 // content? 03061 tIt = tokenizer.iterator("content-encoding"); 03062 while (tIt.hasNext()) { 03063 // This is so wrong !! No wonder kio_http is stripping the 03064 // gzip encoding from downloaded files. This solves multiple 03065 // bug reports and caitoo's problem with downloads when such a 03066 // header is encountered... 03067 03068 // A quote from RFC 2616: 03069 // " When present, its (Content-Encoding) value indicates what additional 03070 // content have been applied to the entity body, and thus what decoding 03071 // mechanism must be applied to obtain the media-type referenced by the 03072 // Content-Type header field. Content-Encoding is primarily used to allow 03073 // a document to be compressed without loosing the identity of its underlying 03074 // media type. Simply put if it is specified, this is the actual mime-type 03075 // we should use when we pull the resource !!! 03076 addEncoding(toQString(tIt.next()), m_contentEncodings); 03077 } 03078 // Refer to RFC 2616 sec 15.5/19.5.1 and RFC 2183 03079 tIt = tokenizer.iterator("content-disposition"); 03080 if (tIt.hasNext()) { 03081 parseContentDisposition(toQString(tIt.next())); 03082 } 03083 tIt = tokenizer.iterator("content-language"); 03084 if (tIt.hasNext()) { 03085 QString language = toQString(tIt.next().trimmed()); 03086 if (!language.isEmpty()) { 03087 setMetaData(QLatin1String("content-language"), language); 03088 } 03089 } 03090 03091 tIt = tokenizer.iterator("proxy-connection"); 03092 if (tIt.hasNext() && isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) { 03093 QByteArray pc = tIt.next().toLower(); 03094 if (pc.startsWith("close")) { // krazy:exclude=strings 03095 m_request.isKeepAlive = false; 03096 } else if (pc.startsWith("keep-alive")) { // krazy:exclude=strings 03097 m_request.isKeepAlive = true; 03098 } 03099 } 03100 03101 tIt = tokenizer.iterator("link"); 03102 if (tIt.hasNext()) { 03103 // We only support Link: <url>; rel="type" so far 03104 QStringList link = toQString(tIt.next()).split(QLatin1Char(';'), QString::SkipEmptyParts); 03105 if (link.count() == 2) { 03106 QString rel = link[1].trimmed(); 03107 if (rel.startsWith(QLatin1String("rel=\""))) { 03108 rel = rel.mid(5, rel.length() - 6); 03109 if (rel.toLower() == QLatin1String("pageservices")) { 03110 //### the remove() part looks fishy! 03111 QString url = link[0].remove(QRegExp(QLatin1String("[<>]"))).trimmed(); 03112 setMetaData(QLatin1String("PageServices"), url); 03113 } 03114 } 03115 } 03116 } 03117 03118 tIt = tokenizer.iterator("p3p"); 03119 if (tIt.hasNext()) { 03120 // P3P privacy policy information 03121 QStringList policyrefs, compact; 03122 while (tIt.hasNext()) { 03123 QStringList policy = toQString(tIt.next().simplified()) 03124 .split(QLatin1Char('='), QString::SkipEmptyParts); 03125 if (policy.count() == 2) { 03126 if (policy[0].toLower() == QLatin1String("policyref")) { 03127 policyrefs << policy[1].remove(QRegExp(QLatin1String("[\")\']"))).trimmed(); 03128 } else if (policy[0].toLower() == QLatin1String("cp")) { 03129 // We convert to cp\ncp\ncp\n[...]\ncp to be consistent with 03130 // other metadata sent in strings. This could be a bit more 03131 // efficient but I'm going for correctness right now. 03132 const QString s = policy[1].remove(QRegExp(QLatin1String("[\")\']"))); 03133 const QStringList cps = s.split(QLatin1Char(' '), QString::SkipEmptyParts); 03134 compact << cps; 03135 } 03136 } 03137 } 03138 if (!policyrefs.isEmpty()) { 03139 setMetaData(QLatin1String("PrivacyPolicy"), policyrefs.join(QLatin1String("\n"))); 03140 } 03141 if (!compact.isEmpty()) { 03142 setMetaData(QLatin1String("PrivacyCompactPolicy"), compact.join(QLatin1String("\n"))); 03143 } 03144 } 03145 03146 // continue only if we know that we're at least HTTP/1.0 03147 if (httpRev == HTTP_11 || httpRev == HTTP_10) { 03148 // let them tell us if we should stay alive or not 03149 tIt = tokenizer.iterator("connection"); 03150 while (tIt.hasNext()) { 03151 QByteArray connection = tIt.next().toLower(); 03152 if (!(isHttpProxy(m_request.proxyUrl) && !isAutoSsl())) { 03153 if (connection.startsWith("close")) { // krazy:exclude=strings 03154 m_request.isKeepAlive = false; 03155 } else if (connection.startsWith("keep-alive")) { // krazy:exclude=strings 03156 m_request.isKeepAlive = true; 03157 } 03158 } 03159 if (connection.startsWith("upgrade")) { // krazy:exclude=strings 03160 if (m_request.responseCode == 101) { 03161 // Ok, an upgrade was accepted, now we must do it 03162 upgradeRequired = true; 03163 } else if (upgradeRequired) { // 426 03164 // Nothing to do since we did it above already 03165 } else { 03166 // Just an offer to upgrade - no need to take it 03167 canUpgrade = true; 03168 } 03169 } 03170 } 03171 // what kind of encoding do we have? transfer? 03172 tIt = tokenizer.iterator("transfer-encoding"); 03173 while (tIt.hasNext()) { 03174 // If multiple encodings have been applied to an entity, the 03175 // transfer-codings MUST be listed in the order in which they 03176 // were applied. 03177 addEncoding(toQString(tIt.next().trimmed()), m_transferEncodings); 03178 } 03179 03180 // md5 signature 03181 tIt = tokenizer.iterator("content-md5"); 03182 if (tIt.hasNext()) { 03183 m_contentMD5 = toQString(tIt.next().trimmed()); 03184 } 03185 03186 // *** Responses to the HTTP OPTIONS method follow 03187 // WebDAV capabilities 03188 tIt = tokenizer.iterator("dav"); 03189 while (tIt.hasNext()) { 03190 m_davCapabilities << toQString(tIt.next()); 03191 } 03192 // *** Responses to the HTTP OPTIONS method finished 03193 } 03194 03195 03196 // Now process the HTTP/1.1 upgrade 03197 QStringList upgradeOffers; 03198 tIt = tokenizer.iterator("upgrade"); 03199 if (tIt.hasNext()) { 03200 // Now we have to check to see what is offered for the upgrade 03201 QString offered = toQString(tIt.next()); 03202 upgradeOffers = offered.split(QRegExp(QLatin1String("[ \n,\r\t]")), QString::SkipEmptyParts); 03203 } 03204 Q_FOREACH (const QString &opt, upgradeOffers) { 03205 if (opt == QLatin1String("TLS/1.0")) { 03206 if (!startSsl() && upgradeRequired) { 03207 error(ERR_UPGRADE_REQUIRED, opt); 03208 return false; 03209 } 03210 } else if (opt == QLatin1String("HTTP/1.1")) { 03211 httpRev = HTTP_11; 03212 } else if (upgradeRequired) { 03213 // we are told to do an upgrade we don't understand 03214 error(ERR_UPGRADE_REQUIRED, opt); 03215 return false; 03216 } 03217 } 03218 03219 // Harvest cookies (mmm, cookie fields!) 03220 QByteArray cookieStr; // In case we get a cookie. 03221 tIt = tokenizer.iterator("set-cookie"); 03222 while (tIt.hasNext()) { 03223 cookieStr += "Set-Cookie: "; 03224 cookieStr += tIt.next(); 03225 cookieStr += '\n'; 03226 } 03227 if (!cookieStr.isEmpty()) { 03228 if ((m_request.cookieMode == HTTPRequest::CookiesAuto) && m_request.useCookieJar) { 03229 // Give cookies to the cookiejar. 03230 const QString domain = config()->readEntry("cross-domain"); 03231 if (!domain.isEmpty() && isCrossDomainRequest(m_request.url.host(), domain)) { 03232 cookieStr = "Cross-Domain\n" + cookieStr; 03233 } 03234 addCookies( m_request.url.url(), cookieStr ); 03235 } else if (m_request.cookieMode == HTTPRequest::CookiesManual) { 03236 // Pass cookie to application 03237 setMetaData(QLatin1String("setcookies"), QString::fromUtf8(cookieStr)); // ## is encoding ok? 03238 } 03239 } 03240 03241 // We need to reread the header if we got a '100 Continue' or '102 Processing' 03242 // This may be a non keepalive connection so we handle this kind of loop internally 03243 if ( cont ) 03244 { 03245 kDebug(7113) << "cont; returning to mark try_again"; 03246 goto try_again; 03247 } 03248 03249 if (!m_isChunked && (m_iSize == NO_SIZE) && m_request.isKeepAlive && 03250 canHaveResponseBody(m_request.responseCode, m_request.method)) { 03251 kDebug(7113) << "Ignoring keep-alive: otherwise unable to determine response body length."; 03252 m_request.isKeepAlive = false; 03253 } 03254 03255 // TODO cache the proxy auth data (not doing this means a small performance regression for now) 03256 03257 // we may need to send (Proxy or WWW) authorization data 03258 authRequiresAnotherRoundtrip = false; 03259 if (!m_request.doNotAuthenticate && isAuthenticationRequired(m_request.responseCode)) { 03260 KIO::AuthInfo authinfo; 03261 KAbstractHttpAuthentication **auth; 03262 03263 if (m_request.responseCode == 401) { 03264 auth = &m_wwwAuth; 03265 tIt = tokenizer.iterator("www-authenticate"); 03266 authinfo.url = m_request.url; 03267 authinfo.username = m_server.url.user(); 03268 authinfo.prompt = i18n("You need to supply a username and a " 03269 "password to access this site."); 03270 authinfo.commentLabel = i18n("Site:"); 03271 } else { 03272 // make sure that the 407 header hasn't escaped a lower layer when it shouldn't. 03273 // this may break proxy chains which were never tested anyway, and AFAIK they are 03274 // rare to nonexistent in the wild. 03275 Q_ASSERT(QNetworkProxy::applicationProxy().type() == QNetworkProxy::NoProxy); 03276 auth = &m_proxyAuth; 03277 tIt = tokenizer.iterator("proxy-authenticate"); 03278 authinfo.url = m_request.proxyUrl; 03279 authinfo.username = m_request.proxyUrl.user(); 03280 authinfo.prompt = i18n("You need to supply a username and a password for " 03281 "the proxy server listed below before you are allowed " 03282 "to access any sites." ); 03283 authinfo.commentLabel = i18n("Proxy:"); 03284 } 03285 03286 QList<QByteArray> authTokens = tIt.all(); 03287 // Workaround brain dead server responses that violate the spec and 03288 // incorrectly return a 401/407 without the required WWW/Proxy-Authenticate 03289 // header fields. See bug 215736... 03290 if (!authTokens.isEmpty()) { 03291 authRequiresAnotherRoundtrip = true; 03292 kDebug(7113) << "parsing authentication request; response code =" << m_request.responseCode; 03293 03294 try_next_auth_scheme: 03295 QByteArray bestOffer = KAbstractHttpAuthentication::bestOffer(authTokens); 03296 if (*auth) { 03297 if (!bestOffer.toLower().startsWith((*auth)->scheme().toLower())) { 03298 // huh, the strongest authentication scheme offered has changed. 03299 kDebug(7113) << "deleting old auth class..."; 03300 delete *auth; 03301 *auth = 0; 03302 } 03303 } 03304 03305 if (!(*auth)) { 03306 *auth = KAbstractHttpAuthentication::newAuth(bestOffer, config()); 03307 } 03308 03309 kDebug(7113) << "pointer to auth class is now" << *auth; 03310 03311 if (*auth) { 03312 kDebug(7113) << "Trying authentication scheme:" << (*auth)->scheme(); 03313 03314 // remove trailing space from the method string, or digest auth will fail 03315 (*auth)->setChallenge(bestOffer, authinfo.url, m_request.methodString()); 03316 03317 QString username; 03318 QString password; 03319 bool generateAuthorization = true; 03320 if ((*auth)->needCredentials()) { 03321 // use credentials supplied by the application if available 03322 if (!m_request.url.user().isEmpty() && !m_request.url.pass().isEmpty()) { 03323 username = m_request.url.user(); 03324 password = m_request.url.pass(); 03325 // don't try this password any more 03326 m_request.url.setPass(QString()); 03327 } else { 03328 // try to get credentials from kpasswdserver's cache, then try asking the user. 03329 authinfo.verifyPath = false; // we have realm, no path based checking please! 03330 authinfo.realmValue = (*auth)->realm(); 03331 if (authinfo.realmValue.isEmpty() && !(*auth)->supportsPathMatching()) 03332 authinfo.realmValue = QLatin1String((*auth)->scheme()); 03333 03334 // Save the current authinfo url because it can be modified by the call to 03335 // checkCachedAuthentication. That way we can restore it if the call 03336 // modified it. 03337 const KUrl reqUrl = authinfo.url; 03338 if (!checkCachedAuthentication(authinfo) || 03339 ((*auth)->wasFinalStage() && m_request.responseCode == m_request.prevResponseCode)) { 03340 QString errorMsg; 03341 if ((*auth)->wasFinalStage()) { 03342 switch (m_request.prevResponseCode) { 03343 case 401: 03344 errorMsg = i18n("Authentication Failed."); 03345 break; 03346 case 407: 03347 errorMsg = i18n("Proxy Authentication Failed."); 03348 break; 03349 default: 03350 break; 03351 } 03352 } 03353 03354 // Reset url to the saved url... 03355 authinfo.url = reqUrl; 03356 authinfo.keepPassword = true; 03357 authinfo.comment = i18n("<b>%1</b> at <b>%2</b>", 03358 authinfo.realmValue, authinfo.url.host()); 03359 03360 if (!openPasswordDialog(authinfo, errorMsg)) { 03361 if (sendErrorPageNotification()) { 03362 generateAuthorization = false; 03363 authRequiresAnotherRoundtrip = false; 03364 } else { 03365 error(ERR_ACCESS_DENIED, reqUrl.host()); 03366 return false; 03367 } 03368 } 03369 } 03370 username = authinfo.username; 03371 password = authinfo.password; 03372 } 03373 } 03374 03375 if (generateAuthorization) { 03376 (*auth)->generateResponse(username, password); 03377 03378 kDebug(7113) << "Auth State: isError=" << (*auth)->isError() 03379 << "needCredentials=" << (*auth)->needCredentials() 03380 << "forceKeepAlive=" << (*auth)->forceKeepAlive() 03381 << "forceDisconnect=" << (*auth)->forceDisconnect() 03382 << "headerFragment=" << (*auth)->headerFragment(); 03383 03384 if ((*auth)->isError()) { 03385 authTokens.removeOne(bestOffer); 03386 if (!authTokens.isEmpty()) 03387 goto try_next_auth_scheme; 03388 else { 03389 error(ERR_UNSUPPORTED_ACTION, i18n("Authorization failed.")); 03390 return false; 03391 } 03392 //### return false; ? 03393 } else if ((*auth)->forceKeepAlive()) { 03394 //### think this through for proxied / not proxied 03395 m_request.isKeepAlive = true; 03396 } else if ((*auth)->forceDisconnect()) { 03397 //### think this through for proxied / not proxied 03398 m_request.isKeepAlive = false; 03399 httpCloseConnection(); 03400 } 03401 } 03402 } else { 03403 if (sendErrorPageNotification()) 03404 authRequiresAnotherRoundtrip = false; 03405 else { 03406 error(ERR_UNSUPPORTED_ACTION, i18n("Unknown Authorization method.")); 03407 return false; 03408 } 03409 } 03410 } 03411 } 03412 03413 QString locationStr; 03414 // In fact we should do redirection only if we have a redirection response code (300 range) 03415 tIt = tokenizer.iterator("location"); 03416 if (tIt.hasNext() && m_request.responseCode > 299 && m_request.responseCode < 400) { 03417 locationStr = QString::fromUtf8(tIt.next().trimmed()); 03418 } 03419 // We need to do a redirect 03420 if (!locationStr.isEmpty()) 03421 { 03422 KUrl u(m_request.url, locationStr); 03423 if(!u.isValid()) 03424 { 03425 error(ERR_MALFORMED_URL, u.url()); 03426 return false; 03427 } 03428 if ((u.protocol() != QLatin1String("http")) && (u.protocol() != QLatin1String("https")) && 03429 (u.protocol() != QLatin1String("webdav")) && (u.protocol() != QLatin1String("webdavs"))) 03430 { 03431 redirection(u); 03432 error(ERR_ACCESS_DENIED, u.url()); 03433 return false; 03434 } 03435 03436 // preserve #ref: (bug 124654) 03437 // if we were at http://host/resource1#ref, we sent a GET for "/resource1" 03438 // if we got redirected to http://host/resource2, then we have to re-add 03439 // the fragment: 03440 if (m_request.url.hasRef() && !u.hasRef() && 03441 (m_request.url.host() == u.host()) && 03442 (m_request.url.protocol() == u.protocol())) 03443 u.setRef(m_request.url.ref()); 03444 03445 m_isRedirection = true; 03446 03447 if (!m_request.id.isEmpty()) 03448 { 03449 sendMetaData(); 03450 } 03451 03452 // If we're redirected to a http:// url, remember that we're doing webdav... 03453 if (m_protocol == "webdav" || m_protocol == "webdavs"){ 03454 if(u.protocol() == QLatin1String("http")){ 03455 u.setProtocol(QLatin1String("webdav")); 03456 }else if(u.protocol() == QLatin1String("https")){ 03457 u.setProtocol(QLatin1String("webdavs")); 03458 } 03459 03460 m_request.redirectUrl = u; 03461 } 03462 03463 kDebug(7113) << "Re-directing from" << m_request.url.url() 03464 << "to" << u.url(); 03465 03466 redirection(u); 03467 03468 // It would be hard to cache the redirection response correctly. The possible benefit 03469 // is small (if at all, assuming fast disk and slow network), so don't do it. 03470 cacheFileClose(); 03471 setCacheabilityMetadata(false); 03472 } 03473 03474 // Inform the job that we can indeed resume... 03475 if (bCanResume && m_request.offset) { 03476 //TODO turn off caching??? 03477 canResume(); 03478 } else { 03479 m_request.offset = 0; 03480 } 03481 03482 // Correct a few common wrong content encodings 03483 fixupResponseContentEncoding(); 03484 03485 // Correct some common incorrect pseudo-mimetypes 03486 fixupResponseMimetype(); 03487 03488 // parse everything related to expire and other dates, and cache directives; also switch 03489 // between cache reading and writing depending on cache validation result. 03490 cacheParseResponseHeader(tokenizer); 03491 } 03492 03493 if (m_request.cacheTag.ioMode == ReadFromCache) { 03494 if (m_request.cacheTag.policy == CC_Verify && 03495 m_request.cacheTag.plan(m_maxCacheAge) != CacheTag::UseCached) { 03496 kDebug(7113) << "Reading resource from cache even though the cache plan is not " 03497 "UseCached; the server is probably sending wrong expiry information."; 03498 } 03499 // parseHeaderFromCache replaces this method in case of cached content 03500 return parseHeaderFromCache(); 03501 } 03502 03503 if (config()->readEntry("PropagateHttpHeader", false) || 03504 m_request.cacheTag.ioMode == WriteToCache) { 03505 // store header lines if they will be used; note that the tokenizer removing 03506 // line continuation special cases is probably more good than bad. 03507 int nextLinePos = 0; 03508 int prevLinePos = 0; 03509 bool haveMore = true; 03510 while (haveMore) { 03511 haveMore = nextLine(buffer, &nextLinePos, bufPos); 03512 int prevLineEnd = nextLinePos; 03513 while (buffer[prevLineEnd - 1] == '\r' || buffer[prevLineEnd - 1] == '\n') { 03514 prevLineEnd--; 03515 } 03516 03517 m_responseHeaders.append(QString::fromLatin1(&buffer[prevLinePos], 03518 prevLineEnd - prevLinePos)); 03519 prevLinePos = nextLinePos; 03520 } 03521 03522 // IMPORTNAT: Do not remove this line because forwardHttpResponseHeader 03523 // is called below. This line is added to make http response headers are 03524 // available by the time the content mimetype information is transmitted 03525 // to the job. If the line below is removed, the KIO-QNAM integration 03526 // will not work properly when attempting to put ioslaves on hold. 03527 setMetaData(QLatin1String("HTTP-Headers"), m_responseHeaders.join(QString(QLatin1Char('\n')))); 03528 } 03529 03530 // Let the app know about the mime-type iff this is not a redirection and 03531 // the mime-type string is not empty. 03532 if (!m_isRedirection && 03533 (!m_mimeType.isEmpty() || m_request.method == HTTP_HEAD) && 03534 (m_isLoadingErrorPage || !authRequiresAnotherRoundtrip)) { 03535 kDebug(7113) << "Emitting mimetype " << m_mimeType; 03536 mimeType( m_mimeType ); 03537 } 03538 03539 // Do not move the function call below before doing any redirection. 03540 // Otherwise it might mess up some sites. See BR# 150904. 03541 // IMPORTANT: Do not remove it either thinking it duplicates what is done 03542 // above. Otherwise, the http response headers will not be available if 03543 // this ioslave is put on hold. 03544 forwardHttpResponseHeader(); 03545 03546 if (m_request.method == HTTP_HEAD) 03547 return true; 03548 03549 return !authRequiresAnotherRoundtrip; // return true if no more credentials need to be sent 03550 } 03551 03552 void HTTPProtocol::parseContentDisposition(const QString &disposition) 03553 { 03554 const QMap<QString, QString> parameters = contentDispositionParser(disposition); 03555 03556 QMap<QString, QString>::const_iterator i = parameters.constBegin(); 03557 while (i != parameters.constEnd()) { 03558 setMetaData(QLatin1String("content-disposition-") + i.key(), i.value()); 03559 kDebug(7113) << "Content-Disposition:" << i.key() << "=" << i.value(); 03560 ++i; 03561 } 03562 } 03563 03564 void HTTPProtocol::addEncoding(const QString &_encoding, QStringList &encs) 03565 { 03566 QString encoding = _encoding.trimmed().toLower(); 03567 // Identity is the same as no encoding 03568 if (encoding == QLatin1String("identity")) { 03569 return; 03570 } else if (encoding == QLatin1String("8bit")) { 03571 // Strange encoding returned by http://linac.ikp.physik.tu-darmstadt.de 03572 return; 03573 } else if (encoding == QLatin1String("chunked")) { 03574 m_isChunked = true; 03575 // Anyone know of a better way to handle unknown sizes possibly/ideally with unsigned ints? 03576 //if ( m_cmd != CMD_COPY ) 03577 m_iSize = NO_SIZE; 03578 } else if ((encoding == QLatin1String("x-gzip")) || (encoding == QLatin1String("gzip"))) { 03579 encs.append(QLatin1String("gzip")); 03580 } else if ((encoding == QLatin1String("x-bzip2")) || (encoding == QLatin1String("bzip2"))) { 03581 encs.append(QLatin1String("bzip2")); // Not yet supported! 03582 } else if ((encoding == QLatin1String("x-deflate")) || (encoding == QLatin1String("deflate"))) { 03583 encs.append(QLatin1String("deflate")); 03584 } else { 03585 kDebug(7113) << "Unknown encoding encountered. " 03586 << "Please write code. Encoding =" << encoding; 03587 } 03588 } 03589 03590 void HTTPProtocol::cacheParseResponseHeader(const HeaderTokenizer &tokenizer) 03591 { 03592 if (!m_request.cacheTag.useCache) 03593 return; 03594 03595 // might have to add more response codes 03596 if (m_request.responseCode != 200 && m_request.responseCode != 304) { 03597 return; 03598 } 03599 03600 // -1 is also the value returned by KDateTime::toTime_t() from an invalid instance. 03601 m_request.cacheTag.servedDate = -1; 03602 m_request.cacheTag.lastModifiedDate = -1; 03603 m_request.cacheTag.expireDate = -1; 03604 03605 const qint64 currentDate = time(0); 03606 bool mayCache = m_request.cacheTag.ioMode != NoCache; 03607 03608 TokenIterator tIt = tokenizer.iterator("last-modified"); 03609 if (tIt.hasNext()) { 03610 m_request.cacheTag.lastModifiedDate = 03611 KDateTime::fromString(toQString(tIt.next()), KDateTime::RFCDate).toTime_t(); 03612 03613 //### might be good to canonicalize the date by using KDateTime::toString() 03614 if (m_request.cacheTag.lastModifiedDate != -1) { 03615 setMetaData(QLatin1String("modified"), toQString(tIt.current())); 03616 } 03617 } 03618 03619 // determine from available information when the response was served by the origin server 03620 { 03621 qint64 dateHeader = -1; 03622 tIt = tokenizer.iterator("date"); 03623 if (tIt.hasNext()) { 03624 dateHeader = KDateTime::fromString(toQString(tIt.next()), KDateTime::RFCDate).toTime_t(); 03625 // -1 on error 03626 } 03627 03628 qint64 ageHeader = 0; 03629 tIt = tokenizer.iterator("age"); 03630 if (tIt.hasNext()) { 03631 ageHeader = tIt.next().toLongLong(); 03632 // 0 on error 03633 } 03634 03635 if (dateHeader != -1) { 03636 m_request.cacheTag.servedDate = dateHeader; 03637 } else if (ageHeader) { 03638 m_request.cacheTag.servedDate = currentDate - ageHeader; 03639 } else { 03640 m_request.cacheTag.servedDate = currentDate; 03641 } 03642 } 03643 03644 bool hasCacheDirective = false; 03645 // determine when the response "expires", i.e. becomes stale and needs revalidation 03646 { 03647 // (we also parse other cache directives here) 03648 qint64 maxAgeHeader = 0; 03649 tIt = tokenizer.iterator("cache-control"); 03650 while (tIt.hasNext()) { 03651 QByteArray cacheStr = tIt.next().toLower(); 03652 if (cacheStr.startsWith("no-cache") || cacheStr.startsWith("no-store")) { // krazy:exclude=strings 03653 // Don't put in cache 03654 mayCache = false; 03655 hasCacheDirective = true; 03656 } else if (cacheStr.startsWith("max-age=")) { // krazy:exclude=strings 03657 QByteArray ba = cacheStr.mid(qstrlen("max-age=")).trimmed(); 03658 bool ok = false; 03659 maxAgeHeader = ba.toLongLong(&ok); 03660 if (ok) { 03661 hasCacheDirective = true; 03662 } 03663 } 03664 } 03665 03666 qint64 expiresHeader = -1; 03667 tIt = tokenizer.iterator("expires"); 03668 if (tIt.hasNext()) { 03669 expiresHeader = KDateTime::fromString(toQString(tIt.next()), KDateTime::RFCDate).toTime_t(); 03670 kDebug(7113) << "parsed expire date from 'expires' header:" << tIt.current(); 03671 } 03672 03673 if (maxAgeHeader) { 03674 m_request.cacheTag.expireDate = m_request.cacheTag.servedDate + maxAgeHeader; 03675 } else if (expiresHeader != -1) { 03676 m_request.cacheTag.expireDate = expiresHeader; 03677 } else { 03678 // heuristic expiration date 03679 if (m_request.cacheTag.lastModifiedDate != -1) { 03680 // expAge is following the RFC 2616 suggestion for heuristic expiration 03681 qint64 expAge = (m_request.cacheTag.servedDate - 03682 m_request.cacheTag.lastModifiedDate) / 10; 03683 // not in the RFC: make sure not to have a huge heuristic cache lifetime 03684 expAge = qMin(expAge, qint64(3600 * 24)); 03685 m_request.cacheTag.expireDate = m_request.cacheTag.servedDate + expAge; 03686 } else { 03687 m_request.cacheTag.expireDate = m_request.cacheTag.servedDate + 03688 DEFAULT_CACHE_EXPIRE; 03689 } 03690 } 03691 // make sure that no future clock monkey business causes the cache entry to un-expire 03692 if (m_request.cacheTag.expireDate < currentDate) { 03693 m_request.cacheTag.expireDate = 0; // January 1, 1970 :) 03694 } 03695 } 03696 03697 tIt = tokenizer.iterator("etag"); 03698 if (tIt.hasNext()) { 03699 QString prevEtag = m_request.cacheTag.etag; 03700 m_request.cacheTag.etag = toQString(tIt.next()); 03701 if (m_request.cacheTag.etag != prevEtag && m_request.responseCode == 304) { 03702 kDebug(7103) << "304 Not Modified but new entity tag - I don't think this is legal HTTP."; 03703 } 03704 } 03705 03706 // whoops.. we received a warning 03707 tIt = tokenizer.iterator("warning"); 03708 if (tIt.hasNext()) { 03709 //Don't use warning() here, no need to bother the user. 03710 //Those warnings are mostly about caches. 03711 infoMessage(toQString(tIt.next())); 03712 } 03713 03714 // Cache management (HTTP 1.0) 03715 tIt = tokenizer.iterator("pragma"); 03716 while (tIt.hasNext()) { 03717 if (tIt.next().toLower().startsWith("no-cache")) { // krazy:exclude=strings 03718 mayCache = false; 03719 hasCacheDirective = true; 03720 } 03721 } 03722 03723 // The deprecated Refresh Response 03724 tIt = tokenizer.iterator("refresh"); 03725 if (tIt.hasNext()) { 03726 mayCache = false; 03727 setMetaData(QLatin1String("http-refresh"), toQString(tIt.next().trimmed())); 03728 } 03729 03730 // We don't cache certain text objects 03731 if (m_mimeType.startsWith(QLatin1String("text/")) && (m_mimeType != QLatin1String("text/css")) && 03732 (m_mimeType != QLatin1String("text/x-javascript")) && !hasCacheDirective) { 03733 // Do not cache secure pages or pages 03734 // originating from password protected sites 03735 // unless the webserver explicitly allows it. 03736 if (isUsingSsl() || m_wwwAuth) { 03737 mayCache = false; 03738 } 03739 } 03740 03741 // note that we've updated cacheTag, so the plan() is with current data 03742 if (m_request.cacheTag.plan(m_maxCacheAge) == CacheTag::ValidateCached) { 03743 kDebug(7113) << "Cache needs validation"; 03744 if (m_request.responseCode == 304) { 03745 kDebug(7113) << "...was revalidated by response code but not by updated expire times. " 03746 "We're going to set the expire date to 60 seconds in the future..."; 03747 m_request.cacheTag.expireDate = currentDate + 60; 03748 if (m_request.cacheTag.policy == CC_Verify && 03749 m_request.cacheTag.plan(m_maxCacheAge) != CacheTag::UseCached) { 03750 // "apparently" because we /could/ have made an error ourselves, but the errors I 03751 // witnessed were all the server's fault. 03752 kDebug(7113) << "this proxy or server apparently sends bogus expiry information."; 03753 } 03754 } 03755 } 03756 03757 // validation handling 03758 if (mayCache && m_request.responseCode == 200 && !m_mimeType.isEmpty()) { 03759 kDebug(7113) << "Cache, adding" << m_request.url.url(); 03760 // ioMode can still be ReadFromCache here if we're performing a conditional get 03761 // aka validation 03762 m_request.cacheTag.ioMode = WriteToCache; 03763 if (!cacheFileOpenWrite()) { 03764 kDebug(7113) << "Error creating cache entry for " << m_request.url.url()<<"!\n"; 03765 } 03766 m_maxCacheSize = config()->readEntry("MaxCacheSize", DEFAULT_MAX_CACHE_SIZE); 03767 } else if (m_request.responseCode == 304 && m_request.cacheTag.file) { 03768 if (!mayCache) { 03769 kDebug(7113) << "This webserver is confused about the cacheability of the data it sends."; 03770 } 03771 // the cache file should still be open for reading, see satisfyRequestFromCache(). 03772 Q_ASSERT(m_request.cacheTag.file->openMode() == QIODevice::ReadOnly); 03773 Q_ASSERT(m_request.cacheTag.ioMode == ReadFromCache); 03774 } else { 03775 cacheFileClose(); 03776 } 03777 03778 setCacheabilityMetadata(mayCache); 03779 } 03780 03781 void HTTPProtocol::setCacheabilityMetadata(bool cachingAllowed) 03782 { 03783 if (!cachingAllowed) { 03784 setMetaData(QLatin1String("no-cache"), QLatin1String("true")); 03785 setMetaData(QLatin1String("expire-date"), QLatin1String("1")); // Expired 03786 } else { 03787 QString tmp; 03788 tmp.setNum(m_request.cacheTag.expireDate); 03789 setMetaData(QLatin1String("expire-date"), tmp); 03790 // slightly changed semantics from old creationDate, probably more correct now 03791 tmp.setNum(m_request.cacheTag.servedDate); 03792 setMetaData(QLatin1String("cache-creation-date"), tmp); 03793 } 03794 } 03795 03796 bool HTTPProtocol::sendBody() 03797 { 03798 infoMessage( i18n( "Requesting data to send" ) ); 03799 03800 int readFromApp = -1; 03801 03802 // m_POSTbuf will NOT be empty iff authentication was required before posting 03803 // the data OR a re-connect is requested from ::readResponseHeader because the 03804 // connection was lost for some reason. 03805 if (m_POSTbuf.isEmpty()) 03806 { 03807 kDebug(7113) << "POST'ing live data..."; 03808 03809 QByteArray buffer; 03810 03811 do { 03812 m_POSTbuf.append(buffer); 03813 buffer.clear(); 03814 dataReq(); // Request for data 03815 readFromApp = readData(buffer); 03816 } while (readFromApp > 0); 03817 } 03818 else 03819 { 03820 kDebug(7113) << "POST'ing saved data..."; 03821 readFromApp = 0; 03822 } 03823 03824 if (readFromApp < 0) 03825 { 03826 error(ERR_ABORTED, m_request.url.host()); 03827 return false; 03828 } 03829 03830 infoMessage(i18n("Sending data to %1" , m_request.url.host())); 03831 03832 const QByteArray cLength = QByteArray("Content-Length: ") + QByteArray::number(m_POSTbuf.size()) + "\r\n\r\n"; 03833 kDebug(7113) << cLength.trimmed(); 03834 03835 // Send the content length... 03836 bool sendOk = (write(cLength.constData(), cLength.size()) == (ssize_t) cLength.size()); 03837 if (!sendOk) { 03838 // The server might have closed the connection due to a timeout, or maybe 03839 // some transport problem arose while the connection was idle. 03840 if (m_request.isKeepAlive) 03841 { 03842 httpCloseConnection(); 03843 return true; // Try again 03844 } 03845 03846 kDebug(7113) << "Connection broken while sending POST content size to" << m_request.url.host(); 03847 error( ERR_CONNECTION_BROKEN, m_request.url.host() ); 03848 return false; 03849 } 03850 03851 // Send the data... 03852 //kDebug(7113) << "POST DATA:" << QString::fromLocal8Bit(m_POSTbuf); 03853 sendOk = (write(m_POSTbuf.data(), m_POSTbuf.size()) == (ssize_t) m_POSTbuf.size()); 03854 if (!sendOk) 03855 { 03856 kDebug(7113) << "Connection broken when sending message body: (" 03857 << m_request.url.host() << ")"; 03858 error( ERR_CONNECTION_BROKEN, m_request.url.host() ); 03859 return false; 03860 } 03861 03862 return true; 03863 } 03864 03865 void HTTPProtocol::httpClose( bool keepAlive ) 03866 { 03867 kDebug(7113) << "keepAlive =" << keepAlive; 03868 03869 cacheFileClose(); 03870 03871 // Only allow persistent connections for GET requests. 03872 // NOTE: we might even want to narrow this down to non-form 03873 // based submit requests which will require a meta-data from 03874 // khtml. 03875 if (keepAlive) { 03876 if (!m_request.keepAliveTimeout) 03877 m_request.keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT; 03878 else if (m_request.keepAliveTimeout > 2*DEFAULT_KEEP_ALIVE_TIMEOUT) 03879 m_request.keepAliveTimeout = 2*DEFAULT_KEEP_ALIVE_TIMEOUT; 03880 03881 kDebug(7113) << "keep alive (" << m_request.keepAliveTimeout << ")"; 03882 QByteArray data; 03883 QDataStream stream( &data, QIODevice::WriteOnly ); 03884 stream << int(99); // special: Close connection 03885 setTimeoutSpecialCommand(m_request.keepAliveTimeout, data); 03886 03887 return; 03888 } 03889 03890 httpCloseConnection(); 03891 } 03892 03893 void HTTPProtocol::closeConnection() 03894 { 03895 kDebug(7113); 03896 httpCloseConnection(); 03897 } 03898 03899 void HTTPProtocol::httpCloseConnection() 03900 { 03901 kDebug(7113); 03902 m_request.isKeepAlive = false; 03903 m_server.clear(); 03904 disconnectFromHost(); 03905 clearUnreadBuffer(); 03906 setTimeoutSpecialCommand(-1); // Cancel any connection timeout 03907 } 03908 03909 void HTTPProtocol::slave_status() 03910 { 03911 kDebug(7113); 03912 03913 if ( !isConnected() ) 03914 httpCloseConnection(); 03915 03916 slaveStatus( m_server.url.host(), isConnected() ); 03917 } 03918 03919 void HTTPProtocol::mimetype( const KUrl& url ) 03920 { 03921 kDebug(7113) << url.url(); 03922 03923 if (!maybeSetRequestUrl(url)) 03924 return; 03925 resetSessionSettings(); 03926 03927 m_request.method = HTTP_HEAD; 03928 m_request.cacheTag.policy= CC_Cache; 03929 03930 proceedUntilResponseHeader(); 03931 httpClose(m_request.isKeepAlive); 03932 finished(); 03933 03934 kDebug(7113) << "http: mimetype =" << m_mimeType; 03935 } 03936 03937 void HTTPProtocol::special( const QByteArray &data ) 03938 { 03939 kDebug(7113); 03940 03941 int tmp; 03942 QDataStream stream(data); 03943 03944 stream >> tmp; 03945 switch (tmp) { 03946 case 1: // HTTP POST 03947 { 03948 KUrl url; 03949 stream >> url; 03950 post( url ); 03951 break; 03952 } 03953 case 2: // cache_update 03954 { 03955 KUrl url; 03956 bool no_cache; 03957 qint64 expireDate; 03958 stream >> url >> no_cache >> expireDate; 03959 if (no_cache) { 03960 QString filename = cacheFilePathFromUrl(url); 03961 // there is a tiny risk of deleting the wrong file due to hash collisions here. 03962 // this is an unimportant performance issue. 03963 // FIXME on Windows we may be unable to delete the file if open 03964 QFile::remove(filename); 03965 finished(); 03966 break; 03967 } 03968 // let's be paranoid and inefficient here... 03969 HTTPRequest savedRequest = m_request; 03970 03971 m_request.url = url; 03972 if (cacheFileOpenRead()) { 03973 m_request.cacheTag.expireDate = expireDate; 03974 cacheFileClose(); // this sends an update command to the cache cleaner process 03975 } 03976 03977 m_request = savedRequest; 03978 finished(); 03979 break; 03980 } 03981 case 5: // WebDAV lock 03982 { 03983 KUrl url; 03984 QString scope, type, owner; 03985 stream >> url >> scope >> type >> owner; 03986 davLock( url, scope, type, owner ); 03987 break; 03988 } 03989 case 6: // WebDAV unlock 03990 { 03991 KUrl url; 03992 stream >> url; 03993 davUnlock( url ); 03994 break; 03995 } 03996 case 7: // Generic WebDAV 03997 { 03998 KUrl url; 03999 int method; 04000 stream >> url >> method; 04001 davGeneric( url, (KIO::HTTP_METHOD) method ); 04002 break; 04003 } 04004 case 99: // Close Connection 04005 { 04006 httpCloseConnection(); 04007 break; 04008 } 04009 default: 04010 // Some command we don't understand. 04011 // Just ignore it, it may come from some future version of KDE. 04012 break; 04013 } 04014 } 04015 04019 int HTTPProtocol::readChunked() 04020 { 04021 if ((m_iBytesLeft == 0) || (m_iBytesLeft == NO_SIZE)) 04022 { 04023 // discard CRLF from previous chunk, if any, and read size of next chunk 04024 04025 int bufPos = 0; 04026 m_receiveBuf.resize(4096); 04027 04028 bool foundCrLf = readDelimitedText(m_receiveBuf.data(), &bufPos, m_receiveBuf.size(), 1); 04029 04030 if (foundCrLf && bufPos == 2) { 04031 // The previous read gave us the CRLF from the previous chunk. As bufPos includes 04032 // the trailing CRLF it has to be > 2 to possibly include the next chunksize. 04033 bufPos = 0; 04034 foundCrLf = readDelimitedText(m_receiveBuf.data(), &bufPos, m_receiveBuf.size(), 1); 04035 } 04036 if (!foundCrLf) { 04037 kDebug(7113) << "Failed to read chunk header."; 04038 return -1; 04039 } 04040 Q_ASSERT(bufPos > 2); 04041 04042 long long nextChunkSize = STRTOLL(m_receiveBuf.data(), 0, 16); 04043 if (nextChunkSize < 0) 04044 { 04045 kDebug(7113) << "Negative chunk size"; 04046 return -1; 04047 } 04048 m_iBytesLeft = nextChunkSize; 04049 04050 kDebug(7113) << "Chunk size =" << m_iBytesLeft << "bytes"; 04051 04052 if (m_iBytesLeft == 0) 04053 { 04054 // Last chunk; read and discard chunk trailer. 04055 // The last trailer line ends with CRLF and is followed by another CRLF 04056 // so we have CRLFCRLF like at the end of a standard HTTP header. 04057 // Do not miss a CRLFCRLF spread over two of our 4K blocks: keep three previous bytes. 04058 //NOTE the CRLF after the chunksize also counts if there is no trailer. Copy it over. 04059 char trash[4096]; 04060 trash[0] = m_receiveBuf.constData()[bufPos - 2]; 04061 trash[1] = m_receiveBuf.constData()[bufPos - 1]; 04062 int trashBufPos = 2; 04063 bool done = false; 04064 while (!done && !m_isEOF) { 04065 if (trashBufPos > 3) { 04066 // shift everything but the last three bytes out of the buffer 04067 for (int i = 0; i < 3; i++) { 04068 trash[i] = trash[trashBufPos - 3 + i]; 04069 } 04070 trashBufPos = 3; 04071 } 04072 done = readDelimitedText(trash, &trashBufPos, 4096, 2); 04073 } 04074 if (m_isEOF && !done) { 04075 kDebug(7113) << "Failed to read chunk trailer."; 04076 return -1; 04077 } 04078 04079 return 0; 04080 } 04081 } 04082 04083 int bytesReceived = readLimited(); 04084 if (!m_iBytesLeft) { 04085 m_iBytesLeft = NO_SIZE; // Don't stop, continue with next chunk 04086 } 04087 return bytesReceived; 04088 } 04089 04090 int HTTPProtocol::readLimited() 04091 { 04092 if (!m_iBytesLeft) 04093 return 0; 04094 04095 m_receiveBuf.resize(4096); 04096 04097 int bytesToReceive; 04098 if (m_iBytesLeft > KIO::filesize_t(m_receiveBuf.size())) 04099 bytesToReceive = m_receiveBuf.size(); 04100 else 04101 bytesToReceive = m_iBytesLeft; 04102 04103 const int bytesReceived = readBuffered(m_receiveBuf.data(), bytesToReceive, false); 04104 04105 if (bytesReceived <= 0) 04106 return -1; // Error: connection lost 04107 04108 m_iBytesLeft -= bytesReceived; 04109 return bytesReceived; 04110 } 04111 04112 int HTTPProtocol::readUnlimited() 04113 { 04114 if (m_request.isKeepAlive) 04115 { 04116 kDebug(7113) << "Unbounded datastream on a Keep-alive connection!"; 04117 m_request.isKeepAlive = false; 04118 } 04119 04120 m_receiveBuf.resize(4096); 04121 04122 int result = readBuffered(m_receiveBuf.data(), m_receiveBuf.size()); 04123 if (result > 0) 04124 return result; 04125 04126 m_isEOF = true; 04127 m_iBytesLeft = 0; 04128 return 0; 04129 } 04130 04131 void HTTPProtocol::slotData(const QByteArray &_d) 04132 { 04133 if (!_d.size()) 04134 { 04135 m_isEOD = true; 04136 return; 04137 } 04138 04139 if (m_iContentLeft != NO_SIZE) 04140 { 04141 if (m_iContentLeft >= KIO::filesize_t(_d.size())) 04142 m_iContentLeft -= _d.size(); 04143 else 04144 m_iContentLeft = NO_SIZE; 04145 } 04146 04147 QByteArray d = _d; 04148 if ( !m_dataInternal ) 04149 { 04150 // If a broken server does not send the mime-type, 04151 // we try to id it from the content before dealing 04152 // with the content itself. 04153 if ( m_mimeType.isEmpty() && !m_isRedirection && 04154 !( m_request.responseCode >= 300 && m_request.responseCode <=399) ) 04155 { 04156 kDebug(7113) << "Determining mime-type from content..."; 04157 int old_size = m_mimeTypeBuffer.size(); 04158 m_mimeTypeBuffer.resize( old_size + d.size() ); 04159 memcpy( m_mimeTypeBuffer.data() + old_size, d.data(), d.size() ); 04160 if ( (m_iBytesLeft != NO_SIZE) && (m_iBytesLeft > 0) 04161 && (m_mimeTypeBuffer.size() < 1024) ) 04162 { 04163 m_cpMimeBuffer = true; 04164 return; // Do not send up the data since we do not yet know its mimetype! 04165 } 04166 04167 kDebug(7113) << "Mimetype buffer size:" << m_mimeTypeBuffer.size(); 04168 04169 KMimeType::Ptr mime = KMimeType::findByNameAndContent(m_request.url.fileName(), m_mimeTypeBuffer); 04170 if( mime && !mime->isDefault() ) 04171 { 04172 m_mimeType = mime->name(); 04173 kDebug(7113) << "Mimetype from content:" << m_mimeType; 04174 } 04175 04176 if ( m_mimeType.isEmpty() ) 04177 { 04178 m_mimeType = QLatin1String( DEFAULT_MIME_TYPE ); 04179 kDebug(7113) << "Using default mimetype:" << m_mimeType; 04180 } 04181 04182 //### we could also open the cache file here 04183 04184 if ( m_cpMimeBuffer ) 04185 { 04186 d.resize(0); 04187 d.resize(m_mimeTypeBuffer.size()); 04188 memcpy(d.data(), m_mimeTypeBuffer.data(), d.size()); 04189 } 04190 mimeType(m_mimeType); 04191 m_mimeTypeBuffer.resize(0); 04192 } 04193 04194 //kDebug(7113) << "Sending data of size" << d.size(); 04195 data( d ); 04196 if (m_request.cacheTag.ioMode == WriteToCache) { 04197 cacheFileWritePayload(d); 04198 } 04199 } 04200 else 04201 { 04202 uint old_size = m_webDavDataBuf.size(); 04203 m_webDavDataBuf.resize (old_size + d.size()); 04204 memcpy (m_webDavDataBuf.data() + old_size, d.data(), d.size()); 04205 } 04206 } 04207 04217 bool HTTPProtocol::readBody( bool dataInternal /* = false */ ) 04218 { 04219 // special case for reading cached body since we also do it in this function. oh well. 04220 if (!canHaveResponseBody(m_request.responseCode, m_request.method) && 04221 !(m_request.cacheTag.ioMode == ReadFromCache && m_request.responseCode == 304 && 04222 m_request.method != HTTP_HEAD)) { 04223 return true; 04224 } 04225 04226 m_isEOD = false; 04227 // Note that when dataInternal is true, we are going to: 04228 // 1) save the body data to a member variable, m_webDavDataBuf 04229 // 2) _not_ advertise the data, speed, size, etc., through the 04230 // corresponding functions. 04231 // This is used for returning data to WebDAV. 04232 m_dataInternal = dataInternal; 04233 if (dataInternal) { 04234 m_webDavDataBuf.clear(); 04235 } 04236 04237 // Check if we need to decode the data. 04238 // If we are in copy mode, then use only transfer decoding. 04239 bool useMD5 = !m_contentMD5.isEmpty(); 04240 04241 // Deal with the size of the file. 04242 KIO::filesize_t sz = m_request.offset; 04243 if ( sz ) 04244 m_iSize += sz; 04245 04246 if (!m_isRedirection) { 04247 // Update the application with total size except when 04248 // it is compressed, or when the data is to be handled 04249 // internally (webDAV). If compressed we have to wait 04250 // until we uncompress to find out the actual data size 04251 if ( !dataInternal ) { 04252 if ((m_iSize > 0) && (m_iSize != NO_SIZE)) { 04253 totalSize(m_iSize); 04254 infoMessage(i18n("Retrieving %1 from %2...", KIO::convertSize(m_iSize), 04255 m_request.url.host())); 04256 } else { 04257 totalSize (0); 04258 } 04259 } else { 04260 infoMessage(i18n("Retrieving from %1...", m_request.url.host())); 04261 } 04262 04263 if (m_request.cacheTag.ioMode == ReadFromCache) { 04264 kDebug(7113) << "reading data from cache..."; 04265 04266 m_iContentLeft = NO_SIZE; 04267 04268 QByteArray d; 04269 while (true) { 04270 d = cacheFileReadPayload(MAX_IPC_SIZE); 04271 if (d.isEmpty()) { 04272 break; 04273 } 04274 slotData(d); 04275 sz += d.size(); 04276 if (!dataInternal) { 04277 processedSize(sz); 04278 } 04279 } 04280 04281 m_receiveBuf.resize(0); 04282 04283 if (!dataInternal) { 04284 data(QByteArray()); 04285 } 04286 04287 return true; 04288 } 04289 } 04290 04291 if (m_iSize != NO_SIZE) 04292 m_iBytesLeft = m_iSize - sz; 04293 else 04294 m_iBytesLeft = NO_SIZE; 04295 04296 m_iContentLeft = m_iBytesLeft; 04297 04298 if (m_isChunked) 04299 m_iBytesLeft = NO_SIZE; 04300 04301 kDebug(7113) << KIO::number(m_iBytesLeft) << "bytes left."; 04302 04303 // Main incoming loop... Gather everything while we can... 04304 m_cpMimeBuffer = false; 04305 m_mimeTypeBuffer.resize(0); 04306 04307 HTTPFilterChain chain; 04308 04309 // redirection ignores the body 04310 if (!m_isRedirection) { 04311 QObject::connect(&chain, SIGNAL(output(const QByteArray &)), 04312 this, SLOT(slotData(const QByteArray &))); 04313 } 04314 QObject::connect(&chain, SIGNAL(error(const QString &)), 04315 this, SLOT(slotFilterError(const QString &))); 04316 04317 // decode all of the transfer encodings 04318 while (!m_transferEncodings.isEmpty()) 04319 { 04320 QString enc = m_transferEncodings.takeLast(); 04321 if ( enc == QLatin1String("gzip") ) 04322 chain.addFilter(new HTTPFilterGZip); 04323 else if ( enc == QLatin1String("deflate") ) 04324 chain.addFilter(new HTTPFilterDeflate); 04325 } 04326 04327 // From HTTP 1.1 Draft 6: 04328 // The MD5 digest is computed based on the content of the entity-body, 04329 // including any content-coding that has been applied, but not including 04330 // any transfer-encoding applied to the message-body. If the message is 04331 // received with a transfer-encoding, that encoding MUST be removed 04332 // prior to checking the Content-MD5 value against the received entity. 04333 HTTPFilterMD5 *md5Filter = 0; 04334 if ( useMD5 ) 04335 { 04336 md5Filter = new HTTPFilterMD5; 04337 chain.addFilter(md5Filter); 04338 } 04339 04340 // now decode all of the content encodings 04341 // -- Why ?? We are not 04342 // -- a proxy server, be a client side implementation!! The applications 04343 // -- are capable of determinig how to extract the encoded implementation. 04344 // WB: That's a misunderstanding. We are free to remove the encoding. 04345 // WB: Some braindead www-servers however, give .tgz files an encoding 04346 // WB: of "gzip" (or even "x-gzip") and a content-type of "applications/tar" 04347 // WB: They shouldn't do that. We can work around that though... 04348 while (!m_contentEncodings.isEmpty()) 04349 { 04350 QString enc = m_contentEncodings.takeLast(); 04351 if ( enc == QLatin1String("gzip") ) 04352 chain.addFilter(new HTTPFilterGZip); 04353 else if ( enc == QLatin1String("deflate") ) 04354 chain.addFilter(new HTTPFilterDeflate); 04355 } 04356 04357 while (!m_isEOF) 04358 { 04359 int bytesReceived; 04360 04361 if (m_isChunked) 04362 bytesReceived = readChunked(); 04363 else if (m_iSize != NO_SIZE) 04364 bytesReceived = readLimited(); 04365 else 04366 bytesReceived = readUnlimited(); 04367 04368 // make sure that this wasn't an error, first 04369 // kDebug(7113) << "bytesReceived:" 04370 // << (int) bytesReceived << " m_iSize:" << (int) m_iSize << " Chunked:" 04371 // << m_isChunked << " BytesLeft:"<< (int) m_iBytesLeft; 04372 if (bytesReceived == -1) 04373 { 04374 if (m_iContentLeft == 0) 04375 { 04376 // gzip'ed data sometimes reports a too long content-length. 04377 // (The length of the unzipped data) 04378 m_iBytesLeft = 0; 04379 break; 04380 } 04381 // Oh well... log an error and bug out 04382 kDebug(7113) << "bytesReceived==-1 sz=" << (int)sz 04383 << " Connection broken !"; 04384 error(ERR_CONNECTION_BROKEN, m_request.url.host()); 04385 return false; 04386 } 04387 04388 // I guess that nbytes == 0 isn't an error.. but we certainly 04389 // won't work with it! 04390 if (bytesReceived > 0) 04391 { 04392 // Important: truncate the buffer to the actual size received! 04393 // Otherwise garbage will be passed to the app 04394 m_receiveBuf.truncate( bytesReceived ); 04395 04396 chain.slotInput(m_receiveBuf); 04397 04398 if (m_isError) 04399 return false; 04400 04401 sz += bytesReceived; 04402 if (!dataInternal) 04403 processedSize( sz ); 04404 } 04405 m_receiveBuf.resize(0); // res 04406 04407 if (m_iBytesLeft && m_isEOD && !m_isChunked) 04408 { 04409 // gzip'ed data sometimes reports a too long content-length. 04410 // (The length of the unzipped data) 04411 m_iBytesLeft = 0; 04412 } 04413 04414 if (m_iBytesLeft == 0) 04415 { 04416 kDebug(7113) << "EOD received! Left ="<< KIO::number(m_iBytesLeft); 04417 break; 04418 } 04419 } 04420 chain.slotInput(QByteArray()); // Flush chain. 04421 04422 if ( useMD5 ) 04423 { 04424 QString calculatedMD5 = md5Filter->md5(); 04425 04426 if ( m_contentMD5 != calculatedMD5 ) 04427 kWarning(7113) << "MD5 checksum MISMATCH! Expected:" 04428 << calculatedMD5 << ", Got:" << m_contentMD5; 04429 } 04430 04431 // Close cache entry 04432 if (m_iBytesLeft == 0) { 04433 cacheFileClose(); // no-op if not necessary 04434 } 04435 04436 if (sz <= 1) 04437 { 04438 if (m_request.responseCode >= 500 && m_request.responseCode <= 599) { 04439 error(ERR_INTERNAL_SERVER, m_request.url.host()); 04440 return false; 04441 } else if (m_request.responseCode >= 400 && m_request.responseCode <= 499 && 04442 !isAuthenticationRequired(m_request.responseCode) && 04443 // If we're doing a propfind, a 404 is not an error 04444 m_request.method != DAV_PROPFIND) { 04445 error(ERR_DOES_NOT_EXIST, m_request.url.host()); 04446 return false; 04447 } 04448 } 04449 04450 if (!dataInternal && !m_isRedirection) 04451 data( QByteArray() ); 04452 return true; 04453 } 04454 04455 void HTTPProtocol::slotFilterError(const QString &text) 04456 { 04457 error(KIO::ERR_SLAVE_DEFINED, text); 04458 } 04459 04460 void HTTPProtocol::error( int _err, const QString &_text ) 04461 { 04462 httpClose(false); 04463 04464 if (!m_request.id.isEmpty()) 04465 { 04466 forwardHttpResponseHeader(); 04467 sendMetaData(); 04468 } 04469 04470 // It's over, we don't need it anymore 04471 m_POSTbuf.clear(); 04472 04473 SlaveBase::error( _err, _text ); 04474 m_isError = true; 04475 } 04476 04477 04478 void HTTPProtocol::addCookies( const QString &url, const QByteArray &cookieHeader ) 04479 { 04480 qlonglong windowId = m_request.windowId.toLongLong(); 04481 QDBusInterface kcookiejar( QLatin1String("org.kde.kded"), QLatin1String("/modules/kcookiejar"), QLatin1String("org.kde.KCookieServer") ); 04482 (void)kcookiejar.call( QDBus::NoBlock, QLatin1String("addCookies"), url, 04483 cookieHeader, windowId ); 04484 } 04485 04486 QString HTTPProtocol::findCookies( const QString &url) 04487 { 04488 qlonglong windowId = m_request.windowId.toLongLong(); 04489 QDBusInterface kcookiejar( QLatin1String("org.kde.kded"), QLatin1String("/modules/kcookiejar"), QLatin1String("org.kde.KCookieServer") ); 04490 QDBusReply<QString> reply = kcookiejar.call( QLatin1String("findCookies"), url, windowId ); 04491 04492 if ( !reply.isValid() ) 04493 { 04494 kWarning(7113) << "Can't communicate with kded_kcookiejar!"; 04495 return QString(); 04496 } 04497 return reply; 04498 } 04499 04500 /******************************* CACHING CODE ****************************/ 04501 04502 HTTPProtocol::CacheTag::CachePlan HTTPProtocol::CacheTag::plan(time_t maxCacheAge) const 04503 { 04504 //notable omission: we're not checking cache file presence or integrity 04505 if (policy == KIO::CC_CacheOnly || policy == KIO::CC_Cache) { 04506 return UseCached; 04507 } else if (policy == KIO::CC_Refresh) { 04508 return ValidateCached; 04509 } else if (policy == KIO::CC_Reload) { 04510 return IgnoreCached; 04511 } 04512 Q_ASSERT(policy == CC_Verify); 04513 time_t currentDate = time(0); 04514 if ((servedDate != -1 && currentDate > servedDate + maxCacheAge) || 04515 (expireDate != -1 && currentDate > expireDate)) { 04516 return ValidateCached; 04517 } 04518 return UseCached; 04519 } 04520 04521 // !START SYNC! 04522 // The following code should be kept in sync 04523 // with the code in http_cache_cleaner.cpp 04524 04525 // we use QDataStream; this is just an illustration 04526 struct BinaryCacheFileHeader 04527 { 04528 quint8 version[2]; 04529 quint8 compression; // for now fixed to 0 04530 quint8 reserved; // for now; also alignment 04531 qint32 useCount; 04532 qint64 servedDate; 04533 qint64 lastModifiedDate; 04534 qint64 expireDate; 04535 qint32 bytesCached; 04536 // packed size should be 36 bytes; we explicitly set it here to make sure that no compiler 04537 // padding ruins it. We write the fields to disk without any padding. 04538 static const int size = 36; 04539 }; 04540 04541 enum CacheCleanerCommandCode { 04542 InvalidCommand = 0, 04543 CreateFileNotificationCommand, 04544 UpdateFileCommand 04545 }; 04546 04547 // illustration for cache cleaner update "commands" 04548 struct CacheCleanerCommand 04549 { 04550 BinaryCacheFileHeader header; 04551 quint32 commandCode; 04552 // filename in ASCII, binary isn't worth the coding and decoding 04553 quint8 filename[s_hashedUrlNibbles]; 04554 }; 04555 04556 QByteArray HTTPProtocol::CacheTag::serialize() const 04557 { 04558 QByteArray ret; 04559 QDataStream stream(&ret, QIODevice::WriteOnly); 04560 stream << quint8('A'); 04561 stream << quint8('\n'); 04562 stream << quint8(0); 04563 stream << quint8(0); 04564 04565 stream << fileUseCount; 04566 04567 // time_t overflow will only be checked when reading; we have no way to tell here. 04568 stream << qint64(servedDate); 04569 stream << qint64(lastModifiedDate); 04570 stream << qint64(expireDate); 04571 04572 stream << bytesCached; 04573 Q_ASSERT(ret.size() == BinaryCacheFileHeader::size); 04574 return ret; 04575 } 04576 04577 04578 static bool compareByte(QDataStream *stream, quint8 value) 04579 { 04580 quint8 byte; 04581 *stream >> byte; 04582 return byte == value; 04583 } 04584 04585 static bool readTime(QDataStream *stream, time_t *time) 04586 { 04587 qint64 intTime = 0; 04588 *stream >> intTime; 04589 *time = static_cast<time_t>(intTime); 04590 04591 qint64 check = static_cast<qint64>(*time); 04592 return check == intTime; 04593 } 04594 04595 // If starting a new file cacheFileWriteVariableSizeHeader() must have been called *before* 04596 // calling this! This is to fill in the headerEnd field. 04597 // If the file is not new headerEnd has already been read from the file and in fact the variable 04598 // size header *may* not be rewritten because a size change would mess up the file layout. 04599 bool HTTPProtocol::CacheTag::deserialize(const QByteArray &d) 04600 { 04601 if (d.size() != BinaryCacheFileHeader::size) { 04602 return false; 04603 } 04604 QDataStream stream(d); 04605 stream.setVersion(QDataStream::Qt_4_5); 04606 04607 bool ok = true; 04608 ok = ok && compareByte(&stream, 'A'); 04609 ok = ok && compareByte(&stream, '\n'); 04610 ok = ok && compareByte(&stream, 0); 04611 ok = ok && compareByte(&stream, 0); 04612 if (!ok) { 04613 return false; 04614 } 04615 04616 stream >> fileUseCount; 04617 04618 // read and check for time_t overflow 04619 ok = ok && readTime(&stream, &servedDate); 04620 ok = ok && readTime(&stream, &lastModifiedDate); 04621 ok = ok && readTime(&stream, &expireDate); 04622 if (!ok) { 04623 return false; 04624 } 04625 04626 stream >> bytesCached; 04627 04628 return true; 04629 } 04630 04631 /* Text part of the header, directly following the binary first part: 04632 URL\n 04633 etag\n 04634 mimetype\n 04635 header line\n 04636 header line\n 04637 ... 04638 \n 04639 */ 04640 04641 static KUrl storableUrl(const KUrl &url) 04642 { 04643 KUrl ret(url); 04644 ret.setPassword(QString()); 04645 ret.setFragment(QString()); 04646 return ret; 04647 } 04648 04649 static void writeLine(QIODevice *dev, const QByteArray &line) 04650 { 04651 static const char linefeed = '\n'; 04652 dev->write(line); 04653 dev->write(&linefeed, 1); 04654 } 04655 04656 void HTTPProtocol::cacheFileWriteTextHeader() 04657 { 04658 QFile *&file = m_request.cacheTag.file; 04659 Q_ASSERT(file); 04660 Q_ASSERT(file->openMode() & QIODevice::WriteOnly); 04661 04662 file->seek(BinaryCacheFileHeader::size); 04663 writeLine(file, storableUrl(m_request.url).toEncoded()); 04664 writeLine(file, m_request.cacheTag.etag.toLatin1()); 04665 writeLine(file, m_mimeType.toLatin1()); 04666 writeLine(file, m_responseHeaders.join(QString(QLatin1Char('\n'))).toLatin1()); 04667 // join("\n") adds no \n to the end, but writeLine() does. 04668 // Add another newline to mark the end of text. 04669 writeLine(file, QByteArray()); 04670 } 04671 04672 static bool readLineChecked(QIODevice *dev, QByteArray *line) 04673 { 04674 *line = dev->readLine(8192); 04675 // if nothing read or the line didn't fit into 8192 bytes(!) 04676 if (line->isEmpty() || !line->endsWith('\n')) { 04677 return false; 04678 } 04679 // we don't actually want the newline! 04680 line->chop(1); 04681 return true; 04682 } 04683 04684 bool HTTPProtocol::cacheFileReadTextHeader1(const KUrl &desiredUrl) 04685 { 04686 QFile *&file = m_request.cacheTag.file; 04687 Q_ASSERT(file); 04688 Q_ASSERT(file->openMode() == QIODevice::ReadOnly); 04689 04690 QByteArray readBuf; 04691 bool ok = readLineChecked(file, &readBuf); 04692 if (storableUrl(desiredUrl).toEncoded() != readBuf) { 04693 kDebug(7103) << "You have witnessed a very improbable hash collision!"; 04694 return false; 04695 } 04696 04697 ok = ok && readLineChecked(file, &readBuf); 04698 m_request.cacheTag.etag = toQString(readBuf); 04699 04700 return ok; 04701 } 04702 04703 bool HTTPProtocol::cacheFileReadTextHeader2() 04704 { 04705 QFile *&file = m_request.cacheTag.file; 04706 Q_ASSERT(file); 04707 Q_ASSERT(file->openMode() == QIODevice::ReadOnly); 04708 04709 bool ok = true; 04710 QByteArray readBuf; 04711 #ifndef NDEBUG 04712 // we assume that the URL and etag have already been read 04713 qint64 oldPos = file->pos(); 04714 file->seek(BinaryCacheFileHeader::size); 04715 ok = ok && readLineChecked(file, &readBuf); 04716 ok = ok && readLineChecked(file, &readBuf); 04717 Q_ASSERT(file->pos() == oldPos); 04718 #endif 04719 ok = ok && readLineChecked(file, &readBuf); 04720 m_mimeType = toQString(readBuf); 04721 04722 m_responseHeaders.clear(); 04723 // read as long as no error and no empty line found 04724 while (true) { 04725 ok = ok && readLineChecked(file, &readBuf); 04726 if (ok && !readBuf.isEmpty()) { 04727 m_responseHeaders.append(toQString(readBuf)); 04728 } else { 04729 break; 04730 } 04731 } 04732 return ok; // it may still be false ;) 04733 } 04734 04735 static QString filenameFromUrl(const KUrl &url) 04736 { 04737 QCryptographicHash hash(QCryptographicHash::Sha1); 04738 hash.addData(storableUrl(url).toEncoded()); 04739 return toQString(hash.result().toHex()); 04740 } 04741 04742 QString HTTPProtocol::cacheFilePathFromUrl(const KUrl &url) const 04743 { 04744 QString filePath = m_strCacheDir; 04745 if (!filePath.endsWith(QLatin1Char('/'))) { 04746 filePath.append(QLatin1Char('/')); 04747 } 04748 filePath.append(filenameFromUrl(url)); 04749 return filePath; 04750 } 04751 04752 bool HTTPProtocol::cacheFileOpenRead() 04753 { 04754 kDebug(7113); 04755 QString filename = cacheFilePathFromUrl(m_request.url); 04756 04757 QFile *&file = m_request.cacheTag.file; 04758 if (file) { 04759 kDebug(7113) << "File unexpectedly open; old file is" << file->fileName() 04760 << "new name is" << filename; 04761 Q_ASSERT(file->fileName() == filename); 04762 } 04763 Q_ASSERT(!file); 04764 file = new QFile(filename); 04765 if (file->open(QIODevice::ReadOnly)) { 04766 QByteArray header = file->read(BinaryCacheFileHeader::size); 04767 if (!m_request.cacheTag.deserialize(header)) { 04768 kDebug(7103) << "Cache file header is invalid."; 04769 04770 file->close(); 04771 } 04772 } 04773 04774 if (file->isOpen() && !cacheFileReadTextHeader1(m_request.url)) { 04775 file->close(); 04776 } 04777 04778 if (!file->isOpen()) { 04779 cacheFileClose(); 04780 return false; 04781 } 04782 return true; 04783 } 04784 04785 04786 bool HTTPProtocol::cacheFileOpenWrite() 04787 { 04788 kDebug(7113); 04789 QString filename = cacheFilePathFromUrl(m_request.url); 04790 04791 // if we open a cache file for writing while we have a file open for reading we must have 04792 // found out that the old cached content is obsolete, so delete the file. 04793 QFile *&file = m_request.cacheTag.file; 04794 if (file) { 04795 // ensure that the file is in a known state - either open for reading or null 04796 Q_ASSERT(!qobject_cast<QTemporaryFile *>(file)); 04797 Q_ASSERT((file->openMode() & QIODevice::WriteOnly) == 0); 04798 Q_ASSERT(file->fileName() == filename); 04799 kDebug(7113) << "deleting expired cache entry and recreating."; 04800 file->remove(); 04801 delete file; 04802 file = 0; 04803 } 04804 04805 // note that QTemporaryFile will automatically append random chars to filename 04806 file = new QTemporaryFile(filename); 04807 file->open(QIODevice::WriteOnly); 04808 04809 // if we have started a new file we have not initialized some variables from disk data. 04810 m_request.cacheTag.fileUseCount = 0; // the file has not been *read* yet 04811 m_request.cacheTag.bytesCached = 0; 04812 04813 if ((file->openMode() & QIODevice::WriteOnly) == 0) { 04814 kDebug(7113) << "Could not open file for writing:" << file->fileName() 04815 << "due to error" << file->error(); 04816 cacheFileClose(); 04817 return false; 04818 } 04819 return true; 04820 } 04821 04822 static QByteArray makeCacheCleanerCommand(const HTTPProtocol::CacheTag &cacheTag, 04823 CacheCleanerCommandCode cmd) 04824 { 04825 QByteArray ret = cacheTag.serialize(); 04826 QDataStream stream(&ret, QIODevice::WriteOnly); 04827 stream.setVersion(QDataStream::Qt_4_5); 04828 04829 stream.skipRawData(BinaryCacheFileHeader::size); 04830 // append the command code 04831 stream << quint32(cmd); 04832 // append the filename 04833 QString fileName = cacheTag.file->fileName(); 04834 int basenameStart = fileName.lastIndexOf(QLatin1Char('/')) + 1; 04835 QByteArray baseName = fileName.mid(basenameStart, s_hashedUrlNibbles).toLatin1(); 04836 stream.writeRawData(baseName.constData(), baseName.size()); 04837 04838 Q_ASSERT(ret.size() == BinaryCacheFileHeader::size + sizeof(quint32) + s_hashedUrlNibbles); 04839 return ret; 04840 } 04841 04842 //### not yet 100% sure when and when not to call this 04843 void HTTPProtocol::cacheFileClose() 04844 { 04845 kDebug(7113); 04846 04847 QFile *&file = m_request.cacheTag.file; 04848 if (!file) { 04849 return; 04850 } 04851 04852 m_request.cacheTag.ioMode = NoCache; 04853 04854 QByteArray ccCommand; 04855 QTemporaryFile *tempFile = qobject_cast<QTemporaryFile *>(file); 04856 04857 if (file->openMode() & QIODevice::WriteOnly) { 04858 Q_ASSERT(tempFile); 04859 04860 if (m_request.cacheTag.bytesCached && !m_isError) { 04861 QByteArray header = m_request.cacheTag.serialize(); 04862 tempFile->seek(0); 04863 tempFile->write(header); 04864 04865 ccCommand = makeCacheCleanerCommand(m_request.cacheTag, CreateFileNotificationCommand); 04866 04867 QString oldName = tempFile->fileName(); 04868 QString newName = oldName; 04869 int basenameStart = newName.lastIndexOf(QLatin1Char('/')) + 1; 04870 // remove the randomized name part added by QTemporaryFile 04871 newName.chop(newName.length() - basenameStart - s_hashedUrlNibbles); 04872 kDebug(7113) << "Renaming temporary file" << oldName << "to" << newName; 04873 04874 // on windows open files can't be renamed 04875 tempFile->setAutoRemove(false); 04876 delete tempFile; 04877 file = 0; 04878 04879 if (!QFile::rename(oldName, newName)) { 04880 // ### currently this hides a minor bug when force-reloading a resource. We 04881 // should not even open a new file for writing in that case. 04882 kDebug(7113) << "Renaming temporary file failed, deleting it instead."; 04883 QFile::remove(oldName); 04884 ccCommand.clear(); // we have nothing of value to tell the cache cleaner 04885 } 04886 } else { 04887 // oh, we've never written payload data to the cache file. 04888 // the temporary file is closed and removed and no proper cache entry is created. 04889 } 04890 } else if (file->openMode() == QIODevice::ReadOnly) { 04891 Q_ASSERT(!tempFile); 04892 ccCommand = makeCacheCleanerCommand(m_request.cacheTag, UpdateFileCommand); 04893 } 04894 delete file; 04895 file = 0; 04896 04897 if (!ccCommand.isEmpty()) { 04898 sendCacheCleanerCommand(ccCommand); 04899 } 04900 } 04901 04902 void HTTPProtocol::sendCacheCleanerCommand(const QByteArray &command) 04903 { 04904 kDebug(7113); 04905 Q_ASSERT(command.size() == BinaryCacheFileHeader::size + s_hashedUrlNibbles + sizeof(quint32)); 04906 int attempts = 0; 04907 while (m_cacheCleanerConnection.state() != QLocalSocket::ConnectedState && attempts < 6) { 04908 if (attempts == 2) { 04909 KToolInvocation::startServiceByDesktopPath(QLatin1String("http_cache_cleaner.desktop")); 04910 } 04911 QString socketFileName = KStandardDirs::locateLocal("socket", QLatin1String("kio_http_cache_cleaner")); 04912 m_cacheCleanerConnection.connectToServer(socketFileName, QIODevice::WriteOnly); 04913 m_cacheCleanerConnection.waitForConnected(1500); 04914 attempts++; 04915 } 04916 04917 if (m_cacheCleanerConnection.state() == QLocalSocket::ConnectedState) { 04918 m_cacheCleanerConnection.write(command); 04919 m_cacheCleanerConnection.flush(); 04920 } else { 04921 // updating the stats is not vital, so we just give up. 04922 kDebug(7113) << "Could not connect to cache cleaner, not updating stats of this cache file."; 04923 } 04924 } 04925 04926 QByteArray HTTPProtocol::cacheFileReadPayload(int maxLength) 04927 { 04928 Q_ASSERT(m_request.cacheTag.file); 04929 Q_ASSERT(m_request.cacheTag.ioMode == ReadFromCache); 04930 Q_ASSERT(m_request.cacheTag.file->openMode() == QIODevice::ReadOnly); 04931 QByteArray ret = m_request.cacheTag.file->read(maxLength); 04932 if (ret.isEmpty()) { 04933 cacheFileClose(); 04934 } 04935 return ret; 04936 } 04937 04938 04939 void HTTPProtocol::cacheFileWritePayload(const QByteArray &d) 04940 { 04941 if (!m_request.cacheTag.file) { 04942 return; 04943 } 04944 04945 // If the file being downloaded is so big that it exceeds the max cache size, 04946 // do not cache it! See BR# 244215. NOTE: this can be improved upon in the 04947 // future... 04948 if (m_iSize >= (m_maxCacheSize * 1024)) { 04949 kDebug(7113) << "Caching diabled because content size is too big."; 04950 cacheFileClose(); 04951 return; 04952 } 04953 04954 Q_ASSERT(m_request.cacheTag.ioMode == WriteToCache); 04955 Q_ASSERT(m_request.cacheTag.file->openMode() & QIODevice::WriteOnly); 04956 04957 if (d.isEmpty()) { 04958 cacheFileClose(); 04959 } 04960 04961 //TODO: abort if file grows too big! 04962 04963 // write the variable length text header as soon as we start writing to the file 04964 if (!m_request.cacheTag.bytesCached) { 04965 cacheFileWriteTextHeader(); 04966 } 04967 m_request.cacheTag.bytesCached += d.size(); 04968 m_request.cacheTag.file->write(d); 04969 } 04970 04971 // The above code should be kept in sync 04972 // with the code in http_cache_cleaner.cpp 04973 // !END SYNC! 04974 04975 //************************** AUTHENTICATION CODE ********************/ 04976 04977 QString HTTPProtocol::authenticationHeader() 04978 { 04979 QByteArray ret; 04980 04981 // If the internal meta-data "cached-www-auth" is set, then check for cached 04982 // authentication data and preemtively send the authentication header if a 04983 // matching one is found. 04984 if (!m_wwwAuth && config()->readEntry("cached-www-auth", false)) { 04985 KIO::AuthInfo authinfo; 04986 authinfo.url = m_request.url; 04987 authinfo.realmValue = config()->readEntry("www-auth-realm", QString()); 04988 // If no relam metadata, then make sure path matching is turned on. 04989 authinfo.verifyPath = (authinfo.realmValue.isEmpty()); 04990 04991 if (checkCachedAuthentication(authinfo)) { 04992 const QByteArray cachedChallenge = authinfo.digestInfo.toLatin1(); 04993 if (!cachedChallenge.isEmpty()) { 04994 m_wwwAuth = KAbstractHttpAuthentication::newAuth(cachedChallenge, config()); 04995 if (m_wwwAuth) { 04996 kDebug(7113) << "Creating WWW authentcation object from cached info"; 04997 m_wwwAuth->setChallenge(cachedChallenge, m_request.url, m_request.methodString()); 04998 m_wwwAuth->generateResponse(authinfo.username, authinfo.password); 04999 } 05000 } 05001 } 05002 } 05003 05004 // If the internal meta-data "cached-proxy-auth" is set, then check for cached 05005 // authentication data and preemtively send the authentication header if a 05006 // matching one is found. 05007 if (!m_proxyAuth && config()->readEntry("cached-proxy-auth", false)) { 05008 KIO::AuthInfo authinfo; 05009 authinfo.url = m_request.proxyUrl; 05010 authinfo.realmValue = config()->readEntry("proxy-auth-realm", QString()); 05011 // If no relam metadata, then make sure path matching is turned on. 05012 authinfo.verifyPath = (authinfo.realmValue.isEmpty()); 05013 05014 if (checkCachedAuthentication(authinfo)) { 05015 const QByteArray cachedChallenge = authinfo.digestInfo.toLatin1(); 05016 if (!cachedChallenge.isEmpty()) { 05017 m_proxyAuth = KAbstractHttpAuthentication::newAuth(cachedChallenge, config()); 05018 if (m_proxyAuth) { 05019 kDebug(7113) << "Creating Proxy authentcation object from cached info"; 05020 m_proxyAuth->setChallenge(cachedChallenge, m_request.proxyUrl, m_request.methodString()); 05021 m_proxyAuth->generateResponse(authinfo.username, authinfo.password); 05022 } 05023 } 05024 } 05025 } 05026 05027 // the authentication classes don't know if they are for proxy or webserver authentication... 05028 if (m_wwwAuth && !m_wwwAuth->isError()) { 05029 ret += "Authorization: "; 05030 ret += m_wwwAuth->headerFragment(); 05031 } 05032 05033 if (m_proxyAuth && !m_proxyAuth->isError()) { 05034 ret += "Proxy-Authorization: "; 05035 ret += m_proxyAuth->headerFragment(); 05036 } 05037 05038 return toQString(ret); // ## encoding ok? 05039 } 05040 05041 05042 void HTTPProtocol::proxyAuthenticationForSocket(const QNetworkProxy &proxy, QAuthenticator *authenticator) 05043 { 05044 Q_UNUSED(proxy); 05045 kDebug(7113) << "Authenticator received -- realm:" << authenticator->realm() << "user:" 05046 << authenticator->user(); 05047 05048 AuthInfo info; 05049 Q_ASSERT(proxy.hostName() == m_request.proxyUrl.host() && proxy.port() == m_request.proxyUrl.port()); 05050 info.url = m_request.proxyUrl; 05051 info.realmValue = authenticator->realm(); 05052 info.verifyPath = true; //### whatever 05053 info.username = authenticator->user(); 05054 05055 const bool haveCachedCredentials = checkCachedAuthentication(info); 05056 05057 // if m_socketProxyAuth is a valid pointer then authentication has been attempted before, 05058 // and it was not successful. see below and saveProxyAuthenticationForSocket(). 05059 if (!haveCachedCredentials || m_socketProxyAuth) { 05060 // Save authentication info if the connection succeeds. We need to disconnect 05061 // this after saving the auth data (or an error) so we won't save garbage afterwards! 05062 connect(socket(), SIGNAL(connected()), 05063 this, SLOT(saveProxyAuthenticationForSocket())); 05064 //### fillPromptInfo(&info); 05065 info.prompt = i18n("You need to supply a username and a password for " 05066 "the proxy server listed below before you are allowed " 05067 "to access any sites."); 05068 info.keepPassword = true; 05069 info.commentLabel = i18n("Proxy:"); 05070 info.comment = i18n("<b>%1</b> at <b>%2</b>", info.realmValue, m_request.proxyUrl.host()); 05071 const bool dataEntered = openPasswordDialog(info, i18n("Proxy Authentication Failed.")); 05072 if (!dataEntered) { 05073 kDebug(7103) << "looks like the user canceled proxy authentication."; 05074 error(ERR_USER_CANCELED, m_request.proxyUrl.host()); 05075 } 05076 } 05077 authenticator->setUser(info.username); 05078 authenticator->setPassword(info.password); 05079 05080 if (m_socketProxyAuth) { 05081 *m_socketProxyAuth = *authenticator; 05082 } else { 05083 m_socketProxyAuth = new QAuthenticator(*authenticator); 05084 } 05085 05086 m_request.proxyUrl.setUser(info.username); 05087 m_request.proxyUrl.setPassword(info.password); 05088 } 05089 05090 void HTTPProtocol::saveProxyAuthenticationForSocket() 05091 { 05092 kDebug(7113) << "Saving authenticator"; 05093 disconnect(socket(), SIGNAL(connected()), 05094 this, SLOT(saveProxyAuthenticationForSocket())); 05095 Q_ASSERT(m_socketProxyAuth); 05096 if (m_socketProxyAuth) { 05097 kDebug(7113) << "-- realm:" << m_socketProxyAuth->realm() << "user:" 05098 << m_socketProxyAuth->user(); 05099 KIO::AuthInfo a; 05100 a.verifyPath = true; 05101 a.url = m_request.proxyUrl; 05102 a.realmValue = m_socketProxyAuth->realm(); 05103 a.username = m_socketProxyAuth->user(); 05104 a.password = m_socketProxyAuth->password(); 05105 cacheAuthentication(a); 05106 } 05107 delete m_socketProxyAuth; 05108 m_socketProxyAuth = 0; 05109 } 05110 05111 #include "http.moc"
KDE 4.6 API Reference