wasmify-rails 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE.txt +23 -0
- data/README.md +168 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/checkpoint.rb +13 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/column.rb +4 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/configuration.rb +5 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/core.rb +391 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/empty_result.rb +41 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/index_definition.rb +5 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/null_object.rb +13 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/quoting.rb +3 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/statement.rb +15 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/table_definition.rb +23 -0
- data/lib/active_record/connection_adapters/nulldb_adapter.rb +67 -0
- data/lib/active_record/connection_adapters/sqlite3_wasm_adapter.rb +163 -0
- data/lib/active_storage/null_delivery.rb +15 -0
- data/lib/generators/wasmify/install/USAGE +5 -0
- data/lib/generators/wasmify/install/install_generator.rb +73 -0
- data/lib/generators/wasmify/install/templates/config/environments/wasm.rb +30 -0
- data/lib/generators/wasmify/install/templates/config/wasmify.yml +17 -0
- data/lib/generators/wasmify/pwa/USAGE +5 -0
- data/lib/generators/wasmify/pwa/pwa_generator.rb +13 -0
- data/lib/generators/wasmify/pwa/templates/pwa/README.md +40 -0
- data/lib/generators/wasmify/pwa/templates/pwa/boot.html +102 -0
- data/lib/generators/wasmify/pwa/templates/pwa/boot.js +96 -0
- data/lib/generators/wasmify/pwa/templates/pwa/database.js +18 -0
- data/lib/generators/wasmify/pwa/templates/pwa/index.html +2 -0
- data/lib/generators/wasmify/pwa/templates/pwa/package.json.tt +20 -0
- data/lib/generators/wasmify/pwa/templates/pwa/rails.sw.js +115 -0
- data/lib/generators/wasmify/pwa/templates/pwa/vite.config.js +29 -0
- data/lib/image_processing/null.rb +22 -0
- data/lib/wasmify/rails/builder.rb +45 -0
- data/lib/wasmify/rails/configuration.rb +41 -0
- data/lib/wasmify/rails/packer.rb +60 -0
- data/lib/wasmify/rails/railtie.rb +11 -0
- data/lib/wasmify/rails/shim.rb +65 -0
- data/lib/wasmify/rails/shims/io/console/size.rb +0 -0
- data/lib/wasmify/rails/shims/io/console.rb +11 -0
- data/lib/wasmify/rails/shims/io/wait.rb +0 -0
- data/lib/wasmify/rails/shims/ipaddr.rb +7 -0
- data/lib/wasmify/rails/shims/nio.rb +0 -0
- data/lib/wasmify/rails/shims/nokogiri/nokogiri.rb +0 -0
- data/lib/wasmify/rails/shims/nokogiri.rb +0 -0
- data/lib/wasmify/rails/shims/rails-html-sanitizer.rb +163 -0
- data/lib/wasmify/rails/shims/socket.rb +21 -0
- data/lib/wasmify/rails/shims/sqlite3.rb +0 -0
- data/lib/wasmify/rails/tasks.rake +120 -0
- data/lib/wasmify/rails/version.rb +7 -0
- data/lib/wasmify/rails/wasmtimer.rb +31 -0
- data/lib/wasmify-rails.rb +25 -0
- metadata +200 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wasmify-rails-starter",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"packageManager": "yarn@1.22.22",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "vite",
|
|
9
|
+
"build": "vite build",
|
|
10
|
+
"preview": "vite preview"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"vite": "^5.4.7",
|
|
14
|
+
"vite-plugin-pwa": "^0.20.5"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"wasmify-rails": "^<%= wasmify_rails_version %>",
|
|
18
|
+
"@sqlite.org/sqlite-wasm": "3.46.1-build3"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
initRailsVM,
|
|
3
|
+
Progress,
|
|
4
|
+
registerSQLiteWasmInterface,
|
|
5
|
+
RackHandler,
|
|
6
|
+
} from "wasmify-rails";
|
|
7
|
+
|
|
8
|
+
import { setupSQLiteDatabase } from "./database.js";
|
|
9
|
+
|
|
10
|
+
let db = null;
|
|
11
|
+
|
|
12
|
+
const initDB = async (progress) => {
|
|
13
|
+
if (db) return db;
|
|
14
|
+
|
|
15
|
+
progress.updateStep("Initializing SQLite database...");
|
|
16
|
+
db = await setupSQLiteDatabase();
|
|
17
|
+
progress.updateStep("SQLite database created.");
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
let vm = null;
|
|
21
|
+
|
|
22
|
+
const initVM = async (progress, opts = {}) => {
|
|
23
|
+
if (vm) return vm;
|
|
24
|
+
|
|
25
|
+
if (!db) {
|
|
26
|
+
await initDB(progress);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
registerSQLiteWasmInterface(self, db);
|
|
30
|
+
|
|
31
|
+
let redirectConsole = true;
|
|
32
|
+
|
|
33
|
+
vm = await initRailsVM("/app.wasm", {
|
|
34
|
+
database: { adapter: "sqlite3_wasm" },
|
|
35
|
+
progressCallback: (step) => {
|
|
36
|
+
progress?.updateStep(step);
|
|
37
|
+
},
|
|
38
|
+
outputCallback: (output) => {
|
|
39
|
+
if (!redirectConsole) return;
|
|
40
|
+
progress?.notify(output);
|
|
41
|
+
},
|
|
42
|
+
...opts,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Ensure schema is loaded
|
|
46
|
+
progress?.updateStep("Preparing database...");
|
|
47
|
+
vm.eval("ActiveRecord::Tasks::DatabaseTasks.prepare_all");
|
|
48
|
+
|
|
49
|
+
redirectConsole = false;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const resetVM = () => {
|
|
53
|
+
vm = null;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const installApp = async () => {
|
|
57
|
+
const progress = new Progress();
|
|
58
|
+
await progress.attach(self);
|
|
59
|
+
|
|
60
|
+
await initDB(progress);
|
|
61
|
+
await initVM(progress);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
self.addEventListener("activate", (event) => {
|
|
65
|
+
console.log("[rails-web] Activate Service Worker");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
self.addEventListener("install", (event) => {
|
|
69
|
+
console.log("[rails-web] Install Service Worker");
|
|
70
|
+
event.waitUntil(installApp());
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const rackHandler = new RackHandler(initVM, { assumeSSL: true });
|
|
74
|
+
|
|
75
|
+
self.addEventListener("fetch", (event) => {
|
|
76
|
+
const bootResources = ["/boot", "/boot.js", "/boot.html", "/rails.sw.js"];
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
bootResources.find((r) => new URL(event.request.url).pathname.endsWith(r))
|
|
80
|
+
) {
|
|
81
|
+
console.log(
|
|
82
|
+
"[rails-web] Fetching boot files from network:",
|
|
83
|
+
event.request.url,
|
|
84
|
+
);
|
|
85
|
+
event.respondWith(fetch(event.request.url));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const viteResources = ["node_modules", "@vite"];
|
|
90
|
+
|
|
91
|
+
if (viteResources.find((r) => event.request.url.includes(r))) {
|
|
92
|
+
console.log(
|
|
93
|
+
"[rails-web] Fetching Vite files from network:",
|
|
94
|
+
event.request.url,
|
|
95
|
+
);
|
|
96
|
+
event.respondWith(fetch(event.request.url));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return event.respondWith(rackHandler.handle(event.request));
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
self.addEventListener("message", async (event) => {
|
|
104
|
+
console.log("[rails-web] Received worker message:", event.data);
|
|
105
|
+
|
|
106
|
+
if (event.data.type === "reload-rails") {
|
|
107
|
+
const progress = new Progress();
|
|
108
|
+
await progress.attach(self);
|
|
109
|
+
|
|
110
|
+
progress.updateStep("Reloading Rails application...");
|
|
111
|
+
|
|
112
|
+
resetVM();
|
|
113
|
+
await initVM(progress, { debug: event.data.debug });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import { VitePWA } from "vite-plugin-pwa";
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
server: {
|
|
6
|
+
headers: {
|
|
7
|
+
"Cross-Origin-Opener-Policy": "same-origin",
|
|
8
|
+
"Cross-Origin-Embedder-Policy": "require-corp",
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
optimizeDeps: {
|
|
12
|
+
exclude: ["@sqlite.org/sqlite-wasm"],
|
|
13
|
+
},
|
|
14
|
+
plugins: [
|
|
15
|
+
VitePWA({
|
|
16
|
+
srcDir: ".",
|
|
17
|
+
filename: "rails.sw.js",
|
|
18
|
+
strategies: "injectManifest",
|
|
19
|
+
injectRegister: false,
|
|
20
|
+
manifest: false,
|
|
21
|
+
injectManifest: {
|
|
22
|
+
injectionPoint: null,
|
|
23
|
+
},
|
|
24
|
+
devOptions: {
|
|
25
|
+
enabled: true,
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
],
|
|
29
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ImageProcessing
|
|
4
|
+
# Null processor for image_processing that keeps source files untouched
|
|
5
|
+
# and copy them to the destination if provided.
|
|
6
|
+
module Null
|
|
7
|
+
extend Chainable
|
|
8
|
+
|
|
9
|
+
def self.valid_image?(file) = true
|
|
10
|
+
|
|
11
|
+
class Processor < ImageProcessing::Processor
|
|
12
|
+
def self.call(source:, loader:, operations:, saver:, destination: nil)
|
|
13
|
+
fail ArgumentError, "A string path is expected, got #{source.class}" unless source.is_a?(String)
|
|
14
|
+
fail ArgumentError, "File not found: #{source}" unless File.file?(source)
|
|
15
|
+
|
|
16
|
+
if destination
|
|
17
|
+
FileUtils.cp(source, destination)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "wasmify-rails"
|
|
4
|
+
|
|
5
|
+
require "ruby_wasm"
|
|
6
|
+
require "ruby_wasm/cli"
|
|
7
|
+
|
|
8
|
+
module Wasmify
|
|
9
|
+
module Rails
|
|
10
|
+
# A wrapper for rbwasm build command
|
|
11
|
+
class Builder
|
|
12
|
+
ORIGINAL_EXCLUDED_GEMS = RubyWasm::Packager::EXCLUDED_GEMS.dup.freeze
|
|
13
|
+
|
|
14
|
+
attr_reader :output_dir
|
|
15
|
+
|
|
16
|
+
def initialize(output_dir: Wasmify::Rails.config.tmp_dir)
|
|
17
|
+
@output_dir = output_dir
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def run(name:, exclude_gems: [])
|
|
21
|
+
# Reset excluded gems
|
|
22
|
+
RubyWasm::Packager::EXCLUDED_GEMS.replace(ORIGINAL_EXCLUDED_GEMS)
|
|
23
|
+
|
|
24
|
+
# Add configured excluded gems
|
|
25
|
+
Wasmify::Rails.config.exclude_gems.each do |gem_name|
|
|
26
|
+
RubyWasm::Packager::EXCLUDED_GEMS << gem_name
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Add additional excluded gems
|
|
30
|
+
exclude_gems.each do |gem_name|
|
|
31
|
+
RubyWasm::Packager::EXCLUDED_GEMS << gem_name
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
args = %W(
|
|
35
|
+
build
|
|
36
|
+
--ruby-version #{Wasmify::Rails.config.short_ruby_version}
|
|
37
|
+
-o #{File.join(output_dir, name)}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
FileUtils.mkdir_p(output_dir)
|
|
41
|
+
RubyWasm::CLI.new(stdout: $stdout, stderr: $stderr).run(args)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wasmify
|
|
4
|
+
module Rails
|
|
5
|
+
class Configuration
|
|
6
|
+
attr_reader :pack_directories, :exclude_gems, :ruby_version,
|
|
7
|
+
:tmp_dir, :output_dir,
|
|
8
|
+
:skip_assets_precompile
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
config_path = ::Rails.root.join("config", "wasmify.yml")
|
|
12
|
+
raise "config/wasmify.yml not found" unless File.exist?(config_path)
|
|
13
|
+
|
|
14
|
+
config = YAML.load_file(config_path)
|
|
15
|
+
|
|
16
|
+
@pack_directories = config["pack_directories"]
|
|
17
|
+
@exclude_gems = config["exclude_gems"]
|
|
18
|
+
@ruby_version = config["ruby_version"] || ruby_version_from_file || "3.3"
|
|
19
|
+
@tmp_dir = config["tmp_dir"] || ::Rails.root.join("tmp", "wasmify")
|
|
20
|
+
@output_dir = config["output_dir"] || ::Rails.root.join("dist")
|
|
21
|
+
@skip_assets_precompile = config["skip_assets_precompile"] || false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def short_ruby_version
|
|
25
|
+
if (matches = ruby_version.match(/^(\d+\.\d+)/))
|
|
26
|
+
matches[1]
|
|
27
|
+
else
|
|
28
|
+
ruby_version
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def ruby_version_from_file
|
|
35
|
+
return unless File.file?(::Rails.root.join(".ruby-version"))
|
|
36
|
+
|
|
37
|
+
File.read(::Rails.root.join(".ruby-version")).strip
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "wasmify-rails"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
|
|
6
|
+
module Wasmify
|
|
7
|
+
module Rails
|
|
8
|
+
# A wrapper for wasi-vfs to add application source code to the
|
|
9
|
+
# ruby.wasm module
|
|
10
|
+
class Packer
|
|
11
|
+
ROOT_FILES = %w[Gemfile config.ru .ruby-version Rakefile]
|
|
12
|
+
|
|
13
|
+
attr_reader :output_dir
|
|
14
|
+
|
|
15
|
+
include ActionView::Helpers::NumberHelper
|
|
16
|
+
|
|
17
|
+
def initialize(output_dir: Wasmify::Rails.config.output_dir)
|
|
18
|
+
@output_dir = output_dir
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def run(ruby_wasm_path:, name:, directories: Wasmify::Rails.config.pack_directories, storage_dir: nil)
|
|
22
|
+
unless system("which wasi-vfs > /dev/null 2>&1")
|
|
23
|
+
raise "wasi-vfs is required to pack the application.\n"
|
|
24
|
+
"Please see installations instructions at: https://github.com/kateinoigakukun/wasi-vfs"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
args = %W[
|
|
28
|
+
pack #{ruby_wasm_path}
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
directories.each do |dir|
|
|
32
|
+
args << "--dir #{::Rails.root.join(dir)}::/rails/#{dir}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
output_path = File.join(output_dir, name)
|
|
36
|
+
|
|
37
|
+
FileUtils.mkdir_p(output_dir)
|
|
38
|
+
|
|
39
|
+
# Generate a temporary directory with the require root files
|
|
40
|
+
Dir.mktmpdir do |tdir|
|
|
41
|
+
ROOT_FILES.each do |file|
|
|
42
|
+
FileUtils.cp(::Rails.root.join(file), tdir) if File.exist?(::Rails.root.join(file))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Create a storage/ directory for Active Storage attachments
|
|
46
|
+
FileUtils.mkdir_p(File.join(tdir, storage_dir)) if storage_dir
|
|
47
|
+
|
|
48
|
+
args << "--dir #{tdir}::/rails"
|
|
49
|
+
|
|
50
|
+
args << "-o #{output_path}"
|
|
51
|
+
|
|
52
|
+
spawn("wasi-vfs #{args.join(" ")}").then { Process.wait(_1) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
$stdout.puts "Packed the application to #{output_path}\n" \
|
|
56
|
+
"Size: #{number_to_human_size(File.size(output_path))}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# First, expose the global #on_wasm? helper
|
|
4
|
+
module Kernel
|
|
5
|
+
if RUBY_PLATFORM.include?("wasm")
|
|
6
|
+
def on_wasm? = true
|
|
7
|
+
else
|
|
8
|
+
def on_wasm? = false
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Only load shims when running within a Wasm runtime
|
|
13
|
+
return unless on_wasm?
|
|
14
|
+
|
|
15
|
+
# Setup Bundler
|
|
16
|
+
require "/bundle/setup"
|
|
17
|
+
require "bundler"
|
|
18
|
+
|
|
19
|
+
# Load core classes and deps patches
|
|
20
|
+
$LOAD_PATH.unshift File.expand_path("shims", __dir__)
|
|
21
|
+
|
|
22
|
+
# Misc patches
|
|
23
|
+
|
|
24
|
+
# Make gem no-op
|
|
25
|
+
define_singleton_method(:gem) { |*| nil }
|
|
26
|
+
|
|
27
|
+
# Patch Bundler.require to simply require files without looking at specs
|
|
28
|
+
def Bundler.require(*groups)
|
|
29
|
+
Bundler.definition.dependencies_for([:wasm]).each do |dep|
|
|
30
|
+
required_file = nil
|
|
31
|
+
# Based on https://github.com/rubygems/rubygems/blob/8a079e9061ad4aaf2bc0b9007da8f362b7a2e1f2/bundler/lib/bundler/runtime.rb#L57
|
|
32
|
+
begin
|
|
33
|
+
Array(dep.autorequire || dep.name).each do |file|
|
|
34
|
+
file = dep.name if file == true
|
|
35
|
+
required_file = file
|
|
36
|
+
begin
|
|
37
|
+
Kernel.require file
|
|
38
|
+
rescue RuntimeError => e
|
|
39
|
+
raise e if e.is_a?(LoadError) # we handle this a little later
|
|
40
|
+
raise Bundler::GemRequireError.new e,
|
|
41
|
+
"There was an error while trying to load the gem '#{file}'."
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
rescue LoadError => e
|
|
45
|
+
raise if dep.autorequire || e.path != required_file
|
|
46
|
+
|
|
47
|
+
if dep.autorequire.nil? && dep.name.include?("-")
|
|
48
|
+
begin
|
|
49
|
+
namespaced_file = dep.name.tr("-", "/")
|
|
50
|
+
Kernel.require namespaced_file
|
|
51
|
+
rescue LoadError => e
|
|
52
|
+
raise if e.path != namespaced_file
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class Thread
|
|
60
|
+
def self.new(...)
|
|
61
|
+
f = Fiber.new(...)
|
|
62
|
+
def f.value = resume
|
|
63
|
+
f
|
|
64
|
+
end
|
|
65
|
+
end
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
module Rails
|
|
2
|
+
module HTML
|
|
3
|
+
class Scrubber
|
|
4
|
+
CONTINUE = Object.new.freeze
|
|
5
|
+
|
|
6
|
+
attr_accessor :tags, :attributes
|
|
7
|
+
attr_reader :prune
|
|
8
|
+
|
|
9
|
+
def initialize(**)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def scrub(node) = CONTINUE
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
PermitScrubber = Scrubber
|
|
16
|
+
TargetScrubber = Scrubber
|
|
17
|
+
TextOnlyScrubber = Scrubber
|
|
18
|
+
|
|
19
|
+
class Sanitizer
|
|
20
|
+
class << self
|
|
21
|
+
def html5_support? = true
|
|
22
|
+
def best_supported_vendor = NullSanitizer
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
module Concern
|
|
27
|
+
module SafeList
|
|
28
|
+
# The default safe list for tags
|
|
29
|
+
DEFAULT_ALLOWED_TAGS = Set.new([
|
|
30
|
+
"a",
|
|
31
|
+
"abbr",
|
|
32
|
+
"acronym",
|
|
33
|
+
"address",
|
|
34
|
+
"b",
|
|
35
|
+
"big",
|
|
36
|
+
"blockquote",
|
|
37
|
+
"br",
|
|
38
|
+
"cite",
|
|
39
|
+
"code",
|
|
40
|
+
"dd",
|
|
41
|
+
"del",
|
|
42
|
+
"dfn",
|
|
43
|
+
"div",
|
|
44
|
+
"dl",
|
|
45
|
+
"dt",
|
|
46
|
+
"em",
|
|
47
|
+
"h1",
|
|
48
|
+
"h2",
|
|
49
|
+
"h3",
|
|
50
|
+
"h4",
|
|
51
|
+
"h5",
|
|
52
|
+
"h6",
|
|
53
|
+
"hr",
|
|
54
|
+
"i",
|
|
55
|
+
"img",
|
|
56
|
+
"ins",
|
|
57
|
+
"kbd",
|
|
58
|
+
"li",
|
|
59
|
+
"mark",
|
|
60
|
+
"ol",
|
|
61
|
+
"p",
|
|
62
|
+
"pre",
|
|
63
|
+
"samp",
|
|
64
|
+
"small",
|
|
65
|
+
"span",
|
|
66
|
+
"strong",
|
|
67
|
+
"sub",
|
|
68
|
+
"sup",
|
|
69
|
+
"time",
|
|
70
|
+
"tt",
|
|
71
|
+
"ul",
|
|
72
|
+
"var",
|
|
73
|
+
]).freeze
|
|
74
|
+
|
|
75
|
+
# The default safe list for attributes
|
|
76
|
+
DEFAULT_ALLOWED_ATTRIBUTES = Set.new([
|
|
77
|
+
"abbr",
|
|
78
|
+
"alt",
|
|
79
|
+
"cite",
|
|
80
|
+
"class",
|
|
81
|
+
"datetime",
|
|
82
|
+
"height",
|
|
83
|
+
"href",
|
|
84
|
+
"lang",
|
|
85
|
+
"name",
|
|
86
|
+
"src",
|
|
87
|
+
"title",
|
|
88
|
+
"width",
|
|
89
|
+
"xml:lang",
|
|
90
|
+
]).freeze
|
|
91
|
+
|
|
92
|
+
def self.included(klass)
|
|
93
|
+
class << klass
|
|
94
|
+
attr_accessor :allowed_tags
|
|
95
|
+
attr_accessor :allowed_attributes
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
klass.allowed_tags = DEFAULT_ALLOWED_TAGS.dup
|
|
99
|
+
klass.allowed_attributes = DEFAULT_ALLOWED_ATTRIBUTES.dup
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def initialize(prune: false)
|
|
103
|
+
@permit_scrubber = PermitScrubber.new(prune: prune)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def scrub(fragment, options = {})
|
|
107
|
+
if scrubber = options[:scrubber]
|
|
108
|
+
# No duck typing, Loofah ensures subclass of Loofah::Scrubber
|
|
109
|
+
fragment.scrub!(scrubber)
|
|
110
|
+
elsif allowed_tags(options) || allowed_attributes(options)
|
|
111
|
+
@permit_scrubber.tags = allowed_tags(options)
|
|
112
|
+
@permit_scrubber.attributes = allowed_attributes(options)
|
|
113
|
+
fragment.scrub!(@permit_scrubber)
|
|
114
|
+
else
|
|
115
|
+
fragment.scrub!(:strip)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def sanitize_css(style_string)
|
|
120
|
+
Loofah::HTML5::Scrub.scrub_css(style_string)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
def allowed_tags(options)
|
|
125
|
+
options[:tags] || self.class.allowed_tags
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def allowed_attributes(options)
|
|
129
|
+
options[:attributes] || self.class.allowed_attributes
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# TODO: That should be a real sanitizer (backed by JS or another external interface)
|
|
135
|
+
class NullSanitizer
|
|
136
|
+
class << self
|
|
137
|
+
def safe_list_sanitizer = self
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def sanitize(html, ...) = html
|
|
141
|
+
def sanitize_css(style) = style
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
module HTML4
|
|
146
|
+
Sanitizer = HTML::NullSanitizer
|
|
147
|
+
FullSanitizer = Sanitizer
|
|
148
|
+
LinkSanitizer = Sanitizer
|
|
149
|
+
|
|
150
|
+
class SafeListSanitizer < Sanitizer
|
|
151
|
+
include HTML::Concern::SafeList
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
Html = HTML
|
|
156
|
+
|
|
157
|
+
module HTML
|
|
158
|
+
FullSanitizer = HTML4::FullSanitizer
|
|
159
|
+
LinkSanitizer = HTML4::LinkSanitizer
|
|
160
|
+
SafeListSanitizer = HTML4::SafeListSanitizer
|
|
161
|
+
WhiteListSanitizer = SafeListSanitizer
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class BasicSocket
|
|
4
|
+
def initialize(...)
|
|
5
|
+
raise NotImplementedError, "Socket is not supported in Wasm"
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class Socket < BasicSocket
|
|
10
|
+
AF_UNSPEC = 0
|
|
11
|
+
AF_INET = 1
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class IPSocket < Socket
|
|
15
|
+
def self.getaddress(...)
|
|
16
|
+
raise NotImplementedError, "Socket is not supported in Wasm"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class TCPSocket < Socket
|
|
21
|
+
end
|
|
File without changes
|