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