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

Skip menu "kdelibs-4.10.5 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