[Tizen] Workaround for remote image download
[platform/core/uifw/dali-adaptor.git] / platform-abstractions / tizen / resource-loader / network / 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 "file-download.h"
20
21 // EXTERNAL INCLUDES
22 #include <dali/integration-api/debug.h>
23 #include <curl/curl.h>
24 #include <cstring>
25
26 // INTERNAL INCLUDES
27 #include "portable/file-closer.h"
28
29 #ifdef TPK_CURL_ENABLED
30 #include <tpkp_curl.h>
31 #endif // TPK_CURL_ENABLED
32
33 using namespace Dali::Integration;
34
35 namespace Dali
36 {
37
38 namespace TizenPlatform
39 {
40
41 namespace // unnamed namespace
42 {
43
44 const int CONNECTION_TIMEOUT_SECONDS( 30L );
45 const long VERBOSE_MODE = 0L;                // 0 == off, 1 == on
46 const long CLOSE_CONNECTION_ON_ERROR = 1L;   // 0 == off, 1 == on
47 const long EXCLUDE_HEADER = 0L;
48 const long INCLUDE_HEADER = 1L;
49 const long INCLUDE_BODY = 0L;
50 const long EXCLUDE_BODY = 1L;
51
52 /**
53  * Curl library environment. Direct initialize ensures it's constructed before adaptor
54  * or application creates any threads.
55  */
56 static Dali::TizenPlatform::Network::CurlEnvironment gCurlEnvironment;
57
58
59 void ConfigureCurlOptions( CURL* curlHandle, const std::string& url )
60 {
61   curl_easy_setopt( curlHandle, CURLOPT_URL, url.c_str() );
62   curl_easy_setopt( curlHandle, CURLOPT_VERBOSE, VERBOSE_MODE );
63
64   // CURLOPT_FAILONERROR is not fail-safe especially when authentication is involved ( see manual )
65   curl_easy_setopt( curlHandle, CURLOPT_FAILONERROR, CLOSE_CONNECTION_ON_ERROR );
66   curl_easy_setopt( curlHandle, CURLOPT_CONNECTTIMEOUT, CONNECTION_TIMEOUT_SECONDS );
67   curl_easy_setopt( curlHandle, CURLOPT_HEADER, INCLUDE_HEADER );
68   curl_easy_setopt( curlHandle, CURLOPT_NOBODY, EXCLUDE_BODY );
69
70 #ifdef TPK_CURL_ENABLED
71   // Apply certificate pinning on Tizen
72   curl_easy_setopt( curlHandle, CURLOPT_SSL_CTX_FUNCTION, tpkp_curl_ssl_ctx_callback );
73 #endif // TPK_CURL_ENABLED
74 }
75
76 // Without a write function or a buffer (file descriptor) to write to, curl will pump out
77 // header/body contents to stdout
78 size_t DummyWrite(char *ptr, size_t size, size_t nmemb, void *userdata)
79 {
80   return size * nmemb;
81 }
82
83 struct ChunkData
84 {
85   std::vector< uint8_t > data;
86 };
87
88 size_t ChunkLoader(char *ptr, size_t size, size_t nmemb, void *userdata)
89 {
90   std::vector<ChunkData>* chunks = static_cast<std::vector<ChunkData>*>( userdata );
91   int numBytes = size*nmemb;
92   chunks->push_back( ChunkData() );
93   ChunkData& chunkData = (*chunks)[chunks->size()-1];
94   chunkData.data.reserve( numBytes );
95   memcpy( &chunkData.data[0], ptr, numBytes );
96   return numBytes;
97 }
98
99
100 CURLcode DownloadFileDataWithSize( CURL* curlHandle, Dali::Vector<uint8_t>& dataBuffer, size_t dataSize )
101 {
102   CURLcode result( CURLE_OK );
103   dataBuffer.Resize( dataSize+4);
104
105   // create
106   Dali::Internal::Platform::FileCloser fileCloser( static_cast<void*>(&dataBuffer[0]), dataSize+4, "wb" );
107   FILE* dataBufferFilePointer = fileCloser.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_WARNING( "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_WARNING( "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     dataSize = static_cast<size_t>( size );
193     result = DownloadFileDataWithSize( curlHandle, dataBuffer, dataSize );
194   }
195   else
196   {
197     result = DownloadFileDataByChunk( curlHandle, dataBuffer, dataSize );
198   }
199
200   if( result != CURLE_OK )
201   {
202     DALI_LOG_WARNING( "Failed to download image file \"%s\" with error code %d\n", url.c_str(), result );
203     return false;
204   }
205   return true;
206 }
207
208
209 } // unnamed namespace
210
211
212 namespace Network
213 {
214
215 CurlEnvironment::CurlEnvironment()
216 {
217   // Must be called before we attempt any loads. e.g. by using curl_easy_init()
218   // and before we start any threads.
219   curl_global_init(CURL_GLOBAL_ALL);
220 }
221
222 CurlEnvironment::~CurlEnvironment()
223 {
224   curl_global_cleanup();
225 }
226
227
228 bool DownloadRemoteFileIntoMemory( const std::string& url,
229                                    Dali::Vector<uint8_t>& dataBuffer,
230                                    size_t& dataSize,
231                                    size_t maximumAllowedSizeBytes )
232 {
233   if( url.empty() )
234   {
235     DALI_LOG_WARNING("empty url requested \n");
236     return false;
237   }
238
239   // start a libcurl easy session, this internally calls curl_global_init, if we ever have more than one download
240   // thread we need to explicity call curl_global_init() on startup from a single thread.
241
242   CURL* curlHandle = curl_easy_init();
243
244   bool result = DownloadFile( curlHandle, url, dataBuffer,  dataSize, maximumAllowedSizeBytes);
245
246   // clean up session
247   curl_easy_cleanup( curlHandle );
248
249 #ifdef TPK_CURL_ENABLED
250   // Clean up tpkp(the module for certificate pinning) resources on Tizen
251   tpkp_curl_cleanup();
252 #endif // TPK_CURL_ENABLED
253
254   return result;
255 }
256
257 } // namespace Network
258
259 } // namespace TizenPlatform
260
261 } // namespace Dali