Skip to content

Commit

Permalink
ruby_supportlib utils/json: replace strscan dependency with our own p…
Browse files Browse the repository at this point in the history
…ure-Ruby implementation

strscan has become a default gem nowadays. Since we load utils/json before activating Bundler, if the Gemfile has a dependency on a different strscan version then we'll run into conflicts. To prevent this, we reimplement strscan.
  • Loading branch information
FooBarWidget committed Jun 23, 2024
1 parent 2dba381 commit 1ba2f1b
Show file tree
Hide file tree
Showing 5 changed files with 406 additions and 197 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Release 6.0.23 (Not yet released)
* [Nginx] Upgrades preferred Nginx to 1.26.1 from 1.26.0.
* [Ubuntu] Add packages for Ubuntu 24.04 "noble".
* [RPMs] Remove EL7 RPMs, CentOS7 is EOL.
* Fixes compatibility with Ruby apps whose Gemfile.lock depends on strscan.
* Updated various library versions used in precompiled binaries (used for e.g. gem installs):
- ccache: 4.9.1 -> 4.10
- cmake 3.29.3 -> 3.29.5
Expand Down
199 changes: 2 additions & 197 deletions src/ruby_supportlib/phusion_passenger/utils/json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
# gem being available, for example in 'passenger start' before the RuntimeInstaller
# has run.

require 'strscan'
PhusionPassenger.require_passenger_lib 'utils/strscan'
require 'forwardable'

module PhusionPassenger
Expand Down Expand Up @@ -56,11 +56,10 @@ def self.parse(data) new(data).parse end
private :s, :scan, :matched

def initialize data
@scanner = StringScanner.new data.to_s
@scanner = PhusionPassenger::Utils::StringScanner.new data.to_s
end

def parse
space
object
end

Expand Down Expand Up @@ -197,199 +196,5 @@ def generate_Hash(hash)
extend Generator
end

if __FILE__ == $0
if !$stdin.tty?
data = JSON.parse $stdin.read
require 'pp'
pp data
else
require 'test/unit'
require 'date'
class ParserTest < Test::Unit::TestCase
PARSED = JSON.parse DATA.read
def parsed() PARSED end
def parse_string(str) JSON.parse(%(["#{str}"]).gsub('\\\\', '\\')).first end
def test_string
assert_equal "Pagination library for \"Rails 3\", Sinatra, Merb, DataMapper, and more",
parsed['head']['repository']['description']
end
def test_string_specials
assert_equal "\r\n\t\f\b", parse_string('\r\n\t\f\b')
assert_equal "aA", parse_string('\u0061\u0041')
assert_equal "\e", parse_string('\u001B')
assert_equal "xyz", parse_string('\x\y\z')
assert_equal '"\\/', parse_string('\"\\\\\\/')
assert_equal 'no #{interpolation}', parse_string('no #{interpolation}')
end
def test_hash
assert_equal %w[label ref repository sha user], parsed['head'].keys.sort
end
def test_number
assert_equal 124.3e2, parsed['head']['repository']['size']
end
def test_bool
assert_equal true, parsed['head']['repository']['fork']
assert_equal false, parsed['head']['repository']['private']
end
def test_nil
assert_nil parsed['head']['user']['company']
end
def test_array
assert_equal ["4438f", {"a" => "b"}], parsed['head']['sha']
end
def test_invalid
assert_raises(RuntimeError) { JSON.parse %({) }
assert_raises(RuntimeError) { JSON.parse %({ "foo": }) }
assert_raises(RuntimeError) { JSON.parse %([ "foo": "bar" ]) }
assert_raises(RuntimeError) { JSON.parse %([ ~"foo" ]) }
assert_raises(RuntimeError) { JSON.parse %([ "foo ]) }
assert_raises(RuntimeError) { JSON.parse %([ "foo\\" ]) }
assert_raises(RuntimeError) { JSON.parse %([ "foo\\uabGd" ]) }
end
def test_single_line_comments
source = %Q{
// comment before document
{
// comment
"foo": "1",
"bar": "2",
// another comment
"baz": "3",
"array": [
// comment inside array
1, 2, 3
// comment at end of array
]
// comment at end of hash
}
// comment after document
}
doc = { "foo" => "1", "bar" => "2", "baz" => "3", "array" => [1, 2, 3] }
assert_equal(doc, JSON.parse(source))
end
def test_multi_line_comments
source = %Q{
/* comment before
* document */
{
/* comment */
"foo": "1",
"bar": "2",
/* another
comment
*/
"baz": "3",
"array": [
/* comment inside array */
1, 2, 3,
4, /* comment inside an array */ 5,
/*
// "nested" comments
{ "faux json": "inside comment" }
*/
6, 7
/**
* comment at end of array
*/
]
/**************************
comment at end of hash
**************************/
}
/* comment after
document */
}
doc = { "foo" => "1", "bar" => "2", "baz" => "3", "array" => [1, 2, 3, 4, 5, 6, 7] }
assert_equal(doc, JSON.parse(source))
end
end

class GeneratorTest < Test::Unit::TestCase
def generate(obj) JSON.generate(obj) end
def test_array
assert_equal %([1, 2, 3]), generate([1, 2, 3])
end
def test_bool
assert_equal %([true, false]), generate([true, false])
end
def test_null
assert_equal %([null]), generate([nil])
end
def test_string
assert_equal %(["abc\\n123"]), generate(["abc\n123"])
end
def test_string_unicode
assert_equal %(["ć\\\\\\\\\\đ"]), generate([\"č\nž\tš\\đ"])
end
def test_time
time = Time.utc(2012, 04, 19, 1, 2, 3)
assert_equal %(["2012-04-19 01:02:03 UTC"]), generate([time])
end
def test_date
time = Date.new(2012, 04, 19)
assert_equal %(["2012-04-19"]), generate([time])
end
def test_symbol
assert_equal %(["abc"]), generate([:abc])
end
def test_hash
json = generate(:abc => 123, 123 => 'abc')
assert_match /^\{/, json
assert_match /\}$/, json
assert_equal [%("123": "abc"), %("abc": 123)], json[1...-1].split(', ').sort
end
def test_nested_structure
json = generate(:hash => {1=>2}, :array => [1,2])
assert json.include?(%("hash": {"1": 2}))
assert json.include?(%("array": [1, 2]))
end
def test_invalid_json
assert_raises(ArgumentError) { generate("abc") }
end
def test_invalid_object
err = assert_raises(ArgumentError) { generate("a" => Object.new) }
assert_equal "can't serialize Object", err.message
end
end
end
end

end # module Utils
end # module PhusionPassenger

__END__
{
"head": {
"ref": "master",
"repository": {
"forks": 0,
"integrate_branch": "rails3",
"watchers": 1,
"language": "Ruby",
"description": "Pagination library for \"Rails 3\", Sinatra, Merb, DataMapper, and more",
"has_downloads": true,
"fork": true,
"created_at": "2011/10/24 03:20:48 -0700",
"homepage": "http://github.com/mislav/will_paginate/wikis",
"size": 124.3e2,
"private": false,
"has_wiki": true,
"name": "will_paginate",
"owner": "dbackeus",
"url": "https://github.com/dbackeus/will_paginate",
"has_issues": false,
"open_issues": 0,
"pushed_at": "2011/10/25 05:44:05 -0700"
},
"label": "dbackeus:master",
"sha": ["4438f", { "a" : "b" }],
"user": {
"name": "David Backeus",
"company": null,
"gravatar_id": "ebe96524f5db9e92188f0542dc9d1d1a",
"location": "Stockholm (Sweden)",
"type": "User",
"login": "dbackeus"
}
}
}
67 changes: 67 additions & 0 deletions src/ruby_supportlib/phusion_passenger/utils/strscan.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# encoding: utf-8
# Phusion Passenger - https://www.phusionpassenger.com/
# Copyright (c) 2024 Phusion Holding B.V.
#
# "Passenger", "Phusion Passenger" and "Union Station" are registered
# trademarks of Phusion Holding B.V.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

module PhusionPassenger
module Utils

# A minimal pure-Ruby StringScanner implementation so that
# PhusionPassenger::Utils::JSON doesn't have to depend on the 'strscan' gem.
class StringScanner
attr_reader :pos, :matched

def initialize(data)
@data = data
@pos = 0
@matched = nil
end

def getch
@matched =
if @pos < @data.size
@pos += 1
@data[@pos - 1]
else
nil
end
end

def scan(pattern)
md = @data[@pos .. -1].match(/\A#{pattern}/)
@matched =
if md
@pos += md[0].size
@matched = md[0]
else
nil
end
end

def reset
@pos = 0
end
end

end # module Utils
end # module PhusionPassenger
Loading

0 comments on commit 1ba2f1b

Please sign in to comment.