blob: 71aa40571490771ec2843f77334e0b2a3721cfe5 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_webdav.hxx"
#include "CurlRequest.hxx"
#include <string.h>
using namespace ::com::sun::star;
using namespace http_dav_ucp;
CurlRequest::CurlRequest( CURL *curl )
: curl( curl )
, curlUrl( NULL )
, requestHeaders( NULL )
, requestBody( NULL )
, requestBodyOffset( 0 )
, requestBodySize( 0 )
, useChunkedEncoding( false )
, provideCredentialsCallback( NULL )
, provideCredentialsUserdata( NULL )
, statusCode( 0 )
, responseBodyInputStream( new CurlInputStream() )
{
curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, Curl_HeaderReceived );
curl_easy_setopt( curl, CURLOPT_HEADERDATA, this );
curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, Curl_MoreBodyReceived );
curl_easy_setopt( curl, CURLOPT_WRITEDATA, this );
}
CurlRequest::~CurlRequest()
{
if ( curlUrl != NULL )
curl_url_cleanup( curlUrl );
curl_easy_setopt( curl, CURLOPT_CURLU, NULL );
curl_easy_setopt( curl, CURLOPT_HTTPHEADER, NULL );
if ( requestHeaders != NULL )
curl_slist_free_all( requestHeaders );
curl_easy_setopt( curl, CURLOPT_READFUNCTION, NULL );
curl_easy_setopt( curl, CURLOPT_READDATA, NULL );
curl_easy_setopt( curl, CURLOPT_INFILESIZE, -1 );
curl_easy_setopt( curl, CURLOPT_SEEKFUNCTION, NULL );
curl_easy_setopt( curl, CURLOPT_SEEKDATA, NULL );
curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, NULL );
curl_easy_setopt( curl, CURLOPT_HEADERDATA, NULL );
curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, NULL );
curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, NULL );
curl_easy_setopt( curl, CURLOPT_WRITEDATA, NULL );
}
void CurlRequest::addHeader( const rtl::OString &name, const rtl::OString &value) throw (DAVException)
{
rtl::OString line = name + ": " + value;
struct curl_slist *appended = curl_slist_append( requestHeaders, line.getStr() );
if ( appended != NULL )
{
requestHeaders = appended;
curl_easy_setopt( curl, CURLOPT_HTTPHEADER, requestHeaders );
}
else
throw DAVException( DAVException::DAV_SESSION_CREATE, rtl::OUString::createFromAscii( "Out of memory" ) );
}
void CurlRequest::setRequestBody( const char *body, size_t size )
{
requestBody = body;
requestBodyOffset = 0;
requestBodySize = size;
curl_easy_setopt( curl, CURLOPT_READFUNCTION, Curl_SendMoreBody );
curl_easy_setopt( curl, CURLOPT_READDATA, this );
curl_easy_setopt( curl, CURLOPT_SEEKFUNCTION, Curl_SeekCallback );
curl_easy_setopt( curl, CURLOPT_SEEKDATA, this );
if ( useChunkedEncoding )
addHeader( "Transfer-Encoding", "chunked" );
else
curl_easy_setopt( curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)size );
}
int CurlRequest::Curl_SeekCallback( void *userdata, curl_off_t offset, int origin )
{
CurlRequest *request = static_cast< CurlRequest* >( userdata );
if ( origin == SEEK_SET )
request->requestBodyOffset = (size_t) offset;
else if ( origin == SEEK_CUR )
request->requestBodyOffset += (size_t) offset;
else if ( origin == SEEK_END )
request->requestBodyOffset += (size_t) ((curl_off_t)request->requestBodySize + offset);
else
return CURL_SEEKFUNC_CANTSEEK;
if ( request->requestBodyOffset > request->requestBodySize )
request->requestBodyOffset = request->requestBodySize;
return CURL_SEEKFUNC_OK;
}
size_t CurlRequest::Curl_SendMoreBody( char *buffer, size_t size, size_t nitems, void *userdata )
{
CurlRequest *request = static_cast< CurlRequest* >( userdata );
OSL_TRACE("Curl_SendMoreBody");
return request->curlSendMoreBody( buffer, nitems );
}
size_t CurlRequest::curlSendMoreBody( char *buffer, size_t len )
{
size_t bytesToSend = requestBodySize - requestBodyOffset;
if ( bytesToSend > len )
bytesToSend = len;
memcpy( buffer, requestBody, bytesToSend );
requestBodyOffset += bytesToSend;
return bytesToSend;
}
void CurlRequest::setProvideCredentialsCallback( bool (*callback)(long statusCode, void *userdata) throw (DAVException), void *userdata )
{
provideCredentialsCallback = callback;
provideCredentialsUserdata = userdata;
}
void CurlRequest::setURI( CurlUri uri, rtl::OUString path ) throw (DAVException)
{
if ( curlUrl != NULL )
{
curl_url_cleanup( curlUrl );
curlUrl = NULL;
}
curlUrl = curl_url();
if ( curlUrl == NULL )
throw DAVException( DAVException::DAV_SESSION_CREATE, rtl::OUString::createFromAscii( "Out of memory" ) );
if ( CurlUri( path ).GetHost().isEmpty() )
{
// "path" is really a path, not a URI
curl_url_set( curlUrl, CURLUPART_URL, rtl::OUStringToOString( uri.GetURI(), RTL_TEXTENCODING_UTF8 ).getStr(), 0 );
curl_url_set( curlUrl, CURLUPART_PATH, rtl::OUStringToOString( path, RTL_TEXTENCODING_UTF8 ).getStr(), 0 );
}
else
{
// The "path" is a full URI
curl_url_set( curlUrl, CURLUPART_URL, rtl::OUStringToOString( path, RTL_TEXTENCODING_UTF8 ).getStr(), 0 );
}
curl_easy_setopt( curl, CURLOPT_CURLU, curlUrl );
}
CURLcode CurlRequest::copy( CurlUri uri, rtl::OUString path ) throw(DAVException)
{
setURI( uri, path );
curl_easy_setopt( curl, CURLOPT_HTTPGET, 1L );
curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "COPY" );
return perform();
}
CURLcode CurlRequest::delete_( CurlUri uri, rtl::OUString path ) throw (DAVException)
{
setURI( uri, path );
curl_easy_setopt( curl, CURLOPT_HTTPGET, 1L );
curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "DELETE" );
return perform();
}
CURLcode CurlRequest::get( CurlUri uri, rtl::OUString path ) throw(DAVException)
{
setURI( uri, path );
curl_easy_setopt( curl, CURLOPT_HTTPGET, 1L );
curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "GET" );
return perform();
}
CURLcode CurlRequest::head( CurlUri uri, rtl::OUString path ) throw (DAVException)
{
setURI( uri, path );
curl_easy_setopt( curl, CURLOPT_NOBODY, 1L );
curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "HEAD" );
return perform();
}
CURLcode CurlRequest::lock( CurlUri uri, rtl::OUString path ) throw (DAVException)
{
setURI( uri, path );
curl_easy_setopt( curl, CURLOPT_UPLOAD, 1L );
curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "LOCK" );
return perform();
}
CURLcode CurlRequest::mkcol( CurlUri uri, rtl::OUString path ) throw (DAVException)
{
setURI( uri, path );
curl_easy_setopt( curl, CURLOPT_HTTPGET, 1L );
curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "MKCOL" );
return perform();
}
CURLcode CurlRequest::move( CurlUri uri, rtl::OUString path ) throw (DAVException)
{
setURI( uri, path );
curl_easy_setopt( curl, CURLOPT_HTTPGET, 1L );
curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "MOVE" );
return perform();
}
CURLcode CurlRequest::post( CurlUri uri, rtl::OUString path ) throw (DAVException)
{
setURI( uri, path );
curl_easy_setopt( curl, CURLOPT_UPLOAD, 1L );
curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "POST" );
return perform();
}
CURLcode CurlRequest::propfind( CurlUri uri, rtl::OUString path ) throw (DAVException)
{
setURI( uri, path );
curl_easy_setopt( curl, CURLOPT_UPLOAD, 1L );
curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "PROPFIND" );
return perform();
}
CURLcode CurlRequest::proppatch( CurlUri uri, rtl::OUString path ) throw (DAVException)
{
setURI( uri, path );
curl_easy_setopt( curl, CURLOPT_UPLOAD, 1L );
curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "PROPPATCH" );
return perform();
}
CURLcode CurlRequest::put( CurlUri uri, rtl::OUString path ) throw (DAVException)
{
setURI( uri, path );
curl_easy_setopt( curl, CURLOPT_UPLOAD, 1L );
curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "PUT" );
return perform();
}
CURLcode CurlRequest::unlock( CurlUri uri, rtl::OUString path ) throw (DAVException)
{
setURI( uri, path );
curl_easy_setopt( curl, CURLOPT_HTTPGET, 1L );
curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, "UNLOCK" );
return perform();
}
CURLcode CurlRequest::perform() throw (DAVException)
{
CURLcode rc = curl_easy_perform( curl );
long statusCode = 0;
curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &statusCode );
if ( ( statusCode == 401 || statusCode == 407 ) && provideCredentialsCallback != NULL )
{
bool haveCredentials = provideCredentialsCallback( statusCode, provideCredentialsUserdata );
if ( haveCredentials )
{
// rewind body:
requestBodyOffset = 0;
// retry with credentials:
rc = curl_easy_perform( curl );
// If this was to authenticate with the proxy, we may need to authenticate
// with the destination host too:
if ( statusCode == 407 )
{
curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &statusCode );
if ( statusCode == 401 && provideCredentialsCallback != NULL )
{
haveCredentials = provideCredentialsCallback( statusCode, provideCredentialsUserdata );
if ( haveCredentials )
{
// rewind body:
requestBodyOffset = 0;
// retry with credentials:
rc = curl_easy_perform( curl );
}
}
}
}
}
return rc;
}
size_t CurlRequest::Curl_HeaderReceived( char *buffer, size_t size, size_t nitems, void *userdata )
{
CurlRequest *request = static_cast< CurlRequest* >( userdata );
OSL_TRACE("Curl_HeaderReceived");
request->curlHeaderReceived( buffer, nitems );
return nitems;
}
void CurlRequest::curlHeaderReceived( const char *buffer, size_t size )
{
rtl::OString lineCrLf( buffer, size );
sal_Int32 cr = lineCrLf.indexOf( "\r" );
if ( cr < 0 )
return;
rtl::OString line = lineCrLf.copy( 0, cr );
if ( line.indexOf( "HTTP/" ) == 0 )
{
// Status line
// Throw away any response headers from a prior response:
responseHeaders.clear();
sal_Int32 idxFirstSpace = line.indexOf( ' ' );
if ( idxFirstSpace > 0 )
{
int idxSecondSpace = line.indexOf( ' ', idxFirstSpace + 1 );
if ( idxSecondSpace > 0 )
{
reasonPhrase = line.copy( idxSecondSpace + 1 );
statusCode = line.copy( idxFirstSpace + 1, idxSecondSpace - idxFirstSpace - 1 ).toInt32();
}
else
{
reasonPhrase = "";
statusCode = line.copy( idxFirstSpace + 1 ).toInt32();
}
}
}
else
{
// Header line
if ( line.getLength() == 0 )
{
// End of HTTP header
// Discard any intermediate body from 100 Trying or 401 Authentication required:
responseBodyInputStream = new CurlInputStream();
return;
}
sal_Int32 colon = line.indexOf( ':' );
if ( colon < 0 )
{
OSL_TRACE("Non-empty HTTP line without a ':'. Folded header deprecated by RFC 7230?");
return;
}
Header header;
header.name = line.copy( 0, colon ).toAsciiLowerCase();
header.value = line.copy( colon + 1 ).trim();
responseHeaders.push_back(header);
}
}
const CurlRequest::Header *CurlRequest::findResponseHeader( const rtl::OString &name )
{
std::vector< CurlRequest::Header >::const_iterator it( responseHeaders.begin() );
const std::vector< CurlRequest::Header >::const_iterator end( responseHeaders.end() );
for ( ; it != end; it++ )
{
if ( name.equalsIgnoreAsciiCase( it->name ) )
return &(*it);
}
return NULL;
}
void CurlRequest::saveResponseBodyTo( const uno::Reference< io::XOutputStream > & xOutStream)
{
xOutputStream = xOutStream;
}
size_t CurlRequest::Curl_MoreBodyReceived( char *buffer, size_t size, size_t nitems, void *userdata )
{
CurlRequest *request = static_cast< CurlRequest* >( userdata );
request->curlMoreBodyReceived( buffer, nitems );
return nitems;
}
void CurlRequest::curlMoreBodyReceived( const char *buffer, size_t size )
{
if ( xOutputStream.is() )
{
const uno::Sequence< sal_Int8 > aDataSeq( (sal_Int8 *)buffer, size );
xOutputStream->writeBytes( aDataSeq );
}
else if ( responseBodyInputStream.is() )
responseBodyInputStream->AddToStream( buffer, size );
}