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 ************************************************************************/
32 #pragma warning(push, 1) /* disable warnings within system headers */
34 #include <curl/curl.h>
39 #include <curl/curl.h>
41 #include <com/sun/star/beans/PropertyValue.hpp>
42 #include <com/sun/star/configuration/theDefaultProvider.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 uno::Reference
< lang::XMultiServiceFactory
> xConfigProvider(
197 com::sun::star::configuration::theDefaultProvider::get( m_xContext
) );
199 beans::PropertyValue aProperty
;
200 aProperty
.Name
= UNISTRING( "nodepath" );
201 aProperty
.Value
= uno::makeAny( UNISTRING("org.openoffice.Inet/Settings") );
203 uno::Sequence
< uno::Any
> aArgumentList( 1 );
204 aArgumentList
[0] = uno::makeAny( aProperty
);
206 uno::Reference
< container::XNameAccess
> xNameAccess(
207 xConfigProvider
->createInstanceWithArguments(
208 UNISTRING("com.sun.star.configuration.ConfigurationAccess"), aArgumentList
),
209 uno::UNO_QUERY_THROW
);
211 OSL_ASSERT(xNameAccess
->hasByName(UNISTRING("ooInetProxyType")));
212 uno::Any aValue
= xNameAccess
->getByName(UNISTRING("ooInetProxyType"));
214 sal_Int32 nProxyType
= aValue
.get
< sal_Int32
>();
215 if( 0 != nProxyType
) // type 0 means "direct connection to the internet
217 if( rURL
.matchAsciiL(RTL_CONSTASCII_STRINGPARAM("http:")) )
219 rHost
= getStringValue(xNameAccess
, UNISTRING("ooInetHTTPProxyName"));
220 rPort
= getInt32Value(xNameAccess
, UNISTRING("ooInetHTTPProxyPort"));
222 else if( rURL
.matchAsciiL(RTL_CONSTASCII_STRINGPARAM("https:")) )
224 rHost
= getStringValue(xNameAccess
, UNISTRING("ooInetHTTPSProxyName"));
225 rPort
= getInt32Value(xNameAccess
, UNISTRING("ooInetHTTPSProxyPort"));
227 else if( rURL
.matchAsciiL(RTL_CONSTASCII_STRINGPARAM("ftp:")) )
229 rHost
= getStringValue(xNameAccess
, UNISTRING("ooInetFTPProxyName"));
230 rPort
= getInt32Value(xNameAccess
, UNISTRING("ooInetFTPProxyPort"));
235 //------------------------------------------------------------------------------
237 bool curl_run(const rtl::OUString
& rURL
, OutData
& out
, const rtl::OString
& aProxyHost
, sal_Int32 nProxyPort
)
239 /* Need to investigate further whether it is necessary to call
240 * curl_global_init or not - leave it for now (as the ftp UCB content
241 * provider does as well).
244 CURL
* pCURL
= curl_easy_init();
251 rtl::OString
aURL(rtl::OUStringToOString(rURL
, RTL_TEXTENCODING_UTF8
));
252 curl_easy_setopt(pCURL
, CURLOPT_URL
, aURL
.getStr());
254 // abort on http errors
255 curl_easy_setopt(pCURL
, CURLOPT_FAILONERROR
, 1);
257 // enable redirection
258 curl_easy_setopt(pCURL
, CURLOPT_FOLLOWLOCATION
, 1);
261 curl_easy_setopt(pCURL
, CURLOPT_WRITEDATA
, &out
);
262 curl_easy_setopt(pCURL
, CURLOPT_WRITEFUNCTION
, &write_function
);
264 // progress handler - Condition::check unfortunatly is not defined const
265 curl_easy_setopt(pCURL
, CURLOPT_NOPROGRESS
, 0);
266 curl_easy_setopt(pCURL
, CURLOPT_PROGRESSFUNCTION
, &progress_callback
);
267 curl_easy_setopt(pCURL
, CURLOPT_PROGRESSDATA
, &out
);
270 curl_easy_setopt(pCURL
, CURLOPT_PROXY
, aProxyHost
.getStr());
271 curl_easy_setopt(pCURL
, CURLOPT_PROXYTYPE
, CURLPROXY_HTTP
);
272 if( -1 != nProxyPort
)
273 curl_easy_setopt(pCURL
, CURLOPT_PROXYPORT
, nProxyPort
);
277 // curl_off_t offset = nOffset; libcurl seems to be compiled with large
278 // file support (and we not) ..
279 sal_Int64 offset
= (sal_Int64
) out
.Offset
;
280 curl_easy_setopt(pCURL
, CURLOPT_RESUME_FROM_LARGE
, offset
);
283 CURLcode cc
= curl_easy_perform(pCURL
);
285 // treat zero byte downloads as errors
286 if( NULL
== out
.FileHandle
)
291 out
.Handler
->downloadFinished(out
.File
);
295 if ( CURLE_PARTIAL_FILE
== cc
)
297 // this sometimes happens, when a user throws away his user data, but has already
298 // completed the download of an update.
299 double fDownloadSize
;
300 curl_easy_getinfo( pCURL
, CURLINFO_CONTENT_LENGTH_DOWNLOAD
, &fDownloadSize
);
301 if ( -1 == fDownloadSize
)
303 out
.Handler
->downloadFinished(out
.File
);
308 // Avoid target file being removed
309 else if( (CURLE_ABORTED_BY_CALLBACK
== cc
) || out
.StopCondition
.check() )
312 // Only report errors when not stopped
315 rtl::OString
aMessage(RTL_CONSTASCII_STRINGPARAM("Unknown error"));
317 const char * error_message
= curl_easy_strerror(cc
);
318 if( NULL
!= error_message
)
319 aMessage
= error_message
;
321 if ( CURLE_HTTP_RETURNED_ERROR
== cc
)
324 curl_easy_getinfo( pCURL
, CURLINFO_RESPONSE_CODE
, &nError
);
327 aMessage
+= rtl::OString( RTL_CONSTASCII_STRINGPARAM( " 403: Access denied!" ) );
328 else if ( 404 == nError
)
329 aMessage
+= rtl::OString( RTL_CONSTASCII_STRINGPARAM( " 404: File not found!" ) );
330 else if ( 416 == nError
)
332 // we got this error probably, because we already downloaded the file
333 out
.Handler
->downloadFinished(out
.File
);
338 aMessage
+= rtl::OString( RTL_CONSTASCII_STRINGPARAM( ":error code = " ) );
339 aMessage
+= aMessage
.valueOf( nError
);
340 aMessage
+= rtl::OString( RTL_CONSTASCII_STRINGPARAM( " !" ) );
344 out
.Handler
->downloadStalled( rtl::OStringToOUString(aMessage
, RTL_TEXTENCODING_UTF8
) );
347 curl_easy_cleanup(pCURL
);
353 //------------------------------------------------------------------------------
356 Download::start(const rtl::OUString
& rURL
, const rtl::OUString
& rFile
, const rtl::OUString
& rDestinationDir
)
358 OSL_ASSERT( m_aHandler
.is() );
360 OutData
out(m_aCondition
);
361 rtl::OUString
aFile( rFile
);
363 // when rFile is empty, there is no remembered file name. If there is already a file with the
364 // same name ask the user if she wants to resume a download or restart the download
365 if ( aFile
.isEmpty() )
368 rtl::OUString
aURL( rURL
);
369 // ensure no trailing '/'
370 sal_Int32 nLen
= aURL
.getLength();
371 while( (nLen
> 0) && ('/' == aURL
[ nLen
-1 ]) )
372 aURL
= aURL
.copy( 0, --nLen
);
374 // extract file name last '/'
375 sal_Int32 nIndex
= aURL
.lastIndexOf('/');
376 aFile
= rDestinationDir
+ aURL
.copy( nIndex
);
378 // check for existing file
379 oslFileError rc
= osl_openFile( aFile
.pData
, &out
.FileHandle
, osl_File_OpenFlag_Write
| osl_File_OpenFlag_Create
);
380 osl_closeFile(out
.FileHandle
);
381 out
.FileHandle
= NULL
;
383 if( osl_File_E_EXIST
== rc
)
385 if ( m_aHandler
->checkDownloadDestination( aURL
.copy( nIndex
+1 ) ) )
387 osl_removeFile( aFile
.pData
);
388 aFile
= rtl::OUString();
391 m_aHandler
->downloadStarted( aFile
, 0 );
395 osl_removeFile( aFile
.pData
);
396 aFile
= rtl::OUString();
401 out
.DestinationDir
= rDestinationDir
;
402 out
.Handler
= m_aHandler
;
404 if( !aFile
.isEmpty() )
406 oslFileError rc
= osl_openFile(aFile
.pData
, &out
.FileHandle
, osl_File_OpenFlag_Write
);
408 if( osl_File_E_None
== rc
)
410 // Set file pointer to the end of the file on resume
411 if( osl_File_E_None
== osl_setFilePos(out
.FileHandle
, osl_Pos_End
, 0) )
413 osl_getFilePos(out
.FileHandle
, &out
.Offset
);
416 else if( osl_File_E_NOENT
== rc
) // file has been deleted meanwhile ..
417 out
.File
= rtl::OUString();
420 rtl::OString aProxyHost
;
421 sal_Int32 nProxyPort
= -1;
422 getProxyForURL(rURL
, aProxyHost
, nProxyPort
);
424 bool ret
= curl_run(rURL
, out
, aProxyHost
, nProxyPort
);
426 if( NULL
!= out
.FileHandle
)
428 osl_syncFile(out
.FileHandle
);
429 osl_closeFile(out
.FileHandle
);
431 // #i90930# Don't remove already downloaded bits, when curl_run reports an error
432 // because later calls might be successful
434 // osl_removeFile(out.File.pData);
437 m_aCondition
.reset();
441 //------------------------------------------------------------------------------
449 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */