Skip to content

Commit

Permalink
add option to not follow symlinks
Browse files Browse the repository at this point in the history
  • Loading branch information
mamantoha committed Jul 14, 2023
1 parent 44ce610 commit 9fee6ff
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 6 deletions.
4 changes: 4 additions & 0 deletions src/zipstream/cli.cr
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ module Zipstream
config.hidden = true
end

parser.on("--no-symlinks", "do not follow symlinks") do
config.no_symlinks = true
end

parser.on("--user=user", "the username user for file retrieval") do |name|
config.user = name
end
Expand Down
1 change: 1 addition & 0 deletions src/zipstream/config.cr
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module Zipstream
property prefix : String? = nil
property? junk : Bool = false
property? hidden : Bool = false
property? no_symlinks : Bool = false
property? no_banner : Bool = false

def initialize
Expand Down
13 changes: 8 additions & 5 deletions src/zipstream/http/server/handlers/static_file_handler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ module Zipstream
# paths matching directories are ignored and next handler is called.
#
# If *match_hidden* is `true` the pattern will match hidden files and folders.
def initialize(public_dir : String, fallthrough = true, directory_listing = true, match_hidden = false)
# If *follow_symlinks* is `true` the pattern will follow symlinks.
def initialize(public_dir : String, fallthrough = true, directory_listing = true, match_hidden = false, follow_symlinks = false)
@public_dir = Path.new(public_dir).expand
@fallthrough = !!fallthrough
@directory_listing = !!directory_listing
@match_hidden = match_hidden
@follow_symlinks = follow_symlinks
end

def call(context)
Expand Down Expand Up @@ -66,7 +68,7 @@ module Zipstream

if @directory_listing && is_dir
context.response.content_type = "text/html"
directory_listing(context.response, request_path, file_path, @match_hidden)
directory_listing(context.response, request_path, file_path, @match_hidden, @follow_symlinks)
elsif is_file
last_modified = modification_time(file_path)
add_cache_headers(context.response.headers, last_modified)
Expand Down Expand Up @@ -131,14 +133,15 @@ module Zipstream
File.info(file_path).modification_time
end

record DirectoryListing, request_path : String, path : String, match_hidden : Bool do
record DirectoryListing, request_path : String, path : String, match_hidden : Bool, follow_symlinks : Bool do
def each_file(&)
Dir.children(path).sort_by(&.downcase).each do |entry|
next if !match_hidden && entry.starts_with?('.')

file_path = File.join(path, entry)

next unless File.readable?(file_path)
next if !follow_symlinks && File.symlink?(file_path)

file = File.new(file_path)

Expand All @@ -151,8 +154,8 @@ module Zipstream
ECR.def_to_s "#{__DIR__}/static_file_handler.html.ecr"
end

private def directory_listing(io, request_path, path, match_hidden)
DirectoryListing.new(request_path.to_s, path.to_s, match_hidden).to_s(io)
private def directory_listing(io, request_path, path, match_hidden, follow_symlinks)
DirectoryListing.new(request_path.to_s, path.to_s, match_hidden, follow_symlinks).to_s(io)
end
end
end
37 changes: 37 additions & 0 deletions src/zipstream/http/server/handlers/symlink_static_file_handler.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module Zipstream
class SymlinkStaticFileHandler
include HTTP::Handler

property config

def initialize(@config : Config)
end

def call(context)
if config.no_symlinks? && contains_symlink_in_parent_directory?(config.path, context.request.path.chomp('/'))
context.response.respond_with_status(:not_found)

return
end

call_next(context)
end

# Check if any parent directory within the base path contains a symlink
private def contains_symlink_in_parent_directory?(base_path, relative_path)
path = File.join(base_path, relative_path)

loop do
return true if File.symlink?(path)

parent_directory = File.dirname(path)

break if parent_directory == path || parent_directory == base_path

path = parent_directory
end

false
end
end
end
1 change: 1 addition & 0 deletions src/zipstream/http/server/handlers/tar_helper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Zipstream

Dir.glob(File.join(path, "**/*"), match: file_match_options).each do |entry|
next unless File.readable?(entry)
next if config.no_symlinks? && File.symlink?(entry)

relative_path = [config.prefix, entry.sub(path, "").lstrip("/")].compact.join("/")

Expand Down
1 change: 1 addition & 0 deletions src/zipstream/http/server/handlers/zip_handler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ module Zipstream

Dir.glob(File.join(path, "**/*"), match: file_match_options).each do |entry|
next unless File.readable?(entry)
next if config.no_symlinks? && File.symlink?(entry)

relative_path = [config.prefix, entry.sub(path, "").lstrip("/")].compact.join("/")

Expand Down
3 changes: 2 additions & 1 deletion src/zipstream/web/server.cr
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ module Zipstream
end

handlers << HiddenStaticFileHandler.new(config)
handlers << SymlinkStaticFileHandler.new(config)

if config.basic_auth?
handlers << BasicAuthHandler.new(config.user.to_s, config.password.to_s)
end

handlers << StaticFileHandler.new(config.path, match_hidden: config.hidden?)
handlers << StaticFileHandler.new(config.path, match_hidden: config.hidden?, follow_symlinks: !config.no_symlinks?)

server = HTTP::Server.new(handlers)
server.bind_tcp(config.host, config.port)
Expand Down

0 comments on commit 9fee6ff

Please sign in to comment.