1 | // Copyright 2013 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | #include "tonic/file_loader/file_loader.h" |
6 | |
7 | #include <iostream> |
8 | #include <memory> |
9 | #include <utility> |
10 | |
11 | #include "filesystem/file.h" |
12 | #include "filesystem/path.h" |
13 | #include "filesystem/portable_unistd.h" |
14 | #include "tonic/common/macros.h" |
15 | #include "tonic/converter/dart_converter.h" |
16 | #include "tonic/parsers/packages_map.h" |
17 | |
18 | namespace tonic { |
19 | namespace { |
20 | |
21 | constexpr char kDartScheme[] = "dart:" ; |
22 | |
23 | constexpr char kFileScheme[] = "file:" ; |
24 | constexpr size_t kFileSchemeLength = sizeof(kFileScheme) - 1; |
25 | |
26 | constexpr char kPackageScheme[] = "package:" ; |
27 | constexpr size_t kPackageSchemeLength = sizeof(kPackageScheme) - 1; |
28 | |
29 | // Extract the scheme prefix ('package:' or 'file:' from ) |
30 | std::string (std::string url) { |
31 | if (url.find(kPackageScheme) == 0u) |
32 | return kPackageScheme; |
33 | if (url.find(kFileScheme) == 0u) |
34 | return kFileScheme; |
35 | return std::string(); |
36 | } |
37 | |
38 | // Extract the path from a package: or file: url. |
39 | std::string (std::string url) { |
40 | if (url.find(kPackageScheme) == 0u) |
41 | return url.substr(kPackageSchemeLength); |
42 | if (url.find(kFileScheme) == 0u) |
43 | return url.substr(kFileSchemeLength); |
44 | return url; |
45 | } |
46 | |
47 | } // namespace |
48 | |
49 | FileLoader::FileLoader(int dirfd) : dirfd_(dirfd) {} |
50 | |
51 | FileLoader::~FileLoader() { |
52 | for (auto kernel_buffer : kernel_buffers_) |
53 | free(kernel_buffer); |
54 | |
55 | if (dirfd_ >= 0) |
56 | close(dirfd_); |
57 | } |
58 | |
59 | std::string FileLoader::SanitizeURIEscapedCharacters(const std::string& str) { |
60 | std::string result; |
61 | result.reserve(str.size()); |
62 | for (std::string::size_type i = 0; i < str.size(); ++i) { |
63 | if (str[i] == '%') { |
64 | if (i > str.size() - 3 || !isxdigit(str[i + 1]) || !isxdigit(str[i + 2])) |
65 | return "" ; |
66 | const std::string hex = str.substr(i + 1, 2); |
67 | const unsigned char c = strtoul(hex.c_str(), nullptr, 16); |
68 | if (!c) |
69 | return "" ; |
70 | result += c; |
71 | i += 2; |
72 | } else { |
73 | result += str[i]; |
74 | } |
75 | } |
76 | return result; |
77 | } |
78 | |
79 | bool FileLoader::LoadPackagesMap(const std::string& packages) { |
80 | packages_ = packages; |
81 | std::string packages_source; |
82 | if (!ReadFileToString(packages_, &packages_source)) { |
83 | tonic::Log("error: Unable to load .packages file '%s'." , packages_.c_str()); |
84 | return false; |
85 | } |
86 | packages_map_.reset(new PackagesMap()); |
87 | std::string error; |
88 | if (!packages_map_->Parse(packages_source, &error)) { |
89 | tonic::Log("error: Unable to parse .packages file '%s'. %s" , |
90 | packages_.c_str(), error.c_str()); |
91 | return false; |
92 | } |
93 | return true; |
94 | } |
95 | |
96 | std::string FileLoader::GetFilePathForPackageURL(std::string url) { |
97 | if (!packages_map_) |
98 | return std::string(); |
99 | TONIC_DCHECK(url.find(kPackageScheme) == 0u); |
100 | url = url.substr(kPackageSchemeLength); |
101 | |
102 | size_t slash = url.find(FileLoader::kPathSeparator); |
103 | if (slash == std::string::npos) |
104 | return std::string(); |
105 | std::string package = url.substr(0, slash); |
106 | std::string library_path = url.substr(slash + 1); |
107 | std::string package_path = packages_map_->Resolve(package); |
108 | if (package_path.empty()) |
109 | return std::string(); |
110 | if (package_path.find(FileLoader::kFileURLPrefix) == 0u) |
111 | return SanitizePath(package_path.substr(FileLoader::kFileURLPrefixLength) + |
112 | library_path); |
113 | return filesystem::GetDirectoryName(filesystem::AbsolutePath(packages_)) + |
114 | FileLoader::kPathSeparator + package_path + |
115 | FileLoader::kPathSeparator + library_path; |
116 | } |
117 | |
118 | Dart_Handle FileLoader::HandleLibraryTag(Dart_LibraryTag tag, |
119 | Dart_Handle library, |
120 | Dart_Handle url) { |
121 | TONIC_DCHECK(Dart_IsNull(library) || Dart_IsLibrary(library) || |
122 | Dart_IsString(library)); |
123 | TONIC_DCHECK(Dart_IsString(url)); |
124 | if (tag == Dart_kCanonicalizeUrl) |
125 | return CanonicalizeURL(library, url); |
126 | if (tag == Dart_kKernelTag) |
127 | return Kernel(url); |
128 | if (tag == Dart_kImportTag) |
129 | return Import(url); |
130 | return Dart_NewApiError("Unknown library tag." ); |
131 | } |
132 | |
133 | Dart_Handle FileLoader::CanonicalizeURL(Dart_Handle library, Dart_Handle url) { |
134 | std::string string = StdStringFromDart(url); |
135 | if (string.find(kDartScheme) == 0u) |
136 | return url; |
137 | if (string.find(kPackageScheme) == 0u) |
138 | return StdStringToDart(SanitizePath(string)); |
139 | if (string.find(kFileScheme) == 0u) |
140 | return StdStringToDart(SanitizePath(CanonicalizeFileURL(string))); |
141 | |
142 | std::string library_url = StdStringFromDart(Dart_LibraryUrl(library)); |
143 | std::string prefix = ExtractSchemePrefix(library_url); |
144 | std::string base_path = ExtractPath(library_url); |
145 | std::string simplified_path = |
146 | filesystem::SimplifyPath(filesystem::GetDirectoryName(base_path) + |
147 | FileLoader::kPathSeparator + string); |
148 | return StdStringToDart(SanitizePath(prefix + simplified_path)); |
149 | } |
150 | |
151 | std::string FileLoader::GetFilePathForURL(std::string url) { |
152 | if (url.find(kPackageScheme) == 0u) |
153 | return GetFilePathForPackageURL(std::move(url)); |
154 | if (url.find(kFileScheme) == 0u) |
155 | return GetFilePathForFileURL(std::move(url)); |
156 | return url; |
157 | } |
158 | |
159 | Dart_Handle FileLoader::FetchBytes(const std::string& url, |
160 | uint8_t*& buffer, |
161 | intptr_t& buffer_size) { |
162 | buffer = nullptr; |
163 | buffer_size = -1; |
164 | |
165 | std::string path = filesystem::SimplifyPath(GetFilePathForURL(url)); |
166 | if (path.empty()) { |
167 | std::string error_message = "error: Unable to read '" + url + "'." ; |
168 | return Dart_NewUnhandledExceptionError( |
169 | Dart_NewStringFromCString(error_message.c_str())); |
170 | } |
171 | std::string absolute_path = filesystem::GetAbsoluteFilePath(path); |
172 | auto result = filesystem::ReadFileToBytes(absolute_path); |
173 | if (result.first == nullptr) { |
174 | std::string error_message = |
175 | "error: Unable to read '" + absolute_path + "'." ; |
176 | return Dart_NewUnhandledExceptionError( |
177 | Dart_NewStringFromCString(error_message.c_str())); |
178 | } |
179 | buffer = result.first; |
180 | buffer_size = result.second; |
181 | return Dart_True(); |
182 | } |
183 | |
184 | Dart_Handle FileLoader::Import(Dart_Handle url) { |
185 | std::string url_string = StdStringFromDart(url); |
186 | uint8_t* buffer = nullptr; |
187 | intptr_t buffer_size = -1; |
188 | Dart_Handle result = FetchBytes(url_string, buffer, buffer_size); |
189 | if (Dart_IsError(result)) { |
190 | return result; |
191 | } |
192 | // The embedder must keep the buffer alive until isolate shutdown. |
193 | kernel_buffers_.push_back(buffer); |
194 | return Dart_LoadLibraryFromKernel(buffer, buffer_size); |
195 | } |
196 | |
197 | namespace { |
198 | void MallocFinalizer(void* isolate_callback_data, void* peer) { |
199 | free(peer); |
200 | } |
201 | } // namespace |
202 | |
203 | Dart_Handle FileLoader::Kernel(Dart_Handle url) { |
204 | std::string url_string = StdStringFromDart(url); |
205 | uint8_t* buffer = nullptr; |
206 | intptr_t buffer_size = -1; |
207 | Dart_Handle result = FetchBytes(url_string, buffer, buffer_size); |
208 | if (Dart_IsError(result)) { |
209 | return result; |
210 | } |
211 | result = |
212 | Dart_NewExternalTypedData(Dart_TypedData_kUint8, buffer, buffer_size); |
213 | Dart_NewFinalizableHandle(result, buffer, buffer_size, MallocFinalizer); |
214 | return result; |
215 | } |
216 | |
217 | // This is invoked upon a reload request. |
218 | void FileLoader::SetPackagesUrl(Dart_Handle url) { |
219 | if (url == Dart_Null()) { |
220 | // No packages url specified. |
221 | LoadPackagesMap(packages()); |
222 | return; |
223 | } |
224 | const std::string& packages_url = StdStringFromDart(url); |
225 | LoadPackagesMap(packages_url); |
226 | } |
227 | |
228 | std::string FileLoader::GetFilePathForFileURL(std::string url) { |
229 | TONIC_DCHECK(url.find(FileLoader::kFileURLPrefix) == 0u); |
230 | return SanitizePath(url.substr(FileLoader::kFileURLPrefixLength)); |
231 | } |
232 | |
233 | std::string FileLoader::GetFileURLForPath(const std::string& path) { |
234 | return std::string(FileLoader::kFileURLPrefix) + path; |
235 | } |
236 | |
237 | } // namespace tonic |
238 | |