1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 /**************************************************************************
22 **************************************************************************
24 *************************************************************************/
26 #include <sal/config.h>
31 #include <rtl/ustrbuf.hxx>
32 #include <com/sun/star/ucb/OpenMode.hpp>
34 #include <rtl/uri.hxx>
36 #include "ftpstrcont.hxx"
38 #include "ftphandleprovider.hxx"
39 #include "ftpcfunc.hxx"
40 #include "ftpcontainer.hxx"
41 #include <boost/scoped_array.hpp>
44 using namespace com::sun::star::ucb
;
45 using namespace com::sun::star::uno
;
46 using namespace com::sun::star::io
;
50 OUString
encodePathSegment(OUString
const & decoded
) {
51 return rtl::Uri::encode(
52 decoded
, rtl_UriCharClassPchar
, rtl_UriEncodeIgnoreEscapes
,
53 RTL_TEXTENCODING_UTF8
);
56 OUString
decodePathSegment(OUString
const & encoded
) {
57 return rtl::Uri::decode(
58 encoded
, rtl_UriDecodeWithCharset
, RTL_TEXTENCODING_UTF8
);
63 MemoryContainer::MemoryContainer()
70 MemoryContainer::~MemoryContainer()
72 rtl_freeMemory(m_pBuffer
);
76 int MemoryContainer::append(
82 sal_uInt32 nLen
= size
*nmemb
;
83 sal_uInt32
tmp(nLen
+ m_nWritePos
);
85 if(m_nLen
< tmp
) { // enlarge in steps of multiples of 1K
88 } while(m_nLen
< tmp
);
90 m_pBuffer
= rtl_reallocateMemory(m_pBuffer
,m_nLen
);
93 memcpy(static_cast<sal_Int8
*>(m_pBuffer
)+m_nWritePos
,
102 int memory_write(void *buffer
,size_t size
,size_t nmemb
,void *stream
)
104 MemoryContainer
*_stream
=
105 static_cast<MemoryContainer
*>(stream
);
110 return _stream
->append(buffer
,size
,nmemb
);
116 FTPURL::FTPURL(const FTPURL
& r
)
118 m_aUsername(r
.m_aUsername
),
119 m_bShowPassword(r
.m_bShowPassword
),
122 m_aPathSegmentVec(r
.m_aPathSegmentVec
)
128 FTPURL::FTPURL(const OUString
& url
,
129 FTPHandleProvider
* pFCP
)
134 m_aUsername("anonymous"),
135 m_bShowPassword(false),
138 parse(url
); // can reset m_bShowPassword
147 void FTPURL::parse(const OUString
& url
)
152 OUString aPassword
,aAccount
;
153 OString
aIdent(url
.getStr(),
155 RTL_TEXTENCODING_UTF8
);
157 OString lower
= aIdent
.toAsciiLowerCase();
158 if(lower
.getLength() < 6 ||
159 strncmp("ftp://",lower
.getStr(),6))
160 throw malformed_exception();
162 boost::scoped_array
<char> buffer(new char[1+aIdent
.getLength()]);
163 const char* p2
= aIdent
.getStr();
167 char *p1
= buffer
.get(); // determine "username:password@host:port"
168 while((ch
= *p2
++) != '/' && ch
)
172 OUString
aExpr(buffer
.get(), strlen(buffer
.get()), RTL_TEXTENCODING_UTF8
);
174 sal_Int32 l
= aExpr
.indexOf('@');
175 m_aHost
= aExpr
.copy(1+l
);
178 // Now username and password.
179 aExpr
= aExpr
.copy(0,l
);
180 l
= aExpr
.indexOf(':');
182 aPassword
= aExpr
.copy(1+l
);
183 if(!aPassword
.isEmpty())
184 m_bShowPassword
= true;
187 // Overwritte only if the username is not empty.
188 m_aUsername
= aExpr
.copy(0,l
);
189 else if(!aExpr
.isEmpty())
193 l
= m_aHost
.lastIndexOf(':');
194 sal_Int32 ipv6Back
= m_aHost
.lastIndexOf(']');
195 if((ipv6Back
== -1 && l
!= -1) // not ipv6, but a port
197 (ipv6Back
!= -1 && 1+ipv6Back
== l
) // ipv6, and a port
200 if(1+l
<m_aHost
.getLength())
201 m_aPort
= m_aHost
.copy(1+l
);
202 m_aHost
= m_aHost
.copy(0,l
);
205 while(ch
) { // now determine the pathsegments ...
207 while((ch
= *p2
++) != '/' && ch
)
212 if( strcmp(buffer
.get(),"..") == 0 && !m_aPathSegmentVec
.empty() && m_aPathSegmentVec
.back() != ".." )
213 m_aPathSegmentVec
.pop_back();
214 else if(strcmp(buffer
.get(),".") == 0)
217 // This is a legal name.
218 m_aPathSegmentVec
.push_back(
219 OUString(buffer
.get(),
220 strlen(buffer
.get()),
221 RTL_TEXTENCODING_UTF8
));
228 m_pFCP
->setHost(m_aHost
,
234 // now check for something like ";type=i" at end of url
235 if(m_aPathSegmentVec
.size() &&
236 (l
= m_aPathSegmentVec
.back().indexOf(';')) != -1) {
237 m_aType
= m_aPathSegmentVec
.back().copy(l
);
238 m_aPathSegmentVec
.back() = m_aPathSegmentVec
.back().copy(0,l
);
243 OUString
FTPURL::ident(bool withslash
,bool internal
) const
245 // rebuild the url as one without ellipses,
246 // and more important, as one without username and
247 // password. ( These are set together with the command. )
250 bff
.appendAscii("ftp://");
252 if( m_aUsername
!= "anonymous" ) {
253 bff
.append(m_aUsername
);
255 OUString aPassword
,aAccount
;
256 m_pFCP
->forHost(m_aHost
,
262 if((m_bShowPassword
|| internal
) &&
263 !aPassword
.isEmpty() )
271 if( m_aPort
!= "21" )
278 for(unsigned i
= 0; i
< m_aPathSegmentVec
.size(); ++i
)
280 bff
.append(m_aPathSegmentVec
[i
]);
282 bff
.append('/').append(m_aPathSegmentVec
[i
]);
284 if(!bff
.isEmpty() && bff
[bff
.getLength()-1] != '/')
288 return bff
.makeStringAndClear();
292 OUString
FTPURL::parent(bool internal
) const
296 bff
.appendAscii("ftp://");
298 if( m_aUsername
!= "anonymous" ) {
299 bff
.append(m_aUsername
);
301 OUString aPassword
,aAccount
;
302 m_pFCP
->forHost(m_aHost
,
308 if((internal
|| m_bShowPassword
) && !aPassword
.isEmpty())
317 if( m_aPort
!= "21" )
326 for(unsigned int i
= 0; i
< m_aPathSegmentVec
.size(); ++i
)
327 if(1+i
== m_aPathSegmentVec
.size())
328 last
= m_aPathSegmentVec
[i
];
330 bff
.append(m_aPathSegmentVec
[i
]);
332 bff
.append('/').append(m_aPathSegmentVec
[i
]);
335 bff
.appendAscii("..");
336 else if ( last
== ".." )
337 bff
.append(last
).appendAscii("/..");
340 return bff
.makeStringAndClear();
344 void FTPURL::child(const OUString
& title
)
346 m_aPathSegmentVec
.push_back(encodePathSegment(title
));
350 OUString
FTPURL::child() const
353 m_aPathSegmentVec
.size() ?
354 decodePathSegment(m_aPathSegmentVec
.back()) : OUString();
359 /** Listing of a directory.
365 FTP_DOS
,FTP_UNIX
,FTP_VMS
,FTP_UNKNOWN
371 #define SET_CONTROL_CONTAINER \
372 MemoryContainer control; \
373 curl_easy_setopt(curl, \
374 CURLOPT_HEADERFUNCTION, \
376 curl_easy_setopt(curl, \
377 CURLOPT_WRITEHEADER, \
381 #define SET_DATA_CONTAINER \
382 curl_easy_setopt(curl,CURLOPT_NOBODY,false); \
383 MemoryContainer data; \
384 curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,memory_write); \
385 curl_easy_setopt(curl,CURLOPT_WRITEDATA,&data)
387 #define SET_URL(url) \
388 OString urlParAscii(url.getStr(), \
390 RTL_TEXTENCODING_UTF8); \
391 curl_easy_setopt(curl, \
393 urlParAscii.getStr());
395 oslFileHandle
FTPURL::open()
396 throw(curl_exception
)
398 if(m_aPathSegmentVec
.empty())
399 throw curl_exception(CURLE_FTP_COULDNT_RETR_FILE
);
401 CURL
*curl
= m_pFCP
->handle();
403 SET_CONTROL_CONTAINER
;
404 OUString
url(ident(false,true));
407 oslFileHandle
res( NULL
);
408 if ( osl_createTempFile( NULL
, &res
, NULL
) == osl_File_E_None
)
410 curl_easy_setopt(curl
,CURLOPT_WRITEFUNCTION
,file_write
);
411 curl_easy_setopt(curl
,CURLOPT_WRITEDATA
,res
);
413 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,0);
414 CURLcode err
= curl_easy_perform(curl
);
418 oslFileError rc
= osl_setFilePos( res
, osl_Pos_Absolut
, 0 );
419 SAL_WARN_IF(rc
!= osl_File_E_None
, "ucb.ucp.ftp",
420 "osl_setFilePos failed");
423 osl_closeFile(res
),res
= 0;
424 throw curl_exception(err
);
432 std::vector
<FTPDirentry
> FTPURL::list(
439 CURL
*curl
= m_pFCP
->handle();
441 SET_CONTROL_CONTAINER
;
443 OUString
url(ident(true,true));
445 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,0);
447 CURLcode err
= curl_easy_perform(curl
);
449 throw curl_exception(err
);
451 // now evaluate the error messages
453 sal_uInt32 len
= data
.m_nWritePos
;
454 char* fwd
= static_cast<char*>(data
.m_pBuffer
);
458 OS
osKind(FTP_UNKNOWN
);
459 std::vector
<FTPDirentry
> resvec
;
460 FTPDirentry aDirEntry
;
461 // ensure slash at the end
462 OUString
viewurl(ident(true,false));
465 while(p2
-fwd
< int(len
) && *p2
!= '\n') ++p2
;
466 if(p2
-fwd
== int(len
)) break;
470 // While FTP knows the 'system'-command,
471 // which returns the operating system type,
472 // this is not usable here: There are Windows-server
473 // formatting the output like UNIX-ls command.
475 FTPDirectoryParser::parseDOS(aDirEntry
,p1
);
478 FTPDirectoryParser::parseUNIX(aDirEntry
,p1
);
481 FTPDirectoryParser::parseVMS(aDirEntry
,p1
);
484 if(FTPDirectoryParser::parseUNIX(aDirEntry
,p1
))
486 else if(FTPDirectoryParser::parseDOS(aDirEntry
,p1
))
488 else if(FTPDirectoryParser::parseVMS(aDirEntry
,p1
))
491 aDirEntry
.m_aName
= aDirEntry
.m_aName
.trim();
492 if( osKind
!= int(FTP_UNKNOWN
) && aDirEntry
.m_aName
!= ".." && aDirEntry
.m_aName
!= "." ) {
493 aDirEntry
.m_aURL
= viewurl
+ encodePathSegment(aDirEntry
.m_aName
);
495 bool isDir
= (aDirEntry
.m_nMode
& INETCOREFTP_FILEMODE_ISDIR
) == INETCOREFTP_FILEMODE_ISDIR
;
497 case OpenMode::DOCUMENTS
:
499 resvec
.push_back(aDirEntry
);
501 case OpenMode::FOLDERS
:
503 resvec
.push_back(aDirEntry
);
506 resvec
.push_back(aDirEntry
);
517 OUString
FTPURL::net_title() const
518 throw(curl_exception
)
520 CURL
*curl
= m_pFCP
->handle();
522 SET_CONTROL_CONTAINER
;
523 curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
524 struct curl_slist
*slist
= 0;
526 slist
= curl_slist_append(slist
,"PWD");
527 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
534 OUString
url(ident(false,true));
536 if(try_more
&& !url
.endsWith("/"))
537 url
+= "/"; // add end-slash
538 else if(!try_more
&& url
.endsWith("/"))
539 url
= url
.copy(0,url
.getLength()-1); // remove end-slash
542 err
= curl_easy_perform(curl
);
544 if(err
== CURLE_OK
) { // get the title from the server
545 char* fwd
= static_cast<char*>(control
.m_pBuffer
);
546 sal_uInt32 len
= (sal_uInt32
) control
.m_nWritePos
;
548 aNetTitle
= OUString(fwd
,len
,RTL_TEXTENCODING_UTF8
);
549 // the buffer now contains the name of the file;
550 // analyze the output:
551 // Format of current working directory:
552 // 257 "/bla/bla" is current directory
553 sal_Int32 index1
= aNetTitle
.lastIndexOf("257");
554 index1
= aNetTitle
.indexOf('"', index1
+ std::strlen("257")) + 1;
555 sal_Int32 index2
= aNetTitle
.indexOf('"', index1
);
556 aNetTitle
= index2
> index1
557 ? aNetTitle
.copy(index1
, index2
- index1
) : OUString();
558 if( aNetTitle
!= "/" ) {
559 index1
= aNetTitle
.lastIndexOf('/');
560 aNetTitle
= aNetTitle
.copy(1+index1
);
563 } else if(err
== CURLE_BAD_PASSWORD_ENTERED
)
564 // the client should retry after getting the correct
565 // username + password
566 throw curl_exception(err
);
567 #if LIBCURL_VERSION_NUM>=0x070d01 /* 7.13.1 */
568 else if(err
== CURLE_LOGIN_DENIED
)
569 // the client should retry after getting the correct
570 // username + password
571 throw curl_exception(err
);
573 else if(try_more
&& err
== CURLE_FTP_ACCESS_DENIED
) {
574 // We were either denied access when trying to login to
575 // an FTP server or when trying to change working directory
576 // to the one given in the URL.
577 if(!m_aPathSegmentVec
.empty())
578 // determine title form url
579 aNetTitle
= decodePathSegment(m_aPathSegmentVec
.back());
592 curl_slist_free_all(slist
);
597 FTPDirentry
FTPURL::direntry() const
598 throw (curl_exception
, malformed_exception
)
600 OUString nettitle
= net_title();
601 FTPDirentry aDirentry
;
603 aDirentry
.m_aName
= nettitle
; // init aDirentry
604 if( nettitle
== "/" || nettitle
== ".." )
605 aDirentry
.m_nMode
= INETCOREFTP_FILEMODE_ISDIR
;
607 aDirentry
.m_nMode
= INETCOREFTP_FILEMODE_UNKNOWN
;
609 aDirentry
.m_nSize
= 0;
611 if( nettitle
!= "/" ) {
612 // try to open the parent directory
613 FTPURL
aURL(parent(),m_pFCP
);
615 std::vector
<FTPDirentry
> aList
= aURL
.list(OpenMode::ALL
);
617 for(unsigned i
= 0; i
< aList
.size(); ++i
) {
618 if(aList
[i
].m_aName
== nettitle
) { // the relevant file is found
619 aDirentry
= aList
[i
];
630 size_t memory_read(void *ptr
,size_t size
,size_t nmemb
,void *stream
)
632 sal_Int32 nRequested
= sal_Int32(size
*nmemb
);
633 CurlInput
*curlInput
= static_cast<CurlInput
*>(stream
);
635 return size_t(curlInput
->read(static_cast<sal_Int8
*>(ptr
),nRequested
));
643 void FTPURL::insert(bool replaceExisting
,void* stream
) const
644 throw(curl_exception
)
646 if(!replaceExisting
) {
647 // FTPDirentry aDirentry(direntry());
648 // if(aDirentry.m_nMode == INETCOREFTP_FILEMODE_UNKNOWN)
649 // throw curl_exception(FILE_EXIST_DURING_INSERT);
650 throw curl_exception(FILE_MIGHT_EXIST_DURING_INSERT
);
652 // overwrite is default in libcurl
654 CURL
*curl
= m_pFCP
->handle();
656 SET_CONTROL_CONTAINER
;
657 curl_easy_setopt(curl
,CURLOPT_NOBODY
,false); // no data => no transfer
658 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,0);
659 curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
660 curl_easy_setopt(curl
,CURLOPT_READFUNCTION
,memory_read
);
661 curl_easy_setopt(curl
,CURLOPT_READDATA
,stream
);
662 curl_easy_setopt(curl
, CURLOPT_UPLOAD
,1);
664 OUString
url(ident(false,true));
667 CURLcode err
= curl_easy_perform(curl
);
668 curl_easy_setopt(curl
, CURLOPT_UPLOAD
,false);
671 throw curl_exception(err
);
676 void FTPURL::mkdir(bool ReplaceExisting
) const
677 throw (curl_exception
, malformed_exception
)
680 if(!m_aPathSegmentVec
.empty()) {
681 OUString titleOU
= m_aPathSegmentVec
.back();
682 titleOU
= decodePathSegment(titleOU
);
683 title
= OString(titleOU
.getStr(),
685 RTL_TEXTENCODING_UTF8
);
688 // will give an error
689 title
= OString("/");
691 OString
aDel("del "); aDel
+= title
;
692 OString
mkd("mkd "); mkd
+= title
;
694 struct curl_slist
*slist
= 0;
696 FTPDirentry
aDirentry(direntry());
697 if(!ReplaceExisting
) {
698 // if(aDirentry.m_nMode != INETCOREFTP_FILEMODE_UNKNOWN)
699 // throw curl_exception(FOLDER_EXIST_DURING_INSERT);
700 throw curl_exception(FOLDER_MIGHT_EXIST_DURING_INSERT
);
701 } else if(aDirentry
.m_nMode
!= INETCOREFTP_FILEMODE_UNKNOWN
)
702 slist
= curl_slist_append(slist
,aDel
.getStr());
704 slist
= curl_slist_append(slist
,mkd
.getStr());
706 CURL
*curl
= m_pFCP
->handle();
707 SET_CONTROL_CONTAINER
;
708 curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
709 curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
712 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
714 OUString
url(parent(true));
715 if(!url
.endsWith("/"))
719 CURLcode err
= curl_easy_perform(curl
);
720 curl_slist_free_all(slist
);
722 throw curl_exception(err
);
726 OUString
FTPURL::ren(const OUString
& NewTitle
)
727 throw(curl_exception
)
729 CURL
*curl
= m_pFCP
->handle();
732 OString
renamefrom("RNFR ");
733 OUString OldTitle
= net_title();
735 OString(OldTitle
.getStr(),
736 OldTitle
.getLength(),
737 RTL_TEXTENCODING_UTF8
);
739 OString
renameto("RNTO ");
741 OString(NewTitle
.getStr(),
742 NewTitle
.getLength(),
743 RTL_TEXTENCODING_UTF8
);
745 struct curl_slist
*slist
= 0;
746 slist
= curl_slist_append(slist
,renamefrom
.getStr());
747 slist
= curl_slist_append(slist
,renameto
.getStr());
748 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
750 SET_CONTROL_CONTAINER
;
751 curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
752 curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
754 OUString
url(parent(true));
755 if(!url
.endsWith("/"))
759 CURLcode err
= curl_easy_perform(curl
);
760 curl_slist_free_all(slist
);
762 throw curl_exception(err
);
763 else if( m_aPathSegmentVec
.size() && m_aPathSegmentVec
.back() != ".." )
764 m_aPathSegmentVec
.back() = encodePathSegment(NewTitle
);
770 void FTPURL::del() const
771 throw(curl_exception
, malformed_exception
)
773 FTPDirentry
aDirentry(direntry());
775 OString
dele(aDirentry
.m_aName
.getStr(),
776 aDirentry
.m_aName
.getLength(),
777 RTL_TEXTENCODING_UTF8
);
779 if(aDirentry
.m_nMode
& INETCOREFTP_FILEMODE_ISDIR
) {
780 std::vector
<FTPDirentry
> vec
= list(sal_Int16(OpenMode::ALL
));
781 for( unsigned int i
= 0; i
< vec
.size(); ++i
)
783 FTPURL
url(vec
[i
].m_aURL
,m_pFCP
);
785 } catch(const curl_exception
&) {
787 dele
= OString("RMD ") + dele
;
789 else if(aDirentry
.m_nMode
!= INETCOREFTP_FILEMODE_UNKNOWN
)
790 dele
= OString("DELE ") + dele
;
795 CURL
*curl
= m_pFCP
->handle();
796 struct curl_slist
*slist
= 0;
797 slist
= curl_slist_append(slist
,dele
.getStr());
798 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
800 SET_CONTROL_CONTAINER
;
801 curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
802 curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
804 OUString
url(parent(true));
805 if(!url
.endsWith("/"))
809 CURLcode err
= curl_easy_perform(curl
);
810 curl_slist_free_all(slist
);
812 throw curl_exception(err
);
815 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */