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 *************************************************************************/
27 #include <rtl/ustrbuf.hxx>
28 #include <com/sun/star/ucb/OpenMode.hpp>
30 #include <rtl/uri.hxx>
32 #include "ftpstrcont.hxx"
34 #include "ftphandleprovider.hxx"
35 #include "ftpcfunc.hxx"
36 #include "ftpcontainer.hxx"
39 using namespace com::sun::star::ucb
;
40 using namespace com::sun::star::uno
;
41 using namespace com::sun::star::io
;
45 OUString
encodePathSegment(OUString
const & decoded
) {
46 return rtl::Uri::encode(
47 decoded
, rtl_UriCharClassPchar
, rtl_UriEncodeIgnoreEscapes
,
48 RTL_TEXTENCODING_UTF8
);
51 OUString
decodePathSegment(OUString
const & encoded
) {
52 return rtl::Uri::decode(
53 encoded
, rtl_UriDecodeWithCharset
, RTL_TEXTENCODING_UTF8
);
58 MemoryContainer::MemoryContainer()
65 MemoryContainer::~MemoryContainer()
67 rtl_freeMemory(m_pBuffer
);
71 int MemoryContainer::append(
77 sal_uInt32 nLen
= size
*nmemb
;
78 sal_uInt32
tmp(nLen
+ m_nWritePos
);
80 if(m_nLen
< tmp
) { // enlarge in steps of multiples of 1K
83 } while(m_nLen
< tmp
);
85 m_pBuffer
= rtl_reallocateMemory(m_pBuffer
,m_nLen
);
88 memcpy(static_cast<sal_Int8
*>(m_pBuffer
)+m_nWritePos
,
97 int memory_write(void *buffer
,size_t size
,size_t nmemb
,void *stream
)
99 MemoryContainer
*_stream
=
100 reinterpret_cast<MemoryContainer
*>(stream
);
105 return _stream
->append(buffer
,size
,nmemb
);
111 FTPURL::FTPURL(const FTPURL
& r
)
114 m_aUsername(r
.m_aUsername
),
115 m_bShowPassword(r
.m_bShowPassword
),
118 m_aPathSegmentVec(r
.m_aPathSegmentVec
)
124 FTPURL::FTPURL(const OUString
& url
,
125 FTPHandleProvider
* pFCP
)
130 m_aUsername("anonymous"),
131 m_bShowPassword(false),
134 parse(url
); // can reset m_bShowPassword
143 void FTPURL::parse(const OUString
& url
)
148 OUString aPassword
,aAccount
;
149 OString
aIdent(url
.getStr(),
151 RTL_TEXTENCODING_UTF8
);
153 OString lower
= aIdent
.toAsciiLowerCase();
154 if(lower
.getLength() < 6 ||
155 strncmp("ftp://",lower
.getStr(),6))
156 throw malformed_exception();
158 char *buffer
= new char[1+aIdent
.getLength()];
159 const char* p2
= aIdent
.getStr();
163 char *p1
= buffer
; // determine "username:password@host:port"
164 while((ch
= *p2
++) != '/' && ch
)
168 OUString
aExpr(OUString(buffer
,strlen(buffer
),
169 RTL_TEXTENCODING_UTF8
));
171 sal_Int32 l
= aExpr
.indexOf(sal_Unicode('@'));
172 m_aHost
= aExpr
.copy(1+l
);
175 // Now username and password.
176 aExpr
= aExpr
.copy(0,l
);
177 l
= aExpr
.indexOf(sal_Unicode(':'));
179 aPassword
= aExpr
.copy(1+l
);
180 if(!aPassword
.isEmpty())
181 m_bShowPassword
= true;
184 // Overwritte only if the username is not empty.
185 m_aUsername
= aExpr
.copy(0,l
);
186 else if(!aExpr
.isEmpty())
190 l
= m_aHost
.lastIndexOf(sal_Unicode(':'));
191 sal_Int32 ipv6Back
= m_aHost
.lastIndexOf(sal_Unicode(']'));
192 if((ipv6Back
== -1 && l
!= -1) // not ipv6, but a port
194 (ipv6Back
!= -1 && 1+ipv6Back
== l
) // ipv6, and a port
197 if(1+l
<m_aHost
.getLength())
198 m_aPort
= m_aHost
.copy(1+l
);
199 m_aHost
= m_aHost
.copy(0,l
);
202 while(ch
) { // now determine the pathsegments ...
204 while((ch
= *p2
++) != '/' && ch
)
209 if( strcmp(buffer
,"..") == 0 && !m_aPathSegmentVec
.empty() && m_aPathSegmentVec
.back() != ".." )
210 m_aPathSegmentVec
.pop_back();
211 else if(strcmp(buffer
,".") == 0)
214 // This is a legal name.
215 m_aPathSegmentVec
.push_back(
218 RTL_TEXTENCODING_UTF8
));
225 m_pFCP
->setHost(m_aHost
,
231 // now check for something like ";type=i" at end of url
232 if(m_aPathSegmentVec
.size() &&
233 (l
= m_aPathSegmentVec
.back().indexOf(sal_Unicode(';'))) != -1) {
234 m_aType
= m_aPathSegmentVec
.back().copy(l
);
235 m_aPathSegmentVec
.back() = m_aPathSegmentVec
.back().copy(0,l
);
240 OUString
FTPURL::ident(bool withslash
,bool internal
) const
242 // rebuild the url as one without ellipses,
243 // and more important, as one without username and
244 // password. ( These are set together with the command. )
247 bff
.appendAscii("ftp://");
249 if( m_aUsername
!= "anonymous" ) {
250 bff
.append(m_aUsername
);
252 OUString aPassword
,aAccount
;
253 m_pFCP
->forHost(m_aHost
,
259 if((m_bShowPassword
|| internal
) &&
260 !aPassword
.isEmpty() )
261 bff
.append(sal_Unicode(':'))
264 bff
.append(sal_Unicode('@'));
268 if( m_aPort
!= "21" )
269 bff
.append(sal_Unicode(':'))
271 .append(sal_Unicode('/'));
273 bff
.append(sal_Unicode('/'));
275 for(unsigned i
= 0; i
< m_aPathSegmentVec
.size(); ++i
)
277 bff
.append(m_aPathSegmentVec
[i
]);
279 bff
.append(sal_Unicode('/')).append(m_aPathSegmentVec
[i
]);
281 if(bff
.getLength() && bff
[bff
.getLength()-1] != sal_Unicode('/'))
282 bff
.append(sal_Unicode('/'));
285 return bff
.makeStringAndClear();
289 OUString
FTPURL::parent(bool internal
) const
293 bff
.appendAscii("ftp://");
295 if( m_aUsername
!= "anonymous" ) {
296 bff
.append(m_aUsername
);
298 OUString aPassword
,aAccount
;
299 m_pFCP
->forHost(m_aHost
,
305 if((internal
|| m_bShowPassword
) && !aPassword
.isEmpty())
306 bff
.append(sal_Unicode(':'))
309 bff
.append(sal_Unicode('@'));
314 if( m_aPort
!= "21" )
315 bff
.append(sal_Unicode(':'))
317 .append(sal_Unicode('/'));
319 bff
.append(sal_Unicode('/'));
323 for(unsigned int i
= 0; i
< m_aPathSegmentVec
.size(); ++i
)
324 if(1+i
== m_aPathSegmentVec
.size())
325 last
= m_aPathSegmentVec
[i
];
327 bff
.append(m_aPathSegmentVec
[i
]);
329 bff
.append(sal_Unicode('/')).append(m_aPathSegmentVec
[i
]);
332 bff
.appendAscii("..");
333 else if ( last
== ".." )
334 bff
.append(last
).appendAscii("/..");
337 return bff
.makeStringAndClear();
341 void FTPURL::child(const OUString
& title
)
343 m_aPathSegmentVec
.push_back(encodePathSegment(title
));
347 OUString
FTPURL::child() const
350 m_aPathSegmentVec
.size() ?
351 decodePathSegment(m_aPathSegmentVec
.back()) : OUString();
356 /** Listing of a directory.
362 FTP_DOS
,FTP_UNIX
,FTP_VMS
,FTP_UNKNOWN
368 #define SET_CONTROL_CONTAINER \
369 MemoryContainer control; \
370 curl_easy_setopt(curl, \
371 CURLOPT_HEADERFUNCTION, \
373 curl_easy_setopt(curl, \
374 CURLOPT_WRITEHEADER, \
378 #define SET_DATA_CONTAINER \
379 curl_easy_setopt(curl,CURLOPT_NOBODY,false); \
380 MemoryContainer data; \
381 curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,memory_write); \
382 curl_easy_setopt(curl,CURLOPT_WRITEDATA,&data)
384 #define SET_URL(url) \
385 OString urlParAscii(url.getStr(), \
387 RTL_TEXTENCODING_UTF8); \
388 curl_easy_setopt(curl, \
390 urlParAscii.getStr());
393 throw(curl_exception
)
395 if(m_aPathSegmentVec
.empty())
396 throw curl_exception(CURLE_FTP_COULDNT_RETR_FILE
);
398 CURL
*curl
= m_pFCP
->handle();
400 SET_CONTROL_CONTAINER
;
401 OUString
url(ident(false,true));
403 FILE *res
= tmpfile();
404 curl_easy_setopt(curl
,CURLOPT_WRITEFUNCTION
,file_write
);
405 curl_easy_setopt(curl
,CURLOPT_WRITEDATA
,res
);
407 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,0);
408 CURLcode err
= curl_easy_perform(curl
);
414 throw curl_exception(err
);
421 std::vector
<FTPDirentry
> FTPURL::list(
428 CURL
*curl
= m_pFCP
->handle();
430 SET_CONTROL_CONTAINER
;
432 OUString
url(ident(true,true));
434 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,0);
436 CURLcode err
= curl_easy_perform(curl
);
438 throw curl_exception(err
);
440 // now evaluate the error messages
442 sal_uInt32 len
= data
.m_nWritePos
;
443 char* fwd
= (char*) data
.m_pBuffer
;
447 OS
osKind(FTP_UNKNOWN
);
448 std::vector
<FTPDirentry
> resvec
;
449 FTPDirentry aDirEntry
;
450 // ensure slash at the end
451 OUString
viewurl(ident(true,false));
454 while(p2
-fwd
< int(len
) && *p2
!= '\n') ++p2
;
455 if(p2
-fwd
== int(len
)) break;
459 // While FTP knows the 'system'-command,
460 // which returns the operating system type,
461 // this is not usable here: There are Windows-server
462 // formatting the output like UNIX-ls command.
464 FTPDirectoryParser::parseDOS(aDirEntry
,p1
);
467 FTPDirectoryParser::parseUNIX(aDirEntry
,p1
);
470 FTPDirectoryParser::parseVMS(aDirEntry
,p1
);
473 if(FTPDirectoryParser::parseUNIX(aDirEntry
,p1
))
475 else if(FTPDirectoryParser::parseDOS(aDirEntry
,p1
))
477 else if(FTPDirectoryParser::parseVMS(aDirEntry
,p1
))
480 aDirEntry
.m_aName
= aDirEntry
.m_aName
.trim();
481 if( osKind
!= int(FTP_UNKNOWN
) && aDirEntry
.m_aName
!= ".." && aDirEntry
.m_aName
!= "." ) {
482 aDirEntry
.m_aURL
= viewurl
+ encodePathSegment(aDirEntry
.m_aName
);
485 sal_Bool(aDirEntry
.m_nMode
&INETCOREFTP_FILEMODE_ISDIR
);
487 case OpenMode::DOCUMENTS
:
489 resvec
.push_back(aDirEntry
);
491 case OpenMode::FOLDERS
:
493 resvec
.push_back(aDirEntry
);
496 resvec
.push_back(aDirEntry
);
507 OUString
FTPURL::net_title() const
508 throw(curl_exception
)
510 CURL
*curl
= m_pFCP
->handle();
512 SET_CONTROL_CONTAINER
;
513 curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
514 struct curl_slist
*slist
= 0;
516 slist
= curl_slist_append(slist
,"PWD");
517 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
524 OUString
url(ident(false,true));
527 1+url
.lastIndexOf(sal_Unicode('/')) != url
.getLength())
528 url
+= OUString("/"); // add end-slash
530 1+url
.lastIndexOf(sal_Unicode('/')) == url
.getLength())
531 url
= url
.copy(0,url
.getLength()-1); // remove end-slash
534 err
= curl_easy_perform(curl
);
536 if(err
== CURLE_OK
) { // get the title from the server
537 char* fwd
= (char*) control
.m_pBuffer
;
538 sal_uInt32 len
= (sal_uInt32
) control
.m_nWritePos
;
540 aNetTitle
= OUString(fwd
,len
,RTL_TEXTENCODING_UTF8
);
541 // the buffer now contains the name of the file;
542 // analyze the output:
543 // Format of current working directory:
544 // 257 "/bla/bla" is current directory
545 sal_Int32 index1
= aNetTitle
.lastIndexOf(
547 index1
= 1+aNetTitle
.indexOf(sal_Unicode('"'),index1
);
548 sal_Int32 index2
= aNetTitle
.indexOf(sal_Unicode('"'),index1
);
549 aNetTitle
= aNetTitle
.copy(index1
,index2
-index1
);
550 if( aNetTitle
!= "/" ) {
551 index1
= aNetTitle
.lastIndexOf(sal_Unicode('/'));
552 aNetTitle
= aNetTitle
.copy(1+index1
);
555 } else if(err
== CURLE_BAD_PASSWORD_ENTERED
)
556 // the client should retry after getting the correct
557 // username + password
558 throw curl_exception(err
);
559 #if LIBCURL_VERSION_NUM>=0x070d01 /* 7.13.1 */
560 else if(err
== CURLE_LOGIN_DENIED
)
561 // the client should retry after getting the correct
562 // username + password
563 throw curl_exception(err
);
565 else if(try_more
&& err
== CURLE_FTP_ACCESS_DENIED
) {
566 // We were either denied access when trying to login to
567 // an FTP server or when trying to change working directory
568 // to the one given in the URL.
569 if(!m_aPathSegmentVec
.empty())
570 // determine title form url
571 aNetTitle
= decodePathSegment(m_aPathSegmentVec
.back());
574 aNetTitle
= OUString("/");
584 curl_slist_free_all(slist
);
589 FTPDirentry
FTPURL::direntry() const
590 throw(curl_exception
)
592 OUString nettitle
= net_title();
593 FTPDirentry aDirentry
;
595 aDirentry
.m_aName
= nettitle
; // init aDirentry
596 if( nettitle
== "/" || nettitle
== ".." )
597 aDirentry
.m_nMode
= INETCOREFTP_FILEMODE_ISDIR
;
599 aDirentry
.m_nMode
= INETCOREFTP_FILEMODE_UNKNOWN
;
601 aDirentry
.m_nSize
= 0;
603 if( nettitle
!= "/" ) {
604 // try to open the parent directory
605 FTPURL
aURL(parent(),m_pFCP
);
607 std::vector
<FTPDirentry
> aList
= aURL
.list(OpenMode::ALL
);
609 for(unsigned i
= 0; i
< aList
.size(); ++i
) {
610 if(aList
[i
].m_aName
== nettitle
) { // the relevant file is found
611 aDirentry
= aList
[i
];
622 size_t memory_read(void *ptr
,size_t size
,size_t nmemb
,void *stream
)
624 sal_Int32 nRequested
= sal_Int32(size
*nmemb
);
625 CurlInput
*curlInput
= static_cast<CurlInput
*>(stream
);
627 return size_t(curlInput
->read(((sal_Int8
*)ptr
),nRequested
));
635 void FTPURL::insert(bool replaceExisting
,void* stream
) const
636 throw(curl_exception
)
638 if(!replaceExisting
) {
639 // FTPDirentry aDirentry(direntry());
640 // if(aDirentry.m_nMode == INETCOREFTP_FILEMODE_UNKNOWN)
641 // throw curl_exception(FILE_EXIST_DURING_INSERT);
642 throw curl_exception(FILE_MIGHT_EXIST_DURING_INSERT
);
644 // overwrite is default in libcurl
646 CURL
*curl
= m_pFCP
->handle();
648 SET_CONTROL_CONTAINER
;
649 curl_easy_setopt(curl
,CURLOPT_NOBODY
,false); // no data => no transfer
650 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,0);
651 curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
652 curl_easy_setopt(curl
,CURLOPT_READFUNCTION
,memory_read
);
653 curl_easy_setopt(curl
,CURLOPT_READDATA
,stream
);
654 curl_easy_setopt(curl
, CURLOPT_UPLOAD
,1);
656 OUString
url(ident(false,true));
659 CURLcode err
= curl_easy_perform(curl
);
660 curl_easy_setopt(curl
, CURLOPT_UPLOAD
,false);
663 throw curl_exception(err
);
668 void FTPURL::mkdir(bool ReplaceExisting
) const
669 throw(curl_exception
)
672 if(!m_aPathSegmentVec
.empty()) {
673 OUString titleOU
= m_aPathSegmentVec
.back();
674 titleOU
= decodePathSegment(titleOU
);
675 title
= OString(titleOU
.getStr(),
677 RTL_TEXTENCODING_UTF8
);
680 // will give an error
681 title
= OString("/");
683 OString
aDel("del "); aDel
+= title
;
684 OString
mkd("mkd "); mkd
+= title
;
686 struct curl_slist
*slist
= 0;
688 FTPDirentry
aDirentry(direntry());
689 if(!ReplaceExisting
) {
690 // if(aDirentry.m_nMode != INETCOREFTP_FILEMODE_UNKNOWN)
691 // throw curl_exception(FOLDER_EXIST_DURING_INSERT);
692 throw curl_exception(FOLDER_MIGHT_EXIST_DURING_INSERT
);
693 } else if(aDirentry
.m_nMode
!= INETCOREFTP_FILEMODE_UNKNOWN
)
694 slist
= curl_slist_append(slist
,aDel
.getStr());
696 slist
= curl_slist_append(slist
,mkd
.getStr());
698 CURL
*curl
= m_pFCP
->handle();
699 SET_CONTROL_CONTAINER
;
700 curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
701 curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
704 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
706 OUString
url(parent(true));
707 if(1+url
.lastIndexOf(sal_Unicode('/')) != url
.getLength())
708 url
+= OUString("/");
711 CURLcode err
= curl_easy_perform(curl
);
712 curl_slist_free_all(slist
);
714 throw curl_exception(err
);
718 OUString
FTPURL::ren(const OUString
& NewTitle
)
719 throw(curl_exception
)
721 CURL
*curl
= m_pFCP
->handle();
724 OString
renamefrom("RNFR ");
725 OUString OldTitle
= net_title();
727 OString(OldTitle
.getStr(),
728 OldTitle
.getLength(),
729 RTL_TEXTENCODING_UTF8
);
731 OString
renameto("RNTO ");
733 OString(NewTitle
.getStr(),
734 NewTitle
.getLength(),
735 RTL_TEXTENCODING_UTF8
);
737 struct curl_slist
*slist
= 0;
738 slist
= curl_slist_append(slist
,renamefrom
.getStr());
739 slist
= curl_slist_append(slist
,renameto
.getStr());
740 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
742 SET_CONTROL_CONTAINER
;
743 curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
744 curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
746 OUString
url(parent(true));
747 if(1+url
.lastIndexOf(sal_Unicode('/')) != url
.getLength())
748 url
+= OUString("/");
751 CURLcode err
= curl_easy_perform(curl
);
752 curl_slist_free_all(slist
);
754 throw curl_exception(err
);
755 else if( m_aPathSegmentVec
.size() && m_aPathSegmentVec
.back() != ".." )
756 m_aPathSegmentVec
.back() = encodePathSegment(NewTitle
);
762 void FTPURL::del() const
763 throw(curl_exception
)
765 FTPDirentry
aDirentry(direntry());
767 OString
dele(aDirentry
.m_aName
.getStr(),
768 aDirentry
.m_aName
.getLength(),
769 RTL_TEXTENCODING_UTF8
);
771 if(aDirentry
.m_nMode
& INETCOREFTP_FILEMODE_ISDIR
) {
772 std::vector
<FTPDirentry
> vec
= list(sal_Int16(OpenMode::ALL
));
773 for( unsigned int i
= 0; i
< vec
.size(); ++i
)
775 FTPURL
url(vec
[i
].m_aURL
,m_pFCP
);
777 } catch(const curl_exception
&) {
779 dele
= OString("RMD ") + dele
;
781 else if(aDirentry
.m_nMode
!= INETCOREFTP_FILEMODE_UNKNOWN
)
782 dele
= OString("DELE ") + dele
;
787 CURL
*curl
= m_pFCP
->handle();
788 struct curl_slist
*slist
= 0;
789 slist
= curl_slist_append(slist
,dele
.getStr());
790 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
792 SET_CONTROL_CONTAINER
;
793 curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
794 curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
796 OUString
url(parent(true));
797 if(1+url
.lastIndexOf(sal_Unicode('/')) != url
.getLength())
798 url
+= OUString("/");
801 CURLcode err
= curl_easy_perform(curl
);
802 curl_slist_free_all(slist
);
804 throw curl_exception(err
);
807 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */