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 .
20 #include <sal/config.h>
22 #include <string_view>
24 #include <curl/curl.h>
26 #include <systools/curlinit.hxx>
28 #include <o3tl/string_view.hxx>
29 #include <osl/diagnose.h>
31 #include <com/sun/star/beans/PropertyValue.hpp>
32 #include <com/sun/star/configuration/theDefaultProvider.hpp>
33 #include <com/sun/star/container/XNameAccess.hpp>
34 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
36 #include "download.hxx"
38 namespace beans
= com::sun::star::beans
;
39 namespace container
= com::sun::star::container
;
40 namespace lang
= com::sun::star::lang
;
41 namespace uno
= com::sun::star::uno
;
47 rtl::Reference
< DownloadInteractionHandler
>Handler
;
49 OUString DestinationDir
;
50 oslFileHandle FileHandle
;
52 osl::Condition
& StopCondition
;
55 explicit OutData(osl::Condition
& rCondition
) : FileHandle(nullptr), Offset(0), StopCondition(rCondition
), curl(nullptr) {};
60 static void openFile( OutData
& out
)
63 curl_easy_getinfo(out
.curl
, CURLINFO_EFFECTIVE_URL
, &effective_url
);
65 curl_off_t nDownloadSize
;
66 curl_easy_getinfo(out
.curl
, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
, &nDownloadSize
);
68 OString
aURL(effective_url
);
70 // ensure no trailing '/'
71 sal_Int32 nLen
= aURL
.getLength();
72 while( (nLen
> 0) && ('/' == aURL
[nLen
-1]) )
73 aURL
= aURL
.copy(0, --nLen
);
75 // extract file name last '/'
76 sal_Int32 nIndex
= aURL
.lastIndexOf('/');
79 out
.File
= out
.DestinationDir
80 + OStringToOUString(aURL
.subView(nIndex
), RTL_TEXTENCODING_UTF8
);
84 // Give the user an overwrite warning if the target file exists
85 const sal_Int32 openFlags
= osl_File_OpenFlag_Write
| osl_File_OpenFlag_Create
;
88 rc
= osl_openFile(out
.File
.pData
, &out
.FileHandle
, openFlags
);
90 if( osl_File_E_EXIST
== rc
&& ! out
.Handler
->downloadTargetExists(out
.File
) )
92 out
.StopCondition
.set();
96 } while( osl_File_E_EXIST
== rc
);
98 if( osl_File_E_None
== rc
)
99 out
.Handler
->downloadStarted(out
.File
, static_cast<sal_Int64
>(nDownloadSize
));
105 getStringValue(const uno::Reference
< container::XNameAccess
>& xNameAccess
, const OUString
& aName
)
107 OSL_ASSERT(xNameAccess
->hasByName(aName
));
108 uno::Any aValue
= xNameAccess
->getByName(aName
);
110 return OUStringToOString(aValue
.get
<OUString
>(), RTL_TEXTENCODING_UTF8
);
115 getInt32Value(const uno::Reference
< container::XNameAccess
>& xNameAccess
,
116 const OUString
& aName
)
118 OSL_ASSERT(xNameAccess
->hasByName(aName
));
119 uno::Any aValue
= xNameAccess
->getByName(aName
);
128 write_function( void *ptr
, size_t size
, size_t nmemb
, void *stream
)
130 OutData
*out
= static_cast < OutData
* > (stream
);
132 if( nullptr == out
->FileHandle
)
135 sal_uInt64 nBytesWritten
= 0;
137 if( nullptr != out
->FileHandle
)
138 osl_writeFile(out
->FileHandle
, ptr
, size
* nmemb
, &nBytesWritten
);
140 return static_cast<size_t>(nBytesWritten
);
145 progress_callback( void *clientp
, curl_off_t dltotal
, curl_off_t dlnow
, SAL_UNUSED_PARAMETER curl_off_t
, SAL_UNUSED_PARAMETER curl_off_t
)
147 OutData
*out
= static_cast < OutData
* > (clientp
);
151 if (out
&& !out
->StopCondition
.check())
154 if ( dltotal
+ out
->Offset
)
155 fPercent
= (dlnow
+ out
->Offset
) * 100 / (dltotal
+ out
->Offset
);
159 // Do not report progress for redirection replies
161 curl_easy_getinfo(out
->curl
, CURLINFO_RESPONSE_CODE
, &nCode
);
162 if( (nCode
!= 302) && (nCode
!= 303) && (dltotal
> 0) )
163 out
->Handler
->downloadProgressAt(static_cast<sal_Int8
>(fPercent
));
168 // If stop condition is set, return non 0 value to abort
174 Download::getProxyForURL(std::u16string_view rURL
, OString
& rHost
, sal_Int32
& rPort
) const
176 uno::Reference
< lang::XMultiServiceFactory
> xConfigProvider(
177 css::configuration::theDefaultProvider::get( m_xContext
) );
179 beans::PropertyValue aProperty
;
180 aProperty
.Name
= "nodepath";
181 aProperty
.Value
<<= OUString("org.openoffice.Inet/Settings");
183 uno::Sequence
< uno::Any
> aArgumentList
{ uno::Any(aProperty
) };
185 uno::Reference
< container::XNameAccess
> xNameAccess(
186 xConfigProvider
->createInstanceWithArguments(
187 "com.sun.star.configuration.ConfigurationAccess", aArgumentList
),
188 uno::UNO_QUERY_THROW
);
190 OSL_ASSERT(xNameAccess
->hasByName("ooInetProxyType"));
191 uno::Any aValue
= xNameAccess
->getByName("ooInetProxyType");
193 sal_Int32 nProxyType
= aValue
.get
< sal_Int32
>();
194 if( 0 != nProxyType
) // type 0 means "direct connection to the internet
196 if( o3tl::starts_with(rURL
, u
"http:") )
198 rHost
= getStringValue(xNameAccess
, "ooInetHTTPProxyName");
199 rPort
= getInt32Value(xNameAccess
, "ooInetHTTPProxyPort");
201 else if( o3tl::starts_with(rURL
, u
"https:") )
203 rHost
= getStringValue(xNameAccess
, "ooInetHTTPSProxyName");
204 rPort
= getInt32Value(xNameAccess
, "ooInetHTTPSProxyPort");
210 static bool curl_run(std::u16string_view rURL
, OutData
& out
, const OString
& aProxyHost
, sal_Int32 nProxyPort
)
212 /* Need to investigate further whether it is necessary to call
213 * curl_global_init or not - leave it for now (as the ftp UCB content
214 * provider does as well).
217 CURL
* pCURL
= curl_easy_init();
220 if( nullptr != pCURL
)
222 ::InitCurl_easy(pCURL
);
226 OString
aURL(OUStringToOString(rURL
, RTL_TEXTENCODING_UTF8
));
227 (void)curl_easy_setopt(pCURL
, CURLOPT_URL
, aURL
.getStr());
229 // abort on http errors
230 (void)curl_easy_setopt(pCURL
, CURLOPT_FAILONERROR
, 1);
232 // enable redirection
233 (void)curl_easy_setopt(pCURL
, CURLOPT_FOLLOWLOCATION
, 1);
234 // only allow redirect to https://
235 #if (LIBCURL_VERSION_MAJOR > 7) || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 85)
236 (void)curl_easy_setopt(pCURL
, CURLOPT_REDIR_PROTOCOLS_STR
, "https");
238 (void)curl_easy_setopt(pCURL
, CURLOPT_REDIR_PROTOCOLS
, CURLPROTO_HTTPS
);
242 (void)curl_easy_setopt(pCURL
, CURLOPT_WRITEDATA
, &out
);
243 (void)curl_easy_setopt(pCURL
, CURLOPT_WRITEFUNCTION
, &write_function
);
245 // progress handler - Condition::check unfortunately is not defined const
246 (void)curl_easy_setopt(pCURL
, CURLOPT_NOPROGRESS
, 0);
247 (void)curl_easy_setopt(pCURL
, CURLOPT_XFERINFOFUNCTION
, &progress_callback
);
248 (void)curl_easy_setopt(pCURL
, CURLOPT_PROGRESSDATA
, &out
);
251 (void)curl_easy_setopt(pCURL
, CURLOPT_PROXY
, aProxyHost
.getStr());
252 (void)curl_easy_setopt(pCURL
, CURLOPT_PROXYTYPE
, CURLPROXY_HTTP
);
253 if( -1 != nProxyPort
)
254 (void)curl_easy_setopt(pCURL
, CURLOPT_PROXYPORT
, nProxyPort
);
258 // curl_off_t offset = nOffset; libcurl seems to be compiled with large
259 // file support (and we not) ..
260 sal_Int64 offset
= static_cast<sal_Int64
>(out
.Offset
);
261 (void)curl_easy_setopt(pCURL
, CURLOPT_RESUME_FROM_LARGE
, offset
);
264 CURLcode cc
= curl_easy_perform(pCURL
);
266 // treat zero byte downloads as errors
267 if( nullptr == out
.FileHandle
)
272 out
.Handler
->downloadFinished(out
.File
);
276 if ( CURLE_PARTIAL_FILE
== cc
)
278 // this sometimes happens, when a user throws away his user data, but has already
279 // completed the download of an update.
280 curl_off_t nDownloadSize
;
281 curl_easy_getinfo( pCURL
, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
, &nDownloadSize
);
282 if ( -1 == nDownloadSize
)
284 out
.Handler
->downloadFinished(out
.File
);
289 // Avoid target file being removed
290 else if( (CURLE_ABORTED_BY_CALLBACK
== cc
) || out
.StopCondition
.check() )
293 // Only report errors when not stopped
296 OString
aMessage("Unknown error"_ostr
);
298 const char * error_message
= curl_easy_strerror(cc
);
299 if( nullptr != error_message
)
300 aMessage
= error_message
;
302 if ( CURLE_HTTP_RETURNED_ERROR
== cc
)
305 curl_easy_getinfo( pCURL
, CURLINFO_RESPONSE_CODE
, &nError
);
308 aMessage
+= " 403: Access denied!";
309 else if ( 404 == nError
)
310 aMessage
+= " 404: File not found!";
311 else if ( 416 == nError
)
313 // we got this error probably, because we already downloaded the file
314 out
.Handler
->downloadFinished(out
.File
);
319 aMessage
+= ":error code = " + OString::number( nError
) + " !";
323 out
.Handler
->downloadStalled( OStringToOUString(aMessage
, RTL_TEXTENCODING_UTF8
) );
326 curl_easy_cleanup(pCURL
);
334 Download::start(const OUString
& rURL
, const OUString
& rFile
, const OUString
& rDestinationDir
)
336 OSL_ASSERT( m_aHandler
.is() );
338 OutData
out(m_aCondition
);
339 OUString
aFile( rFile
);
341 // when rFile is empty, there is no remembered file name. If there is already a file with the
342 // same name ask the user if she wants to resume a download or restart the download
343 if ( aFile
.isEmpty() )
346 OUString
aURL( rURL
);
347 // ensure no trailing '/'
348 sal_Int32 nLen
= aURL
.getLength();
349 while( (nLen
> 0) && ('/' == aURL
[ nLen
-1 ]) )
350 aURL
= aURL
.copy( 0, --nLen
);
352 // extract file name last '/'
353 sal_Int32 nIndex
= aURL
.lastIndexOf('/');
354 aFile
= rDestinationDir
+ aURL
.subView( nIndex
);
356 // check for existing file
357 oslFileError rc
= osl_openFile( aFile
.pData
, &out
.FileHandle
, osl_File_OpenFlag_Write
| osl_File_OpenFlag_Create
);
358 osl_closeFile(out
.FileHandle
);
359 out
.FileHandle
= nullptr;
361 if( osl_File_E_EXIST
== rc
)
363 if ( m_aHandler
->checkDownloadDestination( aURL
.copy( nIndex
+1 ) ) )
365 osl_removeFile( aFile
.pData
);
369 m_aHandler
->downloadStarted( aFile
, 0 );
373 osl_removeFile( aFile
.pData
);
379 out
.DestinationDir
= rDestinationDir
;
380 out
.Handler
= m_aHandler
;
382 if( !aFile
.isEmpty() )
384 oslFileError rc
= osl_openFile(aFile
.pData
, &out
.FileHandle
, osl_File_OpenFlag_Write
);
386 if( osl_File_E_None
== rc
)
388 // Set file pointer to the end of the file on resume
389 if( osl_File_E_None
== osl_setFilePos(out
.FileHandle
, osl_Pos_End
, 0) )
391 osl_getFilePos(out
.FileHandle
, &out
.Offset
);
394 else if( osl_File_E_NOENT
== rc
) // file has been deleted meanwhile ..
399 sal_Int32 nProxyPort
= -1;
400 getProxyForURL(rURL
, aProxyHost
, nProxyPort
);
402 bool ret
= curl_run(rURL
, out
, aProxyHost
, nProxyPort
);
404 if( nullptr != out
.FileHandle
)
406 osl_syncFile(out
.FileHandle
);
407 osl_closeFile(out
.FileHandle
);
409 // #i90930# Don't remove already downloaded bits, when curl_run reports an error
410 // because later calls might be successful
412 // osl_removeFile(out.File.pData);
415 m_aCondition
.reset();
426 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */