1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*************************************************************************
4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
6 * Copyright 2000, 2010 Oracle and/or its affiliates.
8 * OpenOffice.org - a multi-platform office productivity suite
10 * This file is part of OpenOffice.org.
12 * OpenOffice.org is free software: you can redistribute it and/or modify
13 * it under the terms of the GNU Lesser General Public License version 3
14 * only, as published by the Free Software Foundation.
16 * OpenOffice.org is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License version 3 for more details
20 * (a copy is included in the LICENSE file that accompanied this code).
22 * You should have received a copy of the GNU Lesser General Public License
23 * version 3 along with OpenOffice.org. If not, see
24 * <http://www.openoffice.org/license.html>
25 * for a copy of the LGPLv3 License.
27 ************************************************************************/
29 /**************************************************************************
31 **************************************************************************
33 *************************************************************************/
36 #include <rtl/ustrbuf.hxx>
37 #include <com/sun/star/ucb/OpenMode.hpp>
39 #include <rtl/uri.hxx>
41 #include "ftpstrcont.hxx"
43 #include "ftphandleprovider.hxx"
44 #include "ftpcfunc.hxx"
45 #include "ftpcontainer.hxx"
48 using namespace com::sun::star::ucb
;
49 using namespace com::sun::star::uno
;
50 using namespace com::sun::star::io
;
54 rtl::OUString
encodePathSegment(rtl::OUString
const & decoded
) {
55 return rtl::Uri::encode(
56 decoded
, rtl_UriCharClassPchar
, rtl_UriEncodeIgnoreEscapes
,
57 RTL_TEXTENCODING_UTF8
);
60 rtl::OUString
decodePathSegment(rtl::OUString
const & encoded
) {
61 return rtl::Uri::decode(
62 encoded
, rtl_UriDecodeWithCharset
, RTL_TEXTENCODING_UTF8
);
67 MemoryContainer::MemoryContainer()
74 MemoryContainer::~MemoryContainer()
76 rtl_freeMemory(m_pBuffer
);
80 int MemoryContainer::append(
86 sal_uInt32 nLen
= size
*nmemb
;
87 sal_uInt32
tmp(nLen
+ m_nWritePos
);
89 if(m_nLen
< tmp
) { // enlarge in steps of multiples of 1K
92 } while(m_nLen
< tmp
);
94 m_pBuffer
= rtl_reallocateMemory(m_pBuffer
,m_nLen
);
97 rtl_copyMemory(static_cast<sal_Int8
*>(m_pBuffer
)+m_nWritePos
,
106 int memory_write(void *buffer
,size_t size
,size_t nmemb
,void *stream
)
108 MemoryContainer
*_stream
=
109 reinterpret_cast<MemoryContainer
*>(stream
);
114 return _stream
->append(buffer
,size
,nmemb
);
120 FTPURL::FTPURL(const FTPURL
& r
)
123 m_aUsername(r
.m_aUsername
),
124 m_bShowPassword(r
.m_bShowPassword
),
127 m_aPathSegmentVec(r
.m_aPathSegmentVec
)
133 FTPURL::FTPURL(const rtl::OUString
& url
,
134 FTPHandleProvider
* pFCP
)
139 m_aUsername("anonymous"),
140 m_bShowPassword(false),
143 parse(url
); // can reset m_bShowPassword
152 void FTPURL::parse(const rtl::OUString
& url
)
157 rtl::OUString aPassword
,aAccount
;
158 rtl::OString
aIdent(url
.getStr(),
160 RTL_TEXTENCODING_UTF8
);
162 rtl::OString lower
= aIdent
.toAsciiLowerCase();
163 if(lower
.getLength() < 6 ||
164 strncmp("ftp://",lower
.getStr(),6))
165 throw malformed_exception();
167 char *buffer
= new char[1+aIdent
.getLength()];
168 const char* p2
= aIdent
.getStr();
172 char *p1
= buffer
; // determine "username:password@host:port"
173 while((ch
= *p2
++) != '/' && ch
)
177 rtl::OUString
aExpr(rtl::OUString(buffer
,strlen(buffer
),
178 RTL_TEXTENCODING_UTF8
));
180 sal_Int32 l
= aExpr
.indexOf(sal_Unicode('@'));
181 m_aHost
= aExpr
.copy(1+l
);
184 // Now username and password.
185 aExpr
= aExpr
.copy(0,l
);
186 l
= aExpr
.indexOf(sal_Unicode(':'));
188 aPassword
= aExpr
.copy(1+l
);
189 if(!aPassword
.isEmpty())
190 m_bShowPassword
= true;
193 // Overwritte only if the username is not empty.
194 m_aUsername
= aExpr
.copy(0,l
);
195 else if(!aExpr
.isEmpty())
199 l
= m_aHost
.lastIndexOf(sal_Unicode(':'));
200 sal_Int32 ipv6Back
= m_aHost
.lastIndexOf(sal_Unicode(']'));
201 if((ipv6Back
== -1 && l
!= -1) // not ipv6, but a port
203 (ipv6Back
!= -1 && 1+ipv6Back
== l
) // ipv6, and a port
206 if(1+l
<m_aHost
.getLength())
207 m_aPort
= m_aHost
.copy(1+l
);
208 m_aHost
= m_aHost
.copy(0,l
);
211 while(ch
) { // now determine the pathsegments ...
213 while((ch
= *p2
++) != '/' && ch
)
218 if( strcmp(buffer
,"..") == 0 && m_aPathSegmentVec
.size() && m_aPathSegmentVec
.back() != ".." )
219 m_aPathSegmentVec
.pop_back();
220 else if(strcmp(buffer
,".") == 0)
223 // This is a legal name.
224 m_aPathSegmentVec
.push_back(
225 rtl::OUString(buffer
,
227 RTL_TEXTENCODING_UTF8
));
234 m_pFCP
->setHost(m_aHost
,
240 // now check for something like ";type=i" at end of url
241 if(m_aPathSegmentVec
.size() &&
242 (l
= m_aPathSegmentVec
.back().indexOf(sal_Unicode(';'))) != -1) {
243 m_aType
= m_aPathSegmentVec
.back().copy(l
);
244 m_aPathSegmentVec
.back() = m_aPathSegmentVec
.back().copy(0,l
);
249 rtl::OUString
FTPURL::ident(bool withslash
,bool internal
) const
251 // rebuild the url as one without ellipses,
252 // and more important, as one without username and
253 // password. ( These are set together with the command. )
255 rtl::OUStringBuffer bff
;
256 bff
.appendAscii("ftp://");
258 if( m_aUsername
!= "anonymous" ) {
259 bff
.append(m_aUsername
);
261 rtl::OUString aPassword
,aAccount
;
262 m_pFCP
->forHost(m_aHost
,
268 if((m_bShowPassword
|| internal
) &&
269 !aPassword
.isEmpty() )
270 bff
.append(sal_Unicode(':'))
273 bff
.append(sal_Unicode('@'));
277 if( m_aPort
!= "21" )
278 bff
.append(sal_Unicode(':'))
280 .append(sal_Unicode('/'));
282 bff
.append(sal_Unicode('/'));
284 for(unsigned i
= 0; i
< m_aPathSegmentVec
.size(); ++i
)
286 bff
.append(m_aPathSegmentVec
[i
]);
288 bff
.append(sal_Unicode('/')).append(m_aPathSegmentVec
[i
]);
290 if(bff
.getLength() && bff
[bff
.getLength()-1] != sal_Unicode('/'))
291 bff
.append(sal_Unicode('/'));
294 return bff
.makeStringAndClear();
298 rtl::OUString
FTPURL::parent(bool internal
) const
300 rtl::OUStringBuffer bff
;
302 bff
.appendAscii("ftp://");
304 if( m_aUsername
!= "anonymous" ) {
305 bff
.append(m_aUsername
);
307 rtl::OUString aPassword
,aAccount
;
308 m_pFCP
->forHost(m_aHost
,
314 if((internal
|| m_bShowPassword
) && !aPassword
.isEmpty())
315 bff
.append(sal_Unicode(':'))
318 bff
.append(sal_Unicode('@'));
323 if( m_aPort
!= "21" )
324 bff
.append(sal_Unicode(':'))
326 .append(sal_Unicode('/'));
328 bff
.append(sal_Unicode('/'));
332 for(unsigned int i
= 0; i
< m_aPathSegmentVec
.size(); ++i
)
333 if(1+i
== m_aPathSegmentVec
.size())
334 last
= m_aPathSegmentVec
[i
];
336 bff
.append(m_aPathSegmentVec
[i
]);
338 bff
.append(sal_Unicode('/')).append(m_aPathSegmentVec
[i
]);
341 bff
.appendAscii("..");
342 else if ( last
== ".." )
343 bff
.append(last
).appendAscii("/..");
346 return bff
.makeStringAndClear();
350 void FTPURL::child(const rtl::OUString
& title
)
352 m_aPathSegmentVec
.push_back(encodePathSegment(title
));
356 rtl::OUString
FTPURL::child() const
359 m_aPathSegmentVec
.size() ?
360 decodePathSegment(m_aPathSegmentVec
.back()) : rtl::OUString();
365 /** Listing of a directory.
371 FTP_DOS
,FTP_UNIX
,FTP_VMS
,FTP_UNKNOWN
377 #define SET_CONTROL_CONTAINER \
378 MemoryContainer control; \
379 curl_easy_setopt(curl, \
380 CURLOPT_HEADERFUNCTION, \
382 curl_easy_setopt(curl, \
383 CURLOPT_WRITEHEADER, \
387 #define SET_DATA_CONTAINER \
388 curl_easy_setopt(curl,CURLOPT_NOBODY,false); \
389 MemoryContainer data; \
390 curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,memory_write); \
391 curl_easy_setopt(curl,CURLOPT_WRITEDATA,&data)
393 #define SET_URL(url) \
394 rtl::OString urlParAscii(url.getStr(), \
396 RTL_TEXTENCODING_UTF8); \
397 curl_easy_setopt(curl, \
399 urlParAscii.getStr());
401 // Setting username:password
402 #define SET_USER_PASSWORD(username,password) \
403 rtl::OUString combi(username + \
404 rtl::OUString(":") + \
406 rtl::OString aUserPsswd(combi.getStr(), \
408 RTL_TEXTENCODING_UTF8); \
409 curl_easy_setopt(curl, \
416 throw(curl_exception
)
418 if(m_aPathSegmentVec
.empty())
419 throw curl_exception(CURLE_FTP_COULDNT_RETR_FILE
);
421 CURL
*curl
= m_pFCP
->handle();
423 SET_CONTROL_CONTAINER
;
424 rtl::OUString
url(ident(false,true));
426 FILE *res
= tmpfile();
427 curl_easy_setopt(curl
,CURLOPT_WRITEFUNCTION
,file_write
);
428 curl_easy_setopt(curl
,CURLOPT_WRITEDATA
,res
);
430 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,0);
431 CURLcode err
= curl_easy_perform(curl
);
437 throw curl_exception(err
);
444 std::vector
<FTPDirentry
> FTPURL::list(
451 CURL
*curl
= m_pFCP
->handle();
453 SET_CONTROL_CONTAINER
;
455 rtl::OUString
url(ident(true,true));
457 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,0);
459 CURLcode err
= curl_easy_perform(curl
);
461 throw curl_exception(err
);
463 // now evaluate the error messages
465 sal_uInt32 len
= data
.m_nWritePos
;
466 char* fwd
= (char*) data
.m_pBuffer
;
467 rtl::OString
str(fwd
,len
);
471 OS
osKind(FTP_UNKNOWN
);
472 std::vector
<FTPDirentry
> resvec
;
473 FTPDirentry aDirEntry
;
474 // ensure slash at the end
475 rtl::OUString
viewurl(ident(true,false));
478 while(p2
-fwd
< int(len
) && *p2
!= '\n') ++p2
;
479 if(p2
-fwd
== int(len
)) break;
483 // While FTP knows the 'system'-command,
484 // which returns the operating system type,
485 // this is not usable here: There are Windows-server
486 // formatting the output like UNIX-ls command.
488 FTPDirectoryParser::parseDOS(aDirEntry
,p1
);
491 FTPDirectoryParser::parseUNIX(aDirEntry
,p1
);
494 FTPDirectoryParser::parseVMS(aDirEntry
,p1
);
497 if(FTPDirectoryParser::parseUNIX(aDirEntry
,p1
))
499 else if(FTPDirectoryParser::parseDOS(aDirEntry
,p1
))
501 else if(FTPDirectoryParser::parseVMS(aDirEntry
,p1
))
504 aDirEntry
.m_aName
= aDirEntry
.m_aName
.trim();
505 if( osKind
!= int(FTP_UNKNOWN
) && aDirEntry
.m_aName
!= ".." && aDirEntry
.m_aName
!= "." ) {
506 aDirEntry
.m_aURL
= viewurl
+ encodePathSegment(aDirEntry
.m_aName
);
509 sal_Bool(aDirEntry
.m_nMode
&INETCOREFTP_FILEMODE_ISDIR
);
511 case OpenMode::DOCUMENTS
:
513 resvec
.push_back(aDirEntry
);
515 case OpenMode::FOLDERS
:
517 resvec
.push_back(aDirEntry
);
520 resvec
.push_back(aDirEntry
);
531 rtl::OUString
FTPURL::net_title() const
532 throw(curl_exception
)
534 CURL
*curl
= m_pFCP
->handle();
536 SET_CONTROL_CONTAINER
;
537 curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
538 struct curl_slist
*slist
= 0;
540 slist
= curl_slist_append(slist
,"PWD");
541 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
545 rtl::OUString aNetTitle
;
548 rtl::OUString
url(ident(false,true));
551 1+url
.lastIndexOf(sal_Unicode('/')) != url
.getLength())
552 url
+= rtl::OUString("/"); // add end-slash
554 1+url
.lastIndexOf(sal_Unicode('/')) == url
.getLength())
555 url
= url
.copy(0,url
.getLength()-1); // remove end-slash
558 err
= curl_easy_perform(curl
);
560 if(err
== CURLE_OK
) { // get the title from the server
561 char* fwd
= (char*) control
.m_pBuffer
;
562 sal_uInt32 len
= (sal_uInt32
) control
.m_nWritePos
;
564 aNetTitle
= rtl::OUString(fwd
,len
,RTL_TEXTENCODING_UTF8
);
565 // the buffer now contains the name of the file;
566 // analyze the output:
567 // Format of current working directory:
568 // 257 "/bla/bla" is current directory
569 sal_Int32 index1
= aNetTitle
.lastIndexOf(
570 rtl::OUString("257"));
571 index1
= 1+aNetTitle
.indexOf(sal_Unicode('"'),index1
);
572 sal_Int32 index2
= aNetTitle
.indexOf(sal_Unicode('"'),index1
);
573 aNetTitle
= aNetTitle
.copy(index1
,index2
-index1
);
574 if( aNetTitle
!= "/" ) {
575 index1
= aNetTitle
.lastIndexOf(sal_Unicode('/'));
576 aNetTitle
= aNetTitle
.copy(1+index1
);
579 } else if(err
== CURLE_BAD_PASSWORD_ENTERED
)
580 // the client should retry after getting the correct
581 // username + password
582 throw curl_exception(err
);
583 #if LIBCURL_VERSION_NUM>=0x070d01 /* 7.13.1 */
584 else if(err
== CURLE_LOGIN_DENIED
)
585 // the client should retry after getting the correct
586 // username + password
587 throw curl_exception(err
);
589 else if(try_more
&& err
== CURLE_FTP_ACCESS_DENIED
) {
590 // We were either denied access when trying to login to
591 // an FTP server or when trying to change working directory
592 // to the one given in the URL.
593 if(!m_aPathSegmentVec
.empty())
594 // determine title form url
595 aNetTitle
= decodePathSegment(m_aPathSegmentVec
.back());
598 aNetTitle
= rtl::OUString("/");
608 curl_slist_free_all(slist
);
613 FTPDirentry
FTPURL::direntry() const
614 throw(curl_exception
)
616 rtl::OUString nettitle
= net_title();
617 FTPDirentry aDirentry
;
619 aDirentry
.m_aName
= nettitle
; // init aDirentry
620 if( nettitle
== "/" || nettitle
== ".." )
621 aDirentry
.m_nMode
= INETCOREFTP_FILEMODE_ISDIR
;
623 aDirentry
.m_nMode
= INETCOREFTP_FILEMODE_UNKNOWN
;
625 aDirentry
.m_nSize
= 0;
627 if( nettitle
!= "/" ) {
628 // try to open the parent directory
629 FTPURL
aURL(parent(),m_pFCP
);
631 std::vector
<FTPDirentry
> aList
= aURL
.list(OpenMode::ALL
);
633 for(unsigned i
= 0; i
< aList
.size(); ++i
) {
634 if(aList
[i
].m_aName
== nettitle
) { // the relevant file is found
635 aDirentry
= aList
[i
];
646 size_t memory_read(void *ptr
,size_t size
,size_t nmemb
,void *stream
)
648 sal_Int32 nRequested
= sal_Int32(size
*nmemb
);
649 CurlInput
*curlInput
= static_cast<CurlInput
*>(stream
);
651 return size_t(curlInput
->read(((sal_Int8
*)ptr
),nRequested
));
659 void FTPURL::insert(bool replaceExisting
,void* stream
) const
660 throw(curl_exception
)
662 if(!replaceExisting
) {
663 // FTPDirentry aDirentry(direntry());
664 // if(aDirentry.m_nMode == INETCOREFTP_FILEMODE_UNKNOWN)
665 // throw curl_exception(FILE_EXIST_DURING_INSERT);
666 throw curl_exception(FILE_MIGHT_EXIST_DURING_INSERT
);
668 // overwrite is default in libcurl
670 CURL
*curl
= m_pFCP
->handle();
672 SET_CONTROL_CONTAINER
;
673 curl_easy_setopt(curl
,CURLOPT_NOBODY
,false); // no data => no transfer
674 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,0);
675 curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
676 curl_easy_setopt(curl
,CURLOPT_READFUNCTION
,memory_read
);
677 curl_easy_setopt(curl
,CURLOPT_READDATA
,stream
);
678 curl_easy_setopt(curl
, CURLOPT_UPLOAD
,1);
680 rtl::OUString
url(ident(false,true));
683 CURLcode err
= curl_easy_perform(curl
);
684 curl_easy_setopt(curl
, CURLOPT_UPLOAD
,false);
687 throw curl_exception(err
);
692 void FTPURL::mkdir(bool ReplaceExisting
) const
693 throw(curl_exception
)
696 if(!m_aPathSegmentVec
.empty()) {
697 rtl::OUString titleOU
= m_aPathSegmentVec
.back();
698 titleOU
= decodePathSegment(titleOU
);
699 title
= rtl::OString(titleOU
.getStr(),
701 RTL_TEXTENCODING_UTF8
);
704 // will give an error
705 title
= rtl::OString("/");
707 rtl::OString
aDel("del "); aDel
+= title
;
708 rtl::OString
mkd("mkd "); mkd
+= title
;
710 struct curl_slist
*slist
= 0;
712 FTPDirentry
aDirentry(direntry());
713 if(!ReplaceExisting
) {
714 // if(aDirentry.m_nMode != INETCOREFTP_FILEMODE_UNKNOWN)
715 // throw curl_exception(FOLDER_EXIST_DURING_INSERT);
716 throw curl_exception(FOLDER_MIGHT_EXIST_DURING_INSERT
);
717 } else if(aDirentry
.m_nMode
!= INETCOREFTP_FILEMODE_UNKNOWN
)
718 slist
= curl_slist_append(slist
,aDel
.getStr());
720 slist
= curl_slist_append(slist
,mkd
.getStr());
722 CURL
*curl
= m_pFCP
->handle();
723 SET_CONTROL_CONTAINER
;
724 curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
725 curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
728 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
730 rtl::OUString
url(parent(true));
731 if(1+url
.lastIndexOf(sal_Unicode('/')) != url
.getLength())
732 url
+= rtl::OUString("/");
735 CURLcode err
= curl_easy_perform(curl
);
736 curl_slist_free_all(slist
);
738 throw curl_exception(err
);
742 rtl::OUString
FTPURL::ren(const rtl::OUString
& NewTitle
)
743 throw(curl_exception
)
745 CURL
*curl
= m_pFCP
->handle();
748 rtl::OString
renamefrom("RNFR ");
749 rtl::OUString OldTitle
= net_title();
751 rtl::OString(OldTitle
.getStr(),
752 OldTitle
.getLength(),
753 RTL_TEXTENCODING_UTF8
);
755 rtl::OString
renameto("RNTO ");
757 rtl::OString(NewTitle
.getStr(),
758 NewTitle
.getLength(),
759 RTL_TEXTENCODING_UTF8
);
761 struct curl_slist
*slist
= 0;
762 slist
= curl_slist_append(slist
,renamefrom
.getStr());
763 slist
= curl_slist_append(slist
,renameto
.getStr());
764 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
766 SET_CONTROL_CONTAINER
;
767 curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
768 curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
770 rtl::OUString
url(parent(true));
771 if(1+url
.lastIndexOf(sal_Unicode('/')) != url
.getLength())
772 url
+= rtl::OUString("/");
775 CURLcode err
= curl_easy_perform(curl
);
776 curl_slist_free_all(slist
);
778 throw curl_exception(err
);
779 else if( m_aPathSegmentVec
.size() && m_aPathSegmentVec
.back() != ".." )
780 m_aPathSegmentVec
.back() = encodePathSegment(NewTitle
);
786 void FTPURL::del() const
787 throw(curl_exception
)
789 FTPDirentry
aDirentry(direntry());
791 rtl::OString
dele(aDirentry
.m_aName
.getStr(),
792 aDirentry
.m_aName
.getLength(),
793 RTL_TEXTENCODING_UTF8
);
795 if(aDirentry
.m_nMode
& INETCOREFTP_FILEMODE_ISDIR
) {
796 std::vector
<FTPDirentry
> vec
= list(sal_Int16(OpenMode::ALL
));
797 for( unsigned int i
= 0; i
< vec
.size(); ++i
)
799 FTPURL
url(vec
[i
].m_aURL
,m_pFCP
);
801 } catch(const curl_exception
&) {
803 dele
= rtl::OString("RMD ") + dele
;
805 else if(aDirentry
.m_nMode
!= INETCOREFTP_FILEMODE_UNKNOWN
)
806 dele
= rtl::OString("DELE ") + dele
;
811 CURL
*curl
= m_pFCP
->handle();
812 struct curl_slist
*slist
= 0;
813 slist
= curl_slist_append(slist
,dele
.getStr());
814 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
816 SET_CONTROL_CONTAINER
;
817 curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
818 curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
820 rtl::OUString
url(parent(true));
821 if(1+url
.lastIndexOf(sal_Unicode('/')) != url
.getLength())
822 url
+= rtl::OUString("/");
825 CURLcode err
= curl_easy_perform(curl
);
826 curl_slist_free_all(slist
);
828 throw curl_exception(err
);
831 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */