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

KIOSlave

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