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