blob: e3dd63285f6419763b370618d92767f58ac5444a [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_ucb.hxx"
/**************************************************************************
TODO
**************************************************************************
*************************************************************************/
#include <memory>
#include <rtl/ustrbuf.hxx>
#include <com/sun/star/ucb/OpenMode.hpp>
#include <string.h>
#include <rtl/uri.hxx>
#include "ftpstrcont.hxx"
#include "ftpurl.hxx"
#include "ftphandleprovider.hxx"
#include "ftpinpstr.hxx"
#include "ftpcfunc.hxx"
#include "ftpcontainer.hxx"
using namespace ftp;
using namespace com::sun::star::ucb;
using namespace com::sun::star::uno;
using namespace com::sun::star::io;
namespace {
rtl::OUString encodePathSegment(rtl::OUString const & decoded) {
return rtl::Uri::encode(
decoded, rtl_UriCharClassPchar, rtl_UriEncodeIgnoreEscapes,
RTL_TEXTENCODING_UTF8);
}
rtl::OUString decodePathSegment(rtl::OUString const & encoded) {
return rtl::Uri::decode(
encoded, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8);
}
}
MemoryContainer::MemoryContainer()
: m_nLen(0),
m_nWritePos(0),
m_pBuffer(0)
{
}
MemoryContainer::~MemoryContainer()
{
rtl_freeMemory(m_pBuffer);
}
int MemoryContainer::append(
const void* pBuffer,
size_t size,
size_t nmemb
) throw()
{
sal_uInt32 nLen = size*nmemb;
sal_uInt32 tmp(nLen + m_nWritePos);
if(m_nLen < tmp) { // enlarge in steps of multiples of 1K
do {
m_nLen+=1024;
} while(m_nLen < tmp);
m_pBuffer = rtl_reallocateMemory(m_pBuffer,m_nLen);
}
rtl_copyMemory(static_cast<sal_Int8*>(m_pBuffer)+m_nWritePos,
pBuffer,nLen);
m_nWritePos = tmp;
return nLen;
}
extern "C" {
int memory_write(void *buffer,size_t size,size_t nmemb,void *stream)
{
MemoryContainer *_stream =
reinterpret_cast<MemoryContainer*>(stream);
if(!_stream)
return 0;
return _stream->append(buffer,size,nmemb);
}
}
FTPURL::FTPURL(const FTPURL& r)
: m_mutex(),
m_pFCP(r.m_pFCP),
m_aUsername(r.m_aUsername),
m_bShowPassword(r.m_bShowPassword),
m_aHost(r.m_aHost),
m_aPort(r.m_aPort),
m_aPathSegmentVec(r.m_aPathSegmentVec)
{
}
FTPURL::FTPURL(const rtl::OUString& url,
FTPHandleProvider* pFCP)
throw(
malformed_exception
)
: m_pFCP(pFCP),
m_aUsername(rtl::OUString::createFromAscii("anonymous")),
m_bShowPassword(false),
m_aPort(rtl::OUString::createFromAscii("21"))
{
parse(url); // can reset m_bShowPassword
}
FTPURL::~FTPURL()
{
}
void FTPURL::parse(const rtl::OUString& url)
throw(
malformed_exception
)
{
rtl::OUString aPassword,aAccount;
rtl::OString aIdent(url.getStr(),
url.getLength(),
RTL_TEXTENCODING_UTF8);
rtl::OString lower = aIdent.toAsciiLowerCase();
if(lower.getLength() < 6 ||
strncmp("ftp://",lower.getStr(),6))
throw malformed_exception();
char *buffer = new char[1+aIdent.getLength()];
const char* p2 = aIdent.getStr();
p2 += 6;
char ch;
char *p1 = buffer; // determine "username:password@host:port"
while((ch = *p2++) != '/' && ch)
*p1++ = ch;
*p1 = 0;
rtl::OUString aExpr(rtl::OUString(buffer,strlen(buffer),
RTL_TEXTENCODING_UTF8));
sal_Int32 l = aExpr.indexOf(sal_Unicode('@'));
m_aHost = aExpr.copy(1+l);
if(l != -1) {
// Now username and password.
aExpr = aExpr.copy(0,l);
l = aExpr.indexOf(sal_Unicode(':'));
if(l != -1) {
aPassword = aExpr.copy(1+l);
if(aPassword.getLength())
m_bShowPassword = true;
}
if(l > 0)
// Overwritte only if the username is not empty.
m_aUsername = aExpr.copy(0,l);
else if(aExpr.getLength())
m_aUsername = aExpr;
}
l = m_aHost.lastIndexOf(sal_Unicode(':'));
sal_Int32 ipv6Back = m_aHost.lastIndexOf(sal_Unicode(']'));
if((ipv6Back == -1 && l != -1) // not ipv6, but a port
||
(ipv6Back != -1 && 1+ipv6Back == l) // ipv6, and a port
)
{
if(1+l<m_aHost.getLength())
m_aPort = m_aHost.copy(1+l);
m_aHost = m_aHost.copy(0,l);
}
while(ch) { // now determine the pathsegments ...
p1 = buffer;
while((ch = *p2++) != '/' && ch)
*p1++ = ch;
*p1 = 0;
if(buffer[0]) {
if(strcmp(buffer,"..") == 0 &&
m_aPathSegmentVec.size() &&
! m_aPathSegmentVec.back().equalsAscii(".."))
m_aPathSegmentVec.pop_back();
else if(strcmp(buffer,".") == 0)
; // Ignore
else
// This is a legal name.
m_aPathSegmentVec.push_back(
rtl::OUString(buffer,
strlen(buffer),
RTL_TEXTENCODING_UTF8));
}
}
delete[] buffer;
if(m_bShowPassword)
m_pFCP->setHost(m_aHost,
m_aPort,
m_aUsername,
aPassword,
aAccount);
// now check for something like ";type=i" at end of url
if(m_aPathSegmentVec.size() &&
(l = m_aPathSegmentVec.back().indexOf(sal_Unicode(';'))) != -1) {
m_aType = m_aPathSegmentVec.back().copy(l);
m_aPathSegmentVec.back() = m_aPathSegmentVec.back().copy(0,l);
}
}
rtl::OUString FTPURL::ident(bool withslash,bool internal) const
{
// rebuild the url as one without ellipses,
// and more important, as one without username and
// password. ( These are set together with the command. )
rtl::OUStringBuffer bff;
bff.appendAscii("ftp://");
if(!m_aUsername.equalsAscii("anonymous")) {
bff.append(m_aUsername);
rtl::OUString aPassword,aAccount;
m_pFCP->forHost(m_aHost,
m_aPort,
m_aUsername,
aPassword,
aAccount);
if((m_bShowPassword || internal) &&
aPassword.getLength() )
bff.append(sal_Unicode(':'))
.append(aPassword);
bff.append(sal_Unicode('@'));
}
bff.append(m_aHost);
if(!m_aPort.equalsAscii("21"))
bff.append(sal_Unicode(':'))
.append(m_aPort)
.append(sal_Unicode('/'));
else
bff.append(sal_Unicode('/'));
for(unsigned i = 0; i < m_aPathSegmentVec.size(); ++i)
if(i == 0)
bff.append(m_aPathSegmentVec[i]);
else
bff.append(sal_Unicode('/')).append(m_aPathSegmentVec[i]);
if(withslash)
if(bff.getLength() && bff[bff.getLength()-1] != sal_Unicode('/'))
bff.append(sal_Unicode('/'));
bff.append(m_aType);
return bff.makeStringAndClear();
}
rtl::OUString FTPURL::parent(bool internal) const
{
rtl::OUStringBuffer bff;
bff.appendAscii("ftp://");
if(!m_aUsername.equalsAscii("anonymous")) {
bff.append(m_aUsername);
rtl::OUString aPassword,aAccount;
m_pFCP->forHost(m_aHost,
m_aPort,
m_aUsername,
aPassword,
aAccount);
if((internal || m_bShowPassword) && aPassword.getLength())
bff.append(sal_Unicode(':'))
.append(aPassword);
bff.append(sal_Unicode('@'));
}
bff.append(m_aHost);
if(!m_aPort.equalsAscii("21"))
bff.append(sal_Unicode(':'))
.append(m_aPort)
.append(sal_Unicode('/'));
else
bff.append(sal_Unicode('/'));
rtl::OUString last;
for(unsigned int i = 0; i < m_aPathSegmentVec.size(); ++i)
if(1+i == m_aPathSegmentVec.size())
last = m_aPathSegmentVec[i];
else if(i == 0)
bff.append(m_aPathSegmentVec[i]);
else
bff.append(sal_Unicode('/')).append(m_aPathSegmentVec[i]);
if(!last.getLength())
bff.appendAscii("..");
else if(last.equalsAscii(".."))
bff.append(last).appendAscii("/..");
bff.append(m_aType);
return bff.makeStringAndClear();
}
void FTPURL::child(const rtl::OUString& title)
{
m_aPathSegmentVec.push_back(encodePathSegment(title));
}
rtl::OUString FTPURL::child() const
{
return
m_aPathSegmentVec.size() ?
decodePathSegment(m_aPathSegmentVec.back()) : rtl::OUString();
}
/** Listing of a directory.
*/
namespace ftp {
enum OS {
FTP_DOS,FTP_UNIX,FTP_VMS,FTP_UNKNOWN
};
}
#define SET_CONTROL_CONTAINER \
MemoryContainer control; \
curl_easy_setopt(curl, \
CURLOPT_HEADERFUNCTION, \
memory_write); \
curl_easy_setopt(curl, \
CURLOPT_WRITEHEADER, \
&control)
#define SET_DATA_CONTAINER \
curl_easy_setopt(curl,CURLOPT_NOBODY,false); \
MemoryContainer data; \
curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,memory_write); \
curl_easy_setopt(curl,CURLOPT_WRITEDATA,&data)
#define SET_URL(url) \
rtl::OString urlParAscii(url.getStr(), \
url.getLength(), \
RTL_TEXTENCODING_UTF8); \
curl_easy_setopt(curl, \
CURLOPT_URL, \
urlParAscii.getStr());
// Setting username:password
#define SET_USER_PASSWORD(username,password) \
rtl::OUString combi(username + \
rtl::OUString::createFromAscii(":") + \
password); \
rtl::OString aUserPsswd(combi.getStr(), \
combi.getLength(), \
RTL_TEXTENCODING_UTF8); \
curl_easy_setopt(curl, \
CURLOPT_USERPWD, \
aUserPsswd.getStr())
oslFileHandle FTPURL::open()
throw(curl_exception)
{
if(!m_aPathSegmentVec.size())
throw curl_exception(CURLE_FTP_COULDNT_RETR_FILE);
CURL *curl = m_pFCP->handle();
SET_CONTROL_CONTAINER;
rtl::OUString url(ident(false,true));
SET_URL(url);
oslFileHandle res( NULL );
if ( osl_createTempFile( NULL, &res, NULL ) == osl_File_E_None )
{
curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,file_write);
curl_easy_setopt(curl,CURLOPT_WRITEDATA,res);
curl_easy_setopt(curl,CURLOPT_POSTQUOTE,0);
CURLcode err = curl_easy_perform(curl);
if(err == CURLE_OK)
osl_setFilePos( res, osl_Pos_Absolut, 0 );
else {
osl_closeFile(res),res = 0;
throw curl_exception(err);
}
}
return res;
}
std::vector<FTPDirentry> FTPURL::list(
sal_Int16 nMode
) const
throw(
curl_exception
)
{
CURL *curl = m_pFCP->handle();
SET_CONTROL_CONTAINER;
SET_DATA_CONTAINER;
rtl::OUString url(ident(true,true));
SET_URL(url);
curl_easy_setopt(curl,CURLOPT_POSTQUOTE,0);
CURLcode err = curl_easy_perform(curl);
if(err != CURLE_OK)
throw curl_exception(err);
// now evaluate the error messages
sal_uInt32 len = data.m_nWritePos;
char* fwd = (char*) data.m_pBuffer;
rtl::OString str(fwd,len);
char *p1, *p2;
p1 = p2 = fwd;
OS osKind(FTP_UNKNOWN);
std::vector<FTPDirentry> resvec;
FTPDirentry aDirEntry;
// ensure slash at the end
rtl::OUString viewurl(ident(true,false));
while(true) {
while(p2-fwd < int(len) && *p2 != '\n') ++p2;
if(p2-fwd == int(len)) break;
*p2 = 0;
switch(osKind) {
// While FTP knows the 'system'-command,
// which returns the operating system type,
// this is not usable here: There are Windows-server
// formatting the output like UNIX-ls command.
case FTP_DOS:
FTPDirectoryParser::parseDOS(aDirEntry,p1);
break;
case FTP_UNIX:
FTPDirectoryParser::parseUNIX(aDirEntry,p1);
break;
case FTP_VMS:
FTPDirectoryParser::parseVMS(aDirEntry,p1);
break;
default:
if(FTPDirectoryParser::parseUNIX(aDirEntry,p1))
osKind = FTP_UNIX;
else if(FTPDirectoryParser::parseDOS(aDirEntry,p1))
osKind = FTP_DOS;
else if(FTPDirectoryParser::parseVMS(aDirEntry,p1))
osKind = FTP_VMS;
}
aDirEntry.m_aName = aDirEntry.m_aName.trim();
if(osKind != int(FTP_UNKNOWN) &&
!aDirEntry.m_aName.equalsAscii("..") &&
!aDirEntry.m_aName.equalsAscii(".")) {
aDirEntry.m_aURL = viewurl + encodePathSegment(aDirEntry.m_aName);
sal_Bool isDir =
sal_Bool(aDirEntry.m_nMode&INETCOREFTP_FILEMODE_ISDIR);
switch(nMode) {
case OpenMode::DOCUMENTS:
if(!isDir)
resvec.push_back(aDirEntry);
break;
case OpenMode::FOLDERS:
if(isDir)
resvec.push_back(aDirEntry);
break;
default:
resvec.push_back(aDirEntry);
};
}
aDirEntry.clear();
p1 = p2 + 1;
}
return resvec;
}
rtl::OUString FTPURL::net_title() const
throw(curl_exception)
{
CURL *curl = m_pFCP->handle();
SET_CONTROL_CONTAINER;
curl_easy_setopt(curl,CURLOPT_NOBODY,true); // no data => no transfer
struct curl_slist *slist = 0;
// post request
slist = curl_slist_append(slist,"PWD");
curl_easy_setopt(curl,CURLOPT_POSTQUOTE,slist);
bool try_more(true);
CURLcode err;
rtl::OUString aNetTitle;
while(true) {
rtl::OUString url(ident(false,true));
if(try_more &&
1+url.lastIndexOf(sal_Unicode('/')) != url.getLength())
url += rtl::OUString::createFromAscii("/"); // add end-slash
else if(!try_more &&
1+url.lastIndexOf(sal_Unicode('/')) == url.getLength())
url = url.copy(0,url.getLength()-1); // remove end-slash
SET_URL(url);
err = curl_easy_perform(curl);
if(err == CURLE_OK) { // get the title from the server
char* fwd = (char*) control.m_pBuffer;
sal_uInt32 len = (sal_uInt32) control.m_nWritePos;
aNetTitle = rtl::OUString(fwd,len,RTL_TEXTENCODING_UTF8);
// the buffer now contains the name of the file;
// analyze the output:
// Format of current working directory:
// 257 "/bla/bla" is current directory
sal_Int32 index1 = aNetTitle.lastIndexOf(
rtl::OUString::createFromAscii("257"));
index1 = 1+aNetTitle.indexOf(sal_Unicode('"'),index1);
sal_Int32 index2 = aNetTitle.indexOf(sal_Unicode('"'),index1);
aNetTitle = aNetTitle.copy(index1,index2-index1);
if(!aNetTitle.equalsAscii("/")) {
index1 = aNetTitle.lastIndexOf(sal_Unicode('/'));
aNetTitle = aNetTitle.copy(1+index1);
}
try_more = false;
} else if(err == CURLE_BAD_PASSWORD_ENTERED)
// the client should retry after getting the correct
// username + password
throw curl_exception(err);
#if LIBCURL_VERSION_NUM>=0x070d01 /* 7.13.1 */
else if(err == CURLE_LOGIN_DENIED)
// the client should retry after getting the correct
// username + password
throw curl_exception(err);
#endif
else if(try_more && err == CURLE_FTP_ACCESS_DENIED) {
// We were either denied access when trying to login to
// an FTP server or when trying to change working directory
// to the one given in the URL.
if(m_aPathSegmentVec.size())
// determine title form url
aNetTitle = decodePathSegment(m_aPathSegmentVec.back());
else
// must be root
aNetTitle = rtl::OUString::createFromAscii("/");
try_more = false;
}
if(try_more)
try_more = false;
else
break;
}
curl_slist_free_all(slist);
return aNetTitle;
}
FTPDirentry FTPURL::direntry() const
throw(curl_exception)
{
rtl::OUString nettitle = net_title();
FTPDirentry aDirentry;
aDirentry.m_aName = nettitle; // init aDirentry
if(nettitle.equalsAscii("/") ||
nettitle.equalsAscii(".."))
aDirentry.m_nMode = INETCOREFTP_FILEMODE_ISDIR;
else
aDirentry.m_nMode = INETCOREFTP_FILEMODE_UNKNOWN;
aDirentry.m_nSize = 0;
if(!nettitle.equalsAscii("/")) {
// try to open the parent directory
FTPURL aURL(parent(),m_pFCP);
std::vector<FTPDirentry> aList = aURL.list(OpenMode::ALL);
for(unsigned i = 0; i < aList.size(); ++i) {
if(aList[i].m_aName == nettitle) { // the relevant file is found
aDirentry = aList[i];
break;
}
}
}
return aDirentry;
}
extern "C" {
size_t memory_read(void *ptr,size_t size,size_t nmemb,void *stream)
{
sal_Int32 nRequested = sal_Int32(size*nmemb);
CurlInput *curlInput = static_cast<CurlInput*>(stream);
if(curlInput)
return size_t(curlInput->read(((sal_Int8*)ptr),nRequested));
else
return 0;
}
}
void FTPURL::insert(bool replaceExisting,void* stream) const
throw(curl_exception)
{
if(!replaceExisting) {
// FTPDirentry aDirentry(direntry());
// if(aDirentry.m_nMode == INETCOREFTP_FILEMODE_UNKNOWN)
// throw curl_exception(FILE_EXIST_DURING_INSERT);
throw curl_exception(FILE_MIGHT_EXIST_DURING_INSERT);
} // else
// overwrite is default in libcurl
CURL *curl = m_pFCP->handle();
SET_CONTROL_CONTAINER;
curl_easy_setopt(curl,CURLOPT_NOBODY,false); // no data => no transfer
curl_easy_setopt(curl,CURLOPT_POSTQUOTE,0);
curl_easy_setopt(curl,CURLOPT_QUOTE,0);
curl_easy_setopt(curl,CURLOPT_READFUNCTION,memory_read);
curl_easy_setopt(curl,CURLOPT_READDATA,stream);
curl_easy_setopt(curl, CURLOPT_UPLOAD,1);
rtl::OUString url(ident(false,true));
SET_URL(url);
CURLcode err = curl_easy_perform(curl);
curl_easy_setopt(curl, CURLOPT_UPLOAD,false);
if(err != CURLE_OK)
throw curl_exception(err);
}
void FTPURL::mkdir(bool ReplaceExisting) const
throw(curl_exception)
{
rtl::OString title;
if(m_aPathSegmentVec.size()) {
rtl::OUString titleOU = m_aPathSegmentVec.back();
titleOU = decodePathSegment(titleOU);
title = rtl::OString(titleOU.getStr(),
titleOU.getLength(),
RTL_TEXTENCODING_UTF8);
}
else
// will give an error
title = rtl::OString("/");
rtl::OString aDel("del "); aDel += title;
rtl::OString mkd("mkd "); mkd += title;
struct curl_slist *slist = 0;
FTPDirentry aDirentry(direntry());
if(!ReplaceExisting) {
// if(aDirentry.m_nMode != INETCOREFTP_FILEMODE_UNKNOWN)
// throw curl_exception(FOLDER_EXIST_DURING_INSERT);
throw curl_exception(FOLDER_MIGHT_EXIST_DURING_INSERT);
} else if(aDirentry.m_nMode != INETCOREFTP_FILEMODE_UNKNOWN)
slist = curl_slist_append(slist,aDel.getStr());
slist = curl_slist_append(slist,mkd.getStr());
CURL *curl = m_pFCP->handle();
SET_CONTROL_CONTAINER;
curl_easy_setopt(curl,CURLOPT_NOBODY,true); // no data => no transfer
curl_easy_setopt(curl,CURLOPT_QUOTE,0);
// post request
curl_easy_setopt(curl,CURLOPT_POSTQUOTE,slist);
rtl::OUString url(parent(true));
if(1+url.lastIndexOf(sal_Unicode('/')) != url.getLength())
url += rtl::OUString::createFromAscii("/");
SET_URL(url);
CURLcode err = curl_easy_perform(curl);
curl_slist_free_all(slist);
if(err != CURLE_OK)
throw curl_exception(err);
}
rtl::OUString FTPURL::ren(const rtl::OUString& NewTitle)
throw(curl_exception)
{
CURL *curl = m_pFCP->handle();
// post request
rtl::OString renamefrom("RNFR ");
rtl::OUString OldTitle = net_title();
renamefrom +=
rtl::OString(OldTitle.getStr(),
OldTitle.getLength(),
RTL_TEXTENCODING_UTF8);
rtl::OString renameto("RNTO ");
renameto +=
rtl::OString(NewTitle.getStr(),
NewTitle.getLength(),
RTL_TEXTENCODING_UTF8);
struct curl_slist *slist = 0;
slist = curl_slist_append(slist,renamefrom.getStr());
slist = curl_slist_append(slist,renameto.getStr());
curl_easy_setopt(curl,CURLOPT_POSTQUOTE,slist);
SET_CONTROL_CONTAINER;
curl_easy_setopt(curl,CURLOPT_NOBODY,true); // no data => no transfer
curl_easy_setopt(curl,CURLOPT_QUOTE,0);
rtl::OUString url(parent(true));
if(1+url.lastIndexOf(sal_Unicode('/')) != url.getLength())
url += rtl::OUString::createFromAscii("/");
SET_URL(url);
CURLcode err = curl_easy_perform(curl);
curl_slist_free_all(slist);
if(err != CURLE_OK)
throw curl_exception(err);
else if(m_aPathSegmentVec.size() &&
!m_aPathSegmentVec.back().equalsAscii(".."))
m_aPathSegmentVec.back() = encodePathSegment(NewTitle);
return OldTitle;
}
void FTPURL::del() const
throw(curl_exception)
{
FTPDirentry aDirentry(direntry());
rtl::OString dele(aDirentry.m_aName.getStr(),
aDirentry.m_aName.getLength(),
RTL_TEXTENCODING_UTF8);
if(aDirentry.m_nMode & INETCOREFTP_FILEMODE_ISDIR) {
std::vector<FTPDirentry> vec = list(sal_Int16(OpenMode::ALL));
for( unsigned int i = 0; i < vec.size(); ++i )
try {
FTPURL url(vec[i].m_aURL,m_pFCP);
url.del();
} catch(const curl_exception&) {
}
dele = rtl::OString("RMD ") + dele;
}
else if(aDirentry.m_nMode != INETCOREFTP_FILEMODE_UNKNOWN)
dele = rtl::OString("DELE ") + dele;
else
return;
// post request
CURL *curl = m_pFCP->handle();
struct curl_slist *slist = 0;
slist = curl_slist_append(slist,dele.getStr());
curl_easy_setopt(curl,CURLOPT_POSTQUOTE,slist);
SET_CONTROL_CONTAINER;
curl_easy_setopt(curl,CURLOPT_NOBODY,true); // no data => no transfer
curl_easy_setopt(curl,CURLOPT_QUOTE,0);
rtl::OUString url(parent(true));
if(1+url.lastIndexOf(sal_Unicode('/')) != url.getLength())
url += rtl::OUString::createFromAscii("/");
SET_URL(url);
CURLcode err = curl_easy_perform(curl);
curl_slist_free_all(slist);
if(err != CURLE_OK)
throw curl_exception(err);
}