1/*
2 * Copyright (c) 2023 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 "file_path_utils.h"
17
18#include <fstream>
19#include <regex>
20#include <vector>
21#include "constants.h"
22#include <atomic>
23#include <chrono>
24#include <climits>
25
26#ifdef WINDOWS_PLATFORM
27#include <io.h>
28
29namespace {
30char* realpath(const char* path, char* resolvedPath)
31{
32    if (_access(path, 0) < 0) {
33        return nullptr;
34    }
35    if (strcpy_s(resolvedPath, PATH_MAX, path) != 0) {
36        return nullptr;
37    }
38    return resolvedPath;
39}
40}
41#endif
42
43namespace panda {
44namespace ecmascript {
45namespace {
46constexpr char EXT_NAME_ABC[] = ".abc";
47constexpr char EXT_NAME_ETS[] = ".ets";
48constexpr char EXT_NAME_TS[] = ".ts";
49constexpr char EXT_NAME_JS[] = ".js";
50constexpr char PREFIX_BUNDLE[] = "@bundle:";
51constexpr char PREFIX_MODULE[] = "@module:";
52constexpr char PREFIX_LOCAL[] = "@local:";
53constexpr char NPM_PATH_SEGMENT[] = "node_modules";
54constexpr char NPM_ENTRY_FILE[] = "index.abc";
55constexpr char NPM_ENTRY_LINK[] = "entry.txt";
56constexpr char BUNDLE_INSTALL_PATH[] = "/data/storage/el1/bundle/";
57constexpr char OTHER_BUNDLE_INSTALL_PATH[] = "/data/bundles/";
58
59constexpr size_t MAX_NPM_LEVEL = 1;
60constexpr size_t SEGMENTS_LIMIT_TWO = 2;
61constexpr size_t SEGMENTS_LIMIT_THREE = 3;
62} // namespace
63
64bool StringStartWith(const std::string& str, const char* startStr, size_t startStrLen)
65{
66    return ((str.length() >= startStrLen) && (str.compare(0, startStrLen, startStr) == 0));
67}
68
69bool StringEndWith(const std::string& str, const char* endStr, size_t endStrLen)
70{
71    size_t len = str.length();
72    return ((len >= endStrLen) && (str.compare(len - endStrLen, endStrLen, endStr) == 0));
73}
74
75void SplitString(const std::string& str, std::vector<std::string>& out, size_t pos, const char* seps)
76{
77    if (str.empty() || pos >= str.length()) {
78        return;
79    }
80
81    size_t startPos = pos;
82    size_t endPos = 0;
83    while ((endPos = str.find_first_of(seps, startPos)) != std::string::npos) {
84        if (endPos > startPos) {
85            out.emplace_back(str.substr(startPos, endPos - startPos));
86        }
87        startPos = endPos + 1;
88    }
89
90    if (startPos < str.length()) {
91        out.emplace_back(str.substr(startPos));
92    }
93}
94
95std::string JoinString(const std::vector<std::string>& strs, char sep, size_t startIndex)
96{
97    std::string out;
98    for (size_t index = startIndex; index < strs.size(); ++index) {
99        if (!strs[index].empty()) {
100            out.append(strs[index]) += sep;
101        }
102    }
103    if (!out.empty()) {
104        out.pop_back();
105    }
106    return out;
107}
108
109std::string StripString(const std::string& str, const char* charSet)
110{
111    size_t startPos = str.find_first_not_of(charSet);
112    if (startPos == std::string::npos) {
113        return std::string();
114    }
115
116    return str.substr(startPos, str.find_last_not_of(charSet) - startPos + 1);
117}
118
119void FixExtName(std::string& path)
120{
121    if (path.empty()) {
122        return;
123    }
124
125    if (StringEndWith(path, EXT_NAME_ABC, sizeof(EXT_NAME_ABC) - 1)) {
126        return;
127    }
128
129    if (StringEndWith(path, EXT_NAME_ETS, sizeof(EXT_NAME_ETS) - 1)) {
130        path.erase(path.length() - (sizeof(EXT_NAME_ETS) - 1), sizeof(EXT_NAME_ETS) - 1);
131    } else if (StringEndWith(path, EXT_NAME_TS, sizeof(EXT_NAME_TS) - 1)) {
132        path.erase(path.length() - (sizeof(EXT_NAME_TS) - 1), sizeof(EXT_NAME_TS) - 1);
133    } else if (StringEndWith(path, EXT_NAME_JS, sizeof(EXT_NAME_JS) - 1)) {
134        path.erase(path.length() - (sizeof(EXT_NAME_JS) - 1), sizeof(EXT_NAME_JS) - 1);
135    }
136
137    path.append(EXT_NAME_ABC);
138}
139
140std::string GetInstallPath(const std::string& curJsModulePath, bool module)
141{
142    size_t pos = std::string::npos;
143    if (StringStartWith(curJsModulePath, BUNDLE_INSTALL_PATH, std::string(BUNDLE_INSTALL_PATH).length())) {
144        pos = std::string(BUNDLE_INSTALL_PATH).length() - 1;
145    } else {
146        if (!StringStartWith(curJsModulePath, OTHER_BUNDLE_INSTALL_PATH,
147            std::string(OTHER_BUNDLE_INSTALL_PATH).length())) {
148            return std::string();
149        }
150
151        pos = curJsModulePath.find('/', std::string(OTHER_BUNDLE_INSTALL_PATH).length());
152        if (pos == std::string::npos) {
153            return std::string();
154        }
155    }
156
157    if (module) {
158        pos = curJsModulePath.find('/', pos + 1);
159        if (pos == std::string::npos) {
160            return std::string();
161        }
162    }
163
164    return curJsModulePath.substr(0, pos + 1);
165}
166
167std::string MakeNewJsModulePath(
168    const std::string& curJsModulePath, const std::string& newJsModuleUri)
169{
170    std::string moduleInstallPath = GetInstallPath(curJsModulePath, true);
171    if (moduleInstallPath.empty()) {
172        return std::string();
173    }
174
175    std::vector<std::string> pathVector;
176    SplitString(curJsModulePath, pathVector, moduleInstallPath.length());
177
178    if (pathVector.empty()) {
179        return std::string();
180    }
181
182    // Remove file name, reserve only dir name
183    pathVector.pop_back();
184
185    std::vector<std::string> relativePathVector;
186    SplitString(newJsModuleUri, relativePathVector);
187
188    for (auto& value : relativePathVector) {
189        if (value == ".") {
190            continue;
191        } else if (value == "..") {
192            if (pathVector.empty()) {
193                return std::string();
194            }
195            pathVector.pop_back();
196        } else {
197            pathVector.emplace_back(std::move(value));
198        }
199    }
200
201    std::string jsModulePath = moduleInstallPath + JoinString(pathVector, '/');
202    FixExtName(jsModulePath);
203    if (jsModulePath.size() >= PATH_MAX) {
204        return std::string();
205    }
206
207    char path[PATH_MAX];
208    if (realpath(jsModulePath.c_str(), path) != nullptr) {
209        return std::string(path);
210    }
211    return std::string();
212}
213
214std::string FindNpmPackageInPath(const std::string& npmPath)
215{
216    std::string fileName = npmPath + "/" + NPM_ENTRY_FILE;
217
218    char path[PATH_MAX];
219    if (fileName.size() >= PATH_MAX) {
220        return std::string();
221    }
222    if (realpath(fileName.c_str(), path) != nullptr) {
223        return path;
224    }
225
226    fileName = npmPath + "/" + NPM_ENTRY_LINK;
227    if (fileName.size() >= PATH_MAX) {
228        return std::string();
229    }
230    if (realpath(fileName.c_str(), path) == nullptr) {
231        return std::string();
232    }
233
234    std::ifstream stream(path, std::ios::ate);
235    if (!stream.is_open()) {
236        return std::string();
237    }
238
239    auto fileLen = stream.tellg();
240    if (fileLen >= PATH_MAX) {
241        return std::string();
242    }
243
244    stream.seekg(0);
245    stream.read(path, fileLen);
246    path[fileLen] = '\0';
247    stream.close();
248
249    std::string npmPackagePath = npmPath + '/' + StripString(path);
250    if (npmPackagePath.size() >= PATH_MAX) {
251        return std::string();
252    }
253    if (realpath(npmPackagePath.c_str(), path) == nullptr) {
254        return std::string();
255    }
256    return path;
257}
258
259std::string FindNpmPackageInTopLevel(
260    const std::string& moduleInstallPath, const std::string& npmPackage, size_t start)
261{
262    for (size_t level = start; level <= MAX_NPM_LEVEL; ++level) {
263        std::string path = moduleInstallPath + NPM_PATH_SEGMENT + '/' + std::to_string(level) + '/' + npmPackage;
264        path = FindNpmPackageInPath(path);
265        if (!path.empty()) {
266            return path;
267        }
268    }
269
270    return std::string();
271}
272
273std::string FindNpmPackage(const std::string& curJsModulePath, const std::string& npmPackage)
274{
275    std::string newJsModulePath = MakeNewJsModulePath(curJsModulePath, npmPackage);
276    if (!newJsModulePath.empty()) {
277        return newJsModulePath;
278    }
279    std::string moduleInstallPath = GetInstallPath(curJsModulePath);
280    if (moduleInstallPath.empty()) {
281        return std::string();
282    }
283    std::vector<std::string> pathVector;
284    SplitString(curJsModulePath, pathVector, moduleInstallPath.length());
285    if (pathVector.empty()) {
286        return std::string();
287    }
288
289    if (pathVector[0] != NPM_PATH_SEGMENT) {
290        return FindNpmPackageInTopLevel(moduleInstallPath, npmPackage);
291    }
292
293    // Remove file name, reserve only dir name
294    pathVector.pop_back();
295
296    // Find npm package until reach top level npm path such as 'node_modules/0',
297    // so there must be 2 element in vector
298    while (pathVector.size() > 2) {
299        std::string path =
300            moduleInstallPath + JoinString(pathVector, '/') + '/' + NPM_PATH_SEGMENT + '/' + npmPackage;
301        path = FindNpmPackageInPath(path);
302        if (!path.empty()) {
303            return path;
304        }
305
306        pathVector.pop_back();
307    }
308
309    char* p = nullptr;
310    size_t index = std::strtoul(pathVector.back().c_str(), &p, 10);
311    if (p == nullptr || *p != '\0') {
312        return std::string();
313    }
314
315    return FindNpmPackageInTopLevel(moduleInstallPath, npmPackage, index);
316}
317
318std::string ParseOhmUri(
319    const std::string& originBundleName, const std::string& curJsModulePath, const std::string& newJsModuleUri)
320{
321    std::string moduleInstallPath;
322    std::vector<std::string> pathVector;
323    size_t index = 0;
324
325    if (StringStartWith(newJsModuleUri, PREFIX_BUNDLE, sizeof(PREFIX_BUNDLE) - 1)) {
326        SplitString(newJsModuleUri, pathVector, sizeof(PREFIX_BUNDLE) - 1);
327
328        // Uri should have atleast 3 segments
329        if (pathVector.size() < SEGMENTS_LIMIT_THREE) {
330            return std::string();
331        }
332
333        const auto& bundleName = pathVector[index++];
334        if (bundleName == originBundleName) {
335            moduleInstallPath = std::string(BUNDLE_INSTALL_PATH);
336        } else {
337            moduleInstallPath = std::string(OTHER_BUNDLE_INSTALL_PATH);
338            moduleInstallPath.append(bundleName).append("/");
339        }
340        moduleInstallPath.append(pathVector[index++]).append("/");
341    } else if (StringStartWith(newJsModuleUri, PREFIX_MODULE, sizeof(PREFIX_MODULE) - 1)) {
342        SplitString(newJsModuleUri, pathVector, sizeof(PREFIX_MODULE) - 1);
343
344        // Uri should have atleast 2 segments
345        if (pathVector.size() < SEGMENTS_LIMIT_TWO) {
346            return std::string();
347        }
348
349        moduleInstallPath = GetInstallPath(curJsModulePath, false);
350        if (moduleInstallPath.empty()) {
351            return std::string();
352        }
353        moduleInstallPath.append(pathVector[index++]).append("/");
354    } else if (StringStartWith(newJsModuleUri, PREFIX_LOCAL, sizeof(PREFIX_LOCAL) - 1)) {
355        SplitString(newJsModuleUri, pathVector, sizeof(PREFIX_LOCAL) - 1);
356
357        if (pathVector.empty()) {
358            return std::string();
359        }
360
361        moduleInstallPath = GetInstallPath(curJsModulePath);
362        if (moduleInstallPath.empty()) {
363            return std::string();
364        }
365    } else {
366        return std::string();
367    }
368
369    if (pathVector[index] != NPM_PATH_SEGMENT) {
370        return moduleInstallPath + JoinString(pathVector, '/', index);
371    }
372
373    return FindNpmPackageInTopLevel(moduleInstallPath, JoinString(pathVector, '/', index + 1));
374}
375
376std::string NormalizeUri(
377    const std::string& bundleName, const std::string& curJsModulePath, const std::string& newJsModuleUri)
378{
379    std::string newJsModulePath;
380    if (curJsModulePath.empty() || newJsModuleUri.empty()) {
381        return newJsModulePath;
382    }
383
384    std::string normalizeUri = newJsModuleUri;
385    std::replace(normalizeUri.begin(), normalizeUri.end(), '\\', '/');
386
387    switch (normalizeUri[0]) {
388        case '.': {
389            newJsModulePath = MakeNewJsModulePath(curJsModulePath, normalizeUri);
390            break;
391        }
392        case '@': {
393            newJsModulePath = ParseOhmUri(bundleName, curJsModulePath, normalizeUri);
394            if (newJsModulePath.empty()) {
395                newJsModulePath = FindNpmPackage(curJsModulePath, normalizeUri);
396            }
397            break;
398        }
399        default: {
400            newJsModulePath = FindNpmPackage(curJsModulePath, normalizeUri);
401            break;
402        }
403    }
404
405    FixExtName(newJsModulePath);
406    return newJsModulePath;
407}
408
409bool MakeFilePath(const std::string& codePath, const std::string& modulePath, std::string& fileName)
410{
411    std::string path(codePath);
412    path.append("/").append(modulePath);
413    if (path.length() > PATH_MAX) {
414        return false;
415    }
416    char resolvedPath[PATH_MAX + 1] = { 0 };
417    if (realpath(path.c_str(), resolvedPath) != nullptr) {
418        fileName = resolvedPath;
419        return true;
420    }
421
422    auto start = path.find_last_of('/');
423    auto end = path.find_last_of('.');
424    if (end == std::string::npos || end == 0) {
425        return false;
426    }
427
428    auto pos = path.find_last_of('.', end - 1);
429    if (pos == std::string::npos) {
430        return false;
431    }
432
433    path.erase(start + 1, pos - start);
434
435    if (realpath(path.c_str(), resolvedPath) == nullptr) {
436        return false;
437    }
438
439    fileName = resolvedPath;
440    return true;
441}
442
443std::string GetLoadPath(const std::string& hapPath)
444{
445    std::regex hapPattern(std::string(Constants::ABS_CODE_PATH) + std::string(Constants::FILE_SEPARATOR));
446    std::string loadPath = std::regex_replace(hapPath, hapPattern, "");
447    loadPath = std::string(Constants::LOCAL_CODE_PATH) + std::string(Constants::FILE_SEPARATOR) +
448        loadPath.substr(loadPath.find(std::string(Constants::FILE_SEPARATOR)) + 1);
449    return loadPath;
450}
451
452std::string GetRelativePath(const std::string& srcPath)
453{
454    if (srcPath.empty() || srcPath[0] != '/') {
455        return srcPath;
456    }
457    std::regex srcPattern(Constants::LOCAL_CODE_PATH);
458    std::string relativePath = std::regex_replace(srcPath, srcPattern, "");
459    if (relativePath.find(Constants::FILE_SEPARATOR) == 0) {
460        relativePath = relativePath.substr(1);
461        relativePath = relativePath.substr(relativePath.find(std::string(Constants::FILE_SEPARATOR)) + 1);
462    }
463    return relativePath;
464}
465}  // namespace AbilityBase
466}  // namespace OHOS
467