~ubuntu-branches/ubuntu/oneiric/kde4libs/oneiric-proposed

« back to all changes in this revision

Viewing changes to kioslave/http/http.cpp

  • Committer: Package Import Robot
  • Author(s): Philip Muškovac
  • Date: 2011-07-08 00:08:34 UTC
  • mto: This revision was merged to the branch mainline in revision 247.
  • Revision ID: package-import@ubuntu.com-20110708000834-dr9a8my4iml90qe5
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
44
44
#include <QtCore/QFile>
45
45
#include <QtCore/QRegExp>
46
46
#include <QtCore/QDate>
 
47
#include <QtCore/QBuffer>
 
48
#include <QtCore/QIODevice>
47
49
#include <QtDBus/QtDBus>
48
50
#include <QtNetwork/QAuthenticator>
49
51
#include <QtNetwork/QNetworkProxy>
64
66
#include <kstandarddirs.h>
65
67
#include <kremoteencoding.h>
66
68
#include <ktcpsocket.h>
 
69
#include <kmessagebox.h>
67
70
 
68
71
#include <kio/ioslave_defaults.h>
69
72
#include <kio/http_slave_defaults.h>
92
95
#include <kaboutdata.h>
93
96
#include <kcmdlineargs.h>
94
97
#include <kde_file.h>
 
98
#include <ktemporaryfile.h>
95
99
 
96
100
//string parsing helpers and HeaderTokenizer implementation
97
101
#include "parsinghelpers.cpp"
103
107
static const int s_hashedUrlBits = 160;   // this number should always be divisible by eight
104
108
static const int s_hashedUrlNibbles = s_hashedUrlBits / 4;
105
109
static const int s_hashedUrlBytes = s_hashedUrlBits / 8;
 
110
static const int s_MaxInMemPostBufSize = 256 * 1024;   // Write anyting over 256 KB to file...
106
111
 
107
112
using namespace KIO;
108
113
 
187
192
  return sanitizedHeaders;
188
193
}
189
194
 
 
195
static bool isPotentialSpoofingAttack(const HTTPProtocol::HTTPRequest& request, const KConfigGroup* config)
 
196
{
 
197
    // kDebug(7113) << request.url << "response code: " << request.responseCode << "previous response code:" << request.prevResponseCode;
 
198
    if (request.url.user().isEmpty()) {
 
199
        return false;
 
200
    }
 
201
 
 
202
    // We already have cached authentication.
 
203
    if (config->readEntry(QLatin1String("cached-www-auth"), false)) {
 
204
        return false;
 
205
    }
 
206
 
 
207
    const QString userName = config->readEntry(QLatin1String("LastSpoofedUserName"), QString());
 
208
    return ((userName.isEmpty() || userName != request.url.user()) && request.responseCode != 401 && request.prevResponseCode != 401);
 
209
}
 
210
 
190
211
// for a given response code, conclude if the response is going to/likely to have a response body
191
212
static bool canHaveResponseBody(int responseCode, KIO::HTTP_METHOD method)
192
213
{
247
268
    return isValidProxy(u) && u.protocol() == QLatin1String("http");
248
269
}
249
270
 
 
271
static QIODevice* createPostBufferDeviceFor (KIO::filesize_t size)
 
272
{
 
273
    QIODevice* device;
 
274
    if (size > static_cast<KIO::filesize_t>(s_MaxInMemPostBufSize))
 
275
      device = new KTemporaryFile;
 
276
    else
 
277
      device = new QBuffer;
 
278
 
 
279
    if (!device->open(QIODevice::ReadWrite))
 
280
      return 0;
 
281
 
 
282
    return device;
 
283
}
 
284
 
250
285
QByteArray HTTPProtocol::HTTPRequest::methodString() const
251
286
{
252
287
    if (!methodStringOverride.isEmpty())
253
 
        return (methodStringOverride + QLatin1Char(' ')).toLatin1();
 
288
        return (methodStringOverride + QLatin1Char(' ')).toLatin1();
254
289
 
255
290
    switch(method) {
256
291
    case HTTP_GET:
332
367
                            const QByteArray &app )
333
368
    : TCPSlaveBase(protocol, pool, app, isEncryptedHttpVariety(protocol))
334
369
    , m_iSize(NO_SIZE)
 
370
    , m_iPostDataSize(NO_SIZE)
335
371
    , m_isBusy(false)
 
372
    , m_POSTbuf(0)
336
373
    , m_maxCacheAge(DEFAULT_MAX_CACHE_AGE)
337
374
    , m_maxCacheSize(DEFAULT_MAX_CACHE_SIZE)
338
375
    , m_protocol(protocol)
484
521
  }
485
522
 
