* Copyright (c) 2025 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 "task_builder.h"
#include <regex>
#include "application_context.h"
#include "log.h"
#include "network_security_config.h"
namespace OHOS::Request {
TaskBuilder &TaskBuilder::setAction(Action action)
{
this->config.action = action;
return *this;
}
TaskBuilder &TaskBuilder::setUrl(const std::string &url)
{
this->config.url = url;
return *this;
}
TaskBuilder &TaskBuilder::setTitle(const std::string &title)
{
this->config.title = title;
return *this;
}
TaskBuilder &TaskBuilder::setDescription(const std::string &description)
{
this->config.description = description;
return *this;
}
TaskBuilder &TaskBuilder::setMode(Mode mode)
{
this->config.mode = mode;
return *this;
}
TaskBuilder &TaskBuilder::setOverwrite(bool overwrite)
{
this->config.overwrite = overwrite;
return *this;
}
TaskBuilder &TaskBuilder::setMethod(const std::string &method)
{
this->config.method = method;
return *this;
}
TaskBuilder &TaskBuilder::setHeaders(const std::map<std::string, std::string> &headers)
{
this->config.headers = headers;
return *this;
}
TaskBuilder &TaskBuilder::setData(const std::string &data)
{
this->config.data = data;
return *this;
}
TaskBuilder &TaskBuilder::setData(const std::vector<FormItem> &data)
{
this->config.forms = data;
return *this;
}
TaskBuilder &TaskBuilder::setData(const std::vector<FileSpec> &data)
{
this->config.files = data;
return *this;
}
TaskBuilder &TaskBuilder::setSaveAs(const std::string &saveas)
{
this->config.saveas = saveas;
return *this;
}
TaskBuilder &TaskBuilder::setNetwork(Network network)
{
this->config.network = network;
return *this;
}
TaskBuilder &TaskBuilder::setMetered(bool metered)
{
this->config.metered = metered;
return *this;
}
TaskBuilder &TaskBuilder::setRoaming(bool roaming)
{
this->config.roaming = roaming;
return *this;
}
TaskBuilder &TaskBuilder::setRetry(bool retry)
{
this->config.retry = retry;
return *this;
}
TaskBuilder &TaskBuilder::setRedirect(bool redirect)
{
this->config.redirect = redirect;
return *this;
}
TaskBuilder &TaskBuilder::setProxy(const std::string &proxy)
{
this->config.proxy = proxy;
return *this;
}
TaskBuilder &TaskBuilder::setIndex(uint32_t index)
{
this->config.index = index;
return *this;
}
TaskBuilder &TaskBuilder::setBegins(int begins)
{
this->config.begins = begins;
return *this;
}
TaskBuilder &TaskBuilder::setEnds(int ends)
{
this->config.ends = ends;
return *this;
}
TaskBuilder &TaskBuilder::setGauge(bool gauge)
{
this->config.gauge = gauge;
return *this;
}
TaskBuilder &TaskBuilder::setPrecise(bool precise)
{
this->config.precise = precise;
return *this;
}
TaskBuilder &TaskBuilder::setToken(const std::string &token)
{
this->config.token = token;
return *this;
}
TaskBuilder &TaskBuilder::setPriority(uint32_t priority)
{
this->config.priority = priority;
return *this;
}
TaskBuilder &TaskBuilder::setExtras(const std::map<std::string, std::string> &extras)
{
this->config.extras = extras;
return *this;
}
TaskBuilder &TaskBuilder::setTimeout(const Timeout &timeout)
{
this->config.timeout = timeout;
return *this;
}
TaskBuilder &TaskBuilder::setMinSpeed(const MinSpeed &minSpeed)
{
this->config.minSpeed = minSpeed;
return *this;
}
std::pair<Config, ExceptionErrorCode> TaskBuilder::build()
{
if (!this->checkAction()) {
return { this->config, ExceptionErrorCode::E_PARAMETER_CHECK };
}
if (!this->checkUrl()) {
return { this->config, ExceptionErrorCode::E_PARAMETER_CHECK };
}
if (!this->checkData()) {
return { this->config, ExceptionErrorCode::E_PARAMETER_CHECK };
}
if (!this->checkIndex()) {
return { this->config, ExceptionErrorCode::E_PARAMETER_CHECK };
}
if (!this->checkProxy()) {
return { this->config, ExceptionErrorCode::E_PARAMETER_CHECK };
}
if (!this->checkTitle()) {
return { this->config, ExceptionErrorCode::E_PARAMETER_CHECK };
}
if (!this->checkToken()) {
return { this->config, ExceptionErrorCode::E_PARAMETER_CHECK };
}
if (!this->checkDescription()) {
return { this->config, ExceptionErrorCode::E_PARAMETER_CHECK };
}
if (!this->checkSaveas()) {
return { this->config, ExceptionErrorCode::E_PARAMETER_CHECK };
}
if (!this->checkBundle()) {
return { this->config, ExceptionErrorCode::E_PARAMETER_CHECK };
}
this->checkCertsPath();
this->checkCertificatePins();
this->checkMethod();
this->checkOtherConfig();
return { this->config, ExceptionErrorCode::E_OK };
}
bool TaskBuilder::checkAction()
{
if (this->config.action != Action::DOWNLOAD && this->config.action != Action::UPLOAD) {
REQUEST_HILOGE("Must be UPLOAD or DOWNLOAD");
return false;
}
return true;
}
bool TaskBuilder::checkUrl()
{
constexpr uint32_t URL_MAXIMUM = 8192;
if (this->config.url.size() > URL_MAXIMUM) {
REQUEST_HILOGE("The URL exceeds the maximum length of 8192");
return false;
}
if (!regex_match(this->config.url, std::regex("^http(s)?:\\/\\/.+"))) {
REQUEST_HILOGE("ParseUrl error");
return false;
}
return true;
}
void TaskBuilder::checkCertsPath()
{
typedef std::string::const_iterator iter_t;
iter_t urlEnd = this->config.url.end();
iter_t protocolStart = this->config.url.cbegin();
iter_t protocolEnd = std::find(protocolStart, urlEnd, ':');
std::string protocol = std::string(protocolStart, protocolEnd);
if (protocol != "https") {
REQUEST_HILOGD("Using Http");
return;
}
if (protocolEnd != urlEnd) {
std::string afterProtocol = &*(protocolEnd);
if ((afterProtocol.length() > 3) && (afterProtocol.substr(0, 3) == "://")) {
protocolEnd += 3;
} else {
protocolEnd = this->config.url.cbegin();
}
} else {
protocolEnd = this->config.url.cbegin();
}
iter_t hostStart = protocolEnd;
iter_t pathStart = std::find(hostStart, urlEnd, '/');
iter_t queryStart = std::find(this->config.url.cbegin(), urlEnd, '?');
iter_t hostEnd = std::find(protocolEnd, (pathStart != urlEnd) ? pathStart : queryStart, ':');
std::string hostname = std::string(hostStart, hostEnd);
REQUEST_HILOGD("Hostname is %{public}s", hostname.c_str());
NetManagerStandard::NetworkSecurityConfig::GetInstance().
GetTrustAnchorsForHostName(hostname, this->config.certsPath);
}
bool TaskBuilder::checkData()
{
if (this->config.action == Action::UPLOAD) {
if (this->config.files.empty()) {
REQUEST_HILOGE("Missing mandatory parameters, files is empty");
return false;
}
for (auto &file : this->config.files) {
if (file.uri.empty()) {
REQUEST_HILOGE("Missing mandatory parameters, uri is empty");
return false;
}
}
}
return true;
}
bool TaskBuilder::checkIndex()
{
if (this->config.action == Action::DOWNLOAD) {
this->config.index = 0;
} else if (this->config.files.size() <= config.index) {
REQUEST_HILOGE("files.size is %{public}zu, index is %{public}d", config.files.size(), config.index);
return false;
}
return true;
}
bool TaskBuilder::checkProxy()
{
constexpr uint32_t PROXY_MAXIMUM = 512;
if (this->config.proxy.empty()) {
return true;
}
if (this->config.proxy.size() > PROXY_MAXIMUM) {
REQUEST_HILOGE("The proxy exceeds the maximum length of 512");
return false;
}
if (!regex_match(this->config.proxy, std::regex("^http:\\/\\/.+:\\d{1,5}$"))) {
REQUEST_HILOGE("ParseProxy error");
return false;
}
return true;
}
bool TaskBuilder::checkTitle()
{
static constexpr uint32_t TITLE_MAXIMUM = 256;
if (config.title.size() > TITLE_MAXIMUM) {
REQUEST_HILOGE("Parameter verification failed, the length of config title exceeds 256");
return false;
}
if (this->config.title.empty()) {
this->config.title = this->config.action == Action::UPLOAD ? "upload" : "download";
}
return true;
}
bool TaskBuilder::checkToken()
{
constexpr uint32_t TOKEN_MAX_BYTES = 2048;
constexpr uint32_t TOKEN_MIN_BYTES = 8;
if (this->config.token.compare("null") == 0) {
return true;
}
if ((this->config.token.size() < TOKEN_MIN_BYTES || this->config.token.size() > TOKEN_MAX_BYTES)) {
REQUEST_HILOGE("token error");
return false;
}
return true;
}
bool TaskBuilder::checkDescription()
{
constexpr uint32_t DESCRIPTION_MAXIMUM = 1024;
if (this->config.description.size() > DESCRIPTION_MAXIMUM) {
REQUEST_HILOGE("description error");
return false;
}
return true;
}
bool TaskBuilder::checkSaveas()
{
if (this->config.action != Action::DOWNLOAD) {
this->config.saveas = "";
return true;
}
if (!this->config.saveas.empty()) {
this->config.saveas.erase(0, this->config.saveas.find_first_not_of(" "));
this->config.saveas.erase(this->config.saveas.find_last_not_of(" ") + 1);
}
if (this->config.saveas.empty() || this->config.saveas == "./") {
std::size_t position = this->config.url.find_last_of("/");
if (position == std::string::npos || position + 1 >= this->config.url.size()) {
REQUEST_HILOGE("Parameter verification failed, config.saveas parse error");
return false;
}
this->config.saveas = std::string(this->config.url, position + 1);
return true;
}
if (this->config.saveas.size() == 0 || this->config.saveas[this->config.saveas.size() - 1] == '/') {
REQUEST_HILOGE("Parameter verification failed, config.saveas parse error");
return false;
}
return true;
}
std::string GetHostnameFromURL(const std::string &url)
{
if (url.empty()) {
return "";
}
std::string delimiter = "://";
std::string tempUrl = url;
std::replace(tempUrl.begin(), tempUrl.end(), '\\', '/');
size_t posStart = tempUrl.find(delimiter);
if (posStart != std::string::npos) {
posStart += delimiter.length();
} else {
posStart = 0;
}
size_t notSlash = tempUrl.find_first_not_of('/', posStart);
if (notSlash != std::string::npos) {
posStart = notSlash;
}
size_t posEnd =
std::min({ tempUrl.find(':', posStart), tempUrl.find('/', posStart), tempUrl.find('?', posStart) });
if (posEnd != std::string::npos) {
return tempUrl.substr(posStart, posEnd - posStart);
}
return tempUrl.substr(posStart);
}
void TaskBuilder::checkCertificatePins()
{
auto hostname = GetHostnameFromURL(this->config.url);
if (OHOS::NetManagerStandard::NetworkSecurityConfig::GetInstance().IsPinOpenMode(hostname)) {
REQUEST_HILOGI("Pins is openMode");
return;
}
auto ret = OHOS::NetManagerStandard::NetworkSecurityConfig::GetInstance().GetPinSetForHostName(
hostname, this->config.certificatePins);
if (ret != 0 || this->config.certificatePins.empty()) {
REQUEST_HILOGD("Get No pin set by hostname");
}
}
void TaskBuilder::checkMethod()
{
if (!this->config.method.empty()) {
transform(this->config.method.begin(), this->config.method.end(), this->config.method.begin(), ::toupper);
if (this->config.action == Action::UPLOAD) {
if ((this->config.method == "POST" || this->config.method == "PUT")) {
return;
}
}
if (this->config.action == Action::DOWNLOAD) {
if (this->config.method == "POST" || this->config.method == "GET") {
return;
}
}
}
this->config.method = this->config.action == Action::UPLOAD ? "PUT" : "GET";
}
void TaskBuilder::checkOtherConfig()
{
this->config.version = Version::API10;
if (this->config.begins < 0) {
this->config.begins = 0;
}
if (this->config.mode == Mode::BACKGROUND) {
this->config.background = true;
}
}
bool TaskBuilder::checkBundle()
{
auto context = AbilityRuntime::Context::GetApplicationContext();
if (context == nullptr) {
REQUEST_HILOGE("AppContext is null.");
return false;
}
auto applicationInfo = context->GetApplicationInfo();
if (applicationInfo == nullptr) {
REQUEST_HILOGE("AppInfo is null.");
return false;
}
this->config.bundleType = static_cast<u_int32_t>(applicationInfo->bundleType);
REQUEST_HILOGD("config.bundleType is %{public}d", config.bundleType);
this->config.bundleName = context->GetBundleName();
REQUEST_HILOGD("config.bundleName is %{public}s", config.bundleName.c_str());
return true;
}
}