diff --git a/lib/linguist/blob_helper.rb b/lib/linguist/blob_helper.rb index 96184c65..96b70a38 100644 --- a/lib/linguist/blob_helper.rb +++ b/lib/linguist/blob_helper.rb @@ -2,7 +2,6 @@ require 'linguist/language' require 'linguist/mime' require 'linguist/pathname' -require 'escape_utils' require 'yaml' module Linguist @@ -63,12 +62,7 @@ module Linguist # # Returns a content disposition String. def disposition - case content_type - when 'application/octet-stream', 'application/java-archive' - "attachment; filename=#{EscapeUtils.escape_url(pathname.basename)}" - else - 'inline' - end + pathname.disposition end # Public: Is the blob text? diff --git a/lib/linguist/content_types.yml b/lib/linguist/content_types.yml index 96a5d146..c27a6ced 100644 --- a/lib/linguist/content_types.yml +++ b/lib/linguist/content_types.yml @@ -1,7 +1,11 @@ # MIME types and extensions to override when the raw blob is served # mime types +application/javascript: text/plain +application/perl: text/plain +application/python: text/plain application/rdf+xml: text/plain +application/ruby: text/plain application/sh: text/plain application/tex: text/plain application/xhtml+xml: text/plain diff --git a/lib/linguist/mime.rb b/lib/linguist/mime.rb index badaa4eb..3a3eec79 100644 --- a/lib/linguist/mime.rb +++ b/lib/linguist/mime.rb @@ -1,11 +1,42 @@ require 'mime/types' require 'yaml' +module MIME + class Type + attr_accessor :binary + + undef_method :binary? + def binary? + if defined? @binary + @binary + else + @encoding == 'base64' + end + end + + attr_accessor :attachment + + def attachment? + if defined? @attachment + @attachment + else + binary? + end + end + end +end + # Register additional mime type extensions mime_extensions = YAML.load_file(File.expand_path("../mimes.yml", __FILE__)) -mime_extensions.each do |mime_type, exts| +mime_extensions.each do |mime_type, options| mime = MIME::Types[mime_type].first || MIME::Type.new(mime_type) - exts.each { |ext| mime.extensions << ext } + + (options['extensions'] || []).each { |ext| mime.extensions << ext } + + mime.binary = options['binary'] if options.key?('binary') + mime.attachment = options['attachment'] if options.key?('attachment') + + MIME::Types.add_type_variant(mime) MIME::Types.index_extensions(mime) end @@ -59,5 +90,49 @@ module Linguist type end + + # Internal: Determine if extension or mime type is binary. + # + # ext_or_mime_type - A file extension ".txt" or mime type "text/plain". + # + # Returns true or false + def self.binary?(ext_or_mime_type) + mime_type = lookup_mime_type_for(ext_or_mime_type) + mime_type.nil? || mime_type.binary? + end + + # Internal: Determine if extension or mime type is an attachment. + # + # ext_or_mime_type - A file extension ".txt" or mime type "text/plain". + # + # Attachments are files that should be downloaded rather than be + # displayed in the browser. + # + # This is used to set our Content-Disposition headers. + # + # Attachment files should generally binary files but non- + # attachments do not imply plain text. For an example Images are + # not treated as attachments. + # + # Returns true or false + def self.attachment?(ext_or_mime_type) + mime_type = lookup_mime_type_for(ext_or_mime_type) + mime_type.nil? || mime_type.attachment? + end + + # Internal: Lookup mime type for extension or mime type + # + # Returns a MIME::Type + def self.lookup_mime_type_for(ext_or_mime_type) + ext_or_mime_type ||= '' + + if ext_or_mime_type =~ /\w+\/\w+/ + guesses = ::MIME::Types[ext_or_mime_type] + else + guesses = ::MIME::Types.type_for(ext_or_mime_type) + end + + guesses.first + end end end diff --git a/lib/linguist/mimes.yml b/lib/linguist/mimes.yml index 2be62e35..0731c5c9 100644 --- a/lib/linguist/mimes.yml +++ b/lib/linguist/mimes.yml @@ -2,13 +2,75 @@ # # Review this list if we ever upgrade from mime-types 1.15 to 1.16 +application/atom+xml: + binary: false + +application/javascript: + binary: false + extensions: + - js + +application/json: + binary: false + extensions: + - json + +application/rdf+xml: + binary: false + +application/sh: + binary: false + +application/x-troff-ms: + binary: false + +application/netcdf: + binary: false + +application/x-perl: + binary: false + extensions: + - pl + - pm + +application/x-python: + binary: false + extensions: + - py + +application/x-ruby: + binary: false + extensions: + - rb + +application/x-wais-source: + binary: false + +application/vnd.mozilla.xul+xml: + binary: false + application/octet-stream: -- dmg -- dll + binary: true + extensions: + - dmg + - dll application/java-archive: -- ear -- war + binary: true + extensions: + - ear + - war application/x-shockwave-flash: -- swf + binary: true + extensions: + - swf + +image/gif: + attachment: false + +image/jpeg: + attachment: false + +image/png: + attachment: false diff --git a/lib/linguist/pathname.rb b/lib/linguist/pathname.rb index 9238de58..da6af487 100644 --- a/lib/linguist/pathname.rb +++ b/lib/linguist/pathname.rb @@ -1,6 +1,8 @@ require 'linguist/language' require 'linguist/mime' +require 'escape_utils' + module Linguist # Similar to ::Pathname, Linguist::Pathname wraps a path string and # provides helpful query methods. Its useful when you only have a @@ -99,6 +101,30 @@ module Linguist @content_type ||= Mime.content_type_for(extname) end + # Public: Determine if the Pathname should be served as an + # attachment. + # + # Returns true or false. + def attachment? + @attachment ||= Mime.attachment?(extname) + end + + # Public: Get the Content-Disposition header value + # + # This value is used when serving raw blobs. + # + # # => "attachment; filename=file.tar" + # # => "inline" + # + # Returns a content disposition String. + def disposition + if attachment? + "attachment; filename=#{EscapeUtils.escape_url(basename)}" + else + 'inline' + end + end + def to_s @path.dup end diff --git a/test/test_blob.rb b/test/test_blob.rb index 51aef34f..a8edda84 100644 --- a/test/test_blob.rb +++ b/test/test_blob.rb @@ -23,7 +23,7 @@ class TestBlob < Test::Unit::TestCase end def test_mime_type - assert_equal "text/plain", blob("grit.rb").mime_type + assert_equal "application/ruby", blob("grit.rb").mime_type assert_equal "application/xml", blob("bar.xml").mime_type assert_equal "text/plain", blob("dog.o").mime_type assert_equal "application/sh", blob("script.sh").mime_type @@ -31,6 +31,7 @@ class TestBlob < Test::Unit::TestCase def test_content_type assert_equal "text/plain; charset=utf-8", blob("grit.rb").content_type + assert_equal "text/plain; charset=utf-8", blob("foo.pl").content_type assert_equal "text/plain; charset=utf-8", blob("bar.xml").content_type assert_equal "application/octet-stream", blob("dog.o").content_type assert_equal "text/plain; charset=utf-8", blob("script.sh").content_type @@ -69,7 +70,9 @@ class TestBlob < Test::Unit::TestCase assert blob("git.deb").binary? assert blob("git.exe").binary? assert !blob("file.txt").binary? + assert !blob("foo.rb").binary? assert !blob("octocat.png").binary? + assert !blob("script.pl").binary? end def test_text @@ -88,6 +91,7 @@ class TestBlob < Test::Unit::TestCase def test_viewable assert blob("foo.rb").viewable? + assert blob("script.pl").viewable? assert !blob("octocat.png").viewable? assert !blob("linguist.gem").viewable? end diff --git a/test/test_mime.rb b/test/test_mime.rb index 9829b306..58bb47a3 100644 --- a/test/test_mime.rb +++ b/test/test_mime.rb @@ -8,9 +8,9 @@ class TestMime < Test::Unit::TestCase def test_mime assert_equal 'text/plain', Mime.mime_for(nil) - assert_equal 'text/plain', Mime.mime_for(".rb") - assert_equal 'text/plain', Mime.mime_for(".js") - assert_equal 'text/plain', Mime.mime_for(".py") + assert_equal 'application/ruby', Mime.mime_for(".rb") + assert_equal 'application/javascript', Mime.mime_for(".js") + assert_equal 'application/python', Mime.mime_for(".py") assert_equal 'text/plain', Mime.mime_for(".kt") assert_equal 'text/html', Mime.mime_for(".html") @@ -49,4 +49,116 @@ class TestMime < Test::Unit::TestCase assert_equal 'application/java-archive', Mime.content_type_for(".ear") assert_equal 'application/java-archive', Mime.content_type_for(".war") end + + def test_binary + assert Mime.binary?("application/octet-stream") + assert !Mime.binary?("text/plain") + + assert Mime.binary?("image/gif") + assert Mime.binary?("image/jpeg") + assert Mime.binary?("image/png") + assert Mime.binary?("java-archive") + assert Mime.binary?("x-shockwave-flash") + + assert !Mime.binary?("application/atom+xml") + assert !Mime.binary?("application/javascript") + assert !Mime.binary?("application/json") + assert !Mime.binary?("application/rdf+xml") + assert !Mime.binary?("application/sh") + assert !Mime.binary?("application/x-perl") + assert !Mime.binary?("application/x-python") + assert !Mime.binary?("application/x-ruby") + + assert !Mime.binary?(".ms") + assert !Mime.binary?(".nc") + assert !Mime.binary?(".src") + assert !Mime.binary?(".xul") + end + + def test_attachment + assert Mime.attachment?("application/octet-stream") + assert !Mime.attachment?("text/plain") + + assert Mime.attachment?("application/java-archive") + assert Mime.attachment?("application/ogg") + assert Mime.attachment?("application/pdf") + assert Mime.attachment?("application/x-gzip") + assert Mime.attachment?("application/zip") + assert Mime.attachment?("audio/mp4") + + assert Mime.attachment?(".a") + assert Mime.attachment?(".air") + assert Mime.attachment?(".blend") + assert Mime.attachment?(".crx") + assert Mime.attachment?(".deb") + assert Mime.attachment?(".dmg") + assert Mime.attachment?(".exe") + assert Mime.attachment?(".gem") + assert Mime.attachment?(".graffle") + assert Mime.attachment?(".gz") + assert Mime.attachment?(".icns") + assert Mime.attachment?(".ipa") + assert Mime.attachment?(".lib") + assert Mime.attachment?(".mcz") + assert Mime.attachment?(".mov") + assert Mime.attachment?(".mp3") + assert Mime.attachment?(".nib") + assert Mime.attachment?(".o") + assert Mime.attachment?(".odp") + assert Mime.attachment?(".ods") + assert Mime.attachment?(".odt") + assert Mime.attachment?(".ogg") + assert Mime.attachment?(".ogv") + assert Mime.attachment?(".otf") + assert Mime.attachment?(".pfx") + assert Mime.attachment?(".pigx") + assert Mime.attachment?(".plgx") + assert Mime.attachment?(".pptx") + assert Mime.attachment?(".psd") + assert Mime.attachment?(".sib") + assert Mime.attachment?(".so") + assert Mime.attachment?(".spl") + assert Mime.attachment?(".sqlite3") + assert Mime.attachment?(".swc") + assert Mime.attachment?(".swf") + assert Mime.attachment?(".tar") + assert Mime.attachment?(".ucode") + assert Mime.attachment?(".xpi") + assert Mime.attachment?(".zip") + + assert !Mime.attachment?("application/atom+xml") + assert !Mime.attachment?("application/javascript") + assert !Mime.attachment?("application/json") + assert !Mime.attachment?("application/rdf+xml") + assert !Mime.attachment?("application/sh") + assert !Mime.attachment?("application/xhtml+xml") + assert !Mime.attachment?("application/xml") + assert !Mime.attachment?("image/gif") + assert !Mime.attachment?("image/jpeg") + assert !Mime.attachment?("image/png") + assert !Mime.attachment?("text/css") + assert !Mime.attachment?("text/csv") + assert !Mime.attachment?("text/html") + assert !Mime.attachment?("text/javascript") + assert !Mime.attachment?("text/plain") + + assert !Mime.attachment?(".gif") + assert !Mime.attachment?(".jpeg") + assert !Mime.attachment?(".jpg") + assert !Mime.attachment?(".js") + assert !Mime.attachment?(".latex") + assert !Mime.attachment?(".ms") + assert !Mime.attachment?(".nc") + assert !Mime.attachment?(".pl") + assert !Mime.attachment?(".png") + assert !Mime.attachment?(".ps") + assert !Mime.attachment?(".py") + assert !Mime.attachment?(".rb") + assert !Mime.attachment?(".sh") + assert !Mime.attachment?(".src") + assert !Mime.attachment?(".tcl") + assert !Mime.attachment?(".texi") + assert !Mime.attachment?(".texinfo") + assert !Mime.attachment?(".xul") + end end diff --git a/test/test_pathname.rb b/test/test_pathname.rb index 62ab81ff..310394e5 100644 --- a/test/test_pathname.rb +++ b/test/test_pathname.rb @@ -52,9 +52,9 @@ class TestPathname < Test::Unit::TestCase end def test_mime_type - assert_equal 'text/plain', Pathname.new("file.rb").mime_type - assert_equal 'text/plain', Pathname.new("file.js").mime_type - assert_equal 'text/plain', Pathname.new("itty.py").mime_type + assert_equal 'application/ruby', Pathname.new("file.rb").mime_type + assert_equal 'application/javascript', Pathname.new("file.js").mime_type + assert_equal 'application/python', Pathname.new("itty.py").mime_type assert_equal 'text/plain', Pathname.new("defun.kt").mime_type end