1 /*
2 * Copyright (c) 2021-2025 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 #if HAS_NETMANAGER_BASE
40 #include <openssl/evp.h>
41 #endif // HAS_NETMANAGER_BASE
42
43 #include "netstack_log.h"
44 #if !defined(WINDOWS_PLATFORM) && !defined(MAC_PLATFORM) && !defined(IOS_PLATFORM) && !defined(ANDROID_PLATFORM)
45 #include "netstack_apipolicy_utils.h"
46 #include "netstack_bundle_utils.h"
47 #endif
48 #if HAS_NETMANAGER_BASE
49 #include "net_conn_client.h"
50 #endif // HAS_NETMANAGER_BASE
51
52 constexpr int32_t INET_OPTION_SUC = 1;
53 constexpr size_t MAX_DISPLAY_NUM = 2;
54 #if HAS_NETMANAGER_BASE
55 constexpr unsigned int SHA256_LEN = 32;
56 #endif
57 constexpr int SHA256_BASE64_LEN = 44; // 32-byte base64 -> 44 bytes
58 constexpr int PINNED_PREFIX_LEN = 8; // strlen("sha256//")
59
60 namespace OHOS::NetStack::CommonUtils {
61 const std::regex IP_PATTERN{
62 "((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)"};
63 const std::regex IP_MASK_PATTERN{
64 "((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)/"
65 "(3[0-2]|[1-2]\\d|\\d)"};
66 const std::regex IPV6_PATTERN{"([\\da-fA-F]{0,4}:){2,7}([\\da-fA-F]{0,4})"};
67 const 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])"};
68 static const std::string PROTOCOL_WSS = "wss";
69 std::mutex g_commonUtilsMutex;
70
Split(const std::string & str,const std::string & sep)71 std::vector<std::string> Split(const std::string &str, const std::string &sep)
72 {
73 std::string s = str;
74 std::vector<std::string> res;
75 while (!s.empty()) {
76 auto pos = s.find(sep);
77 if (pos == std::string::npos) {
78 res.emplace_back(s);
79 break;
80 }
81 res.emplace_back(s.substr(0, pos));
82 s = s.substr(pos + sep.size());
83 }
84 return res;
85 }
86
Split(const std::string & str,const std::string & sep,size_t size)87 std::vector<std::string> Split(const std::string &str, const std::string &sep, size_t size)
88 {
89 std::string s = str;
90 std::vector<std::string> res;
91 while (!s.empty()) {
92 if (res.size() + 1 == size) {
93 res.emplace_back(s);
94 break;
95 }
96
97 auto pos = s.find(sep);
98 if (pos == std::string::npos) {
99 res.emplace_back(s);
100 break;
101 }
102 res.emplace_back(s.substr(0, pos));
103 s = s.substr(pos + sep.size());
104 }
105 return res;
106 }
107
Strip(const std::string & str,char ch)108 std::string Strip(const std::string &str, char ch)
109 {
110 int64_t i = 0;
111 while (static_cast<size_t>(i) < str.size() && str[i] == ch) {
112 ++i;
113 }
114 int64_t j = static_cast<int64_t>(str.size()) - 1;
115 while (j > 0 && str[j] == ch) {
116 --j;
117 }
118 if (i >= 0 && static_cast<size_t>(i) < str.size() && j >= 0 && static_cast<size_t>(j) < str.size() &&
119 j - i + 1 > 0) {
120 return str.substr(i, j - i + 1);
121 }
122 return "";
123 }
124
ToLower(const std::string & s)125 std::string ToLower(const std::string &s)
126 {
127 std::string res = s;
128 std::transform(res.begin(), res.end(), res.begin(), tolower);
129 return res;
130 }
131
ToString(const std::list<std::string> & lists,char tab)132 std::string ToString(const std::list<std::string> &lists, char tab)
133 {
134 std::string str;
135 for (auto it = lists.begin(); it != lists.end(); ++it) {
136 if (it != lists.begin()) {
137 str.append(1, tab);
138 }
139 str.append(*it);
140 }
141 return str;
142 }
143
HasInternetPermission()144 bool HasInternetPermission()
145 {
146 #ifndef OH_CORE_NETSTACK_PERMISSION_CHECK
147 #ifdef FUZZ_TEST
148 return true;
149 #endif
150 #ifdef DT_TEST
151 return true;
152 #endif
153 int testSock = socket(AF_INET, SOCK_STREAM, 0);
154 if (testSock < 0 && errno == EPERM) {
155 NETSTACK_LOGE("make tcp testSock failed errno is %{public}d %{public}s", errno, strerror(errno));
156 return false;
157 }
158 if (testSock > 0) {
159 close(testSock);
160 }
161 return true;
162 #else
163 constexpr int inetGroup = 40002003; // 3003 in gateway shell.
164 int groupNum = getgroups(0, nullptr);
165 if (groupNum <= 0) {
166 NETSTACK_LOGE("no group of INTERNET permission");
167 return false;
168 }
169 auto groups = (gid_t *)malloc(groupNum * sizeof(gid_t));
170 if (groups == nullptr) {
171 NETSTACK_LOGE("INTERNET permission denied by malloc");
172 return false;
173 }
174 groupNum = getgroups(groupNum, groups);
175 for (int i = 0; i < groupNum; i++) {
176 if (groups[i] == inetGroup) {
177 free(groups);
178 return true;
179 }
180 }
181 free(groups);
182 NETSTACK_LOGE("INTERNET permission denied by group");
183 return false;
184 #endif
185 }
186
IsAtomicService(std::string & bundleName)187 bool IsAtomicService(std::string &bundleName)
188 {
189 #if !defined(WINDOWS_PLATFORM) && !defined(MAC_PLATFORM) && !defined(IOS_PLATFORM) && !defined(ANDROID_PLATFORM)
190 return BundleUtils::IsAtomicService(bundleName);
191 #else
192 return false;
193 #endif
194 }
195
IsAllowedHostname(const std::string & bundleName,const std::string & domainType,const std::string & url)196 bool IsAllowedHostname(const std::string &bundleName, const std::string &domainType, const std::string &url)
197 {
198 #if !defined(WINDOWS_PLATFORM) && !defined(MAC_PLATFORM) && !defined(IOS_PLATFORM) && !defined(ANDROID_PLATFORM)
199 if (bundleName.empty()) {
200 NETSTACK_LOGE("isAllowedHostnameForAtomicService bundleName is empty");
201 return true;
202 }
203 auto hostname = GetHostnameWithProtocolAndPortFromURL(url);
204 if (hostname.empty()) {
205 NETSTACK_LOGE("isAllowedHostnameForAtomicService url hostname is empty");
206 return true;
207 }
208 return ApiPolicyUtils::IsAllowedHostname(bundleName, domainType, hostname);
209 #else
210 return true;
211 #endif
212 }
213
EndsWith(const std::string & str,const std::string & suffix)214 bool EndsWith(const std::string &str, const std::string &suffix)
215 {
216 if (str.length() < suffix.length()) {
217 return false;
218 }
219 return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0;
220 }
221
Trim(std::string str)222 std::string Trim(std::string str)
223 {
224 size_t start = str.find_first_not_of(" \t\n\r");
225 size_t end = str.find_last_not_of(" \t\n\r");
226 if (start == std::string::npos || end == std::string::npos) {
227 return "";
228 } else {
229 return str.substr(start, end - start + 1);
230 }
231 }
232
IsMatch(const std::string & str,const std::string & patternStr)233 bool IsMatch(const std::string &str, const std::string &patternStr)
234 {
235 if (patternStr.empty()) {
236 return false;
237 }
238 if (patternStr == "*") {
239 return true;
240 }
241 if (!IsRegexValid(patternStr)) {
242 NETSTACK_LOGD("Invalid pattern");
243 return patternStr == str;
244 }
245 std::regex pattern(ReplaceCharacters(patternStr));
246 bool isMacth = patternStr != "" && std::regex_match(str, pattern);
247 if (isMacth) {
248 NETSTACK_LOGD("Match patternStr");
249 }
250 return isMacth;
251 }
252
InsertCharBefore(const std::string & input,const char from,const char preChar,const char nextChar)253 std::string InsertCharBefore(const std::string &input, const char from, const char preChar, const char nextChar)
254 {
255 std::string output = input;
256 char arr[] = {preChar, from};
257 unsigned long strSize = sizeof(arr) / sizeof(arr[0]);
258 std::string str(arr, strSize);
259 std::size_t pos = output.find(from);
260 std::size_t length = output.length();
261 while (pos <= length - 1 && pos != std::string::npos) {
262 char nextCharTemp = pos == length - 1 ? '\0' : output[pos + 1];
263 if (nextChar == '\0' || nextCharTemp == '\0' || nextCharTemp != nextChar) {
264 output.replace(pos, 1, str);
265 length += (strSize - 1);
266 }
267 pos = output.find(from, pos + strSize);
268 }
269 return output;
270 }
271
ReplaceCharacters(const std::string & input)272 std::string ReplaceCharacters(const std::string &input)
273 {
274 std::string output = InsertCharBefore(input, '*', '.', '\0');
275 output = InsertCharBefore(output, '.', '\\', '*');
276 return output;
277 }
278
IsRegexValid(const std::string & regex)279 bool IsRegexValid(const std::string ®ex)
280 {
281 if (Trim(regex).empty()) {
282 return false;
283 }
284 return regex_match(regex, std::regex("^[a-zA-Z0-9\\-_\\.*]+$"));
285 }
286
GetProtocolFromURL(const std::string & url)287 std::string GetProtocolFromURL(const std::string &url)
288 {
289 std::string delimiter = "://";
290 size_t pos = url.find(delimiter);
291 if (pos != std::string::npos) {
292 return url.substr(0, pos);
293 }
294 return "";
295 }
296
GetPortFromURL(const std::string & url)297 std::string GetPortFromURL(const std::string &url)
298 {
299 std::string delimiter = "://";
300 std::string protocol = GetProtocolFromURL(url);
301 std::string hostname = GetHostnameFromURL(url);
302 size_t start = protocol.empty() ? hostname.size() : protocol.size() + delimiter.size() + hostname.size();
303 size_t posStart = url.find_first_of(':', start);
304 if (posStart == std::string::npos) {
305 return "";
306 }
307 size_t posEnd = std::min({url.find('/', start), url.find('?', start)});
308 if (posEnd == std::string::npos) {
309 return url.substr(posStart + 1);
310 }
311 if (posStart > posEnd) {
312 return "";
313 }
314 return url.substr(posStart + 1, posEnd - posStart - 1);
315 }
316
GetHostnameFromURL(const std::string & url)317 std::string GetHostnameFromURL(const std::string &url)
318 {
319 if (url.empty()) {
320 return "";
321 }
322 std::string delimiter = "://";
323 std::string tempUrl = url;
324 std::replace(tempUrl.begin(), tempUrl.end(), '\\', '/');
325 size_t posStart = tempUrl.find(delimiter);
326 if (posStart != std::string::npos) {
327 posStart += delimiter.length();
328 } else {
329 posStart = 0;
330 }
331 size_t notSlash = tempUrl.find_first_not_of('/', posStart);
332 if (notSlash != std::string::npos) {
333 posStart = notSlash;
334 }
335 size_t posEnd = std::min({ tempUrl.find(':', posStart),
336 tempUrl.find('/', posStart), tempUrl.find('?', posStart) });
337 if (posEnd != std::string::npos) {
338 return tempUrl.substr(posStart, posEnd - posStart);
339 }
340 return tempUrl.substr(posStart);
341 }
342
GetHostnameWithProtocolAndPortFromURL(const std::string & url)343 std::string GetHostnameWithProtocolAndPortFromURL(const std::string& url)
344 {
345 std::string delimiter = "://";
346 std::string portDelimiter = ":";
347 auto hostname = GetHostnameFromURL(url);
348 if (!hostname.empty()) {
349 std::string protocol = GetProtocolFromURL(url);
350 if (!protocol.empty()) {
351 hostname = protocol + delimiter + hostname;
352 }
353 if (protocol != PROTOCOL_WSS) {
354 std::string port = GetPortFromURL(url);
355 if (!port.empty()) {
356 hostname += portDelimiter + port;
357 }
358 }
359 }
360 return hostname;
361 }
362
IsExcluded(const std::string & str,const std::string & exclusions,const std::string & split)363 bool IsExcluded(const std::string &str, const std::string &exclusions, const std::string &split)
364 {
365 if (Trim(exclusions).empty()) {
366 return false;
367 }
368 std::size_t start = 0;
369 std::size_t end = exclusions.find(split);
370 while (end != std::string::npos) {
371 if (end - start > 0 && IsMatch(str, Trim(exclusions.substr(start, end - start)))) {
372 return true;
373 }
374 start = end + 1;
375 end = exclusions.find(split, start);
376 }
377 return IsMatch(str, Trim(exclusions.substr(start)));
378 }
379
IsHostNameExcluded(const std::string & url,const std::string & exclusions,const std::string & split)380 bool IsHostNameExcluded(const std::string &url, const std::string &exclusions, const std::string &split)
381 {
382 std::string hostName = GetHostnameFromURL(url);
383 return IsExcluded(hostName, exclusions, split);
384 }
385
DetectIPType(const std::string & ip)386 int DetectIPType(const std::string &ip)
387 {
388 if (ip.empty()) {
389 return INVALID_IP_TYPE;
390 }
391 if (IsValidIPV4(ip)) {
392 return AF_INET;
393 }
394 if (IsValidIPV6(ip)) {
395 return AF_INET6;
396 }
397 return INVALID_IP_TYPE;
398 }
399
IsValidIPV4(const std::string & ip)400 bool IsValidIPV4(const std::string &ip)
401 {
402 return IsValidIP(ip, AF_INET);
403 }
404
IsValidIPV6(const std::string & ip)405 bool IsValidIPV6(const std::string &ip)
406 {
407 return IsValidIP(ip, AF_INET6);
408 }
409
IsValidIP(const std::string & ip,int af)410 bool IsValidIP(const std::string& ip, int af)
411 {
412 if (ip.empty()) {
413 return false;
414 }
415 #ifdef WINDOWS_PLATFORM
416 if (af == AF_INET6) {
417 struct sockaddr_in6 sa;
418 return inet_pton(af, ip.c_str(), &(sa.sin6_addr)) == INET_OPTION_SUC;
419 } else {
420 struct sockaddr_in sa;
421 return inet_pton(af, ip.c_str(), &(sa.sin_addr)) == INET_OPTION_SUC;
422 }
423 #else
424 if (af == AF_INET6) {
425 struct in6_addr addr;
426 return inet_pton(af, ip.c_str(), reinterpret_cast<void *>(&addr)) == INET_OPTION_SUC;
427 } else {
428 struct in_addr addr;
429 return inet_pton(af, ip.c_str(), reinterpret_cast<void *>(&addr)) == INET_OPTION_SUC;
430 }
431 #endif
432 }
433
MaskIpv4(std::string & maskedResult)434 std::string MaskIpv4(std::string &maskedResult)
435 {
436 int maxDisplayNum = MAX_DISPLAY_NUM;
437 for (char &i : maskedResult) {
438 if (i == '/') {
439 break;
440 }
441 if (maxDisplayNum > 0) {
442 if (i == '.') {
443 maxDisplayNum--;
444 }
445 } else {
446 if (i != '.') {
447 i = '*';
448 }
449 }
450 }
451 return maskedResult;
452 }
453
MaskIpv6(std::string & maskedResult)454 std::string MaskIpv6(std::string &maskedResult)
455 {
456 size_t colonCount = 0;
457 for (char &i : maskedResult) {
458 if (i == '/') {
459 break;
460 }
461 if (i == ':') {
462 colonCount++;
463 }
464
465 if (colonCount >= MAX_DISPLAY_NUM) {
466 if (i != ':') {
467 i = '*';
468 }
469 }
470 }
471 return maskedResult;
472 }
473
AnonymizeIp(std::string & input)474 std::string AnonymizeIp(std::string &input)
475 {
476 if (input.empty()) {
477 return input;
478 }
479 std::lock_guard<std::mutex> lock(g_commonUtilsMutex);
480 std::string maskedResult{input};
481 if (std::regex_match(maskedResult, IP_PATTERN) || std::regex_match(maskedResult, IP_MASK_PATTERN)) {
482 return MaskIpv4(maskedResult);
483 }
484 if (std::regex_match(maskedResult, IPV6_PATTERN) || std::regex_match(maskedResult, IPV6_MASK_PATTERN)) {
485 return MaskIpv6(maskedResult);
486 }
487 return input;
488 }
489
GetBundleName()490 std::optional<std::string> GetBundleName()
491 {
492 #if HAS_NETMANAGER_BASE
493 return OHOS::NetManagerStandard::NetConnClient::ObtainBundleNameForSelf();
494 #endif
495 return std::nullopt;
496 }
497
GetFileDataFromFilePath(const std::string & filePath,std::string & fileData)498 bool GetFileDataFromFilePath(const std::string& filePath, std::string& fileData)
499 {
500 #if !defined(IOS_PLATFORM) && !defined(ANDROID_PLATFORM)
501 std::error_code error;
502 auto path = std::filesystem::absolute(filePath, error);
503 if (error) {
504 NETSTACK_LOGE("Failed to obtain the absolute path: %{public}s", error.message().c_str());
505 return false;
506 }
507 std::ifstream file(path);
508 #else
509 std::ifstream file(filePath);
510 #endif
511 if (file.is_open()) {
512 std::stringstream buffer;
513 buffer << file.rdbuf();
514 file.close();
515 fileData = buffer.str();
516 return true;
517 } else {
518 NETSTACK_LOGE("Failed to obtain the file data stream.");
519 return false;
520 }
521 }
522
Sha256sum(unsigned char * buf,size_t buflen,std::string & digestStr)523 bool Sha256sum(unsigned char *buf, size_t buflen, std::string &digestStr)
524 {
525 #ifdef HAS_NETMANAGER_BASE
526 EVP_MD_CTX *mdctx = EVP_MD_CTX_create();
527 unsigned int digestLen = 0;
528 unsigned char digest[SHA256_LEN];
529 unsigned char out[SHA256_BASE64_LEN + 1] = {0};
530 if (!mdctx) {
531 NETSTACK_LOGE("create MD_CTX failed.");
532 return false;
533 }
534 if (!EVP_DigestInit(mdctx, EVP_sha256())) {
535 NETSTACK_LOGE("EVP_DigestInit failed.");
536 return false;
537 }
538 if (!EVP_DigestUpdate(mdctx, buf, buflen)) {
539 NETSTACK_LOGE("EVP_DigestUpdate failed.");
540 return false;
541 }
542 if (!EVP_DigestFinal_ex(mdctx, digest, &digestLen)) {
543 NETSTACK_LOGE("EVP_DigestFinal_ex failed.");
544 return false;
545 }
546 EVP_MD_CTX_free(mdctx);
547 if (digestLen != SHA256_LEN) {
548 NETSTACK_LOGE("SHA256 length invalid");
549 return false;
550 }
551 int base64Len = EVP_EncodeBlock(out, digest, SHA256_LEN);
552 if (base64Len != SHA256_BASE64_LEN) {
553 NETSTACK_LOGE("SHA256-Base64 length invalid.");
554 return false;
555 }
556 digestStr = std::string(reinterpret_cast<const char *>(out), SHA256_BASE64_LEN);
557 return true;
558 #else
559 return false;
560 #endif
561 }
562
IsCertPubKeyInPinned(const std::string & certPubKeyDigest,const std::string & pinnedPubkey)563 bool IsCertPubKeyInPinned(const std::string &certPubKeyDigest, const std::string &pinnedPubkey)
564 {
565 auto begin = pinnedPubkey.find("sha256//");
566 if (begin != 0) {
567 NETSTACK_LOGE("pinnedPubkey format invalid, should start with sha256//");
568 return false;
569 }
570 while (begin < pinnedPubkey.size()) {
571 auto end = pinnedPubkey.find(";", begin);
572 if (end == std::string::npos) {
573 end = pinnedPubkey.size();
574 }
575 if (pinnedPubkey.find("sha256//", begin) != begin) {
576 NETSTACK_LOGE("pinnedPubkey format invalid, should be like sha256//[hash1];sha256//[hash2]");
577 begin = end + 1;
578 continue;
579 }
580 if (end - begin != PINNED_PREFIX_LEN + SHA256_BASE64_LEN) {
581 NETSTACK_LOGE("pinnedPubkey format invalid, hash length not match");
582 begin = end + 1;
583 continue;
584 }
585 std::string candidate = pinnedPubkey.substr(begin + PINNED_PREFIX_LEN, SHA256_BASE64_LEN);
586 if (candidate == certPubKeyDigest) {
587 return true;
588 }
589 begin = end + 1;
590 }
591 return false;
592 }
593
IsCleartextPermitted(const std::string & url,const std::string & protocol)594 bool IsCleartextPermitted(const std::string &url, const std::string &protocol)
595 {
596 bool isCleartextPermitted = true;
597 #if HAS_NETMANAGER_BASE
598 using namespace OHOS::NetManagerStandard;
599 if (url.find(protocol) != std::string::npos) {
600 std::string hostName = GetHostnameFromURL(url);
601 NetConnClient::GetInstance().IsCleartextPermitted(hostName, isCleartextPermitted);
602 }
603 #endif
604 return isCleartextPermitted;
605 }
606 } // namespace OHOS::NetStack::CommonUtils