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