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