1/*
2 * Copyright (c) 2021-2024 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 "netstack_common_utils.h"
17
18#ifdef WINDOWS_PLATFORM
19#include <winsock2.h>
20#include <ws2tcpip.h>
21#pragma comment(lib, "ws2_32.lib")
22#else
23#include <arpa/inet.h>
24#endif
25
26#include <algorithm>
27#include <cctype>
28#include <cerrno>
29#include <regex>
30#include <string>
31#include <unistd.h>
32#include <vector>
33#include <fstream>
34#include <sstream>
35#if !defined(IOS_PLATFORM) && !defined(ANDROID_PLATFORM)
36#include <filesystem>
37#endif
38
39#include "netstack_log.h"
40#if !defined(WINDOWS_PLATFORM) && !defined(MAC_PLATFORM) && !defined(IOS_PLATFORM) && !defined(ANDROID_PLATFORM)
41#include "netstack_apipolicy_utils.h"
42#include "netstack_bundle_utils.h"
43#endif
44#if HAS_NETMANAGER_BASE
45#include "net_conn_client.h"
46#endif // HAS_NETMANAGER_BASE
47
48constexpr int32_t INET_OPTION_SUC = 1;
49constexpr size_t MAX_DISPLAY_NUM = 2;
50
51namespace OHOS::NetStack::CommonUtils {
52const std::regex IP_PATTERN{
53    "((2([0-4]\\d|5[0-5])|1\\d\\d|[1-9]\\d|\\d)\\.){3}(2([0-4]\\d|5[0-5])|1\\d\\d|[1-9]\\d|\\d)"};
54const std::regex IP_MASK_PATTERN{
55    "((2([0-4]\\d|5[0-5])|1\\d\\d|[1-9]\\d|\\d)\\.){3}(2([0-4]\\d|5[0-5])|1\\d\\d|[1-9]\\d|\\d)/"
56    "(3[0-2]|[1-2]\\d|\\d)"};
57const std::regex IPV6_PATTERN{"([\\da-fA-F]{0,4}:){2,7}([\\da-fA-F]{0,4})"};
58const std::regex IPV6_MASK_PATTERN{"([\\da-fA-F]{0,4}:){2,7}([\\da-fA-F]{0,4})/(1[0-2][0-8]|[1-9]\\d|[1-9])"};
59static const std::string PROTOCOL_WSS = "wss";
60std::mutex g_commonUtilsMutex;
61
62std::vector<std::string> Split(const std::string &str, const std::string &sep)
63{
64    std::string s = str;
65    std::vector<std::string> res;
66    while (!s.empty()) {
67        auto pos = s.find(sep);
68        if (pos == std::string::npos) {
69            res.emplace_back(s);
70            break;
71        }
72        res.emplace_back(s.substr(0, pos));
73        s = s.substr(pos + sep.size());
74    }
75    return res;
76}
77
78std::vector<std::string> Split(const std::string &str, const std::string &sep, size_t size)
79{
80    std::string s = str;
81    std::vector<std::string> res;
82    while (!s.empty()) {
83        if (res.size() + 1 == size) {
84            res.emplace_back(s);
85            break;
86        }
87
88        auto pos = s.find(sep);
89        if (pos == std::string::npos) {
90            res.emplace_back(s);
91            break;
92        }
93        res.emplace_back(s.substr(0, pos));
94        s = s.substr(pos + sep.size());
95    }
96    return res;
97}
98
99std::string Strip(const std::string &str, char ch)
100{
101    int64_t i = 0;
102    while (static_cast<size_t>(i) < str.size() && str[i] == ch) {
103        ++i;
104    }
105    int64_t j = static_cast<int64_t>(str.size()) - 1;
106    while (j > 0 && str[j] == ch) {
107        --j;
108    }
109    if (i >= 0 && static_cast<size_t>(i) < str.size() && j >= 0 && static_cast<size_t>(j) < str.size() &&
110        j - i + 1 > 0) {
111        return str.substr(i, j - i + 1);
112    }
113    return "";
114}
115
116std::string ToLower(const std::string &s)
117{
118    std::string res = s;
119    std::transform(res.begin(), res.end(), res.begin(), tolower);
120    return res;
121}
122
123std::string ToString(const std::list<std::string> &lists, char tab)
124{
125    std::string str;
126    for (auto it = lists.begin(); it != lists.end(); ++it) {
127        if (it != lists.begin()) {
128            str.append(1, tab);
129        }
130        str.append(*it);
131    }
132    return str;
133}
134
135bool HasInternetPermission()
136{
137#ifndef OH_CORE_NETSTACK_PERMISSION_CHECK
138#ifdef FUZZ_TEST
139    return true;
140#endif
141    int testSock = socket(AF_INET, SOCK_STREAM, 0);
142    if (testSock < 0 && errno == EPERM) {
143        NETSTACK_LOGE("make tcp testSock failed errno is %{public}d %{public}s", errno, strerror(errno));
144        return false;
145    }
146    if (testSock > 0) {
147        close(testSock);
148    }
149    return true;
150#else
151    constexpr int inetGroup = 40002003; // 3003 in gateway shell.
152    int groupNum = getgroups(0, nullptr);
153    if (groupNum <= 0) {
154        NETSTACK_LOGE("no group of INTERNET permission");
155        return false;
156    }
157    auto groups = (gid_t *)malloc(groupNum * sizeof(gid_t));
158    if (groups == nullptr) {
159        NETSTACK_LOGE("INTERNET permission denied by malloc");
160        return false;
161    }
162    groupNum = getgroups(groupNum, groups);
163    for (int i = 0; i < groupNum; i++) {
164        if (groups[i] == inetGroup) {
165            free(groups);
166            return true;
167        }
168    }
169    free(groups);
170    NETSTACK_LOGE("INTERNET permission denied by group");
171    return false;
172#endif
173}
174
175bool IsAtomicService(std::string &bundleName)
176{
177#if !defined(WINDOWS_PLATFORM) && !defined(MAC_PLATFORM) && !defined(IOS_PLATFORM) && !defined(ANDROID_PLATFORM)
178    return BundleUtils::IsAtomicService(bundleName);
179#else
180    return false;
181#endif
182}
183
184bool IsAllowedHostname(const std::string &bundleName, const std::string &domainType, const std::string &url)
185{
186#if !defined(WINDOWS_PLATFORM) && !defined(MAC_PLATFORM) && !defined(IOS_PLATFORM) && !defined(ANDROID_PLATFORM)
187    if (bundleName.empty()) {
188        NETSTACK_LOGE("isAllowedHostnameForAtomicService bundleName is empty");
189        return true;
190    }
191    auto hostname = GetHostnameWithProtocolAndPortFromURL(url);
192    if (hostname.empty()) {
193        NETSTACK_LOGE("isAllowedHostnameForAtomicService url hostname is empty");
194        return true;
195    }
196    return ApiPolicyUtils::IsAllowedHostname(bundleName, domainType, hostname);
197#else
198    return true;
199#endif
200}
201
202bool EndsWith(const std::string &str, const std::string &suffix)
203{
204    if (str.length() < suffix.length()) {
205        return false;
206    }
207    return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0;
208}
209
210std::string Trim(std::string str)
211{
212    size_t start = str.find_first_not_of(" \t\n\r");
213    size_t end = str.find_last_not_of(" \t\n\r");
214    if (start == std::string::npos || end == std::string::npos) {
215        return "";
216    } else {
217        return str.substr(start, end - start + 1);
218    }
219}
220
221bool IsMatch(const std::string &str, const std::string &patternStr)
222{
223    if (patternStr.empty()) {
224        return false;
225    }
226    if (patternStr == "*") {
227        return true;
228    }
229    if (!IsRegexValid(patternStr)) {
230        NETSTACK_LOGD("Invalid pattern");
231        return patternStr == str;
232    }
233    std::regex pattern(ReplaceCharacters(patternStr));
234    bool isMacth = patternStr != "" && std::regex_match(str, pattern);
235    if (isMacth) {
236        NETSTACK_LOGD("Match patternStr");
237    }
238    return isMacth;
239}
240
241std::string InsertCharBefore(const std::string &input, const char from, const char preChar, const char nextChar)
242{
243    std::string output = input;
244    char arr[] = {preChar, from};
245    unsigned long strSize = sizeof(arr) / sizeof(arr[0]);
246    std::string str(arr, strSize);
247    std::size_t pos = output.find(from);
248    std::size_t length = output.length();
249    while (pos <= length - 1 && pos != std::string::npos) {
250        char nextCharTemp = pos == length - 1 ? '\0' : output[pos + 1];
251        if (nextChar == '\0' || nextCharTemp == '\0' || nextCharTemp != nextChar) {
252            output.replace(pos, 1, str);
253            length += (strSize - 1);
254        }
255        pos = output.find(from, pos + strSize);
256    }
257    return output;
258}
259
260std::string ReplaceCharacters(const std::string &input)
261{
262    std::string output = InsertCharBefore(input, '*', '.', '\0');
263    output = InsertCharBefore(output, '.', '\\', '*');
264    return output;
265}
266
267bool IsRegexValid(const std::string &regex)
268{
269    if (Trim(regex).empty()) {
270        return false;
271    }
272    return regex_match(regex, std::regex("^[a-zA-Z0-9\\-_\\.*]+$"));
273}
274
275std::string GetProtocolFromURL(const std::string &url)
276{
277    std::string delimiter = "://";
278    size_t pos = url.find(delimiter);
279    if (pos != std::string::npos) {
280        return url.substr(0, pos);
281    }
282    return "";
283}
284
285std::string GetPortFromURL(const std::string &url)
286{
287    std::string delimiter = "://";
288    std::string protocol = GetProtocolFromURL(url);
289    std::string hostname = GetHostnameFromURL(url);
290    size_t start = protocol.empty() ? hostname.size() : protocol.size() + delimiter.size() + hostname.size();
291    size_t posStart = url.find_first_of(':', start);
292    if (posStart == std::string::npos) {
293        return "";
294    }
295    size_t posEnd = std::min({url.find('/', start), url.find('?', start)});
296    if (posEnd == std::string::npos) {
297        return url.substr(posStart + 1);
298    }
299    if (posStart > posEnd) {
300        return "";
301    }
302    return url.substr(posStart + 1, posEnd - posStart - 1);
303}
304
305std::string GetHostnameFromURL(const std::string &url)
306{
307    if (url.empty()) {
308        return "";
309    }
310    std::string delimiter = "://";
311    std::string tempUrl = url;
312    std::replace(tempUrl.begin(), tempUrl.end(), '\\', '/');
313    size_t posStart = tempUrl.find(delimiter);
314    if (posStart != std::string::npos) {
315        posStart += delimiter.length();
316    } else {
317        posStart = 0;
318    }
319    size_t notSlash = tempUrl.find_first_not_of('/', posStart);
320    if (notSlash != std::string::npos) {
321        posStart = notSlash;
322    }
323    size_t posEnd = std::min({ tempUrl.find(':', posStart),
324                              tempUrl.find('/', posStart), tempUrl.find('?', posStart) });
325    if (posEnd != std::string::npos) {
326        return tempUrl.substr(posStart, posEnd - posStart);
327    }
328    return tempUrl.substr(posStart);
329}
330
331std::string GetHostnameWithProtocolAndPortFromURL(const std::string& url)
332{
333    std::string delimiter = "://";
334    std::string portDelimiter = ":";
335    auto hostname = GetHostnameFromURL(url);
336    if (!hostname.empty()) {
337        std::string protocol = GetProtocolFromURL(url);
338        if (!protocol.empty()) {
339            hostname = protocol + delimiter + hostname;
340        }
341        if (protocol != PROTOCOL_WSS) {
342            std::string port = GetPortFromURL(url);
343            if (!port.empty()) {
344                hostname += portDelimiter + port;
345            }
346        }
347    }
348    return hostname;
349}
350
351bool IsExcluded(const std::string &str, const std::string &exclusions, const std::string &split)
352{
353    if (Trim(exclusions).empty()) {
354        return false;
355    }
356    std::size_t start = 0;
357    std::size_t end = exclusions.find(split);
358    while (end != std::string::npos) {
359        if (end - start > 0 && IsMatch(str, Trim(exclusions.substr(start, end - start)))) {
360            return true;
361        }
362        start = end + 1;
363        end = exclusions.find(split, start);
364    }
365    return IsMatch(str, Trim(exclusions.substr(start)));
366}
367
368bool IsHostNameExcluded(const std::string &url, const std::string &exclusions, const std::string &split)
369{
370    std::string hostName = GetHostnameFromURL(url);
371    return IsExcluded(hostName, exclusions, split);
372}
373
374bool IsValidIPV4(const std::string &ip)
375{
376    return IsValidIP(ip, AF_INET);
377}
378
379bool IsValidIPV6(const std::string &ip)
380{
381    return IsValidIP(ip, AF_INET6);
382}
383
384bool IsValidIP(const std::string& ip, int af)
385{
386    if (ip.empty()) {
387        return false;
388    }
389#ifdef WINDOWS_PLATFORM
390    if (af == AF_INET6) {
391        struct sockaddr_in6 sa;
392        return inet_pton(af, ip.c_str(), &(sa.sin6_addr)) == INET_OPTION_SUC;
393    } else {
394        struct sockaddr_in sa;
395        return inet_pton(af, ip.c_str(), &(sa.sin_addr)) == INET_OPTION_SUC;
396    }
397#else
398    if (af == AF_INET6) {
399        struct in6_addr addr;
400        return inet_pton(af, ip.c_str(), reinterpret_cast<void *>(&addr)) == INET_OPTION_SUC;
401    } else {
402        struct in_addr addr;
403        return inet_pton(af, ip.c_str(), reinterpret_cast<void *>(&addr)) == INET_OPTION_SUC;
404    }
405#endif
406}
407
408std::string MaskIpv4(std::string &maskedResult)
409{
410    int maxDisplayNum = MAX_DISPLAY_NUM;
411    for (char &i : maskedResult) {
412        if (i == '/') {
413            break;
414        }
415        if (maxDisplayNum > 0) {
416            if (i == '.') {
417                maxDisplayNum--;
418            }
419        } else {
420            if (i != '.') {
421                i = '*';
422            }
423        }
424    }
425    return maskedResult;
426}
427
428std::string MaskIpv6(std::string &maskedResult)
429{
430    size_t colonCount = 0;
431    for (char &i : maskedResult) {
432        if (i == '/') {
433            break;
434        }
435        if (i == ':') {
436            colonCount++;
437        }
438
439        if (colonCount >= MAX_DISPLAY_NUM) {
440            if (i != ':') {
441                i = '*';
442            }
443        }
444    }
445    return maskedResult;
446}
447
448std::string AnonymizeIp(std::string &input)
449{
450    if (input.empty()) {
451        return input;
452    }
453    std::lock_guard<std::mutex> lock(g_commonUtilsMutex);
454    std::string maskedResult{input};
455    if (std::regex_match(maskedResult, IP_PATTERN) || std::regex_match(maskedResult, IP_MASK_PATTERN)) {
456        return MaskIpv4(maskedResult);
457    }
458    if (std::regex_match(maskedResult, IPV6_PATTERN) || std::regex_match(maskedResult, IPV6_MASK_PATTERN)) {
459        return MaskIpv6(maskedResult);
460    }
461    return input;
462}
463
464std::optional<std::string> GetBundleName()
465{
466#if HAS_NETMANAGER_BASE
467    return OHOS::NetManagerStandard::NetConnClient::ObtainBundleNameForSelf();
468#endif
469    return std::nullopt;
470}
471
472bool GetFileDataFromFilePath(const std::string& filePath, std::string& fileData)
473{
474#if !defined(IOS_PLATFORM) && !defined(ANDROID_PLATFORM)
475    std::error_code error;
476    auto path = std::filesystem::absolute(filePath, error);
477    if (error) {
478        NETSTACK_LOGE("Failed to obtain the absolute path: %{public}s", error.message().c_str());
479        return false;
480    }
481    std::ifstream file(path);
482#else
483    std::ifstream file(filePath);
484#endif
485    if (file.is_open()) {
486        std::stringstream buffer;
487        buffer << file.rdbuf();
488        file.close();
489        fileData = buffer.str();
490        return true;
491    } else {
492        NETSTACK_LOGE("Failed to obtain the file data stream.");
493        return false;
494    }
495}
496} // namespace OHOS::NetStack::CommonUtils