• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/log/log.h"
23 #include "base/utils/string_utils.h"
24 
25 #ifdef WINDOWS_PLATFORM
26 #include <io.h>
27 
28 namespace {
realpath(const char * path,char * resolvedPath)29 char* realpath(const char* path, char* resolvedPath)
30 {
31     if (_access(path, 0) < 0) {
32         return nullptr;
33     }
34     if (strcpy_s(resolvedPath, PATH_MAX, path) != 0) {
35         return nullptr;
36     }
37     return resolvedPath;
38 }
39 }
40 #endif
41 
42 namespace OHOS::Ace::Framework {
43 namespace {
44 constexpr char PREFIX_BUNDLE[] = "@bundle:";
45 constexpr char PREFIX_MODULE[] = "@module:";
46 constexpr char PREFIX_LOCAL[] = "@local:";
47 
48 constexpr char NPM_PATH_SEGMENT[] = "node_modules";
49 constexpr char DIST_PATH_SEGMENT[] = "dist";
50 
51 constexpr char NPM_ENTRY_FILE[] = "index.abc";
52 constexpr char NPM_ENTRY_LINK[] = "entry.txt";
53 
54 #if defined(WINDOWS_PLATFORM)
55 constexpr char SEPERATOR[] = "\\";
56 constexpr char SOURCE_ASSETS_PATH[] = "\\assets\\default";
57 constexpr char NODE_MODULES_PATH[] = "\\assets_esmodule\\default\\node_modules\\0";
58 #else
59 constexpr char SEPERATOR[] = "/";
60 constexpr char SOURCE_ASSETS_PATH[] = "/assets/default";
61 constexpr char NODE_MODULES_PATH[] = "/assets_esmodule/default/node_modules/0";
62 #endif
63 
64 constexpr char EXT_NAME_ABC[] = ".abc";
65 constexpr char EXT_NAME_ETS[] = ".ets";
66 constexpr char EXT_NAME_TS[] = ".ts";
67 constexpr char EXT_NAME_JS[] = ".js";
68 
69 constexpr size_t MAX_NPM_LEVEL = 1;
70 
SplitString(const std::string & str,std::vector<std::string> & out,size_t pos=0,const char * seps=SEPERATOR)71 void SplitString(const std::string& str, std::vector<std::string>& out, size_t pos = 0, const char* seps = SEPERATOR)
72 {
73     if (str.empty() || pos >= str.length()) {
74         return;
75     }
76 
77     size_t startPos = pos;
78     size_t endPos = 0;
79     while ((endPos = str.find_first_of(seps, startPos)) != std::string::npos) {
80         if (endPos > startPos) {
81             out.emplace_back(str.substr(startPos, endPos - startPos));
82         }
83         startPos = endPos + 1;
84     }
85 
86     if (startPos < str.length()) {
87         out.emplace_back(str.substr(startPos));
88     }
89 }
90 
JoinString(const std::vector<std::string> & strs,const char * sep,size_t startIndex=0)91 std::string JoinString(const std::vector<std::string>& strs, const char* sep, size_t startIndex = 0)
92 {
93     std::string out;
94     for (size_t index = startIndex; index < strs.size(); ++index) {
95         if (!strs[index].empty()) {
96             out.append(strs[index]) += sep;
97         }
98     }
99     if (!out.empty()) {
100         out.pop_back();
101     }
102     return out;
103 }
104 
StripString(const std::string & str,const char * charSet=" \\t\\n\\r")105 inline std::string StripString(const std::string& str, const char* charSet = " \t\n\r")
106 {
107     size_t startPos = str.find_first_not_of(charSet);
108     if (startPos == std::string::npos) {
109         return std::string();
110     }
111 
112     return str.substr(startPos, str.find_last_not_of(charSet) - startPos + 1);
113 }
114 }
115 
JsiModuleSearcher(const std::string & bundleName,const std::string & assetPath)116 JsiModuleSearcher::JsiModuleSearcher(const std::string& bundleName, const std::string& assetPath)
117 {
118     std::vector<std::string> pathVector;
119     SplitString(assetPath, pathVector);
120     // pop "ets" directory from path
121     pathVector.pop_back();
122     // pop "default" directory from path
123     pathVector.pop_back();
124     // pop "assets" directory from path
125     pathVector.pop_back();
126     bundleInstallPath_ = JoinString(pathVector, SEPERATOR);
127     bundleName_ = bundleName;
128 }
129 
operator ()(const std::string & curJsModulePath,const std::string & newJsModuleUri) const130 std::string JsiModuleSearcher::operator()(const std::string& curJsModulePath, const std::string& newJsModuleUri) const
131 {
132     LOGD("Search JS module (%{public}s, %{public}s) begin",
133         curJsModulePath.c_str(), newJsModuleUri.c_str());
134 
135     std::string newJsModulePath;
136 
137     if (curJsModulePath.empty() || newJsModuleUri.empty()) {
138         return newJsModulePath;
139     }
140 
141     switch (newJsModuleUri[0]) {
142         case '.': {
143             newJsModulePath = MakeNewJsModulePath(curJsModulePath, newJsModuleUri);
144             break;
145         }
146         case '@': {
147             newJsModulePath = ParseOhmUri(curJsModulePath, newJsModuleUri);
148             if (newJsModulePath.empty()) {
149                 newJsModulePath = FindNpmPackage(curJsModulePath, newJsModuleUri);
150             }
151             break;
152         }
153         default: {
154             newJsModulePath = FindNpmPackage(curJsModulePath, newJsModuleUri);
155             break;
156         }
157     }
158 
159     FixExtName(newJsModulePath);
160 
161     LOGD("Search JS module (%{public}s, %{public}s) => %{public}s end",
162         curJsModulePath.c_str(), newJsModuleUri.c_str(), newJsModulePath.c_str());
163 
164     return newJsModulePath;
165 }
166 
FixExtName(std::string & path)167 void JsiModuleSearcher::FixExtName(std::string& path)
168 {
169     if (path.empty()) {
170         return;
171     }
172 
173     if (StringUtils::EndWith(path, EXT_NAME_ABC, sizeof(EXT_NAME_ABC) - 1)) {
174         return;
175     }
176 
177     if (StringUtils::EndWith(path, EXT_NAME_ETS, sizeof(EXT_NAME_ETS) - 1)) {
178         path.erase(path.length() - (sizeof(EXT_NAME_ETS) - 1), sizeof(EXT_NAME_ETS) - 1);
179     } else if (StringUtils::EndWith(path, EXT_NAME_TS, sizeof(EXT_NAME_TS) - 1)) {
180         path.erase(path.length() - (sizeof(EXT_NAME_TS) - 1), sizeof(EXT_NAME_TS) - 1);
181     } else if (StringUtils::EndWith(path, EXT_NAME_JS, sizeof(EXT_NAME_JS) - 1)) {
182         path.erase(path.length() - (sizeof(EXT_NAME_JS) - 1), sizeof(EXT_NAME_JS) - 1);
183     }
184 
185     path.append(EXT_NAME_ABC);
186 }
187 
GetInstallPath(const std::string & curJsModulePath,bool module) const188 std::string JsiModuleSearcher::GetInstallPath(const std::string& curJsModulePath, bool module) const
189 {
190     size_t pos = std::string::npos;
191     if (StringUtils::StartWith(curJsModulePath, bundleInstallPath_.c_str(), bundleInstallPath_.length())) {
192         pos = bundleInstallPath_.length() - 1;
193     }
194 
195     if (module) {
196         pos = curJsModulePath.find(SEPERATOR, pos + 1);
197         if (pos == std::string::npos) {
198             return std::string();
199         }
200     }
201 
202     return curJsModulePath.substr(0, pos + 1);
203 }
204 
MakeNewJsModulePath(const std::string & curJsModulePath,const std::string & newJsModuleUri) const205 std::string JsiModuleSearcher::MakeNewJsModulePath(
206     const std::string& curJsModulePath, const std::string& newJsModuleUri) const
207 {
208     std::string moduleInstallPath = GetInstallPath(curJsModulePath, true);
209     if (moduleInstallPath.empty()) {
210         return std::string();
211     }
212 
213     std::vector<std::string> pathVector;
214     SplitString(curJsModulePath, pathVector, moduleInstallPath.length());
215 
216     if (pathVector.empty()) {
217         return std::string();
218     }
219 
220     // Remove file name, reserve only dir name
221     pathVector.pop_back();
222 
223     std::vector<std::string> relativePathVector;
224     SplitString(newJsModuleUri, relativePathVector);
225 
226     for (auto& value : relativePathVector) {
227         if (value == ".") {
228             continue;
229         } else if (value == "..") {
230             if (pathVector.empty()) {
231                 return std::string();
232             }
233             pathVector.pop_back();
234         } else {
235             pathVector.emplace_back(std::move(value));
236         }
237     }
238 
239     std::string jsModulePath = moduleInstallPath + JoinString(pathVector, SEPERATOR);
240     FixExtName(jsModulePath);
241     if (jsModulePath.size() >= PATH_MAX) {
242         return std::string();
243     }
244 
245     char path[PATH_MAX];
246     if (realpath(jsModulePath.c_str(), path) != nullptr) {
247         return std::string(path);
248     }
249     return std::string();
250 }
251 
FindNpmPackageInPath(const std::string & npmPath) const252 std::string JsiModuleSearcher::FindNpmPackageInPath(const std::string& npmPath) const
253 {
254     std::string fileName = npmPath + SEPERATOR + DIST_PATH_SEGMENT + SEPERATOR + NPM_ENTRY_FILE;
255 
256     char path[PATH_MAX];
257     if (fileName.size() >= PATH_MAX) {
258         return std::string();
259     }
260     if (realpath(fileName.c_str(), path) != nullptr) {
261         return path;
262     }
263 
264     fileName = npmPath + SEPERATOR + NPM_ENTRY_LINK;
265     if (fileName.size() >= PATH_MAX) {
266         return std::string();
267     }
268     if (realpath(fileName.c_str(), path) == nullptr) {
269         return std::string();
270     }
271 
272     std::ifstream stream(path, std::ios::ate);
273     if (!stream.is_open()) {
274         return std::string();
275     }
276 
277     auto fileLen = stream.tellg();
278     if (fileLen >= PATH_MAX) {
279         return std::string();
280     }
281 
282     stream.seekg(0);
283     stream.read(path, fileLen);
284     path[fileLen] = '\0';
285     return npmPath + SEPERATOR + StripString(path);
286 }
287 
FindNpmPackageInTopLevel(const std::string & moduleInstallPath,const std::string & npmPackage,size_t start) const288 std::string JsiModuleSearcher::FindNpmPackageInTopLevel(
289     const std::string& moduleInstallPath, const std::string& npmPackage, size_t start) const
290 {
291     for (size_t level = start; level <= MAX_NPM_LEVEL; ++level) {
292         std::string path =
293             moduleInstallPath + NPM_PATH_SEGMENT + SEPERATOR + std::to_string(level) + SEPERATOR + npmPackage;
294         path = FindNpmPackageInPath(path);
295         if (!path.empty()) {
296             return path;
297         }
298     }
299 
300     return std::string();
301 }
302 
FindNpmPackage(const std::string & curJsModulePath,const std::string & npmPackage) const303 std::string JsiModuleSearcher::FindNpmPackage(const std::string& curJsModulePath, const std::string& npmPackage) const
304 {
305     std::string moduleInstallPath = bundleInstallPath_;
306     moduleInstallPath.append(NODE_MODULES_PATH).append(SEPERATOR);
307 
308     std::string path = moduleInstallPath + npmPackage;
309     path = FindNpmPackageInPath(path);
310     if (!path.empty()) {
311         return path;
312     }
313     LOGE("Find npm package failed");
314     return std::string();
315 }
316 
ParseOhmUri(const std::string & curJsModulePath,const std::string & newJsModuleUri) const317 std::string JsiModuleSearcher::ParseOhmUri(const std::string& curJsModulePath, const std::string& newJsModuleUri) const
318 {
319     std::string moduleInstallPath;
320     std::vector<std::string> pathVector;
321     size_t index = 0;
322 
323     if (StringUtils::StartWith(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() < 3) {
328             return std::string();
329         }
330 
331         const auto& bundleName = pathVector[index];
332         // skip hapName for preview has no hap directory
333         index = index + 2; // skip 2 of directory segments
334         if (bundleName == bundleName_) {
335             moduleInstallPath = bundleInstallPath_;
336         }
337         moduleInstallPath.append(SOURCE_ASSETS_PATH).append(SEPERATOR);
338         moduleInstallPath.append(pathVector[index++]).append(SEPERATOR);
339     } else if (StringUtils::StartWith(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() < 2) {
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(SEPERATOR);
352     } else if (StringUtils::StartWith(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, SEPERATOR, index);
369     }
370 
371     return FindNpmPackageInTopLevel(moduleInstallPath, JoinString(pathVector, SEPERATOR, index + 1));
372 }
373 } // namespace OHOS::Ace::Framework