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 .
21 #include <curl/curl.h>
23 #include <osl/diagnose.h>
24 #include <com/sun/star/beans/PropertyValue.hpp>
25 #include <com/sun/star/configuration/theDefaultProvider.hpp>
26 #include <com/sun/star/container/XNameAccess.hpp>
27 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
29 #include "download.hxx"
31 namespace beans
= com::sun::star::beans
;
32 namespace container
= com::sun::star::container
;
33 namespace lang
= com::sun::star::lang
;
34 namespace uno
= com::sun::star::uno
;
39 rtl::Reference
< DownloadInteractionHandler
>Handler
;
41 OUString DestinationDir
;
42 oslFileHandle FileHandle
;
44 osl::Condition
& StopCondition
;
47 explicit OutData(osl::Condition
& rCondition
) : FileHandle(nullptr), Offset(0), StopCondition(rCondition
), curl(nullptr) {};
51 static void openFile( OutData
& out
)
54 curl_easy_getinfo(out
.curl
, CURLINFO_EFFECTIVE_URL
, &effective_url
);
57 curl_easy_getinfo(out
.curl
, CURLINFO_CONTENT_LENGTH_DOWNLOAD
, &fDownloadSize
);
59 OString
aURL(effective_url
);
61 // ensure no trailing '/'
62 sal_Int32 nLen
= aURL
.getLength();
63 while( (nLen
> 0) && ('/' == aURL
[nLen
-1]) )
64 aURL
= aURL
.copy(0, --nLen
);
66 // extract file name last '/'
67 sal_Int32 nIndex
= aURL
.lastIndexOf('/');
70 out
.File
= out
.DestinationDir
+ OStringToOUString(aURL
.copy(nIndex
), RTL_TEXTENCODING_UTF8
);
74 // Give the user an overwrite warning if the target file exists
75 const sal_Int32 openFlags
= osl_File_OpenFlag_Write
| osl_File_OpenFlag_Create
;
78 rc
= osl_openFile(out
.File
.pData
, &out
.FileHandle
, openFlags
);
80 if( osl_File_E_EXIST
== rc
&& ! out
.Handler
->downloadTargetExists(out
.File
) )
82 out
.StopCondition
.set();
86 } while( osl_File_E_EXIST
== rc
);
88 if( osl_File_E_None
== rc
)
89 out
.Handler
->downloadStarted(out
.File
, static_cast<sal_Int64
>(fDownloadSize
));
95 getStringValue(const uno::Reference
< container::XNameAccess
>& xNameAccess
, const OUString
& aName
)
97 OSL_ASSERT(xNameAccess
->hasByName(aName
));
98 uno::Any aValue
= xNameAccess
->getByName(aName
);
100 return OUStringToOString(aValue
.get
<OUString
>(), RTL_TEXTENCODING_UTF8
);
105 getInt32Value(const uno::Reference
< container::XNameAccess
>& xNameAccess
,
106 const OUString
& aName
)
108 OSL_ASSERT(xNameAccess
->hasByName(aName
));
109 uno::Any aValue
= xNameAccess
->getByName(aName
);
118 write_function( void *ptr
, size_t size
, size_t nmemb
, void *stream
)
120 OutData
*out
= static_cast < OutData
* > (stream
);
122 if( nullptr == out
->FileHandle
)
125 sal_uInt64 nBytesWritten
= 0;
127 if( nullptr != out
->FileHandle
)
128 osl_writeFile(out
->FileHandle
, ptr
, size
* nmemb
, &nBytesWritten
);
130 return static_cast<size_t>(nBytesWritten
);
135 progress_callback( void *clientp
, double dltotal
, double dlnow
, SAL_UNUSED_PARAMETER
double, SAL_UNUSED_PARAMETER
double )
137 OutData
*out
= static_cast < OutData
* > (clientp
);
141 if (out
&& !out
->StopCondition
.check())
144 if ( dltotal
+ out
->Offset
)
145 fPercent
= (dlnow
+ out
->Offset
) * 100 / (dltotal
+ out
->Offset
);
149 // Do not report progress for redirection replies
151 curl_easy_getinfo(out
->curl
, CURLINFO_RESPONSE_CODE
, &nCode
);
152 if( (nCode
!= 302) && (nCode
!= 303) && (dltotal
> 0) )
153 out
->Handler
->downloadProgressAt(static_cast<sal_Int8
>(fPercent
));
158 // If stop condition is set, return non 0 value to abort
164 Download::getProxyForURL(const OUString
& rURL
, OString
& rHost
, sal_Int32
& rPort
) const
166 uno::Reference
< lang::XMultiServiceFactory
> xConfigProvider(
167 css::configuration::theDefaultProvider::get( m_xContext
) );
169 beans::PropertyValue aProperty
;
170 aProperty
.Name
= "nodepath";
171 aProperty
.Value
<<= OUString("org.openoffice.Inet/Settings");
173 uno::Sequence
< uno::Any
> aArgumentList( 1 );
174 aArgumentList
[0] <<= aProperty
;
176 uno::Reference
< container::XNameAccess
> xNameAccess(
177 xConfigProvider
->createInstanceWithArguments(
178 "com.sun.star.configuration.ConfigurationAccess", aArgumentList
),
179 uno::UNO_QUERY_THROW
);
181 OSL_ASSERT(xNameAccess
->hasByName("ooInetProxyType"));
182 uno::Any aValue
= xNameAccess
->getByName("ooInetProxyType");
184 sal_Int32 nProxyType
= aValue
.get
< sal_Int32
>();
185 if( 0 != nProxyType
) // type 0 means "direct connection to the internet
187 if( rURL
.startsWith("http:") )
189 rHost
= getStringValue(xNameAccess
, "ooInetHTTPProxyName");
190 rPort
= getInt32Value(xNameAccess
, "ooInetHTTPProxyPort");
192 else if( rURL
.startsWith("https:") )
194 rHost
= getStringValue(xNameAccess
, "ooInetHTTPSProxyName");
195 rPort
= getInt32Value(xNameAccess
, "ooInetHTTPSProxyPort");
197 else if( rURL
.startsWith("ftp:") )
199 rHost
= getStringValue(xNameAccess
, "ooInetFTPProxyName");
200 rPort
= getInt32Value(xNameAccess
, "ooInetFTPProxyPort");
206 static bool curl_run(const OUString
& rURL
, OutData
& out
, const OString
& aProxyHost
, sal_Int32 nProxyPort
)
208 /* Need to investigate further whether it is necessary to call
209 * curl_global_init or not - leave it for now (as the ftp UCB content
210 * provider does as well).
213 CURL
* pCURL
= curl_easy_init();
216 if( nullptr != pCURL
)
220 OString
aURL(OUStringToOString(rURL
, RTL_TEXTENCODING_UTF8
));
221 curl_easy_setopt(pCURL
, CURLOPT_URL
, aURL
.getStr());
223 // abort on http errors
224 curl_easy_setopt(pCURL
, CURLOPT_FAILONERROR
, 1);
226 // enable redirection
227 curl_easy_setopt(pCURL
, CURLOPT_FOLLOWLOCATION
, 1);
228 // only allow redirect to http:// and https://
229 curl_easy_setopt(pCURL
, CURLOPT_REDIR_PROTOCOLS
, CURLPROTO_HTTP
| CURLPROTO_HTTPS
);
232 curl_easy_setopt(pCURL
, CURLOPT_WRITEDATA
, &out
);
233 curl_easy_setopt(pCURL
, CURLOPT_WRITEFUNCTION
, &write_function
);
235 // progress handler - Condition::check unfortunately is not defined const
236 curl_easy_setopt(pCURL
, CURLOPT_NOPROGRESS
, 0);
237 curl_easy_setopt(pCURL
, CURLOPT_PROGRESSFUNCTION
, &progress_callback
);
238 curl_easy_setopt(pCURL
, CURLOPT_PROGRESSDATA
, &out
);
241 curl_easy_setopt(pCURL
, CURLOPT_PROXY
, aProxyHost
.getStr());
242 curl_easy_setopt(pCURL
, CURLOPT_PROXYTYPE
, CURLPROXY_HTTP
);
243 if( -1 != nProxyPort
)
244 curl_easy_setopt(pCURL
, CURLOPT_PROXYPORT
, nProxyPort
);
248 // curl_off_t offset = nOffset; libcurl seems to be compiled with large
249 // file support (and we not) ..
250 sal_Int64 offset
= static_cast<sal_Int64
>(out
.Offset
);
251 curl_easy_setopt(pCURL
, CURLOPT_RESUME_FROM_LARGE
, offset
);
254 CURLcode cc
= curl_easy_perform(pCURL
);
256 // treat zero byte downloads as errors
257 if( nullptr == out
.FileHandle
)
262 out
.Handler
->downloadFinished(out
.File
);
266 if ( CURLE_PARTIAL_FILE
== cc
)
268 // this sometimes happens, when a user throws away his user data, but has already
269 // completed the download of an update.
270 double fDownloadSize
;
271 curl_easy_getinfo( pCURL
, CURLINFO_CONTENT_LENGTH_DOWNLOAD
, &fDownloadSize
);
272 if ( -1 == fDownloadSize
)
274 out
.Handler
->downloadFinished(out
.File
);
279 // Avoid target file being removed
280 else if( (CURLE_ABORTED_BY_CALLBACK
== cc
) || out
.StopCondition
.check() )
283 // Only report errors when not stopped
286 OString
aMessage("Unknown error");
288 const char * error_message
= curl_easy_strerror(cc
);
289 if( nullptr != error_message
)
290 aMessage
= error_message
;
292 if ( CURLE_HTTP_RETURNED_ERROR
== cc
)
295 curl_easy_getinfo( pCURL
, CURLINFO_RESPONSE_CODE
, &nError
);
298 aMessage
+= " 403: Access denied!";
299 else if ( 404 == nError
)
300 aMessage
+= " 404: File not found!";
301 else if ( 416 == nError
)
303 // we got this error probably, because we already downloaded the file
304 out
.Handler
->downloadFinished(out
.File
);
309 aMessage
+= ":error code = ";
310 aMessage
+= OString::number( nError
);
315 out
.Handler
->downloadStalled( OStringToOUString(aMessage
, RTL_TEXTENCODING_UTF8
) );
318 curl_easy_cleanup(pCURL
);
326 Download::start(const OUString
& rURL
, const OUString
& rFile
, const OUString
& rDestinationDir
)
328 OSL_ASSERT( m_aHandler
.is() );
330 OutData
out(m_aCondition
);
331 OUString
aFile( rFile
);
333 // when rFile is empty, there is no remembered file name. If there is already a file with the
334 // same name ask the user if she wants to resume a download or restart the download
335 if ( aFile
.isEmpty() )
338 OUString
aURL( rURL
);
339 // ensure no trailing '/'
340 sal_Int32 nLen
= aURL
.getLength();
341 while( (nLen
> 0) && ('/' == aURL
[ nLen
-1 ]) )
342 aURL
= aURL
.copy( 0, --nLen
);
344 // extract file name last '/'
345 sal_Int32 nIndex
= aURL
.lastIndexOf('/');
346 aFile
= rDestinationDir
+ aURL
.copy( nIndex
);
348 // check for existing file
349 oslFileError rc
= osl_openFile( aFile
.pData
, &out
.FileHandle
, osl_File_OpenFlag_Write
| osl_File_OpenFlag_Create
);
350 osl_closeFile(out
.FileHandle
);
351 out
.FileHandle
= nullptr;
353 if( osl_File_E_EXIST
== rc
)
355 if ( m_aHandler
->checkDownloadDestination( aURL
.copy( nIndex
+1 ) ) )
357 osl_removeFile( aFile
.pData
);
361 m_aHandler
->downloadStarted( aFile
, 0 );
365 osl_removeFile( aFile
.pData
);
371 out
.DestinationDir
= rDestinationDir
;
372 out
.Handler
= m_aHandler
;
374 if( !aFile
.isEmpty() )
376 oslFileError rc
= osl_openFile(aFile
.pData
, &out
.FileHandle
, osl_File_OpenFlag_Write
);
378 if( osl_File_E_None
== rc
)
380 // Set file pointer to the end of the file on resume
381 if( osl_File_E_None
== osl_setFilePos(out
.FileHandle
, osl_Pos_End
, 0) )
383 osl_getFilePos(out
.FileHandle
, &out
.Offset
);
386 else if( osl_File_E_NOENT
== rc
) // file has been deleted meanwhile ..
391 sal_Int32 nProxyPort
= -1;
392 getProxyForURL(rURL
, aProxyHost
, nProxyPort
);
394 bool ret
= curl_run(rURL
, out
, aProxyHost
, nProxyPort
);
396 if( nullptr != out
.FileHandle
)
398 osl_syncFile(out
.FileHandle
);
399 osl_closeFile(out
.FileHandle
);
401 // #i90930# Don't remove already downloaded bits, when curl_run reports an error
402 // because later calls might be successful
404 // osl_removeFile(out.File.pData);
407 m_aCondition
.reset();
418 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */