otto 2.0.1 → 2.0.2
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 +4 -4
- data/.github/workflows/ci.yml +41 -3
- data/CHANGELOG.rst +20 -0
- data/Gemfile +1 -2
- data/Gemfile.lock +1 -3
- data/lib/otto/helpers/validation.rb +24 -9
- data/lib/otto/version.rb +1 -1
- data/otto.gemspec +0 -1
- metadata +1 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fabc1e8d6f7eef1d982b1f298fd0fe68382417aedd4b250ded28e5c7ba39a588
|
|
4
|
+
data.tar.gz: 02f4509daad92151895100c75339d82e3f8b76106dbcaa04b2db34f7b4d39976
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2c5f1958418f70a429bfc2379f7407387ca4a1a6cdd129160ff6bf3e416f33a637c0d14049bd97ab0c7abce02ab5b11eb943aff330b5c1c0425d55c387ff3351
|
|
7
|
+
data.tar.gz: '08e5ec5c3278adeced0dd3c1cffb7452129a12ad742c381b29a29bd569be4d013a67a660ad111bfe51631672e21d96cf2c0f39c69ab595758aa0e7e1d562ed32'
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -22,20 +22,45 @@ jobs:
|
|
|
22
22
|
test:
|
|
23
23
|
timeout-minutes: 10
|
|
24
24
|
runs-on: ubuntu-24.04
|
|
25
|
-
name: "RSpec Tests (Ruby ${{ matrix.ruby }})"
|
|
25
|
+
name: "RSpec Tests (Ruby ${{ matrix.ruby }}, ${{ matrix.lockfile }})"
|
|
26
26
|
continue-on-error: ${{ matrix.experimental }}
|
|
27
27
|
strategy:
|
|
28
28
|
fail-fast: false
|
|
29
29
|
matrix:
|
|
30
|
+
# Each Ruby runs twice: once against the committed Gemfile.lock
|
|
31
|
+
# (floor of the declared version range, reproducible) and once
|
|
32
|
+
# with the lockfile removed so Bundler resolves fresh inside the
|
|
33
|
+
# gemspec's pessimistic constraints (ceiling, what a downstream
|
|
34
|
+
# user will actually hit). The unlocked cells catch upstream
|
|
35
|
+
# releases that satisfy `~> X.Y` but break Otto at load time -
|
|
36
|
+
# e.g. facets 3.2.0 shipping a self-referential
|
|
37
|
+
# `require_relative 'file/write.rb'` against a file deleted in
|
|
38
|
+
# the same release, the reason 2.0.2 exists.
|
|
30
39
|
include:
|
|
31
40
|
- ruby: "3.3"
|
|
32
41
|
experimental: false
|
|
42
|
+
lockfile: "locked"
|
|
33
43
|
- ruby: "3.4"
|
|
34
44
|
experimental: false
|
|
45
|
+
lockfile: "locked"
|
|
35
46
|
- ruby: "3.5"
|
|
36
47
|
experimental: true
|
|
48
|
+
lockfile: "locked"
|
|
37
49
|
- ruby: "4.0"
|
|
38
50
|
experimental: true
|
|
51
|
+
lockfile: "locked"
|
|
52
|
+
- ruby: "3.3"
|
|
53
|
+
experimental: false
|
|
54
|
+
lockfile: "unlocked"
|
|
55
|
+
- ruby: "3.4"
|
|
56
|
+
experimental: false
|
|
57
|
+
lockfile: "unlocked"
|
|
58
|
+
- ruby: "3.5"
|
|
59
|
+
experimental: true
|
|
60
|
+
lockfile: "unlocked"
|
|
61
|
+
- ruby: "4.0"
|
|
62
|
+
experimental: true
|
|
63
|
+
lockfile: "unlocked"
|
|
39
64
|
|
|
40
65
|
steps:
|
|
41
66
|
- uses: actions/checkout@v6
|
|
@@ -44,7 +69,9 @@ jobs:
|
|
|
44
69
|
continue-on-error: ${{ matrix.experimental }}
|
|
45
70
|
with:
|
|
46
71
|
ruby-version: ${{ matrix.ruby }}
|
|
47
|
-
|
|
72
|
+
# Bundler cache keys off Gemfile.lock, so only enable it on the
|
|
73
|
+
# locked matrix cells. Unlocked cells need a fresh resolve each run.
|
|
74
|
+
bundler-cache: ${{ !matrix.experimental && matrix.lockfile == 'locked' }}
|
|
48
75
|
|
|
49
76
|
- name: Setup tmate session
|
|
50
77
|
uses: mxschmitt/action-tmate@c0afd6f790e3a5564914980036ebf83216678101 # v3
|
|
@@ -56,12 +83,23 @@ jobs:
|
|
|
56
83
|
continue-on-error: ${{ matrix.experimental }}
|
|
57
84
|
env:
|
|
58
85
|
EXPERIMENTAL: ${{ matrix.experimental }}
|
|
86
|
+
LOCKFILE: ${{ matrix.lockfile }}
|
|
59
87
|
run: |
|
|
60
88
|
bundle config path vendor/bundle
|
|
89
|
+
# Enable the optional :development, :test group (json_schemer,
|
|
90
|
+
# rack-attack) that specs depend on. `bundle install --with` was
|
|
91
|
+
# removed in Bundler 2.7, which ships with Ruby 4.0.
|
|
92
|
+
bundle config set --local with 'development test'
|
|
61
93
|
if [ "$EXPERIMENTAL" = "true" ]; then
|
|
62
94
|
bundle config set --local force_ruby_platform true
|
|
63
95
|
fi
|
|
64
|
-
|
|
96
|
+
if [ "$LOCKFILE" = "unlocked" ]; then
|
|
97
|
+
# Drop the committed lockfile so Bundler resolves fresh inside
|
|
98
|
+
# the gemspec's pessimistic constraints. This surfaces the gap
|
|
99
|
+
# between "version range we declare" and "version range we test."
|
|
100
|
+
rm -f Gemfile.lock
|
|
101
|
+
fi
|
|
102
|
+
bundle install --jobs 4 --retry 3
|
|
65
103
|
|
|
66
104
|
- name: Verify setup
|
|
67
105
|
run: |
|
data/CHANGELOG.rst
CHANGED
|
@@ -7,6 +7,26 @@ The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.1.0/>`
|
|
|
7
7
|
|
|
8
8
|
<!--scriv-insert-here-->
|
|
9
9
|
|
|
10
|
+
.. _changelog-2.0.2:
|
|
11
|
+
|
|
12
|
+
2.0.2 — 2026-04-15
|
|
13
|
+
==================
|
|
14
|
+
|
|
15
|
+
- Load failure under facets 3.2.0. ``Otto::Security::ValidationHelpers`` no
|
|
16
|
+
longer requires ``facets/file``, whose aggregator in 3.2.0 does
|
|
17
|
+
``require_relative 'file/write.rb'`` against a file deleted in the same
|
|
18
|
+
release. The one function Otto borrowed from facets — ``File.sanitize`` —
|
|
19
|
+
is now inlined as a private method on the helper module (with credit in
|
|
20
|
+
the source comment), and the ``facets`` runtime dependency is removed
|
|
21
|
+
from the gemspec entirely. Applications depending on facets directly are
|
|
22
|
+
unaffected.
|
|
23
|
+
|
|
24
|
+
- CI now runs the RSpec suite twice for each Ruby in the matrix: once
|
|
25
|
+
against the committed ``Gemfile.lock`` and once with the lockfile removed
|
|
26
|
+
so Bundler resolves fresh inside the gemspec's pessimistic constraints.
|
|
27
|
+
The unlocked cells catch upstream releases that satisfy ``~> X.Y`` but
|
|
28
|
+
break Otto at load time.
|
|
29
|
+
|
|
10
30
|
.. _changelog-2.0.1:
|
|
11
31
|
|
|
12
32
|
2.0.1 — 2026-04-15
|
data/Gemfile
CHANGED
|
@@ -9,8 +9,6 @@ source 'https://rubygems.org'
|
|
|
9
9
|
|
|
10
10
|
gemspec
|
|
11
11
|
|
|
12
|
-
gem 'rackup'
|
|
13
|
-
|
|
14
12
|
group :test do
|
|
15
13
|
gem 'rack-test'
|
|
16
14
|
gem 'rspec', '~> 3.13'
|
|
@@ -28,6 +26,7 @@ end
|
|
|
28
26
|
group :development do
|
|
29
27
|
gem 'benchmark'
|
|
30
28
|
gem 'debug'
|
|
29
|
+
gem 'rackup' # Used to boot examples/ apps; not needed by specs
|
|
31
30
|
gem 'rubocop', '~> 1.81.7', require: false
|
|
32
31
|
gem 'rubocop-performance', require: false
|
|
33
32
|
gem 'rubocop-rspec', require: false
|
data/Gemfile.lock
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
otto (2.0.
|
|
4
|
+
otto (2.0.2)
|
|
5
5
|
concurrent-ruby (~> 1.3, < 2.0)
|
|
6
|
-
facets (~> 3.1)
|
|
7
6
|
ipaddr (~> 1, < 2.0)
|
|
8
7
|
logger (~> 1, < 2.0)
|
|
9
8
|
loofah (~> 2.20)
|
|
@@ -54,7 +53,6 @@ GEM
|
|
|
54
53
|
dry-logic (~> 1.4)
|
|
55
54
|
zeitwerk (~> 2.6)
|
|
56
55
|
erb (5.1.1)
|
|
57
|
-
facets (3.1.0)
|
|
58
56
|
hana (1.3.7)
|
|
59
57
|
io-console (0.8.1)
|
|
60
58
|
ipaddr (1.2.8)
|
|
@@ -3,12 +3,20 @@
|
|
|
3
3
|
# frozen_string_literal: true
|
|
4
4
|
|
|
5
5
|
require 'loofah'
|
|
6
|
-
require 'facets/file'
|
|
7
6
|
|
|
8
7
|
class Otto
|
|
9
8
|
module Security
|
|
10
9
|
# Validation helper methods providing input validation and sanitization
|
|
11
10
|
module ValidationHelpers
|
|
11
|
+
# Replace filesystem-unsafe characters with an underscore. Borrowed
|
|
12
|
+
# verbatim from facets 3.1.0's `File.sanitize` (lib/core/facets/file/
|
|
13
|
+
# sanitize.rb, credit: George Moschovitis) and inlined here so Otto
|
|
14
|
+
# doesn't take a runtime dep on the whole facets grab-bag for one
|
|
15
|
+
# 12-line function. See commit message for 2.0.2 for context.
|
|
16
|
+
FILENAME_SANITIZE_PATTERN = /[^a-zA-Z0-9.\-+_]/
|
|
17
|
+
FILENAME_DOT_ONLY = /^\.+$/
|
|
18
|
+
private_constant :FILENAME_SANITIZE_PATTERN, :FILENAME_DOT_ONLY
|
|
19
|
+
|
|
12
20
|
def validate_input(input, max_length: 1000, allow_html: false)
|
|
13
21
|
return input if input.nil?
|
|
14
22
|
|
|
@@ -42,20 +50,16 @@ class Otto
|
|
|
42
50
|
return nil if filename.nil?
|
|
43
51
|
return 'file' if filename.empty?
|
|
44
52
|
|
|
45
|
-
|
|
46
|
-
clean_name = File.sanitize(filename.to_s)
|
|
53
|
+
clean_name = basic_filename_sanitize(filename.to_s)
|
|
47
54
|
|
|
48
|
-
# Handle edge cases and improve on Facets behavior to match test expectations
|
|
49
55
|
if clean_name.nil? || clean_name.empty?
|
|
50
56
|
clean_name = 'file'
|
|
51
57
|
else
|
|
52
|
-
|
|
53
|
-
clean_name = clean_name.gsub(
|
|
54
|
-
clean_name =
|
|
55
|
-
clean_name = 'file' if clean_name.empty? # Handle case where only underscores remain
|
|
58
|
+
clean_name = clean_name.gsub(/_{2,}/, '_')
|
|
59
|
+
clean_name = clean_name.gsub(/^_+|_+$/, '')
|
|
60
|
+
clean_name = 'file' if clean_name.empty? || clean_name.match?(FILENAME_DOT_ONLY)
|
|
56
61
|
end
|
|
57
62
|
|
|
58
|
-
# Ensure reasonable length (255 is filesystem limit, leave some padding)
|
|
59
63
|
clean_name = clean_name[0..99] if clean_name.length > 100
|
|
60
64
|
|
|
61
65
|
clean_name
|
|
@@ -63,6 +67,17 @@ class Otto
|
|
|
63
67
|
|
|
64
68
|
private
|
|
65
69
|
|
|
70
|
+
# Filesystem-safe basename. Port of facets 3.1.0's `File.sanitize`:
|
|
71
|
+
# strip directory components (handling backslashes for IE-uploaded
|
|
72
|
+
# paths), replace anything outside [A-Za-z0-9.\-+_] with '_', and
|
|
73
|
+
# prefix a leading '_' if the whole name is just dots ('.', '..').
|
|
74
|
+
def basic_filename_sanitize(filename)
|
|
75
|
+
name = File.basename(filename.gsub('\\', '/'))
|
|
76
|
+
name = name.gsub(FILENAME_SANITIZE_PATTERN, '_')
|
|
77
|
+
name = "_#{name}" if name.match?(FILENAME_DOT_ONLY)
|
|
78
|
+
name
|
|
79
|
+
end
|
|
80
|
+
|
|
66
81
|
# Check if content looks like it contains HTML tags or entities
|
|
67
82
|
def contains_html_like_content?(content)
|
|
68
83
|
content.match?(/[<>&]/) || content.match?(/&\w+;/)
|
data/lib/otto/version.rb
CHANGED
data/otto.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: otto
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Delano Mandelbaum
|
|
@@ -117,20 +117,6 @@ dependencies:
|
|
|
117
117
|
- - "~>"
|
|
118
118
|
- !ruby/object:Gem::Version
|
|
119
119
|
version: '3.4'
|
|
120
|
-
- !ruby/object:Gem::Dependency
|
|
121
|
-
name: facets
|
|
122
|
-
requirement: !ruby/object:Gem::Requirement
|
|
123
|
-
requirements:
|
|
124
|
-
- - "~>"
|
|
125
|
-
- !ruby/object:Gem::Version
|
|
126
|
-
version: '3.1'
|
|
127
|
-
type: :runtime
|
|
128
|
-
prerelease: false
|
|
129
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
130
|
-
requirements:
|
|
131
|
-
- - "~>"
|
|
132
|
-
- !ruby/object:Gem::Version
|
|
133
|
-
version: '3.1'
|
|
134
120
|
- !ruby/object:Gem::Dependency
|
|
135
121
|
name: loofah
|
|
136
122
|
requirement: !ruby/object:Gem::Requirement
|