LibreOffice Module extensions (master) 1
download.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20#include <sal/config.h>
21
22#include <string_view>
23
24#include <curl/curl.h>
25
26#include <o3tl/string_view.hxx>
27#include <osl/diagnose.h>
28#include <osl/file.h>
29#include <com/sun/star/beans/PropertyValue.hpp>
30#include <com/sun/star/configuration/theDefaultProvider.hpp>
31#include <com/sun/star/container/XNameAccess.hpp>
32#include <com/sun/star/lang/XMultiServiceFactory.hpp>
33
34#include "download.hxx"
35
36namespace beans = com::sun::star::beans ;
37namespace container = com::sun::star::container ;
38namespace lang = com::sun::star::lang ;
39namespace uno = com::sun::star::uno ;
40
41namespace {
42
43struct OutData
44{
46 OUString File;
47 OUString DestinationDir;
48 oslFileHandle FileHandle;
49 sal_uInt64 Offset;
50 osl::Condition& StopCondition;
51 CURL *curl;
52
53 explicit OutData(osl::Condition& rCondition) : FileHandle(nullptr), Offset(0), StopCondition(rCondition), curl(nullptr) {};
54};
55
56}
57
58static void openFile( OutData& out )
59{
60 char * effective_url;
61 curl_easy_getinfo(out.curl, CURLINFO_EFFECTIVE_URL, &effective_url);
62
63 curl_off_t nDownloadSize;
64 curl_easy_getinfo(out.curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &nDownloadSize);
65
66 OString aURL(effective_url);
67
68 // ensure no trailing '/'
69 sal_Int32 nLen = aURL.getLength();
70 while( (nLen > 0) && ('/' == aURL[nLen-1]) )
71 aURL = aURL.copy(0, --nLen);
72
73 // extract file name last '/'
74 sal_Int32 nIndex = aURL.lastIndexOf('/');
75 if( nIndex > 0 )
76 {
77 out.File = out.DestinationDir
78 + OStringToOUString(aURL.subView(nIndex), RTL_TEXTENCODING_UTF8);
79
80 oslFileError rc;
81
82 // Give the user an overwrite warning if the target file exists
83 const sal_Int32 openFlags = osl_File_OpenFlag_Write | osl_File_OpenFlag_Create;
84 do
85 {
86 rc = osl_openFile(out.File.pData, &out.FileHandle, openFlags);
87
88 if( osl_File_E_EXIST == rc && ! out.Handler->downloadTargetExists(out.File) )
89 {
90 out.StopCondition.set();
91 break;
92 }
93
94 } while( osl_File_E_EXIST == rc );
95
96 if( osl_File_E_None == rc )
97 out.Handler->downloadStarted(out.File, static_cast<sal_Int64>(nDownloadSize));
98 }
99}
100
101
102static OString
103getStringValue(const uno::Reference< container::XNameAccess >& xNameAccess, const OUString& aName)
104{
105 OSL_ASSERT(xNameAccess->hasByName(aName));
106 uno::Any aValue = xNameAccess->getByName(aName);
107
108 return OUStringToOString(aValue.get<OUString>(), RTL_TEXTENCODING_UTF8);
109}
110
111
112static sal_Int32
113getInt32Value(const uno::Reference< container::XNameAccess >& xNameAccess,
114 const OUString& aName)
115{
116 OSL_ASSERT(xNameAccess->hasByName(aName));
117 uno::Any aValue = xNameAccess->getByName(aName);
118
119 sal_Int32 n = -1;
120 aValue >>= n;
121 return n;
122}
123
124
125static size_t
126write_function( void *ptr, size_t size, size_t nmemb, void *stream )
127{
128 OutData *out = static_cast < OutData * > (stream);
129
130 if( nullptr == out->FileHandle )
131 openFile(*out);
132
133 sal_uInt64 nBytesWritten = 0;
134
135 if( nullptr != out->FileHandle )
136 osl_writeFile(out->FileHandle, ptr, size * nmemb, &nBytesWritten);
137
138 return static_cast<size_t>(nBytesWritten);
139}
140
141
142static int
143progress_callback( void *clientp, curl_off_t dltotal, curl_off_t dlnow, SAL_UNUSED_PARAMETER curl_off_t, SAL_UNUSED_PARAMETER curl_off_t)
144{
145 OutData *out = static_cast < OutData * > (clientp);
146
147 assert(out);
148
149 if (out && !out->StopCondition.check())
150 {
151 float fPercent = 0;
152 if ( dltotal + out->Offset )
153 fPercent = (dlnow + out->Offset) * 100 / (dltotal + out->Offset);
154 if( fPercent < 0 )
155 fPercent = 0;
156
157 // Do not report progress for redirection replies
158 long nCode;
159 curl_easy_getinfo(out->curl, CURLINFO_RESPONSE_CODE, &nCode);
160 if( (nCode != 302) && (nCode != 303) && (dltotal > 0) )
161 out->Handler->downloadProgressAt(static_cast<sal_Int8>(fPercent));
162
163 return 0;
164 }
165
166 // If stop condition is set, return non 0 value to abort
167 return -1;
168}
169
170
171void
172Download::getProxyForURL(std::u16string_view rURL, OString& rHost, sal_Int32& rPort) const
173{
174 uno::Reference< lang::XMultiServiceFactory > xConfigProvider(
175 css::configuration::theDefaultProvider::get( m_xContext ) );
176
177 beans::PropertyValue aProperty;
178 aProperty.Name = "nodepath";
179 aProperty.Value <<= OUString("org.openoffice.Inet/Settings");
180
181 uno::Sequence< uno::Any > aArgumentList{ uno::Any(aProperty) };
182
183 uno::Reference< container::XNameAccess > xNameAccess(
184 xConfigProvider->createInstanceWithArguments(
185 "com.sun.star.configuration.ConfigurationAccess", aArgumentList ),
186 uno::UNO_QUERY_THROW );
187
188 OSL_ASSERT(xNameAccess->hasByName("ooInetProxyType"));
189 uno::Any aValue = xNameAccess->getByName("ooInetProxyType");
190
191 sal_Int32 nProxyType = aValue.get< sal_Int32 >();
192 if( 0 != nProxyType ) // type 0 means "direct connection to the internet
193 {
194 if( o3tl::starts_with(rURL, u"http:") )
195 {
196 rHost = getStringValue(xNameAccess, "ooInetHTTPProxyName");
197 rPort = getInt32Value(xNameAccess, "ooInetHTTPProxyPort");
198 }
199 else if( o3tl::starts_with(rURL, u"https:") )
200 {
201 rHost = getStringValue(xNameAccess, "ooInetHTTPSProxyName");
202 rPort = getInt32Value(xNameAccess, "ooInetHTTPSProxyPort");
203 }
204 else if( o3tl::starts_with(rURL, u"ftp:") )
205 {
206 rHost = getStringValue(xNameAccess, "ooInetFTPProxyName");
207 rPort = getInt32Value(xNameAccess, "ooInetFTPProxyPort");
208 }
209 }
210}
211
212
213static bool curl_run(std::u16string_view rURL, OutData& out, const OString& aProxyHost, sal_Int32 nProxyPort)
214{
215 /* Need to investigate further whether it is necessary to call
216 * curl_global_init or not - leave it for now (as the ftp UCB content
217 * provider does as well).
218 */
219
220 CURL * pCURL = curl_easy_init();
221 bool ret = false;
222
223 if( nullptr != pCURL )
224 {
225 out.curl = pCURL;
226
227 OString aURL(OUStringToOString(rURL, RTL_TEXTENCODING_UTF8));
228 (void)curl_easy_setopt(pCURL, CURLOPT_URL, aURL.getStr());
229
230 // abort on http errors
231 (void)curl_easy_setopt(pCURL, CURLOPT_FAILONERROR, 1);
232
233 // enable redirection
234 (void)curl_easy_setopt(pCURL, CURLOPT_FOLLOWLOCATION, 1);
235 // only allow redirect to https://
236#if (LIBCURL_VERSION_MAJOR > 7) || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 85)
237 (void)curl_easy_setopt(pCURL, CURLOPT_REDIR_PROTOCOLS_STR, "https");
238#else
239 (void)curl_easy_setopt(pCURL, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
240#endif
241
242 // write function
243 (void)curl_easy_setopt(pCURL, CURLOPT_WRITEDATA, &out);
244 (void)curl_easy_setopt(pCURL, CURLOPT_WRITEFUNCTION, &write_function);
245
246 // progress handler - Condition::check unfortunately is not defined const
247 (void)curl_easy_setopt(pCURL, CURLOPT_NOPROGRESS, 0);
248 (void)curl_easy_setopt(pCURL, CURLOPT_XFERINFOFUNCTION, &progress_callback);
249 (void)curl_easy_setopt(pCURL, CURLOPT_PROGRESSDATA, &out);
250
251 // proxy
252 (void)curl_easy_setopt(pCURL, CURLOPT_PROXY, aProxyHost.getStr());
253 (void)curl_easy_setopt(pCURL, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
254 if( -1 != nProxyPort )
255 (void)curl_easy_setopt(pCURL, CURLOPT_PROXYPORT, nProxyPort);
256
257 if( out.Offset > 0 )
258 {
259 // curl_off_t offset = nOffset; libcurl seems to be compiled with large
260 // file support (and we not) ..
261 sal_Int64 offset = static_cast<sal_Int64>(out.Offset);
262 (void)curl_easy_setopt(pCURL, CURLOPT_RESUME_FROM_LARGE, offset);
263 }
264
265 CURLcode cc = curl_easy_perform(pCURL);
266
267 // treat zero byte downloads as errors
268 if( nullptr == out.FileHandle )
269 openFile(out);
270
271 if( CURLE_OK == cc )
272 {
273 out.Handler->downloadFinished(out.File);
274 ret = true;
275 }
276
277 if ( CURLE_PARTIAL_FILE == cc )
278 {
279 // this sometimes happens, when a user throws away his user data, but has already
280 // completed the download of an update.
281 curl_off_t nDownloadSize;
282 curl_easy_getinfo( pCURL, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &nDownloadSize );
283 if ( -1 == nDownloadSize )
284 {
285 out.Handler->downloadFinished(out.File);
286 ret = true;
287 }
288 }
289
290 // Avoid target file being removed
291 else if( (CURLE_ABORTED_BY_CALLBACK == cc) || out.StopCondition.check() )
292 ret = true;
293
294 // Only report errors when not stopped
295 else
296 {
297 OString aMessage("Unknown error");
298
299 const char * error_message = curl_easy_strerror(cc);
300 if( nullptr != error_message )
301 aMessage = error_message;
302
303 if ( CURLE_HTTP_RETURNED_ERROR == cc )
304 {
305 long nError;
306 curl_easy_getinfo( pCURL, CURLINFO_RESPONSE_CODE, &nError );
307
308 if ( 403 == nError )
309 aMessage += " 403: Access denied!";
310 else if ( 404 == nError )
311 aMessage += " 404: File not found!";
312 else if ( 416 == nError )
313 {
314 // we got this error probably, because we already downloaded the file
315 out.Handler->downloadFinished(out.File);
316 ret = true;
317 }
318 else
319 {
320 aMessage += ":error code = " + OString::number( nError ) + " !";
321 }
322 }
323 if ( !ret )
324 out.Handler->downloadStalled( OStringToOUString(aMessage, RTL_TEXTENCODING_UTF8) );
325 }
326
327 curl_easy_cleanup(pCURL);
328 }
329
330 return ret;
331}
332
333
334bool
335Download::start(const OUString& rURL, const OUString& rFile, const OUString& rDestinationDir)
336{
337 OSL_ASSERT( m_aHandler.is() );
338
339 OutData out(m_aCondition);
340 OUString aFile( rFile );
341
342 // when rFile is empty, there is no remembered file name. If there is already a file with the
343 // same name ask the user if she wants to resume a download or restart the download
344 if ( aFile.isEmpty() )
345 {
346 // GetFileName()
347 OUString aURL( rURL );
348 // ensure no trailing '/'
349 sal_Int32 nLen = aURL.getLength();
350 while( (nLen > 0) && ('/' == aURL[ nLen-1 ]) )
351 aURL = aURL.copy( 0, --nLen );
352
353 // extract file name last '/'
354 sal_Int32 nIndex = aURL.lastIndexOf('/');
355 aFile = rDestinationDir + aURL.subView( nIndex );
356
357 // check for existing file
358 oslFileError rc = osl_openFile( aFile.pData, &out.FileHandle, osl_File_OpenFlag_Write | osl_File_OpenFlag_Create );
359 osl_closeFile(out.FileHandle);
360 out.FileHandle = nullptr;
361
362 if( osl_File_E_EXIST == rc )
363 {
364 if ( m_aHandler->checkDownloadDestination( aURL.copy( nIndex+1 ) ) )
365 {
366 osl_removeFile( aFile.pData );
367 aFile.clear();
368 }
369 else
370 m_aHandler->downloadStarted( aFile, 0 );
371 }
372 else
373 {
374 osl_removeFile( aFile.pData );
375 aFile.clear();
376 }
377 }
378
379 out.File = aFile;
380 out.DestinationDir = rDestinationDir;
381 out.Handler = m_aHandler;
382
383 if( !aFile.isEmpty() )
384 {
385 oslFileError rc = osl_openFile(aFile.pData, &out.FileHandle, osl_File_OpenFlag_Write);
386
387 if( osl_File_E_None == rc )
388 {
389 // Set file pointer to the end of the file on resume
390 if( osl_File_E_None == osl_setFilePos(out.FileHandle, osl_Pos_End, 0) )
391 {
392 osl_getFilePos(out.FileHandle, &out.Offset);
393 }
394 }
395 else if( osl_File_E_NOENT == rc ) // file has been deleted meanwhile ..
396 out.File.clear();
397 }
398
399 OString aProxyHost;
400 sal_Int32 nProxyPort = -1;
401 getProxyForURL(rURL, aProxyHost, nProxyPort);
402
403 bool ret = curl_run(rURL, out, aProxyHost, nProxyPort);
404
405 if( nullptr != out.FileHandle )
406 {
407 osl_syncFile(out.FileHandle);
408 osl_closeFile(out.FileHandle);
409
410// #i90930# Don't remove already downloaded bits, when curl_run reports an error
411// because later calls might be successful
412// if( ! ret )
413// osl_removeFile(out.File.pData);
414 }
415
416 m_aCondition.reset();
417 return ret;
418}
419
420
421void
423{
424 m_aCondition.set();
425}
426
427/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
bool start(const OUString &rURL, const OUString &rFile, const OUString &rDestinationDir)
Definition: download.cxx:335
void getProxyForURL(std::u16string_view rURL, OString &rHost, sal_Int32 &rPort) const
Definition: download.cxx:172
const rtl::Reference< DownloadInteractionHandler > m_aHandler
Definition: download.hxx:77
const css::uno::Reference< css::uno::XComponentContext > & m_xContext
Definition: download.hxx:76
osl::Condition m_aCondition
Definition: download.hxx:75
void stop()
Definition: download.cxx:422
URL aURL
Reference< XOutputStream > stream
static bool curl_run(std::u16string_view rURL, OutData &out, const OString &aProxyHost, sal_Int32 nProxyPort)
Definition: download.cxx:213
static sal_Int32 getInt32Value(const uno::Reference< container::XNameAccess > &xNameAccess, const OUString &aName)
Definition: download.cxx:113
static int progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, SAL_UNUSED_PARAMETER curl_off_t, SAL_UNUSED_PARAMETER curl_off_t)
Definition: download.cxx:143
static void openFile(OutData &out)
Definition: download.cxx:58
static size_t write_function(void *ptr, size_t size, size_t nmemb, void *stream)
Definition: download.cxx:126
static OString getStringValue(const uno::Reference< container::XNameAccess > &xNameAccess, const OUString &aName)
Definition: download.cxx:103
float u
sal_Int32 nIndex
OUString aName
sal_Int64 n
size
constexpr bool starts_with(std::basic_string_view< charT, traits > sv, std::basic_string_view< charT, traits > x) noexcept
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
signed char sal_Int8