Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Fixed
- Fix `NoMethodError` when `group=` param is absent from `/node/version/view` and `/node/version/diffs` requests. `params[:group]` was assigned directly to `@info[:group]`; the HAML templates and `get_diff` calls then called methods on `nil`. Normalise to `''` on input and pass `nil` to the oxidized backend where required (@makaiver)
- Fix `NoMethodError` and `RegexpError` crashes on `/node/version` and `/nodes/conf_search` when params are missing or contain a malformed regex; return empty results instead of a 500 (@makaiver)


## [0.18.1 – 2026-01-19]
Expand Down
24 changes: 16 additions & 8 deletions lib/oxidized/web/webapp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,21 @@ class WebApp < Sinatra::Base
end

post '/nodes/conf_search.?:format?' do
@to_research = Regexp.new params[:search_in_conf_textbox]
nodes_list = nodes.list.map
search_term = params[:search_in_conf_textbox].to_s
@nodes_match = []
nodes_list.each do |n|
node, @json = route_parse n[:name]
config = nodes.fetch node, n[:group]
@nodes_match.push({ node: n[:name], full_name: n[:full_name] }) if config[@to_research]
unless search_term.empty?
begin
@to_research = Regexp.new search_term
rescue RegexpError
@to_research = nil
end
if @to_research
nodes.list.map.each do |n|
node, @json = route_parse n[:name]
config = nodes.fetch node, n[:group]
@nodes_match.push({ node: n[:name], full_name: n[:full_name] }) if config[@to_research]
end
end
end
@data = @nodes_match
out :conf_search
Expand Down Expand Up @@ -142,7 +150,7 @@ class WebApp < Sinatra::Base
@data = nil
@group = nil
@node = nil
node_full = params[:node_full]
node_full = params[:node_full].to_s
if node_full.include? '/'
node_full = node_full.rpartition("/")
@group = node_full[0]
Expand Down Expand Up @@ -254,7 +262,7 @@ def route_parse(param)
e = if param.respond_to?(:to_str)
param.split '.'
else
params[param].split '.'
params[param].to_s.split '.'
end
if e.last == 'json'
e.pop
Expand Down
40 changes: 40 additions & 0 deletions spec/web/conf_search_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require_relative '../spec_helper'

describe Oxidized::API::WebApp do
include Rack::Test::Methods

def app
Oxidized::API::WebApp
end

before do
@nodes = mock('Oxidized::Nodes')
app.set(:nodes, @nodes)
end

describe 'post /nodes/conf_search' do
it 'returns 200 with empty results when search_in_conf_textbox is missing' do
post '/nodes/conf_search'
_(last_response.ok?).must_equal true
end

it 'returns 200 with empty results when search_in_conf_textbox is blank' do
post '/nodes/conf_search', search_in_conf_textbox: ''
_(last_response.ok?).must_equal true
end

it 'returns 200 with empty results when regex is malformed (no crash)' do
post '/nodes/conf_search', search_in_conf_textbox: '['
_(last_response.ok?).must_equal true
end

it 'returns 200 with matches when regex is valid' do
@nodes.expects(:list).returns(
[{ name: 'sw5', full_name: 'sw5', group: nil }]
)
@nodes.expects(:fetch).with('sw5', nil).returns("hostname sw5\n")
post '/nodes/conf_search', search_in_conf_textbox: 'hostname'
_(last_response.ok?).must_equal true
end
end
end
15 changes: 11 additions & 4 deletions spec/web/node/version_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,18 @@ def app
"2025-02-05 19:49:00 +0100</td>\n"
)).must_equal true
end

it 'returns 200 when node_full is absent (no NoMethodError)' do
@nodes.expects(:version).with('', nil).returns([])

get '/node/version'
_(last_response.ok?).must_equal true
end
end

describe 'get /node/version/view.?:format?' do
it 'fetches a previous version from git' do
@nodes.expects(:get_version).with('sw5', '', 'c8aa93cab5').returns('Old configuration of sw5')
@nodes.expects(:get_version).with('sw5', nil, 'c8aa93cab5').returns('Old configuration of sw5')

get '/node/version/view?node=sw5&group=&oid=c8aa93cab5&epoch=1738781340&num=2'
_(last_response.ok?).must_equal true
Expand All @@ -90,7 +97,7 @@ def app
end

it 'does not display binary content' do
@nodes.expects(:get_version).with('sw5', '', 'c8aa93cab5').returns("\xff\x42 binary content\x00")
@nodes.expects(:get_version).with('sw5', nil, 'c8aa93cab5').returns("\xff\x42 binary content\x00")

get '/node/version/view?node=sw5&group=&oid=c8aa93cab5&epoch=1738781340&num=2'
_(last_response.ok?).must_equal true
Expand All @@ -107,7 +114,7 @@ def app

it 'does not encode html-chars in text-format' do
configuration = "text &/<> \n ascii;"
@nodes.expects(:get_version).with('sw5', '', 'c8aa93cab5').returns(configuration)
@nodes.expects(:get_version).with('sw5', nil, 'c8aa93cab5').returns(configuration)
get '/node/version/view?node=sw5&group=&oid=c8aa93cab5&epoch=1738781340&num=2&format=text'

_(last_response.ok?).must_equal true
Expand All @@ -116,7 +123,7 @@ def app

it 'does not encode html-chars in json-format' do
configuration = "text &/<> \n ascii;"
@nodes.expects(:get_version).with('sw5', '', 'c8aa93cab5').returns(configuration)
@nodes.expects(:get_version).with('sw5', nil, 'c8aa93cab5').returns(configuration)
get '/node/version/view?node=sw5&group=&oid=c8aa93cab5&epoch=1738781340&num=2&format=json'

_(last_response.ok?).must_equal true
Expand Down