You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

attachments_controller.rb 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. # frozen_string_literal: true
  2. # Redmine - project management software
  3. # Copyright (C) 2006-2021 Jean-Philippe Lang
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; either version 2
  8. # of the License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. class AttachmentsController < ApplicationController
  19. include ActionView::Helpers::NumberHelper
  20. before_action :find_attachment, :only => [:show, :download, :thumbnail, :update, :destroy]
  21. before_action :find_container, :only => [:edit_all, :update_all, :download_all]
  22. before_action :find_downloadable_attachments, :only => :download_all
  23. before_action :find_editable_attachments, :only => [:edit_all, :update_all]
  24. before_action :file_readable, :read_authorize, :only => [:show, :download, :thumbnail]
  25. before_action :update_authorize, :only => :update
  26. before_action :delete_authorize, :only => :destroy
  27. before_action :authorize_global, :only => :upload
  28. # Disable check for same origin requests for JS files, i.e. attachments with
  29. # MIME type text/javascript.
  30. skip_after_action :verify_same_origin_request, :only => :download
  31. accept_api_auth :show, :download, :thumbnail, :upload, :update, :destroy
  32. def show
  33. respond_to do |format|
  34. format.html do
  35. if @attachment.container.respond_to?(:attachments)
  36. @attachments = @attachment.container.attachments.to_a
  37. if index = @attachments.index(@attachment)
  38. @paginator = Redmine::Pagination::Paginator.new(
  39. @attachments.size, 1, index+1
  40. )
  41. end
  42. end
  43. if @attachment.is_diff?
  44. @diff = File.read(@attachment.diskfile, :mode => "rb")
  45. @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
  46. @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
  47. # Save diff type as user preference
  48. if User.current.logged? && @diff_type != User.current.pref[:diff_type]
  49. User.current.pref[:diff_type] = @diff_type
  50. User.current.preference.save
  51. end
  52. render :action => 'diff'
  53. elsif @attachment.is_text? && @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte
  54. @content = File.read(@attachment.diskfile, :mode => "rb")
  55. render :action => 'file'
  56. elsif @attachment.is_image?
  57. render :action => 'image'
  58. else
  59. render :action => 'other'
  60. end
  61. end
  62. format.api
  63. end
  64. end
  65. def download
  66. if @attachment.container.is_a?(Version) || @attachment.container.is_a?(Project)
  67. @attachment.increment_download
  68. end
  69. if stale?(:etag => @attachment.digest, :template => false)
  70. # images are sent inline
  71. send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
  72. :type => detect_content_type(@attachment),
  73. :disposition => disposition(@attachment)
  74. end
  75. end
  76. def thumbnail
  77. if @attachment.thumbnailable? && tbnail = @attachment.thumbnail(:size => params[:size])
  78. if stale?(:etag => tbnail, :template => false)
  79. send_file(
  80. tbnail,
  81. :filename => filename_for_content_disposition(@attachment.filename),
  82. :type => detect_content_type(@attachment, true),
  83. :disposition => 'inline')
  84. end
  85. else
  86. # No thumbnail for the attachment or thumbnail could not be created
  87. head 404
  88. end
  89. end
  90. def upload
  91. # Make sure that API users get used to set this content type
  92. # as it won't trigger Rails' automatic parsing of the request body for parameters
  93. unless request.content_type == 'application/octet-stream'
  94. head 406
  95. return
  96. end
  97. @attachment = Attachment.new(:file => request.raw_post)
  98. @attachment.author = User.current
  99. @attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16)
  100. @attachment.content_type = params[:content_type].presence
  101. saved = @attachment.save
  102. respond_to do |format|
  103. format.js
  104. format.api do
  105. if saved
  106. render :action => 'upload', :status => :created
  107. else
  108. render_validation_errors(@attachment)
  109. end
  110. end
  111. end
  112. end
  113. # Edit all the attachments of a container
  114. def edit_all
  115. end
  116. # Update all the attachments of a container
  117. def update_all
  118. if Attachment.update_attachments(@attachments, update_all_params)
  119. redirect_back_or_default home_path
  120. return
  121. end
  122. render :action => 'edit_all'
  123. end
  124. def download_all
  125. zip_data = Attachment.archive_attachments(@attachments)
  126. if zip_data
  127. file_name = "#{@container.class.to_s.downcase}-#{@container.id}-attachments.zip"
  128. send_data(
  129. zip_data,
  130. :type => Redmine::MimeType.of(file_name),
  131. :filename => file_name
  132. )
  133. else
  134. render_404
  135. end
  136. end
  137. def update
  138. @attachment.safe_attributes = params[:attachment]
  139. saved = @attachment.save
  140. respond_to do |format|
  141. format.api do
  142. if saved
  143. render_api_ok
  144. else
  145. render_validation_errors(@attachment)
  146. end
  147. end
  148. end
  149. end
  150. def destroy
  151. if @attachment.container.respond_to?(:init_journal)
  152. @attachment.container.init_journal(User.current)
  153. end
  154. if @attachment.container
  155. # Make sure association callbacks are called
  156. @attachment.container.attachments.delete(@attachment)
  157. else
  158. @attachment.destroy
  159. end
  160. respond_to do |format|
  161. format.html {redirect_to_referer_or project_path(@project)}
  162. format.js
  163. format.api {render_api_ok}
  164. end
  165. end
  166. # Returns the menu item that should be selected when viewing an attachment
  167. def current_menu_item
  168. container = @attachment.try(:container) || @container
  169. if container
  170. case container
  171. when WikiPage
  172. :wiki
  173. when Message
  174. :boards
  175. when Project, Version
  176. :files
  177. else
  178. container.class.name.pluralize.downcase.to_sym
  179. end
  180. end
  181. end
  182. private
  183. def find_attachment
  184. @attachment = Attachment.find(params[:id])
  185. # Show 404 if the filename in the url is wrong
  186. raise ActiveRecord::RecordNotFound if params[:filename] && params[:filename] != @attachment.filename
  187. @project = @attachment.project
  188. rescue ActiveRecord::RecordNotFound
  189. render_404
  190. end
  191. def find_editable_attachments
  192. @attachments = @container.attachments.select(&:editable?)
  193. render_404 if @attachments.empty?
  194. end
  195. def find_container
  196. klass =
  197. begin
  198. params[:object_type].to_s.singularize.classify.constantize
  199. rescue
  200. nil
  201. end
  202. unless klass && klass.reflect_on_association(:attachments)
  203. render_404
  204. return
  205. end
  206. @container = klass.find(params[:object_id])
  207. if @container.respond_to?(:visible?) && !@container.visible?
  208. render_403
  209. return
  210. end
  211. if @container.respond_to?(:project)
  212. @project = @container.project
  213. end
  214. rescue ActiveRecord::RecordNotFound
  215. render_404
  216. end
  217. def find_downloadable_attachments
  218. @attachments = @container.attachments.select(&:readable?)
  219. bulk_download_max_size = Setting.bulk_download_max_size.to_i.kilobytes
  220. if @attachments.sum(&:filesize) > bulk_download_max_size
  221. flash[:error] = l(:error_bulk_download_size_too_big,
  222. :max_size => number_to_human_size(bulk_download_max_size.to_i))
  223. redirect_to back_url
  224. return
  225. end
  226. end
  227. # Checks that the file exists and is readable
  228. def file_readable
  229. if @attachment.readable?
  230. true
  231. else
  232. logger.error "Cannot send attachment, #{@attachment.diskfile} does not exist or is unreadable."
  233. render_404
  234. end
  235. end
  236. def read_authorize
  237. @attachment.visible? ? true : deny_access
  238. end
  239. def update_authorize
  240. @attachment.editable? ? true : deny_access
  241. end
  242. def delete_authorize
  243. @attachment.deletable? ? true : deny_access
  244. end
  245. def detect_content_type(attachment, is_thumb = false)
  246. content_type = attachment.content_type
  247. if content_type.blank? || content_type == "application/octet-stream"
  248. content_type =
  249. Redmine::MimeType.of(attachment.filename).presence ||
  250. "application/octet-stream"
  251. end
  252. if is_thumb && content_type == "application/pdf"
  253. # PDF previews are stored in PNG format
  254. content_type = "image/png"
  255. end
  256. content_type
  257. end
  258. def disposition(attachment)
  259. if attachment.is_pdf?
  260. 'inline'
  261. else
  262. 'attachment'
  263. end
  264. end
  265. # Returns attachments param for #update_all
  266. def update_all_params
  267. params.permit(:attachments => [:filename, :description]).require(:attachments)
  268. end
  269. end