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