diff --git a/Gemfile b/Gemfile index 54dde2e8..36634fb0 100644 --- a/Gemfile +++ b/Gemfile @@ -18,6 +18,7 @@ gem 'tilt' gem 'erubis' gem 'stripe', :git => 'https://github.com/stripe/stripe-ruby' gem 'screencap' +gem 'cocaine' platform :mri do gem 'magic' # sudo apt-get install file, For OSX: brew install libmagic diff --git a/Gemfile.lock b/Gemfile.lock index 60c6c663..e730f3ae 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,6 +10,12 @@ GIT GEM remote: https://rubygems.org/ specs: + activesupport (4.1.4) + i18n (~> 0.6, >= 0.6.9) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.1) + tzinfo (~> 1.1) addressable (2.3.6) ago (0.1.5) ansi (1.4.3) @@ -30,7 +36,11 @@ GEM minitest (>= 2) celluloid (0.15.2) timers (~> 1.1.0) + climate_control (0.0.3) + activesupport (>= 3.0) cliver (0.3.2) + cocaine (0.5.4) + climate_control (>= 0.0.3, < 1.0) coderay (1.1.0) columnize (0.3.6) connection_pool (2.0.0) @@ -168,11 +178,14 @@ GEM sinatra-xsendfile (0.4.2) sinatra (>= 0.9.1) slop (3.5.0) + thread_safe (0.3.4) tilt (1.4.1) timers (1.1.0) treetop (1.4.15) polyglot polyglot (>= 0.3.1) + tzinfo (1.2.2) + thread_safe (~> 0.1) unicorn (4.8.2) kgio (~> 2.6) rack @@ -193,6 +206,7 @@ DEPENDENCIES bcrypt capybara capybara_minitest_spec + cocaine erubis fabrication faker diff --git a/app.rb b/app.rb index 5409544b..290888ec 100644 --- a/app.rb +++ b/app.rb @@ -636,7 +636,7 @@ post '/site_files/upload' do end if !Site.valid_file_type? file - file_upload_response "#{file[:filename]}: file type is not allowed on Neocities, upload cancelled." + file_upload_response "#{file[:filename]}: file type (or content in file) is not allowed on Neocities, upload cancelled." end end @@ -941,7 +941,7 @@ post '/api/upload' do files.each do |file| if !Site.valid_file_type?(file) - api_error 400, 'invalid_file_type', "#{file[:filename]} is not a valid file type, files have not been uploaded" + api_error 400, 'invalid_file_type', "#{file[:filename]} is not a valid file type (or contains not allowed content), files have not been uploaded" end end diff --git a/models/site.rb b/models/site.rb index 9c3fc3b8..23301977 100644 --- a/models/site.rb +++ b/models/site.rb @@ -60,6 +60,12 @@ class Site < Sequel::Model SCREENSHOT_RESOLUTIONS = ['235x141', '105x63', '270x162', '37x37', '146x88', '302x182', '90x63', '82x62', '348x205'] THUMBNAIL_RESOLUTIONS = ['105x63', '90x63'] + CLAMAV_THREAT_MATCHES = [ + /^VBS/, + /^PUA.Win32/, + /^JS.Popupper/ + ] + BANNED_TIME = 2592000 # 30 days in seconds TITLE_MAX = 100 @@ -261,9 +267,26 @@ class Site < Sequel::Model def self.valid_file_type?(uploaded_file) mime_type = Magic.guess_file_mime_type uploaded_file[:tempfile].path - return true if (Site::VALID_MIME_TYPES.include?(mime_type) || mime_type =~ /text/) && - Site::VALID_EXTENSIONS.include?(File.extname(uploaded_file[:filename]).sub(/^./, '').downcase) - false + return false unless (Site::VALID_MIME_TYPES.include?(mime_type) || mime_type =~ /text/) && + Site::VALID_EXTENSIONS.include?(File.extname(uploaded_file[:filename]).sub(/^./, '').downcase) + + File.chmod 0640, uploaded_file[:tempfile].path + line = Cocaine::CommandLine.new( + "clamdscan", "-i --remove=no --no-summary --stdout :path", + expected_outcodes: [0, 1] + ) + + output = line.run path: uploaded_file[:tempfile].path + + return true if output == '' + + threat = output.strip.match(/^.+: (.+) FOUND$/).captures.first + + CLAMAV_THREAT_MATCHES.each do |threat_match| + return false if threat.match threat_match + end + + true end def store_file(filename, uploaded)