486
523
  if (config()->readEntry("SendLanguageSettings", true)) {
487
 
      m_request.charsets = config()->readEntry( "Charsets", "iso-8859-1" );
488
 
      if (!m_request.charsets.isEmpty()) {
489
 
          m_request.charsets += QLatin1String(DEFAULT_PARTIAL_CHARSET_HEADER);
 
524
      m_request.charsets = config()->readEntry("Charsets", DEFAULT_PARTIAL_CHARSET_HEADER);
 
525
      if (!m_request.charsets.contains(QLatin1String("*;"), Qt::CaseInsensitive)) {
 
526
          m_request.charsets += QLatin1String(",*;q=0.5");
490
527
      }
491
 
      m_request.languages = config()->readEntry( "Languages", DEFAULT_LANGUAGE_HEADER );
 
528
      m_request.languages = config()->readEntry("Languages", DEFAULT_LANGUAGE_HEADER);
492
529
  } else {
493
530
      m_request.charsets.clear();
494
531
      m_request.languages.clear();
539
576
 
540
577
  // Bounce back the actual referrer sent
541
578
  setMetaData(QLatin1String("referrer"), m_request.referrer);
 
579
 
 
580
  // Reset the post data size
 
581
  m_iPostDataSize = NO_SIZE;
542
582
}
543
583
 
544
584
void HTTPProtocol::setHost( const QString& host, quint16 port,
605
645
 
606
646
  // if data is required internally, don't finish,
607
647
  // it is processed before we finish()
608
 
  if (!dataInternal) {
609
 
      if ((m_request.responseCode == 204) &&
610
 
          ((m_request.method == HTTP_GET) || (m_request.method == HTTP_POST))) {
611
 
          error(ERR_NO_CONTENT, QString());
612
 
      } else {
613
 
          finished();
614
 
      }
615
 
  }
 
648
  if (dataInternal) {
 
649
      return;
 
650
  }
 
651
 
 
652
  if (m_request.responseCode == 204 &&
 
653
      (m_request.method == HTTP_GET || m_request.method == HTTP_POST)) {
 
654
      infoMessage(QLatin1String(""));
 
655
      error(ERR_NO_CONTENT, QString());
 
656
      return;
 
657
  }
 
658
 
 
659
  finished();
616
660
}
617
661
 
618
662
bool HTTPProtocol::proceedUntilResponseHeader()
666
710
  setMetaData(QLatin1String("content-type"), m_mimeType);
667
711
 
668
712
  // At this point sendBody() should have delivered any POST data.
669
 
  m_POSTbuf.clear();
 
713
  clearPostDataBuffer();
670
714
 
671
715
  return true;
672
716
}
717
761
void HTTPProtocol::davSetRequest( const QByteArray& requestXML )
718
762
{
719
763
  // insert the document into the POST buffer, kill trailing zero byte
720
 
  m_POSTbuf = requestXML;
 
764
  cachePostData(requestXML);
721
765
}
722
766
 
723
767
void HTTPProtocol::davStatList( const KUrl& url, bool stat )
858
902
  }
859
903
}
860
904
 
861
 
void HTTPProtocol::davGeneric( const KUrl& url, KIO::HTTP_METHOD method )
 
905
void HTTPProtocol::davGeneric( const KUrl& url, KIO::HTTP_METHOD method, qint64 size )
862
906
{
863
907
  kDebug(7113) << url.url();
864
908
 
875
919
  m_request.url.setQuery(QString());
876
920
  m_request.cacheTag.policy = CC_Reload;
877
921
 
 
922
  m_iPostDataSize = (size > -1 ? static_cast<KIO::filesize_t>(size) : NO_SIZE);
878
923
  proceedUntilResponseContent( false );
879
924
}
880
925
 
1459
1504
  }
1460
1505
}
1461
1506
 
1462
 
void HTTPProtocol::post( const KUrl& url )
 
1507
void HTTPProtocol::post( const KUrl& url, qint64 size )
1463
1508
{
1464
1509
  kDebug(7113) << url.url();
1465
1510
 
1470
1515
  m_request.method = HTTP_POST;
1471
1516
  m_request.cacheTag.policy= CC_Reload;
1472
1517
 
 
1518
  m_iPostDataSize = (size > -1 ? static_cast<KIO::filesize_t>(size) : NO_SIZE);
1473
1519
  proceedUntilResponseContent();
1474
1520
}
1475
1521
 
1513
1559
  }
1514
1560
 
1515
1561
  // insert the document into the POST buffer
1516
 
  m_POSTbuf = lockReq.toByteArray();
 
1562
  cachePostData(lockReq.toByteArray());
1517
1563
 
1518
1564
  proceedUntilResponseContent( true );
1519
1565
 
1573
1619
    url = m_request.url.url();
1574
1620
 
1575
1621
  QString action, errorString;
1576
 
  KIO::Error kError;
1577
1622
 
1578
1623
  // for 412 Precondition Failed
1579
1624
  QString ow = i18n( "Otherwise, the request would have succeeded." );
1624
1669
  }
1625
1670
 
1626
1671
  // default error message if the following code fails
1627
 
  kError = ERR_INTERNAL;
1628
1672
  errorString = i18nc("%1: code, %2: request type", "An unexpected error (%1) occurred "
1629
1673
                      "while attempting to %2.", code, action);
1630
1674
 
1632
1676
  {
1633
1677
    case -2:
1634
1678
      // internal error: OPTIONS request did not specify DAV compliance
1635
 
      kError = ERR_UNSUPPORTED_PROTOCOL;
 
1679
      // ERR_UNSUPPORTED_PROTOCOL
1636
1680
      errorString = i18n("The server does not support the WebDAV protocol.");
1637
1681
      break;
1638
1682
    case 207:
1688
1732
    case 403:
1689
1733
    case 500: // hack: Apache mod_dav returns this instead of 403 (!)
1690
1734
      // 403 Forbidden
1691
 
      kError = ERR_ACCESS_DENIED;
 
1735
      // ERR_ACCESS_DENIED
1692
1736
      errorString = i18nc( "%1: request type", "Access was denied while attempting to %1.",  action );
1693
1737
      break;
1694
1738
    case 405:
1695
1739
      // 405 Method Not Allowed
1696
 
      if ( m_request.method == DAV_MKCOL )
1697
 
      {
1698
 
        kError = ERR_DIR_ALREADY_EXIST;
 
1740
      if ( m_request.method == DAV_MKCOL ) {
 
1741
        // ERR_DIR_ALREADY_EXIST
1699
1742
        errorString = i18n("The specified folder already exists.");
1700
1743
      }
1701
1744
      break;
1702
1745
    case 409:
1703
1746
      // 409 Conflict
1704
 
      kError = ERR_ACCESS_DENIED;
 
1747
      // ERR_ACCESS_DENIED
1705
1748
      errorString = i18n("A resource cannot be created at the destination "
1706
1749
                  "until one or more intermediate collections (folders) "
1707
1750
                  "have been created.");
1708
1751
      break;
1709
1752
    case 412:
1710
1753
      // 412 Precondition failed
1711
 
      if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE )
1712
 
      {
1713
 
        kError = ERR_ACCESS_DENIED;
 
1754
      if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE ) {
 
1755
        // ERR_ACCESS_DENIED
1714
1756
        errorString = i18n("The server was unable to maintain the liveness of "
1715
1757
                           "the properties listed in the propertybehavior XML "
1716
1758
                           "element or you attempted to overwrite a file while "
1717
1759
                           "requesting that files are not overwritten. %1",
1718
1760
                             ow );
1719
1761
 
1720
 
      }
1721
 
      else if ( m_request.method == DAV_LOCK )
1722
 
      {
1723
 
        kError = ERR_ACCESS_DENIED;
 
1762
      } else if ( m_request.method == DAV_LOCK ) {
 
1763
        // ERR_ACCESS_DENIED
1724
1764
        errorString = i18n("The requested lock could not be granted. %1",  ow );
1725
1765
      }
1726
1766
      break;
1727
1767
    case 415:
1728
1768
      // 415 Unsupported Media Type
1729
 
      kError = ERR_ACCESS_DENIED;
 
1769
      // ERR_ACCESS_DENIED
1730
1770
      errorString = i18n("The server does not support the request type of the body.");
1731
1771
      break;
1732
1772
    case 423:
1733
1773
      // 423 Locked
1734
 
      kError = ERR_ACCESS_DENIED;
 
1774
      // ERR_ACCESS_DENIED
1735
1775
      errorString = i18nc( "%1: request type", "Unable to %1 because the resource is locked.",  action );
1736
1776
      break;
1737
1777
    case 425:
1740
1780
      break;
1741
1781
    case 502:
1742
1782
      // 502 Bad Gateway
1743
 
      if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE )
1744
 
      {
1745
 
        kError = ERR_WRITE_ACCESS_DENIED;
 
1783
      if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE ) {
 
1784
        // ERR_WRITE_ACCESS_DENIED
1746
1785
        errorString = i18nc( "%1: request type", "Unable to %1 because the destination server refuses "
1747
1786
                           "to accept the file or folder.",  action );
1748
1787
      }
1749
1788
      break;
1750
1789
    case 507:
1751
1790
      // 507 Insufficient Storage
1752
 
      kError = ERR_DISK_FULL;
 
1791
      // ERR_DISK_FULL
1753
1792
      errorString = i18n("The destination resource does not have sufficient space "
1754
1793
                         "to record the state of the resource after the execution "
1755
1794
                         "of this method.");
1768
1807
void HTTPProtocol::httpPutError()
1769
1808
{
1770
1809
  QString action, errorString;
1771
 
  KIO::Error kError;
1772
1810
 
1773
1811
  switch ( m_request.method ) {
1774
1812
    case HTTP_PUT:
1781
1819
  }
1782
1820
 
1783
1821
  // default error message if the following code fails
1784
 
  kError = ERR_INTERNAL;
1785
1822
  errorString = i18nc("%1: response code, %2: request type",
1786
1823
                      "An unexpected error (%1) occurred while attempting to %2.",
1787
1824
                       m_request.responseCode, action);
1793
1830
    case 500: // hack: Apache mod_dav returns this instead of 403 (!)
1794
1831
      // 403 Forbidden
1795
1832
      // 405 Method Not Allowed
1796
 
      kError = ERR_ACCESS_DENIED;
 
1833
      // ERR_ACCESS_DENIED
1797
1834
      errorString = i18nc( "%1: request type", "Access was denied while attempting to %1.",  action );
1798
1835
      break;
1799
1836
    case 409:
1800
1837
      // 409 Conflict
1801
 
      kError = ERR_ACCESS_DENIED;
 
1838
      // ERR_ACCESS_DENIED
1802
1839
      errorString = i18n("A resource cannot be created at the destination "
1803
1840
                  "until one or more intermediate collections (folders) "
1804
1841
                  "have been created.");
1805
1842
      break;
1806
1843
    case 423:
1807
1844
      // 423 Locked
1808
 
      kError = ERR_ACCESS_DENIED;
 
1845
      // ERR_ACCESS_DENIED
1809
1846
      errorString = i18nc( "%1: request type", "Unable to %1 because the resource is locked.",  action );
1810
1847
      break;
1811
1848
    case 502:
1812
1849
      // 502 Bad Gateway
1813
 
      kError = ERR_WRITE_ACCESS_DENIED;
 
1850
      // ERR_WRITE_ACCESS_DENIED;
1814
1851
      errorString = i18nc( "%1: request type", "Unable to %1 because the destination server refuses "
1815
 
                         "to accept the file or folder.",  action );
 
1852
                           "to accept the file or folder.",  action );
1816
1853
      break;
1817
1854
    case 507:
1818
1855
      // 507 Insufficient Storage
1819
 
      kError = ERR_DISK_FULL;
 
1856
      // ERR_DISK_FULL
1820
1857
      errorString = i18n("The destination resource does not have sufficient space "
1821
1858
                         "to record the state of the resource after the execution "
1822
1859
                         "of this method.");
1870
1907
 
1871
1908
    resetSessionSettings();
1872
1909
 
1873
 
    for (unsigned i = 0; i < n; i++) {
 
1910
    for (unsigned i = 0; i < n; ++i) {
1874
1911
        KUrl url;
1875
1912
        stream >> url >> mIncomingMetaData;
1876
1913
 
2035
2072
        }
2036
2073
        size_t bufferFill = readBuffered(mybuf, step);
2037
2074
 
2038
 
        for (size_t i = 0; i < bufferFill ; i++, pos++) {
 
2075
        for (size_t i = 0; i < bufferFill ; ++i, ++pos) {
2039
2076
            // we copy the data from mybuf to buf immediately and look for the newlines in buf.
2040
2077
            // that way we don't miss newlines split over several invocations of this method.
2041
2078
            buf[pos] = mybuf[i];
2390
2427
      kDebug(7113) << "needs validation, performing conditional get.";
2391
2428
      /* conditional get */
2392
2429
      if (!m_request.cacheTag.etag.isEmpty())
2393
 
        header += QLatin1String("If-None-Match: ")+m_request.cacheTag.etag+QLatin1String("\r\n");
 
2430
        header += QLatin1String("If-None-Match: ") + m_request.cacheTag.etag + QLatin1String("\r\n");
2394
2431
 
2395
2432
      if (m_request.cacheTag.lastModifiedDate != -1) {
2396
 
        QString httpDate = formatHttpDate(m_request.cacheTag.lastModifiedDate);
 
2433
        const QString httpDate = formatHttpDate(m_request.cacheTag.lastModifiedDate);
2397
2434
        header += QLatin1String("If-Modified-Since: ") + httpDate + QLatin1String("\r\n");
2398
2435
        setMetaData(QLatin1String("modified"), httpDate);
2399
2436
      }
2408
2445
    header += QLatin1String("\r\n");
2409
2446
 
2410
2447
    if (m_request.allowTransferCompression)
2411
 
      header += QLatin1String("Accept-Encoding: x-gzip, x-deflate, gzip, deflate\r\n");
 
2448
      header += QLatin1String("Accept-Encoding: gzip, deflate, x-gzip, x-deflate\r\n");
2412
2449
 
2413
2450
    if (!m_request.charsets.isEmpty())
2414
2451
      header += QLatin1String("Accept-Charset: ") + m_request.charsets + QLatin1String("\r\n");
2454
2491
      header += QLatin1String("\r\n");
2455
2492
    }
2456
2493
 
 
2494
    // DoNotTrack feature...
 
2495
    if (config()->readEntry("DoNotTrack", false))
 
2496
      header += QLatin1String("DNT: 1\r\n");
 
2497
 
2457
2498
    // Remember that at least one failed (with 401 or 407) request/response
2458
2499
    // roundtrip is necessary for the server to tell us that it requires
2459
 
    // authentication.
2460
 
    // We proactively add authentication headers if we have cached credentials
2461
 
    // to avoid the extra roundtrip where possible.
2462
 
    // (TODO: implement this caching)
 
2500
    // authentication. However, we proactively add authentication headers if when
 
2501
    // we have cached credentials to avoid the extra roundtrip where possible.
2463
2502
    header += authenticationHeader();
2464
2503
 
2465
2504
    if ( m_protocol == "webdav" || m_protocol == "webdavs" )
2553
2592
  // Send the response header if it was requested...
2554
2593
  if (!config()->readEntry("PropagateHttpHeader", false))
2555
2594
      return;
2556
 
  
 
2595
 
2557
2596
  setMetaData(QLatin1String("HTTP-Headers"), m_responseHeaders.join(QString(QLatin1Char('\n'))));
2558
2597
 
2559
2598
  if (forwardImmediately)
2614
2653
        m_mimeType = QLatin1String("audio/mpeg");
2615
2654
    else if (m_mimeType == QLatin1String("audio/microsoft-wave"))
2616
2655
        m_mimeType = QLatin1String("audio/x-wav");
 
2656
    else if (m_mimeType == QLatin1String("image/x-ms-bmp"))
 
2657
        m_mimeType = QLatin1String("image/bmp");
2617
2658
 
2618
2659
    // Crypto ones....
2619
2660
    else if (m_mimeType == QLatin1String("application/pkix-cert") ||
2630
2671
            m_mimeType = QLatin1String("application/x-gzpostscript");
2631
2672
    }
2632
2673
 
 
2674
    // Prefer application/x-xz-compressed-tar over application/x-xz for LMZA compressed
 
2675
    // tar files. Arch Linux AUR servers notoriously send the wrong mimetype for this.
 
2676
    else if(m_mimeType == QLatin1String("application/x-xz")) {
 
2677
        if (m_request.url.path().endsWith(QLatin1String(".tar.xz")) ||
 
2678
            m_request.url.path().endsWith(QLatin1String(".txz"))) {
 
2679
            m_mimeType = QLatin1String("application/x-xz-compressed-tar");
 
2680
        }
 
2681
    }
 
2682
 
2633
2683
    // Some webservers say "text/plain" when they mean "application/x-bzip"
2634
2684
    else if ((m_mimeType == QLatin1String("text/plain")) || (m_mimeType == QLatin1String("application/octet-stream"))) {
2635
 
        QString ext = m_request.url.path().right(4).toUpper();
2636
 
        if (ext == QLatin1String(".BZ2"))
 
2685
        const QString ext = QFileInfo(m_request.url.path()).suffix().toUpper();
 
2686
        if (ext == QLatin1String("BZ2"))
2637
2687
            m_mimeType = QLatin1String("application/x-bzip");
2638
 
        else if (ext == QLatin1String(".PEM"))
 
2688
        else if (ext == QLatin1String("PEM"))
2639
2689
            m_mimeType = QLatin1String("application/x-x509-ca-cert");
2640
 
        else if (ext == QLatin1String(".SWF"))
 
2690
        else if (ext == QLatin1String("SWF"))
2641
2691
            m_mimeType = QLatin1String("application/x-shockwave-flash");
2642
 
        else if (ext == QLatin1String(".PLS"))
 
2692
        else if (ext == QLatin1String("PLS"))
2643
2693
            m_mimeType = QLatin1String("audio/x-scpls");
2644
 
        else if (ext == QLatin1String(".WMV"))
 
2694
        else if (ext == QLatin1String("WMV"))
2645
2695
            m_mimeType = QLatin1String("video/x-ms-wmv");
 
2696
        else if (ext == QLatin1String("WEBM"))
 
2697
            m_mimeType = QLatin1String("video/webm");
2646
2698
    }
2647
2699
    kDebug(7113) << "after fixup" << m_mimeType;
2648
2700
}
2692
2744
    }
2693
2745
}
2694
2746
 
 
2747
//Return true if the term was found, false otherwise. Advance *pos.
 
2748
//If (*pos + strlen(term) >= end) just advance *pos to end and return false.
 
2749
//This means that users should always search for the shortest terms first.
 
2750
static bool consume(const char input[], int *pos, int end, const char *term)
 
2751
{
 
2752
    // note: gcc/g++ is quite good at optimizing away redundant strlen()s
 
2753
    int idx = *pos;
 
2754
    if (idx + (int)strlen(term) >= end) {
 
2755
        *pos = end;
 
2756
        return false;
 
2757
    }
 
2758
    if (strncasecmp(&input[idx], term, strlen(term)) == 0) {
 
2759
        *pos = idx + strlen(term);
 
2760
        return true;
 
2761
    }
 
2762
    return false;
 
2763
}
2695
2764
 
2696
2765
/**
2697
2766
 * This function will read in the return header from the server.  It will
2715
2784
                                    // This is also true if we ask to upgrade and
2716
2785
                                    // the server accepts, since we are now
2717
2786
                                    // committed to doing so
2718
 
    bool canUpgrade = false;        // The server offered an upgrade //### currently not queried
2719
2787
    bool noHeadersFound = false;
2720
2788
 
2721
2789
    m_request.cacheTag.charset.clear();
2778
2846
    kDebug(7103) << QByteArray(buffer, bufPos).trimmed();
2779
2847
 
2780
2848
    HTTP_REV httpRev = HTTP_None;
2781
 
    int headerSize = 0;
2782
 
 
2783
2849
    int idx = 0;
2784
2850
 
2785
2851
    if (idx != bufPos && buffer[idx] == '<') {
2841
2907
 
2842
2908
    // immediately act on most response codes...
2843
2909
 
 
2910
    // Protect users against bogus username intended to fool them into visiting
 
2911
    // sites they had no intention of visiting.
 
2912
    if (isPotentialSpoofingAttack(m_request, config())) {
 
2913
        // kDebug(7113) << "**** POTENTIAL ADDRESS SPOOFING:" << m_request.url;
 
2914
        const int result = messageBox(WarningYesNo,
 
2915
                                      i18nc("@warning: Security check on url "
 
2916
                                            "being accessed", "You are about to "
 
2917
                                            "log in to the site \"%1\" with the "
 
2918
                                            "username \"%2\", but the website "
 
2919
                                            "does not require authentication. "
 
2920
                                            "This may be an attempt to trick you."
 
2921
                                            "<p>Is \"%1\" the site you want to visit?",
 
2922
                                            m_request.url.host(), m_request.url.user()),
 
2923
                                      i18nc("@title:window", "Confirm Website Access"));
 
2924
        if (result == KMessageBox::No) {
 
2925
            error(ERR_USER_CANCELED, m_request.url.url());
 
2926
            return false;
 
2927
        }
 
2928
        setMetaData(QLatin1String("{internal~currenthost}LastSpoofedUserName"), m_request.url.user());
 
2929
    }
 
2930
 
2844
2931
    if (m_request.responseCode != 200 && m_request.responseCode != 304) {
2845
2932
        m_request.cacheTag.ioMode = NoCache;
2846
2933
    }
2963
3050
                        if (auth->realm().isEmpty() && !auth->supportsPathMatching())
2964
3051
                            setMetaData(QLatin1String("{internal~allhosts}proxy-auth-realm"), authinfo.realmValue);
2965
3052
                    }
2966
 
                    cacheAuthentication(authinfo);
 
3053
                    if (authinfo.keepPassword) {
 
3054
                        cacheAuthentication(authinfo);
 
3055
                    }
2967
3056
                    kDebug(7113) << "Caching authentication for" << m_request.url;
2968
3057
                }
2969
3058
                // Update our server connection state which includes www and proxy username and password.
2984
3073
        //     either we have a http response line -> try to parse the header, fail if it doesn't work
2985
3074
        //     or we have garbage -> fail.
2986
3075
        HeaderTokenizer tokenizer(buffer);
2987
 
        headerSize = tokenizer.tokenize(idx, sizeof(buffer));
 
3076
        tokenizer.tokenize(idx, sizeof(buffer));
2988
3077
 
2989
3078
        // Note that not receiving "accept-ranges" means that all bets are off
2990
3079
        // wrt the server supporting ranges.
3037
3126
                mediaAttribute = mediaAttribute.trimmed();
3038
3127
                mediaValue     = mediaValue.trimmed();
3039
3128
 
 
3129
                bool quoted = false;
3040
3130
                if (mediaValue.startsWith(QLatin1Char('"'))) {
 
3131
                    quoted = true;
3041
3132
                    mediaValue.remove(QLatin1Char('"'));
3042
3133
                }
3043
3134
 
3053
3144
                    setMetaData(QLatin1String("charset"), mediaValue);
3054
3145
                } else {
3055
3146
                    setMetaData(QLatin1String("media-") + mediaAttribute, mediaValue);
 
3147
                    if (quoted) {
 
3148
                        setMetaData(QLatin1String("media-") + mediaAttribute + QLatin1String("-kio-quoted"),
 
3149
                                    QLatin1String("true"));
 
3150
                    }
3056
3151
                }
3057
3152
            }
3058
3153
        }
3162
3257
                        upgradeRequired = true;
3163
3258
                    } else if (upgradeRequired) {  // 426
3164
3259
                        // Nothing to do since we did it above already
3165
 
                    } else {
3166
 
                        // Just an offer to upgrade - no need to take it
3167
 
                        canUpgrade = true;
3168
3260
                    }
3169
3261
                }
3170
3262
            }
3518
3610
                                                         prevLineEnd - prevLinePos));
3519
3611
            prevLinePos = nextLinePos;
3520
3612
        }
3521
 
        
 
3613
 
3522
3614
        // IMPORTNAT: Do not remove this line because forwardHttpResponseHeader
3523
 
        // is called below. This line is added to make http response headers are
3524
 
        // available by the time the content mimetype information is transmitted
3525
 
        // to the job. If the line below is removed, the KIO-QNAM integration
3526
 
        // will not work properly when attempting to put ioslaves on hold.
 
3615
        // is called below. This line is here to ensure the response headers are
 
3616
        // available to the client before it receives mimetype information.
 
3617
        // The support for putting ioslaves on hold in the KIO-QNAM integration
 
3618
        // will break if this line is removed.
3527
3619
        setMetaData(QLatin1String("HTTP-Headers"), m_responseHeaders.join(QString(QLatin1Char('\n'))));
3528
3620
    }
3529
3621
 
3530
3622
    // Let the app know about the mime-type iff this is not a redirection and
3531
3623
    // the mime-type string is not empty.
3532
 
    if (!m_isRedirection &&
 
3624
    if (!m_isRedirection && m_request.responseCode != 204 &&
3533
3625
        (!m_mimeType.isEmpty() || m_request.method == HTTP_HEAD) &&
3534
3626
        (m_isLoadingErrorPage || !authRequiresAnotherRoundtrip)) {
3535
3627
        kDebug(7113) << "Emitting mimetype " << m_mimeType;
3536
3628
        mimeType( m_mimeType );
3537
3629
    }
3538
3630
 
3539
 
    // Do not move the function call below before doing any redirection.
3540
 
    // Otherwise it might mess up some sites. See BR# 150904.
3541
 
    // IMPORTANT: Do not remove it either thinking it duplicates what is done
3542
 
    // above. Otherwise, the http response headers will not be available if
3543
 
    // this ioslave is put on hold.    
 
3631
    // IMPORTANT: Do not move the function call below before doing any
 
3632
    // redirection. Otherwise it might mess up some sites, see BR# 150904.
3544
3633
    forwardHttpResponseHeader();
3545
 
    
 
3634
 
3546
3635
    if (m_request.method == HTTP_HEAD)
3547
3636
        return true;
3548
3637
 
3793
3882
    }
3794
3883
}
3795
3884
 
 
3885
bool HTTPProtocol::sendCachedBody()
 
3886
{
 
3887
  infoMessage(i18n("Sending data to %1" ,  m_request.url.host()));
 
3888
 
 
3889
  QByteArray cLength ("Content-Length: ");
 
3890
  cLength += QByteArray::number(m_POSTbuf->size());
 
3891
  cLength += "\r\n\r\n";
 
3892
 
 
3893
  kDebug(7113) << "sending cached data (size=" << m_POSTbuf->size() << ")";
 
3894
 
 
3895
  // Send the content length...
 
3896
  bool sendOk = (write(cLength.data(), cLength.size()) == (ssize_t) cLength.size());
 
3897
  if (!sendOk) {
 
3898
    kDebug( 7113 ) << "Connection broken when sending "
 
3899
                    << "content length: (" << m_request.url.host() << ")";
 
3900
    error( ERR_CONNECTION_BROKEN, m_request.url.host() );
 
3901
    return false;
 
3902
  }
 
3903
 
 
3904
  // Make sure the read head is at the beginning...
 
3905
  m_POSTbuf->reset();
 
3906
 
 
3907
  // Send the data...
 
3908
  while (!m_POSTbuf->atEnd()) {
 
3909
      const QByteArray buffer = m_POSTbuf->read(s_MaxInMemPostBufSize);
 
3910
      sendOk = (write(buffer.data(), buffer.size()) == (ssize_t) buffer.size());
 
3911
      if (!sendOk)  {
 
3912
        kDebug(7113) << "Connection broken when sending message body: ("
 
3913
                      << m_request.url.host() << ")";
 
3914
        error( ERR_CONNECTION_BROKEN, m_request.url.host() );
 
3915
        return false;
 
3916
      }
 
3917
  }
 
3918
 
 
3919
  return true;
 
3920
}
 
3921
 
3796
3922
bool HTTPProtocol::sendBody()
3797
3923
{
3798
 
  infoMessage( i18n( "Requesting data to send" ) );
3799
 
 
3800
 
  int readFromApp = -1;
3801
 
 
3802
 
  // m_POSTbuf will NOT be empty iff authentication was required before posting
3803
 
  // the data OR a re-connect is requested from ::readResponseHeader because the
3804
 
  // connection was lost for some reason.
3805
 
  if (m_POSTbuf.isEmpty())
3806
 
  {
3807
 
    kDebug(7113) << "POST'ing live data...";
3808
 
 
3809
 
    QByteArray buffer;
3810
 
 
3811
 
    do {
3812
 
      m_POSTbuf.append(buffer);
3813
 
      buffer.clear();
3814
 
      dataReq(); // Request for data
3815
 
      readFromApp = readData(buffer);
3816
 
    } while (readFromApp > 0);
3817
 
  }
3818
 
  else
3819
 
  {
3820
 
    kDebug(7113) << "POST'ing saved data...";
3821
 
    readFromApp = 0;
3822
 
  }
3823
 
 
3824
 
  if (readFromApp < 0)
3825
 
  {
3826
 
    error(ERR_ABORTED, m_request.url.host());
 
3924
  // If we have cached data, the it is either a repost or a DAV request so send
 
3925
  // the cached data...
 
3926
  if (m_POSTbuf)
 
3927
    return sendCachedBody();
 
3928
 
 
3929
  if (m_iPostDataSize == NO_SIZE) {
 
3930
    // Try the old approach of retireving content data from the job
 
3931
    // before giving up.
 
3932
    if (retrieveAllData())
 
3933
      return sendCachedBody();
 
3934
 
 
3935
    error(ERR_POST_NO_SIZE, m_request.url.host());
3827
3936
    return false;
3828
3937
  }
3829
3938
 
3830
 
  infoMessage(i18n("Sending data to %1" ,  m_request.url.host()));
3831
 
 
3832
 
  const QByteArray cLength = QByteArray("Content-Length: ") + QByteArray::number(m_POSTbuf.size()) + "\r\n\r\n";
 
3939
  kDebug(7113) << "sending data (size=" << m_iPostDataSize << ")";
 
3940
 
 
3941
  infoMessage(i18n("Sending data to %1",  m_request.url.host()));
 
3942
 
 
3943
  QByteArray cLength ("Content-Length: ");
 
3944
  cLength += QByteArray::number(m_iPostDataSize);
 
3945
  cLength += "\r\n\r\n";
 
3946
 
3833
3947
  kDebug(7113) << cLength.trimmed();
3834
3948
 
3835
3949
  // Send the content length...
3836
 
  bool sendOk = (write(cLength.constData(), cLength.size()) == (ssize_t) cLength.size());
 
3950
  bool sendOk = (write(cLength.data(), cLength.size()) == (ssize_t) cLength.size());
3837
3951
  if (!sendOk) {
3838
3952
    // The server might have closed the connection due to a timeout, or maybe
3839
3953
    // some transport problem arose while the connection was idle.
3848
3962
    return false;
3849
3963
  }
3850
3964
 
3851
 
  // Send the data...
3852
 
  //kDebug(7113) << "POST DATA:" << QString::fromLocal8Bit(m_POSTbuf);
3853
 
  sendOk = (write(m_POSTbuf.data(), m_POSTbuf.size()) == (ssize_t) m_POSTbuf.size());
3854
 
  if (!sendOk)
3855
 
  {
3856
 
    kDebug(7113) << "Connection broken when sending message body: ("
3857
 
                  << m_request.url.host() << ")";
3858
 
    error( ERR_CONNECTION_BROKEN, m_request.url.host() );
3859
 
    return false;
 
3965
  // Send the amount
 
3966
  totalSize(m_iPostDataSize);
 
3967
 
 
3968
  sendOk = true;
 
3969
  KIO::filesize_t bytesSent = 0;
 
3970
 
 
3971
  while (true) {
 
3972
    dataReq();
 
3973
 
 
3974
    QByteArray buffer;
 
3975
    const int bytesRead = readData(buffer);
 
3976
 
 
3977
    // On done...
 
3978
    if (bytesRead == 0) {
 
3979
      sendOk = (bytesSent == m_iPostDataSize);
 
3980
      break;
 
3981
    }
 
3982
 
 
3983
    // On error return false...
 
3984
    if (bytesRead < 0) {
 
3985
      error(ERR_ABORTED, m_request.url.host());
 
3986
      sendOk = false;
 
3987
      break;
 
3988
    }
 
3989
 
 
3990
    // Cache the POST data in case of a repost request.
 
3991
    cachePostData(buffer);
 
3992
 
 
3993
    // This will only happen if transmitting the data fails, so we will simply
 
3994
    // cache the content locally for the potential re-transmit...
 
3995
    if (!sendOk)
 
3996
      continue;
 
3997
 
 
3998
    if (write(buffer.data(), bytesRead) == static_cast<ssize_t>(bytesRead)) {
 
3999
      bytesSent += bytesRead;
 
4000
      processedSize(bytesSent);  // Send update status...
 
4001
      continue;
 
4002
    }
 
4003
 
 
4004
    kDebug(7113) << "Connection broken while sending POST content to" << m_request.url.host();
 
4005
    error(ERR_CONNECTION_BROKEN, m_request.url.host());
 
4006
    sendOk = false;
3860
4007
  }
3861
4008
 
3862
 
  return true;
 
4009
  return sendOk;
3863
4010
}
3864
4011
 
3865
4012
void HTTPProtocol::httpClose( bool keepAlive )
3946
4093
    case 1: // HTTP POST
3947
4094
    {
3948
4095
      KUrl url;
3949
 
      stream >> url;
3950
 
      post( url );
 
4096
      qint64 size;
 
4097
      stream >> url >> size;
 
4098
      post( url, size );
3951
4099
      break;
3952
4100
    }
3953
4101
    case 2: // cache_update
3997
4145
    {
3998
4146
      KUrl url;
3999
4147
      int method;
4000
 
      stream >> url >> method;
4001
 
      davGeneric( url, (KIO::HTTP_METHOD) method );
 
4148
      qint64 size;
 
4149
      stream >> url >> method >> size;
 
4150
      davGeneric( url, (KIO::HTTP_METHOD) method, size );
4002
4151
      break;
4003
4152
    }
4004
4153
    case 99: // Close Connection
4254
4403
              infoMessage(i18n("Retrieving %1 from %2...", KIO::convertSize(m_iSize),
4255
4404
                          m_request.url.host()));
4256
4405
          } else {
4257
 
              totalSize (0);
 
4406
              totalSize(0);
4258
4407
          }
4259
4408
      } else {
4260
4409
          infoMessage(i18n("Retrieving from %1...",  m_request.url.host()));
4468
4617
  }
4469
4618
 
4470
4619
  // It's over, we don't need it anymore
4471
 
  m_POSTbuf.clear();
 
4620
  clearPostDataBuffer();
4472
4621
 
4473
4622
  SlaveBase::error( _err, _text );
4474
4623
  m_isError = true;
4502
4651
HTTPProtocol::CacheTag::CachePlan HTTPProtocol::CacheTag::plan(time_t maxCacheAge) const
4503
4652
{
4504
4653
    //notable omission: we're not checking cache file presence or integrity
4505
 
    if (policy == KIO::CC_CacheOnly || policy == KIO::CC_Cache) {
 
4654
    switch (policy) {
 
4655
    case KIO::CC_Refresh:
 
4656
        // Conditional GET requires the presence of either an ETag or
 
4657
        // last modified date.
 
4658
        if (lastModifiedDate != -1 || !etag.isEmpty()) {
 
4659
            return ValidateCached;
 
4660
        }
 
4661
        break;
 
4662
    case KIO::CC_Reload:
 
4663
        return IgnoreCached;
 
4664
    case KIO::CC_CacheOnly:
 
4665
    case KIO::CC_Cache:
4506
4666
        return UseCached;
4507
 
    } else if (policy == KIO::CC_Refresh) {
4508
 
        return ValidateCached;
4509
 
    } else if (policy == KIO::CC_Reload) {
4510
 
        return IgnoreCached;
 
4667
    default:
 
4668
        break;
4511
4669
    }
4512
 
    Q_ASSERT(policy == CC_Verify);
 
4670
 
 
4671
    Q_ASSERT((policy == CC_Verify || policy == CC_Refresh));
4513
4672
    time_t currentDate = time(0);
4514
 
    if ((servedDate != -1 && currentDate > servedDate + maxCacheAge) ||
 
4673
    if ((servedDate != -1 && currentDate > (servedDate + maxCacheAge)) ||
4515
4674
        (expireDate != -1 && currentDate > expireDate)) {
4516
4675
        return ValidateCached;
4517
4676
    }
4671
4830
 
4672
4831
static bool readLineChecked(QIODevice *dev, QByteArray *line)
4673
4832
{
4674
 
    *line = dev->readLine(8192);
 
4833
    *line = dev->readLine(MAX_IPC_SIZE);
4675
4834
    // if nothing read or the line didn't fit into 8192 bytes(!)
4676
4835
    if (line->isEmpty() || !line->endsWith('\n')) {
4677
4836
        return false;
4945
5104
    // If the file being downloaded is so big that it exceeds the max cache size,
4946
5105
    // do not cache it! See BR# 244215. NOTE: this can be improved upon in the
4947
5106
    // future...
4948
 
    if (m_iSize >= (m_maxCacheSize * 1024)) {
4949
 
        kDebug(7113) << "Caching diabled because content size is too big.";
 
5107
    if (m_iSize >= KIO::filesize_t(m_maxCacheSize * 1024)) {
 
5108
        kDebug(7113) << "Caching disabled because content size is too big.";
4950
5109
        cacheFileClose();
4951
5110
        return;
4952
5111
    }
4968
5127
    m_request.cacheTag.file->write(d);
4969
5128
}
4970
5129
 
 
5130
void HTTPProtocol::cachePostData(const QByteArray& data)
 
5131
{
 
5132
    if (!m_POSTbuf) {
 
5133
        m_POSTbuf = createPostBufferDeviceFor(qMax(m_iPostDataSize, static_cast<KIO::filesize_t>(data.size())));
 
5134
        if (!m_POSTbuf)
 
5135
            return;
 
5136
    }
 
5137
 
 
5138
    m_POSTbuf->write (data.constData(), data.size());
 
5139
}
 
5140
 
 
5141
void HTTPProtocol::clearPostDataBuffer()
 
5142
{
 
5143
    if (!m_POSTbuf)
 
5144
        return;
 
5145
 
 
5146
    delete m_POSTbuf;
 
5147
    m_POSTbuf = 0;
 
5148
}
 
5149
 
 
5150
bool HTTPProtocol::retrieveAllData()
 
5151
{
 
5152
    if (!m_POSTbuf) {
 
5153
        m_POSTbuf = createPostBufferDeviceFor(s_MaxInMemPostBufSize + 1);
 
5154
    }
 
5155
 
 
5156
    if (!m_POSTbuf) {
 
5157
        error (ERR_OUT_OF_MEMORY, m_request.url.host());
 
5158
        return false;
 
5159
    }
 
5160
 
 
5161
    while (true) {
 
5162
        dataReq();
 
5163
        QByteArray buffer;
 
5164
        const int bytesRead = readData(buffer);
 
5165
 
 
5166
        if (bytesRead < 0) {
 
5167
            error(ERR_ABORTED, m_request.url.host());
 
5168
            return false;
 
5169
        }
 
5170
 
 
5171
        if (bytesRead == 0) {
 
5172
            break;
 
5173
        }
 
5174
 
 
5175
        m_POSTbuf->write(buffer.constData(), buffer.size());
 
5176
    }
 
5177
 
 
5178
    return true;
 
5179
}
 
5180
 
4971
5181
// The above code should be kept in sync
4972
5182
// with the code in http_cache_cleaner.cpp
4973
5183
// !END SYNC!
4988
5198
        // If no relam metadata, then make sure path matching is turned on.
4989
5199
        authinfo.verifyPath = (authinfo.realmValue.isEmpty());
4990
5200
 
4991
 
        if (checkCachedAuthentication(authinfo)) {
 
5201
        const bool useCachedAuth = (m_request.responseCode == 401 || !(config()->readEntry("no-preemptive-auth-reuse", false)));
 
5202
        if (useCachedAuth && checkCachedAuthentication(authinfo)) {
4992
5203
            const QByteArray cachedChallenge = authinfo.digestInfo.toLatin1();
4993
5204
            if (!cachedChallenge.isEmpty()) {
4994
5205
                m_wwwAuth = KAbstractHttpAuthentication::newAuth(cachedChallenge, config());