diff options
72 files changed, 708 insertions, 376 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml index ff8eb4859..83a2088d7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2 aliases: - &defaults docker: - - image: circleci/ruby:2.6-stretch-node + - image: circleci/ruby:2.6-buster-node environment: &ruby_environment BUNDLE_APP_CONFIG: ./.bundle/ DB_HOST: localhost @@ -39,7 +39,6 @@ aliases: steps: - checkout - *attach_workspace - - restore_cache: keys: - v1-node-dependencies-{{ checksum "yarn.lock" }} @@ -49,7 +48,6 @@ aliases: key: v1-node-dependencies-{{ checksum "yarn.lock" }} paths: - ./node_modules/ - - *persist_to_workspace - &install_system_dependencies @@ -58,13 +56,17 @@ aliases: command: | sudo apt-get update sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler + + ## TODO: FIX THESE BUSTER DEPENDANCES + sudo wget http://ftp.au.debian.org/debian/pool/main/i/icu/libicu57_57.1-6+deb9u3_amd64.deb + sudo dpkg -i libicu57_57.1-6+deb9u3_amd64.deb + sudo wget http://ftp.au.debian.org/debian/pool/main/p/protobuf/libprotobuf10_3.0.0-9_amd64.deb + sudo dpkg -i libprotobuf10_3.0.0-9_amd64.deb - &install_ruby_dependencies steps: - *attach_workspace - - *install_system_dependencies - - run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version - *restore_ruby_dependencies - run: bundle install --clean --jobs 16 --path ./vendor/bundle/ --retry 3 --with pam_authentication --without development production && bundle clean @@ -82,10 +84,8 @@ aliases: - &test_steps steps: - *attach_workspace - - *install_system_dependencies - run: sudo apt-get install -y ffmpeg - - run: name: Prepare Tests command: ./bin/rails parallel:create parallel:load_schema parallel:prepare @@ -105,14 +105,14 @@ jobs: install-ruby2.5: <<: *defaults docker: - - image: circleci/ruby:2.5-stretch-node + - image: circleci/ruby:2.5-buster-node environment: *ruby_environment <<: *install_ruby_dependencies install-ruby2.4: <<: *defaults docker: - - image: circleci/ruby:2.4-stretch-node + - image: circleci/ruby:2.4-buster-node environment: *ruby_environment <<: *install_ruby_dependencies @@ -131,7 +131,7 @@ jobs: test-ruby2.6: <<: *defaults docker: - - image: circleci/ruby:2.6-stretch-node + - image: circleci/ruby:2.6-buster-node environment: *ruby_environment - image: circleci/postgres:10.6-alpine environment: @@ -142,7 +142,7 @@ jobs: test-ruby2.5: <<: *defaults docker: - - image: circleci/ruby:2.5-stretch-node + - image: circleci/ruby:2.5-buster-node environment: *ruby_environment - image: circleci/postgres:10.6-alpine environment: @@ -153,7 +153,7 @@ jobs: test-ruby2.4: <<: *defaults docker: - - image: circleci/ruby:2.4-stretch-node + - image: circleci/ruby:2.4-buster-node environment: *ruby_environment - image: circleci/postgres:10.6-alpine environment: @@ -164,7 +164,7 @@ jobs: test-webui: <<: *defaults docker: - - image: circleci/node:12.9-stretch + - image: circleci/node:12-buster steps: - *attach_workspace - run: ./bin/retry yarn test:jest diff --git a/Dockerfile b/Dockerfile index 117727d08..cc75bd6be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ FROM ubuntu:18.04 as build-dep SHELL ["bash", "-c"] # Install Node v12 (LTS) -ENV NODE_VER="12.13.1" +ENV NODE_VER="12.14.0" RUN echo "Etc/UTC" > /etc/localtime && \ apt update && \ apt -y install wget python && \ diff --git a/Gemfile b/Gemfile index bb4e65fdc..a0ad8e464 100644 --- a/Gemfile +++ b/Gemfile @@ -7,11 +7,11 @@ gem 'pkg-config', '~> 1.4' gem 'puma', '~> 4.3' gem 'rails', '~> 5.2.4' -gem 'sprockets', '~> 3.7' +gem 'sprockets', '~> 3.7.2' gem 'thor', '~> 0.20' gem 'hamlit-rails', '~> 0.2' -gem 'pg', '~> 1.1' +gem 'pg', '~> 1.2' gem 'makara', '~> 0.4' gem 'pghero', '~> 2.4' gem 'dotenv-rails', '~> 2.7' @@ -31,7 +31,7 @@ gem 'browser' gem 'charlock_holmes', '~> 0.7.7' gem 'iso-639' gem 'chewy', '~> 5.1' -gem 'cld3', '~> 3.2.4' +gem 'cld3', '~> 3.2.6' gem 'devise', '~> 4.7' gem 'devise-two-factor', '~> 3.1' @@ -50,7 +50,7 @@ gem 'fast_blank', '~> 1.0' gem 'fastimage' gem 'goldfinger', '~> 2.1' gem 'hiredis', '~> 0.6' -gem 'redis-namespace', '~> 1.5' +gem 'redis-namespace', '~> 1.7' gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b' gem 'html2text' gem 'htmlentities', '~> 4.3' @@ -61,7 +61,7 @@ gem 'httplog', '~> 1.3' gem 'idn-ruby', require: 'idn' gem 'kaminari', '~> 1.1' gem 'link_header', '~> 0.0' -gem 'mime-types', '~> 3.3', require: 'mime/types/columnar' +gem 'mime-types', '~> 3.3.1', require: 'mime/types/columnar' gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532' gem 'nokogiri', '~> 1.10' gem 'nsa', '~> 0.2' @@ -120,8 +120,8 @@ end group :test do gem 'capybara', '~> 3.29' gem 'climate_control', '~> 0.2' - gem 'faker', '~> 2.9' - gem 'microformats', '~> 4.1' + gem 'faker', '~> 2.10' + gem 'microformats', '~> 4.2' gem 'rails-controller-testing', '~> 1.0' gem 'rspec-sidekiq', '~> 3.0' gem 'simplecov', '~> 0.17', require: false diff --git a/Gemfile.lock b/Gemfile.lock index cfa4201fd..fec95d27f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -185,10 +185,10 @@ GEM crack (0.4.3) safe_yaml (~> 1.0.0) crass (1.0.5) - css_parser (1.7.0) + css_parser (1.7.1) addressable debug_inspector (0.0.3) - derailed_benchmarks (1.4.2) + derailed_benchmarks (1.4.3) benchmark-ips (~> 2) get_process_mem (~> 0) heapy (~> 0) @@ -240,9 +240,9 @@ GEM tzinfo excon (0.71.0) fabrication (2.21.0) - faker (2.9.0) + faker (2.10.0) i18n (>= 1.6, < 1.8) - faraday (0.15.4) + faraday (1.0.0) multipart-post (>= 1.2, < 3) fast_blank (1.0.0) fastimage (2.1.7) @@ -275,8 +275,8 @@ GEM http (~> 3.0) nokogiri (~> 1.8) oj (~> 3.0) - hamlit (2.9.3) - temple (>= 0.8.0) + hamlit (2.11.0) + temple (>= 0.8.2) thor tilt hamlit-rails (0.2.3) @@ -374,9 +374,9 @@ GEM microformats (4.1.0) json (~> 2.1) nokogiri (~> 1.8, >= 1.8.3) - mime-types (3.3) + mime-types (3.3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2019.0904) + mime-types-data (3.2019.1009) mimemagic (0.3.3) mini_mime (1.0.2) mini_portile2 (2.4.0) @@ -434,7 +434,7 @@ GEM pastel (0.7.3) equatable (~> 0.6) tty-color (~> 0.5) - pg (1.1.4) + pg (1.2.0) pghero (2.4.1) activerecord (>= 5) pkg-config (1.4.0) @@ -454,7 +454,7 @@ GEM pry (~> 0.10) pry-rails (0.3.9) pry (>= 0.10.4) - public_suffix (4.0.1) + public_suffix (4.0.2) puma (4.3.1) nio4r (~> 2.0) pundit (2.1.0) @@ -463,7 +463,7 @@ GEM rack (2.0.8) rack-attack (6.2.2) rack (>= 1.0, < 3) - rack-cors (1.1.0) + rack-cors (1.1.1) rack (>= 2.0.0) rack-protection (2.0.7) rack @@ -520,7 +520,7 @@ GEM redis-activesupport (5.0.4) activesupport (>= 3, < 6) redis-store (>= 1.3, < 2) - redis-namespace (1.6.0) + redis-namespace (1.7.0) redis (>= 3.0.4) redis-rack (2.0.4) rack (>= 1.5, < 3) @@ -532,7 +532,7 @@ GEM redis-store (1.5.0) redis (>= 2.2, < 5) regexp_parser (1.6.0) - request_store (1.4.1) + request_store (1.5.0) rack (>= 1.4) responders (3.0.0) actionpack (>= 5.0) @@ -618,21 +618,21 @@ GEM sshkit (1.20.0) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) - stackprof (0.2.14) + stackprof (0.2.15) statsd-ruby (1.4.0) stoplight (2.2.0) streamio-ffmpeg (3.0.2) multi_json (~> 1.8) strong_migrations (0.5.1) activerecord (>= 5) - temple (0.8.1) + temple (0.8.2) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) terrapin (0.6.0) climate_control (>= 0.0.3, < 1.0) thor (0.20.3) thread_safe (0.3.6) - tilt (2.0.9) + tilt (2.0.10) tty-color (0.5.0) tty-command (0.9.0) pastel (~> 0.7.0) @@ -654,7 +654,7 @@ GEM tzinfo (>= 1.0.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.5) + unf_ext (0.0.7.6) unicode-display_width (1.6.0) uniform_notifier (1.12.1) warden (1.2.8) @@ -713,7 +713,7 @@ DEPENDENCIES doorkeeper (~> 5.2) dotenv-rails (~> 2.7) fabrication (~> 2.21) - faker (~> 2.9) + faker (~> 2.10) fast_blank (~> 1.0) fastimage fog-core (<= 2.1.0) @@ -759,7 +759,7 @@ DEPENDENCIES parallel (~> 1.19) parallel_tests (~> 2.30) parslet - pg (~> 1.1) + pg (~> 1.2) pghero (~> 2.4) pkg-config (~> 1.4) posix-spawn! @@ -778,7 +778,7 @@ DEPENDENCIES rdf-normalize (~> 0.3) redcarpet (~> 3.4) redis (~> 4.1) - redis-namespace (~> 1.5) + redis-namespace (~> 1.7) redis-rails (~> 5.0) rqrcode (~> 0.10) rspec-rails (~> 3.9) @@ -808,9 +808,3 @@ DEPENDENCIES webmock (~> 3.7) webpacker (~> 4.2) webpush - -RUBY VERSION - ruby 2.6.5p114 - -BUNDLED WITH - 1.17.3 diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb index 2af90f051..a446465c9 100644 --- a/app/controllers/admin/custom_emojis_controller.rb +++ b/app/controllers/admin/custom_emojis_controller.rb @@ -2,10 +2,6 @@ module Admin class CustomEmojisController < BaseController - include ObfuscateFilename - - obfuscate_filename [:custom_emoji, :image] - def index authorize :custom_emoji, :index? diff --git a/app/controllers/api/v1/accounts/follower_accounts_controller.rb b/app/controllers/api/v1/accounts/follower_accounts_controller.rb index 2dabb8398..e360b8a92 100644 --- a/app/controllers/api/v1/accounts/follower_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/follower_accounts_controller.rb @@ -21,11 +21,13 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController def load_accounts return [] if hide_results? - default_accounts.merge(paginated_follows).to_a + scope = default_accounts + scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? + scope.merge(paginated_follows).to_a end def hide_results? - (@account.user_hides_network? && current_account.id != @account.id) || (current_account && @account.blocking?(current_account)) + (@account.user_hides_network? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account)) end def default_accounts diff --git a/app/controllers/api/v1/accounts/following_accounts_controller.rb b/app/controllers/api/v1/accounts/following_accounts_controller.rb index 44e89804b..a405b365f 100644 --- a/app/controllers/api/v1/accounts/following_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/following_accounts_controller.rb @@ -21,11 +21,13 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController def load_accounts return [] if hide_results? - default_accounts.merge(paginated_follows).to_a + scope = default_accounts + scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? + scope.merge(paginated_follows).to_a end def hide_results? - (@account.user_hides_network? && current_account.id != @account.id) || (current_account && @account.blocking?(current_account)) + (@account.user_hides_network? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account)) end def default_accounts diff --git a/app/controllers/api/v1/media_controller.rb b/app/controllers/api/v1/media_controller.rb index aaa93b615..81825db15 100644 --- a/app/controllers/api/v1/media_controller.rb +++ b/app/controllers/api/v1/media_controller.rb @@ -4,9 +4,6 @@ class Api::V1::MediaController < Api::BaseController before_action -> { doorkeeper_authorize! :write, :'write:media' } before_action :require_user! - include ObfuscateFilename - obfuscate_filename :file - respond_to :json def create diff --git a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb index 657e57831..99eff360e 100644 --- a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb @@ -17,7 +17,9 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController private def load_accounts - default_accounts.merge(paginated_favourites).to_a + scope = default_accounts + scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? + scope.merge(paginated_favourites).to_a end def default_accounts diff --git a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb index 6851099f6..cc285ad23 100644 --- a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb @@ -17,7 +17,9 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController private def load_accounts - default_accounts.merge(paginated_statuses).to_a + scope = default_accounts + scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? + scope.merge(paginated_statuses).to_a end def default_accounts diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f1a4f0d02..c882d40ab 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -25,6 +25,7 @@ class ApplicationController < ActionController::Base rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity rescue_from ActionController::UnknownFormat, with: :not_acceptable rescue_from ActionController::ParameterMissing, with: :bad_request + rescue_from Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from Mastodon::NotPermittedError, with: :forbidden rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error @@ -211,7 +212,12 @@ class ApplicationController < ActionController::Base end def respond_with_error(code) - use_pack 'error' - render "errors/#{code}", layout: 'error', status: code, formats: [:html] + respond_to do |format| + format.any do + use_pack 'error' + render "errors/#{code}", layout: 'error', status: code, formats: [:html] + end + format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code } + end end end diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index 068375843..a9d075a45 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -11,6 +11,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController before_action :set_instance_presenter, only: [:new, :create, :update] before_action :set_body_classes, only: [:new, :create, :edit, :update] before_action :require_not_suspended!, only: [:update] + before_action :set_cache_headers, only: [:edit, :update] skip_before_action :require_functional!, only: [:edit, :update] @@ -114,4 +115,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController def require_not_suspended! forbidden if current_account.suspended? end + + def set_cache_headers + response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate' + end end diff --git a/app/controllers/concerns/obfuscate_filename.rb b/app/controllers/concerns/obfuscate_filename.rb deleted file mode 100644 index 22736ec3a..000000000 --- a/app/controllers/concerns/obfuscate_filename.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module ObfuscateFilename - extend ActiveSupport::Concern - - class_methods do - def obfuscate_filename(path) - before_action do - file = params.dig(*path) - next if file.nil? - - file.original_filename = SecureRandom.hex(8) + File.extname(file.original_filename) - end - end - end -end diff --git a/app/controllers/filters_controller.rb b/app/controllers/filters_controller.rb index f1e110d87..76be03e53 100644 --- a/app/controllers/filters_controller.rb +++ b/app/controllers/filters_controller.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true class FiltersController < ApplicationController - include Authorization - layout 'admin' + before_action :authenticate_user! before_action :set_filters, only: :index before_action :set_filter, only: [:edit, :update, :destroy] before_action :set_pack diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb index df46f5f72..a5dfffd6d 100644 --- a/app/controllers/follower_accounts_controller.rb +++ b/app/controllers/follower_accounts_controller.rb @@ -19,7 +19,6 @@ class FollowerAccountsController < ApplicationController next if @account.user_hides_network? follows - @relationships = AccountRelationshipsPresenter.new(follows.map(&:account_id), current_user.account_id) if user_signed_in? end format.json do @@ -38,7 +37,11 @@ class FollowerAccountsController < ApplicationController private def follows - @follows ||= Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account) + return @follows if defined?(@follows) + + scope = Follow.where(target_account: @account) + scope = scope.where.not(account_id: current_account.excluded_from_timeline_account_ids) if user_signed_in? + @follows = scope.recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account) end def page_requested? diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb index 8cab67ff5..ff23d97f9 100644 --- a/app/controllers/following_accounts_controller.rb +++ b/app/controllers/following_accounts_controller.rb @@ -19,7 +19,6 @@ class FollowingAccountsController < ApplicationController next if @account.user_hides_network? follows - @relationships = AccountRelationshipsPresenter.new(follows.map(&:target_account_id), current_user.account_id) if user_signed_in? end format.json do @@ -38,7 +37,11 @@ class FollowingAccountsController < ApplicationController private def follows - @follows ||= Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account) + return @follows if defined?(@follows) + + scope = Follow.where(account: @account) + scope = scope.where.not(target_account_id: current_account.excluded_from_timeline_account_ids) if user_signed_in? + @follows = scope.recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account) end def page_requested? diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb index f6f5d1ecc..137346ed0 100644 --- a/app/controllers/oauth/authorizations_controller.rb +++ b/app/controllers/oauth/authorizations_controller.rb @@ -6,6 +6,7 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController before_action :store_current_location before_action :authenticate_resource_owner! before_action :set_pack + before_action :set_cache_headers include Localized @@ -32,4 +33,8 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController def truthy_param?(key) ActiveModel::Type::Boolean.new.cast(params[key]) end + + def set_cache_headers + response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate' + end end diff --git a/app/controllers/settings/base_controller.rb b/app/controllers/settings/base_controller.rb index 8c394a6d3..b97603af6 100644 --- a/app/controllers/settings/base_controller.rb +++ b/app/controllers/settings/base_controller.rb @@ -3,6 +3,7 @@ class Settings::BaseController < ApplicationController before_action :set_pack before_action :set_body_classes + before_action :set_cache_headers private @@ -13,4 +14,8 @@ class Settings::BaseController < ApplicationController def set_body_classes @body_classes = 'admin' end + + def set_cache_headers + response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate' + end end diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb index 8b640cdca..19a7ce157 100644 --- a/app/controllers/settings/profiles_controller.rb +++ b/app/controllers/settings/profiles_controller.rb @@ -1,16 +1,11 @@ # frozen_string_literal: true class Settings::ProfilesController < Settings::BaseController - include ObfuscateFilename - layout 'admin' before_action :authenticate_user! before_action :set_account - obfuscate_filename [:account, :avatar] - obfuscate_filename [:account, :header] - def show @account.build_fields end diff --git a/app/controllers/well_known/host_meta_controller.rb b/app/controllers/well_known/host_meta_controller.rb index 2e9298c4a..2fd6bc7cc 100644 --- a/app/controllers/well_known/host_meta_controller.rb +++ b/app/controllers/well_known/host_meta_controller.rb @@ -8,12 +8,8 @@ module WellKnown def show @webfinger_template = "#{webfinger_url}?resource={uri}" - - respond_to do |format| - format.xml { render content_type: 'application/xrd+xml' } - end - expires_in 3.days, public: true + render content_type: 'application/xrd+xml', formats: [:xml] end end end diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb index 7fcc4e816..986fd1805 100644 --- a/app/helpers/accounts_helper.rb +++ b/app/helpers/accounts_helper.rb @@ -13,7 +13,7 @@ module AccountsHelper if account.local? "@#{account.acct}@#{Rails.configuration.x.local_domain}" else - "@#{account.acct}" + "@#{account.pretty_acct}" end end diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js index 06a19afc3..5640201c6 100644 --- a/app/javascript/mastodon/actions/statuses.js +++ b/app/javascript/mastodon/actions/statuses.js @@ -26,8 +26,9 @@ export const STATUS_UNMUTE_REQUEST = 'STATUS_UNMUTE_REQUEST'; export const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS'; export const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL'; -export const STATUS_REVEAL = 'STATUS_REVEAL'; -export const STATUS_HIDE = 'STATUS_HIDE'; +export const STATUS_REVEAL = 'STATUS_REVEAL'; +export const STATUS_HIDE = 'STATUS_HIDE'; +export const STATUS_COLLAPSE = 'STATUS_COLLAPSE'; export const REDRAFT = 'REDRAFT'; @@ -320,3 +321,11 @@ export function revealStatus(ids) { ids, }; }; + +export function toggleStatusCollapse(id, isCollapsed) { + return { + type: STATUS_COLLAPSE, + id, + isCollapsed, + }; +} diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js index 421756803..47a87b149 100644 --- a/app/javascript/mastodon/components/scrollable_list.js +++ b/app/javascript/mastodon/components/scrollable_list.js @@ -208,10 +208,13 @@ export default class ScrollableList extends PureComponent { } attachIntersectionObserver () { - this.intersectionObserverWrapper.connect({ + let nodeOptions = { root: this.node, rootMargin: '300% 0px', - }); + }; + + this.intersectionObserverWrapper + .connect(this.props.bindToDocument ? {} : nodeOptions); } detachIntersectionObserver () { diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index e120278a0..0dc00cb98 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -76,6 +76,7 @@ class Status extends ImmutablePureComponent { onEmbed: PropTypes.func, onHeightChange: PropTypes.func, onToggleHidden: PropTypes.func, + onToggleCollapsed: PropTypes.func, muted: PropTypes.bool, hidden: PropTypes.bool, unread: PropTypes.bool, @@ -102,19 +103,6 @@ class Status extends ImmutablePureComponent { statusId: undefined, }; - // Track height changes we know about to compensate scrolling - componentDidMount () { - this.didShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card'); - } - - getSnapshotBeforeUpdate () { - if (this.props.getScrollPosition) { - return this.props.getScrollPosition(); - } else { - return null; - } - } - static getDerivedStateFromProps(nextProps, prevState) { if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) { return { @@ -126,32 +114,6 @@ class Status extends ImmutablePureComponent { } } - // Compensate height changes - componentDidUpdate (prevProps, prevState, snapshot) { - const doShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card'); - - if (doShowCard && !this.didShowCard) { - this.didShowCard = true; - - if (snapshot !== null && this.props.updateScrollBottom) { - if (this.node && this.node.offsetTop < snapshot.top) { - this.props.updateScrollBottom(snapshot.height - snapshot.top); - } - } - } - } - - componentWillUnmount() { - if (this.node && this.props.getScrollPosition) { - const position = this.props.getScrollPosition(); - if (position !== null && this.node.offsetTop < position.top) { - requestAnimationFrame(() => { - this.props.updateScrollBottom(position.height - position.top); - }); - } - } - } - handleToggleMediaVisibility = () => { this.setState({ showMedia: !this.state.showMedia }); } @@ -196,7 +158,11 @@ class Status extends ImmutablePureComponent { handleExpandedToggle = () => { this.props.onToggleHidden(this._properStatus()); - }; + } + + handleCollapsedToggle = isCollapsed => { + this.props.onToggleCollapsed(this._properStatus(), isCollapsed); + } renderLoadingMediaGallery () { return <div className='media-gallery' style={{ height: '110px' }} />; @@ -466,7 +432,7 @@ class Status extends ImmutablePureComponent { </a> </div> - <StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} collapsable /> + <StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} collapsable onCollapsedToggle={this.handleCollapsedToggle} /> {media} diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index d13091325..5d921fd41 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -23,11 +23,11 @@ export default class StatusContent extends React.PureComponent { onExpandedToggle: PropTypes.func, onClick: PropTypes.func, collapsable: PropTypes.bool, + onCollapsedToggle: PropTypes.func, }; state = { hidden: true, - collapsed: null, // `collapsed: null` indicates that an element doesn't need collapsing, while `true` or `false` indicates that it does (and is/isn't). }; _updateStatusLinks () { @@ -62,14 +62,16 @@ export default class StatusContent extends React.PureComponent { link.setAttribute('rel', 'noopener noreferrer'); } - if ( - this.props.collapsable - && this.props.onClick - && this.state.collapsed === null - && node.clientHeight > MAX_HEIGHT - && this.props.status.get('spoiler_text').length === 0 - ) { - this.setState({ collapsed: true }); + if (this.props.status.get('collapsed', null) === null) { + let collapsed = + this.props.collapsable + && this.props.onClick + && node.clientHeight > MAX_HEIGHT + && this.props.status.get('spoiler_text').length === 0; + + if(this.props.onCollapsedToggle) this.props.onCollapsedToggle(collapsed); + + this.props.status.set('collapsed', collapsed); } } @@ -178,6 +180,7 @@ export default class StatusContent extends React.PureComponent { } const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden; + const renderReadMore = this.props.onClick && status.get('collapsed'); const content = { __html: status.get('contentHtml') }; const spoilerContent = { __html: status.get('spoilerHtml') }; @@ -185,7 +188,7 @@ export default class StatusContent extends React.PureComponent { const classNames = classnames('status__content', { 'status__content--with-action': this.props.onClick && this.context.router, 'status__content--with-spoiler': status.get('spoiler_text').length > 0, - 'status__content--collapsed': this.state.collapsed === true, + 'status__content--collapsed': renderReadMore, }); if (isRtl(status.get('search_index'))) { @@ -237,7 +240,7 @@ export default class StatusContent extends React.PureComponent { </div>, ]; - if (this.state.collapsed) { + if (renderReadMore) { output.push(readMoreButton); } diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js index 35c16a20c..2ba3a3123 100644 --- a/app/javascript/mastodon/containers/status_container.js +++ b/app/javascript/mastodon/containers/status_container.js @@ -23,6 +23,7 @@ import { deleteStatus, hideStatus, revealStatus, + toggleStatusCollapse, } from '../actions/statuses'; import { unmuteAccount, @@ -190,6 +191,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, + onToggleCollapsed (status, isCollapsed) { + dispatch(toggleStatusCollapse(status.get('id'), isCollapsed)); + }, + onBlockDomain (domain) { dispatch(openModal('CONFIRM', { message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />, diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index 772f98bcb..398a48cff 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -12,6 +12,7 @@ import { STATUS_UNMUTE_SUCCESS, STATUS_REVEAL, STATUS_HIDE, + STATUS_COLLAPSE, } from '../actions/statuses'; import { TIMELINE_DELETE } from '../actions/timelines'; import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer'; @@ -73,6 +74,8 @@ export default function statuses(state = initialState, action) { } }); }); + case STATUS_COLLAPSE: + return state.setIn([action.id, 'collapsed'], action.isCollapsed); case TIMELINE_DELETE: return deleteStatus(state, action.id, action.references); default: diff --git a/app/javascript/mastodon/utils/numbers.js b/app/javascript/mastodon/utils/numbers.js index f7e4ceb93..af18dcfdd 100644 --- a/app/javascript/mastodon/utils/numbers.js +++ b/app/javascript/mastodon/utils/numbers.js @@ -4,9 +4,13 @@ import { FormattedNumber } from 'react-intl'; export const shortNumberFormat = number => { if (number < 1000) { return <FormattedNumber value={number} />; - } else if (number < 1000000) { + } else if (number < 10000) { return <Fragment><FormattedNumber value={number / 1000} maximumFractionDigits={1} />K</Fragment>; - } else { + } else if (number < 1000000) { + return <Fragment><FormattedNumber value={number / 1000} maximumFractionDigits={0} />K</Fragment>; + } else if (number < 10000000) { return <Fragment><FormattedNumber value={number / 1000000} maximumFractionDigits={1} />M</Fragment>; + } else { + return <Fragment><FormattedNumber value={number / 1000000} maximumFractionDigits={0} />M</Fragment>; } }; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 01a633c5f..1a5d9e3e3 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -6399,13 +6399,13 @@ noscript { &__links { font-size: 14px; color: $darker-text-color; + padding: 10px 0; a { display: inline-block; color: $darker-text-color; text-decoration: none; - padding: 10px; - padding-top: 20px; + padding: 5px 10px; font-weight: 500; strong { diff --git a/app/lib/search_query_transformer.rb b/app/lib/search_query_transformer.rb index 6a299f59d..e07ebfffe 100644 --- a/app/lib/search_query_transformer.rb +++ b/app/lib/search_query_transformer.rb @@ -78,7 +78,7 @@ class SearchQueryTransformer < Parslet::Transform elsif clause[:shortcode] TermClause.new(prefix, operator, ":#{clause[:term]}:") elsif clause[:phrase] - PhraseClause.new(prefix, operator, clause[:phrase].map { |p| p[:term].to_s }.join(' ')) + PhraseClause.new(prefix, operator, clause[:phrase].is_a?(Array) ? clause[:phrase].map { |p| p[:term].to_s }.join(' ') : clause[:phrase].to_s) else raise "Unexpected clause type: #{clause}" end diff --git a/app/middleware/handle_bad_encoding_middleware.rb b/app/middleware/handle_bad_encoding_middleware.rb new file mode 100644 index 000000000..6fce84b15 --- /dev/null +++ b/app/middleware/handle_bad_encoding_middleware.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true +# See: https://jamescrisp.org/2018/05/28/fixing-invalid-query-parameters-invalid-encoding-in-a-rails-app/ + +class HandleBadEncodingMiddleware + def initialize(app) + @app = app + end + + def call(env) + begin + Rack::Utils.parse_nested_query(env['QUERY_STRING'].to_s) + rescue Rack::Utils::InvalidParameterError + env['QUERY_STRING'] = '' + end + + @app.call(env) + end +end diff --git a/app/models/account.rb b/app/models/account.rb index 25cde6d6c..86f7295bc 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -50,7 +50,7 @@ class Account < ApplicationRecord USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i - MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i + MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[[:word:]\.\-]+[a-z0-9]+)?)/i include AccountAssociations include AccountAvatar @@ -168,6 +168,10 @@ class Account < ApplicationRecord local? ? username : "#{username}@#{domain}" end + def pretty_acct + local? ? username : "#{username}@#{Addressable::IDNA.to_unicode(domain)}" + end + def local_username_and_domain "#{username}@#{Rails.configuration.x.local_domain}" end diff --git a/app/models/concerns/attachmentable.rb b/app/models/concerns/attachmentable.rb index 3bbc6453c..1e8c4806f 100644 --- a/app/models/concerns/attachmentable.rb +++ b/app/models/concerns/attachmentable.rb @@ -9,6 +9,7 @@ module Attachmentable GIF_MATRIX_LIMIT = 921_600 # 1280x720px included do + before_post_process :obfuscate_file_name before_post_process :set_file_extensions before_post_process :check_image_dimensions before_post_process :set_file_content_type @@ -68,4 +69,14 @@ module Attachmentable rescue Terrapin::CommandLineError '' end + + def obfuscate_file_name + self.class.attachment_definitions.each_key do |attachment_name| + attachment = send(attachment_name) + + next if attachment.blank? + + attachment.instance_write :file_name, SecureRandom.hex(8) + File.extname(attachment.instance_read(:file_name)) + end + end end diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 880599028..b87b1b9d3 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -202,9 +202,12 @@ class MediaAttachment < ApplicationRecord end after_commit :reset_parent_cache, on: :update + before_create :prepare_description, unless: :local? before_create :set_shortcode + before_post_process :set_type_and_extension + before_save :set_meta class << self diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index 7bdb5d7ff..657dd36f3 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -24,6 +24,10 @@ class REST::AccountSerializer < ActiveModel::Serializer object.id.to_s end + def acct + object.pretty_acct + end + def note Formatter.instance.simplified_format(object) end diff --git a/app/services/backup_service.rb b/app/services/backup_service.rb index 0b57b6d0c..ab6d090a0 100644 --- a/app/services/backup_service.rb +++ b/app/services/backup_service.rb @@ -166,7 +166,7 @@ class BackupService < BaseService io.write(buffer) end end - rescue Errno::ENOENT + rescue Errno::ENOENT, Seahorse::Client::NetworkingError Rails.logger.warn "Could not backup file #{filename}: file not found" end end diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index 19de37717..3c257451c 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -14,7 +14,16 @@ class ProcessMentionsService < BaseService mentions = [] status.text = status.text.gsub(Account::MENTION_RE) do |match| - username, domain = Regexp.last_match(1).split('@') + username, domain = Regexp.last_match(1).split('@') + + domain = begin + if TagManager.instance.local_domain?(domain) + nil + else + TagManager.instance.normalize_domain(domain) + end + end + mentioned_account = Account.find_remote(username, domain) if mention_undeliverable?(mentioned_account) diff --git a/app/services/resolve_url_service.rb b/app/services/resolve_url_service.rb index 79b1bad0c..1a2b0d60c 100644 --- a/app/services/resolve_url_service.rb +++ b/app/services/resolve_url_service.rb @@ -12,6 +12,8 @@ class ResolveURLService < BaseService process_local_url elsif !fetched_resource.nil? process_url + elsif @on_behalf_of.present? + process_url_from_db end end @@ -24,15 +26,19 @@ class ResolveURLService < BaseService status = FetchRemoteStatusService.new.call(resource_url, body) authorize_with @on_behalf_of, status, :show? unless status.nil? status - elsif fetched_resource.nil? && @on_behalf_of.present? - # It may happen that the resource is a private toot, and thus not fetchable, - # but we can return the toot if we already know about it. - status = Status.find_by(uri: @url) || Status.find_by(url: @url) - authorize_with @on_behalf_of, status, :show? unless status.nil? - status end end + def process_url_from_db + # It may happen that the resource is a private toot, and thus not fetchable, + # but we can return the toot if we already know about it. + status = Status.find_by(uri: @url) || Status.find_by(url: @url) + authorize_with @on_behalf_of, status, :show? unless status.nil? + status + rescue Mastodon::NotPermittedError + nil + end + def fetched_resource @fetched_resource ||= FetchResourceService.new.call(@url) end diff --git a/app/views/admin/tags/_tag.html.haml b/app/views/admin/tags/_tag.html.haml index 670f3bc05..287d28e53 100644 --- a/app/views/admin/tags/_tag.html.haml +++ b/app/views/admin/tags/_tag.html.haml @@ -1,6 +1,7 @@ .batch-table__row - %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox - = f.check_box :tag_ids, { multiple: true, include_hidden: false }, tag.id + - if batch_available + %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox + = f.check_box :tag_ids, { multiple: true, include_hidden: false }, tag.id .directory__tag = link_to admin_tag_path(tag.id) do diff --git a/app/views/admin/tags/index.html.haml b/app/views/admin/tags/index.html.haml index 298ac59e9..aa4f4c297 100644 --- a/app/views/admin/tags/index.html.haml +++ b/app/views/admin/tags/index.html.haml @@ -47,25 +47,26 @@ .batch-table.optional .batch-table__toolbar - %label.batch-table__toolbar__select.batch-checkbox-all - = check_box_tag :batch_checkbox_all, nil, false - .batch-table__toolbar__actions - - if params[:pending_review] == '1' + - if params[:pending_review] == '1' || params[:unreviewed] == '1' + %label.batch-table__toolbar__select.batch-checkbox-all + = check_box_tag :batch_checkbox_all, nil, false + .batch-table__toolbar__actions = f.button safe_join([fa_icon('check'), t('admin.accounts.approve')]), name: :approve, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } = f.button safe_join([fa_icon('times'), t('admin.accounts.reject')]), name: :reject, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - - else + - else + .batch-table__toolbar__actions %span.neutral-hint= t('generic.no_batch_actions_available') .batch-table__body - if @tags.empty? = nothing_here 'nothing-here--under-tabs' - else - = render partial: 'tag', collection: @tags, locals: { f: f } + = render partial: 'tag', collection: @tags, locals: { f: f, batch_available: params[:pending_review] == '1' || params[:unreviewed] == '1' } = paginate @tags -- if params[:pending_review] == '1' +- if params[:pending_review] == '1' || params[:unreviewed] == '1' %hr.spacer/ %div{ style: 'overflow: hidden' } diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml index f1e3d2e97..f460cebba 100644 --- a/app/views/settings/preferences/appearance/show.html.haml +++ b/app/views/settings/preferences/appearance/show.html.haml @@ -5,6 +5,10 @@ .fields-group = f.input :locale, collection: I18n.available_locales, wrapper: :with_label, include_blank: false, label_method: lambda { |locale| human_locale(locale) }, selected: I18n.locale, hint: false + - unless I18n.locale == :en + .flash-message{ style: "text-align: unset; color: unset" } + #{t 'appearance.localization.body'} #{content_tag(:a, t('appearance.localization.guide_link_text'), href: t('appearance.localization.guide_link'), target: "_blank", rel: "noopener", style: "text-decoration: underline")} + %h4= t 'appearance.advanced_web_interface' %p.hint= t 'appearance.advanced_web_interface_hint' diff --git a/app/views/statuses/embed.html.haml b/app/views/statuses/embed.html.haml index 6f2ec646f..2f111f53f 100644 --- a/app/views/statuses/embed.html.haml +++ b/app/views/statuses/embed.html.haml @@ -1,3 +1,2 @@ -- cache @status do - .activity-stream.activity-stream--headless - = render 'status', status: @status, centered: true, autoplay: @autoplay +.activity-stream.activity-stream--headless + = render 'status', status: @status, centered: true, autoplay: @autoplay diff --git a/app/workers/refollow_worker.rb b/app/workers/refollow_worker.rb index 12f2bf671..9b07ce1b5 100644 --- a/app/workers/refollow_worker.rb +++ b/app/workers/refollow_worker.rb @@ -7,15 +7,18 @@ class RefollowWorker def perform(target_account_id) target_account = Account.find(target_account_id) - return unless target_account.protocol == :activitypub + return unless target_account.activitypub? + + target_account.passive_relationships.where(account: Account.where(domain: nil)).includes(:account).reorder(nil).find_each do |follow| + reblogs = follow.show_reblogs? - target_account.followers.where(domain: nil).reorder(nil).find_each do |follower| # Locally unfollow remote account + follower = follow.account follower.unfollow!(target_account) # Schedule re-follow begin - FollowService.new.call(follower, target_account) + FollowService.new.call(follower, target_account, reblogs: reblogs) rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound, Mastodon::UnexpectedResponseError, HTTP::Error, OpenSSL::SSL::SSLError next end diff --git a/config/application.rb b/config/application.rb index e1f7ae707..58e59fd51 100644 --- a/config/application.rb +++ b/config/application.rb @@ -7,6 +7,7 @@ require 'rails/all' Bundler.require(*Rails.groups) require_relative '../app/lib/exceptions' +require_relative '../app/middleware/handle_bad_encoding_middleware' require_relative '../lib/paperclip/lazy_thumbnail' require_relative '../lib/paperclip/gif_transcoder' require_relative '../lib/paperclip/video_transcoder' @@ -118,6 +119,7 @@ module Mastodon config.active_job.queue_adapter = :sidekiq + config.middleware.insert_before Rack::Runtime, HandleBadEncodingMiddleware config.middleware.use Rack::Attack config.middleware.use Rack::Deflater diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 7784bec62..e03380cec 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -8,20 +8,15 @@ Doorkeeper.configure do end resource_owner_from_credentials do |_routes| - if Devise.ldap_authentication - user = User.authenticate_with_ldap({ :email => request.params[:username], :password => request.params[:password] }) - end - - if Devise.pam_authentication - user ||= User.authenticate_with_ldap({ :email => request.params[:username], :password => request.params[:password] }) - end + user = User.authenticate_with_ldap(email: request.params[:username], password: request.params[:password]) if Devise.ldap_authentication + user ||= User.authenticate_with_pam(email: request.params[:username], password: request.params[:password]) if Devise.pam_authentication if user.nil? user = User.find_by(email: request.params[:username]) - user = nil unless user.valid_password?(request.params[:password]) + user = nil unless user&.valid_password?(request.params[:password]) end - user if !user&.otp_required_for_login? + user unless user&.otp_required_for_login? end # If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below. diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb index 5109baff7..8909678d6 100644 --- a/config/initializers/paperclip.rb +++ b/config/initializers/paperclip.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +Paperclip::DataUriAdapter.register + Paperclip.interpolates :filename do |attachment, style| if style == :original attachment.original_filename diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index 273cac9ca..3cd7ea3a6 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -46,10 +46,7 @@ class Rack::Attack PROTECTED_PATHS_REGEX = Regexp.union(PROTECTED_PATHS.map { |path| /\A#{Regexp.escape(path)}/ }) - # Always allow requests from localhost - # (blocklist & throttles are skipped) Rack::Attack.safelist('allow from localhost') do |req| - # Requests are allowed if the return value is truthy req.remote_ip == '127.0.0.1' || req.remote_ip == '::1' end diff --git a/config/locales/en.yml b/config/locales/en.yml index 43c24fc4e..74f397a3f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -594,6 +594,10 @@ en: animations_and_accessibility: Animations and accessibility confirmation_dialogs: Confirmation dialogs discovery: Discovery + localization: + body: Mastodon is translated by volunteers. + guide_link: https://crowdin.com/project/mastodon + guide_link_text: Everyone can contribute. sensitive_content: Sensitive content toot_layout: Toot layout application_mailer: diff --git a/db/migrate/20170711225116_fix_null_booleans.rb b/db/migrate/20170711225116_fix_null_booleans.rb index 5b319471d..aabb81f21 100644 --- a/db/migrate/20170711225116_fix_null_booleans.rb +++ b/db/migrate/20170711225116_fix_null_booleans.rb @@ -1,17 +1,19 @@ class FixNullBooleans < ActiveRecord::Migration[5.1] def change - change_column_default :domain_blocks, :reject_media, false - change_column_null :domain_blocks, :reject_media, false, false + safety_assured do + change_column_default :domain_blocks, :reject_media, false + change_column_null :domain_blocks, :reject_media, false, false - change_column_default :imports, :approved, false - change_column_null :imports, :approved, false, false + change_column_default :imports, :approved, false + change_column_null :imports, :approved, false, false - change_column_null :statuses, :sensitive, false, false - change_column_null :statuses, :reply, false, false + change_column_null :statuses, :sensitive, false, false + change_column_null :statuses, :reply, false, false - change_column_null :users, :admin, false, false + change_column_null :users, :admin, false, false - change_column_default :users, :otp_required_for_login, false - change_column_null :users, :otp_required_for_login, false, false + change_column_default :users, :otp_required_for_login, false + change_column_null :users, :otp_required_for_login, false, false + end end end diff --git a/db/migrate/20171010025614_change_accounts_nonnullable_in_account_moderation_notes.rb b/db/migrate/20171010025614_change_accounts_nonnullable_in_account_moderation_notes.rb index 747e5a826..1d7a0086c 100644 --- a/db/migrate/20171010025614_change_accounts_nonnullable_in_account_moderation_notes.rb +++ b/db/migrate/20171010025614_change_accounts_nonnullable_in_account_moderation_notes.rb @@ -1,6 +1,8 @@ class ChangeAccountsNonnullableInAccountModerationNotes < ActiveRecord::Migration[5.1] def change - change_column_null :account_moderation_notes, :account_id, false - change_column_null :account_moderation_notes, :target_account_id, false + safety_assured do + change_column_null :account_moderation_notes, :account_id, false + change_column_null :account_moderation_notes, :target_account_id, false + end end end diff --git a/db/migrate/20171201000000_change_account_id_nonnullable_in_lists.rb b/db/migrate/20171201000000_change_account_id_nonnullable_in_lists.rb index 3369e3b9e..ac86c9e77 100644 --- a/db/migrate/20171201000000_change_account_id_nonnullable_in_lists.rb +++ b/db/migrate/20171201000000_change_account_id_nonnullable_in_lists.rb @@ -1,5 +1,7 @@ class ChangeAccountIdNonnullableInLists < ActiveRecord::Migration[5.1] def change - change_column_null :lists, :account_id, false + safety_assured do + change_column_null :lists, :account_id, false + end end end diff --git a/db/migrate/20180310000000_change_columns_in_notifications_nonnullable.rb b/db/migrate/20180310000000_change_columns_in_notifications_nonnullable.rb index 05ffd0501..dba789207 100644 --- a/db/migrate/20180310000000_change_columns_in_notifications_nonnullable.rb +++ b/db/migrate/20180310000000_change_columns_in_notifications_nonnullable.rb @@ -1,8 +1,10 @@ class ChangeColumnsInNotificationsNonnullable < ActiveRecord::Migration[5.1] def change - change_column_null :notifications, :activity_id, false - change_column_null :notifications, :activity_type, false - change_column_null :notifications, :account_id, false - change_column_null :notifications, :from_account_id, false + safety_assured do + change_column_null :notifications, :activity_id, false + change_column_null :notifications, :activity_type, false + change_column_null :notifications, :account_id, false + change_column_null :notifications, :from_account_id, false + end end end diff --git a/package.json b/package.json index 9e0c44550..71cab74ab 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ }, "private": true, "dependencies": { - "@babel/core": "^7.7.5", + "@babel/core": "^7.7.7", "@babel/plugin-proposal-class-properties": "^7.7.4", "@babel/plugin-proposal-decorators": "^7.7.4", "@babel/plugin-transform-react-inline-elements": "^7.7.4", @@ -69,7 +69,7 @@ "@babel/preset-react": "^7.7.4", "@babel/runtime": "^7.7.7", "@gamestdio/websocket": "^0.3.2", - "@clusterws/cws": "^0.16.0", + "@clusterws/cws": "^0.16.1", "array-includes": "^3.1.1", "atrament": "^0.2.3", "arrow-key-navigation": "^1.1.0", @@ -107,7 +107,7 @@ "intl": "^1.2.5", "intl-messageformat": "^2.2.0", "intl-relativeformat": "^6.4.3", - "is-nan": "^1.2.1", + "is-nan": "^1.3.0", "js-yaml": "^3.13.1", "lodash": "^4.17.14", "mark-loader": "^0.1.6", @@ -146,7 +146,7 @@ "react-textarea-autosize": "^7.1.2", "react-toggle": "^4.1.1", "redis": "^2.7.1", - "redux": "^4.0.4", + "redux": "^4.0.5", "redux-immutable": "^4.0.0", "redux-thunk": "^2.2.0", "rellax": "^1.10.0", @@ -157,13 +157,13 @@ "sass-loader": "^8.0.0", "stringz": "^2.0.0", "substring-trie": "^1.0.2", - "terser-webpack-plugin": "^2.2.2", + "terser-webpack-plugin": "^2.3.1", "tesseract.js": "^2.0.0-alpha.16", "throng": "^4.0.0", "tiny-queue": "^0.2.1", "uuid": "^3.3.3", "wavesurfer.js": "^3.2.0", - "webpack": "^4.41.2", + "webpack": "^4.41.5", "webpack-assets-manifest": "^3.1.1", "webpack-bundle-analyzer": "^3.6.0", "webpack-cli": "^3.3.10", @@ -175,7 +175,7 @@ "babel-jest": "^24.9.0", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.1", - "eslint": "^6.7.2", + "eslint": "^6.8.0", "eslint-plugin-import": "~2.19.1", "eslint-plugin-jsx-a11y": "~6.2.3", "eslint-plugin-promise": "~4.2.1", @@ -183,7 +183,7 @@ "jest": "^24.9.0", "raf": "^3.4.1", "react-intl-translations-manager": "^5.0.3", - "react-test-renderer": "^16.11.0", + "react-test-renderer": "^16.12.0", "sass-lint": "^1.13.1", "webpack-dev-server": "^3.9.0", "yargs": "^15.0.2" diff --git a/spec/controllers/api/proofs_controller_spec.rb b/spec/controllers/api/proofs_controller_spec.rb index dbde4927f..2fe615005 100644 --- a/spec/controllers/api/proofs_controller_spec.rb +++ b/spec/controllers/api/proofs_controller_spec.rb @@ -85,10 +85,7 @@ describe Api::ProofsController do end it 'has the correct avatar url' do - first_part = 'https://cb6e6126.ngrok.io/system/accounts/avatars/' - last_part = 'original/avatar.gif' - - expect(body_as_json[:avatar]).to match /#{Regexp.quote(first_part)}(?:\d{3,5}\/){3}#{Regexp.quote(last_part)}/ + expect(body_as_json[:avatar]).to match "https://cb6e6126.ngrok.io#{alice.avatar.url}" end end end diff --git a/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb index 75e0570e9..54587187f 100644 --- a/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb @@ -3,19 +3,38 @@ require 'rails_helper' describe Api::V1::Accounts::FollowerAccountsController do render_views - let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } + let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } + let(:account) { Fabricate(:account) } + let(:alice) { Fabricate(:account) } + let(:bob) { Fabricate(:account) } before do - Fabricate(:follow, target_account: user.account) + alice.follow!(account) + bob.follow!(account) allow(controller).to receive(:doorkeeper_token) { token } end describe 'GET #index' do it 'returns http success' do - get :index, params: { account_id: user.account.id, limit: 1 } + get :index, params: { account_id: account.id, limit: 2 } expect(response).to have_http_status(200) end + + it 'returns accounts following the given account' do + get :index, params: { account_id: account.id, limit: 2 } + + expect(body_as_json.size).to eq 2 + expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s]) + end + + it 'does not return blocked users' do + user.account.block!(bob) + get :index, params: { account_id: account.id, limit: 2 } + + expect(body_as_json.size).to eq 1 + expect(body_as_json[0][:id]).to eq alice.id.to_s + end end end diff --git a/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb index 7f7105ad3..a580a7368 100644 --- a/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb @@ -3,19 +3,38 @@ require 'rails_helper' describe Api::V1::Accounts::FollowingAccountsController do render_views - let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } + let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } + let(:account) { Fabricate(:account) } + let(:alice) { Fabricate(:account) } + let(:bob) { Fabricate(:account) } before do - Fabricate(:follow, account: user.account) + account.follow!(alice) + account.follow!(bob) allow(controller).to receive(:doorkeeper_token) { token } end describe 'GET #index' do it 'returns http success' do - get :index, params: { account_id: user.account.id, limit: 1 } + get :index, params: { account_id: account.id, limit: 2 } expect(response).to have_http_status(200) end + + it 'returns accounts followed by the given account' do + get :index, params: { account_id: account.id, limit: 2 } + + expect(body_as_json.size).to eq 2 + expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s]) + end + + it 'does not return blocked users' do + user.account.block!(bob) + get :index, params: { account_id: account.id, limit: 2 } + + expect(body_as_json.size).to eq 1 + expect(body_as_json[0][:id]).to eq alice.id.to_s + end end end diff --git a/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb b/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb index 40f75c700..f053ae573 100644 --- a/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb @@ -6,6 +6,8 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app, scopes: 'read:accounts') } + let(:alice) { Fabricate(:account) } + let(:bob) { Fabricate(:account) } context 'with an oauth token' do before do @@ -16,14 +18,28 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control let(:status) { Fabricate(:status, account: user.account) } before do - Fabricate(:favourite, status: status) + Favourite.create!(account: alice, status: status) + Favourite.create!(account: bob, status: status) end it 'returns http success' do - get :index, params: { status_id: status.id, limit: 1 } + get :index, params: { status_id: status.id, limit: 2 } expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end + + it 'returns accounts who favorited the status' do + get :index, params: { status_id: status.id, limit: 2 } + expect(body_as_json.size).to eq 2 + expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s]) + end + + it 'does not return blocked users' do + user.account.block!(bob) + get :index, params: { status_id: status.id, limit: 2 } + expect(body_as_json.size).to eq 1 + expect(body_as_json[0][:id]).to eq alice.id.to_s + end end end diff --git a/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb b/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb index d758786dc..60908b7b3 100644 --- a/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb @@ -6,6 +6,8 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app, scopes: 'read:accounts') } + let(:alice) { Fabricate(:account) } + let(:bob) { Fabricate(:account) } context 'with an oauth token' do before do @@ -16,14 +18,28 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll let(:status) { Fabricate(:status, account: user.account) } before do - Fabricate(:status, reblog_of_id: status.id) + Fabricate(:status, account: alice, reblog_of_id: status.id) + Fabricate(:status, account: bob, reblog_of_id: status.id) end it 'returns http success' do - get :index, params: { status_id: status.id, limit: 1 } + get :index, params: { status_id: status.id, limit: 2 } expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end + + it 'returns accounts who reblogged the status' do + get :index, params: { status_id: status.id, limit: 2 } + expect(body_as_json.size).to eq 2 + expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s]) + end + + it 'does not return blocked users' do + user.account.block!(bob) + get :index, params: { status_id: status.id, limit: 2 } + expect(body_as_json.size).to eq 1 + expect(body_as_json[0][:id]).to eq alice.id.to_s + end end end diff --git a/spec/controllers/concerns/obfuscate_filename_spec.rb b/spec/controllers/concerns/obfuscate_filename_spec.rb deleted file mode 100644 index e06d53c03..000000000 --- a/spec/controllers/concerns/obfuscate_filename_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe ApplicationController, type: :controller do - controller do - include ObfuscateFilename - - obfuscate_filename :file - - def file - render plain: params[:file]&.original_filename - end - end - - before do - routes.draw { get 'file' => 'anonymous#file' } - end - - it 'obfusticates filename if the given parameter is specified' do - file = fixture_file_upload('files/imports.txt', 'text/plain') - post 'file', params: { file: file } - expect(response.body).to end_with '.txt' - expect(response.body).not_to include 'imports' - end - - it 'does nothing if the given parameter is not specified' do - post 'file' - end -end diff --git a/spec/controllers/follower_accounts_controller_spec.rb b/spec/controllers/follower_accounts_controller_spec.rb index 83032ab64..34a0cf3f4 100644 --- a/spec/controllers/follower_accounts_controller_spec.rb +++ b/spec/controllers/follower_accounts_controller_spec.rb @@ -22,6 +22,18 @@ describe FollowerAccountsController do expect(assigned[0]).to eq follow1 expect(assigned[1]).to eq follow0 end + + it 'does not assign blocked users' do + user = Fabricate(:user) + user.account.block!(follower0) + sign_in(user) + + expect(response).to have_http_status(200) + + assigned = assigns(:follows).to_a + expect(assigned.size).to eq 1 + expect(assigned[0]).to eq follow1 + end end context 'when format is json' do diff --git a/spec/controllers/following_accounts_controller_spec.rb b/spec/controllers/following_accounts_controller_spec.rb index d5e4ee587..e9a1f597d 100644 --- a/spec/controllers/following_accounts_controller_spec.rb +++ b/spec/controllers/following_accounts_controller_spec.rb @@ -22,6 +22,18 @@ describe FollowingAccountsController do expect(assigned[0]).to eq follow1 expect(assigned[1]).to eq follow0 end + + it 'does not assign blocked users' do + user = Fabricate(:user) + user.account.block!(followee0) + sign_in(user) + + expect(response).to have_http_status(200) + + assigned = assigns(:follows).to_a + expect(assigned.size).to eq 1 + expect(assigned[0]).to eq follow1 + end end context 'when format is json' do diff --git a/spec/features/log_in_spec.rb b/spec/features/log_in_spec.rb index b874c255b..de1a6de03 100644 --- a/spec/features/log_in_spec.rb +++ b/spec/features/log_in_spec.rb @@ -1,47 +1,51 @@ -require "rails_helper" +# frozen_string_literal: true + +require 'rails_helper' + +feature 'Log in' do + include ProfileStories -feature "Log in" do given(:email) { "test@example.com" } given(:password) { "password" } given(:confirmed_at) { Time.zone.now } background do - Fabricate(:user, email: email, password: password, confirmed_at: confirmed_at) + as_a_registered_user visit new_user_session_path end subject { page } - scenario "A valid email and password user is able to log in" do - fill_in "user_email", with: email - fill_in "user_password", with: password + scenario 'A valid email and password user is able to log in' do + fill_in 'user_email', with: email + fill_in 'user_password', with: password click_on I18n.t('auth.login') - is_expected.to have_css("div.app-holder") + is_expected.to have_css('div.app-holder') end - scenario "A invalid email and password user is not able to log in" do - fill_in "user_email", with: "invalid_email" - fill_in "user_password", with: "invalid_password" + scenario 'A invalid email and password user is not able to log in' do + fill_in 'user_email', with: 'invalid_email' + fill_in 'user_password', with: 'invalid_password' click_on I18n.t('auth.login') - is_expected.to have_css(".flash-message", text: failure_message("invalid")) + is_expected.to have_css('.flash-message', text: failure_message('invalid')) end context do given(:confirmed_at) { nil } - scenario "A unconfirmed user is able to log in" do - fill_in "user_email", with: email - fill_in "user_password", with: password + scenario 'A unconfirmed user is able to log in' do + fill_in 'user_email', with: email + fill_in 'user_password', with: password click_on I18n.t('auth.login') - is_expected.to have_css("div.admin-wrapper") + is_expected.to have_css('div.admin-wrapper') end end def failure_message(message) keys = User.authentication_keys.map { |key| User.human_attribute_name(key) } - I18n.t("devise.failure.#{message}", authentication_keys: keys.join("support.array.words_connector")) + I18n.t("devise.failure.#{message}", authentication_keys: keys.join('support.array.words_connector')) end end diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb new file mode 100644 index 000000000..3202167ca --- /dev/null +++ b/spec/features/profile_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'rails_helper' + +feature 'Profile' do + include ProfileStories + + given(:local_domain) { ENV['LOCAL_DOMAIN'] } + + background do + as_a_logged_in_user + with_alice_as_local_user + end + + subject { page } + + scenario 'I can view Annes public account' do + visit account_path('alice') + + is_expected.to have_title("alice (@alice@#{local_domain})") + + within('.public-account-header h1') do + is_expected.to have_content("alice @alice@#{local_domain}") + end + + bio_elem = first('.public-account-bio') + expect(bio_elem).to have_content(alice_bio) + # The bio has hashtags made clickable + expect(bio_elem).to have_link('cryptology') + expect(bio_elem).to have_link('science') + # Nicknames are make clickable + expect(bio_elem).to have_link('@alice') + expect(bio_elem).to have_link('@bob') + # Nicknames not on server are not clickable + expect(bio_elem).not_to have_link('@pepe') + end + + scenario 'I can change my account' do + visit settings_profile_path + fill_in 'Display name', with: 'Bob' + fill_in 'Bio', with: 'Bob is silent' + click_on 'Save changes' + is_expected.to have_content 'Changes successfully saved!' + + # View my own public profile and see the changes + click_link "Bob @bob@#{local_domain}" + + within('.public-account-header h1') do + is_expected.to have_content("Bob @bob@#{local_domain}") + end + expect(first('.public-account-bio')).to have_content('Bob is silent') + end +end diff --git a/spec/middleware/handle_bad_encoding_middleware_spec.rb b/spec/middleware/handle_bad_encoding_middleware_spec.rb new file mode 100644 index 000000000..8c0d24f18 --- /dev/null +++ b/spec/middleware/handle_bad_encoding_middleware_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +RSpec.describe HandleBadEncodingMiddleware do + let(:app) { double() } + let(:middleware) { HandleBadEncodingMiddleware.new(app) } + + it "request with query string is unchanged" do + expect(app).to receive(:call).with("PATH" => "/some/path", "QUERY_STRING" => "name=fred") + middleware.call("PATH" => "/some/path", "QUERY_STRING" => "name=fred") + end + + it "request with no query string is unchanged" do + expect(app).to receive(:call).with("PATH" => "/some/path") + middleware.call("PATH" => "/some/path") + end + + it "request with invalid encoding in query string drops query string" do + expect(app).to receive(:call).with("QUERY_STRING" => "", "PATH" => "/some/path") + middleware.call("QUERY_STRING" => "q=%2Fsearch%2Fall%Forder%3Ddescending%26page%3D5%26sort%3Dcreated_at", "PATH" => "/some/path") + end +end diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index b2f6234cb..3cca9b343 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -823,4 +823,5 @@ RSpec.describe Account, type: :model do end include_examples 'AccountAvatar', :account + include_examples 'AccountHeader', :account end diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb index 7ddfba7ed..a275621a1 100644 --- a/spec/models/media_attachment_spec.rb +++ b/spec/models/media_attachment_spec.rb @@ -133,6 +133,24 @@ RSpec.describe MediaAttachment, type: :model do expect(media.file.meta["small"]["height"]).to eq 327 expect(media.file.meta["small"]["aspect"]).to eq 490.0 / 327 end + + it 'gives the file a random name' do + expect(media.file_file_name).to_not eq 'attachment.jpg' + end + end + + describe 'base64-encoded jpeg' do + let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" } + let(:media) { MediaAttachment.create(account: Fabricate(:account), file: base64_attachment) } + + it 'saves media attachment' do + expect(media.persisted?).to be true + expect(media.file).to_not be_nil + end + + it 'gives the file a file name' do + expect(media.file_file_name).to_not be_blank + end end describe 'descriptions for remote attachments' do diff --git a/spec/services/process_mentions_service_spec.rb b/spec/services/process_mentions_service_spec.rb index b1abd79b0..c30de8eeb 100644 --- a/spec/services/process_mentions_service_spec.rb +++ b/spec/services/process_mentions_service_spec.rb @@ -5,11 +5,11 @@ RSpec.describe ProcessMentionsService, type: :service do let(:visibility) { :public } let(:status) { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}", visibility: visibility) } + subject { ProcessMentionsService.new } + context 'OStatus with public toot' do let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') } - subject { ProcessMentionsService.new } - before do stub_request(:post, remote_user.salmon_url) subject.call(status) @@ -24,8 +24,6 @@ RSpec.describe ProcessMentionsService, type: :service do let(:visibility) { :private } let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') } - subject { ProcessMentionsService.new } - before do stub_request(:post, remote_user.salmon_url) subject.call(status) @@ -41,29 +39,45 @@ RSpec.describe ProcessMentionsService, type: :service do end context 'ActivityPub' do - let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } + context do + let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } - subject { ProcessMentionsService.new } + before do + stub_request(:post, remote_user.inbox_url) + subject.call(status) + end - before do - stub_request(:post, remote_user.inbox_url) - subject.call(status) - end + it 'creates a mention' do + expect(remote_user.mentions.where(status: status).count).to eq 1 + end - it 'creates a mention' do - expect(remote_user.mentions.where(status: status).count).to eq 1 + it 'sends activity to the inbox' do + expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once + end end - it 'sends activity to the inbox' do - expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once + context 'with an IDN domain' do + let(:remote_user) { Fabricate(:account, username: 'sneak', protocol: :activitypub, domain: 'xn--hresiar-mxa.ch', inbox_url: 'http://example.com/inbox') } + let(:status) { Fabricate(:status, account: account, text: "Hello @sneak@hæresiar.ch") } + + before do + stub_request(:post, remote_user.inbox_url) + subject.call(status) + end + + it 'creates a mention' do + expect(remote_user.mentions.where(status: status).count).to eq 1 + end + + it 'sends activity to the inbox' do + expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once + end end end context 'Temporarily-unreachable ActivityPub user' do let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox', last_webfingered_at: nil) } - subject { ProcessMentionsService.new } - before do stub_request(:get, "https://example.com/.well-known/host-meta").to_return(status: 404) stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:remote_user@example.com").to_return(status: 500) diff --git a/spec/support/examples/models/concerns/account_avatar.rb b/spec/support/examples/models/concerns/account_avatar.rb index f2a8a2459..2180f5273 100644 --- a/spec/support/examples/models/concerns/account_avatar.rb +++ b/spec/support/examples/models/concerns/account_avatar.rb @@ -16,4 +16,24 @@ shared_examples 'AccountAvatar' do |fabricator| end end end + + describe 'base64-encoded files' do + let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" } + let(:account) { Fabricate(fabricator, avatar: base64_attachment) } + + it 'saves avatar' do + expect(account.persisted?).to be true + expect(account.avatar).to_not be_nil + end + + it 'gives the avatar a file name' do + expect(account.avatar_file_name).to_not be_blank + end + + it 'saves a new avatar under a different file name' do + previous_file_name = account.avatar_file_name + account.update(avatar: base64_attachment) + expect(account.avatar_file_name).to_not eq previous_file_name + end + end end diff --git a/spec/support/examples/models/concerns/account_header.rb b/spec/support/examples/models/concerns/account_header.rb new file mode 100644 index 000000000..77ee0e629 --- /dev/null +++ b/spec/support/examples/models/concerns/account_header.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +shared_examples 'AccountHeader' do |fabricator| + describe 'base64-encoded files' do + let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" } + let(:account) { Fabricate(fabricator, header: base64_attachment) } + + it 'saves header' do + expect(account.persisted?).to be true + expect(account.header).to_not be_nil + end + + it 'gives the header a file name' do + expect(account.header_file_name).to_not be_blank + end + + it 'saves a new header under a different file name' do + previous_file_name = account.header_file_name + account.update(header: base64_attachment) + expect(account.header_file_name).to_not eq previous_file_name + end + end +end diff --git a/spec/support/stories/profile_stories.rb b/spec/support/stories/profile_stories.rb new file mode 100644 index 000000000..75b413330 --- /dev/null +++ b/spec/support/stories/profile_stories.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module ProfileStories + attr_reader :bob, :alice, :alice_bio + + def as_a_registered_user + @bob = Fabricate( + :user, + email: email, password: password, confirmed_at: confirmed_at, + account: Fabricate(:account, username: 'bob') + ) + end + + def as_a_logged_in_user + as_a_registered_user + visit new_user_session_path + fill_in 'user_email', with: email + fill_in 'user_password', with: password + click_on I18n.t('auth.login') + end + + def with_alice_as_local_user + @alice_bio = '@alice and @bob are fictional characters commonly used as'\ + 'placeholder names in #cryptology, as well as #science and'\ + 'engineering 📖 literature. Not affilated with @pepe.' + + @alice = Fabricate( + :user, + email: 'alice@example.com', password: password, confirmed_at: confirmed_at, + account: Fabricate(:account, username: 'alice', note: @alice_bio) + ) + end + + def confirmed_at + @confirmed_at ||= Time.zone.now + end + + def email + @email ||= 'test@example.com' + end + + def password + @password ||= 'password' + end +end diff --git a/spec/workers/refollow_worker_spec.rb b/spec/workers/refollow_worker_spec.rb new file mode 100644 index 000000000..29771aa59 --- /dev/null +++ b/spec/workers/refollow_worker_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe RefollowWorker do + subject { described_class.new } + let(:account) { Fabricate(:account, domain: 'example.org', protocol: :activitypub) } + let(:alice) { Fabricate(:account, domain: nil, username: 'alice') } + let(:bob) { Fabricate(:account, domain: nil, username: 'bob') } + + describe 'perform' do + let(:service) { double } + + before do + allow(FollowService).to receive(:new).and_return(service) + allow(service).to receive(:call) + + alice.follow!(account, reblogs: true) + bob.follow!(account, reblogs: false) + end + + it 'calls FollowService for local followers' do + result = subject.perform(account.id) + + expect(result).to be_nil + expect(service).to have_received(:call).with(alice, account, reblogs: true) + expect(service).to have_received(:call).with(bob, account, reblogs: false) + end + end +end diff --git a/yarn.lock b/yarn.lock index f7834e469..258567ddb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,15 +9,15 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/core@^7.1.0", "@babel/core@^7.4.5", "@babel/core@^7.7.5": - version "7.7.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.5.tgz#ae1323cd035b5160293307f50647e83f8ba62f7e" - integrity sha512-M42+ScN4+1S9iB6f+TL7QBpoQETxbclx+KNoKJABghnKYE+fMzSGqst0BZJc8CpI625bwPwYgUyRvxZ+0mZzpw== +"@babel/core@^7.1.0", "@babel/core@^7.4.5", "@babel/core@^7.7.7": + version "7.7.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.7.tgz#ee155d2e12300bcc0cff6a8ad46f2af5063803e9" + integrity sha512-jlSjuj/7z138NLZALxVgrx13AOtqip42ATZP7+kYl53GvDV6+4dCek1mVUo8z8c8Xnw/mx2q3d9HWh3griuesQ== dependencies: "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.7.4" + "@babel/generator" "^7.7.7" "@babel/helpers" "^7.7.4" - "@babel/parser" "^7.7.5" + "@babel/parser" "^7.7.7" "@babel/template" "^7.7.4" "@babel/traverse" "^7.7.4" "@babel/types" "^7.7.4" @@ -29,10 +29,10 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.0.0", "@babel/generator@^7.7.4": - version "7.7.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.7.4.tgz#db651e2840ca9aa66f327dcec1dc5f5fa9611369" - integrity sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg== +"@babel/generator@^7.0.0", "@babel/generator@^7.7.4", "@babel/generator@^7.7.7": + version "7.7.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.7.7.tgz#859ac733c44c74148e1a72980a64ec84b85f4f45" + integrity sha512-/AOIBpHh/JU1l0ZFS4kiRCBnLi6OTHzh0RPk3h9isBxkkqELtQNFi1Vr/tiG9p1yfoUdKVwISuXWQR+hwwM4VQ== dependencies: "@babel/types" "^7.7.4" jsesc "^2.5.1" @@ -252,10 +252,10 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.7.4", "@babel/parser@^7.7.5": - version "7.7.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.5.tgz#cbf45321619ac12d83363fcf9c94bb67fa646d71" - integrity sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.7.4", "@babel/parser@^7.7.7": + version "7.7.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.7.tgz#1b886595419cf92d811316d5b715a53ff38b4937" + integrity sha512-WtTZMZAZLbeymhkd/sEaPD8IQyGAhmuTuvTzLiCFM7iXiVdY0gc0IaI+cW0fh1BnSMbJSzXX6/fHllgHKwHhXw== "@babel/plugin-proposal-async-generator-functions@^7.7.4": version "7.7.4" @@ -795,10 +795,10 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" -"@clusterws/cws@^0.16.0": - version "0.16.0" - resolved "https://registry.yarnpkg.com/@clusterws/cws/-/cws-0.16.0.tgz#f6116cbf3a8b7ad0657916616ce5f8248746b797" - integrity sha512-YeGpAPIdkBsOnAkmFKVMWEjCKDH900U2if0B+nc1imfv+64AIb2JX2xiTA6BLDLppEgWV5c6bpWESjbHCNblHw== +"@clusterws/cws@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@clusterws/cws/-/cws-0.16.1.tgz#443c47909601e87b881ffb2c4c83cbf27a228487" + integrity sha512-OamMsXwVppfmwX14Ed1msJJN0KNi+VBQh5AkAqUvIGTcJyHcjpsKjU15wS8QumvEewxx9gvGp7aSDop0/R5Gqg== "@cnakazawa/watch@^1.0.3": version "1.0.3" @@ -3247,7 +3247,7 @@ default-gateway@^4.2.0: execa "^1.0.0" ip-regex "^2.1.0" -define-properties@^1.1.1, define-properties@^1.1.2, define-properties@^1.1.3: +define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -3942,10 +3942,10 @@ eslint@^2.7.0: text-table "~0.2.0" user-home "^2.0.0" -eslint@^6.7.2: - version "6.7.2" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.7.2.tgz#c17707ca4ad7b2d8af986a33feba71e18a9fecd1" - integrity sha512-qMlSWJaCSxDFr8fBPvJM9kJwbazrhNcBU3+DszDW1OlEwKBBRWsJc7NJFelvwQpanHCR14cOLD41x8Eqvo3Nng== +eslint@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" + integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.10.0" @@ -4031,12 +4031,7 @@ estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -esutils@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" - integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= - -esutils@^2.0.2: +esutils@^2.0.0, esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== @@ -4385,10 +4380,10 @@ find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: make-dir "^2.0.0" pkg-dir "^3.0.0" -find-cache-dir@^3.0.0, find-cache-dir@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.1.0.tgz#9935894999debef4cf9f677fdf646d002c4cdecb" - integrity sha512-zw+EFiNBNPgI2NTrKkDd1xd7q0cs6wr/iWnr/oUkI0yF9K9GqQ+riIt4aiyFaaqpaWbxPrJXHI+QvmNUQbX+0Q== +find-cache-dir@^3.0.0, find-cache-dir@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.2.0.tgz#e7fe44c1abc1299f516146e563108fd1006c1874" + integrity sha512-1JKclkYYsf1q9WIJKLZa9S9muC+08RIjzAlLrK4QcYLJMS6mk9yombQ9qf+zJ7H9LS800k0s44L4sDq9VYzqyg== dependencies: commondir "^1.0.1" make-dir "^3.0.0" @@ -5620,12 +5615,12 @@ is-my-json-valid@^2.10.0: jsonpointer "^4.0.0" xtend "^4.0.0" -is-nan@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.2.1.tgz#9faf65b6fb6db24b7f5c0628475ea71f988401e2" - integrity sha1-n69ltvttskt/XAYoR16nH5iEAeI= +is-nan@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.0.tgz#85d1f5482f7051c2019f5673ccebdb06f3b0db03" + integrity sha512-z7bbREymOqt2CCaZVly8aC4ML3Xhfi0ekuOnjO2L8vKdl+CttdVoGZQhd4adMFAsxQ5VeRVwORs4tU8RH+HFtQ== dependencies: - define-properties "^1.1.1" + define-properties "^1.1.3" is-number-object@^1.0.4: version "1.0.4" @@ -8785,15 +8780,15 @@ react-swipeable-views@^0.13.3: react-swipeable-views-utils "^0.13.3" warning "^4.0.1" -react-test-renderer@^16.0.0-0, react-test-renderer@^16.11.0: - version "16.11.0" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.11.0.tgz#72574566496462c808ac449b0287a4c0a1a7d8f8" - integrity sha512-nh9gDl8R4ut+ZNNb2EeKO5VMvTKxwzurbSMuGBoKtjpjbg8JK/u3eVPVNi1h1Ue+eYK9oSzJjb+K3lzLxyA4ag== +react-test-renderer@^16.0.0-0, react-test-renderer@^16.12.0: + version "16.12.0" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.12.0.tgz#11417ffda579306d4e841a794d32140f3da1b43f" + integrity sha512-Vj/teSqt2oayaWxkbhQ6gKis+t5JrknXfPVo+aIJ8QwYAqMPH77uptOdrlphyxl8eQI/rtkOYg86i/UWkpFu0w== dependencies: object-assign "^4.1.1" prop-types "^15.6.2" react-is "^16.8.6" - scheduler "^0.17.0" + scheduler "^0.18.0" react-textarea-autosize@^7.1.2: version "7.1.2" @@ -8946,10 +8941,10 @@ redux-thunk@^2.2.0: resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== -redux@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796" - integrity sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q== +redux@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== dependencies: loose-envify "^1.4.0" symbol-observable "^1.2.0" @@ -9395,14 +9390,6 @@ sax@^1.2.4, sax@~1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -scheduler@^0.17.0: - version "0.17.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.17.0.tgz#7c9c673e4ec781fac853927916d1c426b6f3ddfe" - integrity sha512-7rro8Io3tnCPuY4la/NuI5F2yfESpnfZyT6TtkXnSWVkcu0BCDJ+8gk5ozUaFaxpIyNuWAPXrH0yFcSi28fnDA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - scheduler@^0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4" @@ -9487,12 +9474,7 @@ send@0.17.1: range-parser "~1.2.1" statuses "~1.5.0" -serialize-javascript@^1.7.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb" - integrity sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A== - -serialize-javascript@^2.1.1, serialize-javascript@^2.1.2: +serialize-javascript@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== @@ -10164,39 +10146,39 @@ tcomb@^2.5.0: resolved "https://registry.yarnpkg.com/tcomb/-/tcomb-2.7.0.tgz#10d62958041669a5d53567b9a4ee8cde22b1c2b0" integrity sha1-ENYpWAQWaaXVNWe5pO6M3iKxwrA= -terser-webpack-plugin@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4" - integrity sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg== +terser-webpack-plugin@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c" + integrity sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA== dependencies: cacache "^12.0.2" find-cache-dir "^2.1.0" is-wsl "^1.1.0" schema-utils "^1.0.0" - serialize-javascript "^1.7.0" + serialize-javascript "^2.1.2" source-map "^0.6.1" terser "^4.1.2" webpack-sources "^1.4.0" worker-farm "^1.7.0" -terser-webpack-plugin@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.2.2.tgz#2a6e00237125564a455ad69b22e08ee59420473a" - integrity sha512-/CHMNswPMAwuD2kd++qys8UmBRmsshPSzHw4BlDwurPtK9YjeK93OV89YWkJulHk972cs07K/7Z92V6PNjWF8A== +terser-webpack-plugin@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.3.1.tgz#6a63c27debc15b25ffd2588562ee2eeabdcab923" + integrity sha512-dNxivOXmDgZqrGxOttBH6B4xaxT4zNC+Xd+2K8jwGDMK5q2CZI+KZMA1AAnSRT+BTRvuzKsDx+fpxzPAmAMVcA== dependencies: cacache "^13.0.1" - find-cache-dir "^3.1.0" + find-cache-dir "^3.2.0" jest-worker "^24.9.0" schema-utils "^2.6.1" - serialize-javascript "^2.1.1" + serialize-javascript "^2.1.2" source-map "^0.6.1" - terser "^4.4.2" + terser "^4.4.3" webpack-sources "^1.4.3" -terser@^4.1.2, terser@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.4.2.tgz#448fffad0245f4c8a277ce89788b458bfd7706e8" - integrity sha512-Uufrsvhj9O1ikwgITGsZ5EZS6qPokUOkCegS7fYOdGTv+OA90vndUbU6PEjr5ePqHfNUbGyMO7xyIZv2MhsALQ== +terser@^4.1.2, terser@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.4.3.tgz#401abc52b88869cf904412503b1eb7da093ae2f0" + integrity sha512-0ikKraVtRDKGzHrzkCv5rUNDzqlhmhowOBqC0XqUHFpW+vJ45+20/IFBcebwKfiS2Z9fJin6Eo+F1zLZsxi8RA== dependencies: commander "^2.20.0" source-map "~0.6.1" @@ -10858,10 +10840,10 @@ webpack-sources@^1.0.0, webpack-sources@^1.0.1, webpack-sources@^1.1.0, webpack- source-list-map "^2.0.0" source-map "~0.6.1" -webpack@^4.41.2: - version "4.41.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.2.tgz#c34ec76daa3a8468c9b61a50336d8e3303dce74e" - integrity sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A== +webpack@^4.41.5: + version "4.41.5" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.5.tgz#3210f1886bce5310e62bb97204d18c263341b77c" + integrity sha512-wp0Co4vpyumnp3KlkmpM5LWuzvZYayDwM2n17EHFr4qxBBbRokC7DJawPJC7TfSFZ9HZ6GsdH40EBj4UV0nmpw== dependencies: "@webassemblyjs/ast" "1.8.5" "@webassemblyjs/helper-module-context" "1.8.5" @@ -10883,7 +10865,7 @@ webpack@^4.41.2: node-libs-browser "^2.2.1" schema-utils "^1.0.0" tapable "^1.1.3" - terser-webpack-plugin "^1.4.1" + terser-webpack-plugin "^1.4.3" watchpack "^1.6.0" webpack-sources "^1.4.1" |