diff --git a/lib/volumen/server.rb b/lib/volumen/server.rb index 2f0b91a..7aaf77e 100644 --- a/lib/volumen/server.rb +++ b/lib/volumen/server.rb @@ -17,6 +17,7 @@ module Volumen class Server < Sinatra::Base MAX_LOGIN_ATTEMPTS = 10 LOGIN_WINDOW = 60 # seconds + SLUG_REGEX = /\A[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?\z/ register ApiRoutes register AdminRoutes @@ -260,6 +261,11 @@ module Volumen def change_username new_name = presence(params["username"]) return render_settings(error: "Username cannot be empty.") if new_name.nil? + unless new_name.match?(/\A[a-zA-Z0-9._-]+\z/) + return render_settings(error: "Username may use letters, numbers, dot, dash, underscore.") + end + + return render_settings(notice: "Username unchanged.") if new_name == current_user if users.rename(current_user, new_name) session[:user] = new_name @@ -335,6 +341,7 @@ module Volumen def creation_error(post) return "Slug is required." if presence(post.slug).nil? + return "Invalid slug." unless post.slug.match?(SLUG_REGEX) return "A post with that slug already exists." if store.find(post.slug) nil diff --git a/lib/volumen/store.rb b/lib/volumen/store.rb index c729197..ef74b27 100644 --- a/lib/volumen/store.rb +++ b/lib/volumen/store.rb @@ -85,7 +85,12 @@ module Volumen end def default_path_for(post) - File.join(@content_dir, "#{post.slug}.md") + target = File.join(@content_dir, "#{post.slug}.md") + unless File.expand_path(target).start_with?(File.expand_path(@content_dir) + File::SEPARATOR) + raise ArgumentError, "slug escapes content directory" + end + + target end def safe_media_name(original) diff --git a/lib/volumen/web/views/form.erb b/lib/volumen/web/views/form.erb index 2848300..2788bdf 100644 --- a/lib/volumen/web/views/form.erb +++ b/lib/volumen/web/views/form.erb @@ -5,7 +5,7 @@ <% end %>