• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.14.38 API Reference
  • KDE Home
  • Contact Us
 

KIOSlave

  • kioslave
  • http
http.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2000-2003 Waldo Bastian <bastian@kde.org>
3 Copyright (C) 2000-2002 George Staikos <staikos@kde.org>
4 Copyright (C) 2000-2002 Dawit Alemayehu <adawit@kde.org>
5 Copyright (C) 2001,2002 Hamish Rodda <rodda@kde.org>
6 Copyright (C) 2007 Nick Shaforostoff <shafff@ukr.net>
7 Copyright (C) 2007 Daniel Nicoletti <mirttex@users.sourceforge.net>
8 Copyright (C) 2008,2009 Andreas Hartmetz <ahartmetz@gmail.com>
9
10 This library is free software; you can redistribute it and/or
11 modify it under the terms of the GNU Library General Public
12 License (LGPL) as published by the Free Software Foundation;
13 either version 2 of the License, or (at your option) any later
14 version.
15
16 This library is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 Library General Public License for more details.
20
21 You should have received a copy of the GNU Library General Public License
22 along with this library; see the file COPYING.LIB. If not, write to
23 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 Boston, MA 02110-1301, USA.
25*/
26
27// TODO delete / do not save very big files; "very big" to be defined
28
29#define QT_NO_CAST_FROM_ASCII
30
31#include "http.h"
32
33#include <config.h>
34
35#include <fcntl.h>
36#include <utime.h>
37#include <stdlib.h>
38#include <stdio.h>
39#include <sys/stat.h>
40#include <sys/time.h>
41#include <unistd.h> // must be explicitly included for MacOSX
42
43#include <QtXml/qdom.h>
44#include <QtCore/QFile>
45#include <QtCore/QRegExp>
46#include <QtCore/QDate>
47#include <QtCore/QBuffer>
48#include <QtCore/QIODevice>
49#include <QtDBus/QtDBus>
50#include <QtNetwork/QAuthenticator>
51#include <QtNetwork/QNetworkProxy>
52#include <QtNetwork/QTcpSocket>
53
54#include <kurl.h>
55#include <kdebug.h>
56#include <klocale.h>
57#include <kconfig.h>
58#include <kconfiggroup.h>
59#include <kservice.h>
60#include <kdatetime.h>
61#include <kcomponentdata.h>
62#include <kmimetype.h>
63#include <ktoolinvocation.h>
64#include <kstandarddirs.h>
65#include <kremoteencoding.h>
66#include <ktcpsocket.h>
67#include <kmessagebox.h>
68
69#include <kio/ioslave_defaults.h>
70#include <kio/http_slave_defaults.h>
71
72#include <httpfilter.h>
73
74#include <solid/networking.h>
75
76#include <kapplication.h>
77#include <kaboutdata.h>
78#include <kcmdlineargs.h>
79#include <kde_file.h>
80#include <ktemporaryfile.h>
81
82#include "httpauthentication.h"
83
84// HeaderTokenizer declarations
85#include "parsinghelpers.h"
86//string parsing helpers and HeaderTokenizer implementation
87#include "parsinghelpers.cpp"
88
89// KDE5 TODO (QT5) : use QString::htmlEscape or whatever https://qt.gitorious.org/qt/qtbase/merge_requests/56
90// ends up with.
91static QString htmlEscape(const QString &plain)
92{
93 QString rich;
94 rich.reserve(int(plain.length() * 1.1));
95 for (int i = 0; i < plain.length(); ++i) {
96 if (plain.at(i) == QLatin1Char('<'))
97 rich += QLatin1String("&lt;");
98 else if (plain.at(i) == QLatin1Char('>'))
99 rich += QLatin1String("&gt;");
100 else if (plain.at(i) == QLatin1Char('&'))
101 rich += QLatin1String("&amp;");
102 else if (plain.at(i) == QLatin1Char('"'))
103 rich += QLatin1String("&quot;");
104 else
105 rich += plain.at(i);
106 }
107 rich.squeeze();
108 return rich;
109}
110
111static bool supportedProxyScheme(const QString& scheme)
112{
113 // scheme is supposed to be lowercase
114 return (scheme.startsWith(QLatin1String("http"))
115 || scheme == QLatin1String("socks"));
116}
117
118// see filenameFromUrl(): a sha1 hash is 160 bits
119static const int s_hashedUrlBits = 160; // this number should always be divisible by eight
120static const int s_hashedUrlNibbles = s_hashedUrlBits / 4;
121static const int s_hashedUrlBytes = s_hashedUrlBits / 8;
122static const int s_MaxInMemPostBufSize = 256 * 1024; // Write anyting over 256 KB to file...
123
124using namespace KIO;
125
126extern "C" int KDE_EXPORT kdemain( int argc, char **argv )
127{
128 QCoreApplication app( argc, argv ); // needed for QSocketNotifier
129 KComponentData componentData( "kio_http", "kdelibs4" );
130 (void) KGlobal::locale();
131
132 if (argc != 4)
133 {
134 fprintf(stderr, "Usage: kio_http protocol domain-socket1 domain-socket2\n");
135 exit(-1);
136 }
137
138 HTTPProtocol slave(argv[1], argv[2], argv[3]);
139 slave.dispatchLoop();
140 return 0;
141}
142
143/*********************************** Generic utility functions ********************/
144
145static QString toQString(const QByteArray& value)
146{
147 return QString::fromLatin1(value.constData(), value.size());
148}
149
150static bool isCrossDomainRequest( const QString& fqdn, const QString& originURL )
151{
152 //TODO read the RFC
153 if (originURL == QLatin1String("true")) // Backwards compatibility
154 return true;
155
156 KUrl url ( originURL );
157
158 // Document Origin domain
159 QString a = url.host();
160 // Current request domain
161 QString b = fqdn;
162
163 if (a == b)
164 return false;
165
166 QStringList la = a.split(QLatin1Char('.'), QString::SkipEmptyParts);
167 QStringList lb = b.split(QLatin1Char('.'), QString::SkipEmptyParts);
168
169 if (qMin(la.count(), lb.count()) < 2) {
170 return true; // better safe than sorry...
171 }
172
173 while(la.count() > 2)
174 la.pop_front();
175 while(lb.count() > 2)
176 lb.pop_front();
177
178 return la != lb;
179}
180
181/*
182 Eliminates any custom header that could potentially alter the request
183*/
184static QString sanitizeCustomHTTPHeader(const QString& _header)
185{
186 QString sanitizedHeaders;
187 const QStringList headers = _header.split(QRegExp(QLatin1String("[\r\n]")));
188
189 for(QStringList::ConstIterator it = headers.begin(); it != headers.end(); ++it)
190 {
191 // Do not allow Request line to be specified and ignore
192 // the other HTTP headers.
193 if (!(*it).contains(QLatin1Char(':')) ||
194 (*it).startsWith(QLatin1String("host"), Qt::CaseInsensitive) ||
195 (*it).startsWith(QLatin1String("proxy-authorization"), Qt::CaseInsensitive) ||
196 (*it).startsWith(QLatin1String("via"), Qt::CaseInsensitive))
197 continue;
198
199 sanitizedHeaders += (*it);
200 sanitizedHeaders += QLatin1String("\r\n");
201 }
202 sanitizedHeaders.chop(2);
203
204 return sanitizedHeaders;
205}
206
207static bool isPotentialSpoofingAttack(const HTTPProtocol::HTTPRequest& request, const KConfigGroup* config)
208{
209 // kDebug(7113) << request.url << "response code: " << request.responseCode << "previous response code:" << request.prevResponseCode;
210 if (config->readEntry("no-spoof-check", false)) {
211 return false;
212 }
213
214 if (request.url.user().isEmpty()) {
215 return false;
216 }
217
218 // We already have cached authentication.
219 if (config->readEntry(QLatin1String("cached-www-auth"), false)) {
220 return false;
221 }
222
223 const QString userName = config->readEntry(QLatin1String("LastSpoofedUserName"), QString());
224 return ((userName.isEmpty() || userName != request.url.user()) && request.responseCode != 401 && request.prevResponseCode != 401);
225}
226
227// for a given response code, conclude if the response is going to/likely to have a response body
228static bool canHaveResponseBody(int responseCode, KIO::HTTP_METHOD method)
229{
230/* RFC 2616 says...
231 1xx: false
232 200: method HEAD: false, otherwise:true
233 201: true
234 202: true
235 203: see 200
236 204: false
237 205: false
238 206: true
239 300: see 200
240 301: see 200
241 302: see 200
242 303: see 200
243 304: false
244 305: probably like 300, RFC seems to expect disconnection afterwards...
245 306: (reserved), for simplicity do it just like 200
246 307: see 200
247 4xx: see 200
248 5xx :see 200
249*/
250 if (responseCode >= 100 && responseCode < 200) {
251 return false;
252 }
253 switch (responseCode) {
254 case 201:
255 case 202:
256 case 206:
257 // RFC 2616 does not mention HEAD in the description of the above. if the assert turns out
258 // to be a problem the response code should probably be treated just like 200 and friends.
259 Q_ASSERT(method != HTTP_HEAD);
260 return true;
261 case 204:
262 case 205:
263 case 304:
264 return false;
265 default:
266 break;
267 }
268 // safe (and for most remaining response codes exactly correct) default
269 return method != HTTP_HEAD;
270}
271
272static bool isEncryptedHttpVariety(const QByteArray &p)
273{
274 return p == "https" || p == "webdavs";
275}
276
277static bool isValidProxy(const KUrl &u)
278{
279 return u.isValid() && u.hasHost();
280}
281
282static bool isHttpProxy(const KUrl &u)
283{
284 return isValidProxy(u) && u.protocol() == QLatin1String("http");
285}
286
287static QIODevice* createPostBufferDeviceFor (KIO::filesize_t size)
288{
289 QIODevice* device;
290 if (size > static_cast<KIO::filesize_t>(s_MaxInMemPostBufSize))
291 device = new KTemporaryFile;
292 else
293 device = new QBuffer;
294
295 if (!device->open(QIODevice::ReadWrite))
296 return 0;
297
298 return device;
299}
300
301static qint64 toTime_t(const QString& value, KDateTime::TimeFormat format)
302{
303 const KDateTime dt = KDateTime::fromString(value, format);
304 return (dt.isValid() ? (dt.toUtc().dateTime().toMSecsSinceEpoch()/1000) : -1);
305}
306
307static qint64 parseDateTime( const QString& input, const QString& type )
308{
309 if (type == QLatin1String("dateTime.tz") ) {
310 return toTime_t(input, KDateTime::ISODate);
311 } else if (type == QLatin1String("dateTime.rfc1123")) {
312 return toTime_t(input, KDateTime::RFCDate);
313 }
314
315 // format not advertised... try to parse anyway
316 qint64 tsec = toTime_t(input, KDateTime::RFCDate);
317 if (tsec == -1)
318 tsec = toTime_t(input, KDateTime::ISODate);
319
320 return tsec;
321}
322
323// Since a lot of webdav servers seem not to send the content-type information
324// for the requested directory listings, we attempt to guess the mime-type from
325// the resource name so long as the resource is not a directory.
326static void updateUDSEntryMimeType(UDSEntry* entry)
327{
328 const QString mimeType(entry->stringValue(KIO::UDSEntry::UDS_MIME_TYPE));
329 const qint64 type = entry->numberValue(KIO::UDSEntry::UDS_FILE_TYPE);
330 const QString name (entry->stringValue(KIO::UDSEntry::UDS_NAME));
331
332 kDebug(7113) << "item:" << name << ", mimeType:" << mimeType;
333
334 if (mimeType.isEmpty() && type != S_IFDIR) {
335 KMimeType::Ptr mime = KMimeType::findByUrl(name, 0, false, true);
336 if (mime && !mime->isDefault()) {
337 kDebug(7113) << "Setting" << mime->name() << "as guessed mime type for" << name;
338 entry->insert(KIO::UDSEntry::UDS_GUESSED_MIME_TYPE, mime->name());
339 }
340 }
341}
342
343static void changeProtocolToHttp(KUrl* url)
344{
345 const QString protocol(url->protocol());
346 if (protocol == QLatin1String("webdavs")) {
347 url->setProtocol(QLatin1String("https"));
348 } else if (protocol == QLatin1String("webdav")) {
349 url->setProtocol(QLatin1String("http"));
350 }
351}
352
353/************************************************************************************************************************/
354
355
356QByteArray HTTPProtocol::HTTPRequest::methodString() const
357{
358 if (!methodStringOverride.isEmpty())
359 return (methodStringOverride).toLatin1();
360
361 switch(method) {
362 case HTTP_GET:
363 return "GET";
364 case HTTP_PUT:
365 return "PUT";
366 case HTTP_POST:
367 return "POST";
368 case HTTP_HEAD:
369 return "HEAD";
370 case HTTP_DELETE:
371 return "DELETE";
372 case HTTP_OPTIONS:
373 return "OPTIONS";
374 case DAV_PROPFIND:
375 return "PROPFIND";
376 case DAV_PROPPATCH:
377 return "PROPPATCH";
378 case DAV_MKCOL:
379 return "MKCOL";
380 case DAV_COPY:
381 return "COPY";
382 case DAV_MOVE:
383 return "MOVE";
384 case DAV_LOCK:
385 return "LOCK";
386 case DAV_UNLOCK:
387 return "UNLOCK";
388 case DAV_SEARCH:
389 return "SEARCH";
390 case DAV_SUBSCRIBE:
391 return "SUBSCRIBE";
392 case DAV_UNSUBSCRIBE:
393 return "UNSUBSCRIBE";
394 case DAV_POLL:
395 return "POLL";
396 case DAV_NOTIFY:
397 return "NOTIFY";
398 case DAV_REPORT:
399 return "REPORT";
400 default:
401 Q_ASSERT(false);
402 return QByteArray();
403 }
404}
405
406static QString formatHttpDate(qint64 date)
407{
408 KDateTime dt;
409 dt.setTime_t(date);
410 QString ret = dt.toString(KDateTime::RFCDateDay);
411 ret.chop(6); // remove " +0000"
412 // RFCDate[Day] omits the second if zero, but HTTP requires it; see bug 240585.
413 if (!dt.time().second()) {
414 ret.append(QLatin1String(":00"));
415 }
416 ret.append(QLatin1String(" GMT"));
417 return ret;
418}
419
420static bool isAuthenticationRequired(int responseCode)
421{
422 return (responseCode == 401) || (responseCode == 407);
423}
424
425#define NO_SIZE ((KIO::filesize_t) -1)
426
427#ifdef HAVE_STRTOLL
428#define STRTOLL strtoll
429#else
430#define STRTOLL strtol
431#endif
432
433
434/************************************** HTTPProtocol **********************************************/
435
436
437HTTPProtocol::HTTPProtocol( const QByteArray &protocol, const QByteArray &pool,
438 const QByteArray &app )
439 : TCPSlaveBase(protocol, pool, app, isEncryptedHttpVariety(protocol))
440 , m_iSize(NO_SIZE)
441 , m_iPostDataSize(NO_SIZE)
442 , m_isBusy(false)
443 , m_POSTbuf(0)
444 , m_maxCacheAge(DEFAULT_MAX_CACHE_AGE)
445 , m_maxCacheSize(DEFAULT_MAX_CACHE_SIZE)
446 , m_protocol(protocol)
447 , m_wwwAuth(0)
448 , m_proxyAuth(0)
449 , m_socketProxyAuth(0)
450 , m_iError(0)
451 , m_isLoadingErrorPage(false)
452 , m_remoteRespTimeout(DEFAULT_RESPONSE_TIMEOUT)
453 , m_iEOFRetryCount(0)
454{
455 reparseConfiguration();
456 setBlocking(true);
457 connect(socket(), SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
458 this, SLOT(proxyAuthenticationForSocket(QNetworkProxy,QAuthenticator*)));
459}
460
461HTTPProtocol::~HTTPProtocol()
462{
463 httpClose(false);
464}
465
466void HTTPProtocol::reparseConfiguration()
467{
468 kDebug(7113);
469
470 delete m_proxyAuth;
471 delete m_wwwAuth;
472 m_proxyAuth = 0;
473 m_wwwAuth = 0;
474 m_request.proxyUrl.clear(); //TODO revisit
475 m_request.proxyUrls.clear();
476
477 TCPSlaveBase::reparseConfiguration();
478}
479
480void HTTPProtocol::resetConnectionSettings()
481{
482 m_isEOF = false;
483 m_iError = 0;
484 m_isLoadingErrorPage = false;
485}
486
487quint16 HTTPProtocol::defaultPort() const
488{
489 return isEncryptedHttpVariety(m_protocol) ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT;
490}
491
492void HTTPProtocol::resetResponseParsing()
493{
494 m_isRedirection = false;
495 m_isChunked = false;
496 m_iSize = NO_SIZE;
497 clearUnreadBuffer();
498
499 m_responseHeaders.clear();
500 m_contentEncodings.clear();
501 m_transferEncodings.clear();
502 m_contentMD5.clear();
503 m_mimeType.clear();
504
505 setMetaData(QLatin1String("request-id"), m_request.id);
506}
507
508void HTTPProtocol::resetSessionSettings()
509{
510 // Follow HTTP/1.1 spec and enable keep-alive by default
511 // unless the remote side tells us otherwise or we determine
512 // the persistent link has been terminated by the remote end.
513 m_request.isKeepAlive = true;
514 m_request.keepAliveTimeout = 0;
515
516 m_request.redirectUrl = KUrl();
517 m_request.useCookieJar = config()->readEntry("Cookies", false);
518 m_request.cacheTag.useCache = config()->readEntry("UseCache", true);
519 m_request.preferErrorPage = config()->readEntry("errorPage", true);
520 const bool noAuth = config()->readEntry("no-auth", false);
521 m_request.doNotWWWAuthenticate = config()->readEntry("no-www-auth", noAuth);
522 m_request.doNotProxyAuthenticate = config()->readEntry("no-proxy-auth", noAuth);
523 m_strCacheDir = config()->readPathEntry("CacheDir", QString());
524 m_maxCacheAge = config()->readEntry("MaxCacheAge", DEFAULT_MAX_CACHE_AGE);
525 m_request.windowId = config()->readEntry("window-id");
526
527 m_request.methodStringOverride = metaData(QLatin1String("CustomHTTPMethod"));
528 m_request.sentMethodString.clear();
529
530 kDebug(7113) << "Window Id =" << m_request.windowId;
531 kDebug(7113) << "ssl_was_in_use =" << metaData(QLatin1String("ssl_was_in_use"));
532
533 m_request.referrer.clear();
534 // RFC 2616: do not send the referrer if the referrer page was served using SSL and
535 // the current page does not use SSL.
536 if ( config()->readEntry("SendReferrer", true) &&
537 (isEncryptedHttpVariety(m_protocol) || metaData(QLatin1String("ssl_was_in_use")) != QLatin1String("TRUE") ) )
538 {
539 KUrl refUrl(metaData(QLatin1String("referrer")));
540 if (refUrl.isValid()) {
541 // Sanitize
542 QString protocol = refUrl.protocol();
543 if (protocol.startsWith(QLatin1String("webdav"))) {
544 protocol.replace(0, 6, QLatin1String("http"));
545 refUrl.setProtocol(protocol);
546 }
547
548 if (protocol.startsWith(QLatin1String("http"))) {
549 m_request.referrer = toQString(refUrl.toEncoded(QUrl::RemoveUserInfo | QUrl::RemoveFragment));
550 }
551 }
552 }
553
554 if (config()->readEntry("SendLanguageSettings", true)) {
555 m_request.charsets = config()->readEntry("Charsets", DEFAULT_PARTIAL_CHARSET_HEADER);
556 if (!m_request.charsets.contains(QLatin1String("*;"), Qt::CaseInsensitive)) {
557 m_request.charsets += QLatin1String(",*;q=0.5");
558 }
559 m_request.languages = config()->readEntry("Languages", DEFAULT_LANGUAGE_HEADER);
560 } else {
561 m_request.charsets.clear();
562 m_request.languages.clear();
563 }
564
565 // Adjust the offset value based on the "resume" meta-data.
566 QString resumeOffset = metaData(QLatin1String("resume"));
567 if (!resumeOffset.isEmpty()) {
568 m_request.offset = resumeOffset.toULongLong();
569 } else {
570 m_request.offset = 0;
571 }
572 // Same procedure for endoffset.
573 QString resumeEndOffset = metaData(QLatin1String("resume_until"));
574 if (!resumeEndOffset.isEmpty()) {
575 m_request.endoffset = resumeEndOffset.toULongLong();
576 } else {
577 m_request.endoffset = 0;
578 }
579
580 m_request.disablePassDialog = config()->readEntry("DisablePassDlg", false);
581 m_request.allowTransferCompression = config()->readEntry("AllowCompressedPage", true);
582 m_request.id = metaData(QLatin1String("request-id"));
583
584 // Store user agent for this host.
585 if (config()->readEntry("SendUserAgent", true)) {
586 m_request.userAgent = metaData(QLatin1String("UserAgent"));
587 } else {
588 m_request.userAgent.clear();
589 }
590
591 m_request.cacheTag.etag.clear();
592
593 m_request.cacheTag.servedDate = -1;
594 m_request.cacheTag.lastModifiedDate = -1;
595 m_request.cacheTag.expireDate = -1;
596
597 m_request.responseCode = 0;
598 m_request.prevResponseCode = 0;
599
600 delete m_wwwAuth;
601 m_wwwAuth = 0;
602 delete m_socketProxyAuth;
603 m_socketProxyAuth = 0;
604
605 // Obtain timeout values
606 m_remoteRespTimeout = responseTimeout();
607
608 // Bounce back the actual referrer sent
609 setMetaData(QLatin1String("referrer"), m_request.referrer);
610
611 // Reset the post data size
612 m_iPostDataSize = NO_SIZE;
613
614 // Reset the EOF retry counter
615 m_iEOFRetryCount = 0;
616}
617
618void HTTPProtocol::setHost( const QString& host, quint16 port,
619 const QString& user, const QString& pass )
620{
621 // Reset the webdav-capable flags for this host
622 if ( m_request.url.host() != host )
623 m_davHostOk = m_davHostUnsupported = false;
624
625 m_request.url.setHost(host);
626
627 // is it an IPv6 address?
628 if (host.indexOf(QLatin1Char(':')) == -1) {
629 m_request.encoded_hostname = toQString(QUrl::toAce(host));
630 } else {
631 int pos = host.indexOf(QLatin1Char('%'));
632 if (pos == -1)
633 m_request.encoded_hostname = QLatin1Char('[') + host + QLatin1Char(']');
634 else
635 // don't send the scope-id in IPv6 addresses to the server
636 m_request.encoded_hostname = QLatin1Char('[') + host.left(pos) + QLatin1Char(']');
637 }
638 m_request.url.setPort((port > 0 && port != defaultPort()) ? port : -1);
639 m_request.url.setUser(user);
640 m_request.url.setPass(pass);
641
642 // On new connection always clear previous proxy information...
643 m_request.proxyUrl.clear();
644 m_request.proxyUrls.clear();
645
646 kDebug(7113) << "Hostname is now:" << m_request.url.host()
647 << "(" << m_request.encoded_hostname << ")";
648}
649
650bool HTTPProtocol::maybeSetRequestUrl(const KUrl &u)
651{
652 kDebug(7113) << u;
653
654 m_request.url = u;
655 m_request.url.setPort(u.port(defaultPort()) != defaultPort() ? u.port() : -1);
656
657 if (u.host().isEmpty()) {
658 error( KIO::ERR_UNKNOWN_HOST, i18n("No host specified."));
659 return false;
660 }
661
662 if (u.path().isEmpty()) {
663 KUrl newUrl(u);
664 newUrl.setPath(QLatin1String("/"));
665 redirection(newUrl);
666 finished();
667 return false;
668 }
669
670 return true;
671}
672
673void HTTPProtocol::proceedUntilResponseContent( bool dataInternal /* = false */ )
674{
675 kDebug (7113);
676
677 const bool status = (proceedUntilResponseHeader() && readBody(dataInternal));
678
679 // If not an error condition or internal request, close
680 // the connection based on the keep alive settings...
681 if (!m_iError && !dataInternal) {
682 httpClose(m_request.isKeepAlive);
683 }
684
685 // if data is required internally or we got error, don't finish,
686 // it is processed before we finish()
687 if (dataInternal || !status) {
688 return;
689 }
690
691 if (!sendHttpError()) {
692 finished();
693 }
694}
695
696bool HTTPProtocol::proceedUntilResponseHeader()
697{
698 kDebug (7113);
699
700 // Retry the request until it succeeds or an unrecoverable error occurs.
701 // Recoverable errors are, for example:
702 // - Proxy or server authentication required: Ask for credentials and try again,
703 // this time with an authorization header in the request.
704 // - Server-initiated timeout on keep-alive connection: Reconnect and try again
705
706 while (true) {
707 if (!sendQuery()) {
708 return false;
709 }
710 if (readResponseHeader()) {
711 // Success, finish the request.
712 break;
713 }
714
715 // If not loading error page and the response code requires us to resend the query,
716 // then throw away any error message that might have been sent by the server.
717 if (!m_isLoadingErrorPage && isAuthenticationRequired(m_request.responseCode)) {
718 // This gets rid of any error page sent with 401 or 407 authentication required response...
719 readBody(true);
720 }
721
722 // no success, close the cache file so the cache state is reset - that way most other code
723 // doesn't have to deal with the cache being in various states.
724 cacheFileClose();
725 if (m_iError || m_isLoadingErrorPage) {
726 // Unrecoverable error, abort everything.
727 // Also, if we've just loaded an error page there is nothing more to do.
728 // In that case we abort to avoid loops; some webservers manage to send 401 and
729 // no authentication request. Or an auth request we don't understand.
730 setMetaData(QLatin1String("responsecode"), QString::number(m_request.responseCode));
731 return false;
732 }
733
734 if (!m_request.isKeepAlive) {
735 httpCloseConnection();
736 m_request.isKeepAlive = true;
737 m_request.keepAliveTimeout = 0;
738 }
739 }
740
741 // Do not save authorization if the current response code is
742 // 4xx (client error) or 5xx (server error).
743 kDebug(7113) << "Previous Response:" << m_request.prevResponseCode;
744 kDebug(7113) << "Current Response:" << m_request.responseCode;
745
746 setMetaData(QLatin1String("responsecode"), QString::number(m_request.responseCode));
747 setMetaData(QLatin1String("content-type"), m_mimeType);
748
749 // At this point sendBody() should have delivered any POST data.
750 clearPostDataBuffer();
751
752 return true;
753}
754
755void HTTPProtocol::stat(const KUrl& url)
756{
757 kDebug(7113) << url;
758
759 if (!maybeSetRequestUrl(url))
760 return;
761 resetSessionSettings();
762
763 if ( m_protocol != "webdav" && m_protocol != "webdavs" )
764 {
765 QString statSide = metaData(QLatin1String("statSide"));
766 if (statSide != QLatin1String("source"))
767 {
768 // When uploading we assume the file doesn't exit
769 error( ERR_DOES_NOT_EXIST, url.prettyUrl() );
770 return;
771 }
772
773 // When downloading we assume it exists
774 UDSEntry entry;
775 entry.insert( KIO::UDSEntry::UDS_NAME, url.fileName() );
776 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG ); // a file
777 entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH ); // readable by everybody
778
779 statEntry( entry );
780 finished();
781 return;
782 }
783
784 davStatList( url );
785}
786
787void HTTPProtocol::listDir( const KUrl& url )
788{
789 kDebug(7113) << url;
790
791 if (!maybeSetRequestUrl(url))
792 return;
793 resetSessionSettings();
794
795 davStatList( url, false );
796}
797
798void HTTPProtocol::davSetRequest( const QByteArray& requestXML )
799{
800 // insert the document into the POST buffer, kill trailing zero byte
801 cachePostData(requestXML);
802}
803
804void HTTPProtocol::davStatList( const KUrl& url, bool stat )
805{
806 UDSEntry entry;
807
808 // check to make sure this host supports WebDAV
809 if ( !davHostOk() )
810 return;
811
812 // Maybe it's a disguised SEARCH...
813 QString query = metaData(QLatin1String("davSearchQuery"));
814 if ( !query.isEmpty() )
815 {
816 QByteArray request = "<?xml version=\"1.0\"?>\r\n";
817 request.append( "<D:searchrequest xmlns:D=\"DAV:\">\r\n" );
818 request.append( query.toUtf8() );
819 request.append( "</D:searchrequest>\r\n" );
820
821 davSetRequest( request );
822 } else {
823 // We are only after certain features...
824 QByteArray request;
825 request = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
826 "<D:propfind xmlns:D=\"DAV:\">";
827
828 // insert additional XML request from the davRequestResponse metadata
829 if ( hasMetaData(QLatin1String("davRequestResponse")) )
830 request += metaData(QLatin1String("davRequestResponse")).toUtf8();
831 else {
832 // No special request, ask for default properties
833 request += "<D:prop>"
834 "<D:creationdate/>"
835 "<D:getcontentlength/>"
836 "<D:displayname/>"
837 "<D:source/>"
838 "<D:getcontentlanguage/>"
839 "<D:getcontenttype/>"
840 "<D:getlastmodified/>"
841 "<D:getetag/>"
842 "<D:supportedlock/>"
843 "<D:lockdiscovery/>"
844 "<D:resourcetype/>"
845 "</D:prop>";
846 }
847 request += "</D:propfind>";
848
849 davSetRequest( request );
850 }
851
852 // WebDAV Stat or List...
853 m_request.method = query.isEmpty() ? DAV_PROPFIND : DAV_SEARCH;
854 m_request.url.setQuery(QString());
855 m_request.cacheTag.policy = CC_Reload;
856 m_request.davData.depth = stat ? 0 : 1;
857 if (!stat)
858 m_request.url.adjustPath(KUrl::AddTrailingSlash);
859
860 proceedUntilResponseContent( true );
861 infoMessage(QLatin1String(""));
862
863 // Has a redirection already been called? If so, we're done.
864 if (m_isRedirection || m_iError) {
865 if (m_isRedirection) {
866 davFinished();
867 }
868 return;
869 }
870
871 QDomDocument multiResponse;
872 multiResponse.setContent( m_webDavDataBuf, true );
873
874 bool hasResponse = false;
875
876 // kDebug(7113) << endl << multiResponse.toString(2);
877
878 for ( QDomNode n = multiResponse.documentElement().firstChild();
879 !n.isNull(); n = n.nextSibling()) {
880 QDomElement thisResponse = n.toElement();
881 if (thisResponse.isNull())
882 continue;
883
884 hasResponse = true;
885
886 QDomElement href = thisResponse.namedItem(QLatin1String("href")).toElement();
887 if ( !href.isNull() ) {
888 entry.clear();
889
890 QString urlStr = QUrl::fromPercentEncoding(href.text().toUtf8());
891#if 0 // qt4/kde4 say: it's all utf8...
892 int encoding = remoteEncoding()->encodingMib();
893 if ((encoding == 106) && (!KStringHandler::isUtf8(KUrl::decode_string(urlStr, 4).toLatin1())))
894 encoding = 4; // Use latin1 if the file is not actually utf-8
895
896 KUrl thisURL ( urlStr, encoding );
897#else
898 KUrl thisURL( urlStr );
899#endif
900
901 if ( thisURL.isValid() ) {
902 QString name = thisURL.fileName();
903
904 // base dir of a listDir(): name should be "."
905 if ( !stat && thisURL.path(KUrl::AddTrailingSlash).length() == url.path(KUrl::AddTrailingSlash).length() )
906 name = QLatin1Char('.');
907
908 entry.insert( KIO::UDSEntry::UDS_NAME, name.isEmpty() ? href.text() : name );
909 }
910
911 QDomNodeList propstats = thisResponse.elementsByTagName(QLatin1String("propstat"));
912
913 davParsePropstats( propstats, entry );
914
915 updateUDSEntryMimeType(&entry);
916
917 if ( stat ) {
918 // return an item
919 statEntry( entry );
920 davFinished();
921 return;
922 }
923
924 listEntry( entry, false );
925 } else {
926 kDebug(7113) << "Error: no URL contained in response to PROPFIND on" << url;
927 }
928 }
929
930 if ( stat || !hasResponse ) {
931 error( ERR_DOES_NOT_EXIST, url.prettyUrl() );
932 return;
933 }
934
935 listEntry( entry, true );
936 davFinished();
937}
938
939void HTTPProtocol::davGeneric( const KUrl& url, KIO::HTTP_METHOD method, qint64 size )
940{
941 kDebug(7113) << url;
942
943 if (!maybeSetRequestUrl(url))
944 return;
945 resetSessionSettings();
946
947 // check to make sure this host supports WebDAV
948 if ( !davHostOk() )
949 return;
950
951 // WebDAV method
952 m_request.method = method;
953 m_request.url.setQuery(QString());
954 m_request.cacheTag.policy = CC_Reload;
955
956 m_iPostDataSize = (size > -1 ? static_cast<KIO::filesize_t>(size) : NO_SIZE);
957 proceedUntilResponseContent();
958}
959
960int HTTPProtocol::codeFromResponse( const QString& response )
961{
962 const int firstSpace = response.indexOf( QLatin1Char(' ') );
963 const int secondSpace = response.indexOf( QLatin1Char(' '), firstSpace + 1 );
964 return response.mid( firstSpace + 1, secondSpace - firstSpace - 1 ).toInt();
965}
966
967void HTTPProtocol::davParsePropstats( const QDomNodeList& propstats, UDSEntry& entry )
968{
969 QString mimeType;
970 bool foundExecutable = false;
971 bool isDirectory = false;
972 uint lockCount = 0;
973 uint supportedLockCount = 0;
974
975 for ( int i = 0; i < propstats.count(); i++)
976 {
977 QDomElement propstat = propstats.item(i).toElement();
978
979 QDomElement status = propstat.namedItem(QLatin1String("status")).toElement();
980 if ( status.isNull() )
981 {
982 // error, no status code in this propstat
983 kDebug(7113) << "Error, no status code in this propstat";
984 return;
985 }
986
987 int code = codeFromResponse( status.text() );
988
989 if ( code != 200 )
990 {
991 kDebug(7113) << "Got status code" << code << "(this may mean that some properties are unavailable)";
992 continue;
993 }
994
995 QDomElement prop = propstat.namedItem( QLatin1String("prop") ).toElement();
996 if ( prop.isNull() )
997 {
998 kDebug(7113) << "Error: no prop segment in this propstat.";
999 return;
1000 }
1001
1002 if ( hasMetaData( QLatin1String("davRequestResponse") ) )
1003 {
1004 QDomDocument doc;
1005 doc.appendChild(prop);
1006 entry.insert( KIO::UDSEntry::UDS_XML_PROPERTIES, doc.toString() );
1007 }
1008
1009 for ( QDomNode n = prop.firstChild(); !n.isNull(); n = n.nextSibling() )
1010 {
1011 QDomElement property = n.toElement();
1012 if (property.isNull())
1013 continue;
1014
1015 if ( property.namespaceURI() != QLatin1String("DAV:") )
1016 {
1017 // break out - we're only interested in properties from the DAV namespace
1018 continue;
1019 }
1020
1021 if ( property.tagName() == QLatin1String("creationdate") )
1022 {
1023 // Resource creation date. Should be is ISO 8601 format.
1024 entry.insert( KIO::UDSEntry::UDS_CREATION_TIME, parseDateTime( property.text(), property.attribute(QLatin1String("dt")) ) );
1025 }
1026 else if ( property.tagName() == QLatin1String("getcontentlength") )
1027 {
1028 // Content length (file size)
1029 entry.insert( KIO::UDSEntry::UDS_SIZE, property.text().toULong() );
1030 }
1031 else if ( property.tagName() == QLatin1String("displayname") )
1032 {
1033 // Name suitable for presentation to the user
1034 setMetaData( QLatin1String("davDisplayName"), property.text() );
1035 }
1036 else if ( property.tagName() == QLatin1String("source") )
1037 {
1038 // Source template location
1039 QDomElement source = property.namedItem( QLatin1String("link") ).toElement()
1040 .namedItem( QLatin1String("dst") ).toElement();
1041 if ( !source.isNull() )
1042 setMetaData( QLatin1String("davSource"), source.text() );
1043 }
1044 else if ( property.tagName() == QLatin1String("getcontentlanguage") )
1045 {
1046 // equiv. to Content-Language header on a GET
1047 setMetaData( QLatin1String("davContentLanguage"), property.text() );
1048 }
1049 else if ( property.tagName() == QLatin1String("getcontenttype") )
1050 {
1051 // Content type (mime type)
1052 // This may require adjustments for other server-side webdav implementations
1053 // (tested with Apache + mod_dav 1.0.3)
1054 if ( property.text() == QLatin1String("httpd/unix-directory") )
1055 {
1056 isDirectory = true;
1057 }
1058 else
1059 {
1060 mimeType = property.text();
1061 }
1062 }
1063 else if ( property.tagName() == QLatin1String("executable") )
1064 {
1065 // File executable status
1066 if ( property.text() == QLatin1String("T") )
1067 foundExecutable = true;
1068
1069 }
1070 else if ( property.tagName() == QLatin1String("getlastmodified") )
1071 {
1072 // Last modification date
1073 entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, parseDateTime( property.text(), property.attribute(QLatin1String("dt")) ) );
1074 }
1075 else if ( property.tagName() == QLatin1String("getetag") )
1076 {
1077 // Entity tag
1078 setMetaData( QLatin1String("davEntityTag"), property.text() );
1079 }
1080 else if ( property.tagName() == QLatin1String("supportedlock") )
1081 {
1082 // Supported locking specifications
1083 for ( QDomNode n2 = property.firstChild(); !n2.isNull(); n2 = n2.nextSibling() )
1084 {
1085 QDomElement lockEntry = n2.toElement();
1086 if ( lockEntry.tagName() == QLatin1String("lockentry") )
1087 {
1088 QDomElement lockScope = lockEntry.namedItem( QLatin1String("lockscope") ).toElement();
1089 QDomElement lockType = lockEntry.namedItem( QLatin1String("locktype") ).toElement();
1090 if ( !lockScope.isNull() && !lockType.isNull() )
1091 {
1092 // Lock type was properly specified
1093 supportedLockCount++;
1094 const QString lockCountStr = QString::number(supportedLockCount);
1095 const QString scope = lockScope.firstChild().toElement().tagName();
1096 const QString type = lockType.firstChild().toElement().tagName();
1097
1098 setMetaData( QLatin1String("davSupportedLockScope") + lockCountStr, scope );
1099 setMetaData( QLatin1String("davSupportedLockType") + lockCountStr, type );
1100 }
1101 }
1102 }
1103 }
1104 else if ( property.tagName() == QLatin1String("lockdiscovery") )
1105 {
1106 // Lists the available locks
1107 davParseActiveLocks( property.elementsByTagName( QLatin1String("activelock") ), lockCount );
1108 }
1109 else if ( property.tagName() == QLatin1String("resourcetype") )
1110 {
1111 // Resource type. "Specifies the nature of the resource."
1112 if ( !property.namedItem( QLatin1String("collection") ).toElement().isNull() )
1113 {
1114 // This is a collection (directory)
1115 isDirectory = true;
1116 }
1117 }
1118 else
1119 {
1120 kDebug(7113) << "Found unknown webdav property:" << property.tagName();
1121 }
1122 }
1123 }
1124
1125 setMetaData( QLatin1String("davLockCount"), QString::number(lockCount) );
1126 setMetaData( QLatin1String("davSupportedLockCount"), QString::number(supportedLockCount) );
1127
1128 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, isDirectory ? S_IFDIR : S_IFREG );
1129
1130 if ( foundExecutable || isDirectory )
1131 {
1132 // File was executable, or is a directory.
1133 entry.insert( KIO::UDSEntry::UDS_ACCESS, 0700 );
1134 }
1135 else
1136 {
1137 entry.insert( KIO::UDSEntry::UDS_ACCESS, 0600 );
1138 }
1139
1140 if ( !isDirectory && !mimeType.isEmpty() )
1141 {
1142 entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, mimeType );
1143 }
1144}
1145
1146void HTTPProtocol::davParseActiveLocks( const QDomNodeList& activeLocks,
1147 uint& lockCount )
1148{
1149 for ( int i = 0; i < activeLocks.count(); i++ )
1150 {
1151 const QDomElement activeLock = activeLocks.item(i).toElement();
1152
1153 lockCount++;
1154 // required
1155 const QDomElement lockScope = activeLock.namedItem( QLatin1String("lockscope") ).toElement();
1156 const QDomElement lockType = activeLock.namedItem( QLatin1String("locktype") ).toElement();
1157 const QDomElement lockDepth = activeLock.namedItem( QLatin1String("depth") ).toElement();
1158 // optional
1159 const QDomElement lockOwner = activeLock.namedItem( QLatin1String("owner") ).toElement();
1160 const QDomElement lockTimeout = activeLock.namedItem( QLatin1String("timeout") ).toElement();
1161 const QDomElement lockToken = activeLock.namedItem( QLatin1String("locktoken") ).toElement();
1162
1163 if ( !lockScope.isNull() && !lockType.isNull() && !lockDepth.isNull() )
1164 {
1165 // lock was properly specified
1166 lockCount++;
1167 const QString lockCountStr = QString::number(lockCount);
1168 const QString scope = lockScope.firstChild().toElement().tagName();
1169 const QString type = lockType.firstChild().toElement().tagName();
1170 const QString depth = lockDepth.text();
1171
1172 setMetaData( QLatin1String("davLockScope") + lockCountStr, scope );
1173 setMetaData( QLatin1String("davLockType") + lockCountStr, type );
1174 setMetaData( QLatin1String("davLockDepth") + lockCountStr, depth );
1175
1176 if ( !lockOwner.isNull() )
1177 setMetaData( QLatin1String("davLockOwner") + lockCountStr, lockOwner.text() );
1178
1179 if ( !lockTimeout.isNull() )
1180 setMetaData( QLatin1String("davLockTimeout") + lockCountStr, lockTimeout.text() );
1181
1182 if ( !lockToken.isNull() )
1183 {
1184 QDomElement tokenVal = lockScope.namedItem( QLatin1String("href") ).toElement();
1185 if ( !tokenVal.isNull() )
1186 setMetaData( QLatin1String("davLockToken") + lockCountStr, tokenVal.text() );
1187 }
1188 }
1189 }
1190}
1191
1192QString HTTPProtocol::davProcessLocks()
1193{
1194 if ( hasMetaData( QLatin1String("davLockCount") ) )
1195 {
1196 QString response = QLatin1String("If:");
1197 int numLocks = metaData( QLatin1String("davLockCount") ).toInt();
1198 bool bracketsOpen = false;
1199 for ( int i = 0; i < numLocks; i++ )
1200 {
1201 const QString countStr = QString::number(i);
1202 if ( hasMetaData( QLatin1String("davLockToken") + countStr ) )
1203 {
1204 if ( hasMetaData( QLatin1String("davLockURL") + countStr ) )
1205 {
1206 if ( bracketsOpen )
1207 {
1208 response += QLatin1Char(')');
1209 bracketsOpen = false;
1210 }
1211 response += QLatin1String(" <") + metaData( QLatin1String("davLockURL") + countStr ) + QLatin1Char('>');
1212 }
1213
1214 if ( !bracketsOpen )
1215 {
1216 response += QLatin1String(" (");
1217 bracketsOpen = true;
1218 }
1219 else
1220 {
1221 response += QLatin1Char(' ');
1222 }
1223
1224 if ( hasMetaData( QLatin1String("davLockNot") + countStr ) )
1225 response += QLatin1String("Not ");
1226
1227 response += QLatin1Char('<') + metaData( QLatin1String("davLockToken") + countStr ) + QLatin1Char('>');
1228 }
1229 }
1230
1231 if ( bracketsOpen )
1232 response += QLatin1Char(')');
1233
1234 response += QLatin1String("\r\n");
1235 return response;
1236 }
1237
1238 return QString();
1239}
1240
1241bool HTTPProtocol::davHostOk()
1242{
1243 // FIXME needs to be reworked. Switched off for now.
1244 return true;
1245
1246 // cached?
1247 if ( m_davHostOk )
1248 {
1249 kDebug(7113) << "true";
1250 return true;
1251 }
1252 else if ( m_davHostUnsupported )
1253 {
1254 kDebug(7113) << " false";
1255 davError( -2 );
1256 return false;
1257 }
1258
1259 m_request.method = HTTP_OPTIONS;
1260
1261 // query the server's capabilities generally, not for a specific URL
1262 m_request.url.setPath(QLatin1String("*"));
1263 m_request.url.setQuery(QString());
1264 m_request.cacheTag.policy = CC_Reload;
1265
1266 // clear davVersions variable, which holds the response to the DAV: header
1267 m_davCapabilities.clear();
1268
1269 proceedUntilResponseHeader();
1270
1271 if (m_davCapabilities.count())
1272 {
1273 for (int i = 0; i < m_davCapabilities.count(); i++)
1274 {
1275 bool ok;
1276 uint verNo = m_davCapabilities[i].toUInt(&ok);
1277 if (ok && verNo > 0 && verNo < 3)
1278 {
1279 m_davHostOk = true;
1280 kDebug(7113) << "Server supports DAV version" << verNo;
1281 }
1282 }
1283
1284 if ( m_davHostOk )
1285 return true;
1286 }
1287
1288 m_davHostUnsupported = true;
1289 davError( -2 );
1290 return false;
1291}
1292
1293// This function is for closing proceedUntilResponseHeader(); requests
1294// Required because there may or may not be further info expected
1295void HTTPProtocol::davFinished()
1296{
1297 // TODO: Check with the DAV extension developers
1298 httpClose(m_request.isKeepAlive);
1299 finished();
1300}
1301
1302void HTTPProtocol::mkdir( const KUrl& url, int )
1303{
1304 kDebug(7113) << url;
1305
1306 if (!maybeSetRequestUrl(url))
1307 return;
1308 resetSessionSettings();
1309
1310 m_request.method = DAV_MKCOL;
1311 m_request.url.setQuery(QString());
1312 m_request.cacheTag.policy = CC_Reload;
1313
1314 proceedUntilResponseContent(true);
1315
1316 if ( m_request.responseCode == 201 )
1317 davFinished();
1318 else
1319 davError();
1320}
1321
1322void HTTPProtocol::get( const KUrl& url )
1323{
1324 kDebug(7113) << url;
1325
1326 if (!maybeSetRequestUrl(url))
1327 return;
1328 resetSessionSettings();
1329
1330 m_request.method = HTTP_GET;
1331 const QString tmp (metaData(QLatin1String("cache")));
1332 m_request.cacheTag.policy = (tmp.isEmpty() ? DEFAULT_CACHE_CONTROL : parseCacheControl(tmp));
1333
1334 proceedUntilResponseContent();
1335}
1336
1337void HTTPProtocol::put( const KUrl &url, int, KIO::JobFlags flags )
1338{
1339 kDebug(7113) << url;
1340
1341 if (!maybeSetRequestUrl(url))
1342 return;
1343
1344 resetSessionSettings();
1345
1346 // Webdav hosts are capable of observing overwrite == false
1347 if (m_protocol.startsWith("webdav")) { // krazy:exclude=strings
1348 if (!(flags & KIO::Overwrite)) {
1349 // check to make sure this host supports WebDAV
1350 if (!davHostOk())
1351 return;
1352
1353 // Checks if the destination exists and return an error if it does.
1354 if (!davStatDestination()) {
1355 return;
1356 }
1357 }
1358 }
1359
1360 m_request.method = HTTP_PUT;
1361 m_request.cacheTag.policy = CC_Reload;
1362
1363 proceedUntilResponseContent();
1364}
1365
1366void HTTPProtocol::copy( const KUrl& src, const KUrl& dest, int, KIO::JobFlags flags )
1367{
1368 kDebug(7113) << src << "->" << dest;
1369
1370 const bool isSourceLocal = src.isLocalFile();
1371 const bool isDestinationLocal = dest.isLocalFile();
1372
1373 if (isSourceLocal && !isDestinationLocal) {
1374 copyPut(src, dest, flags);
1375 } else {
1376 if (!maybeSetRequestUrl(dest) || !maybeSetRequestUrl(src)) {
1377 return;
1378 }
1379
1380 resetSessionSettings();
1381
1382 // destination has to be "http(s)://..."
1383 KUrl newDest (dest);
1384 changeProtocolToHttp(&newDest);
1385
1386 m_request.method = DAV_COPY;
1387 m_request.davData.desturl = newDest.url();
1388 m_request.davData.overwrite = (flags & KIO::Overwrite);
1389 m_request.url.setQuery(QString());
1390 m_request.cacheTag.policy = CC_Reload;
1391
1392 proceedUntilResponseHeader();
1393
1394 // The server returns a HTTP/1.1 201 Created or 204 No Content on successful completion
1395 if (m_request.responseCode == 201 || m_request.responseCode == 204) {
1396 davFinished();
1397 } else {
1398 davError();
1399 }
1400 }
1401}
1402
1403void HTTPProtocol::rename( const KUrl& src, const KUrl& dest, KIO::JobFlags flags )
1404{
1405 kDebug(7113) << src << "->" << dest;
1406
1407 if (!maybeSetRequestUrl(dest) || !maybeSetRequestUrl(src))
1408 return;
1409 resetSessionSettings();
1410
1411 // destination has to be "http://..."
1412 KUrl newDest(dest);
1413 changeProtocolToHttp(&newDest);
1414
1415 m_request.method = DAV_MOVE;
1416 m_request.davData.desturl = newDest.url();
1417 m_request.davData.overwrite = (flags & KIO::Overwrite);
1418 m_request.url.setQuery(QString());
1419 m_request.cacheTag.policy = CC_Reload;
1420
1421 proceedUntilResponseHeader();
1422
1423 // Work around strict Apache-2 WebDAV implementation which refuses to cooperate
1424 // with webdav://host/directory, instead requiring webdav://host/directory/
1425 // (strangely enough it accepts Destination: without a trailing slash)
1426 // See BR# 209508 and BR#187970
1427 if ( m_request.responseCode == 301) {
1428 m_request.url = m_request.redirectUrl;
1429 m_request.method = DAV_MOVE;
1430 m_request.davData.desturl = newDest.url();
1431 m_request.davData.overwrite = (flags & KIO::Overwrite);
1432 m_request.url.setQuery(QString());
1433 m_request.cacheTag.policy = CC_Reload;
1434 // force re-authentication...
1435 delete m_wwwAuth;
1436 m_wwwAuth = 0;
1437 proceedUntilResponseHeader();
1438 }
1439
1440 if ( m_request.responseCode == 201 )
1441 davFinished();
1442 else
1443 davError();
1444}
1445
1446void HTTPProtocol::del(const KUrl& url, bool)
1447{
1448 kDebug(7113) << url;
1449
1450 if (!maybeSetRequestUrl(url))
1451 return;
1452
1453 resetSessionSettings();
1454
1455 m_request.method = HTTP_DELETE;
1456 m_request.cacheTag.policy = CC_Reload;
1457
1458 if (m_protocol.startsWith("webdav")) { //krazy:exclude=strings due to QByteArray
1459 m_request.url.setQuery(QString());
1460 if (!proceedUntilResponseHeader()) {
1461 return;
1462 }
1463
1464 // The server returns a HTTP/1.1 200 Ok or HTTP/1.1 204 No Content
1465 // on successful completion.
1466 if ( m_request.responseCode == 200 || m_request.responseCode == 204 || m_isRedirection)
1467 davFinished();
1468 else
1469 davError();
1470
1471 return;
1472 }
1473
1474 proceedUntilResponseContent();
1475}
1476
1477void HTTPProtocol::post( const KUrl& url, qint64 size )
1478{
1479 kDebug(7113) << url;
1480
1481 if (!maybeSetRequestUrl(url))
1482 return;
1483 resetSessionSettings();
1484
1485 m_request.method = HTTP_POST;
1486 m_request.cacheTag.policy= CC_Reload;
1487
1488 m_iPostDataSize = (size > -1 ? static_cast<KIO::filesize_t>(size) : NO_SIZE);
1489 proceedUntilResponseContent();
1490}
1491
1492void HTTPProtocol::davLock( const KUrl& url, const QString& scope,
1493 const QString& type, const QString& owner )
1494{
1495 kDebug(7113) << url;
1496
1497 if (!maybeSetRequestUrl(url))
1498 return;
1499 resetSessionSettings();
1500
1501 m_request.method = DAV_LOCK;
1502 m_request.url.setQuery(QString());
1503 m_request.cacheTag.policy= CC_Reload;
1504
1505 /* Create appropriate lock XML request. */
1506 QDomDocument lockReq;
1507
1508 QDomElement lockInfo = lockReq.createElementNS( QLatin1String("DAV:"), QLatin1String("lockinfo") );
1509 lockReq.appendChild( lockInfo );
1510
1511 QDomElement lockScope = lockReq.createElement( QLatin1String("lockscope") );
1512 lockInfo.appendChild( lockScope );
1513
1514 lockScope.appendChild( lockReq.createElement( scope ) );
1515
1516 QDomElement lockType = lockReq.createElement( QLatin1String("locktype") );
1517 lockInfo.appendChild( lockType );
1518
1519 lockType.appendChild( lockReq.createElement( type ) );
1520
1521 if ( !owner.isNull() ) {
1522 QDomElement ownerElement = lockReq.createElement( QLatin1String("owner") );
1523 lockReq.appendChild( ownerElement );
1524
1525 QDomElement ownerHref = lockReq.createElement( QLatin1String("href") );
1526 ownerElement.appendChild( ownerHref );
1527
1528 ownerHref.appendChild( lockReq.createTextNode( owner ) );
1529 }
1530
1531 // insert the document into the POST buffer
1532 cachePostData(lockReq.toByteArray());
1533
1534 proceedUntilResponseContent( true );
1535
1536 if ( m_request.responseCode == 200 ) {
1537 // success
1538 QDomDocument multiResponse;
1539 multiResponse.setContent( m_webDavDataBuf, true );
1540
1541 QDomElement prop = multiResponse.documentElement().namedItem( QLatin1String("prop") ).toElement();
1542
1543 QDomElement lockdiscovery = prop.namedItem( QLatin1String("lockdiscovery") ).toElement();
1544
1545 uint lockCount = 0;
1546 davParseActiveLocks( lockdiscovery.elementsByTagName( QLatin1String("activelock") ), lockCount );
1547
1548 setMetaData( QLatin1String("davLockCount"), QString::number( lockCount ) );
1549
1550 finished();
1551
1552 } else
1553 davError();
1554}
1555
1556void HTTPProtocol::davUnlock( const KUrl& url )
1557{
1558 kDebug(7113) << url;
1559
1560 if (!maybeSetRequestUrl(url))
1561 return;
1562 resetSessionSettings();
1563
1564 m_request.method = DAV_UNLOCK;
1565 m_request.url.setQuery(QString());
1566 m_request.cacheTag.policy= CC_Reload;
1567
1568 proceedUntilResponseContent( true );
1569
1570 if ( m_request.responseCode == 200 )
1571 finished();
1572 else
1573 davError();
1574}
1575
1576QString HTTPProtocol::davError( int code /* = -1 */, const QString &_url )
1577{
1578 bool callError = false;
1579 if ( code == -1 ) {
1580 code = m_request.responseCode;
1581 callError = true;
1582 }
1583 if ( code == -2 ) {
1584 callError = true;
1585 }
1586
1587 QString url = _url;
1588 if ( !url.isNull() )
1589 url = m_request.url.prettyUrl();
1590
1591 QString action, errorString;
1592 int errorCode = ERR_SLAVE_DEFINED;
1593
1594 // for 412 Precondition Failed
1595 QString ow = i18n( "Otherwise, the request would have succeeded." );
1596
1597 switch ( m_request.method ) {
1598 case DAV_PROPFIND:
1599 action = i18nc( "request type", "retrieve property values" );
1600 break;
1601 case DAV_PROPPATCH:
1602 action = i18nc( "request type", "set property values" );
1603 break;
1604 case DAV_MKCOL:
1605 action = i18nc( "request type", "create the requested folder" );
1606 break;
1607 case DAV_COPY:
1608 action = i18nc( "request type", "copy the specified file or folder" );
1609 break;
1610 case DAV_MOVE:
1611 action = i18nc( "request type", "move the specified file or folder" );
1612 break;
1613 case DAV_SEARCH:
1614 action = i18nc( "request type", "search in the specified folder" );
1615 break;
1616 case DAV_LOCK:
1617 action = i18nc( "request type", "lock the specified file or folder" );
1618 break;
1619 case DAV_UNLOCK:
1620 action = i18nc( "request type", "unlock the specified file or folder" );
1621 break;
1622 case HTTP_DELETE:
1623 action = i18nc( "request type", "delete the specified file or folder" );
1624 break;
1625 case HTTP_OPTIONS:
1626 action = i18nc( "request type", "query the server's capabilities" );
1627 break;
1628 case HTTP_GET:
1629 action = i18nc( "request type", "retrieve the contents of the specified file or folder" );
1630 break;
1631 case DAV_REPORT:
1632 action = i18nc( "request type", "run a report in the specified folder" );
1633 break;
1634 case HTTP_PUT:
1635 case HTTP_POST:
1636 case HTTP_HEAD:
1637 default:
1638 // this should not happen, this function is for webdav errors only
1639 Q_ASSERT(0);
1640 }
1641
1642 // default error message if the following code fails
1643 errorString = i18nc("%1: code, %2: request type", "An unexpected error (%1) occurred "
1644 "while attempting to %2.", code, action);
1645
1646 switch ( code )
1647 {
1648 case -2:
1649 // internal error: OPTIONS request did not specify DAV compliance
1650 // ERR_UNSUPPORTED_PROTOCOL
1651 errorString = i18n("The server does not support the WebDAV protocol.");
1652 break;
1653 case 207:
1654 // 207 Multi-status
1655 {
1656 // our error info is in the returned XML document.
1657 // retrieve the XML document
1658
1659 // there was an error retrieving the XML document.
1660 // ironic, eh?
1661 if ( !readBody( true ) && m_iError )
1662 return QString();
1663
1664 QStringList errors;
1665 QDomDocument multiResponse;
1666
1667 multiResponse.setContent( m_webDavDataBuf, true );
1668
1669 QDomElement multistatus = multiResponse.documentElement().namedItem( QLatin1String("multistatus") ).toElement();
1670
1671 QDomNodeList responses = multistatus.elementsByTagName( QLatin1String("response") );
1672
1673 for (int i = 0; i < responses.count(); i++)
1674 {
1675 int errCode;
1676 QString errUrl;
1677
1678 QDomElement response = responses.item(i).toElement();
1679 QDomElement code = response.namedItem( QLatin1String("status") ).toElement();
1680
1681 if ( !code.isNull() )
1682 {
1683 errCode = codeFromResponse( code.text() );
1684 QDomElement href = response.namedItem( QLatin1String("href") ).toElement();
1685 if ( !href.isNull() )
1686 errUrl = href.text();
1687 errors << davError( errCode, errUrl );
1688 }
1689 }
1690
1691 //kError = ERR_SLAVE_DEFINED;
1692 errorString = i18nc( "%1: request type, %2: url",
1693 "An error occurred while attempting to %1, %2. A "
1694 "summary of the reasons is below.", action, url );
1695
1696 errorString += QLatin1String("<ul>");
1697
1698 Q_FOREACH(const QString& error, errors)
1699 errorString += QLatin1String("<li>") + error + QLatin1String("</li>");
1700
1701 errorString += QLatin1String("</ul>");
1702 }
1703 case 403:
1704 case 500: // hack: Apache mod_dav returns this instead of 403 (!)
1705 // 403 Forbidden
1706 // ERR_ACCESS_DENIED
1707 errorString = i18nc( "%1: request type", "Access was denied while attempting to %1.", action );
1708 break;
1709 case 405:
1710 // 405 Method Not Allowed
1711 if ( m_request.method == DAV_MKCOL ) {
1712 // ERR_DIR_ALREADY_EXIST
1713 errorString = url;
1714 errorCode = ERR_DIR_ALREADY_EXIST;
1715 }
1716 break;
1717 case 409:
1718 // 409 Conflict
1719 // ERR_ACCESS_DENIED
1720 errorString = i18n("A resource cannot be created at the destination "
1721 "until one or more intermediate collections (folders) "
1722 "have been created.");
1723 break;
1724 case 412:
1725 // 412 Precondition failed
1726 if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE ) {
1727 // ERR_ACCESS_DENIED
1728 errorString = i18n("The server was unable to maintain the liveness of "
1729 "the properties listed in the propertybehavior XML "
1730 "element or you attempted to overwrite a file while "
1731 "requesting that files are not overwritten. %1",
1732 ow );
1733
1734 } else if ( m_request.method == DAV_LOCK ) {
1735 // ERR_ACCESS_DENIED
1736 errorString = i18n("The requested lock could not be granted. %1", ow );
1737 }
1738 break;
1739 case 415:
1740 // 415 Unsupported Media Type
1741 // ERR_ACCESS_DENIED
1742 errorString = i18n("The server does not support the request type of the body.");
1743 break;
1744 case 423:
1745 // 423 Locked
1746 // ERR_ACCESS_DENIED
1747 errorString = i18nc( "%1: request type", "Unable to %1 because the resource is locked.", action );
1748 break;
1749 case 425:
1750 // 424 Failed Dependency
1751 errorString = i18n("This action was prevented by another error.");
1752 break;
1753 case 502:
1754 // 502 Bad Gateway
1755 if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE ) {
1756 // ERR_WRITE_ACCESS_DENIED
1757 errorString = i18nc( "%1: request type", "Unable to %1 because the destination server refuses "
1758 "to accept the file or folder.", action );
1759 }
1760 break;
1761 case 507:
1762 // 507 Insufficient Storage
1763 // ERR_DISK_FULL
1764 errorString = i18n("The destination resource does not have sufficient space "
1765 "to record the state of the resource after the execution "
1766 "of this method.");
1767 break;
1768 default:
1769 break;
1770 }
1771
1772 // if ( kError != ERR_SLAVE_DEFINED )
1773 //errorString += " (" + url + ')';
1774
1775 if ( callError )
1776 error( errorCode, errorString );
1777
1778 return errorString;
1779}
1780
1781// HTTP generic error
1782static int httpGenericError(const HTTPProtocol::HTTPRequest& request, QString* errorString)
1783{
1784 Q_ASSERT(errorString);
1785
1786 int errorCode = 0;
1787 errorString->clear();
1788
1789 if (request.responseCode == 204) {
1790 errorCode = ERR_NO_CONTENT;
1791 }
1792
1793 return errorCode;
1794}
1795
1796// HTTP DELETE specific errors
1797static int httpDelError(const HTTPProtocol::HTTPRequest& request, QString* errorString)
1798{
1799 Q_ASSERT(errorString);
1800
1801 int errorCode = 0;
1802 const int responseCode = request.responseCode;
1803 errorString->clear();
1804
1805 switch (responseCode) {
1806 case 204:
1807 errorCode = ERR_NO_CONTENT;
1808 break;
1809 default:
1810 break;
1811 }
1812
1813 if (!errorCode
1814 && (responseCode < 200 || responseCode > 400)
1815 && responseCode != 404) {
1816 errorCode = ERR_SLAVE_DEFINED;
1817 *errorString = i18n( "The resource cannot be deleted." );
1818 }
1819
1820 return errorCode;
1821}
1822
1823// HTTP PUT specific errors
1824static int httpPutError(const HTTPProtocol::HTTPRequest& request, QString* errorString)
1825{
1826 Q_ASSERT(errorString);
1827
1828 int errorCode = 0;
1829 const int responseCode = request.responseCode;
1830 const QString action (i18nc("request type", "upload %1", request.url.prettyUrl()));
1831
1832 switch (responseCode) {
1833 case 403:
1834 case 405:
1835 case 500: // hack: Apache mod_dav returns this instead of 403 (!)
1836 // 403 Forbidden
1837 // 405 Method Not Allowed
1838 // ERR_ACCESS_DENIED
1839 *errorString = i18nc( "%1: request type", "Access was denied while attempting to %1.", action );
1840 errorCode = ERR_SLAVE_DEFINED;
1841 break;
1842 case 409:
1843 // 409 Conflict
1844 // ERR_ACCESS_DENIED
1845 *errorString = i18n("A resource cannot be created at the destination "
1846 "until one or more intermediate collections (folders) "
1847 "have been created.");
1848 errorCode = ERR_SLAVE_DEFINED;
1849 break;
1850 case 423:
1851 // 423 Locked
1852 // ERR_ACCESS_DENIED
1853 *errorString = i18nc( "%1: request type", "Unable to %1 because the resource is locked.", action );
1854 errorCode = ERR_SLAVE_DEFINED;
1855 break;
1856 case 502:
1857 // 502 Bad Gateway
1858 // ERR_WRITE_ACCESS_DENIED;
1859 *errorString = i18nc( "%1: request type", "Unable to %1 because the destination server refuses "
1860 "to accept the file or folder.", action );
1861 errorCode = ERR_SLAVE_DEFINED;
1862 break;
1863 case 507:
1864 // 507 Insufficient Storage
1865 // ERR_DISK_FULL
1866 *errorString = i18n("The destination resource does not have sufficient space "
1867 "to record the state of the resource after the execution "
1868 "of this method.");
1869 errorCode = ERR_SLAVE_DEFINED;
1870 break;
1871 default:
1872 break;
1873 }
1874
1875 if (!errorCode
1876 && (responseCode < 200 || responseCode > 400)
1877 && responseCode != 404) {
1878 errorCode = ERR_SLAVE_DEFINED;
1879 *errorString = i18nc("%1: response code, %2: request type",
1880 "An unexpected error (%1) occurred while attempting to %2.",
1881 responseCode, action);
1882 }
1883
1884 return errorCode;
1885}
1886
1887bool HTTPProtocol::sendHttpError()
1888{
1889 QString errorString;
1890 int errorCode = 0;
1891
1892 switch (m_request.method) {
1893 case HTTP_GET:
1894 case HTTP_POST:
1895 errorCode = httpGenericError(m_request, &errorString);
1896 break;
1897 case HTTP_PUT:
1898 errorCode = httpPutError(m_request, &errorString);
1899 break;
1900 case HTTP_DELETE:
1901 errorCode = httpDelError(m_request, &errorString);
1902 break;
1903 default:
1904 break;
1905 }
1906
1907 // Force any message previously shown by the client to be cleared.
1908 infoMessage(QLatin1String(""));
1909
1910 if (errorCode) {
1911 error( errorCode, errorString );
1912 return true;
1913 }
1914
1915 return false;
1916}
1917
1918bool HTTPProtocol::sendErrorPageNotification()
1919{
1920 if (!m_request.preferErrorPage)
1921 return false;
1922
1923 if (m_isLoadingErrorPage)
1924 kWarning(7113) << "called twice during one request, something is probably wrong.";
1925
1926 m_isLoadingErrorPage = true;
1927 SlaveBase::errorPage();
1928 return true;
1929}
1930
1931bool HTTPProtocol::isOffline()
1932{
1933 // ### TEMPORARY WORKAROUND (While investigating why solid may
1934 // produce false positives)
1935 return false;
1936
1937 Solid::Networking::Status status = Solid::Networking::status();
1938
1939 kDebug(7113) << "networkstatus:" << status;
1940
1941 // on error or unknown, we assume online
1942 return status == Solid::Networking::Unconnected;
1943}
1944
1945void HTTPProtocol::multiGet(const QByteArray &data)
1946{
1947 QDataStream stream(data);
1948 quint32 n;
1949 stream >> n;
1950
1951 kDebug(7113) << n;
1952
1953 HTTPRequest saveRequest;
1954 if (m_isBusy)
1955 saveRequest = m_request;
1956
1957 resetSessionSettings();
1958
1959 for (unsigned i = 0; i < n; ++i) {
1960 KUrl url;
1961 stream >> url >> mIncomingMetaData;
1962
1963 if (!maybeSetRequestUrl(url))
1964 continue;
1965
1966 //### should maybe call resetSessionSettings() if the server/domain is
1967 // different from the last request!
1968
1969 kDebug(7113) << url;
1970
1971 m_request.method = HTTP_GET;
1972 m_request.isKeepAlive = true; //readResponseHeader clears it if necessary
1973
1974 QString tmp = metaData(QLatin1String("cache"));
1975 if (!tmp.isEmpty())
1976 m_request.cacheTag.policy= parseCacheControl(tmp);
1977 else
1978 m_request.cacheTag.policy= DEFAULT_CACHE_CONTROL;
1979
1980 m_requestQueue.append(m_request);
1981 }
1982
1983 if (m_isBusy)
1984 m_request = saveRequest;
1985#if 0
1986 if (!m_isBusy) {
1987 m_isBusy = true;
1988 QMutableListIterator<HTTPRequest> it(m_requestQueue);
1989 while (it.hasNext()) {
1990 m_request = it.next();
1991 it.remove();
1992 proceedUntilResponseContent();
1993 }
1994 m_isBusy = false;
1995 }
1996#endif
1997 if (!m_isBusy) {
1998 m_isBusy = true;
1999 QMutableListIterator<HTTPRequest> it(m_requestQueue);
2000 // send the requests
2001 while (it.hasNext()) {
2002 m_request = it.next();
2003 sendQuery();
2004 // save the request state so we can pick it up again in the collection phase
2005 it.setValue(m_request);
2006 kDebug(7113) << "check one: isKeepAlive =" << m_request.isKeepAlive;
2007 if (m_request.cacheTag.ioMode != ReadFromCache) {
2008 m_server.initFrom(m_request);
2009 }
2010 }
2011 // collect the responses
2012 //### for the moment we use a hack: instead of saving and restoring request-id
2013 // we just count up like ParallelGetJobs does.
2014 int requestId = 0;
2015 Q_FOREACH (const HTTPRequest &r, m_requestQueue) {
2016 m_request = r;
2017 kDebug(7113) << "check two: isKeepAlive =" << m_request.isKeepAlive;
2018 setMetaData(QLatin1String("request-id"), QString::number(requestId++));
2019 sendAndKeepMetaData();
2020 if (!(readResponseHeader() && readBody())) {
2021 return;
2022 }
2023 // the "next job" signal for ParallelGetJob is data of size zero which
2024 // readBody() sends without our intervention.
2025 kDebug(7113) << "check three: isKeepAlive =" << m_request.isKeepAlive;
2026 httpClose(m_request.isKeepAlive); //actually keep-alive is mandatory for pipelining
2027 }
2028
2029 finished();
2030 m_requestQueue.clear();
2031 m_isBusy = false;
2032 }
2033}
2034
2035ssize_t HTTPProtocol::write (const void *_buf, size_t nbytes)
2036{
2037 size_t sent = 0;
2038 const char* buf = static_cast<const char*>(_buf);
2039 while (sent < nbytes)
2040 {
2041 int n = TCPSlaveBase::write(buf + sent, nbytes - sent);
2042
2043 if (n < 0) {
2044 // some error occurred
2045 return -1;
2046 }
2047
2048 sent += n;
2049 }
2050
2051 return sent;
2052}
2053
2054void HTTPProtocol::clearUnreadBuffer()
2055{
2056 m_unreadBuf.clear();
2057}
2058
2059// Note: the implementation of unread/readBuffered assumes that unread will only
2060// be used when there is extra data we don't want to handle, and not to wait for more data.
2061void HTTPProtocol::unread(char *buf, size_t size)
2062{
2063 // implement LIFO (stack) semantics
2064 const int newSize = m_unreadBuf.size() + size;
2065 m_unreadBuf.resize(newSize);
2066 for (size_t i = 0; i < size; i++) {
2067 m_unreadBuf.data()[newSize - i - 1] = buf[i];
2068 }
2069 if (size) {
2070 //hey, we still have data, closed connection or not!
2071 m_isEOF = false;
2072 }
2073}
2074
2075size_t HTTPProtocol::readBuffered(char *buf, size_t size, bool unlimited)
2076{
2077 size_t bytesRead = 0;
2078 if (!m_unreadBuf.isEmpty()) {
2079 const int bufSize = m_unreadBuf.size();
2080 bytesRead = qMin((int)size, bufSize);
2081
2082 for (size_t i = 0; i < bytesRead; i++) {
2083 buf[i] = m_unreadBuf.constData()[bufSize - i - 1];
2084 }
2085 m_unreadBuf.truncate(bufSize - bytesRead);
2086
2087 // If we have an unread buffer and the size of the content returned by the
2088 // server is unknown, e.g. chuncked transfer, return the bytes read here since
2089 // we may already have enough data to complete the response and don't want to
2090 // wait for more. See BR# 180631.
2091 if (unlimited)
2092 return bytesRead;
2093 }
2094 if (bytesRead < size) {
2095 int rawRead = TCPSlaveBase::read(buf + bytesRead, size - bytesRead);
2096 if (rawRead < 1) {
2097 m_isEOF = true;
2098 return bytesRead;
2099 }
2100 bytesRead += rawRead;
2101 }
2102 return bytesRead;
2103}
2104
2105//### this method will detect an n*(\r\n) sequence if it crosses invocations.
2106// it will look (n*2 - 1) bytes before start at most and never before buf, naturally.
2107// supported number of newlines are one and two, in line with HTTP syntax.
2108// return true if numNewlines newlines were found.
2109bool HTTPProtocol::readDelimitedText(char *buf, int *idx, int end, int numNewlines)
2110{
2111 Q_ASSERT(numNewlines >=1 && numNewlines <= 2);
2112 char mybuf[64]; //somewhere close to the usual line length to avoid unread()ing too much
2113 int pos = *idx;
2114 while (pos < end && !m_isEOF) {
2115 int step = qMin((int)sizeof(mybuf), end - pos);
2116 if (m_isChunked) {
2117 //we might be reading the end of the very last chunk after which there is no data.
2118 //don't try to read any more bytes than there are because it causes stalls
2119 //(yes, it shouldn't stall but it does)
2120 step = 1;
2121 }
2122 size_t bufferFill = readBuffered(mybuf, step);
2123
2124 for (size_t i = 0; i < bufferFill ; ++i, ++pos) {
2125 // we copy the data from mybuf to buf immediately and look for the newlines in buf.
2126 // that way we don't miss newlines split over several invocations of this method.
2127 buf[pos] = mybuf[i];
2128
2129 // did we just copy one or two times the (usually) \r\n delimiter?
2130 // until we find even more broken webservers in the wild let's assume that they either
2131 // send \r\n (RFC compliant) or \n (broken) as delimiter...
2132 if (buf[pos] == '\n') {
2133 bool found = numNewlines == 1;
2134 if (!found) { // looking for two newlines
2135 // Detect \n\n and \n\r\n. The other cases (\r\n\n, \r\n\r\n) are covered by the first two.
2136 found = ((pos >= 1 && buf[pos - 1] == '\n') ||
2137 (pos >= 2 && buf[pos - 2] == '\n' && buf[pos - 1] == '\r'));
2138 }
2139 if (found) {
2140 i++; // unread bytes *after* CRLF
2141 unread(&mybuf[i], bufferFill - i);
2142 *idx = pos + 1;
2143 return true;
2144 }
2145 }
2146 }
2147 }
2148 *idx = pos;
2149 return false;
2150}
2151
2152static bool isCompatibleNextUrl(const KUrl &previous, const KUrl &now)
2153{
2154 if (previous.host() != now.host() || previous.port() != now.port()) {
2155 return false;
2156 }
2157 if (previous.user().isEmpty() && previous.pass().isEmpty()) {
2158 return true;
2159 }
2160 return previous.user() == now.user() && previous.pass() == now.pass();
2161}
2162
2163bool HTTPProtocol::httpShouldCloseConnection()
2164{
2165 kDebug(7113);
2166
2167 if (!isConnected()) {
2168 return false;
2169 }
2170
2171 if (!m_request.proxyUrls.isEmpty() && !isAutoSsl()) {
2172 Q_FOREACH(const QString& url, m_request.proxyUrls) {
2173 if (url != QLatin1String("DIRECT")) {
2174 if (isCompatibleNextUrl(m_server.proxyUrl, KUrl(url))) {
2175 return false;
2176 }
2177 }
2178 }
2179 return true;
2180 }
2181
2182 return !isCompatibleNextUrl(m_server.url, m_request.url);
2183}
2184
2185bool HTTPProtocol::httpOpenConnection()
2186{
2187 kDebug(7113);
2188 m_server.clear();
2189
2190 // Only save proxy auth information after proxy authentication has
2191 // actually taken place, which will set up exactly this connection.
2192 disconnect(socket(), SIGNAL(connected()),
2193 this, SLOT(saveProxyAuthenticationForSocket()));
2194
2195 clearUnreadBuffer();
2196
2197 int connectError = 0;
2198 QString errorString;
2199
2200 // Get proxy information...
2201 if (m_request.proxyUrls.isEmpty()) {
2202 m_request.proxyUrls = config()->readEntry("ProxyUrls", QStringList());
2203 kDebug(7113) << "Proxy URLs:" << m_request.proxyUrls;
2204 }
2205
2206 if (m_request.proxyUrls.isEmpty()) {
2207 QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);
2208 connectError = connectToHost(m_request.url.host(), m_request.url.port(defaultPort()), &errorString);
2209 } else {
2210 KUrl::List badProxyUrls;
2211 Q_FOREACH(const QString& proxyUrl, m_request.proxyUrls) {
2212 if (proxyUrl == QLatin1String("DIRECT")) {
2213 QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);
2214 connectError = connectToHost(m_request.url.host(), m_request.url.port(defaultPort()), &errorString);
2215 if (connectError == 0) {
2216 kDebug(7113) << "Connected DIRECT: host=" << m_request.url.host() << "port=" << m_request.url.port(defaultPort());
2217 break;
2218 } else {
2219 continue;
2220 }
2221 }
2222
2223 const KUrl url(proxyUrl);
2224 const QString proxyScheme(url.protocol());
2225 if (!supportedProxyScheme(proxyScheme)) {
2226 connectError = ERR_COULD_NOT_CONNECT;
2227 errorString = url.url();
2228 badProxyUrls << url;
2229 continue;
2230 }
2231
2232 QNetworkProxy::ProxyType proxyType = QNetworkProxy::NoProxy;
2233 if (proxyScheme == QLatin1String("socks")) {
2234 proxyType = QNetworkProxy::Socks5Proxy;
2235 } else if (isAutoSsl()) {
2236 proxyType = QNetworkProxy::HttpProxy;
2237 }
2238
2239 kDebug(7113) << "Connecting to proxy: address=" << proxyUrl << "type=" << proxyType;
2240
2241 if (proxyType == QNetworkProxy::NoProxy) {
2242 QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);
2243 connectError = connectToHost(url.host(), url.port(), &errorString);
2244 if (connectError == 0) {
2245 m_request.proxyUrl = url;
2246 kDebug(7113) << "Connected to proxy: host=" << url.host() << "port=" << url.port();
2247 break;
2248 } else {
2249 if (connectError == ERR_UNKNOWN_HOST) {
2250 connectError = ERR_UNKNOWN_PROXY_HOST;
2251 }
2252 kDebug(7113) << "Failed to connect to proxy:" << proxyUrl;
2253 badProxyUrls << url;
2254 }
2255 } else {
2256 QNetworkProxy proxy(proxyType, url.host(), url.port(), url.user(), url.pass());
2257 QNetworkProxy::setApplicationProxy(proxy);
2258 connectError = connectToHost(m_request.url.host(), m_request.url.port(defaultPort()), &errorString);
2259 if (connectError == 0) {
2260 kDebug(7113) << "Tunneling thru proxy: host=" << url.host() << "port=" << url.port();
2261 break;
2262 } else {
2263 if (connectError == ERR_UNKNOWN_HOST) {
2264 connectError = ERR_UNKNOWN_PROXY_HOST;
2265 }
2266 kDebug(7113) << "Failed to connect to proxy:" << proxyUrl;
2267 badProxyUrls << url;
2268 QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);
2269 }
2270 }
2271 }
2272
2273 if (!badProxyUrls.isEmpty()) {
2274 //TODO: Notify the client of BAD proxy addresses (needed for PAC setups).
2275 }
2276 }
2277
2278 if (connectError != 0) {
2279 error(connectError, errorString);
2280 return false;
2281 }
2282
2283 // Disable Nagle's algorithm, i.e turn on TCP_NODELAY.
2284 KTcpSocket *sock = qobject_cast<KTcpSocket*>(socket());
2285 if (sock) {
2286 // kDebug(7113) << "TCP_NODELAY:" << sock->socketOption(QAbstractSocket::LowDelayOption);
2287 sock->setSocketOption(QAbstractSocket::LowDelayOption, 1);
2288 }
2289
2290 m_server.initFrom(m_request);
2291 connected();
2292 return true;
2293}
2294
2295bool HTTPProtocol::satisfyRequestFromCache(bool *cacheHasPage)
2296{
2297 kDebug(7113);
2298
2299 if (m_request.cacheTag.useCache) {
2300 const bool offline = isOffline();
2301
2302 if (offline && m_request.cacheTag.policy != KIO::CC_Reload) {
2303 m_request.cacheTag.policy= KIO::CC_CacheOnly;
2304 }
2305
2306 const bool isCacheOnly = m_request.cacheTag.policy == KIO::CC_CacheOnly;
2307 const CacheTag::CachePlan plan = m_request.cacheTag.plan(m_maxCacheAge);
2308
2309 bool openForReading = false;
2310 if (plan == CacheTag::UseCached || plan == CacheTag::ValidateCached) {
2311 openForReading = cacheFileOpenRead();
2312
2313 if (!openForReading && (isCacheOnly || offline)) {
2314 // cache-only or offline -> we give a definite answer and it is "no"
2315 *cacheHasPage = false;
2316 if (isCacheOnly) {
2317 error(ERR_DOES_NOT_EXIST, m_request.url.url());
2318 } else if (offline) {
2319 error(ERR_COULD_NOT_CONNECT, m_request.url.url());
2320 }
2321 return true;
2322 }
2323 }
2324
2325 if (openForReading) {
2326 m_request.cacheTag.ioMode = ReadFromCache;
2327 *cacheHasPage = true;
2328 // return false if validation is required, so a network request will be sent
2329 return m_request.cacheTag.plan(m_maxCacheAge) == CacheTag::UseCached;
2330 }
2331 }
2332 *cacheHasPage = false;
2333 return false;
2334}
2335
2336QString HTTPProtocol::formatRequestUri() const
2337{
2338 // Only specify protocol, host and port when they are not already clear, i.e. when
2339 // we handle HTTP proxying ourself and the proxy server needs to know them.
2340 // Sending protocol/host/port in other cases confuses some servers, and it's not their fault.
2341 if (isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) {
2342 KUrl u;
2343
2344 QString protocol = m_request.url.protocol();
2345 if (protocol.startsWith(QLatin1String("webdav"))) {
2346 protocol.replace(0, qstrlen("webdav"), QLatin1String("http"));
2347 }
2348 u.setProtocol(protocol);
2349
2350 u.setHost(m_request.url.host());
2351 // if the URL contained the default port it should have been stripped earlier
2352 Q_ASSERT(m_request.url.port() != defaultPort());
2353 u.setPort(m_request.url.port());
2354 u.setEncodedPathAndQuery(m_request.url.encodedPathAndQuery(
2355 KUrl::LeaveTrailingSlash, KUrl::AvoidEmptyPath));
2356 return u.url();
2357 } else {
2358 return m_request.url.encodedPathAndQuery(KUrl::LeaveTrailingSlash, KUrl::AvoidEmptyPath);
2359 }
2360}
2361
2377bool HTTPProtocol::sendQuery()
2378{
2379 kDebug(7113);
2380
2381 // Cannot have an https request without autoSsl! This can
2382 // only happen if the current installation does not support SSL...
2383 if (isEncryptedHttpVariety(m_protocol) && !isAutoSsl()) {
2384 error(ERR_UNSUPPORTED_PROTOCOL, toQString(m_protocol));
2385 return false;
2386 }
2387
2388 // Check the reusability of the current connection.
2389 if (httpShouldCloseConnection()) {
2390 httpCloseConnection();
2391 }
2392
2393 // Create a new connection to the remote machine if we do
2394 // not already have one...
2395 // NB: the !m_socketProxyAuth condition is a workaround for a proxied Qt socket sometimes
2396 // looking disconnected after receiving the initial 407 response.
2397 // I guess the Qt socket fails to hide the effect of proxy-connection: close after receiving
2398 // the 407 header.
2399 if ((!isConnected() && !m_socketProxyAuth))
2400 {
2401 if (!httpOpenConnection())
2402 {
2403 kDebug(7113) << "Couldn't connect, oopsie!";
2404 return false;
2405 }
2406 }
2407
2408 m_request.cacheTag.ioMode = NoCache;
2409 m_request.cacheTag.servedDate = -1;
2410 m_request.cacheTag.lastModifiedDate = -1;
2411 m_request.cacheTag.expireDate = -1;
2412
2413 QString header;
2414
2415 bool hasBodyData = false;
2416 bool hasDavData = false;
2417
2418 {
2419 m_request.sentMethodString = m_request.methodString();
2420 header = toQString(m_request.sentMethodString) + QLatin1Char(' ');
2421
2422 QString davHeader;
2423
2424 // Fill in some values depending on the HTTP method to guide further processing
2425 switch (m_request.method)
2426 {
2427 case HTTP_GET: {
2428 bool cacheHasPage = false;
2429 if (satisfyRequestFromCache(&cacheHasPage)) {
2430 kDebug(7113) << "cacheHasPage =" << cacheHasPage;
2431 return cacheHasPage;
2432 }
2433 if (!cacheHasPage) {
2434 // start a new cache file later if appropriate
2435 m_request.cacheTag.ioMode = WriteToCache;
2436 }
2437 break;
2438 }
2439 case HTTP_HEAD:
2440 break;
2441 case HTTP_PUT:
2442 case HTTP_POST:
2443 hasBodyData = true;
2444 break;
2445 case HTTP_DELETE:
2446 case HTTP_OPTIONS:
2447 break;
2448 case DAV_PROPFIND:
2449 hasDavData = true;
2450 davHeader = QLatin1String("Depth: ");
2451 if ( hasMetaData( QLatin1String("davDepth") ) )
2452 {
2453 kDebug(7113) << "Reading DAV depth from metadata:" << metaData( QLatin1String("davDepth") );
2454 davHeader += metaData( QLatin1String("davDepth") );
2455 }
2456 else
2457 {
2458 if ( m_request.davData.depth == 2 )
2459 davHeader += QLatin1String("infinity");
2460 else
2461 davHeader += QString::number( m_request.davData.depth );
2462 }
2463 davHeader += QLatin1String("\r\n");
2464 break;
2465 case DAV_PROPPATCH:
2466 hasDavData = true;
2467 break;
2468 case DAV_MKCOL:
2469 break;
2470 case DAV_COPY:
2471 case DAV_MOVE:
2472 davHeader = QLatin1String("Destination: ") + m_request.davData.desturl;
2473 // infinity depth means copy recursively
2474 // (optional for copy -> but is the desired action)
2475 davHeader += QLatin1String("\r\nDepth: infinity\r\nOverwrite: ");
2476 davHeader += QLatin1Char(m_request.davData.overwrite ? 'T' : 'F');
2477 davHeader += QLatin1String("\r\n");
2478 break;
2479 case DAV_LOCK:
2480 davHeader = QLatin1String("Timeout: ");
2481 {
2482 uint timeout = 0;
2483 if ( hasMetaData( QLatin1String("davTimeout") ) )
2484 timeout = metaData( QLatin1String("davTimeout") ).toUInt();
2485 if ( timeout == 0 )
2486 davHeader += QLatin1String("Infinite");
2487 else
2488 davHeader += QLatin1String("Seconds-") + QString::number(timeout);
2489 }
2490 davHeader += QLatin1String("\r\n");
2491 hasDavData = true;
2492 break;
2493 case DAV_UNLOCK:
2494 davHeader = QLatin1String("Lock-token: ") + metaData(QLatin1String("davLockToken")) + QLatin1String("\r\n");
2495 break;
2496 case DAV_SEARCH:
2497 case DAV_REPORT:
2498 hasDavData = true;
2499 /* fall through */
2500 case DAV_SUBSCRIBE:
2501 case DAV_UNSUBSCRIBE:
2502 case DAV_POLL:
2503 break;
2504 default:
2505 error (ERR_UNSUPPORTED_ACTION, QString());
2506 return false;
2507 }
2508 // DAV_POLL; DAV_NOTIFY
2509
2510 header += formatRequestUri() + QLatin1String(" HTTP/1.1\r\n"); /* start header */
2511
2512 /* support for virtual hosts and required by HTTP 1.1 */
2513 header += QLatin1String("Host: ") + m_request.encoded_hostname;
2514 if (m_request.url.port(defaultPort()) != defaultPort()) {
2515 header += QLatin1Char(':') + QString::number(m_request.url.port());
2516 }
2517 header += QLatin1String("\r\n");
2518
2519 // Support old HTTP/1.0 style keep-alive header for compatibility
2520 // purposes as well as performance improvements while giving end
2521 // users the ability to disable this feature for proxy servers that
2522 // don't support it, e.g. junkbuster proxy server.
2523 if (isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) {
2524 header += QLatin1String("Proxy-Connection: ");
2525 } else {
2526 header += QLatin1String("Connection: ");
2527 }
2528 if (m_request.isKeepAlive) {
2529 header += QLatin1String("keep-alive\r\n");
2530 } else {
2531 header += QLatin1String("close\r\n");
2532 }
2533
2534 if (!m_request.userAgent.isEmpty())
2535 {
2536 header += QLatin1String("User-Agent: ");
2537 header += m_request.userAgent;
2538 header += QLatin1String("\r\n");
2539 }
2540
2541 if (!m_request.referrer.isEmpty())
2542 {
2543 header += QLatin1String("Referer: "); //Don't try to correct spelling!
2544 header += m_request.referrer;
2545 header += QLatin1String("\r\n");
2546 }
2547
2548 if ( m_request.endoffset > m_request.offset )
2549 {
2550 header += QLatin1String("Range: bytes=");
2551 header += KIO::number(m_request.offset);
2552 header += QLatin1Char('-');
2553 header += KIO::number(m_request.endoffset);
2554 header += QLatin1String("\r\n");
2555 kDebug(7103) << "kio_http : Range =" << KIO::number(m_request.offset)
2556 << "-" << KIO::number(m_request.endoffset);
2557 }
2558 else if ( m_request.offset > 0 && m_request.endoffset == 0 )
2559 {
2560 header += QLatin1String("Range: bytes=");
2561 header += KIO::number(m_request.offset);
2562 header += QLatin1String("-\r\n");
2563 kDebug(7103) << "kio_http: Range =" << KIO::number(m_request.offset);
2564 }
2565
2566 if ( !m_request.cacheTag.useCache || m_request.cacheTag.policy==CC_Reload )
2567 {
2568 /* No caching for reload */
2569 header += QLatin1String("Pragma: no-cache\r\n"); /* for HTTP/1.0 caches */
2570 header += QLatin1String("Cache-control: no-cache\r\n"); /* for HTTP >=1.1 caches */
2571 }
2572 else if (m_request.cacheTag.plan(m_maxCacheAge) == CacheTag::ValidateCached)
2573 {
2574 kDebug(7113) << "needs validation, performing conditional get.";
2575 /* conditional get */
2576 if (!m_request.cacheTag.etag.isEmpty())
2577 header += QLatin1String("If-None-Match: ") + m_request.cacheTag.etag + QLatin1String("\r\n");
2578
2579 if (m_request.cacheTag.lastModifiedDate != -1) {
2580 const QString httpDate = formatHttpDate(m_request.cacheTag.lastModifiedDate);
2581 header += QLatin1String("If-Modified-Since: ") + httpDate + QLatin1String("\r\n");
2582 setMetaData(QLatin1String("modified"), httpDate);
2583 }
2584 }
2585
2586 header += QLatin1String("Accept: ");
2587 const QString acceptHeader = metaData(QLatin1String("accept"));
2588 if (!acceptHeader.isEmpty())
2589 header += acceptHeader;
2590 else
2591 header += QLatin1String(DEFAULT_ACCEPT_HEADER);
2592 header += QLatin1String("\r\n");
2593
2594 if (m_request.allowTransferCompression)
2595 header += QLatin1String("Accept-Encoding: gzip, deflate, x-gzip, x-deflate\r\n");
2596
2597 if (!m_request.charsets.isEmpty())
2598 header += QLatin1String("Accept-Charset: ") + m_request.charsets + QLatin1String("\r\n");
2599
2600 if (!m_request.languages.isEmpty())
2601 header += QLatin1String("Accept-Language: ") + m_request.languages + QLatin1String("\r\n");
2602
2603 QString cookieStr;
2604 const QString cookieMode = metaData(QLatin1String("cookies")).toLower();
2605
2606 if (cookieMode == QLatin1String("none"))
2607 {
2608 m_request.cookieMode = HTTPRequest::CookiesNone;
2609 }
2610 else if (cookieMode == QLatin1String("manual"))
2611 {
2612 m_request.cookieMode = HTTPRequest::CookiesManual;
2613 cookieStr = metaData(QLatin1String("setcookies"));
2614 }
2615 else
2616 {
2617 m_request.cookieMode = HTTPRequest::CookiesAuto;
2618 if (m_request.useCookieJar)
2619 cookieStr = findCookies(m_request.url.url());
2620 }
2621
2622 if (!cookieStr.isEmpty())
2623 header += cookieStr + QLatin1String("\r\n");
2624
2625 const QString customHeader = metaData( QLatin1String("customHTTPHeader") );
2626 if (!customHeader.isEmpty())
2627 {
2628 header += sanitizeCustomHTTPHeader(customHeader);
2629 header += QLatin1String("\r\n");
2630 }
2631
2632 const QString contentType = metaData(QLatin1String("content-type"));
2633 if (!contentType.isEmpty())
2634 {
2635 if (!contentType.startsWith(QLatin1String("content-type"), Qt::CaseInsensitive))
2636 header += QLatin1String("Content-Type: ");
2637 header += contentType;
2638 header += QLatin1String("\r\n");
2639 }
2640
2641 // DoNotTrack feature...
2642 if (config()->readEntry("DoNotTrack", false))
2643 header += QLatin1String("DNT: 1\r\n");
2644
2645 // Remember that at least one failed (with 401 or 407) request/response
2646 // roundtrip is necessary for the server to tell us that it requires
2647 // authentication. However, we proactively add authentication headers if when
2648 // we have cached credentials to avoid the extra roundtrip where possible.
2649 header += authenticationHeader();
2650
2651 if ( m_protocol == "webdav" || m_protocol == "webdavs" )
2652 {
2653 header += davProcessLocks();
2654
2655 // add extra webdav headers, if supplied
2656 davHeader += metaData(QLatin1String("davHeader"));
2657
2658 // Set content type of webdav data
2659 if (hasDavData)
2660 davHeader += QLatin1String("Content-Type: text/xml; charset=utf-8\r\n");
2661
2662 // add extra header elements for WebDAV
2663 header += davHeader;
2664 }
2665 }
2666
2667 kDebug(7103) << "============ Sending Header:";
2668 Q_FOREACH (const QString &s, header.split(QLatin1String("\r\n"), QString::SkipEmptyParts)) {
2669 kDebug(7103) << s;
2670 }
2671
2672 // End the header iff there is no payload data. If we do have payload data
2673 // sendBody() will add another field to the header, Content-Length.
2674 if (!hasBodyData && !hasDavData)
2675 header += QLatin1String("\r\n");
2676
2677
2678 // Now that we have our formatted header, let's send it!
2679
2680 // Clear out per-connection settings...
2681 resetConnectionSettings();
2682
2683 // Send the data to the remote machine...
2684 const QByteArray headerBytes = header.toLatin1();
2685 ssize_t written = write(headerBytes.constData(), headerBytes.length());
2686 bool sendOk = (written == (ssize_t) headerBytes.length());
2687 if (!sendOk)
2688 {
2689 kDebug(7113) << "Connection broken! (" << m_request.url.host() << ")"
2690 << " -- intended to write" << headerBytes.length()
2691 << "bytes but wrote" << (int)written << ".";
2692
2693 // The server might have closed the connection due to a timeout, or maybe
2694 // some transport problem arose while the connection was idle.
2695 if (m_request.isKeepAlive)
2696 {
2697 httpCloseConnection();
2698 return true; // Try again
2699 }
2700
2701 kDebug(7113) << "sendOk == false. Connection broken !"
2702 << " -- intended to write" << headerBytes.length()
2703 << "bytes but wrote" << (int)written << ".";
2704 error( ERR_CONNECTION_BROKEN, m_request.url.host() );
2705 return false;
2706 }
2707 else
2708 kDebug(7113) << "sent it!";
2709
2710 bool res = true;
2711 if (hasBodyData || hasDavData)
2712 res = sendBody();
2713
2714 infoMessage(i18n("%1 contacted. Waiting for reply...", m_request.url.host()));
2715
2716 return res;
2717}
2718
2719void HTTPProtocol::forwardHttpResponseHeader(bool forwardImmediately)
2720{
2721 // Send the response header if it was requested...
2722 if (!config()->readEntry("PropagateHttpHeader", false))
2723 return;
2724
2725 setMetaData(QLatin1String("HTTP-Headers"), m_responseHeaders.join(QString(QLatin1Char('\n'))));
2726
2727 if (forwardImmediately)
2728 sendMetaData();
2729}
2730
2731bool HTTPProtocol::parseHeaderFromCache()
2732{
2733 kDebug(7113);
2734 if (!cacheFileReadTextHeader2()) {
2735 return false;
2736 }
2737
2738 Q_FOREACH (const QString &str, m_responseHeaders) {
2739 const QString header = str.trimmed();
2740 if (header.startsWith(QLatin1String("content-type:"), Qt::CaseInsensitive)) {
2741 int pos = header.indexOf(QLatin1String("charset="), Qt::CaseInsensitive);
2742 if (pos != -1) {
2743 const QString charset = header.mid(pos + 8).toLower();
2744 m_request.cacheTag.charset = charset;
2745 setMetaData(QLatin1String("charset"), charset);
2746 }
2747 } else if (header.startsWith(QLatin1String("content-language:"), Qt::CaseInsensitive)) {
2748 const QString language = header.mid(17).trimmed().toLower();
2749 setMetaData(QLatin1String("content-language"), language);
2750 } else if (header.startsWith(QLatin1String("content-disposition:"), Qt::CaseInsensitive)) {
2751 parseContentDisposition(header.mid(20).toLower());
2752 }
2753 }
2754
2755 if (m_request.cacheTag.lastModifiedDate != -1) {
2756 setMetaData(QLatin1String("modified"), formatHttpDate(m_request.cacheTag.lastModifiedDate));
2757 }
2758
2759 // this header comes from the cache, so the response must have been cacheable :)
2760 setCacheabilityMetadata(true);
2761 kDebug(7113) << "Emitting mimeType" << m_mimeType;
2762 forwardHttpResponseHeader(false);
2763 mimeType(m_mimeType);
2764 // IMPORTANT: Do not remove the call below or the http response headers will
2765 // not be available to the application if this slave is put on hold.
2766 forwardHttpResponseHeader();
2767 return true;
2768}
2769
2770void HTTPProtocol::fixupResponseMimetype()
2771{
2772 if (m_mimeType.isEmpty())
2773 return;
2774
2775 kDebug(7113) << "before fixup" << m_mimeType;
2776 // Convert some common mimetypes to standard mimetypes
2777 if (m_mimeType == QLatin1String("application/x-targz"))
2778 m_mimeType = QLatin1String("application/x-compressed-tar");
2779 else if (m_mimeType == QLatin1String("image/x-png"))
2780 m_mimeType = QLatin1String("image/png");
2781 else if (m_mimeType == QLatin1String("audio/x-mp3") || m_mimeType == QLatin1String("audio/x-mpeg") || m_mimeType == QLatin1String("audio/mp3"))
2782 m_mimeType = QLatin1String("audio/mpeg");
2783 else if (m_mimeType == QLatin1String("audio/microsoft-wave"))
2784 m_mimeType = QLatin1String("audio/x-wav");
2785 else if (m_mimeType == QLatin1String("image/x-ms-bmp"))
2786 m_mimeType = QLatin1String("image/bmp");
2787
2788 // Crypto ones....
2789 else if (m_mimeType == QLatin1String("application/pkix-cert") ||
2790 m_mimeType == QLatin1String("application/binary-certificate")) {
2791 m_mimeType = QLatin1String("application/x-x509-ca-cert");
2792 }
2793
2794 // Prefer application/x-compressed-tar or x-gzpostscript over application/x-gzip.
2795 else if (m_mimeType == QLatin1String("application/x-gzip")) {
2796 if ((m_request.url.path().endsWith(QLatin1String(".tar.gz"))) ||
2797 (m_request.url.path().endsWith(QLatin1String(".tar"))))
2798 m_mimeType = QLatin1String("application/x-compressed-tar");
2799 if ((m_request.url.path().endsWith(QLatin1String(".ps.gz"))))
2800 m_mimeType = QLatin1String("application/x-gzpostscript");
2801 }
2802
2803 // Prefer application/x-xz-compressed-tar over application/x-xz for LMZA compressed
2804 // tar files. Arch Linux AUR servers notoriously send the wrong mimetype for this.
2805 else if(m_mimeType == QLatin1String("application/x-xz")) {
2806 if (m_request.url.path().endsWith(QLatin1String(".tar.xz")) ||
2807 m_request.url.path().endsWith(QLatin1String(".txz"))) {
2808 m_mimeType = QLatin1String("application/x-xz-compressed-tar");
2809 }
2810 }
2811
2812 // Some webservers say "text/plain" when they mean "application/x-bzip"
2813 else if ((m_mimeType == QLatin1String("text/plain")) || (m_mimeType == QLatin1String("application/octet-stream"))) {
2814 const QString ext = QFileInfo(m_request.url.path()).suffix().toUpper();
2815 if (ext == QLatin1String("BZ2"))
2816 m_mimeType = QLatin1String("application/x-bzip");
2817 else if (ext == QLatin1String("PEM"))
2818 m_mimeType = QLatin1String("application/x-x509-ca-cert");
2819 else if (ext == QLatin1String("SWF"))
2820 m_mimeType = QLatin1String("application/x-shockwave-flash");
2821 else if (ext == QLatin1String("PLS"))
2822 m_mimeType = QLatin1String("audio/x-scpls");
2823 else if (ext == QLatin1String("WMV"))
2824 m_mimeType = QLatin1String("video/x-ms-wmv");
2825 else if (ext == QLatin1String("WEBM"))
2826 m_mimeType = QLatin1String("video/webm");
2827 else if (ext == QLatin1String("DEB"))
2828 m_mimeType = QLatin1String("application/x-deb");
2829 }
2830 kDebug(7113) << "after fixup" << m_mimeType;
2831}
2832
2833
2834void HTTPProtocol::fixupResponseContentEncoding()
2835{
2836 // WABA: Correct for tgz files with a gzip-encoding.
2837 // They really shouldn't put gzip in the Content-Encoding field!
2838 // Web-servers really shouldn't do this: They let Content-Size refer
2839 // to the size of the tgz file, not to the size of the tar file,
2840 // while the Content-Type refers to "tar" instead of "tgz".
2841 if (!m_contentEncodings.isEmpty() && m_contentEncodings.last() == QLatin1String("gzip")) {
2842 if (m_mimeType == QLatin1String("application/x-tar")) {
2843 m_contentEncodings.removeLast();
2844 m_mimeType = QLatin1String("application/x-compressed-tar");
2845 } else if (m_mimeType == QLatin1String("application/postscript")) {
2846 // LEONB: Adding another exception for psgz files.
2847 // Could we use the mimelnk files instead of hardcoding all this?
2848 m_contentEncodings.removeLast();
2849 m_mimeType = QLatin1String("application/x-gzpostscript");
2850 } else if ((m_request.allowTransferCompression &&
2851 m_mimeType == QLatin1String("text/html"))
2852 ||
2853 (m_request.allowTransferCompression &&
2854 m_mimeType != QLatin1String("application/x-compressed-tar") &&
2855 m_mimeType != QLatin1String("application/x-tgz") && // deprecated name
2856 m_mimeType != QLatin1String("application/x-targz") && // deprecated name
2857 m_mimeType != QLatin1String("application/x-gzip"))) {
2858 // Unzip!
2859 } else {
2860 m_contentEncodings.removeLast();
2861 m_mimeType = QLatin1String("application/x-gzip");
2862 }
2863 }
2864
2865 // We can't handle "bzip2" encoding (yet). So if we get something with
2866 // bzip2 encoding, we change the mimetype to "application/x-bzip".
2867 // Note for future changes: some web-servers send both "bzip2" as
2868 // encoding and "application/x-bzip[2]" as mimetype. That is wrong.
2869 // currently that doesn't bother us, because we remove the encoding
2870 // and set the mimetype to x-bzip anyway.
2871 if (!m_contentEncodings.isEmpty() && m_contentEncodings.last() == QLatin1String("bzip2")) {
2872 m_contentEncodings.removeLast();
2873 m_mimeType = QLatin1String("application/x-bzip");
2874 }
2875}
2876
2877//Return true if the term was found, false otherwise. Advance *pos.
2878//If (*pos + strlen(term) >= end) just advance *pos to end and return false.
2879//This means that users should always search for the shortest terms first.
2880static bool consume(const char input[], int *pos, int end, const char *term)
2881{
2882 // note: gcc/g++ is quite good at optimizing away redundant strlen()s
2883 int idx = *pos;
2884 if (idx + (int)strlen(term) >= end) {
2885 *pos = end;
2886 return false;
2887 }
2888 if (strncasecmp(&input[idx], term, strlen(term)) == 0) {
2889 *pos = idx + strlen(term);
2890 return true;
2891 }
2892 return false;
2893}
2894
2901bool HTTPProtocol::readResponseHeader()
2902{
2903 resetResponseParsing();
2904 if (m_request.cacheTag.ioMode == ReadFromCache &&
2905 m_request.cacheTag.plan(m_maxCacheAge) == CacheTag::UseCached) {
2906 // parseHeaderFromCache replaces this method in case of cached content
2907 return parseHeaderFromCache();
2908 }
2909
2910try_again:
2911 kDebug(7113);
2912
2913 bool upgradeRequired = false; // Server demands that we upgrade to something
2914 // This is also true if we ask to upgrade and
2915 // the server accepts, since we are now
2916 // committed to doing so
2917 bool noHeadersFound = false;
2918
2919 m_request.cacheTag.charset.clear();
2920 m_responseHeaders.clear();
2921
2922 static const int maxHeaderSize = 128 * 1024;
2923
2924 char buffer[maxHeaderSize];
2925 bool cont = false;
2926 bool bCanResume = false;
2927
2928 if (!isConnected()) {
2929 kDebug(7113) << "No connection.";
2930 return false; // Reestablish connection and try again
2931 }
2932
2933#if 0
2934 // NOTE: This is unnecessary since TCPSlaveBase::read does the same exact
2935 // thing. Plus, if we are unable to read from the socket we need to resend
2936 // the request as done below, not error out! Do not assume remote server
2937 // will honor persistent connections!!
2938 if (!waitForResponse(m_remoteRespTimeout)) {
2939 kDebug(7113) << "Got socket error:" << socket()->errorString();
2940 // No response error
2941 error(ERR_SERVER_TIMEOUT , m_request.url.host());
2942 return false;
2943 }
2944#endif
2945
2946 int bufPos = 0;
2947 bool foundDelimiter = readDelimitedText(buffer, &bufPos, maxHeaderSize, 1);
2948 if (!foundDelimiter && bufPos < maxHeaderSize) {
2949 kDebug(7113) << "EOF while waiting for header start.";
2950 if (m_request.isKeepAlive && m_iEOFRetryCount < 2) {
2951 m_iEOFRetryCount++;
2952 httpCloseConnection(); // Try to reestablish connection.
2953 return false; // Reestablish connection and try again.
2954 }
2955
2956 if (m_request.method == HTTP_HEAD) {
2957 // HACK
2958 // Some web-servers fail to respond properly to a HEAD request.
2959 // We compensate for their failure to properly implement the HTTP standard
2960 // by assuming that they will be sending html.
2961 kDebug(7113) << "HEAD -> returned mimetype:" << DEFAULT_MIME_TYPE;
2962 mimeType(QLatin1String(DEFAULT_MIME_TYPE));
2963 return true;
2964 }
2965
2966 kDebug(7113) << "Connection broken !";
2967 error( ERR_CONNECTION_BROKEN, m_request.url.host() );
2968 return false;
2969 }
2970 if (!foundDelimiter) {
2971 //### buffer too small for first line of header(!)
2972 Q_ASSERT(0);
2973 }
2974
2975 kDebug(7103) << "============ Received Status Response:";
2976 kDebug(7103) << QByteArray(buffer, bufPos).trimmed();
2977
2978 HTTP_REV httpRev = HTTP_None;
2979 int idx = 0;
2980
2981 if (idx != bufPos && buffer[idx] == '<') {
2982 kDebug(7103) << "No valid HTTP header found! Document starts with XML/HTML tag";
2983 // document starts with a tag, assume HTML instead of text/plain
2984 m_mimeType = QLatin1String("text/html");
2985 m_request.responseCode = 200; // Fake it
2986 httpRev = HTTP_Unknown;
2987 m_request.isKeepAlive = false;
2988 noHeadersFound = true;
2989 // put string back
2990 unread(buffer, bufPos);
2991 goto endParsing;
2992 }
2993
2994 // "HTTP/1.1" or similar
2995 if (consume(buffer, &idx, bufPos, "ICY ")) {
2996 httpRev = SHOUTCAST;
2997 m_request.isKeepAlive = false;
2998 } else if (consume(buffer, &idx, bufPos, "HTTP/")) {
2999 if (consume(buffer, &idx, bufPos, "1.0")) {
3000 httpRev = HTTP_10;
3001 m_request.isKeepAlive = false;
3002 } else if (consume(buffer, &idx, bufPos, "1.1")) {
3003 httpRev = HTTP_11;
3004 }
3005 }
3006
3007 if (httpRev == HTTP_None && bufPos != 0) {
3008 // Remote server does not seem to speak HTTP at all
3009 // Put the crap back into the buffer and hope for the best
3010 kDebug(7113) << "DO NOT WANT." << bufPos;
3011 unread(buffer, bufPos);
3012 if (m_request.responseCode) {
3013 m_request.prevResponseCode = m_request.responseCode;
3014 }
3015 m_request.responseCode = 200; // Fake it
3016 httpRev = HTTP_Unknown;
3017 m_request.isKeepAlive = false;
3018 noHeadersFound = true;
3019 goto endParsing;
3020 }
3021
3022 // response code //### maybe wrong if we need several iterations for this response...
3023 //### also, do multiple iterations (cf. try_again) to parse one header work w/ pipelining?
3024 if (m_request.responseCode) {
3025 m_request.prevResponseCode = m_request.responseCode;
3026 }
3027 skipSpace(buffer, &idx, bufPos);
3028 //TODO saner handling of invalid response code strings
3029 if (idx != bufPos) {
3030 m_request.responseCode = atoi(&buffer[idx]);
3031 } else {
3032 m_request.responseCode = 200;
3033 }
3034 // move idx to start of (yet to be fetched) next line, skipping the "OK"
3035 idx = bufPos;
3036 // (don't bother parsing the "OK", what do we do if it isn't there anyway?)
3037
3038 // immediately act on most response codes...
3039
3040 // Protect users against bogus username intended to fool them into visiting
3041 // sites they had no intention of visiting.
3042 if (isPotentialSpoofingAttack(m_request, config())) {
3043 // kDebug(7113) << "**** POTENTIAL ADDRESS SPOOFING:" << m_request.url;
3044 const int result =
3045 messageBox(WarningYesNo,
3046 i18nc("@info Security check on url being accessed",
3047 "<p>You are about to log in to the site \"%1\" "
3048 "with the username \"%2\", but the website "
3049 "does not require authentication. "
3050 "This may be an attempt to trick you.</p>"
3051 "<p>Is \"%1\" the site you want to visit?</p>",
3052 m_request.url.host(), m_request.url.user()),
3053 i18nc("@title:window", "Confirm Website Access"));
3054 if (result == KMessageBox::No) {
3055 error(ERR_USER_CANCELED, m_request.url.url());
3056 return false;
3057 }
3058 setMetaData(QLatin1String("{internal~currenthost}LastSpoofedUserName"), m_request.url.user());
3059 }
3060
3061 if (m_request.responseCode != 200 && m_request.responseCode != 304) {
3062 m_request.cacheTag.ioMode = NoCache;
3063
3064 if (m_request.responseCode >= 500 && m_request.responseCode <= 599) {
3065 // Server side errors
3066 if (m_request.method == HTTP_HEAD) {
3067 ; // Ignore error
3068 } else {
3069 if (!sendErrorPageNotification()) {
3070 error(ERR_INTERNAL_SERVER, m_request.url.prettyUrl());
3071 return false;
3072 }
3073 }
3074 } else if (m_request.responseCode == 416) {
3075 // Range not supported
3076 m_request.offset = 0;
3077 return false; // Try again.
3078 } else if (m_request.responseCode == 426) {
3079 // Upgrade Required
3080 upgradeRequired = true;
3081 } else if (m_request.responseCode >= 400 && m_request.responseCode <= 499 && !isAuthenticationRequired(m_request.responseCode)) {
3082 // Any other client errors
3083 // Tell that we will only get an error page here.
3084 if (!sendErrorPageNotification()) {
3085 if (m_request.responseCode == 403)
3086 error(ERR_ACCESS_DENIED, m_request.url.prettyUrl());
3087 else
3088 error(ERR_DOES_NOT_EXIST, m_request.url.prettyUrl());
3089 return false;
3090 }
3091 } else if (m_request.responseCode >= 301 && m_request.responseCode<= 308) {
3092 // NOTE: According to RFC 2616 (section 10.3.[2-4,8]), 301 and 302
3093 // redirects for a POST operation should not be convered to a GET
3094 // request. That should only be done for a 303 response. However,
3095 // because almost all other client implementations do exactly that
3096 // in violation of the spec, many servers have simply adapted to
3097 // this way of doing things! Thus, we are forced to do the same
3098 // thing here. Otherwise, we lose compatibility and might not be
3099 // able to correctly retrieve sites that redirect.
3100 switch (m_request.responseCode) {
3101 case 301: // Moved Permanently
3102 setMetaData(QLatin1String("permanent-redirect"), QLatin1String("true"));
3103 // fall through
3104 case 302: // Found
3105 if (m_request.sentMethodString == "POST") {
3106 setMetaData(QLatin1String("redirect-to-get"), QLatin1String("true"));
3107 }
3108 break;
3109 case 303: // See Other
3110 if (m_request.method != HTTP_HEAD) {
3111 setMetaData(QLatin1String("redirect-to-get"), QLatin1String("true"));
3112 }
3113 break;
3114 case 308: // Permanent Redirect
3115 setMetaData(QLatin1String("permanent-redirect"), QLatin1String("true"));
3116 break;
3117 default:
3118 break;
3119 }
3120 } else if (m_request.responseCode == 204) {
3121 // No content
3122
3123 // error(ERR_NO_CONTENT, i18n("Data have been successfully sent."));
3124 // Short circuit and do nothing!
3125
3126 // The original handling here was wrong, this is not an error: eg. in the
3127 // example of a 204 No Content response to a PUT completing.
3128 // m_iError = true;
3129 // return false;
3130 } else if (m_request.responseCode == 206) {
3131 if (m_request.offset) {
3132 bCanResume = true;
3133 }
3134 } else if (m_request.responseCode == 102) {
3135 // Processing (for WebDAV)
3136 /***
3137 * This status code is given when the server expects the
3138 * command to take significant time to complete. So, inform
3139 * the user.
3140 */
3141 infoMessage( i18n( "Server processing request, please wait..." ) );
3142 cont = true;
3143 } else if (m_request.responseCode == 100) {
3144 // We got 'Continue' - ignore it
3145 cont = true;
3146 }
3147 } // (m_request.responseCode != 200 && m_request.responseCode != 304)
3148
3149endParsing:
3150 bool authRequiresAnotherRoundtrip = false;
3151
3152 // Skip the whole header parsing if we got no HTTP headers at all
3153 if (!noHeadersFound) {
3154 // Auth handling
3155 const bool wasAuthError = isAuthenticationRequired(m_request.prevResponseCode);
3156 const bool isAuthError = isAuthenticationRequired(m_request.responseCode);
3157 const bool sameAuthError = (m_request.responseCode == m_request.prevResponseCode);
3158 kDebug(7113) << "wasAuthError=" << wasAuthError << "isAuthError=" << isAuthError
3159 << "sameAuthError=" << sameAuthError;
3160 // Not the same authorization error as before and no generic error?
3161 // -> save the successful credentials.
3162 if (wasAuthError && (m_request.responseCode < 400 || (isAuthError && !sameAuthError))) {
3163 saveAuthenticationData();
3164 }
3165
3166 // done with the first line; now tokenize the other lines
3167
3168 // TODO review use of STRTOLL vs. QByteArray::toInt()
3169
3170 foundDelimiter = readDelimitedText(buffer, &bufPos, maxHeaderSize, 2);
3171 kDebug(7113) << " -- full response:" << endl << QByteArray(buffer, bufPos).trimmed();
3172 // Use this to see newlines:
3173 //kDebug(7113) << " -- full response:" << endl << QByteArray(buffer, bufPos).replace("\r", "\\r").replace("\n", "\\n\n");
3174 Q_ASSERT(foundDelimiter);
3175
3176 //NOTE because tokenizer will overwrite newlines in case of line continuations in the header
3177 // unread(buffer, bufSize) will not generally work anymore. we don't need it either.
3178 // either we have a http response line -> try to parse the header, fail if it doesn't work
3179 // or we have garbage -> fail.
3180 HeaderTokenizer tokenizer(buffer);
3181 tokenizer.tokenize(idx, sizeof(buffer));
3182
3183 // Note that not receiving "accept-ranges" means that all bets are off
3184 // wrt the server supporting ranges.
3185 TokenIterator tIt = tokenizer.iterator("accept-ranges");
3186 if (tIt.hasNext() && tIt.next().toLower().startsWith("none")) { // krazy:exclude=strings
3187 bCanResume = false;
3188 }
3189
3190 tIt = tokenizer.iterator("keep-alive");
3191 while (tIt.hasNext()) {
3192 QByteArray ka = tIt.next().trimmed().toLower();
3193 if (ka.startsWith("timeout=")) { // krazy:exclude=strings
3194 int ka_timeout = ka.mid(qstrlen("timeout=")).trimmed().toInt();
3195 if (ka_timeout > 0)
3196 m_request.keepAliveTimeout = ka_timeout;
3197 if (httpRev == HTTP_10) {
3198 m_request.isKeepAlive = true;
3199 }
3200
3201 break; // we want to fetch ka timeout only
3202 }
3203 }
3204
3205 // get the size of our data
3206 tIt = tokenizer.iterator("content-length");
3207 if (tIt.hasNext()) {
3208 m_iSize = STRTOLL(tIt.next().constData(), 0, 10);
3209 }
3210
3211 tIt = tokenizer.iterator("content-location");
3212 if (tIt.hasNext()) {
3213 setMetaData(QLatin1String("content-location"), toQString(tIt.next().trimmed()));
3214 }
3215
3216 // which type of data do we have?
3217 QString mediaValue;
3218 QString mediaAttribute;
3219 tIt = tokenizer.iterator("content-type");
3220 if (tIt.hasNext()) {
3221 QList<QByteArray> l = tIt.next().split(';');
3222 if (!l.isEmpty()) {
3223 // Assign the mime-type.
3224 m_mimeType = toQString(l.first().trimmed().toLower());
3225 if (m_mimeType.startsWith(QLatin1Char('"'))) {
3226 m_mimeType.remove(0, 1);
3227 }
3228 if (m_mimeType.endsWith(QLatin1Char('"'))) {
3229 m_mimeType.chop(1);
3230 }
3231 kDebug(7113) << "Content-type:" << m_mimeType;
3232 l.removeFirst();
3233 }
3234
3235 // If we still have text, then it means we have a mime-type with a
3236 // parameter (eg: charset=iso-8851) ; so let's get that...
3237 Q_FOREACH (const QByteArray &statement, l) {
3238 const int index = statement.indexOf('=');
3239 if (index <= 0) {
3240 mediaAttribute = toQString(statement.mid(0, index));
3241 } else {
3242 mediaAttribute = toQString(statement.mid(0, index));
3243 mediaValue = toQString(statement.mid(index+1));
3244 }
3245 mediaAttribute = mediaAttribute.trimmed();
3246 mediaValue = mediaValue.trimmed();
3247
3248 bool quoted = false;
3249 if (mediaValue.startsWith(QLatin1Char('"'))) {
3250 quoted = true;
3251 mediaValue.remove(0, 1);
3252 }
3253
3254 if (mediaValue.endsWith(QLatin1Char('"'))) {
3255 mediaValue.chop(1);
3256 }
3257
3258 kDebug (7113) << "Encoding-type:" << mediaAttribute << "=" << mediaValue;
3259
3260 if (mediaAttribute == QLatin1String("charset")) {
3261 mediaValue = mediaValue.toLower();
3262 m_request.cacheTag.charset = mediaValue;
3263 setMetaData(QLatin1String("charset"), mediaValue);
3264 } else {
3265 setMetaData(QLatin1String("media-") + mediaAttribute, mediaValue);
3266 if (quoted) {
3267 setMetaData(QLatin1String("media-") + mediaAttribute + QLatin1String("-kio-quoted"),
3268 QLatin1String("true"));
3269 }
3270 }
3271 }
3272 }
3273
3274 // content?
3275 tIt = tokenizer.iterator("content-encoding");
3276 while (tIt.hasNext()) {
3277 // This is so wrong !! No wonder kio_http is stripping the
3278 // gzip encoding from downloaded files. This solves multiple
3279 // bug reports and caitoo's problem with downloads when such a
3280 // header is encountered...
3281
3282 // A quote from RFC 2616:
3283 // " When present, its (Content-Encoding) value indicates what additional
3284 // content have been applied to the entity body, and thus what decoding
3285 // mechanism must be applied to obtain the media-type referenced by the
3286 // Content-Type header field. Content-Encoding is primarily used to allow
3287 // a document to be compressed without loosing the identity of its underlying
3288 // media type. Simply put if it is specified, this is the actual mime-type
3289 // we should use when we pull the resource !!!
3290 addEncoding(toQString(tIt.next()), m_contentEncodings);
3291 }
3292 // Refer to RFC 2616 sec 15.5/19.5.1 and RFC 2183
3293 tIt = tokenizer.iterator("content-disposition");
3294 if (tIt.hasNext()) {
3295 parseContentDisposition(toQString(tIt.next()));
3296 }
3297 tIt = tokenizer.iterator("content-language");
3298 if (tIt.hasNext()) {
3299 QString language = toQString(tIt.next().trimmed());
3300 if (!language.isEmpty()) {
3301 setMetaData(QLatin1String("content-language"), language);
3302 }
3303 }
3304
3305 tIt = tokenizer.iterator("proxy-connection");
3306 if (tIt.hasNext() && isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) {
3307 QByteArray pc = tIt.next().toLower();
3308 if (pc.startsWith("close")) { // krazy:exclude=strings
3309 m_request.isKeepAlive = false;
3310 } else if (pc.startsWith("keep-alive")) { // krazy:exclude=strings
3311 m_request.isKeepAlive = true;
3312 }
3313 }
3314
3315 tIt = tokenizer.iterator("link");
3316 if (tIt.hasNext()) {
3317 // We only support Link: <url>; rel="type" so far
3318 QStringList link = toQString(tIt.next()).split(QLatin1Char(';'), QString::SkipEmptyParts);
3319 if (link.count() == 2) {
3320 QString rel = link[1].trimmed();
3321 if (rel.startsWith(QLatin1String("rel=\""))) {
3322 rel = rel.mid(5, rel.length() - 6);
3323 if (rel.toLower() == QLatin1String("pageservices")) {
3324 //### the remove() part looks fishy!
3325 QString url = link[0].remove(QRegExp(QLatin1String("[<>]"))).trimmed();
3326 setMetaData(QLatin1String("PageServices"), url);
3327 }
3328 }
3329 }
3330 }
3331
3332 tIt = tokenizer.iterator("p3p");
3333 if (tIt.hasNext()) {
3334 // P3P privacy policy information
3335 QStringList policyrefs, compact;
3336 while (tIt.hasNext()) {
3337 QStringList policy = toQString(tIt.next().simplified())
3338 .split(QLatin1Char('='), QString::SkipEmptyParts);
3339 if (policy.count() == 2) {
3340 if (policy[0].toLower() == QLatin1String("policyref")) {
3341 policyrefs << policy[1].remove(QRegExp(QLatin1String("[\")\']"))).trimmed();
3342 } else if (policy[0].toLower() == QLatin1String("cp")) {
3343 // We convert to cp\ncp\ncp\n[...]\ncp to be consistent with
3344 // other metadata sent in strings. This could be a bit more
3345 // efficient but I'm going for correctness right now.
3346 const QString s = policy[1].remove(QRegExp(QLatin1String("[\")\']")));
3347 const QStringList cps = s.split(QLatin1Char(' '), QString::SkipEmptyParts);
3348 compact << cps;
3349 }
3350 }
3351 }
3352 if (!policyrefs.isEmpty()) {
3353 setMetaData(QLatin1String("PrivacyPolicy"), policyrefs.join(QLatin1String("\n")));
3354 }
3355 if (!compact.isEmpty()) {
3356 setMetaData(QLatin1String("PrivacyCompactPolicy"), compact.join(QLatin1String("\n")));
3357 }
3358 }
3359
3360 // continue only if we know that we're at least HTTP/1.0
3361 if (httpRev == HTTP_11 || httpRev == HTTP_10) {
3362 // let them tell us if we should stay alive or not
3363 tIt = tokenizer.iterator("connection");
3364 while (tIt.hasNext()) {
3365 QByteArray connection = tIt.next().toLower();
3366 if (!(isHttpProxy(m_request.proxyUrl) && !isAutoSsl())) {
3367 if (connection.startsWith("close")) { // krazy:exclude=strings
3368 m_request.isKeepAlive = false;
3369 } else if (connection.startsWith("keep-alive")) { // krazy:exclude=strings
3370 m_request.isKeepAlive = true;
3371 }
3372 }
3373 if (connection.startsWith("upgrade")) { // krazy:exclude=strings
3374 if (m_request.responseCode == 101) {
3375 // Ok, an upgrade was accepted, now we must do it
3376 upgradeRequired = true;
3377 } else if (upgradeRequired) { // 426
3378 // Nothing to do since we did it above already
3379 }
3380 }
3381 }
3382 // what kind of encoding do we have? transfer?
3383 tIt = tokenizer.iterator("transfer-encoding");
3384 while (tIt.hasNext()) {
3385 // If multiple encodings have been applied to an entity, the
3386 // transfer-codings MUST be listed in the order in which they
3387 // were applied.
3388 addEncoding(toQString(tIt.next().trimmed()), m_transferEncodings);
3389 }
3390
3391 // md5 signature
3392 tIt = tokenizer.iterator("content-md5");
3393 if (tIt.hasNext()) {
3394 m_contentMD5 = toQString(tIt.next().trimmed());
3395 }
3396
3397 // *** Responses to the HTTP OPTIONS method follow
3398 // WebDAV capabilities
3399 tIt = tokenizer.iterator("dav");
3400 while (tIt.hasNext()) {
3401 m_davCapabilities << toQString(tIt.next());
3402 }
3403 // *** Responses to the HTTP OPTIONS method finished
3404 }
3405
3406
3407 // Now process the HTTP/1.1 upgrade
3408 QStringList upgradeOffers;
3409 tIt = tokenizer.iterator("upgrade");
3410 if (tIt.hasNext()) {
3411 // Now we have to check to see what is offered for the upgrade
3412 QString offered = toQString(tIt.next());
3413 upgradeOffers = offered.split(QRegExp(QLatin1String("[ \n,\r\t]")), QString::SkipEmptyParts);
3414 }
3415 Q_FOREACH (const QString &opt, upgradeOffers) {
3416 if (opt == QLatin1String("TLS/1.0")) {
3417 if (!startSsl() && upgradeRequired) {
3418 error(ERR_UPGRADE_REQUIRED, opt);
3419 return false;
3420 }
3421 } else if (opt == QLatin1String("HTTP/1.1")) {
3422 httpRev = HTTP_11;
3423 } else if (upgradeRequired) {
3424 // we are told to do an upgrade we don't understand
3425 error(ERR_UPGRADE_REQUIRED, opt);
3426 return false;
3427 }
3428 }
3429
3430 // Harvest cookies (mmm, cookie fields!)
3431 QByteArray cookieStr; // In case we get a cookie.
3432 tIt = tokenizer.iterator("set-cookie");
3433 while (tIt.hasNext()) {
3434 cookieStr += "Set-Cookie: ";
3435 cookieStr += tIt.next();
3436 cookieStr += '\n';
3437 }
3438 if (!cookieStr.isEmpty()) {
3439 if ((m_request.cookieMode == HTTPRequest::CookiesAuto) && m_request.useCookieJar) {
3440 // Give cookies to the cookiejar.
3441 const QString domain = config()->readEntry("cross-domain");
3442 if (!domain.isEmpty() && isCrossDomainRequest(m_request.url.host(), domain)) {
3443 cookieStr = "Cross-Domain\n" + cookieStr;
3444 }
3445 addCookies( m_request.url.url(), cookieStr );
3446 } else if (m_request.cookieMode == HTTPRequest::CookiesManual) {
3447 // Pass cookie to application
3448 setMetaData(QLatin1String("setcookies"), QString::fromUtf8(cookieStr)); // ## is encoding ok?
3449 }
3450 }
3451
3452 // We need to reread the header if we got a '100 Continue' or '102 Processing'
3453 // This may be a non keepalive connection so we handle this kind of loop internally
3454 if ( cont )
3455 {
3456 kDebug(7113) << "cont; returning to mark try_again";
3457 goto try_again;
3458 }
3459
3460 if (!m_isChunked && (m_iSize == NO_SIZE) && m_request.isKeepAlive &&
3461 canHaveResponseBody(m_request.responseCode, m_request.method)) {
3462 kDebug(7113) << "Ignoring keep-alive: otherwise unable to determine response body length.";
3463 m_request.isKeepAlive = false;
3464 }
3465
3466 // TODO cache the proxy auth data (not doing this means a small performance regression for now)
3467
3468 // we may need to send (Proxy or WWW) authorization data
3469 if ((!m_request.doNotWWWAuthenticate && m_request.responseCode == 401) ||
3470 (!m_request.doNotProxyAuthenticate && m_request.responseCode == 407)) {
3471 authRequiresAnotherRoundtrip = handleAuthenticationHeader(&tokenizer);
3472 if (m_iError) {
3473 // If error is set, then handleAuthenticationHeader failed.
3474 return false;
3475 }
3476 } else {
3477 authRequiresAnotherRoundtrip = false;
3478 }
3479
3480 QString locationStr;
3481 // In fact we should do redirection only if we have a redirection response code (300 range)
3482 tIt = tokenizer.iterator("location");
3483 if (tIt.hasNext() && m_request.responseCode > 299 && m_request.responseCode < 400) {
3484 locationStr = QString::fromUtf8(tIt.next().trimmed());
3485 }
3486 // We need to do a redirect
3487 if (!locationStr.isEmpty())
3488 {
3489 KUrl u(m_request.url, locationStr);
3490 if(!u.isValid())
3491 {
3492 error(ERR_MALFORMED_URL, u.prettyUrl());
3493 return false;
3494 }
3495
3496 // preserve #ref: (bug 124654)
3497 // if we were at http://host/resource1#ref, we sent a GET for "/resource1"
3498 // if we got redirected to http://host/resource2, then we have to re-add
3499 // the fragment:
3500 if (m_request.url.hasRef() && !u.hasRef() &&
3501 (m_request.url.host() == u.host()) &&
3502 (m_request.url.protocol() == u.protocol()))
3503 u.setRef(m_request.url.ref());
3504
3505 m_isRedirection = true;
3506
3507 if (!m_request.id.isEmpty())
3508 {
3509 sendMetaData();
3510 }
3511
3512 // If we're redirected to a http:// url, remember that we're doing webdav...
3513 if (m_protocol == "webdav" || m_protocol == "webdavs"){
3514 if(u.protocol() == QLatin1String("http")){
3515 u.setProtocol(QLatin1String("webdav"));
3516 }else if(u.protocol() == QLatin1String("https")){
3517 u.setProtocol(QLatin1String("webdavs"));
3518 }
3519
3520 m_request.redirectUrl = u;
3521 }
3522
3523 kDebug(7113) << "Re-directing from" << m_request.url
3524 << "to" << u;
3525
3526 redirection(u);
3527
3528 // It would be hard to cache the redirection response correctly. The possible benefit
3529 // is small (if at all, assuming fast disk and slow network), so don't do it.
3530 cacheFileClose();
3531 setCacheabilityMetadata(false);
3532 }
3533
3534 // Inform the job that we can indeed resume...
3535 if (bCanResume && m_request.offset) {
3536 //TODO turn off caching???
3537 canResume();
3538 } else {
3539 m_request.offset = 0;
3540 }
3541
3542 // Correct a few common wrong content encodings
3543 fixupResponseContentEncoding();
3544
3545 // Correct some common incorrect pseudo-mimetypes
3546 fixupResponseMimetype();
3547
3548 // parse everything related to expire and other dates, and cache directives; also switch
3549 // between cache reading and writing depending on cache validation result.
3550 cacheParseResponseHeader(tokenizer);
3551 }
3552
3553 if (m_request.cacheTag.ioMode == ReadFromCache) {
3554 if (m_request.cacheTag.policy == CC_Verify &&
3555 m_request.cacheTag.plan(m_maxCacheAge) != CacheTag::UseCached) {
3556 kDebug(7113) << "Reading resource from cache even though the cache plan is not "
3557 "UseCached; the server is probably sending wrong expiry information.";
3558 }
3559 // parseHeaderFromCache replaces this method in case of cached content
3560 return parseHeaderFromCache();
3561 }
3562
3563 if (config()->readEntry("PropagateHttpHeader", false) ||
3564 m_request.cacheTag.ioMode == WriteToCache) {
3565 // store header lines if they will be used; note that the tokenizer removing
3566 // line continuation special cases is probably more good than bad.
3567 int nextLinePos = 0;
3568 int prevLinePos = 0;
3569 bool haveMore = true;
3570 while (haveMore) {
3571 haveMore = nextLine(buffer, &nextLinePos, bufPos);
3572 int prevLineEnd = nextLinePos;
3573 while (buffer[prevLineEnd - 1] == '\r' || buffer[prevLineEnd - 1] == '\n') {
3574 prevLineEnd--;
3575 }
3576
3577 m_responseHeaders.append(QString::fromLatin1(&buffer[prevLinePos],
3578 prevLineEnd - prevLinePos));
3579 prevLinePos = nextLinePos;
3580 }
3581
3582 // IMPORTANT: Do not remove this line because forwardHttpResponseHeader
3583 // is called below. This line is here to ensure the response headers are
3584 // available to the client before it receives mimetype information.
3585 // The support for putting ioslaves on hold in the KIO-QNAM integration
3586 // will break if this line is removed.
3587 setMetaData(QLatin1String("HTTP-Headers"), m_responseHeaders.join(QString(QLatin1Char('\n'))));
3588 }
3589
3590 // Let the app know about the mime-type iff this is not a redirection and
3591 // the mime-type string is not empty.
3592 if (!m_isRedirection && m_request.responseCode != 204 &&
3593 (!m_mimeType.isEmpty() || m_request.method == HTTP_HEAD) &&
3594 (m_isLoadingErrorPage || !authRequiresAnotherRoundtrip)) {
3595 kDebug(7113) << "Emitting mimetype " << m_mimeType;
3596 mimeType( m_mimeType );
3597 }
3598
3599 // IMPORTANT: Do not move the function call below before doing any
3600 // redirection. Otherwise it might mess up some sites, see BR# 150904.
3601 forwardHttpResponseHeader();
3602
3603 if (m_request.method == HTTP_HEAD)
3604 return true;
3605
3606 return !authRequiresAnotherRoundtrip; // return true if no more credentials need to be sent
3607}
3608
3609void HTTPProtocol::parseContentDisposition(const QString &disposition)
3610{
3611 const QMap<QString, QString> parameters = contentDispositionParser(disposition);
3612
3613 QMap<QString, QString>::const_iterator i = parameters.constBegin();
3614 while (i != parameters.constEnd()) {
3615 setMetaData(QLatin1String("content-disposition-") + i.key(), i.value());
3616 kDebug(7113) << "Content-Disposition:" << i.key() << "=" << i.value();
3617 ++i;
3618 }
3619}
3620
3621void HTTPProtocol::addEncoding(const QString &_encoding, QStringList &encs)
3622{
3623 QString encoding = _encoding.trimmed().toLower();
3624 // Identity is the same as no encoding
3625 if (encoding == QLatin1String("identity")) {
3626 return;
3627 } else if (encoding == QLatin1String("8bit")) {
3628 // Strange encoding returned by http://linac.ikp.physik.tu-darmstadt.de
3629 return;
3630 } else if (encoding == QLatin1String("chunked")) {
3631 m_isChunked = true;
3632 // Anyone know of a better way to handle unknown sizes possibly/ideally with unsigned ints?
3633 //if ( m_cmd != CMD_COPY )
3634 m_iSize = NO_SIZE;
3635 } else if ((encoding == QLatin1String("x-gzip")) || (encoding == QLatin1String("gzip"))) {
3636 encs.append(QLatin1String("gzip"));
3637 } else if ((encoding == QLatin1String("x-bzip2")) || (encoding == QLatin1String("bzip2"))) {
3638 encs.append(QLatin1String("bzip2")); // Not yet supported!
3639 } else if ((encoding == QLatin1String("x-deflate")) || (encoding == QLatin1String("deflate"))) {
3640 encs.append(QLatin1String("deflate"));
3641 } else {
3642 kDebug(7113) << "Unknown encoding encountered. "
3643 << "Please write code. Encoding =" << encoding;
3644 }
3645}
3646
3647void HTTPProtocol::cacheParseResponseHeader(const HeaderTokenizer &tokenizer)
3648{
3649 if (!m_request.cacheTag.useCache)
3650 return;
3651
3652 // might have to add more response codes
3653 if (m_request.responseCode != 200 && m_request.responseCode != 304) {
3654 return;
3655 }
3656
3657 m_request.cacheTag.servedDate = -1;
3658 m_request.cacheTag.lastModifiedDate = -1;
3659 m_request.cacheTag.expireDate = -1;
3660
3661 const qint64 currentDate = QDateTime::currentMSecsSinceEpoch()/1000;
3662 bool mayCache = m_request.cacheTag.ioMode != NoCache;
3663
3664 TokenIterator tIt = tokenizer.iterator("last-modified");
3665 if (tIt.hasNext()) {
3666 m_request.cacheTag.lastModifiedDate = toTime_t(toQString(tIt.next()), KDateTime::RFCDate);
3667
3668 //### might be good to canonicalize the date by using KDateTime::toString()
3669 if (m_request.cacheTag.lastModifiedDate != -1) {
3670 setMetaData(QLatin1String("modified"), toQString(tIt.current()));
3671 }
3672 }
3673
3674 // determine from available information when the response was served by the origin server
3675 {
3676 qint64 dateHeader = -1;
3677 tIt = tokenizer.iterator("date");
3678 if (tIt.hasNext()) {
3679 dateHeader = toTime_t(toQString(tIt.next()), KDateTime::RFCDate);
3680 // -1 on error
3681 }
3682
3683 qint64 ageHeader = 0;
3684 tIt = tokenizer.iterator("age");
3685 if (tIt.hasNext()) {
3686 ageHeader = tIt.next().toLongLong();
3687 // 0 on error
3688 }
3689
3690 if (dateHeader != -1) {
3691 m_request.cacheTag.servedDate = dateHeader;
3692 } else if (ageHeader) {
3693 m_request.cacheTag.servedDate = currentDate - ageHeader;
3694 } else {
3695 m_request.cacheTag.servedDate = currentDate;
3696 }
3697 }
3698
3699 bool hasCacheDirective = false;
3700 // determine when the response "expires", i.e. becomes stale and needs revalidation
3701 {
3702 // (we also parse other cache directives here)
3703 qint64 maxAgeHeader = 0;
3704 tIt = tokenizer.iterator("cache-control");
3705 while (tIt.hasNext()) {
3706 QByteArray cacheStr = tIt.next().toLower();
3707 if (cacheStr.startsWith("no-cache") || cacheStr.startsWith("no-store")) { // krazy:exclude=strings
3708 // Don't put in cache
3709 mayCache = false;
3710 hasCacheDirective = true;
3711 } else if (cacheStr.startsWith("max-age=")) { // krazy:exclude=strings
3712 QByteArray ba = cacheStr.mid(qstrlen("max-age=")).trimmed();
3713 bool ok = false;
3714 maxAgeHeader = ba.toLongLong(&ok);
3715 if (ok) {
3716 hasCacheDirective = true;
3717 }
3718 }
3719 }
3720
3721 qint64 expiresHeader = -1;
3722 tIt = tokenizer.iterator("expires");
3723 if (tIt.hasNext()) {
3724 expiresHeader = toTime_t(toQString(tIt.next()), KDateTime::RFCDate);
3725 kDebug(7113) << "parsed expire date from 'expires' header:" << tIt.current();
3726 }
3727
3728 if (maxAgeHeader) {
3729 m_request.cacheTag.expireDate = m_request.cacheTag.servedDate + maxAgeHeader;
3730 } else if (expiresHeader != -1) {
3731 m_request.cacheTag.expireDate = expiresHeader;
3732 } else {
3733 // heuristic expiration date
3734 if (m_request.cacheTag.lastModifiedDate != -1) {
3735 // expAge is following the RFC 2616 suggestion for heuristic expiration
3736 qint64 expAge = (m_request.cacheTag.servedDate -
3737 m_request.cacheTag.lastModifiedDate) / 10;
3738 // not in the RFC: make sure not to have a huge heuristic cache lifetime
3739 expAge = qMin(expAge, qint64(3600 * 24));
3740 m_request.cacheTag.expireDate = m_request.cacheTag.servedDate + expAge;
3741 } else {
3742 m_request.cacheTag.expireDate = m_request.cacheTag.servedDate +
3743 DEFAULT_CACHE_EXPIRE;
3744 }
3745 }
3746 // make sure that no future clock monkey business causes the cache entry to un-expire
3747 if (m_request.cacheTag.expireDate < currentDate) {
3748 m_request.cacheTag.expireDate = 0; // January 1, 1970 :)
3749 }
3750 }
3751
3752 tIt = tokenizer.iterator("etag");
3753 if (tIt.hasNext()) {
3754 QString prevEtag = m_request.cacheTag.etag;
3755 m_request.cacheTag.etag = toQString(tIt.next());
3756 if (m_request.cacheTag.etag != prevEtag && m_request.responseCode == 304) {
3757 kDebug(7103) << "304 Not Modified but new entity tag - I don't think this is legal HTTP.";
3758 }
3759 }
3760
3761 // whoops.. we received a warning
3762 tIt = tokenizer.iterator("warning");
3763 if (tIt.hasNext()) {
3764 //Don't use warning() here, no need to bother the user.
3765 //Those warnings are mostly about caches.
3766 infoMessage(toQString(tIt.next()));
3767 }
3768
3769 // Cache management (HTTP 1.0)
3770 tIt = tokenizer.iterator("pragma");
3771 while (tIt.hasNext()) {
3772 if (tIt.next().toLower().startsWith("no-cache")) { // krazy:exclude=strings
3773 mayCache = false;
3774 hasCacheDirective = true;
3775 }
3776 }
3777
3778 // The deprecated Refresh Response
3779 tIt = tokenizer.iterator("refresh");
3780 if (tIt.hasNext()) {
3781 mayCache = false;
3782 setMetaData(QLatin1String("http-refresh"), toQString(tIt.next().trimmed()));
3783 }
3784
3785 // We don't cache certain text objects
3786 if (m_mimeType.startsWith(QLatin1String("text/")) && (m_mimeType != QLatin1String("text/css")) &&
3787 (m_mimeType != QLatin1String("text/x-javascript")) && !hasCacheDirective) {
3788 // Do not cache secure pages or pages
3789 // originating from password protected sites
3790 // unless the webserver explicitly allows it.
3791 if (isUsingSsl() || m_wwwAuth) {
3792 mayCache = false;
3793 }
3794 }
3795
3796 // note that we've updated cacheTag, so the plan() is with current data
3797 if (m_request.cacheTag.plan(m_maxCacheAge) == CacheTag::ValidateCached) {
3798 kDebug(7113) << "Cache needs validation";
3799 if (m_request.responseCode == 304) {
3800 kDebug(7113) << "...was revalidated by response code but not by updated expire times. "
3801 "We're going to set the expire date to 60 seconds in the future...";
3802 m_request.cacheTag.expireDate = currentDate + 60;
3803 if (m_request.cacheTag.policy == CC_Verify &&
3804 m_request.cacheTag.plan(m_maxCacheAge) != CacheTag::UseCached) {
3805 // "apparently" because we /could/ have made an error ourselves, but the errors I
3806 // witnessed were all the server's fault.
3807 kDebug(7113) << "this proxy or server apparently sends bogus expiry information.";
3808 }
3809 }
3810 }
3811
3812 // validation handling
3813 if (mayCache && m_request.responseCode == 200 && !m_mimeType.isEmpty()) {
3814 kDebug(7113) << "Cache, adding" << m_request.url;
3815 // ioMode can still be ReadFromCache here if we're performing a conditional get
3816 // aka validation
3817 m_request.cacheTag.ioMode = WriteToCache;
3818 if (!cacheFileOpenWrite()) {
3819 kDebug(7113) << "Error creating cache entry for " << m_request.url << "!\n";
3820 }
3821 m_maxCacheSize = config()->readEntry("MaxCacheSize", DEFAULT_MAX_CACHE_SIZE);
3822 } else if (m_request.responseCode == 304 && m_request.cacheTag.file) {
3823 if (!mayCache) {
3824 kDebug(7113) << "This webserver is confused about the cacheability of the data it sends.";
3825 }
3826 // the cache file should still be open for reading, see satisfyRequestFromCache().
3827 Q_ASSERT(m_request.cacheTag.file->openMode() == QIODevice::ReadOnly);
3828 Q_ASSERT(m_request.cacheTag.ioMode == ReadFromCache);
3829 } else {
3830 cacheFileClose();
3831 }
3832
3833 setCacheabilityMetadata(mayCache);
3834}
3835
3836void HTTPProtocol::setCacheabilityMetadata(bool cachingAllowed)
3837{
3838 if (!cachingAllowed) {
3839 setMetaData(QLatin1String("no-cache"), QLatin1String("true"));
3840 setMetaData(QLatin1String("expire-date"), QLatin1String("1")); // Expired
3841 } else {
3842 QString tmp;
3843 tmp.setNum(m_request.cacheTag.expireDate);
3844 setMetaData(QLatin1String("expire-date"), tmp);
3845 // slightly changed semantics from old creationDate, probably more correct now
3846 tmp.setNum(m_request.cacheTag.servedDate);
3847 setMetaData(QLatin1String("cache-creation-date"), tmp);
3848 }
3849}
3850
3851bool HTTPProtocol::sendCachedBody()
3852{
3853 infoMessage(i18n("Sending data to %1" , m_request.url.host()));
3854
3855 const qint64 size = m_POSTbuf->size();
3856 QByteArray cLength ("Content-Length: ");
3857 cLength += QByteArray::number(size);
3858 cLength += "\r\n\r\n";
3859
3860 kDebug(7113) << "sending cached data (size=" << size << ")";
3861
3862 // Send the content length...
3863 bool sendOk = (write(cLength.data(), cLength.size()) == (ssize_t) cLength.size());
3864 if (!sendOk) {
3865 kDebug( 7113 ) << "Connection broken when sending "
3866 << "content length: (" << m_request.url.host() << ")";
3867 error( ERR_CONNECTION_BROKEN, m_request.url.host() );
3868 return false;
3869 }
3870
3871 totalSize(size);
3872 // Make sure the read head is at the beginning...
3873 m_POSTbuf->reset();
3874 KIO::filesize_t totalBytesSent = 0;
3875
3876 // Send the data...
3877 while (!m_POSTbuf->atEnd()) {
3878 const QByteArray buffer = m_POSTbuf->read(65536);
3879 const ssize_t bytesSent = write(buffer.data(), buffer.size());
3880 if (bytesSent != static_cast<ssize_t>(buffer.size())) {
3881 kDebug(7113) << "Connection broken when sending message body: ("
3882 << m_request.url.host() << ")";
3883 error( ERR_CONNECTION_BROKEN, m_request.url.host() );
3884 return false;
3885 }
3886
3887 totalBytesSent += bytesSent;
3888 processedSize(totalBytesSent);
3889 }
3890
3891 return true;
3892}
3893
3894bool HTTPProtocol::sendBody()
3895{
3896 // If we have cached data, the it is either a repost or a DAV request so send
3897 // the cached data...
3898 if (m_POSTbuf)
3899 return sendCachedBody();
3900
3901 if (m_iPostDataSize == NO_SIZE) {
3902 // Try the old approach of retireving content data from the job
3903 // before giving up.
3904 if (retrieveAllData())
3905 return sendCachedBody();
3906
3907 error(ERR_POST_NO_SIZE, m_request.url.host());
3908 return false;
3909 }
3910
3911 kDebug(7113) << "sending data (size=" << m_iPostDataSize << ")";
3912
3913 infoMessage(i18n("Sending data to %1", m_request.url.host()));
3914
3915 QByteArray cLength ("Content-Length: ");
3916 cLength += QByteArray::number(m_iPostDataSize);
3917 cLength += "\r\n\r\n";
3918
3919 kDebug(7113) << cLength.trimmed();
3920
3921 // Send the content length...
3922 bool sendOk = (write(cLength.data(), cLength.size()) == (ssize_t) cLength.size());
3923 if (!sendOk) {
3924 // The server might have closed the connection due to a timeout, or maybe
3925 // some transport problem arose while the connection was idle.
3926 if (m_request.isKeepAlive)
3927 {
3928 httpCloseConnection();
3929 return true; // Try again
3930 }
3931
3932 kDebug(7113) << "Connection broken while sending POST content size to" << m_request.url.host();
3933 error( ERR_CONNECTION_BROKEN, m_request.url.host() );
3934 return false;
3935 }
3936
3937 // Send the amount
3938 totalSize(m_iPostDataSize);
3939
3940 // If content-length is 0, then do nothing but simply return true.
3941 if (m_iPostDataSize == 0)
3942 return true;
3943
3944 sendOk = true;
3945 KIO::filesize_t bytesSent = 0;
3946
3947 while (true) {
3948 dataReq();
3949
3950 QByteArray buffer;
3951 const int bytesRead = readData(buffer);
3952
3953 // On done...
3954 if (bytesRead == 0) {
3955 sendOk = (bytesSent == m_iPostDataSize);
3956 break;
3957 }
3958
3959 // On error return false...
3960 if (bytesRead < 0) {
3961 error(ERR_ABORTED, m_request.url.host());
3962 sendOk = false;
3963 break;
3964 }
3965
3966 // Cache the POST data in case of a repost request.
3967 cachePostData(buffer);
3968
3969 // This will only happen if transmitting the data fails, so we will simply
3970 // cache the content locally for the potential re-transmit...
3971 if (!sendOk)
3972 continue;
3973
3974 if (write(buffer.data(), bytesRead) == static_cast<ssize_t>(bytesRead)) {
3975 bytesSent += bytesRead;
3976 processedSize(bytesSent); // Send update status...
3977 continue;
3978 }
3979
3980 kDebug(7113) << "Connection broken while sending POST content to" << m_request.url.host();
3981 error(ERR_CONNECTION_BROKEN, m_request.url.host());
3982 sendOk = false;
3983 }
3984
3985 return sendOk;
3986}
3987
3988void HTTPProtocol::httpClose( bool keepAlive )
3989{
3990 kDebug(7113) << "keepAlive =" << keepAlive;
3991
3992 cacheFileClose();
3993
3994 // Only allow persistent connections for GET requests.
3995 // NOTE: we might even want to narrow this down to non-form
3996 // based submit requests which will require a meta-data from
3997 // khtml.
3998 if (keepAlive) {
3999 if (!m_request.keepAliveTimeout)
4000 m_request.keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT;
4001 else if (m_request.keepAliveTimeout > 2*DEFAULT_KEEP_ALIVE_TIMEOUT)
4002 m_request.keepAliveTimeout = 2*DEFAULT_KEEP_ALIVE_TIMEOUT;
4003
4004 kDebug(7113) << "keep alive (" << m_request.keepAliveTimeout << ")";
4005 QByteArray data;
4006 QDataStream stream( &data, QIODevice::WriteOnly );
4007 stream << int(99); // special: Close connection
4008 setTimeoutSpecialCommand(m_request.keepAliveTimeout, data);
4009
4010 return;
4011 }
4012
4013 httpCloseConnection();
4014}
4015
4016void HTTPProtocol::closeConnection()
4017{
4018 kDebug(7113);
4019 httpCloseConnection();
4020}
4021
4022void HTTPProtocol::httpCloseConnection()
4023{
4024 kDebug(7113);
4025 m_server.clear();
4026 disconnectFromHost();
4027 clearUnreadBuffer();
4028 setTimeoutSpecialCommand(-1); // Cancel any connection timeout
4029}
4030
4031void HTTPProtocol::slave_status()
4032{
4033 kDebug(7113);
4034
4035 if ( !isConnected() )
4036 httpCloseConnection();
4037
4038 slaveStatus( m_server.url.host(), isConnected() );
4039}
4040
4041void HTTPProtocol::mimetype( const KUrl& url )
4042{
4043 kDebug(7113) << url;
4044
4045 if (!maybeSetRequestUrl(url))
4046 return;
4047 resetSessionSettings();
4048
4049 m_request.method = HTTP_HEAD;
4050 m_request.cacheTag.policy= CC_Cache;
4051
4052 if (proceedUntilResponseHeader()) {
4053 httpClose(m_request.isKeepAlive);
4054 finished();
4055 }
4056
4057 kDebug(7113) << m_mimeType;
4058}
4059
4060void HTTPProtocol::special( const QByteArray &data )
4061{
4062 kDebug(7113);
4063
4064 int tmp;
4065 QDataStream stream(data);
4066
4067 stream >> tmp;
4068 switch (tmp) {
4069 case 1: // HTTP POST
4070 {
4071 KUrl url;
4072 qint64 size;
4073 stream >> url >> size;
4074 post( url, size );
4075 break;
4076 }
4077 case 2: // cache_update
4078 {
4079 KUrl url;
4080 bool no_cache;
4081 qint64 expireDate;
4082 stream >> url >> no_cache >> expireDate;
4083 if (no_cache) {
4084 QString filename = cacheFilePathFromUrl(url);
4085 // there is a tiny risk of deleting the wrong file due to hash collisions here.
4086 // this is an unimportant performance issue.
4087 // FIXME on Windows we may be unable to delete the file if open
4088 QFile::remove(filename);
4089 finished();
4090 break;
4091 }
4092 // let's be paranoid and inefficient here...
4093 HTTPRequest savedRequest = m_request;
4094
4095 m_request.url = url;
4096 if (cacheFileOpenRead()) {
4097 m_request.cacheTag.expireDate = expireDate;
4098 cacheFileClose(); // this sends an update command to the cache cleaner process
4099 }
4100
4101 m_request = savedRequest;
4102 finished();
4103 break;
4104 }
4105 case 5: // WebDAV lock
4106 {
4107 KUrl url;
4108 QString scope, type, owner;
4109 stream >> url >> scope >> type >> owner;
4110 davLock( url, scope, type, owner );
4111 break;
4112 }
4113 case 6: // WebDAV unlock
4114 {
4115 KUrl url;
4116 stream >> url;
4117 davUnlock( url );
4118 break;
4119 }
4120 case 7: // Generic WebDAV
4121 {
4122 KUrl url;
4123 int method;
4124 qint64 size;
4125 stream >> url >> method >> size;
4126 davGeneric( url, (KIO::HTTP_METHOD) method, size );
4127 break;
4128 }
4129 case 99: // Close Connection
4130 {
4131 httpCloseConnection();
4132 break;
4133 }
4134 default:
4135 // Some command we don't understand.
4136 // Just ignore it, it may come from some future version of KDE.
4137 break;
4138 }
4139}
4140
4144int HTTPProtocol::readChunked()
4145{
4146 if ((m_iBytesLeft == 0) || (m_iBytesLeft == NO_SIZE))
4147 {
4148 // discard CRLF from previous chunk, if any, and read size of next chunk
4149
4150 int bufPos = 0;
4151 m_receiveBuf.resize(4096);
4152
4153 bool foundCrLf = readDelimitedText(m_receiveBuf.data(), &bufPos, m_receiveBuf.size(), 1);
4154
4155 if (foundCrLf && bufPos == 2) {
4156 // The previous read gave us the CRLF from the previous chunk. As bufPos includes
4157 // the trailing CRLF it has to be > 2 to possibly include the next chunksize.
4158 bufPos = 0;
4159 foundCrLf = readDelimitedText(m_receiveBuf.data(), &bufPos, m_receiveBuf.size(), 1);
4160 }
4161 if (!foundCrLf) {
4162 kDebug(7113) << "Failed to read chunk header.";
4163 return -1;
4164 }
4165 Q_ASSERT(bufPos > 2);
4166
4167 long long nextChunkSize = STRTOLL(m_receiveBuf.data(), 0, 16);
4168 if (nextChunkSize < 0)
4169 {
4170 kDebug(7113) << "Negative chunk size";
4171 return -1;
4172 }
4173 m_iBytesLeft = nextChunkSize;
4174
4175 kDebug(7113) << "Chunk size =" << m_iBytesLeft << "bytes";
4176
4177 if (m_iBytesLeft == 0)
4178 {
4179 // Last chunk; read and discard chunk trailer.
4180 // The last trailer line ends with CRLF and is followed by another CRLF
4181 // so we have CRLFCRLF like at the end of a standard HTTP header.
4182 // Do not miss a CRLFCRLF spread over two of our 4K blocks: keep three previous bytes.
4183 //NOTE the CRLF after the chunksize also counts if there is no trailer. Copy it over.
4184 char trash[4096];
4185 trash[0] = m_receiveBuf.constData()[bufPos - 2];
4186 trash[1] = m_receiveBuf.constData()[bufPos - 1];
4187 int trashBufPos = 2;
4188 bool done = false;
4189 while (!done && !m_isEOF) {
4190 if (trashBufPos > 3) {
4191 // shift everything but the last three bytes out of the buffer
4192 for (int i = 0; i < 3; i++) {
4193 trash[i] = trash[trashBufPos - 3 + i];
4194 }
4195 trashBufPos = 3;
4196 }
4197 done = readDelimitedText(trash, &trashBufPos, 4096, 2);
4198 }
4199 if (m_isEOF && !done) {
4200 kDebug(7113) << "Failed to read chunk trailer.";
4201 return -1;
4202 }
4203
4204 return 0;
4205 }
4206 }
4207
4208 int bytesReceived = readLimited();
4209 if (!m_iBytesLeft) {
4210 m_iBytesLeft = NO_SIZE; // Don't stop, continue with next chunk
4211 }
4212 return bytesReceived;
4213}
4214
4215int HTTPProtocol::readLimited()
4216{
4217 if (!m_iBytesLeft)
4218 return 0;
4219
4220 m_receiveBuf.resize(4096);
4221
4222 int bytesToReceive;
4223 if (m_iBytesLeft > KIO::filesize_t(m_receiveBuf.size()))
4224 bytesToReceive = m_receiveBuf.size();
4225 else
4226 bytesToReceive = m_iBytesLeft;
4227
4228 const int bytesReceived = readBuffered(m_receiveBuf.data(), bytesToReceive, false);
4229
4230 if (bytesReceived <= 0)
4231 return -1; // Error: connection lost
4232
4233 m_iBytesLeft -= bytesReceived;
4234 return bytesReceived;
4235}
4236
4237int HTTPProtocol::readUnlimited()
4238{
4239 if (m_request.isKeepAlive)
4240 {
4241 kDebug(7113) << "Unbounded datastream on a Keep-alive connection!";
4242 m_request.isKeepAlive = false;
4243 }
4244
4245 m_receiveBuf.resize(4096);
4246
4247 int result = readBuffered(m_receiveBuf.data(), m_receiveBuf.size());
4248 if (result > 0)
4249 return result;
4250
4251 m_isEOF = true;
4252 m_iBytesLeft = 0;
4253 return 0;
4254}
4255
4256void HTTPProtocol::slotData(const QByteArray &_d)
4257{
4258 if (!_d.size())
4259 {
4260 m_isEOD = true;
4261 return;
4262 }
4263
4264 if (m_iContentLeft != NO_SIZE)
4265 {
4266 if (m_iContentLeft >= KIO::filesize_t(_d.size()))
4267 m_iContentLeft -= _d.size();
4268 else
4269 m_iContentLeft = NO_SIZE;
4270 }
4271
4272 QByteArray d = _d;
4273 if ( !m_dataInternal )
4274 {
4275 // If a broken server does not send the mime-type,
4276 // we try to id it from the content before dealing
4277 // with the content itself.
4278 if ( m_mimeType.isEmpty() && !m_isRedirection &&
4279 !( m_request.responseCode >= 300 && m_request.responseCode <=399) )
4280 {
4281 kDebug(7113) << "Determining mime-type from content...";
4282 int old_size = m_mimeTypeBuffer.size();
4283 m_mimeTypeBuffer.resize( old_size + d.size() );
4284 memcpy( m_mimeTypeBuffer.data() + old_size, d.data(), d.size() );
4285 if ( (m_iBytesLeft != NO_SIZE) && (m_iBytesLeft > 0)
4286 && (m_mimeTypeBuffer.size() < 1024) )
4287 {
4288 m_cpMimeBuffer = true;
4289 return; // Do not send up the data since we do not yet know its mimetype!
4290 }
4291
4292 kDebug(7113) << "Mimetype buffer size:" << m_mimeTypeBuffer.size();
4293
4294 KMimeType::Ptr mime = KMimeType::findByNameAndContent(m_request.url.fileName(), m_mimeTypeBuffer);
4295 if( mime && !mime->isDefault() )
4296 {
4297 m_mimeType = mime->name();
4298 kDebug(7113) << "Mimetype from content:" << m_mimeType;
4299 }
4300
4301 if ( m_mimeType.isEmpty() )
4302 {
4303 m_mimeType = QLatin1String( DEFAULT_MIME_TYPE );
4304 kDebug(7113) << "Using default mimetype:" << m_mimeType;
4305 }
4306
4307 //### we could also open the cache file here
4308
4309 if ( m_cpMimeBuffer )
4310 {
4311 d.resize(0);
4312 d.resize(m_mimeTypeBuffer.size());
4313 memcpy(d.data(), m_mimeTypeBuffer.data(), d.size());
4314 }
4315 mimeType(m_mimeType);
4316 m_mimeTypeBuffer.resize(0);
4317 }
4318
4319 //kDebug(7113) << "Sending data of size" << d.size();
4320 data( d );
4321 if (m_request.cacheTag.ioMode == WriteToCache) {
4322 cacheFileWritePayload(d);
4323 }
4324 }
4325 else
4326 {
4327 uint old_size = m_webDavDataBuf.size();
4328 m_webDavDataBuf.resize (old_size + d.size());
4329 memcpy (m_webDavDataBuf.data() + old_size, d.data(), d.size());
4330 }
4331}
4332
4342bool HTTPProtocol::readBody( bool dataInternal /* = false */ )
4343{
4344 // special case for reading cached body since we also do it in this function. oh well.
4345 if (!canHaveResponseBody(m_request.responseCode, m_request.method) &&
4346 !(m_request.cacheTag.ioMode == ReadFromCache && m_request.responseCode == 304 &&
4347 m_request.method != HTTP_HEAD)) {
4348 return true;
4349 }
4350
4351 m_isEOD = false;
4352 // Note that when dataInternal is true, we are going to:
4353 // 1) save the body data to a member variable, m_webDavDataBuf
4354 // 2) _not_ advertise the data, speed, size, etc., through the
4355 // corresponding functions.
4356 // This is used for returning data to WebDAV.
4357 m_dataInternal = dataInternal;
4358 if (dataInternal) {
4359 m_webDavDataBuf.clear();
4360 }
4361
4362 // Check if we need to decode the data.
4363 // If we are in copy mode, then use only transfer decoding.
4364 bool useMD5 = !m_contentMD5.isEmpty();
4365
4366 // Deal with the size of the file.
4367 KIO::filesize_t sz = m_request.offset;
4368 if ( sz )
4369 m_iSize += sz;
4370
4371 if (!m_isRedirection) {
4372 // Update the application with total size except when
4373 // it is compressed, or when the data is to be handled
4374 // internally (webDAV). If compressed we have to wait
4375 // until we uncompress to find out the actual data size
4376 if ( !dataInternal ) {
4377 if ((m_iSize > 0) && (m_iSize != NO_SIZE)) {
4378 totalSize(m_iSize);
4379 infoMessage(i18n("Retrieving %1 from %2...", KIO::convertSize(m_iSize),
4380 m_request.url.host()));
4381 } else {
4382 totalSize(0);
4383 }
4384 }
4385
4386 if (m_request.cacheTag.ioMode == ReadFromCache) {
4387 kDebug(7113) << "reading data from cache...";
4388
4389 m_iContentLeft = NO_SIZE;
4390
4391 QByteArray d;
4392 while (true) {
4393 d = cacheFileReadPayload(MAX_IPC_SIZE);
4394 if (d.isEmpty()) {
4395 break;
4396 }
4397 slotData(d);
4398 sz += d.size();
4399 if (!dataInternal) {
4400 processedSize(sz);
4401 }
4402 }
4403
4404 m_receiveBuf.resize(0);
4405
4406 if (!dataInternal) {
4407 data(QByteArray());
4408 }
4409
4410 return true;
4411 }
4412 }
4413
4414 if (m_iSize != NO_SIZE)
4415 m_iBytesLeft = m_iSize - sz;
4416 else
4417 m_iBytesLeft = NO_SIZE;
4418
4419 m_iContentLeft = m_iBytesLeft;
4420
4421 if (m_isChunked)
4422 m_iBytesLeft = NO_SIZE;
4423
4424 kDebug(7113) << KIO::number(m_iBytesLeft) << "bytes left.";
4425
4426 // Main incoming loop... Gather everything while we can...
4427 m_cpMimeBuffer = false;
4428 m_mimeTypeBuffer.resize(0);
4429
4430 HTTPFilterChain chain;
4431
4432 // redirection ignores the body
4433 if (!m_isRedirection) {
4434 QObject::connect(&chain, SIGNAL(output(QByteArray)),
4435 this, SLOT(slotData(QByteArray)));
4436 }
4437 QObject::connect(&chain, SIGNAL(error(QString)),
4438 this, SLOT(slotFilterError(QString)));
4439
4440 // decode all of the transfer encodings
4441 while (!m_transferEncodings.isEmpty())
4442 {
4443 QString enc = m_transferEncodings.takeLast();
4444 if ( enc == QLatin1String("gzip") )
4445 chain.addFilter(new HTTPFilterGZip);
4446 else if ( enc == QLatin1String("deflate") )
4447 chain.addFilter(new HTTPFilterDeflate);
4448 }
4449
4450 // From HTTP 1.1 Draft 6:
4451 // The MD5 digest is computed based on the content of the entity-body,
4452 // including any content-coding that has been applied, but not including
4453 // any transfer-encoding applied to the message-body. If the message is
4454 // received with a transfer-encoding, that encoding MUST be removed
4455 // prior to checking the Content-MD5 value against the received entity.
4456 HTTPFilterMD5 *md5Filter = 0;
4457 if ( useMD5 )
4458 {
4459 md5Filter = new HTTPFilterMD5;
4460 chain.addFilter(md5Filter);
4461 }
4462
4463 // now decode all of the content encodings
4464 // -- Why ?? We are not
4465 // -- a proxy server, be a client side implementation!! The applications
4466 // -- are capable of determinig how to extract the encoded implementation.
4467 // WB: That's a misunderstanding. We are free to remove the encoding.
4468 // WB: Some braindead www-servers however, give .tgz files an encoding
4469 // WB: of "gzip" (or even "x-gzip") and a content-type of "applications/tar"
4470 // WB: They shouldn't do that. We can work around that though...
4471 while (!m_contentEncodings.isEmpty())
4472 {
4473 QString enc = m_contentEncodings.takeLast();
4474 if ( enc == QLatin1String("gzip") )
4475 chain.addFilter(new HTTPFilterGZip);
4476 else if ( enc == QLatin1String("deflate") )
4477 chain.addFilter(new HTTPFilterDeflate);
4478 }
4479
4480 while (!m_isEOF)
4481 {
4482 int bytesReceived;
4483
4484 if (m_isChunked)
4485 bytesReceived = readChunked();
4486 else if (m_iSize != NO_SIZE)
4487 bytesReceived = readLimited();
4488 else
4489 bytesReceived = readUnlimited();
4490
4491 // make sure that this wasn't an error, first
4492 // kDebug(7113) << "bytesReceived:"
4493 // << (int) bytesReceived << " m_iSize:" << (int) m_iSize << " Chunked:"
4494 // << m_isChunked << " BytesLeft:"<< (int) m_iBytesLeft;
4495 if (bytesReceived == -1)
4496 {
4497 if (m_iContentLeft == 0)
4498 {
4499 // gzip'ed data sometimes reports a too long content-length.
4500 // (The length of the unzipped data)
4501 m_iBytesLeft = 0;
4502 break;
4503 }
4504 // Oh well... log an error and bug out
4505 kDebug(7113) << "bytesReceived==-1 sz=" << (int)sz
4506 << " Connection broken !";
4507 error(ERR_CONNECTION_BROKEN, m_request.url.host());
4508 return false;
4509 }
4510
4511 // I guess that nbytes == 0 isn't an error.. but we certainly
4512 // won't work with it!
4513 if (bytesReceived > 0)
4514 {
4515 // Important: truncate the buffer to the actual size received!
4516 // Otherwise garbage will be passed to the app
4517 m_receiveBuf.truncate( bytesReceived );
4518
4519 chain.slotInput(m_receiveBuf);
4520
4521 if (m_iError)
4522 return false;
4523
4524 sz += bytesReceived;
4525 if (!dataInternal)
4526 processedSize( sz );
4527 }
4528 m_receiveBuf.resize(0); // res
4529
4530 if (m_iBytesLeft && m_isEOD && !m_isChunked)
4531 {
4532 // gzip'ed data sometimes reports a too long content-length.
4533 // (The length of the unzipped data)
4534 m_iBytesLeft = 0;
4535 }
4536
4537 if (m_iBytesLeft == 0)
4538 {
4539 kDebug(7113) << "EOD received! Left ="<< KIO::number(m_iBytesLeft);
4540 break;
4541 }
4542 }
4543 chain.slotInput(QByteArray()); // Flush chain.
4544
4545 if ( useMD5 )
4546 {
4547 QString calculatedMD5 = md5Filter->md5();
4548
4549 if ( m_contentMD5 != calculatedMD5 )
4550 kWarning(7113) << "MD5 checksum MISMATCH! Expected:"
4551 << calculatedMD5 << ", Got:" << m_contentMD5;
4552 }
4553
4554 // Close cache entry
4555 if (m_iBytesLeft == 0) {
4556 cacheFileClose(); // no-op if not necessary
4557 }
4558
4559 if (!dataInternal && sz <= 1)
4560 {
4561 if (m_request.responseCode >= 500 && m_request.responseCode <= 599) {
4562 error(ERR_INTERNAL_SERVER, m_request.url.host());
4563 return false;
4564 } else if (m_request.responseCode >= 400 && m_request.responseCode <= 499 &&
4565 !isAuthenticationRequired(m_request.responseCode)) {
4566 error(ERR_DOES_NOT_EXIST, m_request.url.host());
4567 return false;
4568 }
4569 }
4570
4571 if (!dataInternal && !m_isRedirection)
4572 data( QByteArray() );
4573
4574 return true;
4575}
4576
4577void HTTPProtocol::slotFilterError(const QString &text)
4578{
4579 error(KIO::ERR_SLAVE_DEFINED, text);
4580}
4581
4582void HTTPProtocol::error( int _err, const QString &_text )
4583{
4584 // Close the connection only on connection errors. Otherwise, honor the
4585 // keep alive flag.
4586 if (_err == ERR_CONNECTION_BROKEN || _err == ERR_COULD_NOT_CONNECT)
4587 httpClose(false);
4588 else
4589 httpClose(m_request.isKeepAlive);
4590
4591 if (!m_request.id.isEmpty())
4592 {
4593 forwardHttpResponseHeader();
4594 sendMetaData();
4595 }
4596
4597 // It's over, we don't need it anymore
4598 clearPostDataBuffer();
4599
4600 SlaveBase::error( _err, _text );
4601 m_iError = _err;
4602}
4603
4604
4605void HTTPProtocol::addCookies( const QString &url, const QByteArray &cookieHeader )
4606{
4607 qlonglong windowId = m_request.windowId.toLongLong();
4608 QDBusInterface kcookiejar( QLatin1String("org.kde.kded"), QLatin1String("/modules/kcookiejar"), QLatin1String("org.kde.KCookieServer") );
4609 (void)kcookiejar.call( QDBus::NoBlock, QLatin1String("addCookies"), url,
4610 cookieHeader, windowId );
4611}
4612
4613QString HTTPProtocol::findCookies( const QString &url)
4614{
4615 qlonglong windowId = m_request.windowId.toLongLong();
4616 QDBusInterface kcookiejar( QLatin1String("org.kde.kded"), QLatin1String("/modules/kcookiejar"), QLatin1String("org.kde.KCookieServer") );
4617 QDBusReply<QString> reply = kcookiejar.call( QLatin1String("findCookies"), url, windowId );
4618
4619 if ( !reply.isValid() )
4620 {
4621 kWarning(7113) << "Can't communicate with kded_kcookiejar!";
4622 return QString();
4623 }
4624 return reply;
4625}
4626
4627/******************************* CACHING CODE ****************************/
4628
4629HTTPProtocol::CacheTag::CachePlan HTTPProtocol::CacheTag::plan(int maxCacheAge) const
4630{
4631 //notable omission: we're not checking cache file presence or integrity
4632 switch (policy) {
4633 case KIO::CC_Refresh:
4634 // Conditional GET requires the presence of either an ETag or
4635 // last modified date.
4636 if (lastModifiedDate != -1 || !etag.isEmpty()) {
4637 return ValidateCached;
4638 }
4639 break;
4640 case KIO::CC_Reload:
4641 return IgnoreCached;
4642 case KIO::CC_CacheOnly:
4643 case KIO::CC_Cache:
4644 return UseCached;
4645 default:
4646 break;
4647 }
4648
4649 Q_ASSERT((policy == CC_Verify || policy == CC_Refresh));
4650 qint64 currentDate = QDateTime::currentMSecsSinceEpoch()/1000;
4651 if ((servedDate != -1 && currentDate > (servedDate + maxCacheAge)) ||
4652 (expireDate != -1 && currentDate > expireDate)) {
4653 return ValidateCached;
4654 }
4655 return UseCached;
4656}
4657
4658// !START SYNC!
4659// The following code should be kept in sync
4660// with the code in http_cache_cleaner.cpp
4661
4662// we use QDataStream; this is just an illustration
4663struct BinaryCacheFileHeader
4664{
4665 quint8 version[2];
4666 quint8 compression; // for now fixed to 0
4667 quint8 reserved; // for now; also alignment
4668 qint32 useCount;
4669 qint64 servedDate;
4670 qint64 lastModifiedDate;
4671 qint64 expireDate;
4672 qint32 bytesCached;
4673 // packed size should be 36 bytes; we explicitly set it here to make sure that no compiler
4674 // padding ruins it. We write the fields to disk without any padding.
4675 static const int size = 36;
4676};
4677
4678enum CacheCleanerCommandCode {
4679 InvalidCommand = 0,
4680 CreateFileNotificationCommand,
4681 UpdateFileCommand
4682};
4683
4684// illustration for cache cleaner update "commands"
4685struct CacheCleanerCommand
4686{
4687 BinaryCacheFileHeader header;
4688 quint32 commandCode;
4689 // filename in ASCII, binary isn't worth the coding and decoding
4690 quint8 filename[s_hashedUrlNibbles];
4691};
4692
4693QByteArray HTTPProtocol::CacheTag::serialize() const
4694{
4695 QByteArray ret;
4696 QDataStream stream(&ret, QIODevice::WriteOnly);
4697 stream << quint8('A');
4698 stream << quint8('\n');
4699 stream << quint8(0);
4700 stream << quint8(0);
4701
4702 stream << fileUseCount;
4703 stream << servedDate;
4704 stream << lastModifiedDate;
4705 stream << expireDate;
4706
4707 stream << bytesCached;
4708 Q_ASSERT(ret.size() == BinaryCacheFileHeader::size);
4709 return ret;
4710}
4711
4712static bool compareByte(QDataStream *stream, quint8 value)
4713{
4714 quint8 byte;
4715 *stream >> byte;
4716 return byte == value;
4717}
4718
4719// If starting a new file cacheFileWriteVariableSizeHeader() must have been called *before*
4720// calling this! This is to fill in the headerEnd field.
4721// If the file is not new headerEnd has already been read from the file and in fact the variable
4722// size header *may* not be rewritten because a size change would mess up the file layout.
4723bool HTTPProtocol::CacheTag::deserialize(const QByteArray &d)
4724{
4725 if (d.size() != BinaryCacheFileHeader::size) {
4726 return false;
4727 }
4728 QDataStream stream(d);
4729 stream.setVersion(QDataStream::Qt_4_5);
4730
4731 bool ok = true;
4732 ok = ok && compareByte(&stream, 'A');
4733 ok = ok && compareByte(&stream, '\n');
4734 ok = ok && compareByte(&stream, 0);
4735 ok = ok && compareByte(&stream, 0);
4736 if (!ok) {
4737 return false;
4738 }
4739
4740 stream >> fileUseCount;
4741 stream >> servedDate;
4742 stream >> lastModifiedDate;
4743 stream >> expireDate;
4744 stream >> bytesCached;
4745
4746 return true;
4747}
4748
4749/* Text part of the header, directly following the binary first part:
4750URL\n
4751etag\n
4752mimetype\n
4753header line\n
4754header line\n
4755...
4756\n
4757*/
4758
4759static KUrl storableUrl(const KUrl &url)
4760{
4761 KUrl ret(url);
4762 ret.setPassword(QString());
4763 ret.setFragment(QString());
4764 return ret;
4765}
4766
4767static void writeLine(QIODevice *dev, const QByteArray &line)
4768{
4769 static const char linefeed = '\n';
4770 dev->write(line);
4771 dev->write(&linefeed, 1);
4772}
4773
4774void HTTPProtocol::cacheFileWriteTextHeader()
4775{
4776 QFile *&file = m_request.cacheTag.file;
4777 Q_ASSERT(file);
4778 Q_ASSERT(file->openMode() & QIODevice::WriteOnly);
4779
4780 file->seek(BinaryCacheFileHeader::size);
4781 writeLine(file, storableUrl(m_request.url).toEncoded());
4782 writeLine(file, m_request.cacheTag.etag.toLatin1());
4783 writeLine(file, m_mimeType.toLatin1());
4784 writeLine(file, m_responseHeaders.join(QString(QLatin1Char('\n'))).toLatin1());
4785 // join("\n") adds no \n to the end, but writeLine() does.
4786 // Add another newline to mark the end of text.
4787 writeLine(file, QByteArray());
4788}
4789
4790static bool readLineChecked(QIODevice *dev, QByteArray *line)
4791{
4792 *line = dev->readLine(MAX_IPC_SIZE);
4793 // if nothing read or the line didn't fit into 8192 bytes(!)
4794 if (line->isEmpty() || !line->endsWith('\n')) {
4795 return false;
4796 }
4797 // we don't actually want the newline!
4798 line->chop(1);
4799 return true;
4800}
4801
4802bool HTTPProtocol::cacheFileReadTextHeader1(const KUrl &desiredUrl)
4803{
4804 QFile *&file = m_request.cacheTag.file;
4805 Q_ASSERT(file);
4806 Q_ASSERT(file->openMode() == QIODevice::ReadOnly);
4807
4808 QByteArray readBuf;
4809 bool ok = readLineChecked(file, &readBuf);
4810 if (storableUrl(desiredUrl).toEncoded() != readBuf) {
4811 kDebug(7103) << "You have witnessed a very improbable hash collision!";
4812 return false;
4813 }
4814
4815 ok = ok && readLineChecked(file, &readBuf);
4816 m_request.cacheTag.etag = toQString(readBuf);
4817
4818 return ok;
4819}
4820
4821bool HTTPProtocol::cacheFileReadTextHeader2()
4822{
4823 QFile *&file = m_request.cacheTag.file;
4824 Q_ASSERT(file);
4825 Q_ASSERT(file->openMode() == QIODevice::ReadOnly);
4826
4827 bool ok = true;
4828 QByteArray readBuf;
4829#ifndef NDEBUG
4830 // we assume that the URL and etag have already been read
4831 qint64 oldPos = file->pos();
4832 file->seek(BinaryCacheFileHeader::size);
4833 ok = ok && readLineChecked(file, &readBuf);
4834 ok = ok && readLineChecked(file, &readBuf);
4835 Q_ASSERT(file->pos() == oldPos);
4836#endif
4837 ok = ok && readLineChecked(file, &readBuf);
4838 m_mimeType = toQString(readBuf);
4839
4840 m_responseHeaders.clear();
4841 // read as long as no error and no empty line found
4842 while (true) {
4843 ok = ok && readLineChecked(file, &readBuf);
4844 if (ok && !readBuf.isEmpty()) {
4845 m_responseHeaders.append(toQString(readBuf));
4846 } else {
4847 break;
4848 }
4849 }
4850 return ok; // it may still be false ;)
4851}
4852
4853static QString filenameFromUrl(const KUrl &url)
4854{
4855 QCryptographicHash hash(QCryptographicHash::Sha1);
4856 hash.addData(storableUrl(url).toEncoded());
4857 return toQString(hash.result().toHex());
4858}
4859
4860QString HTTPProtocol::cacheFilePathFromUrl(const KUrl &url) const
4861{
4862 QString filePath = m_strCacheDir;
4863 if (!filePath.endsWith(QLatin1Char('/'))) {
4864 filePath.append(QLatin1Char('/'));
4865 }
4866 filePath.append(filenameFromUrl(url));
4867 return filePath;
4868}
4869
4870bool HTTPProtocol::cacheFileOpenRead()
4871{
4872 kDebug(7113);
4873 QString filename = cacheFilePathFromUrl(m_request.url);
4874
4875 QFile *&file = m_request.cacheTag.file;
4876 if (file) {
4877 kDebug(7113) << "File unexpectedly open; old file is" << file->fileName()
4878 << "new name is" << filename;
4879 Q_ASSERT(file->fileName() == filename);
4880 }
4881 Q_ASSERT(!file);
4882 file = new QFile(filename);
4883 if (file->open(QIODevice::ReadOnly)) {
4884 QByteArray header = file->read(BinaryCacheFileHeader::size);
4885 if (!m_request.cacheTag.deserialize(header)) {
4886 kDebug(7103) << "Cache file header is invalid.";
4887
4888 file->close();
4889 }
4890 }
4891
4892 if (file->isOpen() && !cacheFileReadTextHeader1(m_request.url)) {
4893 file->close();
4894 }
4895
4896 if (!file->isOpen()) {
4897 cacheFileClose();
4898 return false;
4899 }
4900 return true;
4901}
4902
4903
4904bool HTTPProtocol::cacheFileOpenWrite()
4905{
4906 kDebug(7113);
4907 QString filename = cacheFilePathFromUrl(m_request.url);
4908
4909 // if we open a cache file for writing while we have a file open for reading we must have
4910 // found out that the old cached content is obsolete, so delete the file.
4911 QFile *&file = m_request.cacheTag.file;
4912 if (file) {
4913 // ensure that the file is in a known state - either open for reading or null
4914 Q_ASSERT(!qobject_cast<QTemporaryFile *>(file));
4915 Q_ASSERT((file->openMode() & QIODevice::WriteOnly) == 0);
4916 Q_ASSERT(file->fileName() == filename);
4917 kDebug(7113) << "deleting expired cache entry and recreating.";
4918 file->remove();
4919 delete file;
4920 file = 0;
4921 }
4922
4923 // note that QTemporaryFile will automatically append random chars to filename
4924 file = new QTemporaryFile(filename);
4925 file->open(QIODevice::WriteOnly);
4926
4927 // if we have started a new file we have not initialized some variables from disk data.
4928 m_request.cacheTag.fileUseCount = 0; // the file has not been *read* yet
4929 m_request.cacheTag.bytesCached = 0;
4930
4931 if ((file->openMode() & QIODevice::WriteOnly) == 0) {
4932 kDebug(7113) << "Could not open file for writing:" << file->fileName()
4933 << "due to error" << file->error();
4934 cacheFileClose();
4935 return false;
4936 }
4937 return true;
4938}
4939
4940static QByteArray makeCacheCleanerCommand(const HTTPProtocol::CacheTag &cacheTag,
4941 CacheCleanerCommandCode cmd)
4942{
4943 QByteArray ret = cacheTag.serialize();
4944 QDataStream stream(&ret, QIODevice::WriteOnly);
4945 stream.setVersion(QDataStream::Qt_4_5);
4946
4947 stream.skipRawData(BinaryCacheFileHeader::size);
4948 // append the command code
4949 stream << quint32(cmd);
4950 // append the filename
4951 QString fileName = cacheTag.file->fileName();
4952 int basenameStart = fileName.lastIndexOf(QLatin1Char('/')) + 1;
4953 QByteArray baseName = fileName.mid(basenameStart, s_hashedUrlNibbles).toLatin1();
4954 stream.writeRawData(baseName.constData(), baseName.size());
4955
4956 Q_ASSERT(ret.size() == BinaryCacheFileHeader::size + sizeof(quint32) + s_hashedUrlNibbles);
4957 return ret;
4958}
4959
4960//### not yet 100% sure when and when not to call this
4961void HTTPProtocol::cacheFileClose()
4962{
4963 kDebug(7113);
4964
4965 QFile *&file = m_request.cacheTag.file;
4966 if (!file) {
4967 return;
4968 }
4969
4970 m_request.cacheTag.ioMode = NoCache;
4971
4972 QByteArray ccCommand;
4973 QTemporaryFile *tempFile = qobject_cast<QTemporaryFile *>(file);
4974
4975 if (file->openMode() & QIODevice::WriteOnly) {
4976 Q_ASSERT(tempFile);
4977
4978 if (m_request.cacheTag.bytesCached && !m_iError) {
4979 QByteArray header = m_request.cacheTag.serialize();
4980 tempFile->seek(0);
4981 tempFile->write(header);
4982
4983 ccCommand = makeCacheCleanerCommand(m_request.cacheTag, CreateFileNotificationCommand);
4984
4985 QString oldName = tempFile->fileName();
4986 QString newName = oldName;
4987 int basenameStart = newName.lastIndexOf(QLatin1Char('/')) + 1;
4988 // remove the randomized name part added by QTemporaryFile
4989 newName.chop(newName.length() - basenameStart - s_hashedUrlNibbles);
4990 kDebug(7113) << "Renaming temporary file" << oldName << "to" << newName;
4991
4992 // on windows open files can't be renamed
4993 tempFile->setAutoRemove(false);
4994 delete tempFile;
4995 file = 0;
4996
4997 if (!QFile::rename(oldName, newName)) {
4998 // ### currently this hides a minor bug when force-reloading a resource. We
4999 // should not even open a new file for writing in that case.
5000 kDebug(7113) << "Renaming temporary file failed, deleting it instead.";
5001 QFile::remove(oldName);
5002 ccCommand.clear(); // we have nothing of value to tell the cache cleaner
5003 }
5004 } else {
5005 // oh, we've never written payload data to the cache file.
5006 // the temporary file is closed and removed and no proper cache entry is created.
5007 }
5008 } else if (file->openMode() == QIODevice::ReadOnly) {
5009 Q_ASSERT(!tempFile);
5010 ccCommand = makeCacheCleanerCommand(m_request.cacheTag, UpdateFileCommand);
5011 }
5012 delete file;
5013 file = 0;
5014
5015 if (!ccCommand.isEmpty()) {
5016 sendCacheCleanerCommand(ccCommand);
5017 }
5018}
5019
5020void HTTPProtocol::sendCacheCleanerCommand(const QByteArray &command)
5021{
5022 kDebug(7113);
5023 Q_ASSERT(command.size() == BinaryCacheFileHeader::size + s_hashedUrlNibbles + sizeof(quint32));
5024 int attempts = 0;
5025 while (m_cacheCleanerConnection.state() != QLocalSocket::ConnectedState && attempts < 6) {
5026 if (attempts == 2) {
5027 KToolInvocation::startServiceByDesktopPath(QLatin1String("http_cache_cleaner.desktop"));
5028 }
5029 QString socketFileName = KStandardDirs::locateLocal("socket", QLatin1String("kio_http_cache_cleaner"));
5030 m_cacheCleanerConnection.connectToServer(socketFileName, QIODevice::WriteOnly);
5031 m_cacheCleanerConnection.waitForConnected(1500);
5032 attempts++;
5033 }
5034
5035 if (m_cacheCleanerConnection.state() == QLocalSocket::ConnectedState) {
5036 m_cacheCleanerConnection.write(command);
5037 m_cacheCleanerConnection.flush();
5038 } else {
5039 // updating the stats is not vital, so we just give up.
5040 kDebug(7113) << "Could not connect to cache cleaner, not updating stats of this cache file.";
5041 }
5042}
5043
5044QByteArray HTTPProtocol::cacheFileReadPayload(int maxLength)
5045{
5046 Q_ASSERT(m_request.cacheTag.file);
5047 Q_ASSERT(m_request.cacheTag.ioMode == ReadFromCache);
5048 Q_ASSERT(m_request.cacheTag.file->openMode() == QIODevice::ReadOnly);
5049 QByteArray ret = m_request.cacheTag.file->read(maxLength);
5050 if (ret.isEmpty()) {
5051 cacheFileClose();
5052 }
5053 return ret;
5054}
5055
5056
5057void HTTPProtocol::cacheFileWritePayload(const QByteArray &d)
5058{
5059 if (!m_request.cacheTag.file) {
5060 return;
5061 }
5062
5063 // If the file being downloaded is so big that it exceeds the max cache size,
5064 // do not cache it! See BR# 244215. NOTE: this can be improved upon in the
5065 // future...
5066 if (m_iSize >= KIO::filesize_t(m_maxCacheSize * 1024)) {
5067 kDebug(7113) << "Caching disabled because content size is too big.";
5068 cacheFileClose();
5069 return;
5070 }
5071
5072 Q_ASSERT(m_request.cacheTag.ioMode == WriteToCache);
5073 Q_ASSERT(m_request.cacheTag.file->openMode() & QIODevice::WriteOnly);
5074
5075 if (d.isEmpty()) {
5076 cacheFileClose();
5077 }
5078
5079 //TODO: abort if file grows too big!
5080
5081 // write the variable length text header as soon as we start writing to the file
5082 if (!m_request.cacheTag.bytesCached) {
5083 cacheFileWriteTextHeader();
5084 }
5085 m_request.cacheTag.bytesCached += d.size();
5086 m_request.cacheTag.file->write(d);
5087}
5088
5089void HTTPProtocol::cachePostData(const QByteArray& data)
5090{
5091 if (!m_POSTbuf) {
5092 m_POSTbuf = createPostBufferDeviceFor(qMax(m_iPostDataSize, static_cast<KIO::filesize_t>(data.size())));
5093 if (!m_POSTbuf)
5094 return;
5095 }
5096
5097 m_POSTbuf->write (data.constData(), data.size());
5098}
5099
5100void HTTPProtocol::clearPostDataBuffer()
5101{
5102 if (!m_POSTbuf)
5103 return;
5104
5105 delete m_POSTbuf;
5106 m_POSTbuf = 0;
5107}
5108
5109bool HTTPProtocol::retrieveAllData()
5110{
5111 if (!m_POSTbuf) {
5112 m_POSTbuf = createPostBufferDeviceFor(s_MaxInMemPostBufSize + 1);
5113 }
5114
5115 if (!m_POSTbuf) {
5116 error (ERR_OUT_OF_MEMORY, m_request.url.host());
5117 return false;
5118 }
5119
5120 while (true) {
5121 dataReq();
5122 QByteArray buffer;
5123 const int bytesRead = readData(buffer);
5124
5125 if (bytesRead < 0) {
5126 error(ERR_ABORTED, m_request.url.host());
5127 return false;
5128 }
5129
5130 if (bytesRead == 0) {
5131 break;
5132 }
5133
5134 m_POSTbuf->write(buffer.constData(), buffer.size());
5135 }
5136
5137 return true;
5138}
5139
5140// The above code should be kept in sync
5141// with the code in http_cache_cleaner.cpp
5142// !END SYNC!
5143
5144//************************** AUTHENTICATION CODE ********************/
5145
5146QString HTTPProtocol::authenticationHeader()
5147{
5148 QByteArray ret;
5149
5150 // If the internal meta-data "cached-www-auth" is set, then check for cached
5151 // authentication data and preemtively send the authentication header if a
5152 // matching one is found.
5153 if (!m_wwwAuth && config()->readEntry("cached-www-auth", false)) {
5154 KIO::AuthInfo authinfo;
5155 authinfo.url = m_request.url;
5156 authinfo.realmValue = config()->readEntry("www-auth-realm", QString());
5157 // If no relam metadata, then make sure path matching is turned on.
5158 authinfo.verifyPath = (authinfo.realmValue.isEmpty());
5159
5160 const bool useCachedAuth = (m_request.responseCode == 401 || !config()->readEntry("no-preemptive-auth-reuse", false));
5161
5162 if (useCachedAuth && checkCachedAuthentication(authinfo)) {
5163 const QByteArray cachedChallenge = config()->readEntry("www-auth-challenge", QByteArray());
5164 if (!cachedChallenge.isEmpty()) {
5165 m_wwwAuth = KAbstractHttpAuthentication::newAuth(cachedChallenge, config());
5166 if (m_wwwAuth) {
5167 kDebug(7113) << "creating www authentcation header from cached info";
5168 m_wwwAuth->setChallenge(cachedChallenge, m_request.url, m_request.sentMethodString);
5169 m_wwwAuth->generateResponse(authinfo.username, authinfo.password);
5170 }
5171 }
5172 }
5173 }
5174
5175 // If the internal meta-data "cached-proxy-auth" is set, then check for cached
5176 // authentication data and preemtively send the authentication header if a
5177 // matching one is found.
5178 if (!m_proxyAuth && config()->readEntry("cached-proxy-auth", false)) {
5179 KIO::AuthInfo authinfo;
5180 authinfo.url = m_request.proxyUrl;
5181 authinfo.realmValue = config()->readEntry("proxy-auth-realm", QString());
5182 // If no relam metadata, then make sure path matching is turned on.
5183 authinfo.verifyPath = (authinfo.realmValue.isEmpty());
5184
5185 if (checkCachedAuthentication(authinfo)) {
5186 const QByteArray cachedChallenge = config()->readEntry("proxy-auth-challenge", QByteArray());
5187 if (!cachedChallenge.isEmpty()) {
5188 m_proxyAuth = KAbstractHttpAuthentication::newAuth(cachedChallenge, config());
5189 if (m_proxyAuth) {
5190 kDebug(7113) << "creating proxy authentcation header from cached info";
5191 m_proxyAuth->setChallenge(cachedChallenge, m_request.proxyUrl, m_request.sentMethodString);
5192 m_proxyAuth->generateResponse(authinfo.username, authinfo.password);
5193 }
5194 }
5195 }
5196 }
5197
5198 // the authentication classes don't know if they are for proxy or webserver authentication...
5199 if (m_wwwAuth && !m_wwwAuth->isError()) {
5200 ret += "Authorization: ";
5201 ret += m_wwwAuth->headerFragment();
5202 }
5203
5204 if (m_proxyAuth && !m_proxyAuth->isError()) {
5205 ret += "Proxy-Authorization: ";
5206 ret += m_proxyAuth->headerFragment();
5207 }
5208
5209 return toQString(ret); // ## encoding ok?
5210}
5211
5212static QString protocolForProxyType(QNetworkProxy::ProxyType type)
5213{
5214 switch (type) {
5215 case QNetworkProxy::DefaultProxy:
5216 break;
5217 case QNetworkProxy::Socks5Proxy:
5218 return QLatin1String("socks");
5219 case QNetworkProxy::NoProxy:
5220 break;
5221 case QNetworkProxy::HttpProxy:
5222 case QNetworkProxy::HttpCachingProxy:
5223 case QNetworkProxy::FtpCachingProxy:
5224 default:
5225 break;
5226 }
5227
5228 return QLatin1String("http");
5229}
5230
5231void HTTPProtocol::proxyAuthenticationForSocket(const QNetworkProxy &proxy, QAuthenticator *authenticator)
5232{
5233 kDebug(7113) << "realm:" << authenticator->realm() << "user:" << authenticator->user();
5234
5235 // Set the proxy URL...
5236 m_request.proxyUrl.setProtocol(protocolForProxyType(proxy.type()));
5237 m_request.proxyUrl.setUser(proxy.user());
5238 m_request.proxyUrl.setHost(proxy.hostName());
5239 m_request.proxyUrl.setPort(proxy.port());
5240
5241 AuthInfo info;
5242 info.url = m_request.proxyUrl;
5243 info.realmValue = authenticator->realm();
5244 info.username = authenticator->user();
5245 info.verifyPath = info.realmValue.isEmpty();
5246
5247 const bool haveCachedCredentials = checkCachedAuthentication(info);
5248 const bool retryAuth = (m_socketProxyAuth != 0);
5249
5250 // if m_socketProxyAuth is a valid pointer then authentication has been attempted before,
5251 // and it was not successful. see below and saveProxyAuthenticationForSocket().
5252 if (!haveCachedCredentials || retryAuth) {
5253 // Save authentication info if the connection succeeds. We need to disconnect
5254 // this after saving the auth data (or an error) so we won't save garbage afterwards!
5255 connect(socket(), SIGNAL(connected()),
5256 this, SLOT(saveProxyAuthenticationForSocket()));
5257 //### fillPromptInfo(&info);
5258 info.prompt = i18n("You need to supply a username and a password for "
5259 "the proxy server listed below before you are allowed "
5260 "to access any sites.");
5261 info.keepPassword = true;
5262 info.commentLabel = i18n("Proxy:");
5263 info.comment = i18n("<b>%1</b> at <b>%2</b>", htmlEscape(info.realmValue), m_request.proxyUrl.host());
5264
5265 const QString errMsg ((retryAuth ? i18n("Proxy Authentication Failed.") : QString()));
5266
5267 if (!openPasswordDialog(info, errMsg)) {
5268 kDebug(7113) << "looks like the user canceled proxy authentication.";
5269 error(ERR_USER_CANCELED, m_request.proxyUrl.host());
5270 delete m_proxyAuth;
5271 m_proxyAuth = 0;
5272 return;
5273 }
5274 }
5275 authenticator->setUser(info.username);
5276 authenticator->setPassword(info.password);
5277 authenticator->setOption(QLatin1String("keepalive"), info.keepPassword);
5278
5279 if (m_socketProxyAuth) {
5280 *m_socketProxyAuth = *authenticator;
5281 } else {
5282 m_socketProxyAuth = new QAuthenticator(*authenticator);
5283 }
5284
5285 if (!m_request.proxyUrl.user().isEmpty()) {
5286 m_request.proxyUrl.setUser(info.username);
5287 }
5288}
5289
5290void HTTPProtocol::saveProxyAuthenticationForSocket()
5291{
5292 kDebug(7113) << "Saving authenticator";
5293 disconnect(socket(), SIGNAL(connected()),
5294 this, SLOT(saveProxyAuthenticationForSocket()));
5295 Q_ASSERT(m_socketProxyAuth);
5296 if (m_socketProxyAuth) {
5297 kDebug(7113) << "realm:" << m_socketProxyAuth->realm() << "user:" << m_socketProxyAuth->user();
5298 KIO::AuthInfo a;
5299 a.verifyPath = true;
5300 a.url = m_request.proxyUrl;
5301 a.realmValue = m_socketProxyAuth->realm();
5302 a.username = m_socketProxyAuth->user();
5303 a.password = m_socketProxyAuth->password();
5304 a.keepPassword = m_socketProxyAuth->option(QLatin1String("keepalive")).toBool();
5305 cacheAuthentication(a);
5306 }
5307 delete m_socketProxyAuth;
5308 m_socketProxyAuth = 0;
5309}
5310
5311void HTTPProtocol::saveAuthenticationData()
5312{
5313 KIO::AuthInfo authinfo;
5314 bool alreadyCached = false;
5315 KAbstractHttpAuthentication *auth = 0;
5316 switch (m_request.prevResponseCode) {
5317 case 401:
5318 auth = m_wwwAuth;
5319 alreadyCached = config()->readEntry("cached-www-auth", false);
5320 break;
5321 case 407:
5322 auth = m_proxyAuth;
5323 alreadyCached = config()->readEntry("cached-proxy-auth", false);
5324 break;
5325 default:
5326 Q_ASSERT(false); // should never happen!
5327 }
5328
5329 // Prevent recaching of the same credentials over and over again.
5330 if (auth && (!auth->realm().isEmpty() || !alreadyCached)) {
5331 auth->fillKioAuthInfo(&authinfo);
5332 if (auth == m_wwwAuth) {
5333 setMetaData(QLatin1String("{internal~currenthost}cached-www-auth"), QLatin1String("true"));
5334 if (!authinfo.realmValue.isEmpty())
5335 setMetaData(QLatin1String("{internal~currenthost}www-auth-realm"), authinfo.realmValue);
5336 if (!authinfo.digestInfo.isEmpty())
5337 setMetaData(QLatin1String("{internal~currenthost}www-auth-challenge"), authinfo.digestInfo);
5338 } else {
5339 setMetaData(QLatin1String("{internal~allhosts}cached-proxy-auth"), QLatin1String("true"));
5340 if (!authinfo.realmValue.isEmpty())
5341 setMetaData(QLatin1String("{internal~allhosts}proxy-auth-realm"), authinfo.realmValue);
5342 if (!authinfo.digestInfo.isEmpty())
5343 setMetaData(QLatin1String("{internal~allhosts}proxy-auth-challenge"), authinfo.digestInfo);
5344 }
5345
5346 kDebug(7113) << "Cache authentication info ?" << authinfo.keepPassword;
5347
5348 if (authinfo.keepPassword) {
5349 cacheAuthentication(authinfo);
5350 kDebug(7113) << "Cached authentication for" << m_request.url;
5351 }
5352 }
5353 // Update our server connection state which includes www and proxy username and password.
5354 m_server.updateCredentials(m_request);
5355}
5356
5357bool HTTPProtocol::handleAuthenticationHeader(const HeaderTokenizer* tokenizer)
5358{
5359 KIO::AuthInfo authinfo;
5360 QList<QByteArray> authTokens;
5361 KAbstractHttpAuthentication **auth;
5362
5363 if (m_request.responseCode == 401) {
5364 auth = &m_wwwAuth;
5365 authTokens = tokenizer->iterator("www-authenticate").all();
5366 authinfo.url = m_request.url;
5367 authinfo.username = m_server.url.user();
5368 authinfo.prompt = i18n("You need to supply a username and a "
5369 "password to access this site.");
5370 authinfo.commentLabel = i18n("Site:");
5371 } else {
5372 // make sure that the 407 header hasn't escaped a lower layer when it shouldn't.
5373 // this may break proxy chains which were never tested anyway, and AFAIK they are
5374 // rare to nonexistent in the wild.
5375 Q_ASSERT(QNetworkProxy::applicationProxy().type() == QNetworkProxy::NoProxy);
5376 auth = &m_proxyAuth;
5377 authTokens = tokenizer->iterator("proxy-authenticate").all();
5378 authinfo.url = m_request.proxyUrl;
5379 authinfo.username = m_request.proxyUrl.user();
5380 authinfo.prompt = i18n("You need to supply a username and a password for "
5381 "the proxy server listed below before you are allowed "
5382 "to access any sites." );
5383 authinfo.commentLabel = i18n("Proxy:");
5384 }
5385
5386 bool authRequiresAnotherRoundtrip = false;
5387
5388 // Workaround brain dead server responses that violate the spec and
5389 // incorrectly return a 401/407 without the required WWW/Proxy-Authenticate
5390 // header fields. See bug 215736...
5391 if (!authTokens.isEmpty()) {
5392 QString errorMsg;
5393 authRequiresAnotherRoundtrip = true;
5394
5395 if (m_request.responseCode == m_request.prevResponseCode && *auth) {
5396 // Authentication attempt failed. Retry...
5397 if ((*auth)->wasFinalStage()) {
5398 errorMsg = (m_request.responseCode == 401 ?
5399 i18n("Authentication Failed.") :
5400 i18n("Proxy Authentication Failed."));
5401 delete *auth;
5402 *auth = 0;
5403 } else { // Create authentication header
5404 // WORKAROUND: The following piece of code prevents brain dead IIS
5405 // servers that send back multiple "WWW-Authenticate" headers from
5406 // screwing up our authentication logic during the challenge
5407 // phase (Type 2) of NTLM authenticaiton.
5408 QMutableListIterator<QByteArray> it (authTokens);
5409 const QByteArray authScheme ((*auth)->scheme().trimmed());
5410 while (it.hasNext()) {
5411 if (qstrnicmp(authScheme.constData(), it.next().constData(), authScheme.length()) != 0) {
5412 it.remove();
5413 }
5414 }
5415 }
5416 }
5417
5418try_next_auth_scheme:
5419 QByteArray bestOffer = KAbstractHttpAuthentication::bestOffer(authTokens);
5420 if (*auth) {
5421 const QByteArray authScheme ((*auth)->scheme().trimmed());
5422 if (qstrnicmp(authScheme.constData(), bestOffer.constData(), authScheme.length()) != 0) {
5423 // huh, the strongest authentication scheme offered has changed.
5424 delete *auth;
5425 *auth = 0;
5426 }
5427 }
5428
5429 if (!(*auth)) {
5430 *auth = KAbstractHttpAuthentication::newAuth(bestOffer, config());
5431 }
5432
5433 if (*auth) {
5434 kDebug(7113) << "Trying authentication scheme:" << (*auth)->scheme();
5435
5436 // remove trailing space from the method string, or digest auth will fail
5437 (*auth)->setChallenge(bestOffer, authinfo.url, m_request.sentMethodString);
5438
5439 QString username, password;
5440 bool generateAuthHeader = true;
5441 if ((*auth)->needCredentials()) {
5442 // use credentials supplied by the application if available
5443 if (!m_request.url.user().isEmpty() && !m_request.url.pass().isEmpty()) {
5444 username = m_request.url.user();
5445 password = m_request.url.pass();
5446 // don't try this password any more
5447 m_request.url.setPass(QString());
5448 } else {
5449 // try to get credentials from kpasswdserver's cache, then try asking the user.
5450 authinfo.verifyPath = false; // we have realm, no path based checking please!
5451 authinfo.realmValue = (*auth)->realm();
5452 if (authinfo.realmValue.isEmpty() && !(*auth)->supportsPathMatching())
5453 authinfo.realmValue = QLatin1String((*auth)->scheme());
5454
5455 // Save the current authinfo url because it can be modified by the call to
5456 // checkCachedAuthentication. That way we can restore it if the call
5457 // modified it.
5458 const KUrl reqUrl = authinfo.url;
5459 if (!errorMsg.isEmpty() || !checkCachedAuthentication(authinfo)) {
5460 // Reset url to the saved url...
5461 authinfo.url = reqUrl;
5462 authinfo.keepPassword = true;
5463 authinfo.comment = i18n("<b>%1</b> at <b>%2</b>",
5464 htmlEscape(authinfo.realmValue), authinfo.url.host());
5465
5466 if (!openPasswordDialog(authinfo, errorMsg)) {
5467 generateAuthHeader = false;
5468 authRequiresAnotherRoundtrip = false;
5469 if (!sendErrorPageNotification()) {
5470 error(ERR_ACCESS_DENIED, reqUrl.host());
5471 }
5472 kDebug(7113) << "looks like the user canceled the authentication dialog";
5473 delete *auth;
5474 *auth = 0;
5475 }
5476 }
5477 username = authinfo.username;
5478 password = authinfo.password;
5479 }
5480 }
5481
5482 if (generateAuthHeader) {
5483 (*auth)->generateResponse(username, password);
5484 (*auth)->setCachePasswordEnabled(authinfo.keepPassword);
5485
5486 kDebug(7113) << "isError=" << (*auth)->isError()
5487 << "needCredentials=" << (*auth)->needCredentials()
5488 << "forceKeepAlive=" << (*auth)->forceKeepAlive()
5489 << "forceDisconnect=" << (*auth)->forceDisconnect();
5490
5491 if ((*auth)->isError()) {
5492 authTokens.removeOne(bestOffer);
5493 if (!authTokens.isEmpty()) {
5494 goto try_next_auth_scheme;
5495 } else {
5496 if (!sendErrorPageNotification()) {
5497 error(ERR_UNSUPPORTED_ACTION, i18n("Authorization failed."));
5498 }
5499 authRequiresAnotherRoundtrip = false;
5500 }
5501 //### return false; ?
5502 } else if ((*auth)->forceKeepAlive()) {
5503 //### think this through for proxied / not proxied
5504 m_request.isKeepAlive = true;
5505 } else if ((*auth)->forceDisconnect()) {
5506 //### think this through for proxied / not proxied
5507 m_request.isKeepAlive = false;
5508 httpCloseConnection();
5509 }
5510 }
5511 } else {
5512 authRequiresAnotherRoundtrip = false;
5513 if (!sendErrorPageNotification()) {
5514 error(ERR_UNSUPPORTED_ACTION, i18n("Unknown Authorization method."));
5515 }
5516 }
5517 }
5518
5519 return authRequiresAnotherRoundtrip;
5520}
5521
5522void HTTPProtocol::copyPut(const KUrl& src, const KUrl& dest, JobFlags flags)
5523{
5524 kDebug(7113) << src << "->" << dest;
5525
5526 if (!maybeSetRequestUrl(dest)) {
5527 return;
5528 }
5529
5530 resetSessionSettings();
5531
5532 if (!(flags & KIO::Overwrite)) {
5533 // check to make sure this host supports WebDAV
5534 if (!davHostOk()) {
5535 return;
5536 }
5537
5538 // Checks if the destination exists and return an error if it does.
5539 if (!davStatDestination()) {
5540 return;
5541 }
5542 }
5543
5544 m_POSTbuf = new QFile (src.toLocalFile());
5545 if (!m_POSTbuf->open(QFile::ReadOnly)) {
5546 error(KIO::ERR_CANNOT_OPEN_FOR_READING, QString());
5547 return;
5548 }
5549
5550 m_request.method = HTTP_PUT;
5551 m_request.cacheTag.policy = CC_Reload;
5552
5553 proceedUntilResponseContent();
5554}
5555
5556bool HTTPProtocol::davStatDestination()
5557{
5558 const QByteArray request ("<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
5559 "<D:propfind xmlns:D=\"DAV:\"><D:prop>"
5560 "<D:creationdate/>"
5561 "<D:getcontentlength/>"
5562 "<D:displayname/>"
5563 "<D:resourcetype/>"
5564 "</D:prop></D:propfind>");
5565 davSetRequest(request);
5566
5567 // WebDAV Stat or List...
5568 m_request.method = DAV_PROPFIND;
5569 m_request.url.setQuery(QString());
5570 m_request.cacheTag.policy = CC_Reload;
5571 m_request.davData.depth = 0;
5572
5573 proceedUntilResponseContent(true);
5574
5575 if (!m_request.isKeepAlive) {
5576 httpCloseConnection(); // close connection if server requested it.
5577 m_request.isKeepAlive = true; // reset the keep alive flag.
5578 }
5579
5580 if (m_request.responseCode == 207) {
5581 error(ERR_FILE_ALREADY_EXIST, QString());
5582 return false;
5583 }
5584
5585 // force re-authentication...
5586 delete m_wwwAuth;
5587 m_wwwAuth = 0;
5588
5589 return true;
5590}
5591
5592#include "http.moc"
HTTPFilterChain
HTTPFilterChain::addFilter
void addFilter(HTTPFilterBase *filter)
HTTPFilterChain::slotInput
void slotInput(const QByteArray &d)
HTTPFilterDeflate
HTTPFilterGZip
HTTPFilterMD5
HTTPFilterMD5::md5
QString md5()
HTTPProtocol
Definition: http.h:57
HTTPProtocol::m_socketProxyAuth
QAuthenticator * m_socketProxyAuth
Definition: http.h:583
HTTPProtocol::httpClose
void httpClose(bool keepAlive)
Close transfer.
Definition: http.cpp:3988
HTTPProtocol::maybeSetRequestUrl
bool maybeSetRequestUrl(const KUrl &)
Definition: http.cpp:650
HTTPProtocol::fixupResponseMimetype
void fixupResponseMimetype()
fix common mimetype errors by webservers.
Definition: http.cpp:2770
HTTPProtocol::davError
QString davError(int code=-1, const QString &url=QString())
Definition: http.cpp:1576
HTTPProtocol::cacheParseResponseHeader
void cacheParseResponseHeader(const HeaderTokenizer &tokenizer)
Definition: http.cpp:3647
HTTPProtocol::m_protocol
QByteArray m_protocol
Definition: http.h:578
HTTPProtocol::handleAuthenticationHeader
bool handleAuthenticationHeader(const HeaderTokenizer *tokenizer)
Handles HTTP authentication.
Definition: http.cpp:5357
HTTPProtocol::m_maxCacheSize
long m_maxCacheSize
Maximum cache size in Kb.
Definition: http.h:573
HTTPProtocol::readBody
bool readBody(bool dataInternal=false)
This function is our "receive" function.
Definition: http.cpp:4342
HTTPProtocol::addEncoding
void addEncoding(const QString &, QStringList &)
Add an encoding on to the appropriate stack this is nececesary because transfer encodings and content...
Definition: http.cpp:3621
HTTPProtocol::setHost
virtual void setHost(const QString &host, quint16 port, const QString &user, const QString &pass)
Definition: http.cpp:618
HTTPProtocol::isOffline
bool isOffline()
Check network status.
Definition: http.cpp:1931
HTTPProtocol::proceedUntilResponseHeader
bool proceedUntilResponseHeader()
Ensure we are connected, send our query, and get the response header.
Definition: http.cpp:696
HTTPProtocol::httpCloseConnection
void httpCloseConnection()
Close connection.
Definition: http.cpp:4022
HTTPProtocol::put
virtual void put(const KUrl &url, int _mode, KIO::JobFlags flags)
Definition: http.cpp:1337
HTTPProtocol::httpShouldCloseConnection
bool httpShouldCloseConnection()
Check whether to keep or close the connection.
Definition: http.cpp:2163
HTTPProtocol::formatRequestUri
QString formatRequestUri() const
Definition: http.cpp:2336
HTTPProtocol::cacheFileOpenRead
bool cacheFileOpenRead()
Definition: http.cpp:4870
HTTPProtocol::davStatDestination
bool davStatDestination()
Stats a remote DAV file and returns true if it exists.
Definition: http.cpp:5556
HTTPProtocol::m_mimeTypeBuffer
QByteArray m_mimeTypeBuffer
Definition: http.h:563
HTTPProtocol::m_transferEncodings
QStringList m_transferEncodings
Definition: http.h:546
HTTPProtocol::sendHttpError
bool sendHttpError()
Generate and send error message based on response code.
Definition: http.cpp:1887
HTTPProtocol::m_proxyAuth
KAbstractHttpAuthentication * m_proxyAuth
Definition: http.h:581
HTTPProtocol::m_webDavDataBuf
QByteArray m_webDavDataBuf
Definition: http.h:554
HTTPProtocol::readResponseHeader
bool readResponseHeader()
This function will read in the return header from the server.
Definition: http.cpp:2901
HTTPProtocol::davLock
void davLock(const KUrl &url, const QString &scope, const QString &type, const QString &owner)
Definition: http.cpp:1492
HTTPProtocol::m_responseHeaders
QStringList m_responseHeaders
All headers.
Definition: http.h:542
HTTPProtocol::readLimited
int readLimited()
Read maximum m_iSize bytes.
Definition: http.cpp:4215
HTTPProtocol::copy
virtual void copy(const KUrl &src, const KUrl &dest, int _permissions, KIO::JobFlags flags)
Definition: http.cpp:1366
HTTPProtocol::findCookies
QString findCookies(const QString &url)
Look for cookies in the cookiejar.
Definition: http.cpp:4613
HTTPProtocol::retrieveAllData
bool retrieveAllData()
Returns true on successful retrieval of all content data.
Definition: http.cpp:5109
HTTPProtocol::defaultPort
quint16 defaultPort() const
Definition: http.cpp:487
HTTPProtocol::slotFilterError
void slotFilterError(const QString &text)
Definition: http.cpp:4577
HTTPProtocol::HTTPProtocol
HTTPProtocol(const QByteArray &protocol, const QByteArray &pool, const QByteArray &app)
Definition: http.cpp:437
HTTPProtocol::readDelimitedText
bool readDelimitedText(char *buf, int *idx, int end, int numNewlines)
Definition: http.cpp:2109
HTTPProtocol::cacheFileReadTextHeader2
bool cacheFileReadTextHeader2()
load the rest of the text fields
Definition: http.cpp:4821
HTTPProtocol::m_cacheCleanerConnection
QLocalSocket m_cacheCleanerConnection
Connection to the cache cleaner process.
Definition: http.h:575
HTTPProtocol::resetResponseParsing
void resetResponseParsing()
Resets variables related to parsing a response.
Definition: http.cpp:492
HTTPProtocol::sendBody
bool sendBody()
Definition: http.cpp:3894
HTTPProtocol::m_maxCacheAge
int m_maxCacheAge
Maximum age of a cache entry in seconds.
Definition: http.h:572
HTTPProtocol::addCookies
void addCookies(const QString &url, const QByteArray &cookieHeader)
Send a cookie to the cookiejar.
Definition: http.cpp:4605
HTTPProtocol::del
virtual void del(const KUrl &url, bool _isfile)
Definition: http.cpp:1446
HTTPProtocol::cacheFileOpenWrite
bool cacheFileOpenWrite()
Definition: http.cpp:4904
HTTPProtocol::m_iContentLeft
KIO::filesize_t m_iContentLeft
Definition: http.h:531
HTTPProtocol::readUnlimited
int readUnlimited()
Read as much as possible.
Definition: http.cpp:4237
HTTPProtocol::sendErrorPageNotification
bool sendErrorPageNotification()
Call SlaveBase::errorPage() and remember that we've called it.
Definition: http.cpp:1918
HTTPProtocol::readChunked
int readChunked()
Read a chunk.
Definition: http.cpp:4144
HTTPProtocol::saveProxyAuthenticationForSocket
void saveProxyAuthenticationForSocket()
Definition: http.cpp:5290
HTTPProtocol::codeFromResponse
int codeFromResponse(const QString &response)
Returns the error code from a "HTTP/1.1 code Code Name" string.
Definition: http.cpp:960
HTTPProtocol::resetSessionSettings
void resetSessionSettings()
Resets any per session settings.
Definition: http.cpp:508
HTTPProtocol::slave_status
virtual void slave_status()
Definition: http.cpp:4031
HTTPProtocol::m_strCacheDir
QString m_strCacheDir
Location of the cache.
Definition: http.h:574
HTTPProtocol::davUnlock
void davUnlock(const KUrl &url)
Definition: http.cpp:1556
HTTPProtocol::closeConnection
virtual void closeConnection()
Forced close of connection.
Definition: http.cpp:4016
HTTPProtocol::m_POSTbuf
QIODevice * m_POSTbuf
Definition: http.h:569
HTTPProtocol::cacheFileWriteTextHeader
void cacheFileWriteTextHeader()
Definition: http.cpp:4774
HTTPProtocol::davGeneric
void davGeneric(const KUrl &url, KIO::HTTP_METHOD method, qint64 size=-1)
Definition: http.cpp:939
HTTPProtocol::post
void post(const KUrl &url, qint64 size=-1)
Definition: http.cpp:1477
HTTPProtocol::write
ssize_t write(const void *buf, size_t nbytes)
A thin wrapper around TCPSlaveBase::write() that will retry writing as long as no error occurs.
Definition: http.cpp:2035
HTTPProtocol::slotData
void slotData(const QByteArray &)
Definition: http.cpp:4256
HTTPProtocol::m_iEOFRetryCount
quint8 m_iEOFRetryCount
Definition: http.h:594
HTTPProtocol::fixupResponseContentEncoding
void fixupResponseContentEncoding()
fix common content-encoding errors by webservers.
Definition: http.cpp:2834
HTTPProtocol::m_cpMimeBuffer
bool m_cpMimeBuffer
Definition: http.h:562
HTTPProtocol::m_receiveBuf
QByteArray m_receiveBuf
Receive buffer.
Definition: http.h:532
HTTPProtocol::sendCachedBody
bool sendCachedBody()
Definition: http.cpp:3851
HTTPProtocol::cacheFileReadPayload
QByteArray cacheFileReadPayload(int maxLength)
Definition: http.cpp:5044
HTTPProtocol::listDir
virtual void listDir(const KUrl &url)
Definition: http.cpp:787
HTTPProtocol::HTTP_REV
HTTP_REV
HTTP version.
Definition: http.h:65
HTTPProtocol::HTTP_11
@ HTTP_11
Definition: http.h:65
HTTPProtocol::HTTP_None
@ HTTP_None
Definition: http.h:65
HTTPProtocol::HTTP_10
@ HTTP_10
Definition: http.h:65
HTTPProtocol::HTTP_Unknown
@ HTTP_Unknown
Definition: http.h:65
HTTPProtocol::SHOUTCAST
@ SHOUTCAST
Definition: http.h:65
HTTPProtocol::m_request
HTTPRequest m_request
Definition: http.h:524
HTTPProtocol::davSetRequest
void davSetRequest(const QByteArray &requestXML)
Performs a WebDAV stat or list.
Definition: http.cpp:798
HTTPProtocol::m_isChunked
bool m_isChunked
Chunked transfer encoding.
Definition: http.h:534
HTTPProtocol::unread
void unread(char *buf, size_t size)
Definition: http.cpp:2061
HTTPProtocol::davParsePropstats
void davParsePropstats(const QDomNodeList &propstats, KIO::UDSEntry &entry)
Definition: http.cpp:967
HTTPProtocol::m_isRedirection
bool m_isRedirection
Indicates current request is a redirection.
Definition: http.h:541
HTTPProtocol::cacheFileReadTextHeader1
bool cacheFileReadTextHeader1(const KUrl &desiredUrl)
check URL to guard against hash collisions, and load the etag for validation
Definition: http.cpp:4802
HTTPProtocol::resetConnectionSettings
void resetConnectionSettings()
Resets any per connection settings.
Definition: http.cpp:480
HTTPProtocol::m_dataInternal
bool m_dataInternal
Data is for internal consumption.
Definition: http.h:533
HTTPProtocol::setCacheabilityMetadata
void setCacheabilityMetadata(bool cachingAllowed)
Definition: http.cpp:3836
HTTPProtocol::m_isEOF
bool m_isEOF
Definition: http.h:537
HTTPProtocol::davProcessLocks
QString davProcessLocks()
Extracts locks from metadata Returns the appropriate If: header.
Definition: http.cpp:1192
HTTPProtocol::mimetype
virtual void mimetype(const KUrl &url)
Definition: http.cpp:4041
HTTPProtocol::copyPut
void copyPut(const KUrl &src, const KUrl &dest, KIO::JobFlags flags)
Handles file -> webdav put requests.
Definition: http.cpp:5522
HTTPProtocol::davFinished
void davFinished()
Definition: http.cpp:1295
HTTPProtocol::cacheFileClose
void cacheFileClose()
Definition: http.cpp:4961
HTTPProtocol::parseContentDisposition
void parseContentDisposition(const QString &disposition)
Definition: http.cpp:3609
HTTPProtocol::m_contentEncodings
QStringList m_contentEncodings
Definition: http.h:547
HTTPProtocol::multiGet
void multiGet(const QByteArray &data)
Definition: http.cpp:1945
HTTPProtocol::proxyAuthenticationForSocket
void proxyAuthenticationForSocket(const QNetworkProxy &, QAuthenticator *)
Definition: http.cpp:5231
HTTPProtocol::stat
virtual void stat(const KUrl &url)
Definition: http.cpp:755
HTTPProtocol::m_iBytesLeft
KIO::filesize_t m_iBytesLeft
Definition: http.h:530
HTTPProtocol::proceedUntilResponseContent
void proceedUntilResponseContent(bool dataInternal=false)
Do everything proceedUntilResponseHeader does, and also get the response body.
Definition: http.cpp:673
HTTPProtocol::forwardHttpResponseHeader
void forwardHttpResponseHeader(bool forwardImmediately=true)
Definition: http.cpp:2719
HTTPProtocol::mkdir
virtual void mkdir(const KUrl &url, int _permissions)
Definition: http.cpp:1302
HTTPProtocol::NoCache
@ NoCache
Definition: http.h:85
HTTPProtocol::WriteToCache
@ WriteToCache
Definition: http.h:87
HTTPProtocol::ReadFromCache
@ ReadFromCache
Definition: http.h:86
HTTPProtocol::get
virtual void get(const KUrl &url)
Definition: http.cpp:1322
HTTPProtocol::parseHeaderFromCache
bool parseHeaderFromCache()
Definition: http.cpp:2731
HTTPProtocol::m_isEOD
bool m_isEOD
Definition: http.h:538
HTTPProtocol::m_iPostDataSize
KIO::filesize_t m_iPostDataSize
Definition: http.h:529
HTTPProtocol::authenticationHeader
QString authenticationHeader()
create HTTP authentications response(s), if any
Definition: http.cpp:5146
HTTPProtocol::saveAuthenticationData
void saveAuthenticationData()
Saves HTTP authentication data.
Definition: http.cpp:5311
HTTPProtocol::sendCacheCleanerCommand
void sendCacheCleanerCommand(const QByteArray &command)
Definition: http.cpp:5020
HTTPProtocol::cacheFilePathFromUrl
QString cacheFilePathFromUrl(const KUrl &url) const
Definition: http.cpp:4860
HTTPProtocol::m_isBusy
bool m_isBusy
Busy handling request queue.
Definition: http.h:536
HTTPProtocol::m_isLoadingErrorPage
bool m_isLoadingErrorPage
Definition: http.h:588
HTTPProtocol::m_requestQueue
QList< HTTPRequest > m_requestQueue
Definition: http.h:525
HTTPProtocol::error
void error(int errid, const QString &text)
Definition: http.cpp:4582
HTTPProtocol::reparseConfiguration
virtual void reparseConfiguration()
Definition: http.cpp:466
HTTPProtocol::davStatList
void davStatList(const KUrl &url, bool stat=true)
Definition: http.cpp:804
HTTPProtocol::readBuffered
size_t readBuffered(char *buf, size_t size, bool unlimited=true)
Definition: http.cpp:2075
HTTPProtocol::m_wwwAuth
KAbstractHttpAuthentication * m_wwwAuth
Definition: http.h:580
HTTPProtocol::sendQuery
bool sendQuery()
This function is responsible for opening up the connection to the remote HTTP server and sending the ...
Definition: http.cpp:2377
HTTPProtocol::m_davHostUnsupported
bool m_davHostUnsupported
Definition: http.h:558
HTTPProtocol::m_iError
int m_iError
Definition: http.h:586
HTTPProtocol::m_mimeType
QString m_mimeType
Definition: http.h:549
HTTPProtocol::m_remoteRespTimeout
int m_remoteRespTimeout
Definition: http.h:591
HTTPProtocol::m_davHostOk
bool m_davHostOk
Definition: http.h:557
HTTPProtocol::davParseActiveLocks
void davParseActiveLocks(const QDomNodeList &activeLocks, uint &lockCount)
Definition: http.cpp:1146
HTTPProtocol::rename
virtual void rename(const KUrl &src, const KUrl &dest, KIO::JobFlags flags)
Definition: http.cpp:1403
HTTPProtocol::special
virtual void special(const QByteArray &data)
Special commands supported by this slave : 1 - HTTP POST 2 - Cache has been updated 3 - SSL Certifica...
Definition: http.cpp:4060
HTTPProtocol::cachePostData
void cachePostData(const QByteArray &)
Caches the POST data in a temporary buffer.
Definition: http.cpp:5089
HTTPProtocol::clearPostDataBuffer
void clearPostDataBuffer()
Clears the POST data buffer.
Definition: http.cpp:5100
HTTPProtocol::cacheFileWritePayload
void cacheFileWritePayload(const QByteArray &d)
Definition: http.cpp:5057
HTTPProtocol::clearUnreadBuffer
void clearUnreadBuffer()
Definition: http.cpp:2054
HTTPProtocol::m_contentMD5
QString m_contentMD5
Definition: http.h:548
HTTPProtocol::m_iSize
KIO::filesize_t m_iSize
Expected size of message.
Definition: http.h:528
HTTPProtocol::m_unreadBuf
QByteArray m_unreadBuf
Definition: http.h:596
HTTPProtocol::m_davCapabilities
QStringList m_davCapabilities
Definition: http.h:555
HTTPProtocol::satisfyRequestFromCache
bool satisfyRequestFromCache(bool *cacheHasPage)
Return true if the request is already "done", false otherwise.
Definition: http.cpp:2295
HTTPProtocol::httpOpenConnection
bool httpOpenConnection()
Open connection.
Definition: http.cpp:2185
HTTPProtocol::davHostOk
bool davHostOk()
Definition: http.cpp:1241
HTTPProtocol::m_server
HTTPServerState m_server
Definition: http.h:523
HTTPProtocol::~HTTPProtocol
virtual ~HTTPProtocol()
Definition: http.cpp:461
HeaderTokenizer
Definition: parsinghelpers.h:65
HeaderTokenizer::tokenize
int tokenize(int begin, int end)
Definition: parsinghelpers.cpp:169
KAbstractHttpAuthentication
Definition: httpauthentication.h:38
KAbstractHttpAuthentication::bestOffer
static QByteArray bestOffer(const QList< QByteArray > &offers)
Choose the best authentication mechanism from the offered ones.
Definition: httpauthentication.cpp:227
KAbstractHttpAuthentication::isError
bool isError() const
Definition: httpauthentication.h:117
KAbstractHttpAuthentication::fillKioAuthInfo
virtual void fillKioAuthInfo(KIO::AuthInfo *ai) const =0
KIO compatible data to find cached credentials.
KAbstractHttpAuthentication::generateResponse
virtual void generateResponse(const QString &user, const QString &password)=0
what to do in response to challenge
KAbstractHttpAuthentication::headerFragment
QByteArray headerFragment() const
insert this into the next request header after "Authorization: " or "Proxy-Authorization: "
Definition: httpauthentication.h:131
KAbstractHttpAuthentication::realm
QString realm() const
Returns the realm sent by the server.
Definition: httpauthentication.cpp:334
KAbstractHttpAuthentication::setChallenge
virtual void setChallenge(const QByteArray &c, const KUrl &resource, const QByteArray &httpMethod)
initiate authentication with challenge string (from HTTP header)
Definition: httpauthentication.cpp:322
KAbstractHttpAuthentication::newAuth
static KAbstractHttpAuthentication * newAuth(const QByteArray &offer, KConfigGroup *config=0)
Returns authentication object instance appropriate for offer.
Definition: httpauthentication.cpp:266
KComponentData
KConfigGroup
KConfigGroup::readPathEntry
QString readPathEntry(const char *key, const QString &aDefault) const
KConfigGroup::readEntry
QString readEntry(const char *key, const char *aDefault=0) const
KDateTime
KDateTime::time
QTime time() const
KDateTime::toString
QString toString(const QString &format) const
KDateTime::dateTime
QDateTime dateTime() const
KDateTime::fromString
static KDateTime fromString(const QString &string, const QString &format, const KTimeZones *zones=0, bool offsetIfAmbiguous=true)
KDateTime::toUtc
KDateTime toUtc() const
KDateTime::isValid
bool isValid() const
KDateTime::TimeFormat
TimeFormat
KDateTime::ISODate
ISODate
KDateTime::RFCDate
RFCDate
KDateTime::RFCDateDay
RFCDateDay
KDateTime::setTime_t
void setTime_t(qint64 seconds)
KIO::AuthInfo
KIO::AuthInfo::digestInfo
QString digestInfo
KIO::AuthInfo::realmValue
QString realmValue
KIO::AuthInfo::verifyPath
bool verifyPath
KIO::AuthInfo::keepPassword
bool keepPassword
KIO::AuthInfo::comment
QString comment
KIO::AuthInfo::url
KUrl url
KIO::AuthInfo::username
QString username
KIO::AuthInfo::password
QString password
KIO::AuthInfo::prompt
QString prompt
KIO::AuthInfo::commentLabel
QString commentLabel
KIO::SlaveBase::mimeType
void mimeType(const QString &_type)
KIO::SlaveBase::metaData
QString metaData(const QString &key) const
KIO::SlaveBase::sendAndKeepMetaData
void sendAndKeepMetaData()
KIO::SlaveBase::infoMessage
void infoMessage(const QString &msg)
KIO::SlaveBase::processedSize
void processedSize(KIO::filesize_t _bytes)
KIO::SlaveBase::setTimeoutSpecialCommand
void setTimeoutSpecialCommand(int timeout, const QByteArray &data=QByteArray())
KIO::SlaveBase::errorPage
void errorPage()
KIO::SlaveBase::finished
void finished()
KIO::SlaveBase::slaveStatus
void slaveStatus(const QString &host, bool connected)
KIO::SlaveBase::cacheAuthentication
bool cacheAuthentication(const AuthInfo &info)
KIO::SlaveBase::messageBox
int messageBox(const QString &text, MessageBoxType type, const QString &caption=QString(), const QString &buttonYes=i18n("&Yes"), const QString &buttonNo=i18n("&No"), const QString &dontAskAgainName=QString())
KIO::SlaveBase::dataReq
void dataReq()
KIO::SlaveBase::responseTimeout
int responseTimeout()
KIO::SlaveBase::written
void written(KIO::filesize_t _bytes)
KIO::SlaveBase::redirection
void redirection(const KUrl &_url)
KIO::SlaveBase::error
void error(int _errid, const QString &_text)
KIO::SlaveBase::sendMetaData
void sendMetaData()
KIO::SlaveBase::hasMetaData
bool hasMetaData(const QString &key) const
KIO::SlaveBase::statEntry
void statEntry(const UDSEntry &_entry)
KIO::SlaveBase::openPasswordDialog
bool openPasswordDialog(KIO::AuthInfo &info, const QString &errorMsg=QString())
KIO::SlaveBase::connected
void connected()
KIO::SlaveBase::remoteEncoding
KRemoteEncoding * remoteEncoding()
KIO::SlaveBase::dispatchLoop
void dispatchLoop()
KIO::SlaveBase::readData
int readData(QByteArray &buffer)
KIO::SlaveBase::WarningYesNo
WarningYesNo
KIO::SlaveBase::data
void data(const QByteArray &data)
KIO::SlaveBase::listEntry
void listEntry(const UDSEntry &_entry, bool ready)
KIO::SlaveBase::totalSize
void totalSize(KIO::filesize_t _bytes)
KIO::SlaveBase::mIncomingMetaData
MetaData mIncomingMetaData
KIO::SlaveBase::canResume
void canResume()
KIO::SlaveBase::checkCachedAuthentication
bool checkCachedAuthentication(AuthInfo &info)
KIO::SlaveBase::setMetaData
void setMetaData(const QString &key, const QString &value)
KIO::SlaveBase::config
KConfigGroup * config()
KIO::TCPSlaveBase
KIO::TCPSlaveBase::write
ssize_t write(const char *data, ssize_t len)
KIO::TCPSlaveBase::startSsl
bool startSsl()
KIO::TCPSlaveBase::setBlocking
void setBlocking(bool b)
KIO::TCPSlaveBase::disconnectFromHost
void disconnectFromHost()
KIO::TCPSlaveBase::port
quint16 port() const
KIO::TCPSlaveBase::connectToHost
int connectToHost(const QString &host, quint16 port, QString *errorString=0)
KIO::TCPSlaveBase::isAutoSsl
bool isAutoSsl() const
KIO::TCPSlaveBase::socket
QIODevice * socket() const
KIO::TCPSlaveBase::waitForResponse
bool waitForResponse(int t)
KIO::TCPSlaveBase::isUsingSsl
bool isUsingSsl() const
KIO::TCPSlaveBase::read
ssize_t read(char *data, ssize_t len)
KIO::TCPSlaveBase::isConnected
bool isConnected() const
KIO::UDSEntry
KIO::UDSEntry::stringValue
QString stringValue(uint field) const
KIO::UDSEntry::numberValue
long long numberValue(uint field, long long defaultValue=0) const
KIO::UDSEntry::UDS_CREATION_TIME
UDS_CREATION_TIME
KIO::UDSEntry::UDS_MIME_TYPE
UDS_MIME_TYPE
KIO::UDSEntry::UDS_FILE_TYPE
UDS_FILE_TYPE
KIO::UDSEntry::UDS_MODIFICATION_TIME
UDS_MODIFICATION_TIME
KIO::UDSEntry::UDS_SIZE
UDS_SIZE
KIO::UDSEntry::UDS_NAME
UDS_NAME
KIO::UDSEntry::UDS_GUESSED_MIME_TYPE
UDS_GUESSED_MIME_TYPE
KIO::UDSEntry::UDS_XML_PROPERTIES
UDS_XML_PROPERTIES
KIO::UDSEntry::UDS_ACCESS
UDS_ACCESS
KIO::UDSEntry::insert
void insert(uint field, const QString &value)
KIO::UDSEntry::clear
void clear()
KMessageBox::No
No
KMimeType::findByNameAndContent
static Ptr findByNameAndContent(const QString &name, const QByteArray &data, mode_t mode=0, int *accuracy=0)
KMimeType::findByUrl
static Ptr findByUrl(const KUrl &url, mode_t mode=0, bool is_local_file=false, bool fast_mode=false, int *accuracy=0)
KRemoteEncoding::encodingMib
int encodingMib() const
KSharedPtr
KStandardDirs::locateLocal
static QString locateLocal(const char *type, const QString &filename, bool createDir, const KComponentData &cData=KGlobal::mainComponent())
KTcpSocket
KTcpSocket::setSocketOption
void setSocketOption(QAbstractSocket::SocketOption options, const QVariant &value)
KTemporaryFile
KToolInvocation::startServiceByDesktopPath
static int startServiceByDesktopPath(const QString &_name, const QString &URL, QString *error=0, QString *serviceName=0, int *pid=0, const QByteArray &startup_id=QByteArray(), bool noWait=false)
KUrl::List
KUrl
KUrl::encodedPathAndQuery
QString encodedPathAndQuery(AdjustPathOption trailing=LeaveTrailingSlash, const EncodedPathAndQueryOptions &options=PermitEmptyPath) const
KUrl::prettyUrl
QString prettyUrl(AdjustPathOption trailing=LeaveTrailingSlash) const
KUrl::AvoidEmptyPath
AvoidEmptyPath
KUrl::LeaveTrailingSlash
LeaveTrailingSlash
KUrl::AddTrailingSlash
AddTrailingSlash
KUrl::adjustPath
void adjustPath(AdjustPathOption trailing)
KUrl::url
QString url(AdjustPathOption trailing=LeaveTrailingSlash) const
KUrl::ref
QString ref() const
KUrl::path
QString path(AdjustPathOption trailing=LeaveTrailingSlash) const
KUrl::setProtocol
void setProtocol(const QString &proto)
KUrl::hasHost
bool hasHost() const
KUrl::isLocalFile
bool isLocalFile() const
KUrl::split
static List split(const KUrl &_url)
KUrl::pass
QString pass() const
KUrl::user
QString user() const
KUrl::setPath
void setPath(const QString &path)
KUrl::setQuery
void setQuery(const QString &query)
KUrl::setRef
void setRef(const QString &fragment)
KUrl::setEncodedPathAndQuery
void setEncodedPathAndQuery(const QString &_txt)
KUrl::setPass
void setPass(const QString &pass)
KUrl::fileName
QString fileName(const DirectoryOptions &options=IgnoreTrailingSlash) const
KUrl::protocol
QString protocol() const
KUrl::decode_string
static QString decode_string(const QString &str)
KUrl::hasRef
bool hasRef() const
KUrl::toLocalFile
QString toLocalFile(AdjustPathOption trailing=LeaveTrailingSlash) const
KUrl::setUser
void setUser(const QString &user)
QIODevice
QList
QMap
QTemporaryFile
TokenIterator
Definition: parsinghelpers.h:40
TokenIterator::current
QByteArray current() const
Definition: parsinghelpers.cpp:100
TokenIterator::hasNext
bool hasNext() const
Definition: parsinghelpers.h:42
TokenIterator::next
QByteArray next()
Definition: parsinghelpers.cpp:90
output
void output(QList< Action > actions, QHash< QString, QString > domain)
header
const char header[]
MAX_IPC_SIZE
#define MAX_IPC_SIZE
Definition: file.cpp:94
supportedProxyScheme
static bool supportedProxyScheme(const QString &scheme)
Definition: ftp.cpp:106
kDebug
#define kDebug
kWarning
#define kWarning
changeProtocolToHttp
static void changeProtocolToHttp(KUrl *url)
Definition: http.cpp:343
compareByte
static bool compareByte(QDataStream *stream, quint8 value)
Definition: http.cpp:4712
updateUDSEntryMimeType
static void updateUDSEntryMimeType(UDSEntry *entry)
Definition: http.cpp:326
isEncryptedHttpVariety
static bool isEncryptedHttpVariety(const QByteArray &p)
Definition: http.cpp:272
filenameFromUrl
static QString filenameFromUrl(const KUrl &url)
Definition: http.cpp:4853
isValidProxy
static bool isValidProxy(const KUrl &u)
Definition: http.cpp:277
s_hashedUrlBits
static const int s_hashedUrlBits
Definition: http.cpp:119
writeLine
static void writeLine(QIODevice *dev, const QByteArray &line)
Definition: http.cpp:4767
NO_SIZE
#define NO_SIZE
Definition: http.cpp:425
formatHttpDate
static QString formatHttpDate(qint64 date)
Definition: http.cpp:406
httpPutError
static int httpPutError(const HTTPProtocol::HTTPRequest &request, QString *errorString)
Definition: http.cpp:1824
toTime_t
static qint64 toTime_t(const QString &value, KDateTime::TimeFormat format)
Definition: http.cpp:301
sanitizeCustomHTTPHeader
static QString sanitizeCustomHTTPHeader(const QString &_header)
Definition: http.cpp:184
parseDateTime
static qint64 parseDateTime(const QString &input, const QString &type)
Definition: http.cpp:307
consume
static bool consume(const char input[], int *pos, int end, const char *term)
Definition: http.cpp:2880
STRTOLL
#define STRTOLL
Definition: http.cpp:430
protocolForProxyType
static QString protocolForProxyType(QNetworkProxy::ProxyType type)
Definition: http.cpp:5212
isCompatibleNextUrl
static bool isCompatibleNextUrl(const KUrl &previous, const KUrl &now)
Definition: http.cpp:2152
isAuthenticationRequired
static bool isAuthenticationRequired(int responseCode)
Definition: http.cpp:420
kdemain
int kdemain(int argc, char **argv)
Definition: http.cpp:126
storableUrl
static KUrl storableUrl(const KUrl &url)
Definition: http.cpp:4759
canHaveResponseBody
static bool canHaveResponseBody(int responseCode, KIO::HTTP_METHOD method)
Definition: http.cpp:228
isCrossDomainRequest
static bool isCrossDomainRequest(const QString &fqdn, const QString &originURL)
Definition: http.cpp:150
httpGenericError
static int httpGenericError(const HTTPProtocol::HTTPRequest &request, QString *errorString)
Definition: http.cpp:1782
isPotentialSpoofingAttack
static bool isPotentialSpoofingAttack(const HTTPProtocol::HTTPRequest &request, const KConfigGroup *config)
Definition: http.cpp:207
readLineChecked
static bool readLineChecked(QIODevice *dev, QByteArray *line)
Definition: http.cpp:4790
s_hashedUrlBytes
static const int s_hashedUrlBytes
Definition: http.cpp:121
htmlEscape
static QString htmlEscape(const QString &plain)
Definition: http.cpp:91
supportedProxyScheme
static bool supportedProxyScheme(const QString &scheme)
Definition: http.cpp:111
makeCacheCleanerCommand
static QByteArray makeCacheCleanerCommand(const HTTPProtocol::CacheTag &cacheTag, CacheCleanerCommandCode cmd)
Definition: http.cpp:4940
createPostBufferDeviceFor
static QIODevice * createPostBufferDeviceFor(KIO::filesize_t size)
Definition: http.cpp:287
httpDelError
static int httpDelError(const HTTPProtocol::HTTPRequest &request, QString *errorString)
Definition: http.cpp:1797
isHttpProxy
static bool isHttpProxy(const KUrl &u)
Definition: http.cpp:282
toQString
static QString toQString(const QByteArray &value)
Definition: http.cpp:145
CacheCleanerCommandCode
CacheCleanerCommandCode
Definition: http.cpp:4678
UpdateFileCommand
@ UpdateFileCommand
Definition: http.cpp:4681
InvalidCommand
@ InvalidCommand
Definition: http.cpp:4679
CreateFileNotificationCommand
@ CreateFileNotificationCommand
Definition: http.cpp:4680
s_MaxInMemPostBufSize
static const int s_MaxInMemPostBufSize
Definition: http.cpp:122
s_hashedUrlNibbles
static const int s_hashedUrlNibbles
Definition: http.cpp:120
http.h
CacheCleanerCommand
CacheCleanerCommand
Definition: http_cache_cleaner.cpp:265
version
static const char version[]
Definition: http_cache_cleaner.cpp:65
filePath
static QString filePath(const QString &baseName)
Definition: http_cache_cleaner.cpp:208
http_slave_defaults.h
DEFAULT_KEEP_ALIVE_TIMEOUT
#define DEFAULT_KEEP_ALIVE_TIMEOUT
DEFAULT_CACHE_EXPIRE
#define DEFAULT_CACHE_EXPIRE
DEFAULT_CACHE_CONTROL
#define DEFAULT_CACHE_CONTROL
DEFAULT_MIME_TYPE
#define DEFAULT_MIME_TYPE
DEFAULT_MAX_CACHE_AGE
#define DEFAULT_MAX_CACHE_AGE
DEFAULT_MAX_CACHE_SIZE
#define DEFAULT_MAX_CACHE_SIZE
DEFAULT_PARTIAL_CHARSET_HEADER
#define DEFAULT_PARTIAL_CHARSET_HEADER
DEFAULT_ACCEPT_HEADER
#define DEFAULT_ACCEPT_HEADER
DEFAULT_LANGUAGE_HEADER
#define DEFAULT_LANGUAGE_HEADER
httpauthentication.h
httpfilter.h
ioslave_defaults.h
DEFAULT_RESPONSE_TIMEOUT
#define DEFAULT_RESPONSE_TIMEOUT
DEFAULT_HTTPS_PORT
#define DEFAULT_HTTPS_PORT
DEFAULT_HTTP_PORT
#define DEFAULT_HTTP_PORT
kaboutdata.h
kapplication.h
readEntry
KAutostart::StartPhase readEntry(const KConfigGroup &group, const char *key, const KAutostart::StartPhase &aDefault)
kcmdlineargs.h
kcomponentdata.h
kconfig.h
kconfiggroup.h
kdatetime.h
kdebug.h
timeout
int timeout
klocale.h
i18n
QString i18n(const char *text)
i18nc
QString i18nc(const char *ctxt, const char *text)
kmessagebox.h
kmimetype.h
kremoteencoding.h
kservice.h
kstandarddirs.h
ktcpsocket.h
ktemporaryfile.h
ktoolinvocation.h
kurl.h
KGlobal::locale
KLocale * locale()
config
KSharedConfigPtr config()
KIO
trash
CopyJob * trash(const KUrl &src, JobFlags flags=DefaultFlags)
KIO::convertSize
QString convertSize(KIO::filesize_t size)
link
CopyJob * link(const KUrl &src, const KUrl &destDir, JobFlags flags=DefaultFlags)
CC_Cache
CC_Cache
CC_Verify
CC_Verify
CC_Reload
CC_Reload
KIO::CC_CacheOnly
CC_CacheOnly
KIO::CC_Refresh
CC_Refresh
KIO::Overwrite
Overwrite
KIO::filesize_t
qulonglong filesize_t
ERR_NO_CONTENT
ERR_NO_CONTENT
ERR_UNKNOWN_PROXY_HOST
ERR_UNKNOWN_PROXY_HOST
ERR_SLAVE_DEFINED
ERR_SLAVE_DEFINED
ERR_DOES_NOT_EXIST
ERR_DOES_NOT_EXIST
ERR_DIR_ALREADY_EXIST
ERR_DIR_ALREADY_EXIST
ERR_CONNECTION_BROKEN
ERR_CONNECTION_BROKEN
ERR_COULD_NOT_CONNECT
ERR_COULD_NOT_CONNECT
ERR_FILE_ALREADY_EXIST
ERR_FILE_ALREADY_EXIST
KIO::ERR_CANNOT_OPEN_FOR_READING
ERR_CANNOT_OPEN_FOR_READING
ERR_ABORTED
ERR_ABORTED
ERR_ACCESS_DENIED
ERR_ACCESS_DENIED
ERR_MALFORMED_URL
ERR_MALFORMED_URL
ERR_UPGRADE_REQUIRED
ERR_UPGRADE_REQUIRED
ERR_OUT_OF_MEMORY
ERR_OUT_OF_MEMORY
ERR_INTERNAL_SERVER
ERR_INTERNAL_SERVER
ERR_UNSUPPORTED_ACTION
ERR_UNSUPPORTED_ACTION
ERR_USER_CANCELED
ERR_USER_CANCELED
KIO::ERR_UNKNOWN_HOST
ERR_UNKNOWN_HOST
ERR_UNSUPPORTED_PROTOCOL
ERR_UNSUPPORTED_PROTOCOL
ERR_POST_NO_SIZE
ERR_POST_NO_SIZE
ERR_SERVER_TIMEOUT
ERR_SERVER_TIMEOUT
KIO::number
QString number(KIO::filesize_t size)
parseCacheControl
KIO::CacheControl parseCacheControl(const QString &cacheControl)
name
const char * name(StandardAction id)
cont
KGuiItem cont()
ok
KGuiItem ok()
end
const KShortcut & end()
KStringHandler::isUtf8
bool isUtf8(const char *str)
parsinghelpers.cpp
skipSpace
static void skipSpace(const char input[], int *pos, int end)
Definition: parsinghelpers.cpp:32
contentDispositionParser
static QMap< QString, QString > contentDispositionParser(const QString &disposition)
Definition: parsinghelpers.cpp:581
nextLine
static bool nextLine(const char input[], int *pos, int end)
Definition: parsinghelpers.cpp:44
parsinghelpers.h
HTTPProtocol::CacheTag
Definition: http.h:91
HTTPProtocol::CacheTag::servedDate
qint64 servedDate
Definition: http.h:121
HTTPProtocol::CacheTag::ioMode
enum CacheIOMode ioMode
Definition: http.h:116
HTTPProtocol::CacheTag::CachePlan
CachePlan
Definition: http.h:104
HTTPProtocol::CacheTag::ValidateCached
@ ValidateCached
Definition: http.h:106
HTTPProtocol::CacheTag::UseCached
@ UseCached
Definition: http.h:105
HTTPProtocol::CacheTag::IgnoreCached
@ IgnoreCached
Definition: http.h:107
HTTPProtocol::CacheTag::deserialize
bool deserialize(const QByteArray &)
Definition: http.cpp:4723
HTTPProtocol::CacheTag::file
QFile * file
Definition: http.h:120
HTTPProtocol::CacheTag::bytesCached
quint32 bytesCached
Definition: http.h:118
HTTPProtocol::CacheTag::charset
QString charset
Definition: http.h:124
HTTPProtocol::CacheTag::lastModifiedDate
qint64 lastModifiedDate
Definition: http.h:122
HTTPProtocol::CacheTag::useCache
bool useCache
Definition: http.h:115
HTTPProtocol::CacheTag::policy
KIO::CacheControl policy
Definition: http.h:114
HTTPProtocol::CacheTag::expireDate
qint64 expireDate
Definition: http.h:123
HTTPProtocol::CacheTag::etag
QString etag
Definition: http.h:119
HTTPProtocol::CacheTag::plan
CachePlan plan(int maxCacheAge) const
Definition: http.cpp:4629
HTTPProtocol::CacheTag::serialize
QByteArray serialize() const
Definition: http.cpp:4693
HTTPProtocol::CacheTag::fileUseCount
quint32 fileUseCount
Definition: http.h:117
HTTPProtocol::DAVRequest::overwrite
bool overwrite
Definition: http.h:80
HTTPProtocol::DAVRequest::depth
int depth
Definition: http.h:81
HTTPProtocol::DAVRequest::desturl
QString desturl
Definition: http.h:79
HTTPProtocol::HTTPRequest
The request for the current connection.
Definition: http.h:129
HTTPProtocol::HTTPRequest::encoded_hostname
QString encoded_hostname
Definition: http.h:146
HTTPProtocol::HTTPRequest::useCookieJar
bool useCookieJar
Definition: http.h:181
HTTPProtocol::HTTPRequest::userAgent
QString userAgent
Definition: http.h:161
HTTPProtocol::HTTPRequest::windowId
QString windowId
Definition: http.h:156
HTTPProtocol::HTTPRequest::referrer
QString referrer
Definition: http.h:158
HTTPProtocol::HTTPRequest::methodString
QByteArray methodString() const
Definition: http.cpp:356
HTTPProtocol::HTTPRequest::allowTransferCompression
bool allowTransferCompression
Definition: http.h:173
HTTPProtocol::HTTPRequest::CookiesManual
@ CookiesManual
Definition: http.h:183
HTTPProtocol::HTTPRequest::CookiesNone
@ CookiesNone
Definition: http.h:183
HTTPProtocol::HTTPRequest::CookiesAuto
@ CookiesAuto
Definition: http.h:183
HTTPProtocol::HTTPRequest::sentMethodString
QByteArray sentMethodString
Definition: http.h:153
HTTPProtocol::HTTPRequest::endoffset
KIO::filesize_t endoffset
Definition: http.h:155
HTTPProtocol::HTTPRequest::cacheTag
CacheTag cacheTag
Definition: http.h:185
HTTPProtocol::HTTPRequest::doNotWWWAuthenticate
bool doNotWWWAuthenticate
Definition: http.h:175
HTTPProtocol::HTTPRequest::cookieMode
enum HTTPProtocol::HTTPRequest::@1 cookieMode
HTTPProtocol::HTTPRequest::preferErrorPage
bool preferErrorPage
Definition: http.h:178
HTTPProtocol::HTTPRequest::responseCode
unsigned int responseCode
Definition: http.h:163
HTTPProtocol::HTTPRequest::prevResponseCode
unsigned int prevResponseCode
Definition: http.h:164
HTTPProtocol::HTTPRequest::redirectUrl
KUrl redirectUrl
Definition: http.h:168
HTTPProtocol::HTTPRequest::keepAliveTimeout
int keepAliveTimeout
Definition: http.h:149
HTTPProtocol::HTTPRequest::isKeepAlive
bool isKeepAlive
Definition: http.h:148
HTTPProtocol::HTTPRequest::languages
QString languages
Definition: http.h:160
HTTPProtocol::HTTPRequest::proxyUrl
KUrl proxyUrl
Definition: http.h:169
HTTPProtocol::HTTPRequest::disablePassDialog
bool disablePassDialog
Definition: http.h:174
HTTPProtocol::HTTPRequest::methodStringOverride
QString methodStringOverride
Definition: http.h:152
HTTPProtocol::HTTPRequest::proxyUrls
QStringList proxyUrls
Definition: http.h:170
HTTPProtocol::HTTPRequest::offset
KIO::filesize_t offset
Definition: http.h:154
HTTPProtocol::HTTPRequest::method
KIO::HTTP_METHOD method
Definition: http.h:151
HTTPProtocol::HTTPRequest::url
KUrl url
Definition: http.h:145
HTTPProtocol::HTTPRequest::davData
DAVRequest davData
Definition: http.h:167
HTTPProtocol::HTTPRequest::doNotProxyAuthenticate
bool doNotProxyAuthenticate
Definition: http.h:176
HTTPProtocol::HTTPRequest::id
QString id
Definition: http.h:166
HTTPProtocol::HTTPRequest::charsets
QString charsets
Definition: http.h:159
HTTPProtocol::HTTPServerState::url
KUrl url
Definition: http.h:228
HTTPProtocol::HTTPServerState::clear
void clear()
Definition: http.h:219
HTTPProtocol::HTTPServerState::proxyUrl
KUrl proxyUrl
Definition: http.h:230
HTTPProtocol::HTTPServerState::initFrom
void initFrom(const HTTPRequest &request)
Definition: http.h:197
HTTPProtocol::HTTPServerState::updateCredentials
void updateCredentials(const HTTPRequest &request)
Definition: http.h:206
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Feb 20 2023 00:00:00 by doxygen 1.9.6 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KIOSlave

Skip menu "KIOSlave"
  • Main Page
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdelibs-4.14.38 API Reference

Skip menu "kdelibs-4.14.38 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal