You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdelibs/kioslave/http/http.cc

6109 lines
175 KiB

/*
Copyright (C) 2000-2003 Waldo Bastian <bastian@kde.org>
Copyright (C) 2000-2002 George Staikos <staikos@kde.org>
Copyright (C) 2000-2002 Dawit Alemayehu <adawit@kde.org>
Copyright (C) 2001,2002 Hamish Rodda <rodda@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License (LGPL) as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later
version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <config.h>
#include <errno.h>
#include <fcntl.h>
#include <utime.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h> // Required for AIX
#include <netinet/tcp.h>
#include <unistd.h> // must be explicitly included for MacOSX
/*
#include <netdb.h>
#include <sys/time.h>
#include <sys/wait.h>
*/
#include <tqdom.h>
#include <tqfile.h>
#include <tqregexp.h>
#include <tqdatetime.h>
#include <tqstringlist.h>
#include <tqurl.h>
#include <kurl.h>
#include <kidna.h>
#include <ksocks.h>
#include <kdebug.h>
#include <klocale.h>
#include <kconfig.h>
#include <kextsock.h>
#include <kservice.h>
#include <krfcdate.h>
#include <kmdcodec.h>
#include <kinstance.h>
#include <kresolver.h>
#include <kmimemagic.h>
#include <dcopclient.h>
#include <kdatastream.h>
#include <kapplication.h>
#include <kstandarddirs.h>
#include <kstringhandler.h>
#include <kremoteencoding.h>
#include "kio/ioslave_defaults.h"
#include "kio/http_slave_defaults.h"
#include "httpfilter.h"
#include "http.h"
#ifdef HAVE_LIBGSSAPI
#ifdef GSSAPI_MIT
#include <gssapi/gssapi.h>
#else
#include <gssapi.h>
#endif /* GSSAPI_MIT */
// Catch uncompatible crap (BR86019)
#if defined(GSS_RFC_COMPLIANT_OIDS) && (GSS_RFC_COMPLIANT_OIDS == 0)
#include <gssapi/gssapi_generic.h>
#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
#endif
#endif /* HAVE_LIBGSSAPI */
#include <misc/kntlm/kntlm.h>
using namespace KIO;
extern "C" {
KDE_EXPORT int kdemain(int argc, char **argv);
}
int kdemain( int argc, char **argv )
{
KLocale::setMainCatalogue("tdelibs");
KInstance instance( "kio_http" );
( void ) KGlobal::locale();
if (argc != 4)
{
fprintf(stderr, "Usage: kio_http protocol domain-socket1 domain-socket2\n");
exit(-1);
}
HTTPProtocol slave(argv[1], argv[2], argv[3]);
slave.dispatchLoop();
return 0;
}
/*********************************** Generic utility functions ********************/
static char * trimLead (char *orig_string)
{
while (*orig_string == ' ')
orig_string++;
return orig_string;
}
static bool isCrossDomainRequest( const TQString& fqdn, const TQString& originURL )
{
if (originURL == "true") // Backwards compatibility
return true;
KURL url ( originURL );
// Document Origin domain
TQString a = url.host();
// Current request domain
TQString b = fqdn;
if (a == b)
return false;
TQStringList l1 = TQStringList::split('.', a);
TQStringList l2 = TQStringList::split('.', b);
while(l1.count() > l2.count())
l1.pop_front();
while(l2.count() > l1.count())
l2.pop_front();
while(l2.count() >= 2)
{
if (l1 == l2)
return false;
l1.pop_front();
l2.pop_front();
}
return true;
}
/*
Eliminates any custom header that could potentically alter the request
*/
static TQString sanitizeCustomHTTPHeader(const TQString& _header)
{
TQString sanitizedHeaders;
TQStringList headers = TQStringList::split(TQRegExp("[\r\n]"), _header);
for(TQStringList::Iterator it = headers.begin(); it != headers.end(); ++it)
{
TQString header = (*it).lower();
// Do not allow Request line to be specified and ignore
// the other HTTP headers.
if (header.find(':') == -1 ||
header.startsWith("host") ||
header.startsWith("via"))
continue;
sanitizedHeaders += (*it);
sanitizedHeaders += "\r\n";
}
return sanitizedHeaders.stripWhiteSpace();
}
#define NO_SIZE ((KIO::filesize_t) -1)
#ifdef HAVE_STRTOLL
#define STRTOLL strtoll
#else
#define STRTOLL strtol
#endif
/************************************** HTTPProtocol **********************************************/
HTTPProtocol::HTTPProtocol( const TQCString &protocol, const TQCString &pool,
const TQCString &app )
:TCPSlaveBase( 0, protocol , pool, app,
(protocol == "https" || protocol == "webdavs") )
{
m_requestQueue.setAutoDelete(true);
m_bBusy = false;
m_bFirstRequest = false;
m_bProxyAuthValid = false;
m_iSize = NO_SIZE;
m_lineBufUnget = 0;
m_protocol = protocol;
m_maxCacheAge = DEFAULT_MAX_CACHE_AGE;
m_maxCacheSize = DEFAULT_MAX_CACHE_SIZE / 2;
m_remoteConnTimeout = DEFAULT_CONNECT_TIMEOUT;
m_remoteRespTimeout = DEFAULT_RESPONSE_TIMEOUT;
m_proxyConnTimeout = DEFAULT_PROXY_CONNECT_TIMEOUT;
m_pid = getpid();
setMultipleAuthCaching( true );
reparseConfiguration();
}
HTTPProtocol::~HTTPProtocol()
{
httpClose(false);
}
void HTTPProtocol::reparseConfiguration()
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::reparseConfiguration" << endl;
m_strProxyRealm = TQString::null;
m_strProxyAuthorization = TQString::null;
ProxyAuthentication = AUTH_None;
m_bUseProxy = false;
if (m_protocol == "https" || m_protocol == "webdavs")
m_iDefaultPort = DEFAULT_HTTPS_PORT;
else if (m_protocol == "ftp")
m_iDefaultPort = DEFAULT_FTP_PORT;
else
m_iDefaultPort = DEFAULT_HTTP_PORT;
}
void HTTPProtocol::resetConnectionSettings()
{
m_bEOF = false;
m_bError = false;
m_lineCount = 0;
m_iWWWAuthCount = 0;
m_lineCountUnget = 0;
m_iProxyAuthCount = 0;
}
void HTTPProtocol::resetResponseSettings()
{
m_bRedirect = false;
m_redirectLocation = KURL();
m_bChunked = false;
m_iSize = NO_SIZE;
m_responseHeader.clear();
m_qContentEncodings.clear();
m_qTransferEncodings.clear();
m_sContentMD5 = TQString::null;
m_strMimeType = TQString::null;
setMetaData("request-id", m_request.id);
}
void HTTPProtocol::resetSessionSettings()
{
// Do not reset the URL on redirection if the proxy
// URL, username or password has not changed!
KURL proxy ( config()->readEntry("UseProxy") );
if ( m_strProxyRealm.isEmpty() || !proxy.isValid() ||
m_proxyURL.host() != proxy.host() ||
(!proxy.user().isNull() && proxy.user() != m_proxyURL.user()) ||
(!proxy.pass().isNull() && proxy.pass() != m_proxyURL.pass()) )
{
m_bProxyAuthValid = false;
m_proxyURL = proxy;
m_bUseProxy = m_proxyURL.isValid();
kdDebug(7113) << "(" << m_pid << ") Using proxy: " << m_bUseProxy <<
" URL: " << m_proxyURL.url() <<
" Realm: " << m_strProxyRealm << endl;
}
m_bPersistentProxyConnection = config()->readBoolEntry("PersistentProxyConnection", false);
kdDebug(7113) << "(" << m_pid << ") Enable Persistent Proxy Connection: "
<< m_bPersistentProxyConnection << endl;
m_request.bUseCookiejar = config()->readBoolEntry("Cookies");
m_request.bUseCache = config()->readBoolEntry("UseCache", true);
m_request.bErrorPage = config()->readBoolEntry("errorPage", true);
m_request.bNoAuth = config()->readBoolEntry("no-auth");
m_strCacheDir = config()->readPathEntry("CacheDir");
m_maxCacheAge = config()->readNumEntry("MaxCacheAge", DEFAULT_MAX_CACHE_AGE);
m_request.window = config()->readEntry("window-id");
kdDebug(7113) << "(" << m_pid << ") Window Id = " << m_request.window << endl;
kdDebug(7113) << "(" << m_pid << ") ssl_was_in_use = "
<< metaData ("ssl_was_in_use") << endl;
m_request.referrer = TQString::null;
if ( config()->readBoolEntry("SendReferrer", true) &&
(m_protocol == "https" || m_protocol == "webdavs" ||
metaData ("ssl_was_in_use") != "TRUE" ) )
{
KURL referrerURL ( metaData("referrer") );
if (referrerURL.isValid())
{
// Sanitize
TQString protocol = referrerURL.protocol();
if (protocol.startsWith("webdav"))
{
protocol.replace(0, 6, "http");
referrerURL.setProtocol(protocol);
}
if (protocol.startsWith("http"))
{
referrerURL.setRef(TQString::null);
referrerURL.setUser(TQString::null);
referrerURL.setPass(TQString::null);
m_request.referrer = referrerURL.url();
}
}
}
if ( config()->readBoolEntry("SendLanguageSettings", true) )
{
m_request.charsets = config()->readEntry( "Charsets", "iso-8859-1" );
if ( !m_request.charsets.isEmpty() )
m_request.charsets += DEFAULT_PARTIAL_CHARSET_HEADER;
m_request.languages = config()->readEntry( "Languages", DEFAULT_LANGUAGE_HEADER );
}
else
{
m_request.charsets = TQString::null;
m_request.languages = TQString::null;
}
// Adjust the offset value based on the "resume" meta-data.
TQString resumeOffset = metaData("resume");
if ( !resumeOffset.isEmpty() )
m_request.offset = resumeOffset.toInt(); // TODO: Convert to 64 bit
else
m_request.offset = 0;
m_request.disablePassDlg = config()->readBoolEntry("DisablePassDlg", false);
m_request.allowCompressedPage = config()->readBoolEntry("AllowCompressedPage", true);
m_request.id = metaData("request-id");
// Store user agent for this host.
if ( config()->readBoolEntry("SendUserAgent", true) )
m_request.userAgent = metaData("UserAgent");
else
m_request.userAgent = TQString::null;
// Deal with cache cleaning.
// TODO: Find a smarter way to deal with cleaning the
// cache ?
if ( m_request.bUseCache )
cleanCache();
// Deal with HTTP tunneling
if ( m_bIsSSL && m_bUseProxy && m_proxyURL.protocol() != "https" &&
m_proxyURL.protocol() != "webdavs")
{
m_bNeedTunnel = true;
setRealHost( m_request.hostname );
kdDebug(7113) << "(" << m_pid << ") SSL tunnel: Setting real hostname to: "
<< m_request.hostname << endl;
}
else
{
m_bNeedTunnel = false;
setRealHost( TQString::null);
}
m_responseCode = 0;
m_prevResponseCode = 0;
m_strRealm = TQString::null;
m_strAuthorization = TQString::null;
Authentication = AUTH_None;
// Obtain the proxy and remote server timeout values
m_proxyConnTimeout = proxyConnectTimeout();
m_remoteConnTimeout = connectTimeout();
m_remoteRespTimeout = responseTimeout();
// Set the SSL meta-data here...
setSSLMetaData();
// Bounce back the actual referrer sent
setMetaData("referrer", m_request.referrer);
// Follow HTTP/1.1 spec and enable keep-alive by default
// unless the remote side tells us otherwise or we determine
// the persistent link has been terminated by the remote end.
m_bKeepAlive = true;
m_keepAliveTimeout = 0;
m_bUnauthorized = false;
// A single request can require multiple exchanges with the remote
// server due to authentication challenges or SSL tunneling.
// m_bFirstRequest is a flag that indicates whether we are
// still processing the first request. This is important because we
// should not force a close of a keep-alive connection in the middle
// of the first request.
// m_bFirstRequest is set to "true" whenever a new connection is
// made in httpOpenConnection()
m_bFirstRequest = false;
}
void HTTPProtocol::setHost( const TQString& host, int port,
const TQString& user, const TQString& pass )
{
// Reset the webdav-capable flags for this host
if ( m_request.hostname != host )
m_davHostOk = m_davHostUnsupported = false;
// is it an IPv6 address?
if (host.find(':') == -1)
{
m_request.hostname = host;
m_request.encoded_hostname = KIDNA::toAscii(host);
}
else
{
m_request.hostname = host;
int pos = host.find('%');
if (pos == -1)
m_request.encoded_hostname = '[' + host + ']';
else
// don't send the scope-id in IPv6 addresses to the server
m_request.encoded_hostname = '[' + host.left(pos) + ']';
}
m_request.port = (port == 0) ? m_iDefaultPort : port;
m_request.user = user;
m_request.passwd = pass;
m_bIsTunneled = false;
kdDebug(7113) << "(" << m_pid << ") Hostname is now: " << m_request.hostname <<
" (" << m_request.encoded_hostname << ")" <<endl;
}
bool HTTPProtocol::checkRequestURL( const KURL& u )
{
kdDebug (7113) << "(" << m_pid << ") HTTPProtocol::checkRequestURL: " << u.url() << endl;
m_request.url = u;
if (m_request.hostname.isEmpty())
{
error( KIO::ERR_UNKNOWN_HOST, i18n("No host specified."));
return false;
}
if (u.path().isEmpty())
{
KURL newUrl(u);
newUrl.setPath("/");
redirection(newUrl);
finished();
return false;
}
if ( m_protocol != u.protocol().latin1() )
{
short unsigned int oldDefaultPort = m_iDefaultPort;
m_protocol = u.protocol().latin1();
reparseConfiguration();
if ( m_iDefaultPort != oldDefaultPort &&
m_request.port == oldDefaultPort )
m_request.port = m_iDefaultPort;
}
resetSessionSettings();
return true;
}
void HTTPProtocol::retrieveContent( bool dataInternal /* = false */ )
{
kdDebug (7113) << "(" << m_pid << ") HTTPProtocol::retrieveContent " << endl;
if ( !retrieveHeader( false ) )
{
if ( m_bError )
return;
}
else
{
if ( !readBody( dataInternal ) && m_bError )
return;
}
httpClose(m_bKeepAlive);
// if data is required internally, don't finish,
// it is processed before we finish()
if ( !dataInternal )
{
if ((m_responseCode == 204) &&
((m_request.method == HTTP_GET) || (m_request.method == HTTP_POST)))
error(ERR_NO_CONTENT, "");
else
finished();
}
}
bool HTTPProtocol::retrieveHeader( bool close_connection )
{
kdDebug (7113) << "(" << m_pid << ") HTTPProtocol::retrieveHeader " << endl;
while ( 1 )
{
if (!httpOpen())
return false;
resetResponseSettings();
if (!readHeader())
{
if ( m_bError )
return false;
if (m_bIsTunneled)
{
kdDebug(7113) << "(" << m_pid << ") Re-establishing SSL tunnel..." << endl;
httpCloseConnection();
}
}
else
{
// Do not save authorization if the current response code is
// 4xx (client error) or 5xx (server error).
kdDebug(7113) << "(" << m_pid << ") Previous Response: "
<< m_prevResponseCode << endl;
kdDebug(7113) << "(" << m_pid << ") Current Response: "
<< m_responseCode << endl;
if (isSSLTunnelEnabled() && m_bIsSSL && !m_bUnauthorized && !m_bError)
{
// If there is no error, disable tunneling
if ( m_responseCode < 400 )
{
kdDebug(7113) << "(" << m_pid << ") Unset tunneling flag!" << endl;
setEnableSSLTunnel( false );
m_bIsTunneled = true;
// Reset the CONNECT response code...
m_responseCode = m_prevResponseCode;
continue;
}
else
{
if ( !m_request.bErrorPage )
{
kdDebug(7113) << "(" << m_pid << ") Sending an error message!" << endl;
error( ERR_UNKNOWN_PROXY_HOST, m_proxyURL.host() );
return false;
}
kdDebug(7113) << "(" << m_pid << ") Sending an error page!" << endl;
}
}
if (m_responseCode < 400 && (m_prevResponseCode == 401 ||
m_prevResponseCode == 407))
saveAuthorization();
break;
}
}
// Clear of the temporary POST buffer if it is not empty...
if (!m_bufPOST.isEmpty())
{
m_bufPOST.resize(0);
kdDebug(7113) << "(" << m_pid << ") HTTP::retreiveHeader: Cleared POST "
"buffer..." << endl;
}
if ( close_connection )
{
httpClose(m_bKeepAlive);
finished();
}
return true;
}
void HTTPProtocol::stat(const KURL& url)
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::stat " << url.prettyURL()
<< endl;
if ( !checkRequestURL( url ) )
return;
if ( m_protocol != "webdav" && m_protocol != "webdavs" )
{
TQString statSide = metaData(TQString::fromLatin1("statSide"));
if ( statSide != "source" )
{
// When uploading we assume the file doesn't exit
error( ERR_DOES_NOT_EXIST, url.prettyURL() );
return;
}
// When downloading we assume it exists
UDSEntry entry;
UDSAtom atom;
atom.m_uds = KIO::UDS_NAME;
atom.m_str = url.fileName();
entry.append( atom );
atom.m_uds = KIO::UDS_FILE_TYPE;
atom.m_long = S_IFREG; // a file
entry.append( atom );
atom.m_uds = KIO::UDS_ACCESS;
atom.m_long = S_IRUSR | S_IRGRP | S_IROTH; // readable by everybody
entry.append( atom );
statEntry( entry );
finished();
return;
}
davStatList( url );
}
void HTTPProtocol::listDir( const KURL& url )
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::listDir " << url.url()
<< endl;
if ( !checkRequestURL( url ) )
return;
if (!url.protocol().startsWith("webdav")) {
error(ERR_UNSUPPORTED_ACTION, url.prettyURL());
return;
}
davStatList( url, false );
}
void HTTPProtocol::davSetRequest( const TQCString& requestXML )
{
// insert the document into the POST buffer, kill trailing zero byte
m_bufPOST = requestXML;
if (m_bufPOST.size())
m_bufPOST.truncate( m_bufPOST.size() - 1 );
}
void HTTPProtocol::davStatList( const KURL& url, bool stat )
{
UDSEntry entry;
UDSAtom atom;
// check to make sure this host supports WebDAV
if ( !davHostOk() )
return;
// Maybe it's a disguised SEARCH...
TQString query = metaData("davSearchQuery");
if ( !query.isEmpty() )
{
TQCString request = "<?xml version=\"1.0\"?>\r\n";
request.append( "<D:searchrequest xmlns:D=\"DAV:\">\r\n" );
request.append( query.utf8() );
request.append( "</D:searchrequest>\r\n" );
davSetRequest( request );
} else {
// We are only after certain features...
TQCString request;
request = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
"<D:propfind xmlns:D=\"DAV:\">";
// insert additional XML request from the davRequestResponse metadata
if ( hasMetaData( "davRequestResponse" ) )
request += metaData( "davRequestResponse" ).utf8();
else {
// No special request, ask for default properties
request += "<D:prop>"
"<D:creationdate/>"
"<D:getcontentlength/>"
"<D:displayname/>"
"<D:source/>"
"<D:getcontentlanguage/>"
"<D:getcontenttype/>"
"<D:executable/>"
"<D:getlastmodified/>"
"<D:getetag/>"
"<D:supportedlock/>"
"<D:lockdiscovery/>"
"<D:resourcetype/>"
"</D:prop>";
}
request += "</D:propfind>";
davSetRequest( request );
}
// WebDAV Stat or List...
m_request.method = query.isEmpty() ? DAV_PROPFIND : DAV_SEARCH;
m_request.query = TQString::null;
m_request.cache = CC_Reload;
m_request.doProxy = m_bUseProxy;
m_request.davData.depth = stat ? 0 : 1;
if (!stat)
m_request.url.adjustPath(+1);
retrieveContent( true );
// Has a redirection already been called? If so, we're done.
if (m_bRedirect) {
finished();
return;
}
TQDomDocument multiResponse;
multiResponse.setContent( m_bufWebDavData, true );
bool hasResponse = false;
for ( TQDomNode n = multiResponse.documentElement().firstChild();
!n.isNull(); n = n.nextSibling())
{
TQDomElement thisResponse = n.toElement();
if (thisResponse.isNull())
continue;
hasResponse = true;
TQDomElement href = thisResponse.namedItem( "href" ).toElement();
if ( !href.isNull() )
{
entry.clear();
TQString urlStr = href.text();
#if 0
int encoding = remoteEncoding()->encodingMib();
if ((encoding == 106) && (!KStringHandler::isUtf8(KURL::decode_string(urlStr, 4).latin1())))
encoding = 4; // Use latin1 if the file is not actually utf-8
#else
TQUrl::decode(urlStr);
int encoding = 106;
#endif
KURL thisURL ( urlStr, encoding );
atom.m_uds = KIO::UDS_NAME;
if ( thisURL.isValid() ) {
// don't list the base dir of a listDir()
if ( !stat && thisURL.path(+1).length() == url.path(+1).length() )
continue;
atom.m_str = thisURL.fileName();
} else {
// This is a relative URL.
atom.m_str = href.text();
}
entry.append( atom );
TQDomNodeList propstats = thisResponse.elementsByTagName( "propstat" );
davParsePropstats( propstats, entry );
if ( stat )
{
// return an item
statEntry( entry );
finished();
return;
}
else
{
listEntry( entry, false );
}
}
else
{
kdDebug(7113) << "Error: no URL contained in response to PROPFIND on "
<< url.prettyURL() << endl;
}
}
if ( stat || !hasResponse )
{
error( ERR_DOES_NOT_EXIST, url.prettyURL() );
}
else
{
listEntry( entry, true );
finished();
}
}
void HTTPProtocol::davGeneric( const KURL& url, KIO::HTTP_METHOD method )
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::davGeneric " << url.url()
<< endl;
if ( !checkRequestURL( url ) )
return;
// check to make sure this host supports WebDAV
if ( !davHostOk() )
return;
// WebDAV method
m_request.method = method;
m_request.query = TQString::null;
m_request.cache = CC_Reload;
m_request.doProxy = m_bUseProxy;
retrieveContent( false );
}
int HTTPProtocol::codeFromResponse( const TQString& response )
{
int firstSpace = response.find( ' ' );
int secondSpace = response.find( ' ', firstSpace + 1 );
return response.mid( firstSpace + 1, secondSpace - firstSpace - 1 ).toInt();
}
void HTTPProtocol::davParsePropstats( const TQDomNodeList& propstats, UDSEntry& entry )
{
TQString mimeType;
UDSAtom atom;
bool foundExecutable = false;
bool isDirectory = false;
uint lockCount = 0;
uint supportedLockCount = 0;
for ( uint i = 0; i < propstats.count(); i++)
{
TQDomElement propstat = propstats.item(i).toElement();
TQDomElement status = propstat.namedItem( "status" ).toElement();
if ( status.isNull() )
{
// error, no status code in this propstat
kdDebug(7113) << "Error, no status code in this propstat" << endl;
return;
}
int code = codeFromResponse( status.text() );
if ( code != 200 )
{
kdDebug(7113) << "Warning: status code " << code << " (this may mean that some properties are unavailable" << endl;
continue;
}
TQDomElement prop = propstat.namedItem( "prop" ).toElement();
if ( prop.isNull() )
{
kdDebug(7113) << "Error: no prop segment in this propstat." << endl;
return;
}
if ( hasMetaData( "davRequestResponse" ) )
{
atom.m_uds = KIO::UDS_XML_PROPERTIES;
TQDomDocument doc;
doc.appendChild(prop);
atom.m_str = doc.toString();
entry.append( atom );
}
for ( TQDomNode n = prop.firstChild(); !n.isNull(); n = n.nextSibling() )
{
TQDomElement property = n.toElement();
if (property.isNull())
continue;
if ( property.namespaceURI() != "DAV:" )
{
// break out - we're only interested in properties from the DAV namespace
continue;
}
if ( property.tagName() == "creationdate" )
{
// Resource creation date. Should be is ISO 8601 format.
atom.m_uds = KIO::UDS_CREATION_TIME;
atom.m_long = parseDateTime( property.text(), property.attribute("dt") );
entry.append( atom );
}
else if ( property.tagName() == "getcontentlength" )
{
// Content length (file size)
atom.m_uds = KIO::UDS_SIZE;
atom.m_long = property.text().toULong();
entry.append( atom );
}
else if ( property.tagName() == "displayname" )
{
// Name suitable for presentation to the user
setMetaData( "davDisplayName", property.text() );
}
else if ( property.tagName() == "source" )
{
// Source template location
TQDomElement source = property.namedItem( "link" ).toElement()
.namedItem( "dst" ).toElement();
if ( !source.isNull() )
setMetaData( "davSource", source.text() );
}
else if ( property.tagName() == "getcontentlanguage" )
{
// equiv. to Content-Language header on a GET
setMetaData( "davContentLanguage", property.text() );
}
else if ( property.tagName() == "getcontenttype" )
{
// Content type (mime type)
// This may require adjustments for other server-side webdav implementations
// (tested with Apache + mod_dav 1.0.3)
if ( property.text() == "httpd/unix-directory" )
{
isDirectory = true;
}
else
{
mimeType = property.text();
}
}
else if ( property.tagName() == "executable" )
{
// File executable status
if ( property.text() == "T" )
foundExecutable = true;
}
else if ( property.tagName() == "getlastmodified" )
{
// Last modification date
atom.m_uds = KIO::UDS_MODIFICATION_TIME;
atom.m_long = parseDateTime( property.text(), property.attribute("dt") );
entry.append( atom );
}
else if ( property.tagName() == "getetag" )
{
// Entity tag
setMetaData( "davEntityTag", property.text() );
}
else if ( property.tagName() == "supportedlock" )
{
// Supported locking specifications
for ( TQDomNode n2 = property.firstChild(); !n2.isNull(); n2 = n2.nextSibling() )
{
TQDomElement lockEntry = n2.toElement();
if ( lockEntry.tagName() == "lockentry" )
{
TQDomElement lockScope = lockEntry.namedItem( "lockscope" ).toElement();
TQDomElement lockType = lockEntry.namedItem( "locktype" ).toElement();
if ( !lockScope.isNull() && !lockType.isNull() )
{
// Lock type was properly specified
supportedLockCount++;
TQString scope = lockScope.firstChild().toElement().tagName();
TQString type = lockType.firstChild().toElement().tagName();
setMetaData( TQString("davSupportedLockScope%1").arg(supportedLockCount), scope );
setMetaData( TQString("davSupportedLockType%1").arg(supportedLockCount), type );
}
}
}
}
else if ( property.tagName() == "lockdiscovery" )
{
// Lists the available locks
davParseActiveLocks( property.elementsByTagName( "activelock" ), lockCount );
}
else if ( property.tagName() == "resourcetype" )
{
// Resource type. "Specifies the nature of the resource."
if ( !property.namedItem( "collection" ).toElement().isNull() )
{
// This is a collection (directory)
isDirectory = true;
}
}
else
{
kdDebug(7113) << "Found unknown webdav property: " << property.tagName() << endl;
}
}
}
setMetaData( "davLockCount", TQString("%1").arg(lockCount) );
setMetaData( "davSupportedLockCount", TQString("%1").arg(supportedLockCount) );
atom.m_uds = KIO::UDS_FILE_TYPE;
atom.m_long = isDirectory ? S_IFDIR : S_IFREG;
entry.append( atom );
if ( foundExecutable || isDirectory )
{
// File was executable, or is a directory.
atom.m_uds = KIO::UDS_ACCESS;
atom.m_long = 0700;
entry.append(atom);
}
else
{
atom.m_uds = KIO::UDS_ACCESS;
atom.m_long = 0600;
entry.append(atom);
}
if ( !isDirectory && !mimeType.isEmpty() )
{
atom.m_uds = KIO::UDS_MIME_TYPE;
atom.m_str = mimeType;
entry.append( atom );
}
}
void HTTPProtocol::davParseActiveLocks( const TQDomNodeList& activeLocks,
uint& lockCount )
{
for ( uint i = 0; i < activeLocks.count(); i++ )
{
TQDomElement activeLock = activeLocks.item(i).toElement();
lockCount++;
// required
TQDomElement lockScope = activeLock.namedItem( "lockscope" ).toElement();
TQDomElement lockType = activeLock.namedItem( "locktype" ).toElement();
TQDomElement lockDepth = activeLock.namedItem( "depth" ).toElement();
// optional
TQDomElement lockOwner = activeLock.namedItem( "owner" ).toElement();
TQDomElement lockTimeout = activeLock.namedItem( "timeout" ).toElement();
TQDomElement lockToken = activeLock.namedItem( "locktoken" ).toElement();
if ( !lockScope.isNull() && !lockType.isNull() && !lockDepth.isNull() )
{
// lock was properly specified
lockCount++;
TQString scope = lockScope.firstChild().toElement().tagName();
TQString type = lockType.firstChild().toElement().tagName();
TQString depth = lockDepth.text();
setMetaData( TQString("davLockScope%1").arg( lockCount ), scope );
setMetaData( TQString("davLockType%1").arg( lockCount ), type );
setMetaData( TQString("davLockDepth%1").arg( lockCount ), depth );
if ( !lockOwner.isNull() )
setMetaData( TQString("davLockOwner%1").arg( lockCount ), lockOwner.text() );
if ( !lockTimeout.isNull() )
setMetaData( TQString("davLockTimeout%1").arg( lockCount ), lockTimeout.text() );
if ( !lockToken.isNull() )
{
TQDomElement tokenVal = lockScope.namedItem( "href" ).toElement();
if ( !tokenVal.isNull() )
setMetaData( TQString("davLockToken%1").arg( lockCount ), tokenVal.text() );
}
}
}
}
long HTTPProtocol::parseDateTime( const TQString& input, const TQString& type )
{
if ( type == "dateTime.tz" )
{
return KRFCDate::parseDateISO8601( input );
}
else if ( type == "dateTime.rfc1123" )
{
return KRFCDate::parseDate( input );
}
// format not advertised... try to parse anyway
time_t time = KRFCDate::parseDate( input );
if ( time != 0 )
return time;
return KRFCDate::parseDateISO8601( input );
}
TQString HTTPProtocol::davProcessLocks()
{
if ( hasMetaData( "davLockCount" ) )
{
TQString response("If:");
int numLocks;
numLocks = metaData( "davLockCount" ).toInt();
bool bracketsOpen = false;
for ( int i = 0; i < numLocks; i++ )
{
if ( hasMetaData( TQString("davLockToken%1").arg(i) ) )
{
if ( hasMetaData( TQString("davLockURL%1").arg(i) ) )
{
if ( bracketsOpen )
{
response += ")";
bracketsOpen = false;
}
response += " <" + metaData( TQString("davLockURL%1").arg(i) ) + ">";
}
if ( !bracketsOpen )
{
response += " (";
bracketsOpen = true;
}
else
{
response += " ";
}
if ( hasMetaData( TQString("davLockNot%1").arg(i) ) )
response += "Not ";
response += "<" + metaData( TQString("davLockToken%1").arg(i) ) + ">";
}
}
if ( bracketsOpen )
response += ")";
response += "\r\n";
return response;
}
return TQString::null;
}
bool HTTPProtocol::davHostOk()
{
// FIXME needs to be reworked. Switched off for now.
return true;
// cached?
if ( m_davHostOk )
{
kdDebug(7113) << "(" << m_pid << ") " << k_funcinfo << " true" << endl;
return true;
}
else if ( m_davHostUnsupported )
{
kdDebug(7113) << "(" << m_pid << ") " << k_funcinfo << " false" << endl;
davError( -2 );
return false;
}
m_request.method = HTTP_OPTIONS;
// query the server's capabilities generally, not for a specific URL
m_request.path = "*";
m_request.query = TQString::null;
m_request.cache = CC_Reload;
m_request.doProxy = m_bUseProxy;
// clear davVersions variable, which holds the response to the DAV: header
m_davCapabilities.clear();
retrieveHeader(false);
if (m_davCapabilities.count())
{
for (uint i = 0; i < m_davCapabilities.count(); i++)
{
bool ok;
uint verNo = m_davCapabilities[i].toUInt(&ok);
if (ok && verNo > 0 && verNo < 3)
{
m_davHostOk = true;
kdDebug(7113) << "Server supports DAV version " << verNo << "." << endl;
}
}
if ( m_davHostOk )
return true;
}
m_davHostUnsupported = true;
davError( -2 );
return false;
}
// This function is for closing retrieveHeader( false ); requests
// Required because there may or may not be further info expected
void HTTPProtocol::davFinished()
{
// TODO: Check with the DAV extension developers
httpClose(m_bKeepAlive);
finished();
}
void HTTPProtocol::mkdir( const KURL& url, int )
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::mkdir " << url.url()
<< endl;
if ( !checkRequestURL( url ) )
return;
m_request.method = DAV_MKCOL;
m_request.path = url.path();
m_request.query = TQString::null;
m_request.cache = CC_Reload;
m_request.doProxy = m_bUseProxy;
retrieveHeader( false );
if ( m_responseCode == 201 )
davFinished();
else
davError();
}
void HTTPProtocol::get( const KURL& url )
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::get " << url.url()
<< endl;
if ( !checkRequestURL( url ) )
return;
m_request.method = HTTP_GET;
m_request.path = url.path();
m_request.query = url.query();
TQString tmp = metaData("cache");
if (!tmp.isEmpty())
m_request.cache = parseCacheControl(tmp);
else
m_request.cache = DEFAULT_CACHE_CONTROL;
m_request.passwd = url.pass();
m_request.user = url.user();
m_request.doProxy = m_bUseProxy;
retrieveContent();
}
void HTTPProtocol::put( const KURL &url, int, bool overwrite, bool)
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::put " << url.prettyURL()
<< endl;
if ( !checkRequestURL( url ) )
return;
// Webdav hosts are capable of observing overwrite == false
if (!overwrite && m_protocol.left(6) == "webdav") {
// check to make sure this host supports WebDAV
if ( !davHostOk() )
return;
TQCString request;
request = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
"<D:propfind xmlns:D=\"DAV:\"><D:prop>"
"<D:creationdate/>"
"<D:getcontentlength/>"
"<D:displayname/>"
"<D:resourcetype/>"
"</D:prop></D:propfind>";
davSetRequest( request );
// WebDAV Stat or List...
m_request.method = DAV_PROPFIND;
m_request.query = TQString::null;
m_request.cache = CC_Reload;
m_request.doProxy = m_bUseProxy;
m_request.davData.depth = 0;
retrieveContent(true);
if (m_responseCode == 207) {
error(ERR_FILE_ALREADY_EXIST, TQString::null);
return;
}
m_bError = false;
}
m_request.method = HTTP_PUT;
m_request.path = url.path();
m_request.query = TQString::null;
m_request.cache = CC_Reload;
m_request.doProxy = m_bUseProxy;
retrieveHeader( false );
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::put error = " << m_bError << endl;
if (m_bError)
return;
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::put responseCode = " << m_responseCode << endl;
httpClose(false); // Always close connection.
if ( (m_responseCode >= 200) && (m_responseCode < 300) )
finished();
else
httpError();
}
void HTTPProtocol::copy( const KURL& src, const KURL& dest, int, bool overwrite )
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::copy " << src.prettyURL()
<< " -> " << dest.prettyURL() << endl;
if ( !checkRequestURL( dest ) || !checkRequestURL( src ) )
return;
// destination has to be "http(s)://..."
KURL newDest = dest;
if (newDest.protocol() == "webdavs")
newDest.setProtocol("https");
else
newDest.setProtocol("http");
m_request.method = DAV_COPY;
m_request.path = src.path();
m_request.davData.desturl = newDest.url();
m_request.davData.overwrite = overwrite;
m_request.query = TQString::null;
m_request.cache = CC_Reload;
m_request.doProxy = m_bUseProxy;
retrieveHeader( false );
// The server returns a HTTP/1.1 201 Created or 204 No Content on successful completion
if ( m_responseCode == 201 || m_responseCode == 204 )
davFinished();
else
davError();
}
void HTTPProtocol::rename( const KURL& src, const KURL& dest, bool overwrite )
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::rename " << src.prettyURL()
<< " -> " << dest.prettyURL() << endl;
if ( !checkRequestURL( dest ) || !checkRequestURL( src ) )
return;
// destination has to be "http://..."
KURL newDest = dest;
if (newDest.protocol() == "webdavs")
newDest.setProtocol("https");
else
newDest.setProtocol("http");
m_request.method = DAV_MOVE;
m_request.path = src.path();
m_request.davData.desturl = newDest.url();
m_request.davData.overwrite = overwrite;
m_request.query = TQString::null;
m_request.cache = CC_Reload;
m_request.doProxy = m_bUseProxy;
retrieveHeader( false );
if ( m_responseCode == 301 )
{
// Work around strict Apache-2 WebDAV implementation which refuses to cooperate
// with webdav://host/directory, instead requiring webdav://host/directory/
// (strangely enough it accepts Destination: without a trailing slash)
if (m_redirectLocation.protocol() == "https")
m_redirectLocation.setProtocol("webdavs");
else
m_redirectLocation.setProtocol("webdav");
if ( !checkRequestURL( m_redirectLocation ) )
return;
m_request.method = DAV_MOVE;
m_request.path = m_redirectLocation.path();
m_request.davData.desturl = newDest.url();
m_request.davData.overwrite = overwrite;
m_request.query = TQString::null;
m_request.cache = CC_Reload;
m_request.doProxy = m_bUseProxy;
retrieveHeader( false );
}
if ( m_responseCode == 201 )
davFinished();
else
davError();
}
void HTTPProtocol::del( const KURL& url, bool )
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::del " << url.prettyURL()
<< endl;
if ( !checkRequestURL( url ) )
return;
m_request.method = HTTP_DELETE;
m_request.path = url.path();
m_request.query = TQString::null;
m_request.cache = CC_Reload;
m_request.doProxy = m_bUseProxy;
retrieveHeader( false );
// The server returns a HTTP/1.1 200 Ok or HTTP/1.1 204 No Content
// on successful completion
if ( m_responseCode == 200 || m_responseCode == 204 )
davFinished();
else
davError();
}
void HTTPProtocol::post( const KURL& url )
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::post "
<< url.prettyURL() << endl;
if ( !checkRequestURL( url ) )
return;
m_request.method = HTTP_POST;
m_request.path = url.path();
m_request.query = url.query();
m_request.cache = CC_Reload;
m_request.doProxy = m_bUseProxy;
retrieveContent();
}
void HTTPProtocol::davLock( const KURL& url, const TQString& scope,
const TQString& type, const TQString& owner )
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::davLock "
<< url.prettyURL() << endl;
if ( !checkRequestURL( url ) )
return;
m_request.method = DAV_LOCK;
m_request.path = url.path();
m_request.query = TQString::null;
m_request.cache = CC_Reload;
m_request.doProxy = m_bUseProxy;
/* Create appropriate lock XML request. */
TQDomDocument lockReq;
TQDomElement lockInfo = lockReq.createElementNS( "DAV:", "lockinfo" );
lockReq.appendChild( lockInfo );
TQDomElement lockScope = lockReq.createElement( "lockscope" );
lockInfo.appendChild( lockScope );
lockScope.appendChild( lockReq.createElement( scope ) );
TQDomElement lockType = lockReq.createElement( "locktype" );
lockInfo.appendChild( lockType );
lockType.appendChild( lockReq.createElement( type ) );
if ( !owner.isNull() ) {
TQDomElement ownerElement = lockReq.createElement( "owner" );
lockReq.appendChild( ownerElement );
TQDomElement ownerHref = lockReq.createElement( "href" );
ownerElement.appendChild( ownerHref );
ownerHref.appendChild( lockReq.createTextNode( owner ) );
}
// insert the document into the POST buffer
m_bufPOST = lockReq.toCString();
retrieveContent( true );
if ( m_responseCode == 200 ) {
// success
TQDomDocument multiResponse;
multiResponse.setContent( m_bufWebDavData, true );
TQDomElement prop = multiResponse.documentElement().namedItem( "prop" ).toElement();
TQDomElement lockdiscovery = prop.namedItem( "lockdiscovery" ).toElement();
uint lockCount = 0;
davParseActiveLocks( lockdiscovery.elementsByTagName( "activelock" ), lockCount );
setMetaData( "davLockCount", TQString("%1").arg( lockCount ) );
finished();
} else
davError();
}
void HTTPProtocol::davUnlock( const KURL& url )
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::davUnlock "
<< url.prettyURL() << endl;
if ( !checkRequestURL( url ) )
return;
m_request.method = DAV_UNLOCK;
m_request.path = url.path();
m_request.query = TQString::null;
m_request.cache = CC_Reload;
m_request.doProxy = m_bUseProxy;
retrieveContent( true );
if ( m_responseCode == 200 )
finished();
else
davError();
}
TQString HTTPProtocol::davError( int code /* = -1 */, TQString url )
{
bool callError = false;
if ( code == -1 ) {
code = m_responseCode;
callError = true;
}
if ( code == -2 ) {
callError = true;
}
if ( !url.isNull() )
url = m_request.url.url();
TQString action, errorString;
KIO::Error kError;
// for 412 Precondition Failed
TQString ow = i18n( "Otherwise, the request would have succeeded." );
switch ( m_request.method ) {
case DAV_PROPFIND:
action = i18n( "retrieve property values" );
break;
case DAV_PROPPATCH:
action = i18n( "set property values" );
break;
case DAV_MKCOL:
action = i18n( "create the requested folder" );
break;
case DAV_COPY:
action = i18n( "copy the specified file or folder" );
break;
case DAV_MOVE:
action = i18n( "move the specified file or folder" );
break;
case DAV_SEARCH:
action = i18n( "search in the specified folder" );
break;
case DAV_LOCK:
action = i18n( "lock the specified file or folder" );
break;
case DAV_UNLOCK:
action = i18n( "unlock the specified file or folder" );
break;
case HTTP_DELETE:
action = i18n( "delete the specified file or folder" );
break;
case HTTP_OPTIONS:
action = i18n( "query the server's capabilities" );
break;
case HTTP_GET:
action = i18n( "retrieve the contents of the specified file or folder" );
break;
case HTTP_PUT:
case HTTP_POST:
case HTTP_HEAD:
default:
// this should not happen, this function is for webdav errors only
Q_ASSERT(0);
}
// default error message if the following code fails
kError = ERR_INTERNAL;
errorString = i18n("An unexpected error (%1) occurred while attempting to %2.")
.arg( code ).arg( action );
switch ( code )
{
case -2:
// internal error: OPTIONS request did not specify DAV compliance
kError = ERR_UNSUPPORTED_PROTOCOL;
errorString = i18n("The server does not support the WebDAV protocol.");
break;
case 207:
// 207 Multi-status
{
// our error info is in the returned XML document.
// retrieve the XML document
// there was an error retrieving the XML document.
// ironic, eh?
if ( !readBody( true ) && m_bError )
return TQString::null;
TQStringList errors;
TQDomDocument multiResponse;
multiResponse.setContent( m_bufWebDavData, true );
TQDomElement multistatus = multiResponse.documentElement().namedItem( "multistatus" ).toElement();
TQDomNodeList responses = multistatus.elementsByTagName( "response" );
for (uint i = 0; i < responses.count(); i++)
{
int errCode;
TQString errUrl;
TQDomElement response = responses.item(i).toElement();
TQDomElement code = response.namedItem( "status" ).toElement();
if ( !code.isNull() )
{
errCode = codeFromResponse( code.text() );
TQDomElement href = response.namedItem( "href" ).toElement();
if ( !href.isNull() )
errUrl = href.text();
errors << davError( errCode, errUrl );
}
}
//kError = ERR_SLAVE_DEFINED;
errorString = i18n("An error occurred while attempting to %1, %2. A "
"summary of the reasons is below.<ul>").arg( action ).arg( url );
for ( TQStringList::Iterator it = errors.begin(); it != errors.end(); ++it )
errorString += "<li>" + *it + "</li>";
errorString += "</ul>";
}
case 403:
case 500: // hack: Apache mod_dav returns this instead of 403 (!)
// 403 Forbidden
kError = ERR_ACCESS_DENIED;
errorString = i18n("Access was denied while attempting to %1.").arg( action );
break;
case 405:
// 405 Method Not Allowed
if ( m_request.method == DAV_MKCOL )
{
kError = ERR_DIR_ALREADY_EXIST;
errorString = i18n("The specified folder already exists.");
}
break;
case 409:
// 409 Conflict
kError = ERR_ACCESS_DENIED;
errorString = i18n("A resource cannot be created at the destination "
"until one or more intermediate collections (folders) "
"have been created.");
break;
case 412:
// 412 Precondition failed
if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE )
{
kError = ERR_ACCESS_DENIED;
errorString = i18n("The server was unable to maintain the liveness of "
"the properties listed in the propertybehavior XML "
"element or you attempted to overwrite a file while "
"requesting that files are not overwritten. %1")
.arg( ow );
}
else if ( m_request.method == DAV_LOCK )
{
kError = ERR_ACCESS_DENIED;
errorString = i18n("The requested lock could not be granted. %1").arg( ow );
}
break;
case 415:
// 415 Unsupported Media Type
kError = ERR_ACCESS_DENIED;
errorString = i18n("The server does not support the request type of the body.");
break;
case 423:
// 423 Locked
kError = ERR_ACCESS_DENIED;
errorString = i18n("Unable to %1 because the resource is locked.").arg( action );
break;
case 425:
// 424 Failed Dependency
errorString = i18n("This action was prevented by another error.");
break;
case 502:
// 502 Bad Gateway
if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE )
{
kError = ERR_WRITE_ACCESS_DENIED;
errorString = i18n("Unable to %1 because the destination server refuses "
"to accept the file or folder.").arg( action );
}
break;
case 507:
// 507 Insufficient Storage
kError = ERR_DISK_FULL;
errorString = i18n("The destination resource does not have sufficient space "
"to record the state of the resource after the execution "
"of this method.");
break;
}
// if ( kError != ERR_SLAVE_DEFINED )
//errorString += " (" + url + ")";
if ( callError )
error( ERR_SLAVE_DEFINED, errorString );
return errorString;
}
void HTTPProtocol::httpError()
{
TQString action, errorString;
KIO::Error kError;
switch ( m_request.method ) {
case HTTP_PUT:
action = i18n( "upload %1" ).arg(m_request.url.prettyURL());
break;
default:
// this should not happen, this function is for http errors only
Q_ASSERT(0);
}
// default error message if the following code fails
kError = ERR_INTERNAL;
errorString = i18n("An unexpected error (%1) occurred while attempting to %2.")
.arg( m_responseCode ).arg( action );
switch ( m_responseCode )
{
case 403:
case 405:
case 500: // hack: Apache mod_dav returns this instead of 403 (!)
// 403 Forbidden
// 405 Method Not Allowed
kError = ERR_ACCESS_DENIED;
errorString = i18n("Access was denied while attempting to %1.").arg( action );
break;
case 409:
// 409 Conflict
kError = ERR_ACCESS_DENIED;
errorString = i18n("A resource cannot be created at the destination "
"until one or more intermediate collections (folders) "
"have been created.");
break;
case 423:
// 423 Locked
kError = ERR_ACCESS_DENIED;
errorString = i18n("Unable to %1 because the resource is locked.").arg( action );
break;
case 502:
// 502 Bad Gateway
kError = ERR_WRITE_ACCESS_DENIED;
errorString = i18n("Unable to %1 because the destination server refuses "
"to accept the file or folder.").arg( action );
break;
case 507:
// 507 Insufficient Storage
kError = ERR_DISK_FULL;
errorString = i18n("The destination resource does not have sufficient space "
"to record the state of the resource after the execution "
"of this method.");
break;
}
// if ( kError != ERR_SLAVE_DEFINED )
//errorString += " (" + url + ")";
error( ERR_SLAVE_DEFINED, errorString );
}
bool HTTPProtocol::isOffline(const KURL &url)
{
const int NetWorkStatusUnknown = 1;
const int NetWorkStatusOnline = 8;
TQCString replyType;
TQByteArray params;
TQByteArray reply;
TQDataStream stream(params, IO_WriteOnly);
if ( url.host() == TQString::fromLatin1("localhost") || url.host() == TQString::fromLatin1("127.0.0.1") || url.host() == TQString::fromLatin1("::") ) {
return false;
}
if ( dcopClient()->call( "kded", "networkstatus", "status()",
params, replyType, reply ) && (replyType == "int") )
{
int result;
TQDataStream stream2( reply, IO_ReadOnly );
stream2 >> result;
kdDebug(7113) << "(" << m_pid << ") networkstatus status = " << result << endl;
return (result != NetWorkStatusUnknown) && (result != NetWorkStatusOnline);
}
kdDebug(7113) << "(" << m_pid << ") networkstatus <unreachable>" << endl;
return false; // On error, assume we are online
}
void HTTPProtocol::multiGet(const TQByteArray &data)
{
TQDataStream stream(data, IO_ReadOnly);
TQ_UINT32 n;
stream >> n;
kdDebug(7113) << "(" << m_pid << ") HTTPProtcool::multiGet n = " << n << endl;
HTTPRequest saveRequest;
if (m_bBusy)
saveRequest = m_request;
// m_requestQueue.clear();
for(unsigned i = 0; i < n; i++)
{
KURL url;
stream >> url >> mIncomingMetaData;
if ( !checkRequestURL( url ) )
continue;
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::multi_get " << url.url() << endl;
m_request.method = HTTP_GET;
m_request.path = url.path();
m_request.query = url.query();
TQString tmp = metaData("cache");
if (!tmp.isEmpty())
m_request.cache = parseCacheControl(tmp);
else
m_request.cache = DEFAULT_CACHE_CONTROL;
m_request.passwd = url.pass();
m_request.user = url.user();
m_request.doProxy = m_bUseProxy;
HTTPRequest *newRequest = new HTTPRequest(m_request);
m_requestQueue.append(newRequest);
}
if (m_bBusy)
m_request = saveRequest;
if (!m_bBusy)
{
m_bBusy = true;
while(!m_requestQueue.isEmpty())
{
HTTPRequest *request = m_requestQueue.take(0);
m_request = *request;
delete request;
retrieveContent();
}
m_bBusy = false;
}
}
ssize_t HTTPProtocol::write (const void *_buf, size_t nbytes)
{
int bytes_sent = 0;
const char* buf = static_cast<const char*>(_buf);
while ( nbytes > 0 )
{
int n = TCPSlaveBase::write(buf, nbytes);
if ( n <= 0 )
{
// remote side closed connection ?
if ( n == 0 )
break;
// a valid exception(s) occurred, let's retry...
if (n < 0 && ((errno == EINTR) || (errno == EAGAIN)))
continue;
// some other error occurred ?
return -1;
}
nbytes -= n;
buf += n;
bytes_sent += n;
}
return bytes_sent;
}
void HTTPProtocol::setRewindMarker()
{
m_rewindCount = 0;
}
void HTTPProtocol::rewind()
{
m_linePtrUnget = m_rewindBuf,
m_lineCountUnget = m_rewindCount;
m_rewindCount = 0;
}
char *HTTPProtocol::gets (char *s, int size)
{
int len=0;
char *buf=s;
char mybuf[2]={0,0};
while (len < size)
{
read(mybuf, 1);
if (m_bEOF)
break;
if (m_rewindCount < sizeof(m_rewindBuf))
m_rewindBuf[m_rewindCount++] = *mybuf;
if (*mybuf == '\r') // Ignore!
continue;
if ((*mybuf == '\n') || !*mybuf)
break;
*buf++ = *mybuf;
len++;
}
*buf=0;
return s;
}
ssize_t HTTPProtocol::read (void *b, size_t nbytes)
{
ssize_t ret = 0;
if (m_lineCountUnget > 0)
{
ret = ( nbytes < m_lineCountUnget ? nbytes : m_lineCountUnget );
m_lineCountUnget -= ret;
memcpy(b, m_linePtrUnget, ret);
m_linePtrUnget += ret;
return ret;
}
if (m_lineCount > 0)
{
ret = ( nbytes < m_lineCount ? nbytes : m_lineCount );
m_lineCount -= ret;
memcpy(b, m_linePtr, ret);
m_linePtr += ret;
return ret;
}
if (nbytes == 1)
{
ret = read(m_lineBuf, 1024); // Read into buffer
m_linePtr = m_lineBuf;
if (ret <= 0)
{
m_lineCount = 0;
return ret;
}
m_lineCount = ret;
return read(b, 1); // Read from buffer
}
do
{
ret = TCPSlaveBase::read( b, nbytes);
if (ret == 0)
m_bEOF = true;
} while ((ret == -1) && (errno == EAGAIN || errno == EINTR));
return ret;
}
void HTTPProtocol::httpCheckConnection()
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpCheckConnection: " <<
" Socket status: " << m_iSock <<
" Keep Alive: " << m_bKeepAlive <<
" First: " << m_bFirstRequest << endl;
if ( !m_bFirstRequest && (m_iSock != -1) )
{
bool closeDown = false;
if ( !isConnectionValid())
{
kdDebug(7113) << "(" << m_pid << ") Connection lost!" << endl;
closeDown = true;
}
else if ( m_request.method != HTTP_GET )
{
closeDown = true;
}
else if ( !m_state.doProxy && !m_request.doProxy )
{
if (m_state.hostname != m_request.hostname ||
m_state.port != m_request.port ||
m_state.user != m_request.user ||
m_state.passwd != m_request.passwd)
closeDown = true;
}
else
{
// Keep the connection to the proxy.
if ( !(m_request.doProxy && m_state.doProxy) )
closeDown = true;
}
if (closeDown)
httpCloseConnection();
}
// Let's update our current state
m_state.hostname = m_request.hostname;
m_state.encoded_hostname = m_request.encoded_hostname;
m_state.port = m_request.port;
m_state.user = m_request.user;
m_state.passwd = m_request.passwd;
m_state.doProxy = m_request.doProxy;
}
bool HTTPProtocol::httpOpenConnection()
{
int errCode;
TQString errMsg;
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpenConnection" << endl;
setBlockConnection( true );
// kio_http uses its own proxying:
KSocks::self()->disableSocks();
if ( m_state.doProxy )
{
TQString proxy_host = m_proxyURL.host();
int proxy_port = m_proxyURL.port();
kdDebug(7113) << "(" << m_pid << ") Connecting to proxy server: "
<< proxy_host << ", port: " << proxy_port << endl;
infoMessage( i18n("Connecting to %1...").arg(m_state.hostname) );
setConnectTimeout( m_proxyConnTimeout );
if ( !connectToHost(proxy_host, proxy_port, false) )
{
if (userAborted()) {
error(ERR_NO_CONTENT, "");
return false;
}
switch ( connectResult() )
{
case IO_LookupError:
errMsg = proxy_host;
errCode = ERR_UNKNOWN_PROXY_HOST;
break;
case IO_TimeOutError:
errMsg = i18n("Proxy %1 at port %2").arg(proxy_host).arg(proxy_port);
errCode = ERR_SERVER_TIMEOUT;
break;
default:
errMsg = i18n("Proxy %1 at port %2").arg(proxy_host).arg(proxy_port);
errCode = ERR_COULD_NOT_CONNECT;
}
error( errCode, errMsg );
return false;
}
}
else
{
// Apparently we don't want a proxy. let's just connect directly
setConnectTimeout(m_remoteConnTimeout);
if ( !connectToHost(m_state.hostname, m_state.port, false ) )
{
if (userAborted()) {
error(ERR_NO_CONTENT, "");
return false;
}
switch ( connectResult() )
{
case IO_LookupError:
errMsg = m_state.hostname;
errCode = ERR_UNKNOWN_HOST;
break;
case IO_TimeOutError:
errMsg = i18n("Connection was to %1 at port %2").arg(m_state.hostname).arg(m_state.port);
errCode = ERR_SERVER_TIMEOUT;
break;
default:
errCode = ERR_COULD_NOT_CONNECT;
if (m_state.port != m_iDefaultPort)
errMsg = i18n("%1 (port %2)").arg(m_state.hostname).arg(m_state.port);
else
errMsg = m_state.hostname;
}
error( errCode, errMsg );
return false;
}
}
// Set our special socket option!!
int on = 1;
(void) setsockopt( m_iSock, IPPROTO_TCP, TCP_NODELAY, (char*)&on, sizeof(on) );
m_bFirstRequest = true;
connected();
return true;
}
/**
* This function is responsible for opening up the connection to the remote
* HTTP server and sending the header. If this requires special
* authentication or other such fun stuff, then it will handle it. This
* function will NOT receive anything from the server, however. This is in
* contrast to previous incarnations of 'httpOpen'.
*
* The reason for the change is due to one small fact: some requests require
* data to be sent in addition to the header (POST requests) and there is no
* way for this function to get that data. This function is called in the
* slotPut() or slotGet() functions which, in turn, are called (indirectly) as
* a result of a KIOJob::put() or KIOJob::get(). It is those latter functions
* which are responsible for starting up this ioslave in the first place.
* This means that 'httpOpen' is called (essentially) as soon as the ioslave
* is created -- BEFORE any data gets to this slave.
*
* The basic process now is this:
*
* 1) Open up the socket and port
* 2) Format our request/header
* 3) Send the header to the remote server
*/
bool HTTPProtocol::httpOpen()
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpen" << endl;
// Cannot have an https request without the m_bIsSSL being set! This can
// only happen if TCPSlaveBase::InitializeSSL() function failed in which it
// means the current installation does not support SSL...
if ( (m_protocol == "https" || m_protocol == "webdavs") && !m_bIsSSL )
{
error( ERR_UNSUPPORTED_PROTOCOL, m_protocol );
return false;
}
m_request.fcache = 0;
m_request.bCachedRead = false;
m_request.bCachedWrite = false;
m_request.bMustRevalidate = false;
m_request.expireDate = 0;
m_request.creationDate = 0;
if (m_request.bUseCache)
{
m_request.fcache = checkCacheEntry( );
bool bCacheOnly = (m_request.cache == KIO::CC_CacheOnly);
bool bOffline = isOffline(m_request.doProxy ? m_proxyURL : m_request.url);
if (bOffline && (m_request.cache != KIO::CC_Reload))
m_request.cache = KIO::CC_CacheOnly;
if (m_request.cache == CC_Reload && m_request.fcache)
{
if (m_request.fcache)
fclose(m_request.fcache);
m_request.fcache = 0;
}
if ((m_request.cache == KIO::CC_CacheOnly) || (m_request.cache == KIO::CC_Cache))
m_request.bMustRevalidate = false;
m_request.bCachedWrite = true;
if (m_request.fcache && !m_request.bMustRevalidate)
{
// Cache entry is OK.
m_request.bCachedRead = true; // Cache hit.
return true;
}
else if (!m_request.fcache)
{
m_request.bMustRevalidate = false; // Cache miss
}
else
{
// Conditional cache hit. (Validate)
}
if (bCacheOnly && bOffline)
{
error( ERR_OFFLINE_MODE, m_request.url.url() );
return false;
}
if (bCacheOnly)
{
error( ERR_DOES_NOT_EXIST, m_request.url.url() );
return false;
}
if (bOffline)
{
error( ERR_OFFLINE_MODE, m_request.url.url() );
return false;
}
}
TQString header;
TQString davHeader;
bool moreData = false;
bool davData = false;
// Clear out per-connection settings...
resetConnectionSettings ();
// Check the validity of the current connection, if one exists.
httpCheckConnection();
if ( !m_bIsTunneled && m_bNeedTunnel )
{
setEnableSSLTunnel( true );
// We send a HTTP 1.0 header since some proxies refuse HTTP 1.1 and we don't
// need any HTTP 1.1 capabilities for CONNECT - Waba
header = TQString("CONNECT %1:%2 HTTP/1.0"
"\r\n").arg( m_request.encoded_hostname).arg(m_request.port);
// Identify who you are to the proxy server!
if (!m_request.userAgent.isEmpty())
header += "User-Agent: " + m_request.userAgent + "\r\n";
/* Add hostname information */
header += "Host: " + m_state.encoded_hostname;
if (m_state.port != m_iDefaultPort)
header += TQString(":%1").arg(m_state.port);
header += "\r\n";
header += proxyAuthenticationHeader();
}
else
{
// Determine if this is a POST or GET method
switch (m_request.method)
{
case HTTP_GET:
header = "GET ";
break;
case HTTP_PUT:
header = "PUT ";
moreData = true;
m_request.bCachedWrite = false; // Do not put any result in the cache
break;
case HTTP_POST:
header = "POST ";
moreData = true;
m_request.bCachedWrite = false; // Do not put any result in the cache
break;
case HTTP_HEAD:
header = "HEAD ";
break;
case HTTP_DELETE:
header = "DELETE ";
m_request.bCachedWrite = false; // Do not put any result in the cache
break;
case HTTP_OPTIONS:
header = "OPTIONS ";
m_request.bCachedWrite = false; // Do not put any result in the cache
break;
case DAV_PROPFIND:
header = "PROPFIND ";
davData = true;
davHeader = "Depth: ";
if ( hasMetaData( "davDepth" ) )
{
kdDebug(7113) << "Reading DAV depth from metadata: " << metaData( "davDepth" ) << endl;
davHeader += metaData( "davDepth" );
}
else
{
if ( m_request.davData.depth == 2 )
davHeader += "infinity";
else
davHeader += TQString("%1").arg( m_request.davData.depth );
}
davHeader += "\r\n";
m_request.bCachedWrite = false; // Do not put any result in the cache
break;
case DAV_PROPPATCH:
header = "PROPPATCH ";
davData = true;
m_request.bCachedWrite = false; // Do not put any result in the cache
break;
case DAV_MKCOL:
header = "MKCOL ";
m_request.bCachedWrite = false; // Do not put any result in the cache
break;
case DAV_COPY:
case DAV_MOVE:
header = ( m_request.method == DAV_COPY ) ? "COPY " : "MOVE ";
davHeader = "Destination: " + m_request.davData.desturl;
// infinity depth means copy recursively
// (optional for copy -> but is the desired action)
davHeader += "\r\nDepth: infinity\r\nOverwrite: ";
davHeader += m_request.davData.overwrite ? "T" : "F";
davHeader += "\r\n";
m_request.bCachedWrite = false; // Do not put any result in the cache
break;
case DAV_LOCK:
header = "LOCK ";
davHeader = "Timeout: ";
{
uint timeout = 0;
if ( hasMetaData( "davTimeout" ) )
timeout = metaData( "davTimeout" ).toUInt();
if ( timeout == 0 )
davHeader += "Infinite";
else
davHeader += TQString("Seconds-%1").arg(timeout);
}
davHeader += "\r\n";
m_request.bCachedWrite = false; // Do not put any result in the cache
davData = true;
break;
case DAV_UNLOCK:
header = "UNLOCK ";
davHeader = "Lock-token: " + metaData("davLockToken") + "\r\n";
m_request.bCachedWrite = false; // Do not put any result in the cache
break;
case DAV_SEARCH:
header = "SEARCH ";
davData = true;
m_request.bCachedWrite = false;
break;
case DAV_SUBSCRIBE:
header = "SUBSCRIBE ";
m_request.bCachedWrite = false;
break;
case DAV_UNSUBSCRIBE:
header = "UNSUBSCRIBE ";
m_request.bCachedWrite = false;
break;
case DAV_POLL:
header = "POLL ";
m_request.bCachedWrite = false;
break;
default:
error (ERR_UNSUPPORTED_ACTION, TQString::null);
return false;
}
// DAV_POLL; DAV_NOTIFY
// format the URI
if (m_state.doProxy && !m_bIsTunneled)
{
KURL u;
if (m_protocol == "webdav")
u.setProtocol( "http" );
else if (m_protocol == "webdavs" )
u.setProtocol( "https" );
else
u.setProtocol( m_protocol );
// For all protocols other than the once handled by this io-slave
// append the username. This fixes a long standing bug of ftp io-slave
// logging in anonymously in proxied connections even when the username
// is explicitly specified.
if (m_protocol != "http" && m_protocol != "https" &&
!m_state.user.isEmpty())
u.setUser (m_state.user);
u.setHost( m_state.hostname );
if (m_state.port != m_iDefaultPort)
u.setPort( m_state.port );
u.setEncodedPathAndQuery( m_request.url.encodedPathAndQuery(0,true) );
header += u.url();
}
else
{
header += m_request.url.encodedPathAndQuery(0, true);
}
header += " HTTP/1.1\r\n"; /* start header */
if (!m_request.userAgent.isEmpty())
{
header += "User-Agent: ";
header += m_request.userAgent;
header += "\r\n";
}
if (!m_request.referrer.isEmpty())
{
header += "Referer: "; //Don't try to correct spelling!
header += m_request.referrer;
header += "\r\n";
}
if ( m_request.offset > 0 )
{
header += TQString("Range: bytes=%1-\r\n").arg(KIO::number(m_request.offset));
kdDebug(7103) << "kio_http : Range = " << KIO::number(m_request.offset) << endl;
}
if ( m_request.cache == CC_Reload )
{
/* No caching for reload */
header += "Pragma: no-cache\r\n"; /* for HTTP/1.0 caches */
header += "Cache-control: no-cache\r\n"; /* for HTTP >=1.1 caches */
}
if (m_request.bMustRevalidate)
{
/* conditional get */
if (!m_request.etag.isEmpty())
header += "If-None-Match: "+m_request.etag+"\r\n";
if (!m_request.lastModified.isEmpty())
header += "If-Modified-Since: "+m_request.lastModified+"\r\n";
}
header += "Accept: ";
TQString acceptHeader = metaData("accept");
if (!acceptHeader.isEmpty())
header += acceptHeader;
else
header += DEFAULT_ACCEPT_HEADER;
header += "\r\n";
#ifdef DO_GZIP
if (m_request.allowCompressedPage)
header += "Accept-Encoding: x-gzip, x-deflate, gzip, deflate\r\n";
#endif
if (!m_request.charsets.isEmpty())
header += "Accept-Charset: " + m_request.charsets + "\r\n";
if (!m_request.languages.isEmpty())
header += "Accept-Language: " + m_request.languages + "\r\n";
/* support for virtual hosts and required by HTTP 1.1 */
header += "Host: " + m_state.encoded_hostname;
if (m_state.port != m_iDefaultPort)
header += TQString(":%1").arg(m_state.port);
header += "\r\n";
TQString cookieStr;
TQString cookieMode = metaData("cookies").lower();
if (cookieMode == "none")
{
m_request.cookieMode = HTTPRequest::CookiesNone;
}
else if (cookieMode == "manual")
{
m_request.cookieMode = HTTPRequest::CookiesManual;
cookieStr = metaData("setcookies");
}
else
{
m_request.cookieMode = HTTPRequest::CookiesAuto;
if (m_request.bUseCookiejar)
cookieStr = findCookies( m_request.url.url());
}
if (!cookieStr.isEmpty())
header += cookieStr + "\r\n";
TQString customHeader = metaData( "customHTTPHeader" );
if (!customHeader.isEmpty())
{
header += sanitizeCustomHTTPHeader(customHeader);
header += "\r\n";
}
if (m_request.method == HTTP_POST)
{
header += metaData("content-type");
header += "\r\n";
}
// Only check for a cached copy if the previous
// response was NOT a 401 or 407.
// no caching for Negotiate auth.
if ( !m_request.bNoAuth && m_responseCode != 401 && m_responseCode != 407 && Authentication != AUTH_Negotiate )
{
kdDebug(7113) << "(" << m_pid << ") Calling checkCachedAuthentication " << endl;
AuthInfo info;
info.url = m_request.url;
info.verifyPath = true;
if ( !m_request.user.isEmpty() )
info.username = m_request.user;
if ( checkCachedAuthentication( info ) && !info.digestInfo.isEmpty() )
{
Authentication = info.digestInfo.startsWith("Basic") ? AUTH_Basic : info.digestInfo.startsWith("NTLM") ? AUTH_NTLM : info.digestInfo.startsWith("Negotiate") ? AUTH_Negotiate : AUTH_Digest ;
m_state.user = info.username;
m_state.passwd = info.password;
m_strRealm = info.realmValue;
if ( Authentication != AUTH_NTLM && Authentication != AUTH_Negotiate ) // don't use the cached challenge
m_strAuthorization = info.digestInfo;
}
}
else
{
kdDebug(7113) << "(" << m_pid << ") Not calling checkCachedAuthentication " << endl;
}
switch ( Authentication )
{
case AUTH_Basic:
header += createBasicAuth();
break;
case AUTH_Digest:
header += createDigestAuth();
break;
#ifdef HAVE_LIBGSSAPI
case AUTH_Negotiate:
header += createNegotiateAuth();
break;
#endif
case AUTH_NTLM:
header += createNTLMAuth();
break;
case AUTH_None:
default:
break;
}
/********* Only for debugging purpose *********/
if ( Authentication != AUTH_None )
{
kdDebug(7113) << "(" << m_pid << ") Using Authentication: " << endl;
kdDebug(7113) << "(" << m_pid << ") HOST= " << m_state.hostname << endl;
kdDebug(7113) << "(" << m_pid << ") PORT= " << m_state.port << endl;
kdDebug(7113) << "(" << m_pid << ") USER= " << m_state.user << endl;
kdDebug(7113) << "(" << m_pid << ") PASSWORD= [protected]" << endl;
kdDebug(7113) << "(" << m_pid << ") REALM= " << m_strRealm << endl;
kdDebug(7113) << "(" << m_pid << ") EXTRA= " << m_strAuthorization << endl;
}
// Do we need to authorize to the proxy server ?
if ( m_state.doProxy && !m_bIsTunneled )
header += proxyAuthenticationHeader();
// Support old HTTP/1.0 style keep-alive header for compatability
// purposes as well as performance improvements while giving end
// users the ability to disable this feature proxy servers that
// don't not support such feature, e.g. junkbuster proxy server.
if (!m_bUseProxy || m_bPersistentProxyConnection || m_bIsTunneled)
header += "Connection: Keep-Alive\r\n";
else
header += "Connection: close\r\n";
if ( m_protocol == "webdav" || m_protocol == "webdavs" )
{
header += davProcessLocks();
// add extra webdav headers, if supplied
TQString davExtraHeader = metaData("davHeader");
if ( !davExtraHeader.isEmpty() )
davHeader += davExtraHeader;
// Set content type of webdav data
if (davData)
davHeader += "Content-Type: text/xml; charset=utf-8\r\n";
// add extra header elements for WebDAV
if ( !davHeader.isNull() )
header += davHeader;
}
}
kdDebug(7103) << "(" << m_pid << ") ============ Sending Header:" << endl;
TQStringList headerOutput = TQStringList::split("\r\n", header);
TQStringList::Iterator it = headerOutput.begin();
for (; it != headerOutput.end(); it++)
kdDebug(7103) << "(" << m_pid << ") " << (*it) << endl;
if ( !moreData && !davData)
header += "\r\n"; /* end header */
// Now that we have our formatted header, let's send it!
// Create a new connection to the remote machine if we do
// not already have one...
if ( m_iSock == -1)
{
if (!httpOpenConnection())
return false;
}
// Send the data to the remote machine...
bool sendOk = (write(header.latin1(), header.length()) == (ssize_t) header.length());
if (!sendOk)
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpen: "
"Connection broken! (" << m_state.hostname << ")" << endl;
// With a Keep-Alive connection this can happen.
// Just reestablish the connection.
if (m_bKeepAlive)
{
httpCloseConnection();
return true; // Try again
}
if (!sendOk)
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpen: sendOk==false."
" Connnection broken !" << endl;
error( ERR_CONNECTION_BROKEN, m_state.hostname );
return false;
}
}
bool res = true;
if ( moreData || davData )
res = sendBody();
infoMessage(i18n("%1 contacted. Waiting for reply...").arg(m_request.hostname));
return res;
}
void HTTPProtocol::forwardHttpResponseHeader()
{
// Send the response header if it was requested
if ( config()->readBoolEntry("PropagateHttpHeader", false) )
{
setMetaData("HTTP-Headers", m_responseHeader.join("\n"));
sendMetaData();
}
m_responseHeader.clear();
}
/**
* This function will read in the return header from the server. It will
* not read in the body of the return message. It will also not transmit
* the header to our client as the client doesn't need to know the gory
* details of HTTP headers.
*/
bool HTTPProtocol::readHeader()
{
try_again:
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader" << endl;
// Check
if (m_request.bCachedRead)
{
m_responseHeader << "HTTP-CACHE";
// Read header from cache...
char buffer[4097];
if (!fgets(buffer, 4096, m_request.fcache) )
{
// Error, delete cache entry
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: "
<< "Could not access cache to obtain mimetype!" << endl;
error( ERR_CONNECTION_BROKEN, m_state.hostname );
return false;
}
m_strMimeType = TQString(TQString::fromUtf8( buffer)).stripWhiteSpace();
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: cached "
<< "data mimetype: " << m_strMimeType << endl;
if (!fgets(buffer, 4096, m_request.fcache) )
{
// Error, delete cache entry
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: "
<< "Could not access cached data! " << endl;
error( ERR_CONNECTION_BROKEN, m_state.hostname );
return false;
}
m_request.strCharset = TQString(TQString::fromUtf8( buffer)).stripWhiteSpace().lower();
setMetaData("charset", m_request.strCharset);
if (!m_request.lastModified.isEmpty())
setMetaData("modified", m_request.lastModified);
TQString tmp;
tmp.setNum(m_request.expireDate);
setMetaData("expire-date", tmp);
tmp.setNum(m_request.creationDate);
setMetaData("cache-creation-date", tmp);
mimeType(m_strMimeType);
forwardHttpResponseHeader();
return true;
}
TQCString locationStr; // In case we get a redirect.
TQCString cookieStr; // In case we get a cookie.
TQString dispositionType; // In case we get a Content-Disposition type
TQString dispositionFilename; // In case we get a Content-Disposition filename
TQString mediaValue;
TQString mediaAttribute;
TQStringList upgradeOffers;
bool upgradeRequired = false; // Server demands that we upgrade to something
// This is also true if we ask to upgrade and
// the server accepts, since we are now
// committed to doing so
bool canUpgrade = false; // The server offered an upgrade
m_request.etag = TQString::null;
m_request.lastModified = TQString::null;
m_request.strCharset = TQString::null;
time_t dateHeader = 0;
time_t expireDate = 0; // 0 = no info, 1 = already expired, > 1 = actual date
int currentAge = 0;
int maxAge = -1; // -1 = no max age, 0 already expired, > 0 = actual time
int maxHeaderSize = 64*1024; // 64Kb to catch DOS-attacks
// read in 8192 bytes at a time (HTTP cookies can be quite large.)
int len = 0;
char buffer[8193];
bool cont = false;
bool cacheValidated = false; // Revalidation was successful
bool mayCache = true;
bool hasCacheDirective = false;
bool bCanResume = false;
if (m_iSock == -1)
{
kdDebug(7113) << "HTTPProtocol::readHeader: No connection." << endl;
return false; // Restablish connection and try again
}
if (!waitForResponse(m_remoteRespTimeout))
{
// No response error
error( ERR_SERVER_TIMEOUT , m_state.hostname );
return false;
}
setRewindMarker();
gets(buffer, sizeof(buffer)-1);
if (m_bEOF || *buffer == '\0')
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: "
<< "EOF while waiting for header start." << endl;
if (m_bKeepAlive) // Try to reestablish connection.
{
httpCloseConnection();
return false; // Reestablish connection and try again.
}
if (m_request.method == HTTP_HEAD)
{
// HACK
// Some web-servers fail to respond properly to a HEAD request.
// We compensate for their failure to properly implement the HTTP standard
// by assuming that they will be sending html.
kdDebug(7113) << "(" << m_pid << ") HTTPPreadHeader: HEAD -> returned "
<< "mimetype: " << DEFAULT_MIME_TYPE << endl;
mimeType(TQString::fromLatin1(DEFAULT_MIME_TYPE));
return true;
}
kdDebug(7113) << "HTTPProtocol::readHeader: Connection broken !" << endl;
error( ERR_CONNECTION_BROKEN, m_state.hostname );
return false;
}
kdDebug(7103) << "(" << m_pid << ") ============ Received Response:"<< endl;
bool noHeader = true;
HTTP_REV httpRev = HTTP_None;
int headerSize = 0;
do
{
// strip off \r and \n if we have them
len = strlen(buffer);
while(len && (buffer[len-1] == '\n' || buffer[len-1] == '\r'))
buffer[--len] = 0;
// if there was only a newline then continue
if (!len)
{
kdDebug(7103) << "(" << m_pid << ") --empty--" << endl;
continue;
}
headerSize += len;
// We have a response header. This flag is a work around for
// servers that append a "\r\n" before the beginning of the HEADER
// response!!! It only catches x number of \r\n being placed at the
// top of the reponse...
noHeader = false;
kdDebug(7103) << "(" << m_pid << ") \"" << buffer << "\"" << endl;
// Save broken servers from damnation!!
char* buf = buffer;
while( *buf == ' ' )
buf++;
if (buf[0] == '<')
{
// We get XML / HTTP without a proper header
// put string back
kdDebug(7103) << "kio_http: No valid HTTP header found! Document starts with XML/HTML tag" << endl;
// Document starts with a tag, assume html instead of text/plain
m_strMimeType = "text/html";
rewind();
break;
}
// Store the the headers so they can be passed to the
// calling application later
m_responseHeader << TQString::fromLatin1(buf);
if ((strncasecmp(buf, "HTTP/", 5) == 0) ||
(strncasecmp(buf, "ICY ", 4) == 0)) // Shoutcast support
{
if (strncasecmp(buf, "ICY ", 4) == 0)
{
// Shoutcast support
httpRev = SHOUTCAST;
m_bKeepAlive = false;
}
else if (strncmp((buf + 5), "1.0",3) == 0)
{
httpRev = HTTP_10;
// For 1.0 servers, the server itself has to explicitly
// tell us whether it supports persistent connection or
// not. By default, we assume it does not, but we do
// send the old style header "Connection: Keep-Alive" to
// inform it that we support persistence.
m_bKeepAlive = false;
}
else if (strncmp((buf + 5), "1.1",3) == 0)
{
httpRev = HTTP_11;
}
else
{
httpRev = HTTP_Unknown;
}
if (m_responseCode)
m_prevResponseCode = m_responseCode;
const char* rptr = buf;
while ( *rptr && *rptr > ' ' )
++rptr;
m_responseCode = atoi(rptr);
// server side errors
if (m_responseCode >= 500 && m_responseCode <= 599)
{
if (m_request.method == HTTP_HEAD)
{
; // Ignore error
}
else
{
if (m_request.bErrorPage)
errorPage();
else
{
error(ERR_INTERNAL_SERVER, m_request.url.url());
return false;
}
}
m_request.bCachedWrite = false; // Don't put in cache
mayCache = false;
}
// Unauthorized access
else if (m_responseCode == 401 || m_responseCode == 407)
{
// Double authorization requests, i.e. a proxy auth
// request followed immediately by a regular auth request.
if ( m_prevResponseCode != m_responseCode &&
(m_prevResponseCode == 401 || m_prevResponseCode == 407) )
saveAuthorization();
m_bUnauthorized = true;
m_request.bCachedWrite = false; // Don't put in cache
mayCache = false;
}
//
else if (m_responseCode == 416) // Range not supported
{
m_request.offset = 0;
httpCloseConnection();
return false; // Try again.
}
// Upgrade Required
else if (m_responseCode == 426)
{
upgradeRequired = true;
}
// Any other client errors
else if (m_responseCode >= 400 && m_responseCode <= 499)
{
// Tell that we will only get an error page here.
if (m_request.bErrorPage)
errorPage();
else
{
error(ERR_DOES_NOT_EXIST, m_request.url.url());
return false;
}
m_request.bCachedWrite = false; // Don't put in cache
mayCache = false;
}
else if (m_responseCode == 307)
{
// 307 Temporary Redirect
m_request.bCachedWrite = false; // Don't put in cache
mayCache = false;
}
else if (m_responseCode == 304)
{
// 304 Not Modified
// The value in our cache is still valid.
cacheValidated = true;
}
else if (m_responseCode >= 301 && m_responseCode<= 303)
{
// 301 Moved permanently
if (m_responseCode == 301)
setMetaData("permanent-redirect", "true");
// 302 Found (temporary location)
// 303 See Other
if (m_request.method != HTTP_HEAD && m_request.method != HTTP_GET)
{
#if 0
// Reset the POST buffer to avoid a double submit
// on redirection
if (m_request.method == HTTP_POST)
m_bufPOST.resize(0);
#endif
// NOTE: This is wrong according to RFC 2616. However,
// because most other existing user agent implementations
// treat a 301/302 response as a 303 response and preform
// a GET action regardless of what the previous method was,
// many servers have simply adapted to this way of doing
// things!! Thus, we are forced to do the same thing or we
// won't be able to retrieve these pages correctly!! See RFC
// 2616 sections 10.3.[2/3/4/8]
m_request.method = HTTP_GET; // Force a GET
}
m_request.bCachedWrite = false; // Don't put in cache
mayCache = false;
}
else if ( m_responseCode == 207 ) // Multi-status (for WebDav)
{
}
else if ( m_responseCode == 204 ) // No content
{
// error(ERR_NO_CONTENT, i18n("Data have been successfully sent."));
// Short circuit and do nothing!
// The original handling here was wrong, this is not an error: eg. in the
// example of a 204 No Content response to a PUT completing.
// m_bError = true;
// return false;
}
else if ( m_responseCode == 206 )
{
if ( m_request.offset )
bCanResume = true;
}
else if (m_responseCode == 102) // Processing (for WebDAV)
{
/***
* This status code is given when the server expects the
* command to take significant time to complete. So, inform
* the user.
*/
infoMessage( i18n( "Server processing request, please wait..." ) );
cont = true;
}
else if (m_responseCode == 100)
{
// We got 'Continue' - ignore it
cont = true;
}
}
// are we allowd to resume? this will tell us
else if (strncasecmp(buf, "Accept-Ranges:", 14) == 0) {
if (strncasecmp(trimLead(buf + 14), "none", 4) == 0)
bCanResume = false;
}
// Keep Alive
else if (strncasecmp(buf, "Keep-Alive:", 11) == 0) {
TQStringList options = TQStringList::split(',',
TQString::fromLatin1(trimLead(buf+11)));
for(TQStringList::ConstIterator it = options.begin();
it != options.end();
it++)
{
TQString option = (*it).stripWhiteSpace().lower();
if (option.startsWith("timeout="))
{
m_keepAliveTimeout = option.mid(8).toInt();
}
}
}
// Cache control
else if (strncasecmp(buf, "Cache-Control:", 14) == 0) {
TQStringList cacheControls = TQStringList::split(',',
TQString::fromLatin1(trimLead(buf+14)));
for(TQStringList::ConstIterator it = cacheControls.begin();
it != cacheControls.end();
it++)
{
TQString cacheControl = (*it).stripWhiteSpace();
if (strncasecmp(cacheControl.latin1(), "no-cache", 8) == 0)
{
m_request.bCachedWrite = false; // Don't put in cache
mayCache = false;
}
else if (strncasecmp(cacheControl.latin1(), "no-store", 8) == 0)
{
m_request.bCachedWrite = false; // Don't put in cache
mayCache = false;
}
else if (strncasecmp(cacheControl.latin1(), "max-age=", 8) == 0)
{
TQString age = cacheControl.mid(8).stripWhiteSpace();
if (!age.isNull())
maxAge = STRTOLL(age.latin1(), 0, 10);
}
}
hasCacheDirective = true;
}
// get the size of our data
else if (strncasecmp(buf, "Content-length:", 15) == 0) {
char* len = trimLead(buf + 15);
if (len)
m_iSize = STRTOLL(len, 0, 10);
}
else if (strncasecmp(buf, "Content-location:", 17) == 0) {
setMetaData ("content-location",
TQString::fromLatin1(trimLead(buf+17)).stripWhiteSpace());
}
// what type of data do we have?
else if (strncasecmp(buf, "Content-type:", 13) == 0) {
char *start = trimLead(buf + 13);
char *pos = start;
// Increment until we encounter ";" or the end of the buffer
while ( *pos && *pos != ';' ) pos++;
// Assign the mime-type.
m_strMimeType = TQString::fromLatin1(start, pos-start).stripWhiteSpace().lower();
kdDebug(7113) << "(" << m_pid << ") Content-type: " << m_strMimeType << endl;
// If we still have text, then it means we have a mime-type with a
// parameter (eg: charset=iso-8851) ; so let's get that...
while (*pos)
{
start = ++pos;
while ( *pos && *pos != '=' ) pos++;
char *end = pos;
while ( *end && *end != ';' ) end++;
if (*pos)
{
mediaAttribute = TQString::fromLatin1(start, pos-start).stripWhiteSpace().lower();
mediaValue = TQString::fromLatin1(pos+1, end-pos-1).stripWhiteSpace();
pos = end;
if (mediaValue.length() &&
(mediaValue[0] == '"') &&
(mediaValue[mediaValue.length()-1] == '"'))
mediaValue = mediaValue.mid(1, mediaValue.length()-2);
kdDebug (7113) << "(" << m_pid << ") Media-Parameter Attribute: "
<< mediaAttribute << endl;
kdDebug (7113) << "(" << m_pid << ") Media-Parameter Value: "
<< mediaValue << endl;
if ( mediaAttribute == "charset")
{
mediaValue = mediaValue.lower();
m_request.strCharset = mediaValue;
}
else
{
setMetaData("media-"+mediaAttribute, mediaValue);
}
}
}
}
// Date
else if (strncasecmp(buf, "Date:", 5) == 0) {
dateHeader = KRFCDate::parseDate(trimLead(buf+5));
}
// Cache management
else if (strncasecmp(buf, "ETag:", 5) == 0) {
m_request.etag = trimLead(buf+5);
}
// Cache management
else if (strncasecmp(buf, "Expires:", 8) == 0) {
expireDate = KRFCDate::parseDate(trimLead(buf+8));
if (!expireDate)
expireDate = 1; // Already expired
}
// Cache management
else if (strncasecmp(buf, "Last-Modified:", 14) == 0) {
m_request.lastModified = (TQString::fromLatin1(trimLead(buf+14))).stripWhiteSpace();
}
// whoops.. we received a warning
else if (strncasecmp(buf, "Warning:", 8) == 0) {
//Don't use warning() here, no need to bother the user.
//Those warnings are mostly about caches.
infoMessage(trimLead(buf + 8));
}
// Cache management (HTTP 1.0)
else if (strncasecmp(buf, "Pragma:", 7) == 0) {
TQCString pragma = TQCString(trimLead(buf+7)).stripWhiteSpace().lower();
if (pragma == "no-cache")
{
m_request.bCachedWrite = false; // Don't put in cache
mayCache = false;
hasCacheDirective = true;
}
}
// The deprecated Refresh Response
else if (strncasecmp(buf,"Refresh:", 8) == 0) {
mayCache = false; // Do not cache page as it defeats purpose of Refresh tag!
setMetaData( "http-refresh", TQString::fromLatin1(trimLead(buf+8)).stripWhiteSpace() );
}
// In fact we should do redirection only if we got redirection code
else if (strncasecmp(buf, "Location:", 9) == 0) {
// Redirect only for 3xx status code, will ya! Thanks, pal!
if ( m_responseCode > 299 && m_responseCode < 400 )
locationStr = TQCString(trimLead(buf+9)).stripWhiteSpace();
}
// Check for cookies
else if (strncasecmp(buf, "Set-Cookie", 10) == 0) {
cookieStr += buf;
cookieStr += '\n';
}
// check for direct authentication
else if (strncasecmp(buf, "WWW-Authenticate:", 17) == 0) {
configAuth(trimLead(buf + 17), false);
}
// check for proxy-based authentication
else if (strncasecmp(buf, "Proxy-Authenticate:", 19) == 0) {
configAuth(trimLead(buf + 19), true);
}
else if (strncasecmp(buf, "Upgrade:", 8) == 0) {
// Now we have to check to see what is offered for the upgrade
TQString offered = &(buf[8]);
upgradeOffers = TQStringList::split(TQRegExp("[ \n,\r\t]"), offered);
}
// content?
else if (strncasecmp(buf, "Content-Encoding:", 17) == 0) {
// This is so wrong !! No wonder kio_http is stripping the
// gzip encoding from downloaded files. This solves multiple
// bug reports and caitoo's problem with downloads when such a
// header is encountered...
// A quote from RFC 2616:
// " When present, its (Content-Encoding) value indicates what additional
// content have been applied to the entity body, and thus what decoding
// mechanism must be applied to obtain the media-type referenced by the
// Content-Type header field. Content-Encoding is primarily used to allow
// a document to be compressed without loosing the identity of its underlying
// media type. Simply put if it is specified, this is the actual mime-type
// we should use when we pull the resource !!!
addEncoding(trimLead(buf + 17), m_qContentEncodings);
}
// Refer to RFC 2616 sec 15.5/19.5.1 and RFC 2183
else if(strncasecmp(buf, "Content-Disposition:", 20) == 0) {
char* dispositionBuf = trimLead(buf + 20);
while ( *dispositionBuf )
{
if ( strncasecmp( dispositionBuf, "filename", 8 ) == 0 )
{
dispositionBuf += 8;
while ( *dispositionBuf == ' ' || *dispositionBuf == '=' )
dispositionBuf++;
char* bufStart = dispositionBuf;
while ( *dispositionBuf && *dispositionBuf != ';' )
dispositionBuf++;
if ( dispositionBuf > bufStart )
{
// Skip any leading quotes...
while ( *bufStart == '"' )
bufStart++;
// Skip any trailing quotes as well as white spaces...
while ( *(dispositionBuf-1) == ' ' || *(dispositionBuf-1) == '"')
dispositionBuf--;
if ( dispositionBuf > bufStart )
dispositionFilename = TQString::fromLatin1( bufStart, dispositionBuf-bufStart );
break;
}
}
else
{
char *bufStart = dispositionBuf;
while ( *dispositionBuf && *dispositionBuf != ';' )
dispositionBuf++;
if ( dispositionBuf > bufStart )
dispositionType = TQString::fromLatin1( bufStart, dispositionBuf-bufStart ).stripWhiteSpace();
while ( *dispositionBuf == ';' || *dispositionBuf == ' ' )
dispositionBuf++;
}
}
// Content-Dispostion is not allowed to dictate directory
// path, thus we extract the filename only.
if ( !dispositionFilename.isEmpty() )
{
int pos = dispositionFilename.findRev( '/' );
if( pos > -1 )
dispositionFilename = dispositionFilename.mid(pos+1);
kdDebug(7113) << "(" << m_pid << ") Content-Disposition: filename="
<< dispositionFilename<< endl;
}
}
else if(strncasecmp(buf, "Content-Language:", 17) == 0) {
TQString language = TQString::fromLatin1(trimLead(buf+17)).stripWhiteSpace();
if (!language.isEmpty())
setMetaData("content-language", language);
}
else if (strncasecmp(buf, "Proxy-Connection:", 17) == 0)
{
if (strncasecmp(trimLead(buf + 17), "Close", 5) == 0)
m_bKeepAlive = false;
else if (strncasecmp(trimLead(buf + 17), "Keep-Alive", 10)==0)
m_bKeepAlive = true;
}
else if (strncasecmp(buf, "Link:", 5) == 0) {
// We only support Link: <url>; rel="type" so far
TQStringList link = TQStringList::split(";", TQString(buf)
.replace(TQRegExp("^Link:[ ]*"),
""));
if (link.count() == 2) {
TQString rel = link[1].stripWhiteSpace();
if (rel.startsWith("rel=\"")) {
rel = rel.mid(5, rel.length() - 6);
if (rel.lower() == "pageservices") {
TQString url = TQString(link[0].replace(TQRegExp("[<>]"),"")).stripWhiteSpace();
setMetaData("PageServices", url);
}
}
}
}
else if (strncasecmp(buf, "P3P:", 4) == 0) {
TQString p3pstr = buf;
p3pstr = p3pstr.mid(4).simplifyWhiteSpace();
TQStringList policyrefs, compact;
TQStringList policyfields = TQStringList::split(TQRegExp(",[ ]*"), p3pstr);
for (TQStringList::Iterator it = policyfields.begin();
it != policyfields.end();
++it) {
TQStringList policy = TQStringList::split("=", *it);
if (policy.count() == 2) {
if (policy[0].lower() == "policyref") {
policyrefs << TQString(policy[1].replace(TQRegExp("[\"\']"), ""))
.stripWhiteSpace();
} else if (policy[0].lower() == "cp") {
// We convert to cp\ncp\ncp\n[...]\ncp to be consistent with
// other metadata sent in strings. This could be a bit more
// efficient but I'm going for correctness right now.
TQStringList cps = TQStringList::split(" ",
TQString(policy[1].replace(TQRegExp("[\"\']"), ""))
.simplifyWhiteSpace());
for (TQStringList::Iterator j = cps.begin(); j != cps.end(); ++j)
compact << *j;
}
}
}
if (!policyrefs.isEmpty())
setMetaData("PrivacyPolicy", policyrefs.join("\n"));
if (!compact.isEmpty())
setMetaData("PrivacyCompactPolicy", compact.join("\n"));
}
// let them tell us if we should stay alive or not
else if (strncasecmp(buf, "Connection:", 11) == 0)
{
if (strncasecmp(trimLead(buf + 11), "Close", 5) == 0)
m_bKeepAlive = false;
else if (strncasecmp(trimLead(buf + 11), "Keep-Alive", 10)==0)
m_bKeepAlive = true;
else if (strncasecmp(trimLead(buf + 11), "Upgrade", 7)==0)
{
if (m_responseCode == 101) {
// Ok, an upgrade was accepted, now we must do it
upgradeRequired = true;
} else if (upgradeRequired) { // 426
// Nothing to do since we did it above already
} else {
// Just an offer to upgrade - no need to take it
canUpgrade = true;
}
}
}
// continue only if we know that we're HTTP/1.1
else if ( httpRev == HTTP_11) {
// what kind of encoding do we have? transfer?
if (strncasecmp(buf, "Transfer-Encoding:", 18) == 0) {
// If multiple encodings have been applied to an entity, the
// transfer-codings MUST be listed in the order in which they
// were applied.
addEncoding(trimLead(buf + 18), m_qTransferEncodings);
}
// md5 signature
else if (strncasecmp(buf, "Content-MD5:", 12) == 0) {
m_sContentMD5 = TQString::fromLatin1(trimLead(buf + 12));
}
// *** Responses to the HTTP OPTIONS method follow
// WebDAV capabilities
else if (strncasecmp(buf, "DAV:", 4) == 0) {
if (m_davCapabilities.isEmpty()) {
m_davCapabilities << TQString::fromLatin1(trimLead(buf + 4));
}
else {
m_davCapabilities << TQString::fromLatin1(trimLead(buf + 4));
}
}
// *** Responses to the HTTP OPTIONS method finished
}
else if ((httpRev == HTTP_None) && (strlen(buf) != 0))
{
// Remote server does not seem to speak HTTP at all
// Put the crap back into the buffer and hope for the best
rewind();
if (m_responseCode)
m_prevResponseCode = m_responseCode;
m_responseCode = 200; // Fake it
httpRev = HTTP_Unknown;
m_bKeepAlive = false;
break;
}
setRewindMarker();
// Clear out our buffer for further use.
memset(buffer, 0, sizeof(buffer));
} while (!m_bEOF && (len || noHeader) && (headerSize < maxHeaderSize) && (gets(buffer, sizeof(buffer)-1)));
// Now process the HTTP/1.1 upgrade
TQStringList::Iterator opt = upgradeOffers.begin();
for( ; opt != upgradeOffers.end(); ++opt) {
if (*opt == "TLS/1.0") {
if(upgradeRequired) {
if (!startTLS() && !usingTLS()) {
error(ERR_UPGRADE_REQUIRED, *opt);
return false;
}
}
} else if (*opt == "HTTP/1.1") {
httpRev = HTTP_11;
} else {
// unknown
if (upgradeRequired) {
error(ERR_UPGRADE_REQUIRED, *opt);
return false;
}
}
}
setMetaData("charset", m_request.strCharset);
// If we do not support the requested authentication method...
if ( (m_responseCode == 401 && Authentication == AUTH_None) ||
(m_responseCode == 407 && ProxyAuthentication == AUTH_None) )
{
m_bUnauthorized = false;
if (m_request.bErrorPage)
errorPage();
else
{
error( ERR_UNSUPPORTED_ACTION, "Unknown Authorization method!" );
return false;
}
}
// Fixup expire date for clock drift.
if (expireDate && (expireDate <= dateHeader))
expireDate = 1; // Already expired.
// Convert max-age into expireDate (overriding previous set expireDate)
if (maxAge == 0)
expireDate = 1; // Already expired.
else if (maxAge > 0)
{
if (currentAge)
maxAge -= currentAge;
if (maxAge <=0)
maxAge = 0;
expireDate = time(0) + maxAge;
}
if (!expireDate)
{
time_t lastModifiedDate = 0;
if (!m_request.lastModified.isEmpty())
lastModifiedDate = KRFCDate::parseDate(m_request.lastModified);
if (lastModifiedDate)
{
long diff = static_cast<long>(difftime(dateHeader, lastModifiedDate));
if (diff < 0)
expireDate = time(0) + 1;
else
expireDate = time(0) + (diff / 10);
}
else
{
expireDate = time(0) + DEFAULT_CACHE_EXPIRE;
}
}
// DONE receiving the header!
if (!cookieStr.isEmpty())
{
if ((m_request.cookieMode == HTTPRequest::CookiesAuto) && m_request.bUseCookiejar)
{
// Give cookies to the cookiejar.
TQString domain = config()->readEntry("cross-domain");
if (!domain.isEmpty() && isCrossDomainRequest(m_request.url.host(), domain))
cookieStr = "Cross-Domain\n" + cookieStr;
addCookies( m_request.url.url(), cookieStr );
}
else if (m_request.cookieMode == HTTPRequest::CookiesManual)
{
// Pass cookie to application
setMetaData("setcookies", cookieStr);
}
}
if (m_request.bMustRevalidate)
{
m_request.bMustRevalidate = false; // Reset just in case.
if (cacheValidated)
{
// Yippie, we can use the cached version.
// Update the cache with new "Expire" headers.
fclose(m_request.fcache);
m_request.fcache = 0;
updateExpireDate( expireDate, true );
m_request.fcache = checkCacheEntry( ); // Re-read cache entry
if (m_request.fcache)
{
m_request.bCachedRead = true;
goto try_again; // Read header again, but now from cache.
}
else
{
// Where did our cache entry go???
}
}
else
{
// Validation failed. Close cache.
fclose(m_request.fcache);
m_request.fcache = 0;
}
}
// We need to reread the header if we got a '100 Continue' or '102 Processing'
if ( cont )
{
goto try_again;
}
// Do not do a keep-alive connection if the size of the
// response is not known and the response is not Chunked.
if (!m_bChunked && (m_iSize == NO_SIZE))
m_bKeepAlive = false;
if ( m_responseCode == 204 )
{
return true;
}
// We need to try to login again if we failed earlier
if ( m_bUnauthorized )
{
if ( (m_responseCode == 401) ||
(m_bUseProxy && (m_responseCode == 407))
)
{
if ( getAuthorization() )
{
// for NTLM Authentication we have to keep the connection open!
if ( Authentication == AUTH_NTLM && m_strAuthorization.length() > 4 )
{
m_bKeepAlive = true;
readBody( true );
}
else if (ProxyAuthentication == AUTH_NTLM && m_strProxyAuthorization.length() > 4)
{
readBody( true );
}
else
httpCloseConnection();
return false; // Try again.
}
if (m_bError)
return false; // Error out
// Show error page...
}
m_bUnauthorized = false;
}
// We need to do a redirect
if (!locationStr.isEmpty())
{
KURL u(m_request.url, locationStr);
if(!u.isValid())
{
error(ERR_MALFORMED_URL, u.url());
return false;
}
if ((u.protocol() != "http") && (u.protocol() != "https") &&
(u.protocol() != "ftp") && (u.protocol() != "webdav") &&
(u.protocol() != "webdavs"))
{
redirection(u);
error(ERR_ACCESS_DENIED, u.url());
return false;
}
// preserve #ref: (bug 124654)
// if we were at http://host/resource1#ref, we sent a GET for "/resource1"
// if we got redirected to http://host/resource2, then we have to re-add
// the fragment:
if (m_request.url.hasRef() && !u.hasRef() &&
(m_request.url.host() == u.host()) &&
(m_request.url.protocol() == u.protocol()))
u.setRef(m_request.url.ref());
m_bRedirect = true;
m_redirectLocation = u;
if (!m_request.id.isEmpty())
{
sendMetaData();
}
kdDebug(7113) << "(" << m_pid << ") request.url: " << m_request.url.url()
<< endl << "LocationStr: " << locationStr.data() << endl;
kdDebug(7113) << "(" << m_pid << ") Requesting redirection to: " << u.url()
<< endl;
// If we're redirected to a http:// url, remember that we're doing webdav...
if (m_protocol == "webdav" || m_protocol == "webdavs")
u.setProtocol(m_protocol);
redirection(u);
m_request.bCachedWrite = false; // Turn off caching on re-direction (DA)
mayCache = false;
}
// Inform the job that we can indeed resume...
if ( bCanResume && m_request.offset )
canResume();
else
m_request.offset = 0;
// We don't cache certain text objects
if (m_strMimeType.startsWith("text/") &&
(m_strMimeType != "text/css") &&
(m_strMimeType != "text/x-javascript") &&
!hasCacheDirective)
{
// Do not cache secure pages or pages
// originating from password protected sites
// unless the webserver explicitly allows it.
if ( m_bIsSSL || (Authentication != AUTH_None) )
{
m_request.bCachedWrite = false;
mayCache = false;
}
}
// WABA: Correct for tgz files with a gzip-encoding.
// They really shouldn't put gzip in the Content-Encoding field!
// Web-servers really shouldn't do this: They let Content-Size refer
// to the size of the tgz file, not to the size of the tar file,
// while the Content-Type refers to "tar" instead of "tgz".
if (m_qContentEncodings.last() == "gzip")
{
if (m_strMimeType == "application/x-tar")
{
m_qContentEncodings.remove(m_qContentEncodings.fromLast());
m_strMimeType = TQString::fromLatin1("application/x-tgz");
}
else if (m_strMimeType == "application/postscript")
{
// LEONB: Adding another exception for psgz files.
// Could we use the mimelnk files instead of hardcoding all this?
m_qContentEncodings.remove(m_qContentEncodings.fromLast());
m_strMimeType = TQString::fromLatin1("application/x-gzpostscript");
}
else if ( m_request.allowCompressedPage &&
m_strMimeType != "application/x-tgz" &&
m_strMimeType != "application/x-targz" &&
m_strMimeType != "application/x-gzip" &&
m_request.url.path().right(6) == ".ps.gz" )
{
m_qContentEncodings.remove(m_qContentEncodings.fromLast());
m_strMimeType = TQString::fromLatin1("application/x-gzpostscript");
}
else if ( (m_request.allowCompressedPage &&
m_strMimeType == "text/html")
||
(m_request.allowCompressedPage &&
m_strMimeType != "application/x-tgz" &&
m_strMimeType != "application/x-targz" &&
m_strMimeType != "application/x-gzip" &&
m_request.url.path().right(3) != ".gz")
)
{
// Unzip!
}
else
{
m_qContentEncodings.remove(m_qContentEncodings.fromLast());
m_strMimeType = TQString::fromLatin1("application/x-gzip");
}
}
// We can't handle "bzip2" encoding (yet). So if we get something with
// bzip2 encoding, we change the mimetype to "application/x-bzip2".
// Note for future changes: some web-servers send both "bzip2" as
// encoding and "application/x-bzip2" as mimetype. That is wrong.
// currently that doesn't bother us, because we remove the encoding
// and set the mimetype to x-bzip2 anyway.
if (m_qContentEncodings.last() == "bzip2")
{
m_qContentEncodings.remove(m_qContentEncodings.fromLast());
m_strMimeType = TQString::fromLatin1("application/x-bzip2");
}
// Convert some common mimetypes to standard KDE mimetypes
if (m_strMimeType == "application/x-targz")
m_strMimeType = TQString::fromLatin1("application/x-tgz");
else if (m_strMimeType == "application/zip")
m_strMimeType = TQString::fromLatin1("application/x-zip");
else if (m_strMimeType == "image/x-png")
m_strMimeType = TQString::fromLatin1("image/png");
else if (m_strMimeType == "image/bmp")
m_strMimeType = TQString::fromLatin1("image/x-bmp");
else if (m_strMimeType == "audio/mpeg" || m_strMimeType == "audio/x-mpeg" || m_strMimeType == "audio/mp3")
m_strMimeType = TQString::fromLatin1("audio/x-mp3");
else if (m_strMimeType == "audio/microsoft-wave")
m_strMimeType = TQString::fromLatin1("audio/x-wav");
else if (m_strMimeType == "audio/midi")
m_strMimeType = TQString::fromLatin1("audio/x-midi");
else if (m_strMimeType == "image/x-xpixmap")
m_strMimeType = TQString::fromLatin1("image/x-xpm");
else if (m_strMimeType == "application/rtf")
m_strMimeType = TQString::fromLatin1("text/rtf");
// Crypto ones....
else if (m_strMimeType == "application/pkix-cert" ||
m_strMimeType == "application/binary-certificate")
{
m_strMimeType = TQString::fromLatin1("application/x-x509-ca-cert");
}
// Prefer application/x-tgz or x-gzpostscript over application/x-gzip.
else if (m_strMimeType == "application/x-gzip")
{
if ((m_request.url.path().right(7) == ".tar.gz") ||
(m_request.url.path().right(4) == ".tar"))
m_strMimeType = TQString::fromLatin1("application/x-tgz");
if ((m_request.url.path().right(6) == ".ps.gz"))
m_strMimeType = TQString::fromLatin1("application/x-gzpostscript");
}
// Some webservers say "text/plain" when they mean "application/x-bzip2"
else if ((m_strMimeType == "text/plain") || (m_strMimeType == "application/octet-stream"))
{
TQString ext = m_request.url.path().right(4).upper();
if (ext == ".BZ2")
m_strMimeType = TQString::fromLatin1("application/x-bzip2");
else if (ext == ".PEM")
m_strMimeType = TQString::fromLatin1("application/x-x509-ca-cert");
else if (ext == ".SWF")
m_strMimeType = TQString::fromLatin1("application/x-shockwave-flash");
else if (ext == ".PLS")
m_strMimeType = TQString::fromLatin1("audio/x-scpls");
else if (ext == ".WMV")
m_strMimeType = TQString::fromLatin1("video/x-ms-wmv");
}
#if 0
// Even if we can't rely on content-length, it seems that we should
// never get more data than content-length. Maybe less, if the
// content-length refers to the unzipped data.
if (!m_qContentEncodings.isEmpty())
{
// If we still have content encoding we can't rely on the Content-Length.
m_iSize = NO_SIZE;
}
#endif
if( !dispositionType.isEmpty() )
{
kdDebug(7113) << "(" << m_pid << ") Setting Content-Disposition type to: "
<< dispositionType << endl;
setMetaData("content-disposition-type", dispositionType);
}
if( !dispositionFilename.isEmpty() )
{
kdDebug(7113) << "(" << m_pid << ") Setting Content-Disposition filename to: "
<< dispositionFilename << endl;
// ### KDE4: setting content-disposition to filename for pre 3.5.2 compatability
setMetaData("content-disposition", dispositionFilename);
setMetaData("content-disposition-filename", dispositionFilename);
}
if (!m_request.lastModified.isEmpty())
setMetaData("modified", m_request.lastModified);
if (!mayCache)
{
setMetaData("no-cache", "true");
setMetaData("expire-date", "1"); // Expired
}
else
{
TQString tmp;
tmp.setNum(expireDate);
setMetaData("expire-date", tmp);
tmp.setNum(time(0)); // Cache entry will be created shortly.
setMetaData("cache-creation-date", tmp);
}
// Let the app know about the mime-type iff this is not
// a redirection and the mime-type string is not empty.
if (locationStr.isEmpty() && (!m_strMimeType.isEmpty() ||
m_request.method == HTTP_HEAD))
{
kdDebug(7113) << "(" << m_pid << ") Emitting mimetype " << m_strMimeType << endl;
mimeType( m_strMimeType );
}
// Do not move send response header before any redirection as it seems
// to screw up some sites. See BR# 150904.
forwardHttpResponseHeader();
if (m_request.method == HTTP_HEAD)
return true;
// Do we want to cache this request?
if (m_request.bUseCache)
{
::unlink( TQFile::encodeName(m_request.cef));
if ( m_request.bCachedWrite && !m_strMimeType.isEmpty() )
{
// Check...
createCacheEntry(m_strMimeType, expireDate); // Create a cache entry
if (!m_request.fcache)
{
m_request.bCachedWrite = false; // Error creating cache entry.
kdDebug(7113) << "(" << m_pid << ") Error creating cache entry for " << m_request.url.url()<<"!\n";
}
m_request.expireDate = expireDate;
m_maxCacheSize = config()->readNumEntry("MaxCacheSize", DEFAULT_MAX_CACHE_SIZE) / 2;
}
}
if (m_request.bCachedWrite && !m_strMimeType.isEmpty())
kdDebug(7113) << "(" << m_pid << ") Cache, adding \"" << m_request.url.url() << "\"" << endl;
else if (m_request.bCachedWrite && m_strMimeType.isEmpty())
kdDebug(7113) << "(" << m_pid << ") Cache, pending \"" << m_request.url.url() << "\"" << endl;
else
kdDebug(7113) << "(" << m_pid << ") Cache, not adding \"" << m_request.url.url() << "\"" << endl;
return true;
}
void HTTPProtocol::addEncoding(TQString encoding, TQStringList &encs)
{
encoding = encoding.stripWhiteSpace().lower();
// Identity is the same as no encoding
if (encoding == "identity") {
return;
} else if (encoding == "8bit") {
// Strange encoding returned by http://linac.ikp.physik.tu-darmstadt.de
return;
} else if (encoding == "chunked") {
m_bChunked = true;
// Anyone know of a better way to handle unknown sizes possibly/ideally with unsigned ints?
//if ( m_cmd != CMD_COPY )
m_iSize = NO_SIZE;
} else if ((encoding == "x-gzip") || (encoding == "gzip")) {
encs.append(TQString::fromLatin1("gzip"));
} else if ((encoding == "x-bzip2") || (encoding == "bzip2")) {
encs.append(TQString::fromLatin1("bzip2")); // Not yet supported!
} else if ((encoding == "x-deflate") || (encoding == "deflate")) {
encs.append(TQString::fromLatin1("deflate"));
} else {
kdDebug(7113) << "(" << m_pid << ") Unknown encoding encountered. "
<< "Please write code. Encoding = \"" << encoding
<< "\"" << endl;
}
}
bool HTTPProtocol::sendBody()
{
int result=-1;
int length=0;
infoMessage( i18n( "Requesting data to send" ) );
// m_bufPOST will NOT be empty iff authentication was required before posting
// the data OR a re-connect is requested from ::readHeader because the
// connection was lost for some reason.
if ( !m_bufPOST.isNull() )
{
kdDebug(7113) << "(" << m_pid << ") POST'ing saved data..." << endl;
result = 0;
length = m_bufPOST.size();
}
else
{
kdDebug(7113) << "(" << m_pid << ") POST'ing live data..." << endl;
TQByteArray buffer;
int old_size;
m_bufPOST.resize(0);
do
{
dataReq(); // Request for data
result = readData( buffer );
if ( result > 0 )
{
length += result;
old_size = m_bufPOST.size();
m_bufPOST.resize( old_size+result );
memcpy( m_bufPOST.data()+ old_size, buffer.data(), buffer.size() );
buffer.resize(0);
}
} while ( result > 0 );
}
if ( result < 0 )
{
error( ERR_ABORTED, m_request.hostname );
return false;
}
infoMessage( i18n( "Sending data to %1" ).arg( m_request.hostname ) );
TQString size = TQString ("Content-Length: %1\r\n\r\n").arg(length);
kdDebug( 7113 ) << "(" << m_pid << ")" << size << endl;
// Send the content length...
bool sendOk = (write(size.latin1(), size.length()) == (ssize_t) size.length());
if (!sendOk)
{
kdDebug( 7113 ) << "(" << m_pid << ") Connection broken when sending "
<< "content length: (" << m_state.hostname << ")" << endl;
error( ERR_CONNECTION_BROKEN, m_state.hostname );
return false;
}
// Send the data...
// kdDebug( 7113 ) << "(" << m_pid << ") POST DATA: " << TQCString(m_bufPOST) << endl;
sendOk = (write(m_bufPOST.data(), m_bufPOST.size()) == (ssize_t) m_bufPOST.size());
if (!sendOk)
{
kdDebug(7113) << "(" << m_pid << ") Connection broken when sending message body: ("
<< m_state.hostname << ")" << endl;
error( ERR_CONNECTION_BROKEN, m_state.hostname );
return false;
}
return true;
}
void HTTPProtocol::httpClose( bool keepAlive )
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpClose" << endl;
if (m_request.fcache)
{
fclose(m_request.fcache);
m_request.fcache = 0;
if (m_request.bCachedWrite)
{
TQString filename = m_request.cef + ".new";
::unlink( TQFile::encodeName(filename) );
}
}
// Only allow persistent connections for GET requests.
// NOTE: we might even want to narrow this down to non-form
// based submit requests which will require a meta-data from
// khtml.
if (keepAlive && (!m_bUseProxy ||
m_bPersistentProxyConnection || m_bIsTunneled))
{
if (!m_keepAliveTimeout)
m_keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT;
else if (m_keepAliveTimeout > 2*DEFAULT_KEEP_ALIVE_TIMEOUT)
m_keepAliveTimeout = 2*DEFAULT_KEEP_ALIVE_TIMEOUT;
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpClose: keep alive (" << m_keepAliveTimeout << ")" << endl;
TQByteArray data;
TQDataStream stream( data, IO_WriteOnly );
stream << int(99); // special: Close connection
setTimeoutSpecialCommand(m_keepAliveTimeout, data);
return;
}
httpCloseConnection();
}
void HTTPProtocol::closeConnection()
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::closeConnection" << endl;
httpCloseConnection ();
}
void HTTPProtocol::httpCloseConnection ()
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpCloseConnection" << endl;
m_bIsTunneled = false;
m_bKeepAlive = false;
closeDescriptor();
setTimeoutSpecialCommand(-1); // Cancel any connection timeout
}
void HTTPProtocol::slave_status()
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::slave_status" << endl;
if ( m_iSock != -1 && !isConnectionValid() )
httpCloseConnection();
slaveStatus( m_state.hostname, (m_iSock != -1) );
}
void HTTPProtocol::mimetype( const KURL& url )
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::mimetype: "
<< url.prettyURL() << endl;
if ( !checkRequestURL( url ) )
return;
m_request.method = HTTP_HEAD;
m_request.path = url.path();
m_request.query = url.query();
m_request.cache = CC_Cache;
m_request.doProxy = m_bUseProxy;
retrieveHeader();
kdDebug(7113) << "(" << m_pid << ") http: mimetype = " << m_strMimeType
<< endl;
}
void HTTPProtocol::special( const TQByteArray &data )
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::special" << endl;
int tmp;
TQDataStream stream(data, IO_ReadOnly);
stream >> tmp;
switch (tmp) {
case 1: // HTTP POST
{
KURL url;
stream >> url;
post( url );
break;
}
case 2: // cache_update
{
KURL url;
bool no_cache;
time_t expireDate;
stream >> url >> no_cache >> expireDate;
cacheUpdate( url, no_cache, expireDate );
break;
}
case 5: // WebDAV lock
{
KURL url;
TQString scope, type, owner;
stream >> url >> scope >> type >> owner;
davLock( url, scope, type, owner );
break;
}
case 6: // WebDAV unlock
{
KURL url;
stream >> url;
davUnlock( url );
break;
}
case 7: // Generic WebDAV
{
KURL url;
int method;
stream >> url >> method;
davGeneric( url, (KIO::HTTP_METHOD) method );
break;
}
case 99: // Close Connection
{
httpCloseConnection();
break;
}
default:
// Some command we don't understand.
// Just ignore it, it may come from some future version of KDE.
break;
}
}
/**
* Read a chunk from the data stream.
*/
int HTTPProtocol::readChunked()
{
if ((m_iBytesLeft == 0) || (m_iBytesLeft == NO_SIZE))
{
setRewindMarker();
m_bufReceive.resize(4096);
if (!gets(m_bufReceive.data(), m_bufReceive.size()-1))
{
kdDebug(7113) << "(" << m_pid << ") gets() failure on Chunk header" << endl;
return -1;
}
// We could have got the CRLF of the previous chunk.
// If so, try again.
if (m_bufReceive[0] == '\0')
{
if (!gets(m_bufReceive.data(), m_bufReceive.size()-1))
{
kdDebug(7113) << "(" << m_pid << ") gets() failure on Chunk header" << endl;
return -1;
}
}
// m_bEOF is set to true when read called from gets returns 0. For chunked reading 0
// means end of chunked transfer and not error. See RFC 2615 section 3.6.1
#if 0
if (m_bEOF)
{
kdDebug(7113) << "(" << m_pid << ") EOF on Chunk header" << endl;
return -1;
}
#endif
long long trunkSize = STRTOLL(m_bufReceive.data(), 0, 16);
if (trunkSize < 0)
{
kdDebug(7113) << "(" << m_pid << ") Negative chunk size" << endl;
return -1;
}
m_iBytesLeft = trunkSize;
// kdDebug(7113) << "(" << m_pid << ") Chunk size = " << m_iBytesLeft << " bytes" << endl;
if (m_iBytesLeft == 0)
{
// Last chunk.
// Skip trailers.
do {
// Skip trailer of last chunk.
if (!gets(m_bufReceive.data(), m_bufReceive.size()-1))
{
kdDebug(7113) << "(" << m_pid << ") gets() failure on Chunk trailer" << endl;
return -1;
}
// kdDebug(7113) << "(" << m_pid << ") Chunk trailer = \"" << m_bufReceive.data() << "\"" << endl;
}
while (strlen(m_bufReceive.data()) != 0);
return 0;
}
}
int bytesReceived = readLimited();
if (!m_iBytesLeft)
m_iBytesLeft = NO_SIZE; // Don't stop, continue with next chunk
// kdDebug(7113) << "(" << m_pid << ") readChunked: BytesReceived=" << bytesReceived << endl;
return bytesReceived;
}
int HTTPProtocol::readLimited()
{
if (!m_iBytesLeft)
return 0;
m_bufReceive.resize(4096);
int bytesReceived;
int bytesToReceive;
if (m_iBytesLeft > m_bufReceive.size())
bytesToReceive = m_bufReceive.size();
else
bytesToReceive = m_iBytesLeft;
bytesReceived = read(m_bufReceive.data(), bytesToReceive);
if (bytesReceived <= 0)
return -1; // Error: connection lost
m_iBytesLeft -= bytesReceived;
return bytesReceived;
}
int HTTPProtocol::readUnlimited()
{
if (m_bKeepAlive)
{
kdDebug(7113) << "(" << m_pid << ") Unbounded datastream on a Keep "
<< "alive connection!" << endl;
m_bKeepAlive = false;
}
m_bufReceive.resize(4096);
int result = read(m_bufReceive.data(), m_bufReceive.size());
if (result > 0)
return result;
m_bEOF = true;
m_iBytesLeft = 0;
return 0;
}
void HTTPProtocol::slotData(const TQByteArray &_d)
{
if (!_d.size())
{
m_bEOD = true;
return;
}
if (m_iContentLeft != NO_SIZE)
{
if (m_iContentLeft >= _d.size())
m_iContentLeft -= _d.size();
else
m_iContentLeft = NO_SIZE;
}
TQByteArray d = _d;
if ( !m_dataInternal )
{
// If a broken server does not send the mime-type,
// we try to id it from the content before dealing
// with the content itself.
if ( m_strMimeType.isEmpty() && !m_bRedirect &&
!( m_responseCode >= 300 && m_responseCode <=399) )
{
kdDebug(7113) << "(" << m_pid << ") Determining mime-type from content..." << endl;
int old_size = m_mimeTypeBuffer.size();
m_mimeTypeBuffer.resize( old_size + d.size() );
memcpy( m_mimeTypeBuffer.data() + old_size, d.data(), d.size() );
if ( (m_iBytesLeft != NO_SIZE) && (m_iBytesLeft > 0)
&& (m_mimeTypeBuffer.size() < 1024) )
{
m_cpMimeBuffer = true;
return; // Do not send up the data since we do not yet know its mimetype!
}
kdDebug(7113) << "(" << m_pid << ") Mimetype buffer size: " << m_mimeTypeBuffer.size()
<< endl;
KMimeMagicResult *result;
result = KMimeMagic::self()->findBufferFileType( m_mimeTypeBuffer,
m_request.url.fileName() );
if( result )
{
m_strMimeType = result->mimeType();
kdDebug(7113) << "(" << m_pid << ") Mimetype from content: "
<< m_strMimeType << endl;
}
if ( m_strMimeType.isEmpty() )
{
m_strMimeType = TQString::fromLatin1( DEFAULT_MIME_TYPE );
kdDebug(7113) << "(" << m_pid << ") Using default mimetype: "
<< m_strMimeType << endl;
}
if ( m_request.bCachedWrite )
{
createCacheEntry( m_strMimeType, m_request.expireDate );
if (!m_request.fcache)
m_request.bCachedWrite = false;
}
if ( m_cpMimeBuffer )
{
// Do not make any assumption about the state of the TQByteArray we received.
// Fix the crash described by BR# 130104.
d.detach();
d.resize(0);
d.resize(m_mimeTypeBuffer.size());
memcpy( d.data(), m_mimeTypeBuffer.data(),
d.size() );
}
mimeType(m_strMimeType);
m_mimeTypeBuffer.resize(0);
}
data( d );
if (m_request.bCachedWrite && m_request.fcache)
writeCacheEntry(d.data(), d.size());
}
else
{
uint old_size = m_bufWebDavData.size();
m_bufWebDavData.resize (old_size + d.size());
memcpy (m_bufWebDavData.data() + old_size, d.data(), d.size());
}
}
/**
* This function is our "receive" function. It is responsible for
* downloading the message (not the header) from the HTTP server. It
* is called either as a response to a client's KIOJob::dataEnd()
* (meaning that the client is done sending data) or by 'httpOpen()'
* (if we are in the process of a PUT/POST request). It can also be
* called by a webDAV function, to receive stat/list/property/etc.
* data; in this case the data is stored in m_bufWebDavData.
*/
bool HTTPProtocol::readBody( bool dataInternal /* = false */ )
{
if (m_responseCode == 204)
return true;
m_bEOD = false;
// Note that when dataInternal is true, we are going to:
// 1) save the body data to a member variable, m_bufWebDavData
// 2) _not_ advertise the data, speed, size, etc., through the
// corresponding functions.
// This is used for returning data to WebDAV.
m_dataInternal = dataInternal;
if ( dataInternal )
m_bufWebDavData.resize (0);
// Check if we need to decode the data.
// If we are in copy mode, then use only transfer decoding.
bool useMD5 = !m_sContentMD5.isEmpty();
// Deal with the size of the file.
KIO::filesize_t sz = m_request.offset;
if ( sz )
m_iSize += sz;
// Update the application with total size except when
// it is compressed, or when the data is to be handled
// internally (webDAV). If compressed we have to wait
// until we uncompress to find out the actual data size
if ( !dataInternal ) {
if ( (m_iSize > 0) && (m_iSize != NO_SIZE)) {
totalSize(m_iSize);
infoMessage( i18n( "Retrieving %1 from %2...").arg(KIO::convertSize(m_iSize))
.arg( m_request.hostname ) );
}
else
{
totalSize ( 0 );
}
}
else
infoMessage( i18n( "Retrieving from %1..." ).arg( m_request.hostname ) );
if (m_request.bCachedRead)
{
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readBody: read data from cache!" << endl;
m_request.bCachedWrite = false;
char buffer[ MAX_IPC_SIZE ];
m_iContentLeft = NO_SIZE;
// Jippie! It's already in the cache :-)
while (!feof(m_request.fcache) && !ferror(m_request.fcache))
{
int nbytes = fread( buffer, 1, MAX_IPC_SIZE, m_request.fcache);
if (nbytes > 0)
{
m_bufReceive.setRawData( buffer, nbytes);
slotData( m_bufReceive );
m_bufReceive.resetRawData( buffer, nbytes );
sz += nbytes;
}
}
m_bufReceive.resize( 0 );
if ( !dataInternal )
{
processedSize( sz );
data( TQByteArray() );
}
return true;
}
if (m_iSize != NO_SIZE)
m_iBytesLeft = m_iSize - sz;
else
m_iBytesLeft = NO_SIZE;
m_iContentLeft = m_iBytesLeft;
if (m_bChunked)
m_iBytesLeft = NO_SIZE;
kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readBody: retrieve data. "
<< KIO::number(m_iBytesLeft) << " left." << endl;
// Main incoming loop... Gather everything while we can...
m_cpMimeBuffer = false;
m_mimeTypeBuffer.resize(0);
struct timeval last_tv;
gettimeofday( &last_tv, 0L );
HTTPFilterChain chain;
TQObject::connect(&chain, TQT_SIGNAL(output(const TQByteArray &)),
this, TQT_SLOT(slotData(const TQByteArray &)));
TQObject::connect(&chain, TQT_SIGNAL(error(int, const TQString &)),
this, TQT_SLOT(error(int, const TQString &)));
// decode all of the transfer encodings
while (!m_qTransferEncodings.isEmpty())
{
TQString enc = m_qTransferEncodings.last();
m_qTransferEncodings.remove(m_qTransferEncodings.fromLast());
if ( enc == "gzip" )
chain.addFilter(new HTTPFilterGZip);
else if ( enc == "deflate" )
chain.addFilter(new HTTPFilterDeflate);
}
// From HTTP 1.1 Draft 6:
// The MD5 digest is computed based on the content of the entity-body,
// including any content-coding that has been applied, but not including
// any transfer-encoding applied to the message-body. If the message is
// received with a transfer-encoding, that encoding MUST be removed
// prior to checking the Content-MD5 value against the received entity.
HTTPFilterMD5 *md5Filter = 0;
if ( useMD5 )
{
md5Filter = new HTTPFilterMD5;
chain.addFilter(md5Filter);
}
// now decode all of the content encodings
// -- Why ?? We are not
// -- a proxy server, be a client side implementation!! The applications
// -- are capable of determinig how to extract the encoded implementation.
// WB: That's a misunderstanding. We are free to remove the encoding.
// WB: Some braindead www-servers however, give .tgz files an encoding
// WB: of "gzip" (or even "x-gzip") and a content-type of "applications/tar"
// WB: They shouldn't do that. We can work around that though...
while (!m_qContentEncodings.isEmpty())
{
TQString enc = m_qContentEncodings.last();
m_qContentEncodings.remove(m_qContentEncodings.fromLast());
if ( enc == "gzip" )
chain.addFilter(new HTTPFilterGZip);
else if ( enc == "deflate" )
chain.addFilter(new HTTPFilterDeflate);
}
while (!m_bEOF)
{
int bytesReceived;
if (m_bChunked)
bytesReceived = readChunked();
else if (m_iSize != NO_SIZE)
bytesReceived = readLimited();
else
bytesReceived = readUnlimited();
// make sure that this wasn't an error, first
// kdDebug(7113) << "(" << (int) m_pid << ") readBody: bytesReceived: "
// << (int) bytesReceived << " m_iSize: " << (int) m_iSize << " Chunked: "
// << (int) m_bChunked << " BytesLeft: "<< (int) m_iBytesLeft << endl;
if (bytesReceived == -1)
{
if (m_iContentLeft == 0)
{
// gzip'ed data sometimes reports a too long content-length.
// (The length of the unzipped data)
m_iBytesLeft = 0;
break;
}
// Oh well... log an error and bug out
kdDebug(7113) << "(" << m_pid << ") readBody: bytesReceived==-1 sz=" << (int)sz
<< " Connnection broken !" << endl;
error(ERR_CONNECTION_BROKEN, m_state.hostname);
return false;
}
// I guess that nbytes == 0 isn't an error.. but we certainly
// won't work with it!
if (bytesReceived > 0)
{
// Important: truncate the buffer to the actual size received!
// Otherwise garbage will be passed to the app
m_bufReceive.truncate( bytesReceived );
chain.slotInput(m_bufReceive);
if (m_bError)
return false;
sz += bytesReceived;
if (!dataInternal)
processedSize( sz );
}
m_bufReceive.resize(0); // res
if (m_iBytesLeft && m_bEOD && !m_bChunked)
{
// gzip'ed data sometimes reports a too long content-length.
// (The length of the unzipped data)
m_iBytesLeft = 0;
}
if (m_iBytesLeft == 0)
{
kdDebug(7113) << "("<<m_pid<<") EOD received! Left = "<< KIO::number(m_iBytesLeft) << endl;
break;
}
}
chain.slotInput(TQByteArray()); // Flush chain.
if ( useMD5 )
{
TQString calculatedMD5 = md5Filter->md5();
if ( m_sContentMD5 == calculatedMD5 )
kdDebug(7113) << "(" << m_pid << ") MD5 checksum MATCHED!!" << endl;
else
kdDebug(7113) << "(" << m_pid << ") MD5 checksum MISMATCH! Expected: "
<< calculatedMD5 << ", Got: " << m_sContentMD5 << endl;
}
// Close cache entry
if (m_iBytesLeft == 0)
{
if (m_request.bCachedWrite && m_request.fcache)
closeCacheEntry();
else if (m_request.bCachedWrite)
kdDebug(7113) << "(" << m_pid << ") no cache file!\n";
}
else
{
kdDebug(7113) << "(" << m_pid << ") still "<< KIO::number(m_iBytesLeft)
<< " bytes left! can't close cache entry!\n";
}
if (sz <= 1)
{
/* kdDebug(7113) << "(" << m_pid << ") readBody: sz = " << KIO::number(sz)
<< ", responseCode =" << m_responseCode << endl; */
if (m_responseCode >= 500 && m_responseCode <= 599)
error(ERR_INTERNAL_SERVER, m_state.hostname);
else if (m_responseCode >= 400 && m_responseCode <= 499)
error(ERR_DOES_NOT_EXIST, m_state.hostname);
}
if (!dataInternal)
data( TQByteArray() );
return true;
}
void HTTPProtocol::error( int _err, const TQString &_text )
{
httpClose(false);
if (!m_request.id.isEmpty())
{
forwardHttpResponseHeader();
sendMetaData();
}
// Clear of the temporary POST buffer if it is not empty...
if (!m_bufPOST.isEmpty())
{
m_bufPOST.resize(0);
kdDebug(7113) << "(" << m_pid << ") HTTP::retreiveHeader: Cleared POST "
"buffer..." << endl;
}
SlaveBase::error( _err, _text );
m_bError = true;
}
void HTTPProtocol::addCookies( const TQString &url, const TQCString &cookieHeader )
{
long windowId = m_request.window.toLong();
TQByteArray params;
TQDataStream stream(params, IO_WriteOnly);
stream << url << cookieHeader << windowId;
kdDebug(7113) << "(" << m_pid << ") " << cookieHeader << endl;
kdDebug(7113) << "(" << m_pid << ") " << "Window ID: "
<< windowId << ", for host = " << url << endl;
if ( !dcopClient()->send( "kded", "kcookiejar", "addCookies(TQString,TQCString,long int)", params ) )
{
kdWarning(7113) << "(" << m_pid << ") Can't communicate with kded_kcookiejar!" << endl;
}
}
TQString HTTPProtocol::findCookies( const TQString &url)
{
TQCString replyType;
TQByteArray params;
TQByteArray reply;
TQString result;
long windowId = m_request.window.toLong();
result = TQString::null;
TQDataStream stream(params, IO_WriteOnly);
stream << url << windowId;
if ( !dcopClient()->call( "kded", "kcookiejar", "findCookies(TQString,long int)",
params, replyType, reply ) )
{
kdWarning(7113) << "(" << m_pid << ") Can't communicate with kded_kcookiejar!" << endl;
return result;
}
if ( replyType == "TQString" )
{
TQDataStream stream2( reply, IO_ReadOnly );
stream2 >> result;
}
else
{
kdError(7113) << "(" << m_pid << ") DCOP function findCookies(...) returns "
<< replyType << ", expected TQString" << endl;
}
return result;
}
/******************************* CACHING CODE ****************************/
void HTTPProtocol::cacheUpdate( const KURL& url, bool no_cache, time_t expireDate)
{
if ( !checkRequestURL( url ) )
return;
m_request.path = url.path();
m_request.query = url.query();
m_request.cache = CC_Reload;
m_request.doProxy = m_bUseProxy;
if (no_cache)
{
m_request.fcache = checkCacheEntry( );
if (m_request.fcache)
{
fclose(m_request.fcache);
m_request.fcache = 0;
::unlink( TQFile::encodeName(m_request.cef) );
}
}
else
{
updateExpireDate( expireDate );
}
finished();
}
// !START SYNC!
// The following code should be kept in sync
// with the code in http_cache_cleaner.cpp
FILE* HTTPProtocol::checkCacheEntry( bool readWrite)
{
const TQChar separator = '_';
TQString CEF = m_request.path;
int p = CEF.find('/');
while(p != -1)
{
CEF[p] = separator;
p = CEF.find('/', p);
}
TQString host = m_request.hostname.lower();
CEF = host + CEF + '_';
TQString dir = m_strCacheDir;
if (dir[dir.length()-1] != '/')
dir += "/";
int l = host.length();
for(int i = 0; i < l; i++)
{
if (host[i].isLetter() && (host[i] != 'w'))
{
dir += host[i];
break;
}
}
if (dir[dir.length()-1] == '/')
dir += "0";
unsigned long hash = 0x00000000;
TQCString u = m_request.url.url().latin1();
for(int i = u.length(); i--;)
{
hash = (hash * 12211 + static_cast<const char>(u.at(i))) % 2147483563;
}
TQString hashString;
hashString.sprintf("%08lx", hash);
CEF = CEF + hashString;
CEF = dir + "/" + CEF;
m_request.cef = CEF;
const char *mode = (readWrite ? "r+" : "r");
FILE *fs = fopen( TQFile::encodeName(CEF), mode); // Open for reading and writing
if (!fs)
return 0;
char buffer[401];
bool ok = true;
// CacheRevision
if (ok && (!fgets(buffer, 400, fs)))
ok = false;
if (ok && (strcmp(buffer, CACHE_REVISION) != 0))
ok = false;
time_t date;
time_t currentDate = time(0);
// URL
if (ok && (!fgets(buffer, 400, fs)))
ok = false;
if (ok)
{
int l = strlen(buffer);
if (l>0)
buffer[l-1] = 0; // Strip newline
if (m_request.url.url() != buffer)
{
ok = false; // Hash collision
}
}
// Creation Date
if (ok && (!fgets(buffer, 400, fs)))
ok = false;
if (ok)
{
date = (time_t) strtoul(buffer, 0, 10);
m_request.creationDate = date;
if (m_maxCacheAge && (difftime(currentDate, date) > m_maxCacheAge))
{
m_request.bMustRevalidate = true;
m_request.expireDate = currentDate;
}
}
// Expiration Date
m_request.cacheExpireDateOffset = ftell(fs);
if (ok && (!fgets(buffer, 400, fs)))
ok = false;
if (ok)
{
if (m_request.cache == CC_Verify)
{
date = (time_t) strtoul(buffer, 0, 10);
// After the expire date we need to revalidate.
if (!date || difftime(currentDate, date) >= 0)
m_request.bMustRevalidate = true;
m_request.expireDate = date;
}
else if (m_request.cache == CC_Refresh)
{
m_request.bMustRevalidate = true;
m_request.expireDate = currentDate;
}
}
// ETag
if (ok && (!fgets(buffer, 400, fs)))
ok = false;
if (ok)
{
m_request.etag = TQString(buffer).stripWhiteSpace();
}
// Last-Modified
if (ok && (!fgets(buffer, 400, fs)))
ok = false;
if (ok)
{
m_request.lastModified = TQString(buffer).stripWhiteSpace();
}
if (ok)
return fs;
fclose(fs);
unlink( TQFile::encodeName(CEF));
return 0;
}
void HTTPProtocol::updateExpireDate(time_t expireDate, bool updateCreationDate)
{
bool ok = true;
FILE *fs = checkCacheEntry(true);
if (fs)
{
TQString date;
char buffer[401];
time_t creationDate;
fseek(fs, 0, SEEK_SET);
if (ok && !fgets(buffer, 400, fs))
ok = false;
if (ok && !fgets(buffer, 400, fs))
ok = false;
long cacheCreationDateOffset = ftell(fs);
if (ok && !fgets(buffer, 400, fs))
ok = false;
creationDate = strtoul(buffer, 0, 10);
if (!creationDate)
ok = false;
if (updateCreationDate)
{
if (!ok || fseek(fs, cacheCreationDateOffset, SEEK_SET))
return;
TQString date;
date.setNum( time(0) );
date = date.leftJustify(16);
fputs(date.latin1(), fs); // Creation date
fputc('\n', fs);
}
if (expireDate>(30*365*24*60*60))
{
// expire date is a really a big number, it can't be
// a relative date.
date.setNum( expireDate );
}
else
{
// expireDate before 2000. those values must be
// interpreted as relative expiration dates from
// <META http-equiv="Expires"> tags.
// so we have to scan the creation time and add
// it to the expiryDate
date.setNum( creationDate + expireDate );
}
date = date.leftJustify(16);
if (!ok || fseek(fs, m_request.cacheExpireDateOffset, SEEK_SET))
return;
fputs(date.latin1(), fs); // Expire date
fseek(fs, 0, SEEK_END);
fclose(fs);
}
}
void HTTPProtocol::createCacheEntry( const TQString &mimetype, time_t expireDate)
{
TQString dir = m_request.cef;
int p = dir.findRev('/');
if (p == -1) return; // Error.
dir.truncate(p);
// Create file
(void) ::mkdir( TQFile::encodeName(dir), 0700 );
TQString filename = m_request.cef + ".new"; // Create a new cache entryexpireDate
// kdDebug( 7103 ) << "creating new cache entry: " << filename << endl;
m_request.fcache = fopen( TQFile::encodeName(filename), "w");
if (!m_request.fcache)
{
kdWarning(7113) << "(" << m_pid << ")createCacheEntry: opening " << filename << " failed." << endl;
return; // Error.
}
fputs(CACHE_REVISION, m_request.fcache); // Revision
fputs(m_request.url.url().latin1(), m_request.fcache); // Url
fputc('\n', m_request.fcache);
TQString date;
m_request.creationDate = time(0);
date.setNum( m_request.creationDate );
date = date.leftJustify(16);
fputs(date.latin1(), m_request.fcache); // Creation date
fputc('\n', m_request.fcache);
date.setNum( expireDate );
date = date.leftJustify(16);
fputs(date.latin1(), m_request.fcache); // Expire date
fputc('\n', m_request.fcache);
if (!m_request.etag.isEmpty())
fputs(m_request.etag.latin1(), m_request.fcache); //ETag
fputc('\n', m_request.fcache);
if (!m_request.lastModified.isEmpty())
fputs(m_request.lastModified.latin1(), m_request.fcache); // Last modified
fputc('\n', m_request.fcache);
fputs(mimetype.latin1(), m_request.fcache); // Mimetype
fputc('\n', m_request.fcache);
if (!m_request.strCharset.isEmpty())
fputs(m_request.strCharset.latin1(), m_request.fcache); // Charset
fputc('\n', m_request.fcache);
return;
}
// The above code should be kept in sync
// with the code in http_cache_cleaner.cpp
// !END SYNC!
void HTTPProtocol::writeCacheEntry( const char *buffer, int nbytes)
{
if (fwrite( buffer, nbytes, 1, m_request.fcache) != 1)
{
kdWarning(7113) << "(" << m_pid << ") writeCacheEntry: writing " << nbytes << " bytes failed." << endl;
fclose(m_request.fcache);
m_request.fcache = 0;
TQString filename = m_request.cef + ".new";
::unlink( TQFile::encodeName(filename) );
return;
}
long file_pos = ftell( m_request.fcache ) / 1024;
if ( file_pos > m_maxCacheSize )
{
kdDebug(7113) << "writeCacheEntry: File size reaches " << file_pos
<< "Kb, exceeds cache limits. (" << m_maxCacheSize << "Kb)" << endl;
fclose(m_request.fcache);
m_request.fcache = 0;
TQString filename = m_request.cef + ".new";
::unlink( TQFile::encodeName(filename) );
return;
}
}
void HTTPProtocol::closeCacheEntry()
{
TQString filename = m_request.cef + ".new";
int result = fclose( m_request.fcache);
m_request.fcache = 0;
if (result == 0)
{
if (::rename( TQFile::encodeName(filename), TQFile::encodeName(m_request.cef)) == 0)
return; // Success
kdWarning(7113) << "(" << m_pid << ") closeCacheEntry: error renaming "
<< "cache entry. (" << filename << " -> " << m_request.cef
<< ")" << endl;
}
kdWarning(7113) << "(" << m_pid << ") closeCacheEntry: error closing cache "
<< "entry. (" << filename<< ")" << endl;
}
void HTTPProtocol::cleanCache()
{
const time_t maxAge = DEFAULT_CLEAN_CACHE_INTERVAL; // 30 Minutes.
bool doClean = false;
TQString cleanFile = m_strCacheDir;
if (cleanFile[cleanFile.length()-1] != '/')
cleanFile += "/";
cleanFile += "cleaned";
struct stat stat_buf;
int result = ::stat(TQFile::encodeName(cleanFile), &stat_buf);
if (result == -1)
{
int fd = creat( TQFile::encodeName(cleanFile), 0600);
if (fd != -1)
{
doClean = true;
::close(fd);
}
}
else
{
time_t age = (time_t) difftime( time(0), stat_buf.st_mtime );
if (age > maxAge) //
doClean = true;
}
if (doClean)
{
// Touch file.
utime(TQFile::encodeName(cleanFile), 0);
KApplication::startServiceByDesktopPath("http_cache_cleaner.desktop");
}
}
//************************** AUTHENTICATION CODE ********************/
void HTTPProtocol::configAuth( char *p, bool isForProxy )
{
HTTP_AUTH f = AUTH_None;
const char *strAuth = p;
if ( strncasecmp( p, "Basic", 5 ) == 0 )
{
f = AUTH_Basic;
p += 5;
strAuth = "Basic"; // Correct for upper-case variations.
}
else if ( strncasecmp (p, "Digest", 6) == 0 )
{
f = AUTH_Digest;
memcpy((void *)p, "Digest", 6); // Correct for upper-case variations.
p += 6;
}
else if (strncasecmp( p, "MBS_PWD_COOKIE", 14 ) == 0)
{
// Found on http://www.webscription.net/baen/default.asp
f = AUTH_Basic;
p += 14;
strAuth = "Basic";
}
#ifdef HAVE_LIBGSSAPI
else if ( strncasecmp( p, "Negotiate", 9 ) == 0 )
{
// if we get two 401 in a row let's assume for now that
// Negotiate isn't working and ignore it
if ( !isForProxy && !(m_responseCode == 401 && m_prevResponseCode == 401) )
{
f = AUTH_Negotiate;
memcpy((void *)p, "Negotiate", 9); // Correct for upper-case variations.
p += 9;
};
}
#endif
else if ( strncasecmp( p, "NTLM", 4 ) == 0 )
{
f = AUTH_NTLM;
memcpy((void *)p, "NTLM", 4); // Correct for upper-case variations.
p += 4;
m_strRealm = "NTLM"; // set a dummy realm
}
else
{
kdWarning(7113) << "(" << m_pid << ") Unsupported or invalid authorization "
<< "type requested" << endl;
if (isForProxy)
kdWarning(7113) << "(" << m_pid << ") Proxy URL: " << m_proxyURL << endl;
else
kdWarning(7113) << "(" << m_pid << ") URL: " << m_request.url << endl;
kdWarning(7113) << "(" << m_pid << ") Request Authorization: " << p << endl;
}
/*
This check ensures the following:
1.) Rejection of any unknown/unsupported authentication schemes
2.) Usage of the strongest possible authentication schemes if
and when multiple Proxy-Authenticate or WWW-Authenticate
header field is sent.
*/
if (isForProxy)
{
if ((f == AUTH_None) ||
((m_iProxyAuthCount > 0) && (f < ProxyAuthentication)))
{
// Since I purposefully made the Proxy-Authentication settings
// persistent to reduce the number of round-trips to tdesud we
// have to take special care when an unknown/unsupported auth-
// scheme is received. This check accomplishes just that...
if ( m_iProxyAuthCount == 0)
ProxyAuthentication = f;
kdDebug(7113) << "(" << m_pid << ") Rejected proxy auth method: " << f << endl;
return;
}
m_iProxyAuthCount++;
kdDebug(7113) << "(" << m_pid << ") Accepted proxy auth method: " << f << endl;
}
else
{
if ((f == AUTH_None) ||
((m_iWWWAuthCount > 0) && (f < Authentication)))
{
kdDebug(7113) << "(" << m_pid << ") Rejected auth method: " << f << endl;
return;
}
m_iWWWAuthCount++;
kdDebug(7113) << "(" << m_pid << ") Accepted auth method: " << f << endl;
}
while (*p)
{
int i = 0;
while( (*p == ' ') || (*p == ',') || (*p == '\t') ) { p++; }
if ( strncasecmp( p, "realm=", 6 ) == 0 )
{
//for sites like lib.homelinux.org
TQTextCodec* oldCodec=TQTextCodec::codecForCStrings();
if (KGlobal::locale()->language().contains("ru"))
TQTextCodec::setCodecForCStrings(TQTextCodec::codecForName("CP1251"));
p += 6;
if (*p == '"') p++;
while( p[i] && p[i] != '"' ) i++;
if( isForProxy )
m_strProxyRealm = TQString::fromAscii( p, i );
else
m_strRealm = TQString::fromAscii( p, i );
TQTextCodec::setCodecForCStrings(oldCodec);
if (!p[i]) break;
}
p+=(i+1);
}
if( isForProxy )
{
ProxyAuthentication = f;
m_strProxyAuthorization = TQString::fromLatin1( strAuth );
}
else
{
Authentication = f;
m_strAuthorization = TQString::fromLatin1( strAuth );
}
}
bool HTTPProtocol::retryPrompt()
{
TQString prompt;
switch ( m_responseCode )
{
case 401:
prompt = i18n("Authentication Failed.");
break;
case 407:
prompt = i18n("Proxy Authentication Failed.");
break;
default:
break;
}
prompt += i18n(" Do you want to retry?");
return (messageBox(QuestionYesNo, prompt, i18n("Authentication")) == 3);
}
void HTTPProtocol::promptInfo( AuthInfo& info )
{
if ( m_responseCode == 401 )
{
info.url = m_request.url;
if ( !m_state.user.isEmpty() )
info.username = m_state.user;
info.readOnly = !m_request.url.user().isEmpty();
info.prompt = i18n( "You need to supply a username and a "
"password to access this site." );
info.keepPassword = true; // Prompt the user for persistence as well.
if ( !m_strRealm.isEmpty() )
{
info.realmValue = m_strRealm;
info.verifyPath = false;
info.digestInfo = m_strAuthorization;
info.commentLabel = i18n( "Site:" );
info.comment = i18n("<b>%1</b> at <b>%2</b>").arg( m_strRealm ).arg( m_request.hostname );
}
}
else if ( m_responseCode == 407 )
{
info.url = m_proxyURL;
info.username = m_proxyURL.user();
info.prompt = i18n( "You need to supply a username and a password for "
"the proxy server listed below before you are allowed "
"to access any sites." );
info.keepPassword = true;
if ( !m_strProxyRealm.isEmpty() )
{
info.realmValue = m_strProxyRealm;
info.verifyPath = false;
info.digestInfo = m_strProxyAuthorization;
info.commentLabel = i18n( "Proxy:" );
info.comment = i18n("<b>%1</b> at <b>%2</b>").arg( m_strProxyRealm ).arg( m_proxyURL.host() );
}
}
}
bool HTTPProtocol::getAuthorization()
{
AuthInfo info;
bool result = false;
kdDebug (7113) << "(" << m_pid << ") HTTPProtocol::getAuthorization: "
<< "Current Response: " << m_responseCode << ", "
<< "Previous Response: " << m_prevResponseCode << ", "
<< "Authentication: " << Authentication << ", "
<< "ProxyAuthentication: " << ProxyAuthentication << endl;
if (m_request.bNoAuth)
{
if (m_request.bErrorPage)
errorPage();
else
error( ERR_COULD_NOT_LOGIN, i18n("Authentication needed for %1 but authentication is disabled.").arg(m_request.hostname));
return false;
}
bool repeatFailure = (m_prevResponseCode == m_responseCode);
TQString errorMsg;
if (repeatFailure)
{
bool prompt = true;
if ( Authentication == AUTH_Digest || ProxyAuthentication == AUTH_Digest )
{
bool isStaleNonce = false;
TQString auth = ( m_responseCode == 401 ) ? m_strAuthorization : m_strProxyAuthorization;
int pos = auth.find("stale", 0, false);
if ( pos != -1 )
{
pos += 5;
int len = auth.length();
while( pos < len && (auth[pos] == ' ' || auth[pos] == '=') ) pos++;
if ( pos < len && auth.find("true", pos, false) != -1 )
{
isStaleNonce = true;
kdDebug(7113) << "(" << m_pid << ") Stale nonce value. "
<< "Will retry using same info..." << endl;
}
}
if ( isStaleNonce )
{
prompt = false;
result = true;
if ( m_responseCode == 401 )
{
info.username = m_request.user;
info.password = m_request.passwd;
info.realmValue = m_strRealm;
info.digestInfo = m_strAuthorization;
}
else if ( m_responseCode == 407 )
{
info.username = m_proxyURL.user();
info.password = m_proxyURL.pass();
info.realmValue = m_strProxyRealm;
info.digestInfo = m_strProxyAuthorization;
}
}
}
if ( Authentication == AUTH_NTLM || ProxyAuthentication == AUTH_NTLM )
{
TQString auth = ( m_responseCode == 401 ) ? m_strAuthorization : m_strProxyAuthorization;
kdDebug(7113) << "auth: " << auth << endl;
if ( auth.length() > 4 )
{
prompt = false;
result = true;
kdDebug(7113) << "(" << m_pid << ") NTLM auth second phase, "
<< "sending response..." << endl;
if ( m_responseCode == 401 )
{
info.username = m_request.user;
info.password = m_request.passwd;
info.realmValue = m_strRealm;
info.digestInfo = m_strAuthorization;
}
else if ( m_responseCode == 407 )
{
info.username = m_proxyURL.user();
info.password = m_proxyURL.pass();
info.realmValue = m_strProxyRealm;
info.digestInfo = m_strProxyAuthorization;
}
}
}
if ( prompt )
{
switch ( m_responseCode )
{
case 401:
errorMsg = i18n("Authentication Failed.");
break;
case 407:
errorMsg = i18n("Proxy Authentication Failed.");
break;
default:
break;
}
}
}
else
{
// At this point we know more details, so use it to find
// out if we have a cached version and avoid a re-prompt!
// We also do not use verify path unlike the pre-emptive
// requests because we already know the realm value...
if (m_bProxyAuthValid)
{
// Reset cached proxy auth
m_bProxyAuthValid = false;
KURL proxy ( config()->readEntry("UseProxy") );
m_proxyURL.setUser(proxy.user());
m_proxyURL.setPass(proxy.pass());
}
info.verifyPath = false;
if ( m_responseCode == 407 )
{
info.url = m_proxyURL;
info.username = m_proxyURL.user();
info.password = m_proxyURL.pass();
info.realmValue = m_strProxyRealm;
info.digestInfo = m_strProxyAuthorization;
}
else
{
info.url = m_request.url;
info.username = m_request.user;
info.password = m_request.passwd;
info.realmValue = m_strRealm;
info.digestInfo = m_strAuthorization;
}
// If either username or password is not supplied
// with the request, check the password cache.
if ( info.username.isNull() ||
info.password.isNull() )
result = checkCachedAuthentication( info );
if ( Authentication == AUTH_Digest )
{
TQString auth;
if (m_responseCode == 401)
auth = m_strAuthorization;
else
auth = m_strProxyAuthorization;
int pos = auth.find("stale", 0, false);
if ( pos != -1 )
{
pos += 5;
int len = auth.length();
while( pos < len && (auth[pos] == ' ' || auth[pos] == '=') ) pos++;
if ( pos < len && auth.find("true", pos, false) != -1 )
{
info.digestInfo = (m_responseCode == 401) ? m_strAuthorization : m_strProxyAuthorization;
kdDebug(7113) << "(" << m_pid << ") Just a stale nonce value! "
<< "Retrying using the new nonce sent..." << endl;
}
}
}
}
if (!result )
{
// Do not prompt if the username & password
// is already supplied and the login attempt
// did not fail before.
if ( !repeatFailure &&
!info.username.isNull() &&
!info.password.isNull() )
result = true;
else
{
if (Authentication == AUTH_Negotiate)
{
if (!repeatFailure)
result = true;
}
else if ( m_request.disablePassDlg == false )
{
kdDebug( 7113 ) << "(" << m_pid << ") Prompting the user for authorization..." << endl;
promptInfo( info );
result = openPassDlg( info, errorMsg );
}
}
}
if ( result )
{
switch (m_responseCode)
{
case 401: // Request-Authentication
m_request.user = info.username;
m_request.passwd = info.password;
m_strRealm = info.realmValue;
m_strAuthorization = info.digestInfo;
break;
case 407: // Proxy-Authentication
m_proxyURL.setUser( info.username );
m_proxyURL.setPass( info.password );
m_strProxyRealm = info.realmValue;
m_strProxyAuthorization = info.digestInfo;
break;
default:
break;
}
return true;
}
if (m_request.bErrorPage)
errorPage();
else
error( ERR_USER_CANCELED, TQString::null );
return false;
}
void HTTPProtocol::saveAuthorization()
{
AuthInfo info;
if ( m_prevResponseCode == 407 )
{
if (!m_bUseProxy)
return;
m_bProxyAuthValid = true;
info.url = m_proxyURL;
info.username = m_proxyURL.user();
info.password = m_proxyURL.pass();
info.realmValue = m_strProxyRealm;
info.digestInfo = m_strProxyAuthorization;
cacheAuthentication( info );
}
else
{
info.url = m_request.url;
info.username = m_request.user;
info.password = m_request.passwd;
info.realmValue = m_strRealm;
info.digestInfo = m_strAuthorization;
cacheAuthentication( info );
}
}
#ifdef HAVE_LIBGSSAPI
TQCString HTTPProtocol::gssError( int major_status, int minor_status )
{
OM_uint32 new_status;
OM_uint32 msg_ctx = 0;
gss_buffer_desc major_string;
gss_buffer_desc minor_string;
OM_uint32 ret;
TQCString errorstr;
errorstr = "";
do {
ret = gss_display_status(&new_status, major_status, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &major_string);
errorstr += (const char *)major_string.value;
errorstr += " ";
ret = gss_display_status(&new_status, minor_status, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &minor_string);
errorstr += (const char *)minor_string.value;
errorstr += " ";
} while (!GSS_ERROR(ret) && msg_ctx != 0);
return errorstr;
}
TQString HTTPProtocol::createNegotiateAuth()
{
TQString auth;
TQCString servicename;
TQByteArray input;
OM_uint32 major_status, minor_status;
OM_uint32 req_flags = 0;
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
gss_name_t server;
gss_ctx_id_t ctx;
gss_OID mech_oid;
static gss_OID_desc krb5_oid_desc = {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"};
static gss_OID_desc spnego_oid_desc = {6, (void *) "\x2b\x06\x01\x05\x05\x02"};
int found = 0;
unsigned int i;
gss_OID_set mech_set;
gss_OID tmp_oid;
ctx = GSS_C_NO_CONTEXT;
mech_oid = &krb5_oid_desc;
// see whether we can use the SPNEGO mechanism
major_status = gss_indicate_mechs(&minor_status, &mech_set);
if (GSS_ERROR(major_status)) {
kdDebug(7113) << "(" << m_pid << ") gss_indicate_mechs failed: " << gssError(major_status, minor_status) << endl;
} else {
for (i=0; i<mech_set->count && !found; i++) {
tmp_oid = &mech_set->elements[i];
if (tmp_oid->length == spnego_oid_desc.length &&
!memcmp(tmp_oid->elements, spnego_oid_desc.elements, tmp_oid->length)) {
kdDebug(7113) << "(" << m_pid << ") createNegotiateAuth: found SPNEGO mech" << endl;
found = 1;
mech_oid = &spnego_oid_desc;
break;
}
}
gss_release_oid_set(&minor_status, &mech_set);
}
// the service name is "HTTP/f.q.d.n"
servicename = "HTTP@";
servicename += m_state.hostname.ascii();
input_token.value = (void *)servicename.data();
input_token.length = servicename.length() + 1;
major_status = gss_import_name(&minor_status, &input_token,
GSS_C_NT_HOSTBASED_SERVICE, &server);
input_token.value = NULL;
input_token.length = 0;
if (GSS_ERROR(major_status)) {
kdDebug(7113) << "(" << m_pid << ") gss_import_name failed: " << gssError(major_status, minor_status) << endl;
// reset the auth string so that subsequent methods aren't confused
m_strAuthorization = TQString::null;
return TQString::null;
}
major_status = gss_init_sec_context(&minor_status, GSS_C_NO_CREDENTIAL,
&ctx, server, mech_oid,
req_flags, GSS_C_INDEFINITE,
GSS_C_NO_CHANNEL_BINDINGS,
GSS_C_NO_BUFFER, NULL, &output_token,
NULL, NULL);
if (GSS_ERROR(major_status) || (output_token.length == 0)) {
kdDebug(7113) << "(" << m_pid << ") gss_init_sec_context failed: " << gssError(major_status, minor_status) << endl;
gss_release_name(&minor_status, &server);
if (ctx != GSS_C_NO_CONTEXT) {
gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER);
ctx = GSS_C_NO_CONTEXT;
}
// reset the auth string so that subsequent methods aren't confused
m_strAuthorization = TQString::null;
return TQString::null;
}
input.duplicate((const char *)output_token.value, output_token.length);
auth = "Authorization: Negotiate ";
auth += KCodecs::base64Encode( input );
auth += "\r\n";
// free everything
gss_release_name(&minor_status, &server);
if (ctx != GSS_C_NO_CONTEXT) {
gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER);
ctx = GSS_C_NO_CONTEXT;
}
gss_release_buffer(&minor_status, &output_token);
return auth;
}
#else
// Dummy
TQCString HTTPProtocol::gssError( int, int )
{
return "";
}
// Dummy
TQString HTTPProtocol::createNegotiateAuth()
{
return TQString::null;
}
#endif
TQString HTTPProtocol::createNTLMAuth( bool isForProxy )
{
uint len;
TQString auth, user, domain, passwd;
TQCString strauth;
TQByteArray buf;
if ( isForProxy )
{
auth = "Proxy-Connection: Keep-Alive\r\n";
auth += "Proxy-Authorization: NTLM ";
user = m_proxyURL.user();
passwd = m_proxyURL.pass();
strauth = m_strProxyAuthorization.latin1();
len = m_strProxyAuthorization.length();
}
else
{
auth = "Authorization: NTLM ";
user = m_state.user;
passwd = m_state.passwd;
strauth = m_strAuthorization.latin1();
len = m_strAuthorization.length();
}
if ( user.contains('\\') ) {
domain = user.section( '\\', 0, 0);
user = user.section( '\\', 1 );
}
kdDebug(7113) << "(" << m_pid << ") NTLM length: " << len << endl;
if ( user.isEmpty() || passwd.isEmpty() || len < 4 )
return TQString::null;
if ( len > 4 )
{
// create a response
TQByteArray challenge;
KCodecs::base64Decode( strauth.right( len - 5 ), challenge );
KNTLM::getAuth( buf, challenge, user, passwd, domain,
KNetwork::KResolver::localHostName(), false, false );
}
else
{
KNTLM::getNegotiate( buf );
}
// remove the challenge to prevent reuse
if ( isForProxy )
m_strProxyAuthorization = "NTLM";
else
m_strAuthorization = "NTLM";
auth += KCodecs::base64Encode( buf );
auth += "\r\n";
return auth;
}
TQString HTTPProtocol::createBasicAuth( bool isForProxy )
{
TQString auth;
TQCString user, passwd;
if ( isForProxy )
{
auth = "Proxy-Authorization: Basic ";
user = m_proxyURL.user().latin1();
passwd = m_proxyURL.pass().latin1();
}
else
{
auth = "Authorization: Basic ";
user = m_state.user.latin1();
passwd = m_state.passwd.latin1();
}
if ( user.isEmpty() )
user = "";
if ( passwd.isEmpty() )
passwd = "";
user += ':';
user += passwd;
auth += KCodecs::base64Encode( user );
auth += "\r\n";
return auth;
}
void HTTPProtocol::calculateResponse( DigestAuthInfo& info, TQCString& Response )
{
KMD5 md;
TQCString HA1;
TQCString HA2;
// Calculate H(A1)
TQCString authStr = info.username;
authStr += ':';
authStr += info.realm;
authStr += ':';
authStr += info.password;
md.update( authStr );
if ( info.algorithm.lower() == "md5-sess" )
{
authStr = md.hexDigest();
authStr += ':';
authStr += info.nonce;
authStr += ':';
authStr += info.cnonce;
md.reset();
md.update( authStr );
}
HA1 = md.hexDigest();
kdDebug(7113) << "(" << m_pid << ") calculateResponse(): A1 => " << HA1 << endl;
// Calcualte H(A2)
authStr = info.method;
authStr += ':';
authStr += m_request.url.encodedPathAndQuery(0, true).latin1();
if ( info.qop == "auth-int" )
{
authStr += ':';
authStr += info.entityBody;
}
md.reset();
md.update( authStr );
HA2 = md.hexDigest();
kdDebug(7113) << "(" << m_pid << ") calculateResponse(): A2 => "
<< HA2 << endl;
// Calcualte the response.
authStr = HA1;
authStr += ':';
authStr += info.nonce;
authStr += ':';
if ( !info.qop.isEmpty() )
{
authStr += info.nc;
authStr += ':';
authStr += info.cnonce;
authStr += ':';
authStr += info.qop;
authStr += ':';
}
authStr += HA2;
md.reset();
md.update( authStr );
Response = md.hexDigest();
kdDebug(7113) << "(" << m_pid << ") calculateResponse(): Response => "
<< Response << endl;
}
TQString HTTPProtocol::createDigestAuth ( bool isForProxy )
{
const char *p;
TQString auth;
TQCString opaque;
TQCString Response;
DigestAuthInfo info;
opaque = "";
if ( isForProxy )
{
auth = "Proxy-Authorization: Digest ";
info.username = m_proxyURL.user().latin1();
info.password = m_proxyURL.pass().latin1();
p = m_strProxyAuthorization.latin1();
}
else
{
auth = "Authorization: Digest ";
info.username = m_state.user.latin1();
info.password = m_state.passwd.latin1();
p = m_strAuthorization.latin1();
}
if (!p || !*p)
return TQString::null;
p += 6; // Skip "Digest"
if ( info.username.isEmpty() || info.password.isEmpty() || !p )
return TQString::null;
// info.entityBody = p; // FIXME: send digest of data for POST action ??
info.realm = "";
info.algorithm = "MD5";
info.nonce = "";
info.qop = "";
// cnonce is recommended to contain about 64 bits of entropy
info.cnonce = KApplication::randomString(16).latin1();
// HACK: Should be fixed according to RFC 2617 section 3.2.2
info.nc = "00000001";
// Set the method used...
switch ( m_request.method )
{
case HTTP_GET:
info.method = "GET";
break;
case HTTP_PUT:
info.method = "PUT";
break;
case HTTP_POST:
info.method = "POST";
break;
case HTTP_HEAD:
info.method = "HEAD";
break;
case HTTP_DELETE:
info.method = "DELETE";
break;
case DAV_PROPFIND:
info.method = "PROPFIND";
break;
case DAV_PROPPATCH:
info.method = "PROPPATCH";
break;
case DAV_MKCOL:
info.method = "MKCOL";
break;
case DAV_COPY:
info.method = "COPY";
break;
case DAV_MOVE:
info.method = "MOVE";
break;
case DAV_LOCK:
info.method = "LOCK";
break;
case DAV_UNLOCK:
info.method = "UNLOCK";
break;
case DAV_SEARCH:
info.method = "SEARCH";
break;
case DAV_SUBSCRIBE:
info.method = "SUBSCRIBE";
break;
case DAV_UNSUBSCRIBE:
info.method = "UNSUBSCRIBE";
break;
case DAV_POLL:
info.method = "POLL";
break;
default:
error( ERR_UNSUPPORTED_ACTION, i18n("Unsupported method: authentication will fail. Please submit a bug report."));
break;
}
// Parse the Digest response....
while (*p)
{
int i = 0;
while ( (*p == ' ') || (*p == ',') || (*p == '\t')) { p++; }
if (strncasecmp(p, "realm=", 6 )==0)
{
p+=6;
while ( *p == '"' ) p++; // Go past any number of " mark(s) first
while ( p[i] != '"' ) i++; // Read everything until the last " mark
info.realm = TQCString( p, i+1 );
}
else if (strncasecmp(p, "algorith=", 9)==0)
{
p+=9;
while ( *p == '"' ) p++; // Go past any number of " mark(s) first
while ( ( p[i] != '"' ) && ( p[i] != ',' ) && ( p[i] != '\0' ) ) i++;
info.algorithm = TQCString(p, i+1);
}
else if (strncasecmp(p, "algorithm=", 10)==0)
{
p+=10;
while ( *p == '"' ) p++; // Go past any " mark(s) first
while ( ( p[i] != '"' ) && ( p[i] != ',' ) && ( p[i] != '\0' ) ) i++;
info.algorithm = TQCString(p,i+1);
}
else if (strncasecmp(p, "domain=", 7)==0)
{
p+=7;
while ( *p == '"' ) p++; // Go past any " mark(s) first
while ( p[i] != '"' ) i++; // Read everything until the last " mark
int pos;
int idx = 0;
TQCString uri = TQCString(p,i+1);
do
{
pos = uri.find( ' ', idx );
if ( pos != -1 )
{
KURL u (m_request.url, uri.mid(idx, pos-idx));
if (u.isValid ())
info.digestURI.append( u.url().latin1() );
}
else
{
KURL u (m_request.url, uri.mid(idx, uri.length()-idx));
if (u.isValid ())
info.digestURI.append( u.url().latin1() );
}
idx = pos+1;
} while ( pos != -1 );
}
else if (strncasecmp(p, "nonce=", 6)==0)
{
p+=6;
while ( *p == '"' ) p++; // Go past any " mark(s) first
while ( p[i] != '"' ) i++; // Read everything until the last " mark
info.nonce = TQCString(p,i+1);
}
else if (strncasecmp(p, "opaque=", 7)==0)
{
p+=7;
while ( *p == '"' ) p++; // Go past any " mark(s) first
while ( p[i] != '"' ) i++; // Read everything until the last " mark
opaque = TQCString(p,i+1);
}
else if (strncasecmp(p, "qop=", 4)==0)
{
p+=4;
while ( *p == '"' ) p++; // Go past any " mark(s) first
while ( p[i] != '"' ) i++; // Read everything until the last " mark
info.qop = TQCString(p,i+1);
}
p+=(i+1);
}
if (info.realm.isEmpty() || info.nonce.isEmpty())
return TQString::null;
// If the "domain" attribute was not specified and the current response code
// is authentication needed, add the current request url to the list over which
// this credential can be automatically applied.
if (info.digestURI.isEmpty() && (m_responseCode == 401 || m_responseCode == 407))
info.digestURI.append (m_request.url.url().latin1());
else
{
// Verify whether or not we should send a cached credential to the
// server based on the stored "domain" attribute...
bool send = true;
// Determine the path of the request url...
TQString requestPath = m_request.url.directory(false, false);
if (requestPath.isEmpty())
requestPath = "/";
int count = info.digestURI.count();
for (int i = 0; i < count; i++ )
{
KURL u ( info.digestURI.at(i) );
send &= (m_request.url.protocol().lower() == u.protocol().lower());
send &= (m_request.hostname.lower() == u.host().lower());
if (m_request.port > 0 && u.port() > 0)
send &= (m_request.port == u.port());
TQString digestPath = u.directory (false, false);
if (digestPath.isEmpty())
digestPath = "/";
send &= (requestPath.startsWith(digestPath));
if (send)
break;
}
kdDebug(7113) << "(" << m_pid << ") createDigestAuth(): passed digest "
"authentication credential test: " << send << endl;
if (!send)
return TQString::null;
}
kdDebug(7113) << "(" << m_pid << ") RESULT OF PARSING:" << endl;
kdDebug(7113) << "(" << m_pid << ") algorithm: " << info.algorithm << endl;
kdDebug(7113) << "(" << m_pid << ") realm: " << info.realm << endl;
kdDebug(7113) << "(" << m_pid << ") nonce: " << info.nonce << endl;
kdDebug(7113) << "(" << m_pid << ") opaque: " << opaque << endl;
kdDebug(7113) << "(" << m_pid << ") qop: " << info.qop << endl;
// Calculate the response...
calculateResponse( info, Response );
auth += "username=\"";
auth += info.username;
auth += "\", realm=\"";
auth += info.realm;
auth += "\"";
auth += ", nonce=\"";
auth += info.nonce;
auth += "\", uri=\"";
auth += m_request.url.encodedPathAndQuery(0, true);
auth += "\", algorithm=\"";
auth += info.algorithm;
auth +="\"";
if ( !info.qop.isEmpty() )
{
auth += ", qop=\"";
auth += info.qop;
auth += "\", cnonce=\"";
auth += info.cnonce;
auth += "\", nc=";
auth += info.nc;
}
auth += ", response=\"";
auth += Response;
if ( !opaque.isEmpty() )
{
auth += "\", opaque=\"";
auth += opaque;
}
auth += "\"\r\n";
return auth;
}
TQString HTTPProtocol::proxyAuthenticationHeader()
{
TQString header;
// We keep proxy authentication locally until they are changed.
// Thus, no need to check with the password manager for every
// connection.
if ( m_strProxyRealm.isEmpty() )
{
AuthInfo info;
info.url = m_proxyURL;
info.username = m_proxyURL.user();
info.password = m_proxyURL.pass();
info.verifyPath = true;
// If the proxy URL already contains username
// and password simply attempt to retrieve it
// without prompting the user...
if ( !info.username.isNull() && !info.password.isNull() )
{
if( m_strProxyAuthorization.isEmpty() )
ProxyAuthentication = AUTH_None;
else if( m_strProxyAuthorization.startsWith("Basic") )
ProxyAuthentication = AUTH_Basic;
else if( m_strProxyAuthorization.startsWith("NTLM") )
ProxyAuthentication = AUTH_NTLM;
else
ProxyAuthentication = AUTH_Digest;
}
else
{
if ( checkCachedAuthentication(info) && !info.digestInfo.isEmpty() )
{
m_proxyURL.setUser( info.username );
m_proxyURL.setPass( info.password );
m_strProxyRealm = info.realmValue;
m_strProxyAuthorization = info.digestInfo;
if( m_strProxyAuthorization.startsWith("Basic") )
ProxyAuthentication = AUTH_Basic;
else if( m_strProxyAuthorization.startsWith("NTLM") )
ProxyAuthentication = AUTH_NTLM;
else
ProxyAuthentication = AUTH_Digest;
}
else
{
ProxyAuthentication = AUTH_None;
}
}
}
/********* Only for debugging purpose... *********/
if ( ProxyAuthentication != AUTH_None )
{
kdDebug(7113) << "(" << m_pid << ") Using Proxy Authentication: " << endl;
kdDebug(7113) << "(" << m_pid << ") HOST= " << m_proxyURL.host() << endl;
kdDebug(7113) << "(" << m_pid << ") PORT= " << m_proxyURL.port() << endl;
kdDebug(7113) << "(" << m_pid << ") USER= " << m_proxyURL.user() << endl;
kdDebug(7113) << "(" << m_pid << ") PASSWORD= [protected]" << endl;
kdDebug(7113) << "(" << m_pid << ") REALM= " << m_strProxyRealm << endl;
kdDebug(7113) << "(" << m_pid << ") EXTRA= " << m_strProxyAuthorization << endl;
}
switch ( ProxyAuthentication )
{
case AUTH_Basic:
header += createBasicAuth( true );
break;
case AUTH_Digest:
header += createDigestAuth( true );
break;
case AUTH_NTLM:
if ( m_bFirstRequest ) header += createNTLMAuth( true );
break;
case AUTH_None:
default:
break;
}
return header;
}
#include "http.moc"