Module: MarkdownServer::Helpers::PathHelpers

Defined in:
lib/markdown_server/helpers/path_helpers.rb

Instance Method Summary collapse

Instance Method Details

#encode_path_component(str) ⇒ Object



12
13
14
# File 'lib/markdown_server/helpers/path_helpers.rb', line 12

def encode_path_component(str)
  URI.encode_www_form_component(str).gsub("+", "%20")
end

#entry_admitted?(parent_real, parent_relative_str, entry_name) ⇒ Boolean

Name-gate check for an entry in a directory listing.

Boundary gate (against served root) is NOT applied here so non-followed external non-dot symlinks still appear in listings — matching today’s UX; clicking them produces a 403 via safe_path/permitted_path?.

When the name gate admits a normally-restricted entry (dotfile or EXCLUDED) that is also a symlink, additionally require the symlink’s realpath to be explicitly listed in –follow-link. Otherwise the unhide rule does not surface it. Keeps ‘unhide` (visibility by name) and `–follow-link` (which symlinks may be entered) as orthogonal opt-ins; prevents internal-aliased dotfile symlinks like `.claude → claude` from showing up as duplicates of their targets.

Returns:

  • (Boolean)


64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/markdown_server/helpers/path_helpers.rb', line 64

def entry_admitted?(parent_real, parent_relative_str, entry_name)
  rules = Array(settings.unhide_rules)
  parent_segs = parent_relative_str.to_s.empty? ? [] : parent_relative_str.split("/")

  mode = :open
  parent_segs.each_with_index do |seg, i|
    ok, mode = MarkdownServer::Unhide.step(mode, parent_segs, i, seg, rules)
    return false unless ok
  end

  ok, _ = MarkdownServer::Unhide.entry_step(mode, parent_segs, entry_name, rules)
  return false unless ok

  if MarkdownServer::Unhide.restricted?(entry_name)
    full = File.join(parent_real, entry_name)
    if File.symlink?(full)
      real = File.realpath(full) rescue (return false)
      return false unless Array(settings.followed_links).include?(real)
    end
  end

  true
end

#h(text) ⇒ Object



8
9
10
# File 'lib/markdown_server/helpers/path_helpers.rb', line 8

def h(text)
  CGI.escapeHTML(text.to_s)
end

#permitted_base_for(real) ⇒ Object

Returns the permitted base (root realpath or a followed-link target realpath) that contains ‘real`, or nil if no permitted base contains it.



18
19
20
# File 'lib/markdown_server/helpers/path_helpers.rb', line 18

def permitted_base_for(real)
  MarkdownServer::PermittedBases.base_for(real, permitted_bases)
end

#permitted_basesObject



22
23
24
# File 'lib/markdown_server/helpers/path_helpers.rb', line 22

def permitted_bases
  [File.realpath(root_dir), *Array(settings.followed_links)]
end

#permitted_path?(real) ⇒ Boolean

Returns true if ‘real` is under a permitted base AND every restricted segment of the path under that base is admitted by the configured unhide rules (per Unhide.visible? algorithm).

Returns:

  • (Boolean)


29
30
31
32
33
34
35
# File 'lib/markdown_server/helpers/path_helpers.rb', line 29

def permitted_path?(real)
  base = permitted_base_for(real)
  return false unless base
  return true if real == base
  rel = real.sub("#{base}/", "")
  MarkdownServer::Unhide.visible?(rel.split("/"), Array(settings.unhide_rules))
end

#root_dirObject



4
5
6
# File 'lib/markdown_server/helpers/path_helpers.rb', line 4

def root_dir
  settings.root_dir
end

#safe_path(requested) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/markdown_server/helpers/path_helpers.rb', line 37

def safe_path(requested)
  base = File.realpath(root_dir)
  full = File.join(base, requested)

  begin
    real = File.realpath(full)
  rescue Errno::ENOENT
    halt 404, erb(:layout) { "<h1>Not Found</h1><p>#{h(requested)}</p>" }
  end

  halt 403, erb(:layout) { "<h1>Forbidden</h1>" } unless permitted_path?(real)
  real
end