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

KIOSlave

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

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

Search



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