X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;ds=sidebyside;f=platform-abstractions%2Ftizen%2Fresource-loader%2Fnetwork%2Ffile-download.cpp;h=b276678350c90782e76cfef54203ed5f41d41878;hb=b1f227365a03d621f6c5bf464d8ec8e601ec979f;hp=362fb7f13a695c2105cbb9564ce551f04ea00724;hpb=9a4af4b84c8bb19105d5e8c30faf4120d6c9cc8a;p=platform%2Fcore%2Fuifw%2Fdali-adaptor.git diff --git a/platform-abstractions/tizen/resource-loader/network/file-download.cpp b/platform-abstractions/tizen/resource-loader/network/file-download.cpp index 362fb7f..b276678 100755 --- a/platform-abstractions/tizen/resource-loader/network/file-download.cpp +++ b/platform-abstractions/tizen/resource-loader/network/file-download.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,14 +20,17 @@ // EXTERNAL INCLUDES #include +#include #include +#include +#include // INTERNAL INCLUDES -#include "portable/file-closer.h" +#include "portable/file-writer.h" -#ifndef DALI_PROFILE_UBUNTU +#ifdef TPK_CURL_ENABLED #include -#endif // DALI_PROFILE_UBUNTU +#endif // TPK_CURL_ENABLED using namespace Dali::Integration; @@ -48,21 +51,27 @@ const long INCLUDE_HEADER = 1L; const long INCLUDE_BODY = 0L; const long EXCLUDE_BODY = 1L; -void ConfigureCurlOptions( CURL* curl_handle, const std::string& url ) +/** + * Curl library environment. Direct initialize ensures it's constructed before adaptor + * or application creates any threads. + */ +static Dali::TizenPlatform::Network::CurlEnvironment gCurlEnvironment; + +void ConfigureCurlOptions( CURL* curlHandle, const std::string& url ) { - curl_easy_setopt( curl_handle, CURLOPT_URL, url.c_str() ); - curl_easy_setopt( curl_handle, CURLOPT_VERBOSE, VERBOSE_MODE ); + curl_easy_setopt( curlHandle, CURLOPT_URL, url.c_str() ); + curl_easy_setopt( curlHandle, CURLOPT_VERBOSE, VERBOSE_MODE ); // CURLOPT_FAILONERROR is not fail-safe especially when authentication is involved ( see manual ) - curl_easy_setopt( curl_handle, CURLOPT_FAILONERROR, CLOSE_CONNECTION_ON_ERROR ); - curl_easy_setopt( curl_handle, CURLOPT_CONNECTTIMEOUT, CONNECTION_TIMEOUT_SECONDS ); - curl_easy_setopt( curl_handle, CURLOPT_HEADER, INCLUDE_HEADER ); - curl_easy_setopt( curl_handle, CURLOPT_NOBODY, EXCLUDE_BODY ); + // Removed CURLOPT_FAILONERROR option + curl_easy_setopt( curlHandle, CURLOPT_CONNECTTIMEOUT, CONNECTION_TIMEOUT_SECONDS ); + curl_easy_setopt( curlHandle, CURLOPT_HEADER, INCLUDE_HEADER ); + curl_easy_setopt( curlHandle, CURLOPT_NOBODY, EXCLUDE_BODY ); -#ifndef DALI_PROFILE_UBUNTU +#ifdef TPK_CURL_ENABLED // Apply certificate pinning on Tizen - curl_easy_setopt( curl_handle, CURLOPT_SSL_CTX_FUNCTION, tpkp_curl_ssl_ctx_callback ); -#endif // DALI_PROFILE_UBUNTU + curl_easy_setopt( curlHandle, CURLOPT_SSL_CTX_FUNCTION, tpkp_curl_ssl_ctx_callback ); +#endif // TPK_CURL_ENABLED } // Without a write function or a buffer (file descriptor) to write to, curl will pump out @@ -72,80 +81,211 @@ size_t DummyWrite(char *ptr, size_t size, size_t nmemb, void *userdata) return size * nmemb; } +struct ChunkData +{ + std::vector< uint8_t > data; +}; + +size_t ChunkLoader(char *ptr, size_t size, size_t nmemb, void *userdata) +{ + std::vector* chunks = static_cast*>( userdata ); + int numBytes = size*nmemb; + chunks->push_back( ChunkData() ); + ChunkData& chunkData = (*chunks)[chunks->size()-1]; + chunkData.data.reserve( numBytes ); + memcpy( &chunkData.data[0], ptr, numBytes ); + return numBytes; +} + + +CURLcode DownloadFileDataWithSize( CURL* curlHandle, Dali::Vector& dataBuffer, size_t dataSize ) +{ + CURLcode result( CURLE_OK ); + + // create + Dali::Internal::Platform::FileWriter fileWriter( dataBuffer, dataSize ); + FILE* dataBufferFilePointer = fileWriter.GetFile(); + if( NULL != dataBufferFilePointer ) + { + // we only want the body which contains the file data + curl_easy_setopt( curlHandle, CURLOPT_HEADER, EXCLUDE_HEADER ); + curl_easy_setopt( curlHandle, CURLOPT_NOBODY, INCLUDE_BODY ); + + // disable the write callback, and get curl to write directly into our data buffer + curl_easy_setopt( curlHandle, CURLOPT_WRITEFUNCTION, NULL ); + curl_easy_setopt( curlHandle, CURLOPT_WRITEDATA, dataBufferFilePointer ); + + // synchronous request of the body data + result = curl_easy_perform( curlHandle ); + } + return result; +} + +CURLcode DownloadFileDataByChunk( CURL* curlHandle, Dali::Vector& dataBuffer, size_t& dataSize ) +{ + // create + std::vector< ChunkData > chunks; + + // we only want the body which contains the file data + curl_easy_setopt( curlHandle, CURLOPT_HEADER, EXCLUDE_HEADER ); + curl_easy_setopt( curlHandle, CURLOPT_NOBODY, INCLUDE_BODY ); + + // Enable the write callback. + curl_easy_setopt( curlHandle, CURLOPT_WRITEFUNCTION, ChunkLoader ); + curl_easy_setopt( curlHandle, CURLOPT_WRITEDATA, &chunks ); + + // synchronous request of the body data + CURLcode result = curl_easy_perform( curlHandle ); -bool DownloadFile( CURL* curl_handle, + // chunks should now contain all of the chunked data. Reassemble into a single vector + dataSize = 0; + for( size_t i=0; i& dataBuffer, size_t& dataSize, size_t maximumAllowedSizeBytes ) { - CURLcode res( CURLE_OK ); + CURLcode result( CURLE_OK ); double size(0); // setup curl to download just the header so we can extract the content length - ConfigureCurlOptions( curl_handle, url ); + ConfigureCurlOptions( curlHandle, url ); - curl_easy_setopt( curl_handle, CURLOPT_WRITEFUNCTION, DummyWrite); + curl_easy_setopt( curlHandle, CURLOPT_WRITEFUNCTION, DummyWrite); // perform the request to get the header - res = curl_easy_perform( curl_handle ); + result = curl_easy_perform( curlHandle ); - if( res != CURLE_OK) + if( result != CURLE_OK) { - DALI_LOG_WARNING( "Failed to download http header for \"%s\" with error code %d\n", url.c_str(), res ); + DALI_LOG_WARNING( "Failed to download http header for \"%s\" with error code %d\n", url.c_str(), result ); return false; } // get the content length, -1 == size is not known - curl_easy_getinfo( curl_handle,CURLINFO_CONTENT_LENGTH_DOWNLOAD , &size ); + curl_easy_getinfo( curlHandle,CURLINFO_CONTENT_LENGTH_DOWNLOAD , &size ); + - if( size < 1 ) + if( size >= maximumAllowedSizeBytes ) { - DALI_LOG_WARNING( "Header missing content length \"%s\" \n", url.c_str() ); + DALI_LOG_WARNING( "File content length %f > max allowed %zu \"%s\" \n", size, maximumAllowedSizeBytes, url.c_str() ); return false; } - if( size >= maximumAllowedSizeBytes ) + else if( size > 0 ) { - DALI_LOG_WARNING( "File content length %f > max allowed %zu \"%s\" \n", size, maximumAllowedSizeBytes, url.c_str() ); + // If we know the size up front, allocate once and avoid chunk copies. + dataSize = static_cast( size ); + result = DownloadFileDataWithSize( curlHandle, dataBuffer, dataSize ); + } + else + { + result = DownloadFileDataByChunk( curlHandle, dataBuffer, dataSize ); + } + + if( result != CURLE_OK ) + { + DALI_LOG_WARNING( "Failed to download image file \"%s\" with error code %d\n", url.c_str(), result ); return false; } + return true; +} - dataSize = static_cast( size ); - dataBuffer.Resize( dataSize ); +} // unnamed namespace - // create - Dali::Internal::Platform::FileCloser fileCloser( static_cast(&dataBuffer[0]), dataSize, "wb" ); - FILE* dataBufferFilePointer = fileCloser.GetFile(); - if( NULL != dataBufferFilePointer ) - { - // we only want the body which contains the file data - curl_easy_setopt( curl_handle, CURLOPT_HEADER, EXCLUDE_HEADER ); - curl_easy_setopt( curl_handle, CURLOPT_NOBODY, INCLUDE_BODY ); - // disable the write callback, and get curl to write directly into our data buffer - curl_easy_setopt( curl_handle, CURLOPT_WRITEFUNCTION, NULL ); - curl_easy_setopt( curl_handle, CURLOPT_WRITEDATA, dataBufferFilePointer ); +namespace Network +{ - // synchronous request of the body data - res = curl_easy_perform( curl_handle ); +std::mutex* CurlEnvironment::mMutexs = NULL; + +CurlEnvironment::CurlEnvironment() +{ + // Must be called before we attempt any loads. e.g. by using curl_easy_init() + // and before we start any threads. + curl_global_init(CURL_GLOBAL_ALL); + + // libcurl with openssl needs locking_function and thread id for threadsafe + // https://curl.haxx.se/libcurl/c/threadsafe.html + // https://www.openssl.org/docs/man1.0.2/crypto/threads.html#DESCRIPTION + // SetLockingFunction sets locking_function and get thread id by the guide. + SetLockingFunction(); +} + +CurlEnvironment::~CurlEnvironment() +{ + UnsetLockingFunction(); + + curl_global_cleanup(); +} - if( CURLE_OK != res ) - { - DALI_LOG_WARNING( "Failed to download image file \"%s\" with error code %d\n", url.c_str(), res ); - return false; - } +// libcurl with openssl needs locking_function and thread id for threadsafe +// https://curl.haxx.se/libcurl/c/threadsafe.html +// https://www.openssl.org/docs/man1.0.2/crypto/threads.html#DESCRIPTION +void CurlEnvironment::OnOpenSSLLocking( int mode, int n, const char* file, int line ) +{ + if( mode & CRYPTO_LOCK ) + { + mMutexs[n].lock(); + } + else + { + mMutexs[n].unlock(); } - return true; } -} // unnamed namespace +unsigned long CurlEnvironment::GetThreadId() +{ + // If dali uses c++ thread, we may replace pthread_self() to this_thread::get_id() + return static_cast< unsigned long >( pthread_self() ); +} + +void CurlEnvironment::SetLockingFunction() +{ + if( mMutexs != NULL ) + { + return; + } + + mMutexs = new std::mutex[ CRYPTO_num_locks() ]; + + CRYPTO_set_id_callback( &CurlEnvironment::GetThreadId ); + CRYPTO_set_locking_callback( &CurlEnvironment::OnOpenSSLLocking ); +} + +void CurlEnvironment::UnsetLockingFunction() +{ + if( mMutexs == NULL ) + { + return; + } + CRYPTO_set_id_callback( NULL ); + CRYPTO_set_locking_callback( NULL ); + delete [] mMutexs; + mMutexs = NULL; +} -bool Network::DownloadRemoteFileIntoMemory( const std::string& url, - Dali::Vector& dataBuffer, - size_t& dataSize, - size_t maximumAllowedSizeBytes ) +bool DownloadRemoteFileIntoMemory( const std::string& url, + Dali::Vector& dataBuffer, + size_t& dataSize, + size_t maximumAllowedSizeBytes ) { if( url.empty() ) { @@ -156,21 +296,22 @@ bool Network::DownloadRemoteFileIntoMemory( const std::string& url, // start a libcurl easy session, this internally calls curl_global_init, if we ever have more than one download // thread we need to explicity call curl_global_init() on startup from a single thread. - CURL* curl_handle = curl_easy_init(); + CURL* curlHandle = curl_easy_init(); - bool result = DownloadFile( curl_handle, url, dataBuffer, dataSize, maximumAllowedSizeBytes); + bool result = DownloadFile( curlHandle, url, dataBuffer, dataSize, maximumAllowedSizeBytes); // clean up session - curl_easy_cleanup( curl_handle ); + curl_easy_cleanup( curlHandle ); -#ifndef DALI_PROFILE_UBUNTU +#ifdef TPK_CURL_ENABLED // Clean up tpkp(the module for certificate pinning) resources on Tizen tpkp_curl_cleanup(); -#endif // DALI_PROFILE_UBUNTU +#endif // TPK_CURL_ENABLED return result; } +} // namespace Network } // namespace TizenPlatform