1/*
2 * Copyright (C) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "uri_helper.h"
17#include <cstring>
18#include <climits>
19#include <sys/types.h>
20#include <sys/stat.h>
21#include <type_traits>
22#include "media_errors.h"
23#include "media_log.h"
24
25namespace {
26    constexpr OHOS::HiviewDFX::HiLogLabel LABEL = {LOG_CORE, LOG_DOMAIN_PLAYER, "UriHelper"};
27}
28
29namespace OHOS {
30namespace Media {
31static const std::map<std::string_view, uint8_t> g_validUriTypes = {
32    {"", UriHelper::UriType::URI_TYPE_FILE }, // empty uri head is treated as the file type uri.
33    {"file", UriHelper::UriType::URI_TYPE_FILE},
34    {"fd", UriHelper::UriType::URI_TYPE_FD},
35    {"http", UriHelper::UriType::URI_TYPE_HTTP}
36};
37
38static bool PathToRealFileUrl(const std::string_view &path, std::string &realPath)
39{
40    CHECK_AND_RETURN_RET_LOG(!path.empty(), false, "path is empty!");
41    CHECK_AND_RETURN_RET_LOG(path.length() < PATH_MAX, false,
42        "path len is error, the len is: [%{public}zu]", path.length());
43
44    char tmpPath[PATH_MAX] = {0};
45    char *pathAddr = realpath(path.data(), tmpPath);
46    CHECK_AND_RETURN_RET_LOG(pathAddr != nullptr, false,
47        "path to realpath error, %{public}s", path.data());
48
49    int ret = access(tmpPath, F_OK);
50    CHECK_AND_RETURN_RET_LOG(ret == 0, false,
51        "check realpath (%{private}s) error", tmpPath);
52
53    realPath = std::string("file://") + tmpPath;
54    return true;
55}
56
57template<typename T, typename = std::enable_if_t<std::is_same_v<int64_t, T> || std::is_same_v<int32_t, T>>>
58bool StrToInt(const std::string_view& str, T& value)
59{
60    if (str.empty() || (!isdigit(str.front()) && (str.front() != '-'))) {
61        return false;
62    }
63    std::string valStr(str);
64    char* end = nullptr;
65    errno = 0;
66    const char* addr = valStr.c_str();
67    long long result = strtoll(addr, &end, 10); /* 10 means decimal */
68    CHECK_AND_RETURN_RET_LOG(result >= LLONG_MIN && result <= LLONG_MAX, false,
69        "call StrToInt func false,  input str is: %{public}s!", valStr.c_str());
70    CHECK_AND_RETURN_RET_LOG(end != addr && end[0] == '\0' && errno != ERANGE, false,
71        "call StrToInt func false,  input str is: %{public}s!", valStr.c_str());
72    if constexpr (std::is_same<int32_t, T>::value) {
73        CHECK_AND_RETURN_RET_LOG(result >= INT_MIN && result <= INT_MAX, false,
74            "call StrToInt func false,  input str is: %{public}s!", valStr.c_str());
75        value = static_cast<int32_t>(result);
76        return true;
77    }
78    value = result;
79    return true;
80}
81
82__attribute__((no_sanitize("cfi")))
83std::pair<std::string_view, std::string_view> SplitUriHeadAndBody(const std::string_view &str)
84{
85    std::string_view::size_type start = str.find_first_not_of(' ');
86    std::string_view::size_type end = str.find_last_not_of(' ');
87    std::pair<std::string_view, std::string_view> result;
88    std::string_view noSpaceStr;
89    if (start == std::string_view::npos && end == std::string_view::npos) {
90        result.first = "";
91        result.second = "";
92        return result;
93    }
94    if (end == std::string_view::npos) {
95        noSpaceStr = str.substr(start);
96    } else {
97        if (end >= start) {
98            noSpaceStr = str.substr(start, end - start + 1);
99        }
100    }
101    std::string_view delimiter = "://";
102    std::string_view::size_type pos = noSpaceStr.find(delimiter);
103    if (pos == std::string_view::npos) {
104        result.first = "";
105        result.second = noSpaceStr;
106    } else {
107        result.first = noSpaceStr.substr(0, pos);
108        result.second = noSpaceStr.substr(pos + delimiter.size());
109    }
110    return result;
111}
112
113UriHelper::UriHelper(const std::string_view &uri)
114{
115    FormatMeForUri(uri);
116}
117
118UriHelper::UriHelper(int32_t fd, int64_t offset, int64_t size) : fd_(dup(fd)), offset_(offset), size_(size)
119{
120    FormatMeForFd();
121}
122
123UriHelper::~UriHelper()
124{
125    if (fd_ >= 0) {
126        (void)::close(fd_);
127        fd_ = -1;
128    }
129}
130
131void UriHelper::FormatMeForUri(const std::string_view &uri) noexcept
132{
133    CHECK_AND_RETURN_LOG(formattedUri_.empty(),
134        "formattedUri is valid:%{public}s", formattedUri_.c_str());
135    CHECK_AND_RETURN_LOG(!uri.empty(), "uri is empty");
136    auto [head, body] = SplitUriHeadAndBody(uri);
137    CHECK_AND_RETURN(g_validUriTypes.count(head) != 0);
138    type_ = g_validUriTypes.at(head);
139    // verify whether the uri is readable and generate the formatted uri.
140    switch (type_) {
141        case URI_TYPE_FILE: {
142            if (!PathToRealFileUrl(body, formattedUri_)) {
143                type_ = URI_TYPE_UNKNOWN;
144                formattedUri_ = body;
145            }
146            rawFileUri_ = formattedUri_;
147            if (rawFileUri_.size() > strlen("file://")) {
148                rawFileUri_ = rawFileUri_.substr(strlen("file://"));
149            }
150            break;
151        }
152        case URI_TYPE_FD: {
153            if (!ParseFdUri(body)) {
154                type_ = URI_TYPE_UNKNOWN;
155                formattedUri_ = "";
156            }
157            break;
158        }
159        default:
160            formattedUri_ = std::string(head);
161            formattedUri_ += body;
162            break;
163    }
164    MEDIA_LOGD("0x%{public}06" PRIXPTR " formatted uri: %{private}s", FAKE_POINTER(this), formattedUri_.c_str());
165}
166
167void UriHelper::FormatMeForFd() noexcept
168{
169    MEDIA_LOGI("0x%{public}06" PRIXPTR " UriHelper FormatMeForFd fd is %{public}d", FAKE_POINTER(this), fd_);
170    CHECK_AND_RETURN_LOG(formattedUri_.empty(),
171        "formattedUri is valid:%{public}s", formattedUri_.c_str());
172    type_ = URI_TYPE_FD;
173    (void)CorrectFdParam();
174}
175
176bool UriHelper::CorrectFdParam()
177{
178    int flags = fcntl(fd_, F_GETFL);
179    CHECK_AND_RETURN_RET_LOG(flags != -1, false, "Fail to get File Status Flags");
180    struct stat64 st;
181    CHECK_AND_RETURN_RET_LOG(fstat64(fd_, &st) == 0, false,
182        "can not get file state");
183    int64_t fdSize = static_cast<int64_t>(st.st_size);
184    int64_t stIno = static_cast<int64_t>(st.st_ino);
185    int64_t stSec = static_cast<int64_t>(st.st_atim.tv_sec);
186    MEDIA_LOGI("CorrectFdParam fd: %{public}d, fdSize: %{public}" PRId64 ", stIno: %{public}" PRId64
187        ", stSec: %{public}" PRId64, fd_, fdSize, stIno, stSec);
188    if (offset_ < 0 || offset_ > fdSize) {
189        offset_ = 0;
190    }
191    if ((size_ <= 0) || (size_ > fdSize - offset_)) {
192        size_ = fdSize - offset_;
193    }
194    formattedUri_ = std::string("fd://") + std::to_string(fd_) + "?offset=" +
195        std::to_string(offset_) + "&size=" + std::to_string(size_);
196    MEDIA_LOGI("CorrectFdParam formattedUri: %{public}s", formattedUri_.c_str());
197    return true;
198}
199
200uint8_t UriHelper::UriType() const
201{
202    return type_;
203}
204
205std::string UriHelper::FormattedUri() const
206{
207    return formattedUri_;
208}
209
210bool UriHelper::AccessCheck(uint8_t flag) const
211{
212    CHECK_AND_RETURN_RET_LOG(type_ != URI_TYPE_UNKNOWN, false, "type is unknown");
213    if (type_ == URI_TYPE_FILE) {
214        uint32_t mode = (flag & URI_READ) ? R_OK : 0;
215        mode |= (flag & URI_WRITE) ? W_OK : 0;
216        int ret = access(rawFileUri_.data(), static_cast<int>(mode));
217        CHECK_AND_RETURN_RET_LOG(ret == 0, false,
218            "Fail to access path: %{public}s", rawFileUri_.data());
219        return true;
220    } else if (type_ == URI_TYPE_FD) {
221        CHECK_AND_RETURN_RET_LOG(fd_ > 0, false, "Fail to get file descriptor from uri, fd %{public}d", fd_);
222        int flags = fcntl(fd_, F_GETFL);
223        CHECK_AND_RETURN_RET_LOG(flags != -1, false, "Fail to get File Status Flags, fd %{public}d", fd_);
224        uint32_t mode = (flag & URI_WRITE) ? O_RDWR : O_RDONLY;
225        return ((static_cast<unsigned int>(flags) & mode) != mode) ? false : true;
226    }
227    return true; // Not implemented, defaultly return true.
228}
229
230bool UriHelper::ParseFdUri(std::string_view uri)
231{
232    static constexpr std::string_view::size_type delim1Len = std::string_view("?offset=").size();
233    static constexpr std::string_view::size_type delim2Len = std::string_view("&size=").size();
234    std::string_view::size_type delim1 = uri.find("?");
235    std::string_view::size_type delim2 = uri.find("&");
236    if (delim1 == std::string_view::npos && delim2 == std::string_view::npos) {
237        CHECK_AND_RETURN_RET_LOG(StrToInt(uri, fd_), false, "Invalid fd url");
238    } else if (delim1 != std::string_view::npos && delim2 != std::string_view::npos) {
239        std::string_view fdstr = uri.substr(0, delim1);
240        int32_t fd = -1;
241        CHECK_AND_RETURN_RET_LOG(StrToInt(fdstr, fd) && delim1 + delim1Len < uri.size()
242            && delim2 - delim1 - delim1Len > 0, false, "Invalid fd url");
243        std::string_view offsetStr = uri.substr(delim1 + delim1Len, delim2 - delim1 - delim1Len);
244        CHECK_AND_RETURN_RET_LOG(StrToInt(offsetStr, offset_) && delim2 + delim2Len < uri.size(), false,
245            "Invalid fd url");
246        std::string_view sizeStr = uri.substr(delim2 + delim2Len);
247        CHECK_AND_RETURN_RET_LOG(StrToInt(sizeStr, size_), false, "Invalid fd url");
248        MEDIA_LOGI("UriHelper ParseFdUri try close fd, fd is %{public}d, Set fd: %{public}d", fd_, fd);
249        if (fd_ >= 0) {
250            close(fd_);
251            fd_ = -1;
252        }
253        fd_ = dup(fd);
254        MEDIA_LOGI("UriHelper ParseFdUri dup, fd is %{public}d", fd_);
255    } else {
256        MEDIA_LOGE("invalid fd uri: %{private}s", uri.data());
257        return false;
258    }
259
260    MEDIA_LOGD("parse fd uri, fd: %{public}d, offset: %{public}" PRIi64 ", size: %{public}" PRIi64,
261               fd_, offset_, size_);
262    return CorrectFdParam();
263}
264} // namespace Media
265} // namespace OHOS
266