| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "tonic/file_loader/file_loader.h" |
| |
| #include <iostream> |
| #include <memory> |
| #include <utility> |
| |
| #include "filesystem/file.h" |
| #include "filesystem/path.h" |
| #include "filesystem/portable_unistd.h" |
| #include "tonic/common/macros.h" |
| #include "tonic/converter/dart_converter.h" |
| #include "tonic/parsers/packages_map.h" |
| |
| namespace tonic { |
| namespace { |
| |
| constexpr char kDartScheme[] = "dart:"; |
| |
| constexpr char kFileScheme[] = "file:"; |
| constexpr size_t kFileSchemeLength = sizeof(kFileScheme) - 1; |
| |
| constexpr char kPackageScheme[] = "package:"; |
| constexpr size_t kPackageSchemeLength = sizeof(kPackageScheme) - 1; |
| |
| // Extract the scheme prefix ('package:' or 'file:' from ) |
| std::string ExtractSchemePrefix(std::string url) { |
| if (url.find(kPackageScheme) == 0u) |
| return kPackageScheme; |
| if (url.find(kFileScheme) == 0u) |
| return kFileScheme; |
| return std::string(); |
| } |
| |
| // Extract the path from a package: or file: url. |
| std::string ExtractPath(std::string url) { |
| if (url.find(kPackageScheme) == 0u) |
| return url.substr(kPackageSchemeLength); |
| if (url.find(kFileScheme) == 0u) |
| return url.substr(kFileSchemeLength); |
| return url; |
| } |
| |
| } // namespace |
| |
| FileLoader::FileLoader(int dirfd) : dirfd_(dirfd) {} |
| |
| FileLoader::~FileLoader() { |
| for (auto kernel_buffer : kernel_buffers_) |
| free(kernel_buffer); |
| |
| if (dirfd_ >= 0) |
| close(dirfd_); |
| } |
| |
| std::string FileLoader::SanitizeURIEscapedCharacters(const std::string& str) { |
| std::string result; |
| result.reserve(str.size()); |
| for (std::string::size_type i = 0; i < str.size(); ++i) { |
| if (str[i] == '%') { |
| if (i > str.size() - 3 || !isxdigit(str[i + 1]) || !isxdigit(str[i + 2])) |
| return ""; |
| const std::string hex = str.substr(i + 1, 2); |
| const unsigned char c = strtoul(hex.c_str(), nullptr, 16); |
| if (!c) |
| return ""; |
| result += c; |
| i += 2; |
| } else { |
| result += str[i]; |
| } |
| } |
| return result; |
| } |
| |
| bool FileLoader::LoadPackagesMap(const std::string& packages) { |
| packages_ = packages; |
| std::string packages_source; |
| if (!ReadFileToString(packages_, &packages_source)) { |
| tonic::Log("error: Unable to load .packages file '%s'.", packages_.c_str()); |
| return false; |
| } |
| packages_map_.reset(new PackagesMap()); |
| std::string error; |
| if (!packages_map_->Parse(packages_source, &error)) { |
| tonic::Log("error: Unable to parse .packages file '%s'. %s", |
| packages_.c_str(), error.c_str()); |
| return false; |
| } |
| return true; |
| } |
| |
| std::string FileLoader::GetFilePathForPackageURL(std::string url) { |
| if (!packages_map_) |
| return std::string(); |
| TONIC_DCHECK(url.find(kPackageScheme) == 0u); |
| url = url.substr(kPackageSchemeLength); |
| |
| size_t slash = url.find(FileLoader::kPathSeparator); |
| if (slash == std::string::npos) |
| return std::string(); |
| std::string package = url.substr(0, slash); |
| std::string library_path = url.substr(slash + 1); |
| std::string package_path = packages_map_->Resolve(package); |
| if (package_path.empty()) |
| return std::string(); |
| if (package_path.find(FileLoader::kFileURLPrefix) == 0u) |
| return SanitizePath(package_path.substr(FileLoader::kFileURLPrefixLength) + |
| library_path); |
| return filesystem::GetDirectoryName(filesystem::AbsolutePath(packages_)) + |
| FileLoader::kPathSeparator + package_path + |
| FileLoader::kPathSeparator + library_path; |
| } |
| |
| Dart_Handle FileLoader::HandleLibraryTag(Dart_LibraryTag tag, |
| Dart_Handle library, |
| Dart_Handle url) { |
| TONIC_DCHECK(Dart_IsNull(library) || Dart_IsLibrary(library) || |
| Dart_IsString(library)); |
| TONIC_DCHECK(Dart_IsString(url)); |
| if (tag == Dart_kCanonicalizeUrl) |
| return CanonicalizeURL(library, url); |
| if (tag == Dart_kKernelTag) |
| return Kernel(url); |
| if (tag == Dart_kImportTag) |
| return Import(url); |
| return Dart_NewApiError("Unknown library tag."); |
| } |
| |
| Dart_Handle FileLoader::CanonicalizeURL(Dart_Handle library, Dart_Handle url) { |
| std::string string = StdStringFromDart(url); |
| if (string.find(kDartScheme) == 0u) |
| return url; |
| if (string.find(kPackageScheme) == 0u) |
| return StdStringToDart(SanitizePath(string)); |
| if (string.find(kFileScheme) == 0u) |
| return StdStringToDart(SanitizePath(CanonicalizeFileURL(string))); |
| |
| std::string library_url = StdStringFromDart(Dart_LibraryUrl(library)); |
| std::string prefix = ExtractSchemePrefix(library_url); |
| std::string base_path = ExtractPath(library_url); |
| std::string simplified_path = |
| filesystem::SimplifyPath(filesystem::GetDirectoryName(base_path) + |
| FileLoader::kPathSeparator + string); |
| return StdStringToDart(SanitizePath(prefix + simplified_path)); |
| } |
| |
| std::string FileLoader::GetFilePathForURL(std::string url) { |
| if (url.find(kPackageScheme) == 0u) |
| return GetFilePathForPackageURL(std::move(url)); |
| if (url.find(kFileScheme) == 0u) |
| return GetFilePathForFileURL(std::move(url)); |
| return url; |
| } |
| |
| Dart_Handle FileLoader::FetchBytes(const std::string& url, |
| uint8_t*& buffer, |
| intptr_t& buffer_size) { |
| buffer = nullptr; |
| buffer_size = -1; |
| |
| std::string path = filesystem::SimplifyPath(GetFilePathForURL(url)); |
| if (path.empty()) { |
| std::string error_message = "error: Unable to read '" + url + "'."; |
| return Dart_NewUnhandledExceptionError( |
| Dart_NewStringFromCString(error_message.c_str())); |
| } |
| std::string absolute_path = filesystem::GetAbsoluteFilePath(path); |
| auto result = filesystem::ReadFileToBytes(absolute_path); |
| if (result.first == nullptr) { |
| std::string error_message = |
| "error: Unable to read '" + absolute_path + "'."; |
| return Dart_NewUnhandledExceptionError( |
| Dart_NewStringFromCString(error_message.c_str())); |
| } |
| buffer = result.first; |
| buffer_size = result.second; |
| return Dart_True(); |
| } |
| |
| Dart_Handle FileLoader::Import(Dart_Handle url) { |
| std::string url_string = StdStringFromDart(url); |
| uint8_t* buffer = nullptr; |
| intptr_t buffer_size = -1; |
| Dart_Handle result = FetchBytes(url_string, buffer, buffer_size); |
| if (Dart_IsError(result)) { |
| return result; |
| } |
| // The embedder must keep the buffer alive until isolate shutdown. |
| kernel_buffers_.push_back(buffer); |
| return Dart_LoadLibraryFromKernel(buffer, buffer_size); |
| } |
| |
| namespace { |
| void MallocFinalizer(void* isolate_callback_data, |
| Dart_WeakPersistentHandle handle, |
| void* peer) { |
| free(peer); |
| } |
| } // namespace |
| |
| Dart_Handle FileLoader::Kernel(Dart_Handle url) { |
| std::string url_string = StdStringFromDart(url); |
| uint8_t* buffer = nullptr; |
| intptr_t buffer_size = -1; |
| Dart_Handle result = FetchBytes(url_string, buffer, buffer_size); |
| if (Dart_IsError(result)) { |
| return result; |
| } |
| result = |
| Dart_NewExternalTypedData(Dart_TypedData_kUint8, buffer, buffer_size); |
| Dart_NewWeakPersistentHandle(result, buffer, buffer_size, MallocFinalizer); |
| return result; |
| } |
| |
| // This is invoked upon a reload request. |
| void FileLoader::SetPackagesUrl(Dart_Handle url) { |
| if (url == Dart_Null()) { |
| // No packages url specified. |
| LoadPackagesMap(packages()); |
| return; |
| } |
| const std::string& packages_url = StdStringFromDart(url); |
| LoadPackagesMap(packages_url); |
| } |
| |
| std::string FileLoader::GetFilePathForFileURL(std::string url) { |
| TONIC_DCHECK(url.find(FileLoader::kFileURLPrefix) == 0u); |
| return SanitizePath(url.substr(FileLoader::kFileURLPrefixLength)); |
| } |
| |
| std::string FileLoader::GetFileURLForPath(const std::string& path) { |
| return std::string(FileLoader::kFileURLPrefix) + path; |
| } |
| |
| } // namespace tonic |