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
18namespace tonic {
19namespace {
20
21constexpr char kDartScheme[] = "dart:";
22
23constexpr char kFileScheme[] = "file:";
24constexpr size_t kFileSchemeLength = sizeof(kFileScheme) - 1;
25
26constexpr char kPackageScheme[] = "package:";
27constexpr size_t kPackageSchemeLength = sizeof(kPackageScheme) - 1;
28
29// Extract the scheme prefix ('package:' or 'file:' from )
30std::string ExtractSchemePrefix(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.
39std::string ExtractPath(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
49FileLoader::FileLoader(int dirfd) : dirfd_(dirfd) {}
50
51FileLoader::~FileLoader() {
52 for (auto kernel_buffer : kernel_buffers_)
53 free(kernel_buffer);
54
55 if (dirfd_ >= 0)
56 close(dirfd_);
57}
58
59std::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
79bool 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
96std::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
118Dart_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
133Dart_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
151std::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
159Dart_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
184Dart_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
197namespace {
198void MallocFinalizer(void* isolate_callback_data, void* peer) {
199 free(peer);
200}
201} // namespace
202
203Dart_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.
218void 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
228std::string FileLoader::GetFilePathForFileURL(std::string url) {
229 TONIC_DCHECK(url.find(FileLoader::kFileURLPrefix) == 0u);
230 return SanitizePath(url.substr(FileLoader::kFileURLPrefixLength));
231}
232
233std::string FileLoader::GetFileURLForPath(const std::string& path) {
234 return std::string(FileLoader::kFileURLPrefix) + path;
235}
236
237} // namespace tonic
238