1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2000, 2010 Oracle and/or its affiliates.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * This file is part of OpenOffice.org.
11 * OpenOffice.org is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License version 3
13 * only, as published by the Free Software Foundation.
15 * OpenOffice.org is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License version 3 for more details
19 * (a copy is included in the LICENSE file that accompanied this code).
21 * You should have received a copy of the GNU Lesser General Public License
22 * version 3 along with OpenOffice.org. If not, see
23 * <http://www.openoffice.org/license.html>
24 * for a copy of the LGPLv3 License.
26 ************************************************************************/
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_extensions.hxx"
33 #pragma warning(push, 1) /* disable warnings within system headers */
35 #include <curl/curl.h>
40 #include <curl/curl.h>
42 #include <com/sun/star/beans/PropertyValue.hpp>
43 #include <com/sun/star/container/XNameAccess.hpp>
44 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
46 #include "download.hxx"
48 namespace beans
= com::sun::star::beans
;
49 namespace container
= com::sun::star::container
;
50 namespace lang
= com::sun::star::lang
;
51 namespace uno
= com::sun::star::uno
;
53 #define UNISTRING(s) rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(s))
58 rtl::Reference
< DownloadInteractionHandler
>Handler
;
60 rtl::OUString DestinationDir
;
61 oslFileHandle FileHandle
;
63 osl::Condition
& StopCondition
;
66 OutData(osl::Condition
& rCondition
) : FileHandle(NULL
), Offset(0), StopCondition(rCondition
), curl(NULL
) {};
69 //------------------------------------------------------------------------------
71 static void openFile( OutData
& out
)
74 curl_easy_getinfo(out
.curl
, CURLINFO_EFFECTIVE_URL
, &effective_url
);
77 curl_easy_getinfo(out
.curl
, CURLINFO_CONTENT_LENGTH_DOWNLOAD
, &fDownloadSize
);
79 rtl::OString
aURL(effective_url
);
81 // ensure no trailing '/'
82 sal_Int32 nLen
= aURL
.getLength();
83 while( (nLen
> 0) && ('/' == aURL
[nLen
-1]) )
84 aURL
= aURL
.copy(0, --nLen
);
86 // extract file name last '/'
87 sal_Int32 nIndex
= aURL
.lastIndexOf('/');
90 out
.File
= out
.DestinationDir
+ rtl::OStringToOUString(aURL
.copy(nIndex
), RTL_TEXTENCODING_UTF8
);
94 // Give the user an overwrite warning if the target file exists
95 const sal_Int32 openFlags
= osl_File_OpenFlag_Write
| osl_File_OpenFlag_Create
;
98 rc
= osl_openFile(out
.File
.pData
, &out
.FileHandle
, openFlags
);
100 if( osl_File_E_EXIST
== rc
&& ! out
.Handler
->downloadTargetExists(out
.File
) )
102 out
.StopCondition
.set();
106 } while( osl_File_E_EXIST
== rc
);
108 if( osl_File_E_None
== rc
)
109 out
.Handler
->downloadStarted(out
.File
, (sal_Int64
) fDownloadSize
);
113 //------------------------------------------------------------------------------
115 static inline rtl::OString
116 getStringValue(const uno::Reference
< container::XNameAccess
>& xNameAccess
, const rtl::OUString
& aName
)
120 OSL_ASSERT(xNameAccess
->hasByName(aName
));
121 uno::Any aValue
= xNameAccess
->getByName(aName
);
123 return rtl::OUStringToOString(aValue
.get
<rtl::OUString
>(), RTL_TEXTENCODING_UTF8
);
126 //------------------------------------------------------------------------------
128 static inline sal_Int32
129 getInt32Value(const uno::Reference
< container::XNameAccess
>& xNameAccess
,
130 const rtl::OUString
& aName
, sal_Int32 nDefault
=-1)
132 OSL_ASSERT(xNameAccess
->hasByName(aName
));
133 uno::Any aValue
= xNameAccess
->getByName(aName
);
135 sal_Int32 n
=nDefault
;
140 //------------------------------------------------------------------------------
143 write_function( void *ptr
, size_t size
, size_t nmemb
, void *stream
)
145 OutData
*out
= reinterpret_cast < OutData
* > (stream
);
147 if( NULL
== out
->FileHandle
)
150 sal_uInt64 nBytesWritten
= 0;
152 if( NULL
!= out
->FileHandle
)
153 osl_writeFile(out
->FileHandle
, ptr
, size
* nmemb
, &nBytesWritten
);
155 return (size_t) nBytesWritten
;
158 //------------------------------------------------------------------------------
161 progress_callback( void *clientp
, double dltotal
, double dlnow
, double ultotal
, double ulnow
)
166 OutData
*out
= reinterpret_cast < OutData
* > (clientp
);
170 if( ! out
->StopCondition
.check() )
173 if ( dltotal
+ out
->Offset
)
174 fPercent
= (dlnow
+ out
->Offset
) * 100 / (dltotal
+ out
->Offset
);
178 // Do not report progress for redirection replies
180 curl_easy_getinfo(out
->curl
, CURLINFO_RESPONSE_CODE
, &nCode
);
181 if( (nCode
!= 302) && (nCode
!= 303) && (dltotal
> 0) )
182 out
->Handler
->downloadProgressAt((sal_Int8
)fPercent
);
187 // If stop condition is set, return non 0 value to abort
191 //------------------------------------------------------------------------------
194 Download::getProxyForURL(const rtl::OUString
& rURL
, rtl::OString
& rHost
, sal_Int32
& rPort
) const
196 if( !m_xContext
.is() )
197 throw uno::RuntimeException(
198 UNISTRING( "Download: empty component context" ),
199 uno::Reference
< uno::XInterface
>() );
201 uno::Reference
< lang::XMultiComponentFactory
> xServiceManager(m_xContext
->getServiceManager());
203 if( !xServiceManager
.is() )
204 throw uno::RuntimeException(
205 UNISTRING( "Download: unable to obtain service manager from component context" ),
206 uno::Reference
< uno::XInterface
>() );
208 uno::Reference
< lang::XMultiServiceFactory
> xConfigProvider(
209 xServiceManager
->createInstanceWithContext( UNISTRING( "com.sun.star.configuration.ConfigurationProvider" ), m_xContext
),
210 uno::UNO_QUERY_THROW
);
212 beans::PropertyValue aProperty
;
213 aProperty
.Name
= UNISTRING( "nodepath" );
214 aProperty
.Value
= uno::makeAny( UNISTRING("org.openoffice.Inet/Settings") );
216 uno::Sequence
< uno::Any
> aArgumentList( 1 );
217 aArgumentList
[0] = uno::makeAny( aProperty
);
219 uno::Reference
< container::XNameAccess
> xNameAccess(
220 xConfigProvider
->createInstanceWithArguments(
221 UNISTRING("com.sun.star.configuration.ConfigurationAccess"), aArgumentList
),
222 uno::UNO_QUERY_THROW
);
224 OSL_ASSERT(xNameAccess
->hasByName(UNISTRING("ooInetProxyType")));
225 uno::Any aValue
= xNameAccess
->getByName(UNISTRING("ooInetProxyType"));
227 sal_Int32 nProxyType
= aValue
.get
< sal_Int32
>();
228 if( 0 != nProxyType
) // type 0 means "direct connection to the internet
230 if( rURL
.matchAsciiL(RTL_CONSTASCII_STRINGPARAM("http:")) )
232 rHost
= getStringValue(xNameAccess
, UNISTRING("ooInetHTTPProxyName"));
233 rPort
= getInt32Value(xNameAccess
, UNISTRING("ooInetHTTPProxyPort"));
235 else if( rURL
.matchAsciiL(RTL_CONSTASCII_STRINGPARAM("https:")) )
237 rHost
= getStringValue(xNameAccess
, UNISTRING("ooInetHTTPSProxyName"));
238 rPort
= getInt32Value(xNameAccess
, UNISTRING("ooInetHTTPSProxyPort"));
240 else if( rURL
.matchAsciiL(RTL_CONSTASCII_STRINGPARAM("ftp:")) )
242 rHost
= getStringValue(xNameAccess
, UNISTRING("ooInetFTPProxyName"));
243 rPort
= getInt32Value(xNameAccess
, UNISTRING("ooInetFTPProxyPort"));
248 //------------------------------------------------------------------------------
250 bool curl_run(const rtl::OUString
& rURL
, OutData
& out
, const rtl::OString
& aProxyHost
, sal_Int32 nProxyPort
)
252 /* Need to investigate further whether it is necessary to call
253 * curl_global_init or not - leave it for now (as the ftp UCB content
254 * provider does as well).
257 CURL
* pCURL
= curl_easy_init();
264 rtl::OString
aURL(rtl::OUStringToOString(rURL
, RTL_TEXTENCODING_UTF8
));
265 curl_easy_setopt(pCURL
, CURLOPT_URL
, aURL
.getStr());
267 // abort on http errors
268 curl_easy_setopt(pCURL
, CURLOPT_FAILONERROR
, 1);
270 // enable redirection
271 curl_easy_setopt(pCURL
, CURLOPT_FOLLOWLOCATION
, 1);
274 curl_easy_setopt(pCURL
, CURLOPT_WRITEDATA
, &out
);
275 curl_easy_setopt(pCURL
, CURLOPT_WRITEFUNCTION
, &write_function
);
277 // progress handler - Condition::check unfortunatly is not defined const
278 curl_easy_setopt(pCURL
, CURLOPT_NOPROGRESS
, 0);
279 curl_easy_setopt(pCURL
, CURLOPT_PROGRESSFUNCTION
, &progress_callback
);
280 curl_easy_setopt(pCURL
, CURLOPT_PROGRESSDATA
, &out
);
283 curl_easy_setopt(pCURL
, CURLOPT_PROXY
, aProxyHost
.getStr());
284 curl_easy_setopt(pCURL
, CURLOPT_PROXYTYPE
, CURLPROXY_HTTP
);
285 if( -1 != nProxyPort
)
286 curl_easy_setopt(pCURL
, CURLOPT_PROXYPORT
, nProxyPort
);
290 // curl_off_t offset = nOffset; libcurl seems to be compiled with large
291 // file support (and we not) ..
292 sal_Int64 offset
= (sal_Int64
) out
.Offset
;
293 curl_easy_setopt(pCURL
, CURLOPT_RESUME_FROM_LARGE
, offset
);
296 CURLcode cc
= curl_easy_perform(pCURL
);
298 // treat zero byte downloads as errors
299 if( NULL
== out
.FileHandle
)
304 out
.Handler
->downloadFinished(out
.File
);
308 if ( CURLE_PARTIAL_FILE
== cc
)
310 // this sometimes happens, when a user throws away his user data, but has already
311 // completed the download of an update.
312 double fDownloadSize
;
313 curl_easy_getinfo( pCURL
, CURLINFO_CONTENT_LENGTH_DOWNLOAD
, &fDownloadSize
);
314 if ( -1 == fDownloadSize
)
316 out
.Handler
->downloadFinished(out
.File
);
321 // Avoid target file being removed
322 else if( (CURLE_ABORTED_BY_CALLBACK
== cc
) || out
.StopCondition
.check() )
325 // Only report errors when not stopped
328 rtl::OString
aMessage(RTL_CONSTASCII_STRINGPARAM("Unknown error"));
330 const char * error_message
= curl_easy_strerror(cc
);
331 if( NULL
!= error_message
)
332 aMessage
= error_message
;
334 if ( CURLE_HTTP_RETURNED_ERROR
== cc
)
337 curl_easy_getinfo( pCURL
, CURLINFO_RESPONSE_CODE
, &nError
);
340 aMessage
+= rtl::OString( RTL_CONSTASCII_STRINGPARAM( " 403: Access denied!" ) );
341 else if ( 404 == nError
)
342 aMessage
+= rtl::OString( RTL_CONSTASCII_STRINGPARAM( " 404: File not found!" ) );
343 else if ( 416 == nError
)
345 // we got this error probably, because we already downloaded the file
346 out
.Handler
->downloadFinished(out
.File
);
351 aMessage
+= rtl::OString( RTL_CONSTASCII_STRINGPARAM( ":error code = " ) );
352 aMessage
+= aMessage
.valueOf( nError
);
353 aMessage
+= rtl::OString( RTL_CONSTASCII_STRINGPARAM( " !" ) );
357 out
.Handler
->downloadStalled( rtl::OStringToOUString(aMessage
, RTL_TEXTENCODING_UTF8
) );
360 curl_easy_cleanup(pCURL
);
366 //------------------------------------------------------------------------------
369 Download::start(const rtl::OUString
& rURL
, const rtl::OUString
& rFile
, const rtl::OUString
& rDestinationDir
)
371 OSL_ASSERT( m_aHandler
.is() );
373 OutData
out(m_aCondition
);
374 rtl::OUString
aFile( rFile
);
376 // when rFile is empty, there is no remembered file name. If there is already a file with the
377 // same name ask the user if she wants to resume a download or restart the download
378 if ( !aFile
.getLength() )
381 rtl::OUString
aURL( rURL
);
382 // ensure no trailing '/'
383 sal_Int32 nLen
= aURL
.getLength();
384 while( (nLen
> 0) && ('/' == aURL
[ nLen
-1 ]) )
385 aURL
= aURL
.copy( 0, --nLen
);
387 // extract file name last '/'
388 sal_Int32 nIndex
= aURL
.lastIndexOf('/');
389 aFile
= rDestinationDir
+ aURL
.copy( nIndex
);
391 // check for existing file
392 oslFileError rc
= osl_openFile( aFile
.pData
, &out
.FileHandle
, osl_File_OpenFlag_Write
| osl_File_OpenFlag_Create
);
393 osl_closeFile(out
.FileHandle
);
394 out
.FileHandle
= NULL
;
396 if( osl_File_E_EXIST
== rc
)
398 if ( m_aHandler
->checkDownloadDestination( aURL
.copy( nIndex
+1 ) ) )
400 osl_removeFile( aFile
.pData
);
401 aFile
= rtl::OUString();
404 m_aHandler
->downloadStarted( aFile
, 0 );
408 osl_removeFile( aFile
.pData
);
409 aFile
= rtl::OUString();
414 out
.DestinationDir
= rDestinationDir
;
415 out
.Handler
= m_aHandler
;
417 if( aFile
.getLength() > 0 )
419 oslFileError rc
= osl_openFile(aFile
.pData
, &out
.FileHandle
, osl_File_OpenFlag_Write
);
421 if( osl_File_E_None
== rc
)
423 // Set file pointer to the end of the file on resume
424 if( osl_File_E_None
== osl_setFilePos(out
.FileHandle
, osl_Pos_End
, 0) )
426 osl_getFilePos(out
.FileHandle
, &out
.Offset
);
429 else if( osl_File_E_NOENT
== rc
) // file has been deleted meanwhile ..
430 out
.File
= rtl::OUString();
433 rtl::OString aProxyHost
;
434 sal_Int32 nProxyPort
= -1;
435 getProxyForURL(rURL
, aProxyHost
, nProxyPort
);
437 bool ret
= curl_run(rURL
, out
, aProxyHost
, nProxyPort
);
439 if( NULL
!= out
.FileHandle
)
441 osl_syncFile(out
.FileHandle
);
442 osl_closeFile(out
.FileHandle
);
444 // #i90930# Don't remove already downloaded bits, when curl_run reports an error
445 // because later calls might be successful
447 // osl_removeFile(out.File.pData);
450 m_aCondition
.reset();
454 //------------------------------------------------------------------------------