Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added document converter service; Fixed download_inline vulnerability

  • Loading branch information...
commit e2a0d6a05bb7383e5942a86282bd83cdfc9c4225 1 parent d2bd3dc
@nettsundere nettsundere authored
View
12 Rakefile
@@ -0,0 +1,12 @@
+require 'rake'
+require 'rake/testtask'
+
+desc "Default: run unit tests."
+task :default => :test
+
+desc "Test the redmine_tagging plugin."
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
View
8 app/controllers/attachments_controller.rb
@@ -1,8 +0,0 @@
-class AttachmentsController < ApplicationController
- unloadable
- def download_inline
- send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
- :type => detect_content_type(@attachment),
- :disposition => 'inline'
- end
-end
View
33 app/models/attachment_preview.rb
@@ -0,0 +1,33 @@
+class AttachmentPreview < ActiveRecord::Base
+ belongs_to :attachment
+
+ before_save :create_preview
+
+ after_destroy :delete_from_disk!
+
+ def filename
+ source = attachment.filename
+ RedmineLightbox::Services::DocumentConverter::preview_filename_for(source, file_type)
+ end
+
+ def content_type
+ Redmine::MimeType.of(filename)
+ end
+
+ def diskfile
+ source = attachment.diskfile
+ RedmineLightbox::Services::DocumentConverter::preview_filename_for(source, file_type)
+ end
+
+ private
+ def delete_from_disk!
+ if diskfile.present? && File.exist?(diskfile)
+ File.delete diskfile
+ end
+ end
+
+ def create_preview
+ preview_service = RedmineLightbox::Services::DocumentConverter.new(file_type)
+ preview_service.convert(attachment.diskfile)
+ end
+end
View
7 app/views/attachments/_links.html.erb
@@ -11,6 +11,11 @@
<% if (!Setting.plugin_redmine_lightbox['preview_image_attachments'] && attachment.image?) || (attachment.filename =~ /.(pdf|swf)$/i ) %>
<%= link_to image_tag('preview.png', :plugin => :redmine_lightbox, :class => "preview_button"), {:controller => 'attachments', :action => "#{($1 === 'swf' || attachment.image?) ? 'download_inline' : 'show'}", :id => attachment, :filename => attachment.filename }, :class => $1 || "image", :rel => 'attachments', :title => "#{attachment.filename}#{ ('-' + attachment.description) unless attachment.description.blank? }" %>
<% end -%>
+
+ <% if attachment.attachment_preview %>
+ <%= link_to image_tag('preview.png', :plugin => :redmine_lightbox, :class => "preview_button"), {:controller => 'attachments', :action => 'preview', :id => attachment, :filename => attachment.filename }, :class => "attachment_preview", :rel => 'attachments', :title => "#{attachment.filename}#{ ('-' + attachment.description) unless attachment.description.blank? }" %>
+ <% end %>
+
<% if options[:deletable] %>
<%= link_to image_tag('delete.png'), attachment_path(attachment),
:confirm => l(:text_are_you_sure),
@@ -33,7 +38,7 @@
{:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename }, :class => 'lightbox', :rel => 'attachments', :title => "#{attachment.filename}#{ ('-' + attachment.description) unless attachment.description.blank? }" %>
<% end -%>
</div>
- <% end -%>
+ <% end %>
<% end %>
</div>
View
41 assets/javascripts/lightbox.js
@@ -8,7 +8,7 @@
'speedOut' : 200
}
- $("div.attachments a.lightbox, div.attachments a.swf, div.attachments a.image").fancybox(options);
+ $("div.attachments a.lightbox, div.attachments a.swf, div.attachments a.image, div.attachments a.attachment_preview").fancybox(options);
$.extend(
options,
{
@@ -17,25 +17,46 @@
'autoDimensions': false,
'showNavArrows': false,
'onClosed': function() {
- $("#fancybox-inner").empty();
+ $("#fancybox-inner").empty()
}
}
)
+
$("div.attachments a.pdf").each(function() {
- if(is_chrome()) {
- var inline_link = this.href.replace(/\/attachments\//, "/attachments/download_inline/");
- options.content = '<embed src="' + inline_link + '" type="application/pdf" class="chrome"/>';
- }
- else {
- options.content = '<embed src="' + this.href + '#nameddest=self&page=1&view=FitH, 0&zoom=80,0,0" type="application/pdf" height="100%" width="100%" />';
- }
- $(this).fancybox(options);
+ if(is_chrome()) {
+ var inline_link = this.href.replace(/\/attachments\//, "/attachments/download_inline/")
+ options.content = embed_chrome_pdf(inline_link)
+ }
+ else {
+ options.content = embed_pdf(this.href)
+ }
+ $(this).fancybox(options)
+ })
+
+ $("div.attachments a.attachment_preview").each(function() {
+ if(is_chrome()) {
+ var inline_link = this.href.replace(/\/preview\//, "/preview_inline/")
+ options.content = embed_chrome_pdf(inline_link)
+ }
+ else {
+ options.content = embed_pdf(this.href)
+ }
+ $(this).fancybox(options)
})
+
function is_chrome() {
return navigator.userAgent.indexOf("Chrome") >= -1
}
+ function embed_chrome_pdf(document_url) {
+ return '<embed src="' + document_url + '" type="application/pdf" class="chrome"/>'
+ }
+
+ function embed_pdf(document_url) {
+ return '<embed src="' + document_url + '#nameddest=self&page=1&view=FitH, 0&zoom=80,0,0" type="application/pdf" height="100%" width="100%" />'
+ }
+
})
})(jQuery)
View
14 config/routes.rb
@@ -1,12 +1,22 @@
if Gem::Version.new("3.0") > Gem::Version.new(Rails.version) then
#Redmine 1.x
ActionController::Routing::Routes.draw do |map|
- map.connect 'attachments/download_inline/:id/:filename', :controller => 'attachments', :action => 'download_inline', :id => /\d+/, :filename => /.*/
+ map.with_options :controller => 'attachments', :id => /\d+/, :filename => /.*/ do |a|
+ a.connect 'attachments/download_inline/:id/:filename', :action => 'download_inline'
+ a.connect 'attachments/preview/:id/:filename', :action => 'preview'
+ a.connect 'attachments/preview_inline/:id/:filename', :action => 'preview_inline'
+ end
end
else
#Redmine 2.x
RedmineApp::Application.routes.draw do
- get 'attachments/download_inline/:id/:filename', :controller => 'attachments', :action => 'download_inline', :id => /\d+/, :filename => /.*/
+ with_options :controller => 'attachments', :id => /\d+/, :filename => /.*/ do |a|
+ scope '/attachments' do
+ a.get '/download_inline/:id/:filename', :action => 'download_inline'
+ a.get '/preview/:id/:filename', :action => 'preview'
+ a.get '/preview_inline/:id/:filename', :action => 'preview_inline'
+ end
+ end
end
end
View
20 db/migrate/0001_create_attachment_previews.rb
@@ -0,0 +1,20 @@
+class CreateAttachmentPreviews < ActiveRecord::Migration
+ def up
+ self.class.up
+ end
+
+ def down
+ self.class.down
+ end
+
+ def self.up
+ create_table :attachment_previews do |t|
+ t.references :attachment
+ t.string :file_type
+ end
+ end
+
+ def self.down
+ drop_table :attachment_previews
+ end
+end
View
4 init.rb
@@ -1,7 +1,9 @@
require 'redmine'
require_dependency 'redmine_lightbox/hooks/view_layouts_base_html_head_hook'
-
+require_dependency 'redmine_lightbox/services/document_converter'
+require_dependency 'redmine_lightbox/patches/attachment_patch'
+require_dependency 'redmine_lightbox/patches/attachments_controller_patch'
Redmine::Plugin.register :redmine_lightbox do
name 'Redmine Light Box plugin'
author 'G.K.'
View
11 lib/redmine_lightbox/hooks/view_layouts_base_html_head_hook.rb
@@ -7,10 +7,17 @@ def view_layouts_base_html_head(context={})
context[:controller].is_a?(DocumentsController) ||
context[:controller].is_a?(FilesController) ||
context[:controller].is_a?(BoardsController))
+
+ if Redmine::VERSION.to_s.slice(0, 3) < "2.1"
+ jquery_include = javascript_include_tag('jquery_loader.js', :plugin => 'redmine_lightbox') \
+ + javascript_tag("jQuery.noConflict()")
+ else
+ jquery_include = ""
+ end
+
return stylesheet_link_tag("jquery.fancybox-1.3.4.css", :plugin => "redmine_lightbox", :media => "screen") \
+ stylesheet_link_tag("lightbox.css", :plugin => "redmine_lightbox", :media => "screen") \
- + javascript_include_tag('jquery_loader.js', :plugin => 'redmine_lightbox') \
- + javascript_tag("jQuery.noConflict()") \
+ + jquery_include \
+ javascript_include_tag('jquery.fancybox-1.3.4.pack.js', :plugin => 'redmine_lightbox') \
+ javascript_include_tag('jquery.easing-1.3.pack.js', :plugin => 'redmine_lightbox') \
+ javascript_include_tag('lightbox.js', :plugin => 'redmine_lightbox') \
View
44 lib/redmine_lightbox/patches/attachment_patch.rb
@@ -0,0 +1,44 @@
+require_dependency 'attachment'
+
+module RedmineLightbox
+ module Patches
+ module AttachmentPatch
+ PREVIEW_TRANSFORMATIONS = {
+ 'doc' => 'pdf',
+ 'docx' => 'pdf'
+ }
+
+ class << self
+ def included(base)
+ base.class_eval do
+ has_one :attachment_preview, :dependent => :destroy
+
+ after_save :generate_preview
+ end
+ end
+ end
+
+ def try_to_generate_preview
+ format = preview_format
+ if format && !attachment_preview
+ create_attachment_preview(:file_type => format)
+ else
+ false
+ end
+ end
+
+ private
+ def preview_format
+ attachment_format = filename.rpartition(".")[2]
+ preview_format = PREVIEW_TRANSFORMATIONS[attachment_format]
+ end
+
+ def generate_preview
+ try_to_generate_preview
+ true
+ end
+ end
+ end
+end
+
+Attachment.send(:include, RedmineLightbox::Patches::AttachmentPatch)
View
41 lib/redmine_lightbox/patches/attachments_controller_patch.rb
@@ -0,0 +1,41 @@
+require_dependency 'attachments_controller'
+
+module RedmineLightbox
+ module Patches
+ module AttachmentsControllerPatch
+ class << self
+ def included(base)
+ base.class_eval do
+ unloadable
+
+ before_filter :file_readable, :read_authorize, :only => [:show, :download, :download_inline, :preview, :preview_inline]
+
+ def preview
+ send_preview 'attachment'
+ end
+
+ def preview_inline
+ send_preview 'inline'
+ end
+
+ def download_inline
+ send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
+ :type => detect_content_type(@attachment),
+ :disposition => 'inline'
+ end
+
+ private
+ def send_preview(disposition)
+ preview = @attachment.attachment_preview
+ send_file preview.diskfile, :filename => filename_for_content_disposition(preview.filename),
+ :type => detect_content_type(preview),
+ :disposition => disposition
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+AttachmentsController.send(:include, RedmineLightbox::Patches::AttachmentsControllerPatch)
View
22 lib/redmine_lightbox/services/document_converter.rb
@@ -0,0 +1,22 @@
+module RedmineLightbox
+ module Services
+ class DocumentConverter
+ CONVERTER = "unoconv"
+
+ def initialize(output_format)
+ @output_format = output_format
+ end
+
+ def convert(filename)
+ system "#{CONVERTER} -f #{@output_format} #{filename}"
+ end
+
+ class << self
+ def preview_filename_for(filename, format)
+ name, splitter, original_format = filename.rpartition(".")
+ [name, splitter, format].join
+ end
+ end
+ end
+ end
+end
View
7 lib/tasks/generate_possible_previews.rake
@@ -0,0 +1,7 @@
+namespace :redmine do
+ task :generate_possible_previews => :environment do
+ Attachment.find_each do |attachment|
+ attachment.try_to_generate_preview
+ end
+ end
+end
View
37 test/integration/attachment_test.rb
@@ -0,0 +1,37 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class AttachmentTest < ActionController::IntegrationTest
+ fixtures :attachments, :users
+
+ def setup
+ set_fixtures_attachments_directory
+
+ @some_file = Attachment.find_by_filename("source.rb")
+
+ @some_file.create_attachment_preview(:file_type => "pdf")
+ end
+
+ def test_should_get_file
+ path = "/attachments/download/#{@some_file.id}/#{@some_file.filename}"
+ get_via_redirect(path)
+ assert_response :success
+ end
+
+ def test_should_get_file_inline
+ path = "/attachments/download_inline/#{@some_file.id}/#{@some_file.filename}"
+ get_via_redirect(path)
+ assert_response :success
+ end
+
+ def test_should_get_preview
+ path = "/attachments/preview/#{@some_file.id}/#{@some_file.filename}"
+ get_via_redirect(path)
+ assert_response :success
+ end
+
+ def test_should_get_preview_inline
+ path = "/attachments/preview_inline/#{@some_file.id}/#{@some_file.filename}"
+ get_via_redirect(path)
+ assert_response :success
+ end
+end
View
8 test/test_helper.rb
@@ -1,5 +1,7 @@
# Load the normal Rails helper
-require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
+helper_path = File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper')
+if !File.exists?(helper_path)
+ helper_path = File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
+end
-# Ensure that we are using the temporary fixture path
-Engines::Testing.set_fixture_path
+require helper_path
Please sign in to comment.
Something went wrong with that request. Please try again.