From 6d2301988fdc0118c5583f48ba6da4a3b8247ba4 Mon Sep 17 00:00:00 2001
From: Rakib Hasan <rmhasan@gmail.com>
Date: Wed, 1 Feb 2017 21:07:38 -0500
Subject: [PATCH 1/5] Fix for issue #462 Modified uploadCompose action to send
 media ids of attached media when sending a request. Modified create method in
 MediaController to check if when posting a video, there are no other media
 attached to the status by looking at the media ids sent from the
 uploadCompose action.

---
 app/assets/javascripts/components/actions/compose.jsx | 5 ++++-
 app/controllers/api/v1/media_controller.rb            | 4 ++++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/app/assets/javascripts/components/actions/compose.jsx b/app/assets/javascripts/components/actions/compose.jsx
index 03aae885e..84fbc7fc5 100644
--- a/app/assets/javascripts/components/actions/compose.jsx
+++ b/app/assets/javascripts/components/actions/compose.jsx
@@ -119,7 +119,10 @@ export function uploadCompose(files) {
 
     let data = new FormData();
     data.append('file', files[0]);
-
+    data.append('media_ids', getState().getIn(
+      ['compose', 'media_attachments']
+    ).map(item => item.get('id')));
+    
     api(getState).post('/api/v1/media', data, {
       onUploadProgress: function (e) {
         dispatch(uploadComposeProgress(e.loaded, e.total));
diff --git a/app/controllers/api/v1/media_controller.rb b/app/controllers/api/v1/media_controller.rb
index f8139ade7..582d04daf 100644
--- a/app/controllers/api/v1/media_controller.rb
+++ b/app/controllers/api/v1/media_controller.rb
@@ -11,6 +11,10 @@ class Api::V1::MediaController < ApiController
 
   def create
     @media = MediaAttachment.create!(account: current_user.account, file: params[:file])
+    if @media.video? and params[:media_ids] != "List []"
+      @media.destroy
+      render json: {error: 'Cannot attach a video to a toot that already contains images'}, status: 422
+    end
   rescue Paperclip::Errors::NotIdentifiedByImageMagickError
     render json: { error: 'File type of uploaded media could not be verified' }, status: 422
   rescue Paperclip::Error

From 6f9ecd899e9e7cb335940465c23dd53acc37269c Mon Sep 17 00:00:00 2001
From: Rakib Hasan <rmhasan@gmail.com>
Date: Thu, 2 Feb 2017 23:10:17 -0500
Subject: [PATCH 2/5] revisted fix for #462 Moved validation to
 services/post_status_service.rb

---
 .../javascripts/components/actions/compose.jsx   |  5 +----
 app/controllers/api/v1/media_controller.rb       |  4 ----
 app/controllers/api/v1/statuses_controller.rb    | 16 ++++++++++------
 app/services/post_status_service.rb              |  8 +++++++-
 4 files changed, 18 insertions(+), 15 deletions(-)

diff --git a/app/assets/javascripts/components/actions/compose.jsx b/app/assets/javascripts/components/actions/compose.jsx
index 84fbc7fc5..03aae885e 100644
--- a/app/assets/javascripts/components/actions/compose.jsx
+++ b/app/assets/javascripts/components/actions/compose.jsx
@@ -119,10 +119,7 @@ export function uploadCompose(files) {
 
     let data = new FormData();
     data.append('file', files[0]);
-    data.append('media_ids', getState().getIn(
-      ['compose', 'media_attachments']
-    ).map(item => item.get('id')));
-    
+
     api(getState).post('/api/v1/media', data, {
       onUploadProgress: function (e) {
         dispatch(uploadComposeProgress(e.loaded, e.total));
diff --git a/app/controllers/api/v1/media_controller.rb b/app/controllers/api/v1/media_controller.rb
index 582d04daf..f8139ade7 100644
--- a/app/controllers/api/v1/media_controller.rb
+++ b/app/controllers/api/v1/media_controller.rb
@@ -11,10 +11,6 @@ class Api::V1::MediaController < ApiController
 
   def create
     @media = MediaAttachment.create!(account: current_user.account, file: params[:file])
-    if @media.video? and params[:media_ids] != "List []"
-      @media.destroy
-      render json: {error: 'Cannot attach a video to a toot that already contains images'}, status: 422
-    end
   rescue Paperclip::Errors::NotIdentifiedByImageMagickError
     render json: { error: 'File type of uploaded media could not be verified' }, status: 422
   rescue Paperclip::Error
diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index 69cbdce5d..036383d1e 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -62,12 +62,16 @@ class Api::V1::StatusesController < ApiController
   end
 
   def create
-    @status = PostStatusService.new.call(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]), media_ids: params[:media_ids],
-                                                                                                                                                             sensitive: params[:sensitive],
-                                                                                                                                                             spoiler_text: params[:spoiler_text],
-                                                                                                                                                             visibility: params[:visibility],
-                                                                                                                                                             application: doorkeeper_token.application)
-
+    begin
+      @status = PostStatusService.new.call(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]), media_ids: params[:media_ids],
+                                                                                                                                                               sensitive: params[:sensitive],
+                                                                                                                                                               spoiler_text: params[:spoiler_text],
+                                                                                                                                                               visibility: params[:visibility],
+                                                                                                                                                               application: doorkeeper_token.application)
+    rescue Mastodon::NotPermitted => e
+       render json: {error: e.message}, status: 422
+       return
+    end
     render action: :show
   end
 
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index 979941c84..d70103547 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -35,8 +35,14 @@ class PostStatusService < BaseService
 
   def attach_media(status, media_ids)
     return if media_ids.nil? || !media_ids.is_a?(Enumerable)
-
     media = MediaAttachment.where(status_id: nil).where(id: media_ids.take(4).map(&:to_i))
+    if media.length > 1
+      media.each do |m|
+        if m.video?
+          raise Mastodon::NotPermitted, 'Cannot attach a video to a toot that already contains images'
+        end
+      end
+    end
     media.update(status_id: status.id)
   end
 

From 87a6bed9e947aaad62466f072d1a27cd0f782a7d Mon Sep 17 00:00:00 2001
From: Rakib Hasan <rmhasan@gmail.com>
Date: Sat, 4 Feb 2017 22:03:24 -0500
Subject: [PATCH 3/5] previous commit was creating the status regardless of mix
 of video and images in status, just wasn't rendering the show action. I moved
 the validation before the status creation

---
 app/services/post_status_service.rb | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index d70103547..7ead80430 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -13,6 +13,7 @@ class PostStatusService < BaseService
   # @option [Doorkeeper::Application] :application
   # @return [Status]
   def call(account, text, in_reply_to = nil, options = {})
+    media = validate_media options[:media_ids]
     status = account.statuses.create!(text: text,
                                       thread: in_reply_to,
                                       sensitive: options[:sensitive],
@@ -20,7 +21,7 @@ class PostStatusService < BaseService
                                       visibility: options[:visibility],
                                       application: options[:application])
 
-    attach_media(status, options[:media_ids])
+    attach_media(status, media)
     process_mentions_service.call(status)
     process_hashtags_service.call(status)
 
@@ -33,7 +34,7 @@ class PostStatusService < BaseService
 
   private
 
-  def attach_media(status, media_ids)
+  def validate_media(media_ids)
     return if media_ids.nil? || !media_ids.is_a?(Enumerable)
     media = MediaAttachment.where(status_id: nil).where(id: media_ids.take(4).map(&:to_i))
     if media.length > 1
@@ -43,6 +44,11 @@ class PostStatusService < BaseService
         end
       end
     end
+    return media
+  end
+
+  def attach_media(status, media)
+    return if media.nil?
     media.update(status_id: status.id)
   end
 

From 9433d03705d3aa86a059d82ffc549c699092912d Mon Sep 17 00:00:00 2001
From: Rakib Hasan <rmhasan@gmail.com>
Date: Fri, 17 Feb 2017 02:58:16 +0000
Subject: [PATCH 4/5] Removed try clause from create action in status
 controller Using catch statement in api_controller.rb to catch NotPermitted
 Exception, and render error message

---
 app/controllers/api/v1/statuses_controller.rb | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index 036383d1e..2ffd4a018 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -62,16 +62,11 @@ class Api::V1::StatusesController < ApiController
   end
 
   def create
-    begin
       @status = PostStatusService.new.call(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]), media_ids: params[:media_ids],
                                                                                                                                                                sensitive: params[:sensitive],
                                                                                                                                                                spoiler_text: params[:spoiler_text],
                                                                                                                                                                visibility: params[:visibility],
                                                                                                                                                                application: doorkeeper_token.application)
-    rescue Mastodon::NotPermitted => e
-       render json: {error: e.message}, status: 422
-       return
-    end
     render action: :show
   end
 

From 5f511324b65f94d800dbbd3850214955d7d9eb73 Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Sun, 26 Feb 2017 23:23:06 +0100
Subject: [PATCH 5/5] Add validation of media attachments, clean up
 mastodon-own exception classes

---
 app/controllers/api/v1/statuses_controller.rb | 10 +++++-----
 app/controllers/api_controller.rb             |  4 ++--
 .../authorize_follow_controller.rb            |  2 +-
 app/lib/exceptions.rb                         |  3 ++-
 app/services/favourite_service.rb             |  2 +-
 app/services/follow_service.rb                |  2 +-
 app/services/post_status_service.rb           | 19 +++++++++----------
 app/services/reblog_service.rb                |  2 +-
 8 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index 2ffd4a018..552f1b1b3 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -62,11 +62,11 @@ class Api::V1::StatusesController < ApiController
   end
 
   def create
-      @status = PostStatusService.new.call(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]), media_ids: params[:media_ids],
-                                                                                                                                                               sensitive: params[:sensitive],
-                                                                                                                                                               spoiler_text: params[:spoiler_text],
-                                                                                                                                                               visibility: params[:visibility],
-                                                                                                                                                               application: doorkeeper_token.application)
+    @status = PostStatusService.new.call(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]), media_ids: params[:media_ids],
+                                                                                                                                                             sensitive: params[:sensitive],
+                                                                                                                                                             spoiler_text: params[:spoiler_text],
+                                                                                                                                                             visibility: params[:visibility],
+                                                                                                                                                             application: doorkeeper_token.application)
     render action: :show
   end
 
diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb
index 5d2bd9a22..c2002cb79 100644
--- a/app/controllers/api_controller.rb
+++ b/app/controllers/api_controller.rb
@@ -10,7 +10,7 @@ class ApiController < ApplicationController
 
   before_action :set_rate_limit_headers
 
-  rescue_from ActiveRecord::RecordInvalid do |e|
+  rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
     render json: { error: e.to_s }, status: 422
   end
 
@@ -30,7 +30,7 @@ class ApiController < ApplicationController
     render json: { error: 'Remote SSL certificate could not be verified' }, status: 503
   end
 
-  rescue_from Mastodon::NotPermitted do
+  rescue_from Mastodon::NotPermittedError do
     render json: { error: 'This action is not allowed' }, status: 403
   end
 
diff --git a/app/controllers/authorize_follow_controller.rb b/app/controllers/authorize_follow_controller.rb
index e866b5599..c98a5f45f 100644
--- a/app/controllers/authorize_follow_controller.rb
+++ b/app/controllers/authorize_follow_controller.rb
@@ -25,7 +25,7 @@ class AuthorizeFollowController < ApplicationController
     else
       redirect_to web_url("accounts/#{@account.id}")
     end
-  rescue ActiveRecord::RecordNotFound, Mastodon::NotPermitted
+  rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
     render :error
   end
 
