/* * Copyright (c) 2021 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "adapter/preview/osal/fetch_manager.h" #include #include #include "curl/curl.h" #include "adapter/preview/osal/http_constant.h" #include "base/log/log.h" #include "base/utils/singleton.h" #define ACE_CURL_EASY_SET_OPTION(handle, opt, data) \ do { \ CURLcode result = curl_easy_setopt(handle, opt, data); \ if (result != CURLE_OK) { \ LOGW("Failed to set option: %{public}s, %{public}s", #opt, curl_easy_strerror(result)); \ return false; \ } \ } while (0) namespace OHOS::Ace { namespace { class FetchManagerImpl final : public FetchManager, public Singleton { DECLARE_SINGLETON(FetchManagerImpl); ACE_DISALLOW_MOVE(FetchManagerImpl); public: bool Fetch(const RequestData requestData, const int32_t callbackId, ResponseData& responseData) override { if (!Initialize()) { return false; } std::unique_ptr handle(curl_easy_init(), &curl_easy_cleanup); if (!handle) { return false; } struct curl_slist* header = nullptr; if (!requestData.GetHeader().empty()) { for (auto&& [key, value] : requestData.GetHeader()) { header = curl_slist_append(header, (key + ":" + value).c_str()); } ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_HTTPHEADER, header); } ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_TIMEOUT_MS, HttpConstant::TIME_OUT); ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_CONNECTTIMEOUT_MS, HttpConstant::TIME_OUT); ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_BUFFERSIZE, HttpConstant::BUFFER_SIZE); std::string responseBody; ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_WRITEFUNCTION, OnWritingMemoryBody); ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_WRITEDATA, &responseBody); std::string responseHeader; ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_HEADERFUNCTION, OnWritingMemoryHeader); ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_HEADERDATA, &responseHeader); // Some servers don't like requests that are made without a user-agent field, so we provide one ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_USERAGENT, "libcurl-agent/1.0"); std::string method = requestData.GetMethod(); if (method.empty()) { method = "GET"; } if (method == HttpConstant::HTTP_METHOD_HEAD || method == HttpConstant::HTTP_METHOD_OPTIONS || method == HttpConstant::HTTP_METHOD_DELETE || method == HttpConstant::HTTP_METHOD_TRACE || method == HttpConstant::HTTP_METHOD_GET) { SetOptionForGet(requestData, handle.get()); } else if (method == HttpConstant::HTTP_METHOD_POST || method == HttpConstant::HTTP_METHOD_PUT) { SetOptionForPost(requestData, handle.get()); } else { responseData.SetCode(HttpConstant::ERROR); responseData.SetData(responseBody); return true; } CURLcode result = curl_easy_perform(handle.get()); if (result != CURLE_OK) { LOGW("Failed to fetch, url: %{private}s, %{public}s", requestData.GetUrl().c_str(), curl_easy_strerror(result)); responseData.SetCode(HttpConstant::ERROR); responseData.SetData(responseBody); return true; } char* ct = nullptr; curl_easy_getinfo(handle.get(), CURLINFO_CONTENT_TYPE, &ct); int32_t responseCode = HttpConstant::ERROR; curl_easy_getinfo(handle.get(), CURLINFO_RESPONSE_CODE, &responseCode); responseData.SetCode(responseCode); responseData.SetData(responseBody); responseData.SetHeaders(responseHeader); if (header != nullptr) { curl_slist_free_all(header); } return true; } bool SetOptionForGet(const RequestData requestData, CURL* curl) const { // refer to function buildConnectionWithParam() in HttpFetchImpl.java std::string url = requestData.GetUrl(); if (requestData.GetData() != "") { std::size_t index = url.find(HttpConstant::URL_PARAM_SEPARATOR); if (index != std::string::npos) { std::string param = url.substr(index + 1); std::string encodeIn = param + HttpConstant::URL_PARAM_DELIMITER + requestData.GetData(); char* encodeOut = curl_easy_escape(curl, encodeIn.c_str(), 0); if (encodeOut != nullptr) { url = url.substr(0, index + 1) + encodeOut; curl_free(encodeOut); } } else { char* encodeOut = curl_easy_escape(curl, requestData.GetData().c_str(), 0); if (encodeOut != nullptr) { url = url + HttpConstant::URL_PARAM_SEPARATOR + encodeOut; curl_free(encodeOut); } } } ACE_CURL_EASY_SET_OPTION(curl, CURLOPT_URL, url.c_str()); return true; } bool SetOptionForPost(const RequestData requestData, CURL* curl) const { // refer to function buildConnectionWithStream() in HttpFetchImpl.java std::string url = requestData.GetUrl(); ACE_CURL_EASY_SET_OPTION(curl, CURLOPT_URL, url.c_str()); ACE_CURL_EASY_SET_OPTION(curl, CURLOPT_POSTFIELDS, requestData.GetData().c_str()); return true; } private: static size_t OnWritingMemoryBody(const void* data, size_t size, size_t memBytes, void* userData) { ((std::string*)userData)->append((char*)data, 0, size * memBytes); return size * memBytes; } static size_t OnWritingMemoryHeader(const void* data, size_t size, size_t memBytes, void* userData) { ((std::string*)userData)->append((char*)data, 0, size * memBytes); return size * memBytes; } bool Initialize() { if (initialized_) { return true; } std::lock_guard lock(mutex_); if (initialized_) { return true; } if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { LOGE("Failed to initialize 'curl'"); return false; } initialized_ = true; return true; } std::mutex mutex_; bool initialized_ = false; }; FetchManagerImpl::FetchManagerImpl() = default; FetchManagerImpl::~FetchManagerImpl() { curl_global_cleanup(); } } // namespace FetchManager& FetchManager::GetInstance() { return Singleton::GetInstance(); } } // namespace OHOS::Ace