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