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