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 "jsi_module_searcher.h"
17 
18 #include <algorithm>
19 #include <fstream>
20 #include <vector>
21 
22 #include "base/utils/string_utils.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::Ace::Framework {
42 namespace {
43 constexpr char PREFIX_BUNDLE[] = "@bundle:";
44 constexpr char PREFIX_MODULE[] = "@module:";
45 constexpr char PREFIX_LOCAL[] = "@local:";
46 
47 constexpr char NPM_PATH_SEGMENT[] = "node_modules";
48 constexpr char DIST_PATH_SEGMENT[] = "dist";
49 
50 constexpr char NPM_ENTRY_FILE[] = "index.abc";
51 constexpr char NPM_ENTRY_LINK[] = "entry.txt";
52 
53 #if defined(WINDOWS_PLATFORM)
54 constexpr char SEPERATOR[] = "\\";
55 constexpr char SOURCE_ASSETS_PATH[] = "\\assets\\default";
56 constexpr char NODE_MODULES_PATH[] = "\\assets_esmodule\\default\\node_modules\\0";
57 #else
58 constexpr char SEPERATOR[] = "/";
59 constexpr char SOURCE_ASSETS_PATH[] = "/assets/default";
60 constexpr char NODE_MODULES_PATH[] = "/assets_esmodule/default/node_modules/0";
61 #endif
62 
63 constexpr char EXT_NAME_ABC[] = ".abc";
64 constexpr char EXT_NAME_ETS[] = ".ets";
65 constexpr char EXT_NAME_TS[] = ".ts";
66 constexpr char EXT_NAME_JS[] = ".js";
67 
68 constexpr size_t MAX_NPM_LEVEL = 1;
69 
SplitString(const std::string & str,std::vector<std::string> & out,size_t pos=0,const char * seps=SEPERATOR)70 void SplitString(const std::string& str, std::vector<std::string>& out, size_t pos = 0, const char* seps = SEPERATOR)
71 {
72     if (str.empty() || pos >= str.length()) {
73         return;
74     }
75 
76     size_t startPos = pos;
77     size_t endPos = 0;
78     while ((endPos = str.find_first_of(seps, startPos)) != std::string::npos) {
79         if (endPos > startPos) {
80             out.emplace_back(str.substr(startPos, endPos - startPos));
81         }
82         startPos = endPos + 1;
83     }
84 
85     if (startPos < str.length()) {
86         out.emplace_back(str.substr(startPos));
87     }
88 }
89 
JoinString(const std::vector<std::string> & strs,const char * sep,size_t startIndex=0)90 std::string JoinString(const std::vector<std::string>& strs, const char* sep, size_t startIndex = 0)
91 {
92     std::string out;
93     for (size_t index = startIndex; index < strs.size(); ++index) {
94         if (!strs[index].empty()) {
95             out.append(strs[index]) += sep;
96         }
97     }
98     if (!out.empty()) {
99         out.pop_back();
100     }
101     return out;
102 }
103 
StripString(const std::string & str,const char * charSet="")104 inline std::string StripString(const std::string& str, const char* charSet = " \t\n\r")
105 {
106     size_t startPos = str.find_first_not_of(charSet);
107     if (startPos == std::string::npos) {
108         return std::string();
109     }
110 
111     return str.substr(startPos, str.find_last_not_of(charSet) - startPos + 1);
112 }
113 }
114 
JsiModuleSearcher(const std::string & bundleName,const std::string & assetPath)115 JsiModuleSearcher::JsiModuleSearcher(const std::string& bundleName, const std::string& assetPath)
116 {
117     std::vector<std::string> pathVector;
118     SplitString(assetPath, pathVector);
119     // pop "ets" directory from path
120     pathVector.pop_back();
121     // pop "default" directory from path
122     pathVector.pop_back();
123     // pop "assets" directory from path
124     pathVector.pop_back();
125     bundleInstallPath_ = JoinString(pathVector, SEPERATOR);
126     bundleName_ = bundleName;
127 }
128 
operator ()(const std::string & curJsModulePath,const std::string & newJsModuleUri) const129 std::string JsiModuleSearcher::operator()(const std::string& curJsModulePath, const std::string& newJsModuleUri) const
130 {
131     std::string newJsModulePath;
132 
133     if (curJsModulePath.empty() || newJsModuleUri.empty()) {
134         return newJsModulePath;
135     }
136 
137     switch (newJsModuleUri[0]) {
138         case '.': {
139             newJsModulePath = MakeNewJsModulePath(curJsModulePath, newJsModuleUri);
140             break;
141         }
142         case '@': {
143             newJsModulePath = ParseOhmUri(curJsModulePath, newJsModuleUri);
144             if (newJsModulePath.empty()) {
145                 newJsModulePath = FindNpmPackage(curJsModulePath, newJsModuleUri);
146             }
147             break;
148         }
149         default: {
150             newJsModulePath = FindNpmPackage(curJsModulePath, newJsModuleUri);
151             break;
152         }
153     }
154     FixExtName(newJsModulePath);
155 
156     return newJsModulePath;
157 }
158 
FixExtName(std::string & path)159 void JsiModuleSearcher::FixExtName(std::string& path)
160 {
161     if (path.empty()) {
162         return;
163     }
164 
165     if (StringUtils::EndWith(path, EXT_NAME_ABC, sizeof(EXT_NAME_ABC) - 1)) {
166         return;
167     }
168 
169     if (StringUtils::EndWith(path, EXT_NAME_ETS, sizeof(EXT_NAME_ETS) - 1)) {
170         path.erase(path.length() - (sizeof(EXT_NAME_ETS) - 1), sizeof(EXT_NAME_ETS) - 1);
171     } else if (StringUtils::EndWith(path, EXT_NAME_TS, sizeof(EXT_NAME_TS) - 1)) {
172         path.erase(path.length() - (sizeof(EXT_NAME_TS) - 1), sizeof(EXT_NAME_TS) - 1);
173     } else if (StringUtils::EndWith(path, EXT_NAME_JS, sizeof(EXT_NAME_JS) - 1)) {
174         path.erase(path.length() - (sizeof(EXT_NAME_JS) - 1), sizeof(EXT_NAME_JS) - 1);
175     }
176 
177     path.append(EXT_NAME_ABC);
178 }
179 
GetInstallPath(const std::string & curJsModulePath,bool module) const180 std::string JsiModuleSearcher::GetInstallPath(const std::string& curJsModulePath, bool module) const
181 {
182     size_t pos = std::string::npos;
183     if (StringUtils::StartWith(curJsModulePath, bundleInstallPath_.c_str(), bundleInstallPath_.length())) {
184         pos = bundleInstallPath_.length() - 1;
185     }
186 
187     if (module) {
188         pos = curJsModulePath.find(SEPERATOR, pos + 1);
189         if (pos == std::string::npos) {
190             return std::string();
191         }
192     }
193 
194     return curJsModulePath.substr(0, pos + 1);
195 }
196 
MakeNewJsModulePath(const std::string & curJsModulePath,const std::string & newJsModuleUri) const197 std::string JsiModuleSearcher::MakeNewJsModulePath(
198     const std::string& curJsModulePath, const std::string& newJsModuleUri) const
199 {
200     std::string moduleInstallPath = GetInstallPath(curJsModulePath, true);
201     if (moduleInstallPath.empty()) {
202         return std::string();
203     }
204 
205     std::vector<std::string> pathVector;
206     SplitString(curJsModulePath, pathVector, moduleInstallPath.length());
207 
208     if (pathVector.empty()) {
209         return std::string();
210     }
211 
212     // Remove file name, reserve only dir name
213     pathVector.pop_back();
214 
215     std::vector<std::string> relativePathVector;
216     SplitString(newJsModuleUri, relativePathVector);
217 
218     for (auto& value : relativePathVector) {
219         if (value == ".") {
220             continue;
221         } else if (value == "..") {
222             if (pathVector.empty()) {
223                 return std::string();
224             }
225             pathVector.pop_back();
226         } else {
227             pathVector.emplace_back(std::move(value));
228         }
229     }
230 
231     std::string jsModulePath = moduleInstallPath + JoinString(pathVector, SEPERATOR);
232     FixExtName(jsModulePath);
233     if (jsModulePath.size() >= PATH_MAX) {
234         return std::string();
235     }
236 
237     char path[PATH_MAX];
238     if (realpath(jsModulePath.c_str(), path) != nullptr) {
239         return std::string(path);
240     }
241     return std::string();
242 }
243 
FindNpmPackageInPath(const std::string & npmPath) const244 std::string JsiModuleSearcher::FindNpmPackageInPath(const std::string& npmPath) const
245 {
246     std::string fileName = npmPath + SEPERATOR + DIST_PATH_SEGMENT + SEPERATOR + NPM_ENTRY_FILE;
247 
248     char path[PATH_MAX];
249     if (fileName.size() >= PATH_MAX) {
250         return std::string();
251     }
252     if (realpath(fileName.c_str(), path) != nullptr) {
253         return path;
254     }
255 
256     fileName = npmPath + SEPERATOR + NPM_ENTRY_LINK;
257     if (fileName.size() >= PATH_MAX) {
258         return std::string();
259     }
260     if (realpath(fileName.c_str(), path) == nullptr) {
261         return std::string();
262     }
263 
264     std::ifstream stream(path, std::ios::ate);
265     if (!stream.is_open()) {
266         return std::string();
267     }
268 
269     auto fileLen = stream.tellg();
270     if (fileLen >= PATH_MAX) {
271         return std::string();
272     }
273 
274     stream.seekg(0);
275     stream.read(path, fileLen);
276     path[fileLen] = '\0';
277     return npmPath + SEPERATOR + StripString(path);
278 }
279 
FindNpmPackageInTopLevel(const std::string & moduleInstallPath,const std::string & npmPackage,size_t start) const280 std::string JsiModuleSearcher::FindNpmPackageInTopLevel(
281     const std::string& moduleInstallPath, const std::string& npmPackage, size_t start) const
282 {
283     for (size_t level = start; level <= MAX_NPM_LEVEL; ++level) {
284         std::string path =
285             moduleInstallPath + NPM_PATH_SEGMENT + SEPERATOR + std::to_string(level) + SEPERATOR + npmPackage;
286         path = FindNpmPackageInPath(path);
287         if (!path.empty()) {
288             return path;
289         }
290     }
291 
292     return std::string();
293 }
294 
FindNpmPackage(const std::string & curJsModulePath,const std::string & npmPackage) const295 std::string JsiModuleSearcher::FindNpmPackage(const std::string& curJsModulePath, const std::string& npmPackage) const
296 {
297     std::string moduleInstallPath = bundleInstallPath_;
298     moduleInstallPath.append(NODE_MODULES_PATH).append(SEPERATOR);
299 
300     std::string path = moduleInstallPath + npmPackage;
301     path = FindNpmPackageInPath(path);
302     if (!path.empty()) {
303         return path;
304     }
305     return std::string();
306 }
307 
ParseOhmUri(const std::string & curJsModulePath,const std::string & newJsModuleUri) const308 std::string JsiModuleSearcher::ParseOhmUri(const std::string& curJsModulePath, const std::string& newJsModuleUri) const
309 {
310     std::string moduleInstallPath;
311     std::vector<std::string> pathVector;
312     size_t index = 0;
313 
314     if (StringUtils::StartWith(newJsModuleUri, PREFIX_BUNDLE, sizeof(PREFIX_BUNDLE) - 1)) {
315         SplitString(newJsModuleUri, pathVector, sizeof(PREFIX_BUNDLE) - 1);
316 
317         // Uri should have atleast 3 segments
318         if (pathVector.size() < 3) {
319             return std::string();
320         }
321 
322         const auto& bundleName = pathVector[index];
323         // skip hapName for preview has no hap directory
324         index = index + 2; // skip 2 of directory segments
325         if (bundleName == bundleName_) {
326             moduleInstallPath = bundleInstallPath_;
327         }
328         moduleInstallPath.append(SOURCE_ASSETS_PATH).append(SEPERATOR);
329         moduleInstallPath.append(pathVector[index++]).append(SEPERATOR);
330     } else if (StringUtils::StartWith(newJsModuleUri, PREFIX_MODULE, sizeof(PREFIX_MODULE) - 1)) {
331         SplitString(newJsModuleUri, pathVector, sizeof(PREFIX_MODULE) - 1);
332 
333         // Uri should have atleast 2 segments
334         if (pathVector.size() < 2) {
335             return std::string();
336         }
337 
338         moduleInstallPath = GetInstallPath(curJsModulePath, false);
339         if (moduleInstallPath.empty()) {
340             return std::string();
341         }
342         moduleInstallPath.append(pathVector[index++]).append(SEPERATOR);
343     } else if (StringUtils::StartWith(newJsModuleUri, PREFIX_LOCAL, sizeof(PREFIX_LOCAL) - 1)) {
344         SplitString(newJsModuleUri, pathVector, sizeof(PREFIX_LOCAL) - 1);
345 
346         if (pathVector.empty()) {
347             return std::string();
348         }
349 
350         moduleInstallPath = GetInstallPath(curJsModulePath);
351         if (moduleInstallPath.empty()) {
352             return std::string();
353         }
354     } else {
355         return std::string();
356     }
357 
358     if (pathVector[index] != NPM_PATH_SEGMENT) {
359         return moduleInstallPath + JoinString(pathVector, SEPERATOR, index);
360     }
361 
362     return FindNpmPackageInTopLevel(moduleInstallPath, JoinString(pathVector, SEPERATOR, index + 1));
363 }
364 } // namespace OHOS::Ace::Framework