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>
27 #include <sal/log.hxx>
29 #include <rtl/ustrbuf.hxx>
30 #include <com/sun/star/ucb/OpenMode.hpp>
32 #include <rtl/uri.hxx>
33 #include <o3tl/safeint.hxx>
36 #include "ftpcontentprovider.hxx"
37 #include "ftpcfunc.hxx"
38 #include "ftpcontainer.hxx"
42 using namespace com::sun::star::ucb
;
43 using namespace com::sun::star::uno
;
47 OUString
encodePathSegment(OUString
const & decoded
) {
48 return rtl::Uri::encode(
49 decoded
, rtl_UriCharClassPchar
, rtl_UriEncodeIgnoreEscapes
,
50 RTL_TEXTENCODING_UTF8
);
53 OUString
decodePathSegment(OUString
const & encoded
) {
54 return rtl::Uri::decode(
55 encoded
, rtl_UriDecodeWithCharset
, RTL_TEXTENCODING_UTF8
);
60 MemoryContainer::MemoryContainer()
67 MemoryContainer::~MemoryContainer()
73 int MemoryContainer::append(
79 sal_uInt32 nLen
= size
*nmemb
;
80 sal_uInt32
tmp(nLen
+ m_nWritePos
);
82 if(m_nLen
< tmp
) { // enlarge in steps of multiples of 1K
85 } while(m_nLen
< tmp
);
87 if (auto p
= std::realloc(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 FTPContentProvider
* pFCP
)
131 m_aUsername("anonymous"),
132 m_bShowPassword(false),
135 parse(url
); // can reset m_bShowPassword
144 void FTPURL::parse(const OUString
& url
)
146 OUString aPassword
, urlRest
;
148 if(url
.getLength() < 6 || !url
.startsWithIgnoreAsciiCase("ftp://", &urlRest
))
149 throw malformed_exception();
151 // determine "username:password@host:port"
153 sal_Int32 nIdx
= urlRest
.indexOf('/');
161 aExpr
= urlRest
.copy(0, nIdx
);
162 urlRest
= urlRest
.copy(nIdx
+ 1);
165 sal_Int32 l
= aExpr
.indexOf('@');
166 m_aHost
= aExpr
.copy(1+l
);
169 // Now username and password.
170 aExpr
= aExpr
.copy(0,l
);
171 l
= aExpr
.indexOf(':');
173 aPassword
= aExpr
.copy(1+l
);
174 if(!aPassword
.isEmpty())
175 m_bShowPassword
= true;
178 // Overwritten only if the username is not empty.
179 m_aUsername
= aExpr
.copy(0,l
);
180 else if(!aExpr
.isEmpty())
184 l
= m_aHost
.lastIndexOf(':');
185 sal_Int32 ipv6Back
= m_aHost
.lastIndexOf(']');
186 if((ipv6Back
== -1 && l
!= -1) // not ipv6, but a port
188 (ipv6Back
!= -1 && 1+ipv6Back
== l
) // ipv6, and a port
191 if(1+l
<m_aHost
.getLength())
192 m_aPort
= m_aHost
.copy(1+l
);
193 m_aHost
= m_aHost
.copy(0,l
);
196 // now determine the pathsegments ...
197 while(!urlRest
.isEmpty())
199 nIdx
= urlRest
.indexOf('/');
208 segment
= urlRest
.copy(0, nIdx
);
209 urlRest
= urlRest
.copy(nIdx
+ 1);
211 if( segment
== ".." && !m_aPathSegmentVec
.empty() && m_aPathSegmentVec
.back() != ".." )
212 m_aPathSegmentVec
.pop_back();
213 else if( segment
== "." )
216 // This is a legal name.
217 m_aPathSegmentVec
.push_back( segment
);
221 m_pFCP
->setHost(m_aHost
,
227 // now check for something like ";type=i" at end of url
228 if(!m_aPathSegmentVec
.empty())
230 l
= m_aPathSegmentVec
.back().indexOf(';');
233 m_aType
= m_aPathSegmentVec
.back().copy(l
);
234 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. )
246 OUStringBuffer
bff("ftp://");
248 if( m_aUsername
!= "anonymous" ) {
249 bff
.append(m_aUsername
);
251 OUString aPassword
,aAccount
;
252 m_pFCP
->forHost(m_aHost
,
258 if((m_bShowPassword
|| internal
) &&
259 !aPassword
.isEmpty() )
260 bff
.append(":" + aPassword
);
266 if( m_aPort
!= "21" )
267 bff
.append(":" + m_aPort
+ "/");
271 for(size_t i
= 0; i
< m_aPathSegmentVec
.size(); ++i
)
273 bff
.append(m_aPathSegmentVec
[i
]);
275 bff
.append("/" + m_aPathSegmentVec
[i
]);
277 if(!bff
.isEmpty() && bff
[bff
.getLength()-1] != '/')
281 return bff
.makeStringAndClear();
285 OUString
FTPURL::parent(bool internal
) const
287 OUStringBuffer
bff("ftp://");
289 if( m_aUsername
!= "anonymous" ) {
290 bff
.append(m_aUsername
);
292 OUString aPassword
,aAccount
;
293 m_pFCP
->forHost(m_aHost
,
299 if((internal
|| m_bShowPassword
) && !aPassword
.isEmpty())
300 bff
.append(":" + aPassword
);
307 if( m_aPort
!= "21" )
308 bff
.append(":" + m_aPort
+ "/");
314 for(size_t i
= 0; i
< m_aPathSegmentVec
.size(); ++i
)
315 if(1+i
== m_aPathSegmentVec
.size())
316 last
= m_aPathSegmentVec
[i
];
318 bff
.append(m_aPathSegmentVec
[i
]);
320 bff
.append("/" + m_aPathSegmentVec
[i
]);
324 else if ( last
== ".." )
325 bff
.append(last
+ "/..");
328 return bff
.makeStringAndClear();
332 void FTPURL::child(const OUString
& title
)
334 m_aPathSegmentVec
.push_back(encodePathSegment(title
));
338 OUString
FTPURL::child() const
341 !m_aPathSegmentVec
.empty() ?
342 decodePathSegment(m_aPathSegmentVec
.back()) : OUString();
346 /** Listing of a directory.
354 FTP_DOS
,FTP_UNIX
,FTP_VMS
,FTP_UNKNOWN
362 #define SET_CONTROL_CONTAINER \
363 MemoryContainer control; \
364 (void)curl_easy_setopt(curl, \
365 CURLOPT_HEADERFUNCTION, \
367 (void)curl_easy_setopt(curl, \
368 CURLOPT_WRITEHEADER, \
372 static void setCurlUrl(CURL
* curl
, OUString
const & url
)
374 OString
urlParAscii(url
.getStr(),
376 RTL_TEXTENCODING_UTF8
);
377 (void)curl_easy_setopt(curl
,
379 urlParAscii
.getStr());
382 oslFileHandle
FTPURL::open()
384 if(m_aPathSegmentVec
.empty())
385 throw curl_exception(CURLE_FTP_COULDNT_RETR_FILE
);
387 CURL
*curl
= m_pFCP
->handle();
389 SET_CONTROL_CONTAINER
;
390 OUString
url(ident(false,true));
391 setCurlUrl(curl
, url
);
393 oslFileHandle
res( nullptr );
394 if ( osl_createTempFile( nullptr, &res
, nullptr ) == osl_File_E_None
)
396 (void)curl_easy_setopt(curl
,CURLOPT_WRITEFUNCTION
,file_write
);
397 (void)curl_easy_setopt(curl
,CURLOPT_WRITEDATA
,res
);
399 (void)curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,0);
400 CURLcode err
= curl_easy_perform(curl
);
404 oslFileError rc
= osl_setFilePos( res
, osl_Pos_Absolut
, 0 );
405 SAL_WARN_IF(rc
!= osl_File_E_None
, "ucb.ucp.ftp",
406 "osl_setFilePos failed");
411 throw curl_exception(err
);
419 std::vector
<FTPDirentry
> FTPURL::list(
423 CURL
*curl
= m_pFCP
->handle();
425 SET_CONTROL_CONTAINER
;
426 (void)curl_easy_setopt(curl
,CURLOPT_NOBODY
,false);
427 MemoryContainer data
;
428 (void)curl_easy_setopt(curl
,CURLOPT_WRITEFUNCTION
,memory_write
);
429 (void)curl_easy_setopt(curl
,CURLOPT_WRITEDATA
,&data
);
431 OUString
url(ident(true,true));
432 setCurlUrl(curl
, url
);
433 (void)curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,0);
435 CURLcode err
= curl_easy_perform(curl
);
437 throw curl_exception(err
);
439 // now evaluate the error messages
441 sal_uInt32 len
= data
.m_nWritePos
;
442 char* fwd
= static_cast<char*>(data
.m_pBuffer
);
446 OS
osKind(FTP_UNKNOWN
);
447 std::vector
<FTPDirentry
> resvec
;
448 FTPDirentry aDirEntry
;
449 // ensure slash at the end
450 OUString
viewurl(ident(true,false));
453 while(o3tl::make_unsigned(p2
-fwd
) < len
&& *p2
!= '\n') ++p2
;
454 if(o3tl::make_unsigned(p2
-fwd
) == len
) break;
458 // While FTP knows the 'system'-command,
459 // which returns the operating system type,
460 // this is not usable here: There are Windows-server
461 // formatting the output like UNIX-ls command.
463 FTPDirectoryParser::parseDOS(aDirEntry
,p1
);
466 FTPDirectoryParser::parseUNIX(aDirEntry
,p1
);
469 FTPDirectoryParser::parseVMS(aDirEntry
,p1
);
472 if(FTPDirectoryParser::parseUNIX(aDirEntry
,p1
))
474 else if(FTPDirectoryParser::parseDOS(aDirEntry
,p1
))
476 else if(FTPDirectoryParser::parseVMS(aDirEntry
,p1
))
479 aDirEntry
.m_aName
= aDirEntry
.m_aName
.trim();
480 if( osKind
!= int(FTP_UNKNOWN
) && aDirEntry
.m_aName
!= ".." && aDirEntry
.m_aName
!= "." ) {
481 aDirEntry
.m_aURL
= viewurl
+ encodePathSegment(aDirEntry
.m_aName
);
483 bool isDir
= (aDirEntry
.m_nMode
& INETCOREFTP_FILEMODE_ISDIR
) == INETCOREFTP_FILEMODE_ISDIR
;
485 case OpenMode::DOCUMENTS
:
487 resvec
.push_back(aDirEntry
);
489 case OpenMode::FOLDERS
:
491 resvec
.push_back(aDirEntry
);
494 resvec
.push_back(aDirEntry
);
505 OUString
FTPURL::net_title() const
507 CURL
*curl
= m_pFCP
->handle();
509 SET_CONTROL_CONTAINER
;
510 (void)curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
511 struct curl_slist
*slist
= nullptr;
513 slist
= curl_slist_append(slist
,"PWD");
514 (void)curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
521 OUString
url(ident(false,true));
523 if(try_more
&& !url
.endsWith("/"))
524 url
+= "/"; // add end-slash
525 else if(!try_more
&& url
.endsWith("/"))
526 url
= url
.copy(0,url
.getLength()-1); // remove end-slash
528 setCurlUrl(curl
, url
);
529 err
= curl_easy_perform(curl
);
531 if(err
== CURLE_OK
) { // get the title from the server
532 char* fwd
= static_cast<char*>(control
.m_pBuffer
);
533 sal_uInt32 len
= control
.m_nWritePos
;
535 aNetTitle
= OUString(fwd
,len
,RTL_TEXTENCODING_UTF8
);
536 // the buffer now contains the name of the file;
537 // analyze the output:
538 // Format of current working directory:
539 // 257 "/bla/bla" is current directory
540 sal_Int32 index1
= aNetTitle
.lastIndexOf("257");
541 index1
= aNetTitle
.indexOf('"', index1
+ std::strlen("257")) + 1;
542 sal_Int32 index2
= aNetTitle
.indexOf('"', index1
);
543 aNetTitle
= index2
> index1
544 ? aNetTitle
.copy(index1
, index2
- index1
) : OUString();
545 if( aNetTitle
!= "/" ) {
546 index1
= aNetTitle
.lastIndexOf('/');
547 aNetTitle
= aNetTitle
.copy(1+index1
);
550 } else if(err
== CURLE_BAD_PASSWORD_ENTERED
)
551 // the client should retry after getting the correct
552 // username + password
553 throw curl_exception(err
);
554 #if LIBCURL_VERSION_NUM>=0x070d01 /* 7.13.1 */
555 else if(err
== CURLE_LOGIN_DENIED
)
556 // the client should retry after getting the correct
557 // username + password
558 throw curl_exception(err
);
560 else if(try_more
&& err
== CURLE_FTP_ACCESS_DENIED
) {
561 // We were either denied access when trying to login to
562 // an FTP server or when trying to change working directory
563 // to the one given in the URL.
564 if(!m_aPathSegmentVec
.empty())
565 // determine title from URL
566 aNetTitle
= decodePathSegment(m_aPathSegmentVec
.back());
579 curl_slist_free_all(slist
);
584 FTPDirentry
FTPURL::direntry() const
586 OUString nettitle
= net_title();
587 FTPDirentry aDirentry
;
589 aDirentry
.m_aName
= nettitle
; // init aDirentry
590 if( nettitle
== "/" || nettitle
== ".." )
591 aDirentry
.m_nMode
= INETCOREFTP_FILEMODE_ISDIR
;
593 aDirentry
.m_nMode
= INETCOREFTP_FILEMODE_UNKNOWN
;
595 aDirentry
.m_nSize
= 0;
597 if( nettitle
!= "/" ) {
598 // try to open the parent directory
599 FTPURL
aURL(parent(),m_pFCP
);
601 std::vector
<FTPDirentry
> aList
= aURL
.list(OpenMode::ALL
);
603 for(const FTPDirentry
& d
: aList
) {
604 if(d
.m_aName
== nettitle
) { // the relevant file is found
616 static size_t memory_read(void *ptr
,size_t size
,size_t nmemb
,void *stream
)
618 sal_Int32 nRequested
= sal_Int32(size
*nmemb
);
619 CurlInput
*curlInput
= static_cast<CurlInput
*>(stream
);
621 return size_t(curlInput
->read(static_cast<sal_Int8
*>(ptr
),nRequested
));
629 void FTPURL::insert(bool replaceExisting
,void* stream
) const
631 if(!replaceExisting
) {
632 // FTPDirentry aDirentry(direntry());
633 // if(aDirentry.m_nMode == INETCOREFTP_FILEMODE_UNKNOWN)
634 // throw curl_exception(FILE_EXIST_DURING_INSERT);
635 throw curl_exception(FILE_MIGHT_EXIST_DURING_INSERT
);
637 // overwrite is default in libcurl
639 CURL
*curl
= m_pFCP
->handle();
641 SET_CONTROL_CONTAINER
;
642 (void)curl_easy_setopt(curl
,CURLOPT_NOBODY
,false); // no data => no transfer
643 (void)curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,0);
644 (void)curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
645 (void)curl_easy_setopt(curl
,CURLOPT_READFUNCTION
,memory_read
);
646 (void)curl_easy_setopt(curl
,CURLOPT_READDATA
,stream
);
647 (void)curl_easy_setopt(curl
, CURLOPT_UPLOAD
,1);
649 OUString
url(ident(false,true));
650 setCurlUrl(curl
, url
);
652 CURLcode err
= curl_easy_perform(curl
);
653 (void)curl_easy_setopt(curl
, CURLOPT_UPLOAD
,false);
656 throw curl_exception(err
);
660 void FTPURL::mkdir(bool ReplaceExisting
) const
663 if(!m_aPathSegmentVec
.empty()) {
664 OUString titleOU
= m_aPathSegmentVec
.back();
665 titleOU
= decodePathSegment(titleOU
);
666 title
= OString(titleOU
.getStr(),
668 RTL_TEXTENCODING_UTF8
);
671 // will give an error
672 title
= OString("/");
674 OString aDel
= "del " + title
;
675 OString mkd
= "mkd " + title
;
677 struct curl_slist
*slist
= nullptr;
679 FTPDirentry
aDirentry(direntry());
680 if(!ReplaceExisting
) {
681 // if(aDirentry.m_nMode != INETCOREFTP_FILEMODE_UNKNOWN)
682 // throw curl_exception(FOLDER_EXIST_DURING_INSERT);
683 throw curl_exception(FOLDER_MIGHT_EXIST_DURING_INSERT
);
684 } else if(aDirentry
.m_nMode
!= INETCOREFTP_FILEMODE_UNKNOWN
)
685 slist
= curl_slist_append(slist
,aDel
.getStr());
687 slist
= curl_slist_append(slist
,mkd
.getStr());
689 CURL
*curl
= m_pFCP
->handle();
690 SET_CONTROL_CONTAINER
;
691 (void)curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
692 (void)curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
695 (void)curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
697 OUString
url(parent(true));
698 if(!url
.endsWith("/"))
700 setCurlUrl(curl
, url
);
702 CURLcode err
= curl_easy_perform(curl
);
703 curl_slist_free_all(slist
);
705 throw curl_exception(err
);
709 OUString
FTPURL::ren(const OUString
& NewTitle
)
711 CURL
*curl
= m_pFCP
->handle();
714 OUString OldTitle
= net_title();
715 OString renamefrom
= "RNFR " +
716 OUStringToOString(OldTitle
,
717 RTL_TEXTENCODING_UTF8
);
719 OString renameto
= "RNTO " +
720 OUStringToOString(NewTitle
,
721 RTL_TEXTENCODING_UTF8
);
723 struct curl_slist
*slist
= nullptr;
724 slist
= curl_slist_append(slist
,renamefrom
.getStr());
725 slist
= curl_slist_append(slist
,renameto
.getStr());
726 (void)curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
728 SET_CONTROL_CONTAINER
;
729 (void)curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
730 (void)curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
732 OUString
url(parent(true));
733 if(!url
.endsWith("/"))
735 setCurlUrl(curl
, url
);
737 CURLcode err
= curl_easy_perform(curl
);
738 curl_slist_free_all(slist
);
740 throw curl_exception(err
);
741 else if( !m_aPathSegmentVec
.empty() && m_aPathSegmentVec
.back() != ".." )
742 m_aPathSegmentVec
.back() = encodePathSegment(NewTitle
);
747 void FTPURL::del() const
749 FTPDirentry
aDirentry(direntry());
751 OString
dele(aDirentry
.m_aName
.getStr(),
752 aDirentry
.m_aName
.getLength(),
753 RTL_TEXTENCODING_UTF8
);
755 if(aDirentry
.m_nMode
& INETCOREFTP_FILEMODE_ISDIR
) {
756 std::vector
<FTPDirentry
> vec
= list(sal_Int16(OpenMode::ALL
));
757 for(const FTPDirentry
& i
: vec
)
760 FTPURL
url(i
.m_aURL
,m_pFCP
);
762 } catch(const curl_exception
&) {
765 dele
= "RMD " + dele
;
767 else if(aDirentry
.m_nMode
!= INETCOREFTP_FILEMODE_UNKNOWN
)
768 dele
= "DELE " + dele
;
773 CURL
*curl
= m_pFCP
->handle();
774 struct curl_slist
*slist
= nullptr;
775 slist
= curl_slist_append(slist
,dele
.getStr());
776 (void)curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
778 SET_CONTROL_CONTAINER
;
779 (void)curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
780 (void)curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
782 OUString
url(parent(true));
783 if(!url
.endsWith("/"))
785 setCurlUrl(curl
, url
);
787 CURLcode err
= curl_easy_perform(curl
);
788 curl_slist_free_all(slist
);
790 throw curl_exception(err
);
793 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */