f8d31f2bcf755a9fc59afc404a8b1c7e815366ea
[platform/framework/web/lwnode.git] / src / node_native_module_lwnode-inl.h
1 /*
2  * Copyright (c) 2021-present Samsung Electronics Co., Ltd
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #pragma once
18
19 #include <unzip.h>
20 #include <codecvt>
21 #include <locale>
22 #include <map>
23 #include "lwnode-loader.h"
24 #include "lwnode.h"
25 #include "node_native_module.h"
26 #include "trace.h"
27
28 using namespace LWNode;
29
30 namespace node {
31 namespace native_module {
32 using v8::Isolate;
33 using v8::Local;
34 using v8::MaybeLocal;
35 using v8::String;
36
37 class ArchiveFileScope {
38  public:
39   ArchiveFileScope() = default;
40   ArchiveFileScope(const char* path) { open(path); }
41   ~ArchiveFileScope() {
42     if (file_) {
43       unzClose(file_);
44     }
45   }
46
47   void open(const char* path) {
48     if (file_ == nullptr) {
49       file_ = unzOpen(path);
50     }
51   }
52
53   bool isFileOpened() { return (file_ != nullptr) ? true : false; }
54   unzFile file() { return file_; }
55
56  private:
57   unzFile file_{nullptr};
58 };
59
60 struct UnzFileCachedInfo {
61   unz_file_pos position{0, 0};
62   uLong uncompressedSize{0};
63 };
64
65 static ArchiveFileScope s_archiveFileScope;
66 static std::map<std::string, UnzFileCachedInfo> s_unzFileInfoDictionary;
67
68 std::string getSelfProcPath() {
69   char path[PATH_MAX];
70   ssize_t length = readlink("/proc/self/exe", path, PATH_MAX);
71   if (length < 0) {
72     ERROR_AND_ABORT("readlink fails");
73   }
74   path[length] = '\0';
75   return std::string(path);
76 }
77
78 bool readCurrentFileFromArchive(const unzFile file,
79                                 uLong uncompressedSize,
80                                 char** buffer,
81                                 size_t* fileSize) {
82   if (unzOpenCurrentFile(file) < 0) {
83     return false;
84   }
85
86   *fileSize = uncompressedSize;
87   *buffer = (char*)allocateStringBuffer(uncompressedSize + 1);
88
89   bool result = true;
90
91   if (unzReadCurrentFile(file, *buffer, *fileSize) < 0) {
92     freeStringBuffer(buffer);
93     buffer = nullptr;
94     result = false;
95   }
96
97   (*buffer)[*fileSize] = '\0';
98
99   unzCloseCurrentFile(file);
100   return result;
101 }
102
103 bool readFileFromArchive(const std::string& archiveFilename,
104                          const std::string& filename,
105                          char** buffer,
106                          size_t* fileSize) {
107   DCHECK_NOT_NULL(buffer);
108   DCHECK_NOT_NULL(fileSize);
109
110   if (s_archiveFileScope.isFileOpened() == false) {
111     s_archiveFileScope.open(archiveFilename.c_str());
112   }
113
114   const unzFile file = s_archiveFileScope.file();
115   CHECK_NOT_NULL(file);
116
117   // 1.1 check if the cache on this filename exists
118   const auto& it = s_unzFileInfoDictionary.find(filename);
119   if (it != s_unzFileInfoDictionary.end()) {
120     UnzFileCachedInfo cache = it->second;
121
122     // 1.2 move the file position using the cached info and read the data
123     unzGoToFilePos(file, &cache.position);
124     return readCurrentFileFromArchive(
125         file, cache.uncompressedSize, buffer, fileSize);
126   }
127
128   // 2. read the data by searching the file position from the first one.
129   if (unzGoToFirstFile(file) < 0) {
130     return false;
131   }
132
133   bool isFileFound = false;
134   do {
135     unz_file_info fileInfo;
136     char currentFileName[PATH_MAX];
137
138     if (unzGetCurrentFileInfo(file,
139                               &fileInfo,
140                               currentFileName,
141                               sizeof(currentFileName),
142                               nullptr,
143                               0,
144                               nullptr,
145                               0) < 0) {
146       return false;
147     }
148
149     if (filename.compare(currentFileName) == 0) {
150       isFileFound = true;
151
152       // 2.1 read the data from the current file poistion
153       if (readCurrentFileFromArchive(
154               file, fileInfo.uncompressed_size, buffer, fileSize) == false) {
155         return false;
156       }
157
158       // 2.2 create the cache for this file and register it to the dictionary
159       UnzFileCachedInfo cache;
160       auto result = unzGetFilePos(file, &cache.position);
161       CHECK(result == UNZ_OK);
162       cache.uncompressedSize = fileInfo.uncompressed_size;
163
164       s_unzFileInfoDictionary[filename] = cache;
165       break;
166     }
167
168   } while (unzGoToNextFile(file) != UNZ_END_OF_LIST_OF_FILE);
169
170   return isFileFound;
171 }
172
173 FileData readFileFromArchive(std::string filename,
174                              const Encoding encodingHint) {
175   CHECK(encodingHint != UNKNOWN);
176
177   size_t bufferSize = 0;
178   char* buffer = nullptr;
179
180   static std::string s_externalBuiltinsPath;
181
182   if (s_externalBuiltinsPath.empty()) {
183     std::string executablePath = getSelfProcPath();
184     executablePath = executablePath.substr(0, executablePath.rfind('/') + 1);
185     s_externalBuiltinsPath = executablePath + LWNODE_EXTERNAL_BUILTINS_FILENAME;
186   }
187
188   if (readFileFromArchive(
189           s_externalBuiltinsPath, filename, &buffer, &bufferSize) == false) {
190     return FileData();
191   }
192
193   std::unique_ptr<void, std::function<void(void*)>> bufferHolder(
194       buffer, freeStringBuffer);
195
196   Loader::U8String latin1String;
197   Encoding encoding = UNKNOWN;
198
199   if (encodingHint == ONE_BYTE) {
200     encoding = ONE_BYTE;
201   } else if (encodingHint == TWO_BYTE) {
202     encoding = TWO_BYTE;
203   } else if (encodingHint == ONE_BYTE_LATIN1) {
204     Loader::tryConvertUTF8ToLatin1(
205         latin1String, encoding, (uint8_t*)buffer, bufferSize, encodingHint);
206   } else {
207     CHECK(encodingHint == UNKNOWN);
208     Loader::tryConvertUTF8ToLatin1(
209         latin1String, encoding, (uint8_t*)buffer, bufferSize, encodingHint);
210   }
211
212   if (encoding == TWO_BYTE) {
213     // Treat non-latin1 as UTF-8 and encode it as UTF-16 Little Endian.
214     if (encodingHint == UNKNOWN) {
215       LWNODE_LOG_INFO("%s contains characters outside of the Latin1 range.",
216                       filename.c_str());
217     }
218
219     char* newStringBuffer = nullptr;
220     size_t newStringBufferSize = 0;
221
222     bool isConverted = convertUTF8ToUTF16le(&newStringBuffer,
223                                             &newStringBufferSize,
224                                             (const char*)bufferHolder.get(),
225                                             bufferSize);
226     if (isConverted == false) {
227       return FileData();
228     }
229
230     bufferHolder.reset(newStringBuffer);
231     bufferSize = newStringBufferSize;
232   } else {
233     if (encoding == ONE_BYTE_LATIN1) {
234       if (encodingHint == UNKNOWN) {
235         LWNODE_LOG_INFO("%s contains Latin1 characters.", filename.c_str());
236       }
237
238       bufferSize = latin1String.length();
239       bufferHolder.reset(allocateStringBuffer(bufferSize + 1));
240       ((uint8_t*)bufferHolder.get())[bufferSize] = '\0';
241
242       memcpy(bufferHolder.get(), latin1String.data(), bufferSize);
243     }
244   }
245
246   return FileData(bufferHolder.release(), bufferSize, encoding);
247 }
248
249 bool NativeModuleLoader::IsOneByte(const char* id) {
250   const auto& it = source_.find(id);
251   if (it == source_.end()) {
252     CHECK(false);
253   }
254   return it->second.is_one_byte();
255 }
256
257 static std::string getFileNameOnArchive(const char* id) {
258   std::string filename;
259   if (strncmp(id, "internal/deps", strlen("internal/deps")) == 0) {
260     id += strlen("internal/");
261   } else {
262     filename += "lib/";
263   }
264   filename += id;
265   filename += ".js";
266   return filename;
267 }
268
269 struct Stat {
270   int loaded{0};
271   int reloaded{0};
272 };
273 static Stat s_stat;
274
275 MaybeLocal<String> NativeModuleLoader::LoadExternalBuiltinSource(
276     Isolate* isolate, const char* id) {
277   std::string filename = getFileNameOnArchive(id);
278
279   FileData fileData =
280       readFileFromArchive(filename, (IsOneByte(id) ? ONE_BYTE : TWO_BYTE));
281
282   if (fileData.buffer == nullptr) {
283     ERROR_AND_ABORT("Failed to open builtins");
284     return MaybeLocal<String>();
285   }
286
287   auto data = Loader::ReloadableSourceData::create(
288       filename, fileData.buffer, fileData.size, fileData.encoding);
289
290   return Loader::NewReloadableString(
291       isolate,
292       data,
293       // Load-ReloadableSource
294       [](void* userData) -> void* {
295         auto data = reinterpret_cast<Loader::ReloadableSourceData*>(userData);
296
297         LWNODE_LOG_INFO("  Load: %d (%d) %p %s (+%.2f kB)",
298                         ++s_stat.loaded,
299                         s_stat.reloaded,
300                         data->preloadedData,
301                         data->path(),
302                         (float)data->preloadedDataLength() / 1024);
303
304         if (data->preloadedData) {
305           auto buffer = data->preloadedData;
306           data->preloadedData = nullptr;
307           return buffer;  // move memory ownership to js engine
308         }
309
310         s_stat.reloaded++;
311
312         FileData fileData = readFileFromArchive(data->path(), data->encoding());
313
314         CHECK_NOT_NULL(fileData.buffer);
315         return fileData.buffer;
316       },
317       // Unload-ReloadableSource
318       [](void* preloadedData, void* userData) -> void {
319         auto data = reinterpret_cast<Loader::ReloadableSourceData*>(userData);
320
321         LWNODE_LOG_INFO("Unload: %d (%d) %p %s (-%.2f kB)",
322                         --s_stat.loaded,
323                         s_stat.reloaded,
324                         preloadedData,
325                         data->path(),
326                         (float)data->preloadedDataLength() / 1024);
327
328         if (data->preloadedData) {
329           freeStringBuffer(data->preloadedData);
330           data->preloadedData = nullptr;
331         }
332         freeStringBuffer(preloadedData);
333       });
334 }
335
336 }  // namespace native_module
337 }  // namespace node