Add protect code for curl handle
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / file-download.cpp
1 /*
2  * Copyright (c) 2017 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // HEADER
19 #include <dali/internal/imaging/common/file-download.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/integration-api/debug.h>
23 #include <pthread.h>
24 #include <curl/curl.h>
25 #include <openssl/crypto.h>
26 #include <cstring>
27
28 // INTERNAL INCLUDES
29 #include <dali/internal/system/common/file-writer.h>
30
31 #ifdef TPK_CURL_ENABLED
32 #include <tpkp_curl.h>
33 #endif // TPK_CURL_ENABLED
34
35 using namespace Dali::Integration;
36
37 namespace Dali
38 {
39
40 namespace TizenPlatform
41 {
42
43 namespace // unnamed namespace
44 {
45
46 const int CONNECTION_TIMEOUT_SECONDS( 30L );
47 const long VERBOSE_MODE = 0L;                // 0 == off, 1 == on
48 const long CLOSE_CONNECTION_ON_ERROR = 1L;   // 0 == off, 1 == on
49 const long EXCLUDE_HEADER = 0L;
50 const long INCLUDE_HEADER = 1L;
51 const long INCLUDE_BODY = 0L;
52 const long EXCLUDE_BODY = 1L;
53
54 /**
55  * Curl library environment. Direct initialize ensures it's constructed before adaptor
56  * or application creates any threads.
57  */
58 static Dali::TizenPlatform::Network::CurlEnvironment gCurlEnvironment;
59
60 void ConfigureCurlOptions( CURL* curlHandle, const std::string& url )
61 {
62   curl_easy_setopt( curlHandle, CURLOPT_URL, url.c_str() );
63   curl_easy_setopt( curlHandle, CURLOPT_VERBOSE, VERBOSE_MODE );
64
65   // CURLOPT_FAILONERROR is not fail-safe especially when authentication is involved ( see manual )
66   // Removed CURLOPT_FAILONERROR option
67   curl_easy_setopt( curlHandle, CURLOPT_CONNECTTIMEOUT, CONNECTION_TIMEOUT_SECONDS );
68   curl_easy_setopt( curlHandle, CURLOPT_HEADER, INCLUDE_HEADER );
69   curl_easy_setopt( curlHandle, CURLOPT_NOBODY, EXCLUDE_BODY );
70
71 #ifdef TPK_CURL_ENABLED
72   // Apply certificate pinning on Tizen
73   curl_easy_setopt( curlHandle, CURLOPT_SSL_CTX_FUNCTION, tpkp_curl_ssl_ctx_callback );
74 #endif // TPK_CURL_ENABLED
75 }
76
77 // Without a write function or a buffer (file descriptor) to write to, curl will pump out
78 // header/body contents to stdout
79 size_t DummyWrite(char *ptr, size_t size, size_t nmemb, void *userdata)
80 {
81   return size * nmemb;
82 }
83
84 struct ChunkData
85 {
86   std::vector< uint8_t > data;
87 };
88
89 size_t ChunkLoader(char *ptr, size_t size, size_t nmemb, void *userdata)
90 {
91   std::vector<ChunkData>* chunks = static_cast<std::vector<ChunkData>*>( userdata );
92   int numBytes = size*nmemb;
93   chunks->push_back( ChunkData() );
94   ChunkData& chunkData = (*chunks)[chunks->size()-1];
95   chunkData.data.reserve( numBytes );
96   memcpy( &chunkData.data[0], ptr, numBytes );
97   return numBytes;
98 }
99
100
101 CURLcode DownloadFileDataWithSize( CURL* curlHandle, Dali::Vector<uint8_t>& dataBuffer, size_t dataSize )
102 {
103   CURLcode result( CURLE_OK );
104
105   // create
106   Dali::Internal::Platform::FileWriter fileWriter( dataBuffer, dataSize );
107   FILE* dataBufferFilePointer = fileWriter.GetFile();
108   if( NULL != dataBufferFilePointer )
109   {
110     // we only want the body which contains the file data
111     curl_easy_setopt( curlHandle, CURLOPT_HEADER, EXCLUDE_HEADER );
112     curl_easy_setopt( curlHandle, CURLOPT_NOBODY, INCLUDE_BODY );
113
114     // disable the write callback, and get curl to write directly into our data buffer
115     curl_easy_setopt( curlHandle, CURLOPT_WRITEFUNCTION, NULL );
116     curl_easy_setopt( curlHandle, CURLOPT_WRITEDATA, dataBufferFilePointer );
117
118     // synchronous request of the body data
119     result = curl_easy_perform( curlHandle );
120   }
121   return result;
122 }
123
124 CURLcode DownloadFileDataByChunk( CURL* curlHandle, Dali::Vector<uint8_t>& dataBuffer, size_t& dataSize )
125 {
126   // create
127   std::vector< ChunkData > chunks;
128
129   // we only want the body which contains the file data
130   curl_easy_setopt( curlHandle, CURLOPT_HEADER, EXCLUDE_HEADER );
131   curl_easy_setopt( curlHandle, CURLOPT_NOBODY, INCLUDE_BODY );
132
133   // Enable the write callback.
134   curl_easy_setopt( curlHandle, CURLOPT_WRITEFUNCTION, ChunkLoader );
135   curl_easy_setopt( curlHandle, CURLOPT_WRITEDATA, &chunks );
136
137   // synchronous request of the body data
138   CURLcode result = curl_easy_perform( curlHandle );
139
140   // chunks should now contain all of the chunked data. Reassemble into a single vector
141   dataSize = 0;
142   for( size_t i=0; i<chunks.size() ; ++i )
143   {
144     dataSize += chunks[i].data.capacity();
145   }
146   dataBuffer.Resize(dataSize);
147
148   size_t offset = 0;
149   for( size_t i=0; i<chunks.size() ; ++i )
150   {
151     memcpy( &dataBuffer[offset], &chunks[i].data[0], chunks[i].data.capacity() );
152     offset += chunks[i].data.capacity();
153   }
154
155   return result;
156 }
157
158 bool DownloadFile( CURL* curlHandle,
159                    const std::string& url,
160                    Dali::Vector<uint8_t>& dataBuffer,
161                    size_t& dataSize,
162                    size_t maximumAllowedSizeBytes )
163 {
164   CURLcode result( CURLE_OK );
165   double size(0);
166
167   // setup curl to download just the header so we can extract the content length
168   ConfigureCurlOptions( curlHandle, url );
169
170   curl_easy_setopt( curlHandle, CURLOPT_WRITEFUNCTION, DummyWrite);
171
172   // perform the request to get the header
173   result = curl_easy_perform( curlHandle );
174
175   if( result != CURLE_OK)
176   {
177     DALI_LOG_ERROR( "Failed to download http header for \"%s\" with error code %d\n", url.c_str(), result );
178     return false;
179   }
180
181   // get the content length, -1 == size is not known
182   curl_easy_getinfo( curlHandle,CURLINFO_CONTENT_LENGTH_DOWNLOAD , &size );
183
184
185   if( size >= maximumAllowedSizeBytes )
186   {
187     DALI_LOG_ERROR( "File content length %f > max allowed %zu \"%s\" \n", size, maximumAllowedSizeBytes, url.c_str() );
188     return false;
189   }
190   else if( size > 0 )
191   {
192     // If we know the size up front, allocate once and avoid chunk copies.
193     dataSize = static_cast<size_t>( size );
194     result = DownloadFileDataWithSize( curlHandle, dataBuffer, dataSize );
195   }
196   else
197   {
198     result = DownloadFileDataByChunk( curlHandle, dataBuffer, dataSize );
199   }
200
201   if( result != CURLE_OK )
202   {
203     DALI_LOG_ERROR( "Failed to download image file \"%s\" with error code %d\n", url.c_str(), result );
204     return false;
205   }
206   return true;
207 }
208
209
210 } // unnamed namespace
211
212
213 namespace Network
214 {
215
216 std::mutex* CurlEnvironment::mMutexs = NULL;
217
218 CurlEnvironment::CurlEnvironment()
219 {
220   // Must be called before we attempt any loads. e.g. by using curl_easy_init()
221   // and before we start any threads.
222   curl_global_init(CURL_GLOBAL_ALL);
223
224  // libcurl with openssl needs locking_function and thread id for threadsafe
225  // https://curl.haxx.se/libcurl/c/threadsafe.html
226  // https://www.openssl.org/docs/man1.0.2/crypto/threads.html#DESCRIPTION
227  // SetLockingFunction sets locking_function and get thread id by the guide.
228   SetLockingFunction();
229 }
230
231 CurlEnvironment::~CurlEnvironment()
232 {
233   UnsetLockingFunction();
234
235   curl_global_cleanup();
236 }
237
238 // libcurl with openssl needs locking_function and thread id for threadsafe
239 // https://curl.haxx.se/libcurl/c/threadsafe.html
240 // https://www.openssl.org/docs/man1.0.2/crypto/threads.html#DESCRIPTION
241 void CurlEnvironment::OnOpenSSLLocking( int mode, int n, const char* file, int line )
242 {
243   if( mode & CRYPTO_LOCK )
244   {
245     mMutexs[n].lock();
246   }
247   else
248   {
249     mMutexs[n].unlock();
250   }
251 }
252
253 unsigned long CurlEnvironment::GetThreadId()
254 {
255   // If dali uses c++ thread, we may replace pthread_self() to this_thread::get_id()
256   return static_cast< unsigned long >( pthread_self() );
257 }
258
259 void CurlEnvironment::SetLockingFunction()
260 {
261   if( mMutexs != NULL )
262   {
263     return;
264   }
265
266   mMutexs = new std::mutex[ CRYPTO_num_locks() ];
267
268   CRYPTO_set_id_callback( &CurlEnvironment::GetThreadId );
269   CRYPTO_set_locking_callback( &CurlEnvironment::OnOpenSSLLocking );
270 }
271
272 void CurlEnvironment::UnsetLockingFunction()
273 {
274   if( mMutexs == NULL )
275   {
276     return;
277   }
278
279   CRYPTO_set_id_callback( NULL );
280   CRYPTO_set_locking_callback( NULL );
281   delete [] mMutexs;
282   mMutexs = NULL;
283 }
284
285 bool DownloadRemoteFileIntoMemory( const std::string& url,
286                                    Dali::Vector<uint8_t>& dataBuffer,
287                                    size_t& dataSize,
288                                    size_t maximumAllowedSizeBytes )
289 {
290   bool result = false;
291
292   if( url.empty() )
293   {
294     DALI_LOG_WARNING("empty url requested \n");
295     return false;
296   }
297
298   // start a libcurl easy session, this internally calls curl_global_init, if we ever have more than one download
299   // thread we need to explicity call curl_global_init() on startup from a single thread.
300
301   CURL* curlHandle = curl_easy_init();
302   if ( curlHandle )
303   {
304     result = DownloadFile( curlHandle, url, dataBuffer,  dataSize, maximumAllowedSizeBytes);
305
306     // clean up session
307     curl_easy_cleanup( curlHandle );
308
309 #ifdef TPK_CURL_ENABLED
310     // Clean up tpkp(the module for certificate pinning) resources on Tizen
311     tpkp_curl_cleanup();
312 #endif // TPK_CURL_ENABLED
313   }
314   return result;
315 }
316
317 } // namespace Network
318
319 } // namespace TizenPlatform
320
321 } // namespace Dali