Reject more include directives and invalid journal encodings
This commit is contained in:
@@ -6,7 +6,7 @@ module Hledger
|
||||
# control.
|
||||
class Journal
|
||||
FENCE = /^[ \t]*`{3,}hledger[ \t]*\r?\n(?<source>.*?)^[ \t]*`{3,}[ \t]*$/m
|
||||
INCLUDE = /^[ \t]*!?include\b/i
|
||||
INCLUDE = /^[ \t]*!?[ \t]*include\b/i
|
||||
MAX_LINES = 5_000
|
||||
|
||||
Result =
|
||||
@@ -29,6 +29,8 @@ module Hledger
|
||||
end
|
||||
|
||||
def extract
|
||||
return Result.new(source: nil, error: :invalid_encoding) unless @raw.valid_encoding?
|
||||
|
||||
matches = @raw.scan(FENCE).flatten
|
||||
return Result.new(source: nil, error: :none) if matches.empty?
|
||||
return Result.new(source: nil, error: :ambiguous) if matches.length > 1
|
||||
|
||||
@@ -39,6 +39,32 @@ RSpec.describe Hledger::Journal do
|
||||
expect(result.error).to eq(:include_not_allowed)
|
||||
end
|
||||
|
||||
[
|
||||
"include /etc/passwd",
|
||||
"include ../foo",
|
||||
"include ~/foo",
|
||||
"include *.journal",
|
||||
"include **/*",
|
||||
" include foo",
|
||||
"\tinclude foo",
|
||||
"INCLUDE foo",
|
||||
"!include foo",
|
||||
"! include foo",
|
||||
"! include foo",
|
||||
].each do |directive|
|
||||
it "rejects the include directive #{directive.inspect}" do
|
||||
result = described_class.extract(raw_with(directive))
|
||||
|
||||
expect(result.error).to eq(:include_not_allowed)
|
||||
end
|
||||
end
|
||||
|
||||
it "allows the word include inside a transaction" do
|
||||
result = described_class.extract(raw_with("2024-01-01 include the foo\n a 1\n b -1"))
|
||||
|
||||
expect(result).to be_ok
|
||||
end
|
||||
|
||||
it "rejects oversized journals" do
|
||||
SiteSetting.hledger_max_journal_bytes = 10
|
||||
|
||||
@@ -48,4 +74,21 @@ RSpec.describe Hledger::Journal do
|
||||
ensure
|
||||
SiteSetting.hledger_max_journal_bytes = 262_144
|
||||
end
|
||||
|
||||
it "extracts a fence with trailing whitespace and a longer fence" do
|
||||
raw = "Intro\n\n````hledger \n2024-01-01 x\n a 1\n b -1\n````\n\nOutro\n"
|
||||
|
||||
result = described_class.extract(raw)
|
||||
|
||||
expect(result).to be_ok
|
||||
expect(result.source).to include("2024-01-01 x")
|
||||
end
|
||||
|
||||
it "rejects invalid UTF-8" do
|
||||
raw = "```hledger\n2024-01-01 \xFF\n```\n"
|
||||
|
||||
result = described_class.extract(raw)
|
||||
|
||||
expect(result.error).to eq(:invalid_encoding)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -129,6 +129,15 @@ RSpec.describe Hledger::ReportsController do
|
||||
expect(response.status).to eq(422)
|
||||
end
|
||||
|
||||
it "rejects journals with include directives" do
|
||||
post.update!(raw: "```hledger\ninclude /etc/passwd\n```")
|
||||
|
||||
get_report("accounts")
|
||||
|
||||
expect(response.status).to eq(422)
|
||||
expect(response.body).not_to include("/etc/passwd")
|
||||
end
|
||||
|
||||
it "returns service unavailable when hledger is missing" do
|
||||
allow(Hledger::Runner).to receive(:new).and_return(StubHledgerRunner.new(version: nil))
|
||||
|
||||
@@ -168,6 +177,123 @@ RSpec.describe Hledger::ReportsController do
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
|
||||
describe "authorization" do
|
||||
def journal_raw
|
||||
<<~RAW
|
||||
```hledger
|
||||
2024-01-01 Opening balances
|
||||
assets:bank:checking 1000.00 EUR
|
||||
equity:alice -600.00 EUR
|
||||
equity:bob -400.00 EUR
|
||||
```
|
||||
RAW
|
||||
end
|
||||
|
||||
def report_for(topic)
|
||||
get "/hledger/topics/#{topic.id}/reports/accounts.json"
|
||||
end
|
||||
|
||||
it "allows anonymous access to a public topic" do
|
||||
report_for(post.topic)
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
|
||||
it "allows access to a locked and archived topic the user can see" do
|
||||
post.topic.update!(closed: true, archived: true)
|
||||
|
||||
report_for(post.topic)
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
|
||||
it "returns not found for a deleted topic" do
|
||||
topic = Fabricate(:post, raw: journal_raw).topic
|
||||
topic.trash!
|
||||
|
||||
report_for(topic)
|
||||
|
||||
expect(response.status).to eq(404)
|
||||
expect(response.body).not_to include("assets:bank:checking")
|
||||
end
|
||||
|
||||
it "returns not found when the first post is deleted" do
|
||||
topic = Fabricate(:post, raw: journal_raw).topic
|
||||
topic.first_post.trash!
|
||||
|
||||
report_for(topic)
|
||||
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
|
||||
context "with a group-restricted category" do
|
||||
fab!(:group)
|
||||
fab!(:restricted_category) { Fabricate(:private_category, group: group) }
|
||||
fab!(:restricted_topic) do
|
||||
Fabricate(
|
||||
:post,
|
||||
raw: journal_raw,
|
||||
topic: Fabricate(:topic, category: restricted_category),
|
||||
).topic
|
||||
end
|
||||
|
||||
it "hides it from anonymous users" do
|
||||
report_for(restricted_topic)
|
||||
|
||||
expect(response.status).to eq(404)
|
||||
expect(response.body).not_to include("assets:bank:checking")
|
||||
end
|
||||
|
||||
it "hides it from logged in non-members" do
|
||||
sign_in(Fabricate(:user))
|
||||
|
||||
report_for(restricted_topic)
|
||||
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
|
||||
it "allows members" do
|
||||
user = Fabricate(:user)
|
||||
group.add(user)
|
||||
sign_in(user)
|
||||
|
||||
report_for(restricted_topic)
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a private message" do
|
||||
fab!(:member, :user)
|
||||
fab!(:private_message_post) do
|
||||
Fabricate(:private_message_post, recipient: member, raw: journal_raw)
|
||||
end
|
||||
|
||||
it "hides it from anonymous users" do
|
||||
report_for(private_message_post.topic)
|
||||
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
|
||||
it "hides it from non-members" do
|
||||
sign_in(Fabricate(:user))
|
||||
|
||||
report_for(private_message_post.topic)
|
||||
|
||||
expect(response.status).to eq(404)
|
||||
expect(response.body).not_to include("assets:bank:checking")
|
||||
end
|
||||
|
||||
it "allows members" do
|
||||
sign_in(member)
|
||||
|
||||
report_for(private_message_post.topic)
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "is not found when the plugin is disabled" do
|
||||
SiteSetting.hledger_enabled = false
|
||||
|
||||
|
||||
Reference in New Issue
Block a user