1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: ftpurl.cxx,v $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 // MARKER(update_precomp.py): autogen include statement, do not remove
32 #include "precompiled_ucb.hxx"
33 /**************************************************************************
35 **************************************************************************
37 *************************************************************************/
40 #include <rtl/ustrbuf.hxx>
41 #include <com/sun/star/ucb/OpenMode.hpp>
43 #include <rtl/uri.hxx>
45 #include "ftpstrcont.hxx"
47 #include "ftphandleprovider.hxx"
48 #include "ftpinpstr.hxx"
49 #include "ftpcfunc.hxx"
50 #include "ftpcontainer.hxx"
53 using namespace com::sun::star::ucb
;
54 using namespace com::sun::star::uno
;
55 using namespace com::sun::star::io
;
59 rtl::OUString
encodePathSegment(rtl::OUString
const & decoded
) {
60 return rtl::Uri::encode(
61 decoded
, rtl_UriCharClassPchar
, rtl_UriEncodeIgnoreEscapes
,
62 RTL_TEXTENCODING_UTF8
);
65 rtl::OUString
decodePathSegment(rtl::OUString
const & encoded
) {
66 return rtl::Uri::decode(
67 encoded
, rtl_UriDecodeWithCharset
, RTL_TEXTENCODING_UTF8
);
72 MemoryContainer::MemoryContainer()
79 MemoryContainer::~MemoryContainer()
81 rtl_freeMemory(m_pBuffer
);
85 int MemoryContainer::append(
91 sal_uInt32 nLen
= size
*nmemb
;
92 sal_uInt32
tmp(nLen
+ m_nWritePos
);
94 if(m_nLen
< tmp
) { // enlarge in steps of multiples of 1K
97 } while(m_nLen
< tmp
);
99 m_pBuffer
= rtl_reallocateMemory(m_pBuffer
,m_nLen
);
102 rtl_copyMemory(static_cast<sal_Int8
*>(m_pBuffer
)+m_nWritePos
,
111 int memory_write(void *buffer
,size_t size
,size_t nmemb
,void *stream
)
113 MemoryContainer
*_stream
=
114 reinterpret_cast<MemoryContainer
*>(stream
);
119 return _stream
->append(buffer
,size
,nmemb
);
125 FTPURL::FTPURL(const FTPURL
& r
)
128 m_aUsername(r
.m_aUsername
),
129 m_bShowPassword(r
.m_bShowPassword
),
132 m_aPathSegmentVec(r
.m_aPathSegmentVec
)
138 FTPURL::FTPURL(const rtl::OUString
& url
,
139 FTPHandleProvider
* pFCP
)
144 m_aUsername(rtl::OUString::createFromAscii("anonymous")),
145 m_bShowPassword(false),
146 m_aPort(rtl::OUString::createFromAscii("21"))
148 parse(url
); // can reset m_bShowPassword
157 void FTPURL::parse(const rtl::OUString
& url
)
162 rtl::OUString aPassword
,aAccount
;
163 rtl::OString
aIdent(url
.getStr(),
165 RTL_TEXTENCODING_UTF8
);
166 char *buffer
= new char[1+aIdent
.getLength()];
168 const char* p2
= aIdent
.getStr();
170 rtl::OString lower
= aIdent
.toAsciiLowerCase();
171 if(lower
.getLength() < 6 ||
172 strncmp("ftp://",lower
.getStr(),6))
173 throw malformed_exception();
178 char *p1
= buffer
; // determine "username:password@host:port"
179 while((ch
= *p2
++) != '/' && ch
)
183 rtl::OUString
aExpr(rtl::OUString(buffer
,strlen(buffer
),
184 RTL_TEXTENCODING_UTF8
));
186 sal_Int32 l
= aExpr
.indexOf(sal_Unicode('@'));
187 m_aHost
= aExpr
.copy(1+l
);
190 // Now username and password.
191 aExpr
= aExpr
.copy(0,l
);
192 l
= aExpr
.indexOf(sal_Unicode(':'));
194 aPassword
= aExpr
.copy(1+l
);
195 if(aPassword
.getLength())
196 m_bShowPassword
= true;
199 // Overwritte only if the username is not empty.
200 m_aUsername
= aExpr
.copy(0,l
);
201 else if(aExpr
.getLength())
205 l
= m_aHost
.lastIndexOf(sal_Unicode(':'));
206 sal_Int32 ipv6Back
= m_aHost
.lastIndexOf(sal_Unicode(']'));
207 if((ipv6Back
== -1 && l
!= -1) // not ipv6, but a port
209 (ipv6Back
!= -1 && 1+ipv6Back
== l
) // ipv6, and a port
212 if(1+l
<m_aHost
.getLength())
213 m_aPort
= m_aHost
.copy(1+l
);
214 m_aHost
= m_aHost
.copy(0,l
);
217 while(ch
) { // now determine the pathsegments ...
219 while((ch
= *p2
++) != '/' && ch
)
224 if(strcmp(buffer
,"..") == 0 &&
225 m_aPathSegmentVec
.size() &&
226 ! m_aPathSegmentVec
.back().equalsAscii(".."))
227 m_aPathSegmentVec
.pop_back();
228 else if(strcmp(buffer
,".") == 0)
231 // This is a legal name.
232 m_aPathSegmentVec
.push_back(
233 rtl::OUString(buffer
,
235 RTL_TEXTENCODING_UTF8
));
242 m_pFCP
->setHost(m_aHost
,
248 // now check for something like ";type=i" at end of url
249 if(m_aPathSegmentVec
.size() &&
250 (l
= m_aPathSegmentVec
.back().indexOf(sal_Unicode(';'))) != -1) {
251 m_aType
= m_aPathSegmentVec
.back().copy(l
);
252 m_aPathSegmentVec
.back() = m_aPathSegmentVec
.back().copy(0,l
);
257 rtl::OUString
FTPURL::ident(bool withslash
,bool internal
) const
259 // rebuild the url as one without ellipses,
260 // and more important, as one without username and
261 // password. ( These are set together with the command. )
263 rtl::OUStringBuffer bff
;
264 bff
.appendAscii("ftp://");
266 if(!m_aUsername
.equalsAscii("anonymous")) {
267 bff
.append(m_aUsername
);
269 rtl::OUString aPassword
,aAccount
;
270 m_pFCP
->forHost(m_aHost
,
276 if((m_bShowPassword
|| internal
) &&
277 aPassword
.getLength() )
278 bff
.append(sal_Unicode(':'))
281 bff
.append(sal_Unicode('@'));
285 if(!m_aPort
.equalsAscii("21"))
286 bff
.append(sal_Unicode(':'))
288 .append(sal_Unicode('/'));
290 bff
.append(sal_Unicode('/'));
292 for(unsigned i
= 0; i
< m_aPathSegmentVec
.size(); ++i
)
294 bff
.append(m_aPathSegmentVec
[i
]);
296 bff
.append(sal_Unicode('/')).append(m_aPathSegmentVec
[i
]);
298 if(bff
.getLength() && bff
[bff
.getLength()-1] != sal_Unicode('/'))
299 bff
.append(sal_Unicode('/'));
302 return bff
.makeStringAndClear();
306 rtl::OUString
FTPURL::parent(bool internal
) const
308 rtl::OUStringBuffer bff
;
310 bff
.appendAscii("ftp://");
312 if(!m_aUsername
.equalsAscii("anonymous")) {
313 bff
.append(m_aUsername
);
315 rtl::OUString aPassword
,aAccount
;
316 m_pFCP
->forHost(m_aHost
,
322 if((internal
|| m_bShowPassword
) && aPassword
.getLength())
323 bff
.append(sal_Unicode(':'))
326 bff
.append(sal_Unicode('@'));
331 if(!m_aPort
.equalsAscii("21"))
332 bff
.append(sal_Unicode(':'))
334 .append(sal_Unicode('/'));
336 bff
.append(sal_Unicode('/'));
340 for(unsigned int i
= 0; i
< m_aPathSegmentVec
.size(); ++i
)
341 if(1+i
== m_aPathSegmentVec
.size())
342 last
= m_aPathSegmentVec
[i
];
344 bff
.append(m_aPathSegmentVec
[i
]);
346 bff
.append(sal_Unicode('/')).append(m_aPathSegmentVec
[i
]);
348 if(!last
.getLength())
349 bff
.appendAscii("..");
350 else if(last
.equalsAscii(".."))
351 bff
.append(last
).appendAscii("/..");
354 return bff
.makeStringAndClear();
358 void FTPURL::child(const rtl::OUString
& title
)
360 m_aPathSegmentVec
.push_back(encodePathSegment(title
));
364 rtl::OUString
FTPURL::child() const
367 m_aPathSegmentVec
.size() ?
368 decodePathSegment(m_aPathSegmentVec
.back()) : rtl::OUString();
373 /** Listing of a directory.
379 FTP_DOS
,FTP_UNIX
,FTP_VMS
,FTP_UNKNOWN
385 #define SET_CONTROL_CONTAINER \
386 MemoryContainer control; \
387 curl_easy_setopt(curl, \
388 CURLOPT_HEADERFUNCTION, \
390 curl_easy_setopt(curl, \
391 CURLOPT_WRITEHEADER, \
395 #define SET_DATA_CONTAINER \
396 curl_easy_setopt(curl,CURLOPT_NOBODY,false); \
397 MemoryContainer data; \
398 curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,memory_write); \
399 curl_easy_setopt(curl,CURLOPT_WRITEDATA,&data)
401 #define SET_URL(url) \
402 rtl::OString urlParAscii(url.getStr(), \
404 RTL_TEXTENCODING_UTF8); \
405 curl_easy_setopt(curl, \
407 urlParAscii.getStr());
409 // Setting username:password
410 #define SET_USER_PASSWORD(username,password) \
411 rtl::OUString combi(username + \
412 rtl::OUString::createFromAscii(":") + \
414 rtl::OString aUserPsswd(combi.getStr(), \
416 RTL_TEXTENCODING_UTF8); \
417 curl_easy_setopt(curl, \
424 throw(curl_exception
)
426 if(!m_aPathSegmentVec
.size())
427 throw curl_exception(CURLE_FTP_COULDNT_RETR_FILE
);
429 CURL
*curl
= m_pFCP
->handle();
431 SET_CONTROL_CONTAINER
;
432 rtl::OUString
url(ident(false,true));
434 FILE *res
= tmpfile();
435 curl_easy_setopt(curl
,CURLOPT_WRITEFUNCTION
,file_write
);
436 curl_easy_setopt(curl
,CURLOPT_WRITEDATA
,res
);
438 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,0);
439 CURLcode err
= curl_easy_perform(curl
);
445 throw curl_exception(err
);
452 std::vector
<FTPDirentry
> FTPURL::list(
459 CURL
*curl
= m_pFCP
->handle();
461 SET_CONTROL_CONTAINER
;
463 rtl::OUString
url(ident(true,true));
465 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,0);
467 CURLcode err
= curl_easy_perform(curl
);
469 throw curl_exception(err
);
471 // now evaluate the error messages
473 sal_uInt32 len
= data
.m_nWritePos
;
474 char* fwd
= (char*) data
.m_pBuffer
;
475 rtl::OString
str(fwd
,len
);
479 OS
osKind(FTP_UNKNOWN
);
480 std::vector
<FTPDirentry
> resvec
;
481 FTPDirentry aDirEntry
;
482 // ensure slash at the end
483 rtl::OUString
viewurl(ident(true,false));
486 while(p2
-fwd
< int(len
) && *p2
!= '\n') ++p2
;
487 if(p2
-fwd
== int(len
)) break;
491 // While FTP knows the 'system'-command,
492 // which returns the operating system type,
493 // this is not usable here: There are Windows-server
494 // formatting the output like UNIX-ls command.
496 FTPDirectoryParser::parseDOS(aDirEntry
,p1
);
499 FTPDirectoryParser::parseUNIX(aDirEntry
,p1
);
502 FTPDirectoryParser::parseVMS(aDirEntry
,p1
);
505 if(FTPDirectoryParser::parseUNIX(aDirEntry
,p1
))
507 else if(FTPDirectoryParser::parseDOS(aDirEntry
,p1
))
509 else if(FTPDirectoryParser::parseVMS(aDirEntry
,p1
))
512 aDirEntry
.m_aName
= aDirEntry
.m_aName
.trim();
513 if(osKind
!= int(FTP_UNKNOWN
) &&
514 !aDirEntry
.m_aName
.equalsAscii("..") &&
515 !aDirEntry
.m_aName
.equalsAscii(".")) {
516 aDirEntry
.m_aURL
= viewurl
+ encodePathSegment(aDirEntry
.m_aName
);
519 sal_Bool(aDirEntry
.m_nMode
&INETCOREFTP_FILEMODE_ISDIR
);
521 case OpenMode::DOCUMENTS
:
523 resvec
.push_back(aDirEntry
);
525 case OpenMode::FOLDERS
:
527 resvec
.push_back(aDirEntry
);
530 resvec
.push_back(aDirEntry
);
541 rtl::OUString
FTPURL::net_title() const
542 throw(curl_exception
)
544 CURL
*curl
= m_pFCP
->handle();
546 SET_CONTROL_CONTAINER
;
547 curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
548 struct curl_slist
*slist
= 0;
550 slist
= curl_slist_append(slist
,"PWD");
551 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
555 rtl::OUString aNetTitle
;
558 rtl::OUString
url(ident(false,true));
561 1+url
.lastIndexOf(sal_Unicode('/')) != url
.getLength())
562 url
+= rtl::OUString::createFromAscii("/"); // add end-slash
564 1+url
.lastIndexOf(sal_Unicode('/')) == url
.getLength())
565 url
= url
.copy(0,url
.getLength()-1); // remove end-slash
568 err
= curl_easy_perform(curl
);
570 if(err
== CURLE_OK
) { // get the title from the server
571 char* fwd
= (char*) control
.m_pBuffer
;
572 sal_uInt32 len
= (sal_uInt32
) control
.m_nWritePos
;
574 aNetTitle
= rtl::OUString(fwd
,len
,RTL_TEXTENCODING_UTF8
);
575 // the buffer now contains the name of the file;
576 // analyze the output:
577 // Format of current working directory:
578 // 257 "/bla/bla" is current directory
579 sal_Int32 index1
= aNetTitle
.lastIndexOf(
580 rtl::OUString::createFromAscii("257"));
581 index1
= 1+aNetTitle
.indexOf(sal_Unicode('"'),index1
);
582 sal_Int32 index2
= aNetTitle
.indexOf(sal_Unicode('"'),index1
);
583 aNetTitle
= aNetTitle
.copy(index1
,index2
-index1
);
584 if(!aNetTitle
.equalsAscii("/")) {
585 index1
= aNetTitle
.lastIndexOf(sal_Unicode('/'));
586 aNetTitle
= aNetTitle
.copy(1+index1
);
589 } else if(err
== CURLE_BAD_PASSWORD_ENTERED
)
590 // the client should retry after getting the correct
591 // username + password
592 throw curl_exception(err
);
593 else if(try_more
&& err
== CURLE_FTP_ACCESS_DENIED
) {
594 // We were either denied access when trying to login to
595 // an FTP server or when trying to change working directory
596 // to the one given in the URL.
597 if(m_aPathSegmentVec
.size())
598 // determine title form url
599 aNetTitle
= decodePathSegment(m_aPathSegmentVec
.back());
602 aNetTitle
= rtl::OUString::createFromAscii("/");
612 curl_slist_free_all(slist
);
617 FTPDirentry
FTPURL::direntry() const
618 throw(curl_exception
)
620 rtl::OUString nettitle
= net_title();
621 FTPDirentry aDirentry
;
623 aDirentry
.m_aName
= nettitle
; // init aDirentry
624 if(nettitle
.equalsAscii("/") ||
625 nettitle
.equalsAscii(".."))
626 aDirentry
.m_nMode
= INETCOREFTP_FILEMODE_ISDIR
;
628 aDirentry
.m_nMode
= INETCOREFTP_FILEMODE_UNKNOWN
;
630 aDirentry
.m_nSize
= 0;
632 if(!nettitle
.equalsAscii("/")) {
633 // try to open the parent directory
634 FTPURL
aURL(parent(),m_pFCP
);
636 std::vector
<FTPDirentry
> aList
= aURL
.list(OpenMode::ALL
);
638 for(unsigned i
= 0; i
< aList
.size(); ++i
) {
639 if(aList
[i
].m_aName
== nettitle
) { // the relevant file is found
640 aDirentry
= aList
[i
];
651 size_t memory_read(void *ptr
,size_t size
,size_t nmemb
,void *stream
)
653 sal_Int32 nRequested
= sal_Int32(size
*nmemb
);
654 CurlInput
*curlInput
= static_cast<CurlInput
*>(stream
);
656 return size_t(curlInput
->read(((sal_Int8
*)ptr
),nRequested
));
664 void FTPURL::insert(bool replaceExisting
,void* stream
) const
665 throw(curl_exception
)
667 if(!replaceExisting
) {
668 // FTPDirentry aDirentry(direntry());
669 // if(aDirentry.m_nMode == INETCOREFTP_FILEMODE_UNKNOWN)
670 // throw curl_exception(FILE_EXIST_DURING_INSERT);
671 throw curl_exception(FILE_MIGHT_EXIST_DURING_INSERT
);
673 // overwrite is default in libcurl
675 CURL
*curl
= m_pFCP
->handle();
677 SET_CONTROL_CONTAINER
;
678 curl_easy_setopt(curl
,CURLOPT_NOBODY
,false); // no data => no transfer
679 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,0);
680 curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
681 curl_easy_setopt(curl
,CURLOPT_READFUNCTION
,memory_read
);
682 curl_easy_setopt(curl
,CURLOPT_READDATA
,stream
);
683 curl_easy_setopt(curl
, CURLOPT_UPLOAD
,1);
685 rtl::OUString
url(ident(false,true));
688 CURLcode err
= curl_easy_perform(curl
);
689 curl_easy_setopt(curl
, CURLOPT_UPLOAD
,false);
692 throw curl_exception(err
);
697 void FTPURL::mkdir(bool ReplaceExisting
) const
698 throw(curl_exception
)
701 if(m_aPathSegmentVec
.size()) {
702 rtl::OUString titleOU
= m_aPathSegmentVec
.back();
703 titleOU
= decodePathSegment(titleOU
);
704 title
= rtl::OString(titleOU
.getStr(),
706 RTL_TEXTENCODING_UTF8
);
709 // will give an error
710 title
= rtl::OString("/");
712 rtl::OString
aDel("del "); aDel
+= title
;
713 rtl::OString
mkd("mkd "); mkd
+= title
;
715 struct curl_slist
*slist
= 0;
717 FTPDirentry
aDirentry(direntry());
718 if(!ReplaceExisting
) {
719 // if(aDirentry.m_nMode != INETCOREFTP_FILEMODE_UNKNOWN)
720 // throw curl_exception(FOLDER_EXIST_DURING_INSERT);
721 throw curl_exception(FOLDER_MIGHT_EXIST_DURING_INSERT
);
722 } else if(aDirentry
.m_nMode
!= INETCOREFTP_FILEMODE_UNKNOWN
)
723 slist
= curl_slist_append(slist
,aDel
.getStr());
725 slist
= curl_slist_append(slist
,mkd
.getStr());
727 CURL
*curl
= m_pFCP
->handle();
728 SET_CONTROL_CONTAINER
;
729 curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
730 curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
733 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
735 rtl::OUString
url(parent(true));
736 if(1+url
.lastIndexOf(sal_Unicode('/')) != url
.getLength())
737 url
+= rtl::OUString::createFromAscii("/");
740 CURLcode err
= curl_easy_perform(curl
);
741 curl_slist_free_all(slist
);
743 throw curl_exception(err
);
747 rtl::OUString
FTPURL::ren(const rtl::OUString
& NewTitle
)
748 throw(curl_exception
)
750 CURL
*curl
= m_pFCP
->handle();
753 rtl::OString
renamefrom("RNFR ");
754 rtl::OUString OldTitle
= net_title();
756 rtl::OString(OldTitle
.getStr(),
757 OldTitle
.getLength(),
758 RTL_TEXTENCODING_UTF8
);
760 rtl::OString
renameto("RNTO ");
762 rtl::OString(NewTitle
.getStr(),
763 NewTitle
.getLength(),
764 RTL_TEXTENCODING_UTF8
);
766 struct curl_slist
*slist
= 0;
767 slist
= curl_slist_append(slist
,renamefrom
.getStr());
768 slist
= curl_slist_append(slist
,renameto
.getStr());
769 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
771 SET_CONTROL_CONTAINER
;
772 curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
773 curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
775 rtl::OUString
url(parent(true));
776 if(1+url
.lastIndexOf(sal_Unicode('/')) != url
.getLength())
777 url
+= rtl::OUString::createFromAscii("/");
780 CURLcode err
= curl_easy_perform(curl
);
781 curl_slist_free_all(slist
);
783 throw curl_exception(err
);
784 else if(m_aPathSegmentVec
.size() &&
785 !m_aPathSegmentVec
.back().equalsAscii(".."))
786 m_aPathSegmentVec
.back() = encodePathSegment(NewTitle
);
792 void FTPURL::del() const
793 throw(curl_exception
)
795 FTPDirentry
aDirentry(direntry());
797 rtl::OString
dele(aDirentry
.m_aName
.getStr(),
798 aDirentry
.m_aName
.getLength(),
799 RTL_TEXTENCODING_UTF8
);
801 if(aDirentry
.m_nMode
& INETCOREFTP_FILEMODE_ISDIR
) {
802 std::vector
<FTPDirentry
> vec
= list(sal_Int16(OpenMode::ALL
));
803 for( unsigned int i
= 0; i
< vec
.size(); ++i
)
805 FTPURL
url(vec
[i
].m_aURL
,m_pFCP
);
807 } catch(const curl_exception
&) {
809 dele
= rtl::OString("RMD ") + dele
;
811 else if(aDirentry
.m_nMode
!= INETCOREFTP_FILEMODE_UNKNOWN
)
812 dele
= rtl::OString("DELE ") + dele
;
817 CURL
*curl
= m_pFCP
->handle();
818 struct curl_slist
*slist
= 0;
819 slist
= curl_slist_append(slist
,dele
.getStr());
820 curl_easy_setopt(curl
,CURLOPT_POSTQUOTE
,slist
);
822 SET_CONTROL_CONTAINER
;
823 curl_easy_setopt(curl
,CURLOPT_NOBODY
,true); // no data => no transfer
824 curl_easy_setopt(curl
,CURLOPT_QUOTE
,0);
826 rtl::OUString
url(parent(true));
827 if(1+url
.lastIndexOf(sal_Unicode('/')) != url
.getLength())
828 url
+= rtl::OUString::createFromAscii("/");
831 CURLcode err
= curl_easy_perform(curl
);
832 curl_slist_free_all(slist
);
834 throw curl_exception(err
);