diff --git a/app/lib/exceptions.rb b/app/lib/exceptions.rb
index 359228c29..200da9fe1 100644
--- a/app/lib/exceptions.rb
+++ b/app/lib/exceptions.rb
@@ -2,5 +2,6 @@
 
 module Mastodon
   class Error < StandardError; end
-  class NotPermitted < Error; end
+  class NotPermittedError < Error; end
+  class ValidationError < Error; end
 end
diff --git a/app/services/favourite_service.rb b/app/services/favourite_service.rb
index 818898302..5cc96403c 100644
--- a/app/services/favourite_service.rb
+++ b/app/services/favourite_service.rb
@@ -6,7 +6,7 @@ class FavouriteService < BaseService
   # @param [Status] status
   # @return [Favourite]
   def call(account, status)
-    raise Mastodon::NotPermitted unless status.permitted?(account)
+    raise Mastodon::NotPermittedError unless status.permitted?(account)
 
     favourite = Favourite.create!(account: account, status: status)
 
diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb
index 915f95b4c..17b3b2542 100644
--- a/app/services/follow_service.rb
+++ b/app/services/follow_service.rb
@@ -10,7 +10,7 @@ class FollowService < BaseService
     target_account = FollowRemoteAccountService.new.call(uri)
 
     raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended?
-    raise Mastodon::NotPermitted       if target_account.blocking?(source_account) || source_account.blocking?(target_account)
+    raise Mastodon::NotPermittedError       if target_account.blocking?(source_account) || source_account.blocking?(target_account)
 
     if target_account.locked?
       request_follow(source_account, target_account)
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index 7ead80430..b8179f7dc 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -13,7 +13,7 @@ class PostStatusService < BaseService
   # @option [Doorkeeper::Application] :application
   # @return [Status]
   def call(account, text, in_reply_to = nil, options = {})
-    media = validate_media options[:media_ids]
+    media  = validate_media!(options[:media_ids])
     status = account.statuses.create!(text: text,
                                       thread: in_reply_to,
                                       sensitive: options[:sensitive],
@@ -34,17 +34,16 @@ class PostStatusService < BaseService
 
   private
 
-  def validate_media(media_ids)
+  def validate_media!(media_ids)
     return if media_ids.nil? || !media_ids.is_a?(Enumerable)
+
+    raise Mastodon::ValidationError, 'Cannot attach more than 4 files' if media_ids.size > 4
+
     media = MediaAttachment.where(status_id: nil).where(id: media_ids.take(4).map(&:to_i))
-    if media.length > 1
-      media.each do |m|
-        if m.video?
-          raise Mastodon::NotPermitted, 'Cannot attach a video to a toot that already contains images'
-        end
-      end
-    end
-    return media
+
+    raise Mastodon::ValidationError, 'Cannot attach a video to a toot that already contains images' if media.size > 1 && media.find(&:video?)
+
+    media
   end
 
   def attach_media(status, media)
diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb
index 7a52f041f..c14b2925a 100644
--- a/app/services/reblog_service.rb
+++ b/app/services/reblog_service.rb
@@ -10,7 +10,7 @@ class ReblogService < BaseService
   def call(account, reblogged_status)
     reblogged_status = reblogged_status.reblog if reblogged_status.reblog?
 
-    raise Mastodon::NotPermitted if reblogged_status.private_visibility? || !reblogged_status.permitted?(account)
+    raise Mastodon::NotPermittedError if reblogged_status.private_visibility? || !reblogged_status.permitted?(account)
 
     reblog = account.statuses.create!(reblog: reblogged_status, text: '')