dragonfly 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dragonfly might be problematic. Click here for more details.
- data/History.md +11 -0
- data/README.md +1 -1
- data/VERSION +1 -1
- data/dragonfly.gemspec +3 -2
- data/extra_docs/Configuration.md +5 -0
- data/extra_docs/DataStorage.md +1 -1
- data/extra_docs/Heroku.md +2 -2
- data/extra_docs/ImageMagick.md +10 -0
- data/extra_docs/Models.md +5 -0
- data/extra_docs/Rails2.md +3 -3
- data/extra_docs/Rails3.md +3 -3
- data/lib/dragonfly/app.rb +13 -0
- data/lib/dragonfly/data_storage.rb +3 -2
- data/lib/dragonfly/data_storage/file_data_store.rb +1 -2
- data/lib/dragonfly/data_storage/s3data_store.rb +18 -9
- data/lib/dragonfly/image_magick/processor.rb +12 -13
- data/lib/dragonfly/job_definitions.rb +4 -0
- data/lib/dragonfly/rails/images.rb +2 -2
- data/lib/dragonfly/response.rb +1 -1
- data/lib/dragonfly/server.rb +19 -3
- data/spec/dragonfly/app_spec.rb +20 -0
- data/spec/dragonfly/data_storage/file_data_store_spec.rb +10 -4
- data/spec/dragonfly/data_storage/s3_data_store_spec.rb +31 -16
- data/spec/dragonfly/image_magick/processor_spec.rb +15 -2
- data/spec/dragonfly/job_definitions_spec.rb +22 -0
- data/spec/dragonfly/job_endpoint_spec.rb +6 -0
- data/spec/dragonfly/server_spec.rb +74 -58
- data/spec/support/image_matchers.rb +10 -0
- data/spec/test_imagemagick.ru +49 -0
- data/yard/templates/default/fulldoc/html/css/common.css +1 -1
- data/yard/templates/default/layout/html/layout.erb +1 -1
- metadata +4 -3
    
        data/History.md
    CHANGED
    
    | @@ -1,3 +1,14 @@ | |
| 1 | 
            +
            0.9.1 (2011-05-11)
         | 
| 2 | 
            +
            ==================
         | 
| 3 | 
            +
            Features
         | 
| 4 | 
            +
            --------
         | 
| 5 | 
            +
            - Added reflection methods `app.processor_methods`, `app.generator_methods` and `app.job_methods`
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Fixes
         | 
| 8 | 
            +
            -----
         | 
| 9 | 
            +
            - Improved performance of `resize_and_crop` method, using imagemagick built-in '^' operator
         | 
| 10 | 
            +
            - Improved server security validations
         | 
| 11 | 
            +
             | 
| 1 12 | 
             
            0.9.0 (2011-04-27)
         | 
| 2 13 | 
             
            ==================
         | 
| 3 14 | 
             
            Features
         | 
    
        data/README.md
    CHANGED
    
    
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            0.9. | 
| 1 | 
            +
            0.9.1
         | 
    
        data/dragonfly.gemspec
    CHANGED
    
    | @@ -5,11 +5,11 @@ | |
| 5 5 |  | 
| 6 6 | 
             
            Gem::Specification.new do |s|
         | 
| 7 7 | 
             
              s.name = %q{dragonfly}
         | 
| 8 | 
            -
              s.version = "0.9. | 
| 8 | 
            +
              s.version = "0.9.1"
         | 
| 9 9 |  | 
| 10 10 | 
             
              s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
         | 
| 11 11 | 
             
              s.authors = ["Mark Evans"]
         | 
| 12 | 
            -
              s.date = %q{2011- | 
| 12 | 
            +
              s.date = %q{2011-05-11}
         | 
| 13 13 | 
             
              s.description = %q{Dragonfly is a framework that enables on-the-fly processing for any content type.
         | 
| 14 14 | 
             
              It is especially suited to image handling. Its uses range from image thumbnails to standard attachments to on-demand text generation.}
         | 
| 15 15 | 
             
              s.email = %q{mark@new-bamboo.co.uk}
         | 
| @@ -172,6 +172,7 @@ Gem::Specification.new do |s| | |
| 172 172 | 
             
                "spec/support/argument_matchers.rb",
         | 
| 173 173 | 
             
                "spec/support/image_matchers.rb",
         | 
| 174 174 | 
             
                "spec/support/simple_matchers.rb",
         | 
| 175 | 
            +
                "spec/test_imagemagick.ru",
         | 
| 175 176 | 
             
                "yard/handlers/configurable_attr_handler.rb",
         | 
| 176 177 | 
             
                "yard/setup.rb",
         | 
| 177 178 | 
             
                "yard/templates/default/fulldoc/html/css/common.css",
         | 
    
        data/extra_docs/Configuration.md
    CHANGED
    
    | @@ -81,6 +81,11 @@ Where is configuration done? | |
| 81 81 | 
             
            In Rails, it should be done in an initializer, e.g. 'config/initializers/dragonfly.rb'.
         | 
| 82 82 | 
             
            Otherwise it should be done anywhere where general setup is done, early on.
         | 
| 83 83 |  | 
| 84 | 
            +
            Reflecting on configuration
         | 
| 85 | 
            +
            ---------------------------
         | 
| 86 | 
            +
            There are a few methods you can call on the `app` to see what processors etc. are registered: `processor_methods`, `generator_methods`
         | 
| 87 | 
            +
            and `job_methods`.
         | 
| 88 | 
            +
             | 
| 84 89 | 
             
            Saved configurations
         | 
| 85 90 | 
             
            ====================
         | 
| 86 91 | 
             
            Saved configurations are useful if you often configure the app the same way.
         | 
    
        data/extra_docs/DataStorage.md
    CHANGED
    
    | @@ -83,7 +83,7 @@ To configure with the {Dragonfly::DataStorage::S3DataStore S3DataStore}: | |
| 83 83 |  | 
| 84 84 | 
             
                app.datastore = Dragonfly::DataStorage::S3DataStore.new
         | 
| 85 85 |  | 
| 86 | 
            -
                app.datastore.configure do | | 
| 86 | 
            +
                app.datastore.configure do |c|
         | 
| 87 87 | 
             
                  c.bucket_name = 'my_bucket'
         | 
| 88 88 | 
             
                  c.access_key_id = 'salfjasd34u23'
         | 
| 89 89 | 
             
                  c.secret_access_key = '8u2u3rhkhfo23...'
         | 
    
        data/extra_docs/Heroku.md
    CHANGED
    
    | @@ -6,7 +6,7 @@ The default configuration won't work out of the box for Heroku, because | |
| 6 6 | 
             
            - Heroku doesn't allow saving files to the filesystem (although it does use tempfiles)
         | 
| 7 7 | 
             
            - We won't need {http://tomayko.com/src/rack-cache/ Rack::Cache} on Heroku because it already uses the caching proxy {http://varnish.projects.linpro.no/ Varnish}, which we can make use of
         | 
| 8 8 |  | 
| 9 | 
            -
            Instead of the normal { | 
| 9 | 
            +
            Instead of the normal {file:DataStorage#File\_datastore FileDataStore}, we can use the {file:DataStorage#S3\_datastore S3DataStore}.
         | 
| 10 10 |  | 
| 11 11 | 
             
            Assuming you have an S3 account set up...
         | 
| 12 12 |  | 
| @@ -45,5 +45,5 @@ Now you can benefit from super-fast images served straight from Heroku's cache! | |
| 45 45 |  | 
| 46 46 | 
             
            **NOTE**: HEROKU'S CACHE IS CLEARED EVERY TIME YOU DEPLOY!!!
         | 
| 47 47 |  | 
| 48 | 
            -
            If this is an issue, you may want to look into storing thumbnails on S3 (see {file: | 
| 48 | 
            +
            If this is an issue, you may want to look into storing thumbnails on S3 (see {file:ServingRemotely}), or maybe an after-deploy hook for hitting specific Dragonfly urls you want to cache, etc.
         | 
| 49 49 | 
             
            It won't be a problem for most sites though.
         | 
    
        data/extra_docs/ImageMagick.md
    CHANGED
    
    | @@ -124,3 +124,13 @@ You can use `padding-top`, `padding-left`, etc., as well as the standard css sho | |
| 124 124 |  | 
| 125 125 | 
             
            An alternative for `:font_family` is `:font` (see {http://www.imagemagick.org/script/command-line-options.php#font}), which could be a complete filename.
         | 
| 126 126 | 
             
            Available fonts are those available on your system.
         | 
| 127 | 
            +
             | 
| 128 | 
            +
            Configuration
         | 
| 129 | 
            +
            -------------
         | 
| 130 | 
            +
            There are some options that can be set, e.g. if the imagemagick convert command can't be found:
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                app.configure do |c|
         | 
| 133 | 
            +
                  c.convert_command = "/opt/local/bin/convert"          # defaults to "convert"
         | 
| 134 | 
            +
                  c.identify_command = "/opt/local/bin/convert"         # defaults to "convert"
         | 
| 135 | 
            +
                  c.log_commands = true                                 # defaults to false
         | 
| 136 | 
            +
                end
         | 
    
        data/extra_docs/Models.md
    CHANGED
    
    | @@ -208,6 +208,11 @@ Validations | |
| 208 208 | 
             
            The property argument of `validates_property` will generally be one of the registered analyser properties as described in {file:Analysers.md Analysers}.
         | 
| 209 209 | 
             
            However it would actually work for arbitrary properties, including those of non-dragonfly model attributes.
         | 
| 210 210 |  | 
| 211 | 
            +
            `validates_property` can also take a proc for the message, yielding the actual value and the model
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                validates_property :width, :of => :cover_image, :in => (0..400),
         | 
| 214 | 
            +
                                           :message => proc{|actual, model| "Unlucky #{model.title} - was #{actual}" }
         | 
| 215 | 
            +
             | 
| 211 216 | 
             
            Name and extension
         | 
| 212 217 | 
             
            ------------------
         | 
| 213 218 | 
             
            If the object assigned is a file, or responds to `original_filename` (as is the case with file uploads in Rails, etc.), then `name` will be set.
         | 
    
        data/extra_docs/Rails2.md
    CHANGED
    
    | @@ -25,11 +25,11 @@ config/initializers/dragonfly.rb: | |
| 25 25 |  | 
| 26 26 | 
             
            environment.rb:
         | 
| 27 27 |  | 
| 28 | 
            -
                config.middleware. | 
| 28 | 
            +
                config.middleware.insert 0, 'Dragonfly::Middleware', :images, '/media'
         | 
| 29 29 | 
             
                config.middleware.insert_before 'Dragonfly::Middleware', 'Rack::Cache', {
         | 
| 30 30 | 
             
                  :verbose     => true,
         | 
| 31 | 
            -
                  :metastore   => "file:#{Rails.root}/tmp/dragonfly/cache/meta",
         | 
| 32 | 
            -
                  :entitystore => "file:#{Rails.root}/tmp/dragonfly/cache/body"
         | 
| 31 | 
            +
                  :metastore   => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"),
         | 
| 32 | 
            +
                  :entitystore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/body")
         | 
| 33 33 | 
             
                }
         | 
| 34 34 |  | 
| 35 35 | 
             
            Gems
         | 
    
        data/extra_docs/Rails3.md
    CHANGED
    
    | @@ -26,14 +26,14 @@ application.rb: | |
| 26 26 | 
             
                config.middleware.insert 0, 'Dragonfly::Middleware', :images
         | 
| 27 27 | 
             
                config.middleware.insert_before 'Dragonfly::Middleware', 'Rack::Cache', {
         | 
| 28 28 | 
             
                  :verbose     => true,
         | 
| 29 | 
            -
                  :metastore   => "file:#{Rails.root}/tmp/dragonfly/cache/meta",
         | 
| 30 | 
            -
                  :entitystore => "file:#{Rails.root}/tmp/dragonfly/cache/body"
         | 
| 29 | 
            +
                  :metastore   => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"),
         | 
| 30 | 
            +
                  :entitystore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/body")
         | 
| 31 31 | 
             
                }
         | 
| 32 32 |  | 
| 33 33 | 
             
            Gemfile
         | 
| 34 34 | 
             
            -------
         | 
| 35 35 |  | 
| 36 | 
            -
                gem 'dragonfly', '~>0.9. | 
| 36 | 
            +
                gem 'dragonfly', '~>0.9.1'
         | 
| 37 37 | 
             
                gem 'rack-cache', :require => 'rack/cache'
         | 
| 38 38 |  | 
| 39 39 | 
             
            Capistrano
         | 
    
        data/lib/dragonfly/app.rb
    CHANGED
    
    | @@ -145,6 +145,19 @@ module Dragonfly | |
| 145 145 | 
             
                  end
         | 
| 146 146 | 
             
                end
         | 
| 147 147 |  | 
| 148 | 
            +
                # Reflection
         | 
| 149 | 
            +
                def processor_methods
         | 
| 150 | 
            +
                  processor.functions.keys
         | 
| 151 | 
            +
                end
         | 
| 152 | 
            +
                
         | 
| 153 | 
            +
                def generator_methods
         | 
| 154 | 
            +
                  generator.functions.keys
         | 
| 155 | 
            +
                end
         | 
| 156 | 
            +
                
         | 
| 157 | 
            +
                def job_methods
         | 
| 158 | 
            +
                  job_definitions.definition_names
         | 
| 159 | 
            +
                end
         | 
| 160 | 
            +
                
         | 
| 148 161 | 
             
                # Deprecated methods
         | 
| 149 162 | 
             
                def url_path_prefix=(thing)
         | 
| 150 163 | 
             
                  raise NoMethodError, "url_path_prefix is deprecated - please use url_format, e.g. url_format = '/media/:job/:basename.:format' - see docs for more details"
         | 
| @@ -2,8 +2,9 @@ module Dragonfly | |
| 2 2 | 
             
              module DataStorage
         | 
| 3 3 |  | 
| 4 4 | 
             
                # Exceptions
         | 
| 5 | 
            -
                class  | 
| 6 | 
            -
                class  | 
| 5 | 
            +
                class BadUID < RuntimeError; end
         | 
| 6 | 
            +
                class DataNotFound < RuntimeError; end
         | 
| 7 | 
            +
                class UnableToStore < RuntimeError; end
         | 
| 7 8 |  | 
| 8 9 | 
             
              end
         | 
| 9 10 | 
             
            end
         | 
| @@ -6,7 +6,6 @@ module Dragonfly | |
| 6 6 | 
             
                class FileDataStore
         | 
| 7 7 |  | 
| 8 8 | 
             
                  # Exceptions
         | 
| 9 | 
            -
                  class BadUID < RuntimeError; end
         | 
| 10 9 | 
             
                  class UnableToFormUrl < RuntimeError; end
         | 
| 11 10 |  | 
| 12 11 | 
             
                  include Configurable
         | 
| @@ -139,7 +138,7 @@ module Dragonfly | |
| 139 138 | 
             
                  end
         | 
| 140 139 |  | 
| 141 140 | 
             
                  def validate_uid!(uid)
         | 
| 142 | 
            -
                    raise BadUID, "tried to  | 
| 141 | 
            +
                    raise BadUID, "tried to retrieve uid #{uid.inspect}" if uid.blank? || uid['../']
         | 
| 143 142 | 
             
                  end
         | 
| 144 143 |  | 
| 145 144 | 
             
                end
         | 
| @@ -37,12 +37,14 @@ module Dragonfly | |
| 37 37 | 
             
                    headers = opts[:headers] || {}
         | 
| 38 38 | 
             
                    uid = opts[:path] || generate_uid(meta[:name] || temp_object.original_filename || 'file')
         | 
| 39 39 |  | 
| 40 | 
            -
                     | 
| 41 | 
            -
                       | 
| 42 | 
            -
                         | 
| 40 | 
            +
                    rescuing_socket_errors do
         | 
| 41 | 
            +
                      if use_filesystem
         | 
| 42 | 
            +
                        temp_object.file do |f|
         | 
| 43 | 
            +
                          storage.put_object(bucket_name, uid, f, full_storage_headers(headers, meta))
         | 
| 44 | 
            +
                        end
         | 
| 45 | 
            +
                      else
         | 
| 46 | 
            +
                        storage.put_object(bucket_name, uid, temp_object.data, full_storage_headers(headers, meta))
         | 
| 43 47 | 
             
                      end
         | 
| 44 | 
            -
                    else
         | 
| 45 | 
            -
                      storage.put_object(bucket_name, uid, temp_object.data, full_storage_headers(headers, meta))
         | 
| 46 48 | 
             
                    end
         | 
| 47 49 |  | 
| 48 50 | 
             
                    uid
         | 
| @@ -50,7 +52,7 @@ module Dragonfly | |
| 50 52 |  | 
| 51 53 | 
             
                  def retrieve(uid)
         | 
| 52 54 | 
             
                    ensure_configured
         | 
| 53 | 
            -
                    response = storage.get_object(bucket_name, uid)
         | 
| 55 | 
            +
                    response = rescuing_socket_errors{ storage.get_object(bucket_name, uid) }
         | 
| 54 56 | 
             
                    [
         | 
| 55 57 | 
             
                      response.body,
         | 
| 56 58 | 
             
                      parse_s3_metadata(response.headers)
         | 
| @@ -60,7 +62,7 @@ module Dragonfly | |
| 60 62 | 
             
                  end
         | 
| 61 63 |  | 
| 62 64 | 
             
                  def destroy(uid)
         | 
| 63 | 
            -
                    storage.delete_object(bucket_name, uid)
         | 
| 65 | 
            +
                    rescuing_socket_errors{ storage.delete_object(bucket_name, uid) }
         | 
| 64 66 | 
             
                  rescue Excon::Errors::NotFound => e
         | 
| 65 67 | 
             
                    raise DataNotFound, "#{e} - #{uid}"
         | 
| 66 68 | 
             
                  end
         | 
| @@ -87,7 +89,7 @@ module Dragonfly | |
| 87 89 | 
             
                  end
         | 
| 88 90 |  | 
| 89 91 | 
             
                  def bucket_exists?
         | 
| 90 | 
            -
                    storage.get_bucket_location(bucket_name)
         | 
| 92 | 
            +
                    rescuing_socket_errors{ storage.get_bucket_location(bucket_name) }
         | 
| 91 93 | 
             
                    true
         | 
| 92 94 | 
             
                  rescue Excon::Errors::NotFound => e
         | 
| 93 95 | 
             
                    false
         | 
| @@ -106,7 +108,7 @@ module Dragonfly | |
| 106 108 |  | 
| 107 109 | 
             
                  def ensure_bucket_initialized
         | 
| 108 110 | 
             
                    unless @bucket_initialized
         | 
| 109 | 
            -
                      storage.put_bucket(bucket_name, 'LocationConstraint' => region) unless bucket_exists?
         | 
| 111 | 
            +
                      rescuing_socket_errors{ storage.put_bucket(bucket_name, 'LocationConstraint' => region) } unless bucket_exists?
         | 
| 110 112 | 
             
                      @bucket_initialized = true
         | 
| 111 113 | 
             
                    end
         | 
| 112 114 | 
             
                  end
         | 
| @@ -134,6 +136,13 @@ module Dragonfly | |
| 134 136 | 
             
                    REGIONS.keys
         | 
| 135 137 | 
             
                  end
         | 
| 136 138 |  | 
| 139 | 
            +
                  def rescuing_socket_errors(&block)
         | 
| 140 | 
            +
                    yield
         | 
| 141 | 
            +
                  rescue Excon::Errors::SocketError => e
         | 
| 142 | 
            +
                    storage.reload
         | 
| 143 | 
            +
                    yield
         | 
| 144 | 
            +
                  end
         | 
| 145 | 
            +
             | 
| 137 146 | 
             
                end
         | 
| 138 147 |  | 
| 139 148 | 
             
              end
         | 
| @@ -35,8 +35,9 @@ module Dragonfly | |
| 35 35 | 
             
                    y       = "#{opts[:y] || 0}"
         | 
| 36 36 | 
             
                    y = '+' + y unless y[/^[+-]/]
         | 
| 37 37 | 
             
                    repage  = opts[:repage] == false ? '' : '+repage'
         | 
| 38 | 
            +
                    resize  = opts[:resize]
         | 
| 38 39 |  | 
| 39 | 
            -
                    convert(temp_object, "- | 
| 40 | 
            +
                    convert(temp_object, "#{"-resize #{resize} " if resize}#{"-gravity #{gravity} " if gravity}-crop #{width}x#{height}#{x}#{y} #{repage}")
         | 
| 40 41 | 
             
                  end
         | 
| 41 42 |  | 
| 42 43 | 
             
                  def flip(temp_object)
         | 
| @@ -53,20 +54,18 @@ module Dragonfly | |
| 53 54 | 
             
                  alias grayscale greyscale
         | 
| 54 55 |  | 
| 55 56 | 
             
                  def resize_and_crop(temp_object, opts={})
         | 
| 56 | 
            -
                     | 
| 57 | 
            -
             | 
| 58 | 
            -
                     | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 62 | 
            -
                    gravity = opts[:gravity] || 'c'
         | 
| 63 | 
            -
             | 
| 64 | 
            -
                    if width != current_width || height != current_height
         | 
| 65 | 
            -
                      scale = [width.to_f / current_width, height.to_f / current_height].max
         | 
| 66 | 
            -
                      temp_object = TempObject.new(resize(temp_object, "#{(scale * current_width).ceil}x#{(scale * current_height).ceil}"))
         | 
| 57 | 
            +
                    if !opts[:width] && !opts[:height]
         | 
| 58 | 
            +
                      return temp_object
         | 
| 59 | 
            +
                    elsif !opts[:width] || !opts[:height]
         | 
| 60 | 
            +
                      attrs          = identify(temp_object)
         | 
| 61 | 
            +
                      opts[:width]   ||= attrs[:width]
         | 
| 62 | 
            +
                      opts[:height]  ||= attrs[:height]
         | 
| 67 63 | 
             
                    end
         | 
| 68 64 |  | 
| 69 | 
            -
                     | 
| 65 | 
            +
                    opts[:gravity] ||= 'c'
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    opts[:resize]  = "#{opts[:width]}x#{opts[:height]}^^"
         | 
| 68 | 
            +
                    crop(temp_object, opts)
         | 
| 70 69 | 
             
                  end
         | 
| 71 70 |  | 
| 72 71 | 
             
                  def rotate(temp_object, amount, opts={})
         | 
| @@ -19,8 +19,8 @@ begin | |
| 19 19 | 
             
              require 'rack/cache'
         | 
| 20 20 | 
             
              Rails.application.middleware.insert_before 'Dragonfly::Middleware', 'Rack::Cache', {
         | 
| 21 21 | 
             
                :verbose     => true,
         | 
| 22 | 
            -
                :metastore   => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"), # URI encoded  | 
| 23 | 
            -
                :entitystore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/body") | 
| 22 | 
            +
                :metastore   => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"), # URI encoded in case of spaces
         | 
| 23 | 
            +
                :entitystore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/body")
         | 
| 24 24 | 
             
              }
         | 
| 25 25 | 
             
            rescue LoadError => e  
         | 
| 26 26 | 
             
              app.log.warn("Warning: couldn't find rack-cache for caching dragonfly content")
         | 
    
        data/lib/dragonfly/response.rb
    CHANGED
    
    | @@ -24,7 +24,7 @@ module Dragonfly | |
| 24 24 | 
             
                    job.apply
         | 
| 25 25 | 
             
                    [200, success_headers, job.result]
         | 
| 26 26 | 
             
                  end
         | 
| 27 | 
            -
                rescue DataStorage::DataNotFound => e
         | 
| 27 | 
            +
                rescue DataStorage::DataNotFound, DataStorage::BadUID => e
         | 
| 28 28 | 
             
                  app.log.warn(e.message)
         | 
| 29 29 | 
             
                  [404, {"Content-Type" => 'text/plain'}, ['Not found']]
         | 
| 30 30 | 
             
                end
         | 
    
        data/lib/dragonfly/server.rb
    CHANGED
    
    | @@ -1,9 +1,14 @@ | |
| 1 1 | 
             
            module Dragonfly
         | 
| 2 2 | 
             
              class Server
         | 
| 3 3 |  | 
| 4 | 
            +
                # Exceptions
         | 
| 5 | 
            +
                class JobNotAllowed < RuntimeError; end
         | 
| 6 | 
            +
             | 
| 4 7 | 
             
                include Loggable
         | 
| 5 8 | 
             
                include Configurable
         | 
| 6 9 |  | 
| 10 | 
            +
                configurable_attr :allow_fetch_file, false
         | 
| 11 | 
            +
                configurable_attr :allow_fetch_url, false
         | 
| 7 12 | 
             
                configurable_attr :dragonfly_url, '/dragonfly'
         | 
| 8 13 | 
             
                configurable_attr :protect_from_dos_attacks, false
         | 
| 9 14 | 
             
                configurable_attr :url_format, '/:job/:basename.:format'
         | 
| @@ -28,6 +33,7 @@ module Dragonfly | |
| 28 33 | 
             
                    dragonfly_response
         | 
| 29 34 | 
             
                  elsif (params = url_mapper.params_for(env["PATH_INFO"], env["QUERY_STRING"])) && params['job']
         | 
| 30 35 | 
             
                    job = Job.deserialize(params['job'], app)
         | 
| 36 | 
            +
                    validate_job!(job)
         | 
| 31 37 | 
             
                    job.validate_sha!(params['sha']) if protect_from_dos_attacks
         | 
| 32 38 | 
             
                    response = Response.new(job, env)
         | 
| 33 39 | 
             
                    catch(:halt) do
         | 
| @@ -39,13 +45,16 @@ module Dragonfly | |
| 39 45 | 
             
                  else
         | 
| 40 46 | 
             
                    [404, {'Content-Type' => 'text/plain', 'X-Cascade' => 'pass'}, ['Not found']]
         | 
| 41 47 | 
             
                  end
         | 
| 42 | 
            -
                rescue Serializer::BadString, Job::InvalidArray => e
         | 
| 43 | 
            -
                  log.warn(e.message)
         | 
| 44 | 
            -
                  [404, {'Content-Type' => 'text/plain'}, ['Not found']]
         | 
| 45 48 | 
             
                rescue Job::NoSHAGiven => e
         | 
| 46 49 | 
             
                  [400, {"Content-Type" => 'text/plain'}, ["You need to give a SHA parameter"]]
         | 
| 47 50 | 
             
                rescue Job::IncorrectSHA => e
         | 
| 48 51 | 
             
                  [400, {"Content-Type" => 'text/plain'}, ["The SHA parameter you gave (#{e}) is incorrect"]]
         | 
| 52 | 
            +
                rescue JobNotAllowed => e
         | 
| 53 | 
            +
                  log.warn(e.message)
         | 
| 54 | 
            +
                  [403, {"Content-Type" => 'text/plain'}, ["Forbidden"]]
         | 
| 55 | 
            +
                rescue Serializer::BadString, Job::InvalidArray => e
         | 
| 56 | 
            +
                  log.warn(e.message)
         | 
| 57 | 
            +
                  [404, {'Content-Type' => 'text/plain'}, ['Not found']]
         | 
| 49 58 | 
             
                end
         | 
| 50 59 |  | 
| 51 60 | 
             
                def url_for(job, opts={})
         | 
| @@ -99,5 +108,12 @@ module Dragonfly | |
| 99 108 | 
             
                  ]
         | 
| 100 109 | 
             
                end
         | 
| 101 110 |  | 
| 111 | 
            +
                def validate_job!(job)
         | 
| 112 | 
            +
                  if job.fetch_file_step && !allow_fetch_file ||
         | 
| 113 | 
            +
                     job.fetch_url_step && !allow_fetch_url
         | 
| 114 | 
            +
                    raise JobNotAllowed, "Dragonfly Server doesn't allow requesting job with steps #{job.steps.inspect}"
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
             | 
| 102 118 | 
             
              end
         | 
| 103 119 | 
             
            end
         | 
    
        data/spec/dragonfly/app_spec.rb
    CHANGED
    
    | @@ -132,4 +132,24 @@ describe Dragonfly::App do | |
| 132 132 | 
             
                end
         | 
| 133 133 | 
             
              end
         | 
| 134 134 |  | 
| 135 | 
            +
              describe "reflection methods" do
         | 
| 136 | 
            +
                before(:each) do
         | 
| 137 | 
            +
                  @app = test_app.configure do |c|
         | 
| 138 | 
            +
                    c.processor.add(:milk){}
         | 
| 139 | 
            +
                    c.generator.add(:butter){}
         | 
| 140 | 
            +
                    c.job(:bacon){}
         | 
| 141 | 
            +
                  end
         | 
| 142 | 
            +
                  
         | 
| 143 | 
            +
                end
         | 
| 144 | 
            +
                it "should return processor methods" do
         | 
| 145 | 
            +
                  @app.processor_methods.should == [:milk]
         | 
| 146 | 
            +
                end
         | 
| 147 | 
            +
                it "should return generator methods" do
         | 
| 148 | 
            +
                  @app.generator_methods.should == [:butter]
         | 
| 149 | 
            +
                end
         | 
| 150 | 
            +
                it "should return job methods" do
         | 
| 151 | 
            +
                  @app.job_methods.should == [:bacon]
         | 
| 152 | 
            +
                end
         | 
| 153 | 
            +
              end
         | 
| 154 | 
            +
             | 
| 135 155 | 
             
            end
         | 
| @@ -168,10 +168,16 @@ describe Dragonfly::DataStorage::FileDataStore do | |
| 168 168 | 
             
                  meta.should == {:dog => 'food'}
         | 
| 169 169 | 
             
                end
         | 
| 170 170 |  | 
| 171 | 
            -
                it "should raise  | 
| 171 | 
            +
                it "should raise a BadUID error if the file path has ../ in it" do
         | 
| 172 172 | 
             
                  expect{
         | 
| 173 173 | 
             
                    @data_store.retrieve('jelly_beans/../are/good')
         | 
| 174 | 
            -
                  }.to raise_error(Dragonfly::DataStorage:: | 
| 174 | 
            +
                  }.to raise_error(Dragonfly::DataStorage::BadUID)
         | 
| 175 | 
            +
                end
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                it "should not raise a BadUID error if the file path has .. but not ../ in it" do
         | 
| 178 | 
            +
                  expect{
         | 
| 179 | 
            +
                    @data_store.retrieve('jelly_beans..good')
         | 
| 180 | 
            +
                  }.to raise_error(Dragonfly::DataStorage::DataNotFound)
         | 
| 175 181 | 
             
                end
         | 
| 176 182 | 
             
              end
         | 
| 177 183 |  | 
| @@ -199,10 +205,10 @@ describe Dragonfly::DataStorage::FileDataStore do | |
| 199 205 | 
             
                  File.exist?("#{@data_store.root_path}/#{uid}.extra").should be_false
         | 
| 200 206 | 
             
                end
         | 
| 201 207 |  | 
| 202 | 
            -
                it "should raise an error if the file path has  | 
| 208 | 
            +
                it "should raise an error if the file path has ../ in it" do
         | 
| 203 209 | 
             
                  expect{
         | 
| 204 210 | 
             
                    @data_store.destroy('jelly_beans/../are/good')
         | 
| 205 | 
            -
                  }.to raise_error(Dragonfly::DataStorage:: | 
| 211 | 
            +
                  }.to raise_error(Dragonfly::DataStorage::BadUID)
         | 
| 206 212 | 
             
                end
         | 
| 207 213 | 
             
              end
         | 
| 208 214 |  | 
| @@ -89,25 +89,40 @@ describe Dragonfly::DataStorage::S3DataStore do | |
| 89 89 | 
             
                  uid = @data_store.store(temp_object)
         | 
| 90 90 | 
             
                  @data_store.retrieve(uid).first.should == "gollum"
         | 
| 91 91 | 
             
                end
         | 
| 92 | 
            -
              end
         | 
| 93 | 
            -
             | 
| 94 | 
            -
              if enabled # Fog.mock! doesn't work consistently with real one here
         | 
| 95 92 |  | 
| 96 | 
            -
                 | 
| 97 | 
            -
                   | 
| 98 | 
            -
                     | 
| 93 | 
            +
                if enabled # Fog.mock! doesn't act consistently here
         | 
| 94 | 
            +
                  it "should reset the connection and try again if Fog throws a socket EOFError" do
         | 
| 95 | 
            +
                    temp_object = Dragonfly::TempObject.new('gollum')
         | 
| 96 | 
            +
                    @data_store.storage.should_receive(:put_object).exactly(:once).and_raise(Excon::Errors::SocketError.new(EOFError.new))
         | 
| 97 | 
            +
                    @data_store.storage.should_receive(:put_object).with(BUCKET_NAME, anything, anything, hash_including)
         | 
| 98 | 
            +
                    @data_store.store(temp_object)
         | 
| 99 99 | 
             
                  end
         | 
| 100 | 
            -
             | 
| 101 | 
            -
             | 
| 102 | 
            -
                     | 
| 103 | 
            -
                     | 
| 104 | 
            -
             | 
| 105 | 
            -
                     | 
| 100 | 
            +
             | 
| 101 | 
            +
                  it "should just let it raise if Fog throws a socket EOFError again" do
         | 
| 102 | 
            +
                    temp_object = Dragonfly::TempObject.new('gollum')
         | 
| 103 | 
            +
                    @data_store.storage.should_receive(:put_object).and_raise(Excon::Errors::SocketError.new(EOFError.new))
         | 
| 104 | 
            +
                    @data_store.storage.should_receive(:put_object).and_raise(Excon::Errors::SocketError.new(EOFError.new))
         | 
| 105 | 
            +
                    expect{
         | 
| 106 | 
            +
                      @data_store.store(temp_object)
         | 
| 107 | 
            +
                    }.to raise_error(Excon::Errors::SocketError)
         | 
| 106 108 | 
             
                  end
         | 
| 107 109 | 
             
                end
         | 
| 108 | 
            -
                
         | 
| 109 110 | 
             
              end
         | 
| 110 111 |  | 
| 112 | 
            +
              # Doesn't appear to raise anything right now
         | 
| 113 | 
            +
              # describe "destroy" do
         | 
| 114 | 
            +
              #   before(:each) do
         | 
| 115 | 
            +
              #     @temp_object = Dragonfly::TempObject.new('gollum')
         | 
| 116 | 
            +
              #   end
         | 
| 117 | 
            +
              #   it "should raise an error if the data doesn't exist on destroy" do
         | 
| 118 | 
            +
              #     uid = @data_store.store(@temp_object)
         | 
| 119 | 
            +
              #     @data_store.destroy(uid)
         | 
| 120 | 
            +
              #     lambda{
         | 
| 121 | 
            +
              #       @data_store.destroy(uid)
         | 
| 122 | 
            +
              #     }.should raise_error(Dragonfly::DataStorage::DataNotFound)
         | 
| 123 | 
            +
              #   end
         | 
| 124 | 
            +
              # end
         | 
| 125 | 
            +
             | 
| 111 126 | 
             
              describe "domain" do
         | 
| 112 127 | 
             
                it "should default to the US" do
         | 
| 113 128 | 
             
                  @data_store.region = nil
         | 
| @@ -183,21 +198,21 @@ describe Dragonfly::DataStorage::S3DataStore do | |
| 183 198 | 
             
                end
         | 
| 184 199 |  | 
| 185 200 | 
             
                it "should allow configuring globally" do
         | 
| 186 | 
            -
                  @data_store.storage.should_receive(:put_object).with( | 
| 201 | 
            +
                  @data_store.storage.should_receive(:put_object).with(BUCKET_NAME, anything, anything,
         | 
| 187 202 | 
             
                    hash_including('x-amz-foo' => 'biscuithead')
         | 
| 188 203 | 
             
                  )
         | 
| 189 204 | 
             
                  @data_store.store(@temp_object)
         | 
| 190 205 | 
             
                end
         | 
| 191 206 |  | 
| 192 207 | 
             
                it "should allow adding per-store" do
         | 
| 193 | 
            -
                  @data_store.storage.should_receive(:put_object).with( | 
| 208 | 
            +
                  @data_store.storage.should_receive(:put_object).with(BUCKET_NAME, anything, anything,
         | 
| 194 209 | 
             
                    hash_including('x-amz-foo' => 'biscuithead', 'hello' => 'there')
         | 
| 195 210 | 
             
                  )
         | 
| 196 211 | 
             
                  @data_store.store(@temp_object, :headers => {'hello' => 'there'})
         | 
| 197 212 | 
             
                end
         | 
| 198 213 |  | 
| 199 214 | 
             
                it "should let the per-store one take precedence" do
         | 
| 200 | 
            -
                  @data_store.storage.should_receive(:put_object).with( | 
| 215 | 
            +
                  @data_store.storage.should_receive(:put_object).with(BUCKET_NAME, anything, anything,
         | 
| 201 216 | 
             
                    hash_including('x-amz-foo' => 'override!')
         | 
| 202 217 | 
             
                  )
         | 
| 203 218 | 
             
                  @data_store.store(@temp_object, :headers => {'x-amz-foo' => 'override!'})
         | 
| @@ -103,7 +103,7 @@ describe Dragonfly::ImageMagick::Processor do | |
| 103 103 | 
             
                it "should take into account the gravity given" do
         | 
| 104 104 | 
             
                  image1 = @processor.crop(@image, :width => '10', :height => '10', :gravity => 'nw')
         | 
| 105 105 | 
             
                  image2 = @processor.crop(@image, :width => '10', :height => '10', :gravity => 'se')
         | 
| 106 | 
            -
                  image1.should_not  | 
| 106 | 
            +
                  image1.should_not equal_image(image2)
         | 
| 107 107 | 
             
                end
         | 
| 108 108 |  | 
| 109 109 | 
             
                it "should clip bits of the image outside of the requested crop area when not nw gravity" do
         | 
| @@ -147,12 +147,25 @@ describe Dragonfly::ImageMagick::Processor do | |
| 147 147 | 
             
                  image.should have_height(355)
         | 
| 148 148 | 
             
                end
         | 
| 149 149 |  | 
| 150 | 
            +
                it "should do nothing if called without width and height" do
         | 
| 151 | 
            +
                  image = @processor.resize_and_crop(@image)
         | 
| 152 | 
            +
                  image.should have_width(280)
         | 
| 153 | 
            +
                  image.should have_height(355)
         | 
| 154 | 
            +
                  image.should eq @image
         | 
| 155 | 
            +
                end
         | 
| 156 | 
            +
             | 
| 150 157 | 
             
                it "should crop to the correct dimensions" do
         | 
| 151 158 | 
             
                  image = @processor.resize_and_crop(@image, :width => '100', :height => '100')
         | 
| 152 159 | 
             
                  image.should have_width(100)
         | 
| 153 160 | 
             
                  image.should have_height(100)
         | 
| 154 161 | 
             
                end
         | 
| 155 162 |  | 
| 163 | 
            +
                it "should actually resize before cropping" do
         | 
| 164 | 
            +
                  image1 = @processor.resize_and_crop(@image, :width => '100', :height => '100')
         | 
| 165 | 
            +
                  image2 = @processor.crop(@image, :width => '100', :height => '100', :gravity => 'c')
         | 
| 166 | 
            +
                  image1.should_not equal_image(image2)
         | 
| 167 | 
            +
                end
         | 
| 168 | 
            +
             | 
| 156 169 | 
             
                it "should allow cropping in one dimension" do
         | 
| 157 170 | 
             
                  image = @processor.resize_and_crop(@image, :width => '100')
         | 
| 158 171 | 
             
                  image.should have_width(100)
         | 
| @@ -162,7 +175,7 @@ describe Dragonfly::ImageMagick::Processor do | |
| 162 175 | 
             
                it "should take into account the gravity given" do
         | 
| 163 176 | 
             
                  image1 = @processor.resize_and_crop(@image, :width => '10', :height => '10', :gravity => 'nw')
         | 
| 164 177 | 
             
                  image2 = @processor.resize_and_crop(@image, :width => '10', :height => '10', :gravity => 'se')
         | 
| 165 | 
            -
                  image1.should_not  | 
| 178 | 
            +
                  image1.should_not equal_image(image2)
         | 
| 166 179 | 
             
                end
         | 
| 167 180 |  | 
| 168 181 | 
             
              end
         | 
| @@ -32,4 +32,26 @@ describe Dragonfly::JobDefinitions do | |
| 32 32 |  | 
| 33 33 | 
             
              end
         | 
| 34 34 |  | 
| 35 | 
            +
              
         | 
| 36 | 
            +
              describe "#definition_names" do
         | 
| 37 | 
            +
                
         | 
| 38 | 
            +
                before(:each) do
         | 
| 39 | 
            +
                  @job_definitions = Dragonfly::JobDefinitions.new
         | 
| 40 | 
            +
                  @object = Object.new
         | 
| 41 | 
            +
                  @object.extend @job_definitions
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
                
         | 
| 44 | 
            +
                it "should provide an empty list when no jobs have been defined" do
         | 
| 45 | 
            +
                  @job_definitions.definition_names.should == []
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
                
         | 
| 48 | 
            +
                it "should contain the job name when one is defined" do
         | 
| 49 | 
            +
                  @job_definitions.add :foo do |size|
         | 
| 50 | 
            +
                    process :thumb, size
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
                  @job_definitions.definition_names.should eq [:foo]
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
                
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
              
         | 
| 35 57 | 
             
            end
         | 
| @@ -86,6 +86,12 @@ describe Dragonfly::JobEndpoint do | |
| 86 86 | 
             
                response.status.should == 404
         | 
| 87 87 | 
             
              end
         | 
| 88 88 |  | 
| 89 | 
            +
              it "should return a 404 if the datastore raises bad uid" do
         | 
| 90 | 
            +
                @job.should_receive(:apply).and_raise(Dragonfly::DataStorage::BadUID)
         | 
| 91 | 
            +
                response = make_request(@job)
         | 
| 92 | 
            +
                response.status.should == 404
         | 
| 93 | 
            +
              end
         | 
| 94 | 
            +
             | 
| 89 95 | 
             
              describe "ETag" do
         | 
| 90 96 | 
             
                it "should return an ETag" do
         | 
| 91 97 | 
             
                  response = make_request(@job)
         | 
| @@ -22,7 +22,7 @@ describe Dragonfly::Server do | |
| 22 22 | 
             
                  @app.destroy(@uid)
         | 
| 23 23 | 
             
                end
         | 
| 24 24 |  | 
| 25 | 
            -
                describe "successful  | 
| 25 | 
            +
                describe "successful requests" do
         | 
| 26 26 | 
             
                  before(:each) do
         | 
| 27 27 | 
             
                    @server.url_format = '/media/:job/:name.:format'
         | 
| 28 28 | 
             
                  end
         | 
| @@ -59,74 +59,90 @@ describe Dragonfly::Server do | |
| 59 59 | 
             
                    response.status.should == 200
         | 
| 60 60 | 
             
                    response.body.should == 'eggs'
         | 
| 61 61 | 
             
                  end
         | 
| 62 | 
            -
             | 
| 62 | 
            +
                  
         | 
| 63 | 
            +
                  it "should return a cacheable response" do
         | 
| 64 | 
            +
                    url = "/media/#{@job.serialize}"
         | 
| 65 | 
            +
                    cache = Rack::Cache.new(@server, :entitystore => 'heap:/')
         | 
| 66 | 
            +
                    response = request(cache, url)
         | 
| 67 | 
            +
                    response.status.should == 200
         | 
| 68 | 
            +
                    response.headers['X-Rack-Cache'].should == "miss, store"
         | 
| 69 | 
            +
                    response = request(cache, url)
         | 
| 70 | 
            +
                    response.status.should == 200
         | 
| 71 | 
            +
                    response.headers['X-Rack-Cache'].should == "fresh"
         | 
| 72 | 
            +
                  end
         | 
| 63 73 |  | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 74 | 
            +
                  it "should return successfully even if the job is in the query string" do
         | 
| 75 | 
            +
                    @server.url_format = '/'
         | 
| 76 | 
            +
                    url = "/?job=#{@job.serialize}"
         | 
| 77 | 
            +
                    response = request(@server, url)
         | 
| 78 | 
            +
                    response.status.should == 200
         | 
| 79 | 
            +
                    response.body.should == 'HELLO THERE'
         | 
| 80 | 
            +
                  end
         | 
| 70 81 | 
             
                end
         | 
| 71 82 |  | 
| 72 | 
            -
                 | 
| 73 | 
            -
                   | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 83 | 
            +
                describe "unsuccessful requests" do
         | 
| 84 | 
            +
                  it "should return a 400 if no sha given but protection on" do
         | 
| 85 | 
            +
                    @server.protect_from_dos_attacks = true
         | 
| 86 | 
            +
                    url = "/media/#{@job.serialize}"
         | 
| 87 | 
            +
                    response = request(@server, url)
         | 
| 88 | 
            +
                    response.status.should == 400
         | 
| 89 | 
            +
                  end
         | 
| 78 90 |  | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
                  response = request(@server, url)
         | 
| 83 | 
            -
                  response.status.should == 400
         | 
| 84 | 
            -
                end
         | 
| 85 | 
            -
             | 
| 86 | 
            -
                ['/media', '/media/'].each do |url|
         | 
| 87 | 
            -
                  it "should return a 404 when no job given, e.g. #{url.inspect}" do
         | 
| 91 | 
            +
                  it "should return a 400 if wrong sha given and protection on" do
         | 
| 92 | 
            +
                    @server.protect_from_dos_attacks = true
         | 
| 93 | 
            +
                    url = "/media/#{@job.serialize}?sha=asdfs"
         | 
| 88 94 | 
             
                    response = request(@server, url)
         | 
| 95 | 
            +
                    response.status.should == 400
         | 
| 96 | 
            +
                  end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                  ['/media', '/media/'].each do |url|
         | 
| 99 | 
            +
                    it "should return a 404 when no job given, e.g. #{url.inspect}" do
         | 
| 100 | 
            +
                      response = request(@server, url)
         | 
| 101 | 
            +
                      response.status.should == 404
         | 
| 102 | 
            +
                      response.body.should == 'Not found'
         | 
| 103 | 
            +
                      response.content_type.should == 'text/plain'
         | 
| 104 | 
            +
                      response.headers['X-Cascade'].should == 'pass'
         | 
| 105 | 
            +
                    end
         | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
              
         | 
| 108 | 
            +
                  it "should return a 404 when the url matches but doesn't correspond to a job" do
         | 
| 109 | 
            +
                    response = request(@server, '/media/sadhfasdfdsfsdf')
         | 
| 89 110 | 
             
                    response.status.should == 404
         | 
| 90 111 | 
             
                    response.body.should == 'Not found'
         | 
| 91 112 | 
             
                    response.content_type.should == 'text/plain'
         | 
| 92 | 
            -
                    response.headers['X-Cascade'].should  | 
| 113 | 
            +
                    response.headers['X-Cascade'].should be_nil
         | 
| 93 114 | 
             
                  end
         | 
| 94 | 
            -
                end
         | 
| 95 | 
            -
              
         | 
| 96 | 
            -
                it "should return a 404 when the url matches but doesn't correspond to a job" do
         | 
| 97 | 
            -
                  response = request(@server, '/media/sadhfasdfdsfsdf')
         | 
| 98 | 
            -
                  response.status.should == 404
         | 
| 99 | 
            -
                  response.body.should == 'Not found'
         | 
| 100 | 
            -
                  response.content_type.should == 'text/plain'
         | 
| 101 | 
            -
                  response.headers['X-Cascade'].should be_nil
         | 
| 102 | 
            -
                end
         | 
| 103 115 |  | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 109 | 
            -
             | 
| 110 | 
            -
             | 
| 116 | 
            +
                  it "should return a 404 when the url isn't known at all" do
         | 
| 117 | 
            +
                    response = request(@server, '/jfasd/dsfa')
         | 
| 118 | 
            +
                    response.status.should == 404
         | 
| 119 | 
            +
                    response.body.should == 'Not found'
         | 
| 120 | 
            +
                    response.content_type.should == 'text/plain'
         | 
| 121 | 
            +
                    response.headers['X-Cascade'].should == 'pass'
         | 
| 122 | 
            +
                  end
         | 
| 111 123 |  | 
| 112 | 
            -
             | 
| 113 | 
            -
             | 
| 114 | 
            -
             | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 122 | 
            -
             | 
| 123 | 
            -
             | 
| 124 | 
            -
             | 
| 125 | 
            -
             | 
| 126 | 
            -
                   | 
| 127 | 
            -
                   | 
| 128 | 
            -
                   | 
| 129 | 
            -
             | 
| 124 | 
            +
                  it "should return a 404 when the url is a well-encoded but bad array" do
         | 
| 125 | 
            +
                    url = "/media/#{Dragonfly::Serializer.marshal_encode([[:egg, {:some => 'args'}]])}"
         | 
| 126 | 
            +
                    response = request(@server, url)
         | 
| 127 | 
            +
                    response.status.should == 404
         | 
| 128 | 
            +
                    response.body.should == 'Not found'
         | 
| 129 | 
            +
                    response.content_type.should == 'text/plain'
         | 
| 130 | 
            +
                    response.headers['X-Cascade'].should be_nil
         | 
| 131 | 
            +
                  end
         | 
| 132 | 
            +
                  
         | 
| 133 | 
            +
                  it "should return a 403 Forbidden when someone uses fetch_file" do
         | 
| 134 | 
            +
                    response = request(@server, "/media/#{@app.fetch_file('/some/file.txt').serialize}")
         | 
| 135 | 
            +
                    response.status.should == 403
         | 
| 136 | 
            +
                    response.body.should == 'Forbidden'
         | 
| 137 | 
            +
                    response.content_type.should == 'text/plain'
         | 
| 138 | 
            +
                  end
         | 
| 139 | 
            +
                  
         | 
| 140 | 
            +
                  it "should return a 403 Forbidden when someone uses fetch_url" do
         | 
| 141 | 
            +
                    response = request(@server, "/media/#{@app.fetch_url('some.url').serialize}")
         | 
| 142 | 
            +
                    response.status.should == 403
         | 
| 143 | 
            +
                    response.body.should == 'Forbidden'
         | 
| 144 | 
            +
                    response.content_type.should == 'text/plain'
         | 
| 145 | 
            +
                  end
         | 
| 130 146 | 
             
                end
         | 
| 131 147 |  | 
| 132 148 | 
             
              end
         | 
| @@ -45,3 +45,13 @@ RSpec::Matchers.define :have_size do |size| | |
| 45 45 | 
             
                image_properties(given)[:size].should == size
         | 
| 46 46 | 
             
              end
         | 
| 47 47 | 
             
            end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            RSpec::Matchers.define :equal_image do |other|
         | 
| 50 | 
            +
              match do |given|
         | 
| 51 | 
            +
                image_data = given.open.read
         | 
| 52 | 
            +
                other_image_data = other.open.read
         | 
| 53 | 
            +
                given.close
         | 
| 54 | 
            +
                other.close
         | 
| 55 | 
            +
                image_data == other_image_data
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            require "rubygems"
         | 
| 2 | 
            +
            require "bundler/setup"
         | 
| 3 | 
            +
            $:.unshift(File.expand_path('../../lib', __FILE__))
         | 
| 4 | 
            +
            require 'dragonfly'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            ROOT = (File.expand_path('../..', __FILE__))
         | 
| 7 | 
            +
            APP = Dragonfly[:images].configure_with(:imagemagick).configure do |c|
         | 
| 8 | 
            +
              c.url_format = '/images/:job'
         | 
| 9 | 
            +
              c.allow_fetch_file = true
         | 
| 10 | 
            +
            end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            def row(geometry)
         | 
| 13 | 
            +
              image = APP.fetch_file(ROOT + '/samples/beach.png').thumb('100x100#')
         | 
| 14 | 
            +
              %(<tr>
         | 
| 15 | 
            +
                <th>#{geometry}</th>
         | 
| 16 | 
            +
                <th><img src="#{image.url}" /></th>
         | 
| 17 | 
            +
                <th><img src="#{image.thumb(geometry).url}" /></th>
         | 
| 18 | 
            +
              </tr>)
         | 
| 19 | 
            +
            end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            use Dragonfly::Middleware, :images
         | 
| 22 | 
            +
            run proc{[
         | 
| 23 | 
            +
              200,
         | 
| 24 | 
            +
              {'Content-Type' => 'text/html'},
         | 
| 25 | 
            +
              [%(
         | 
| 26 | 
            +
                <table>
         | 
| 27 | 
            +
                  <tr>
         | 
| 28 | 
            +
                    <th>Geometry</th>
         | 
| 29 | 
            +
                    <th>Original(100x100)</th>
         | 
| 30 | 
            +
                    <th>Thumb</th>
         | 
| 31 | 
            +
                  </tr>
         | 
| 32 | 
            +
                #{[
         | 
| 33 | 
            +
                  row('80x60'),
         | 
| 34 | 
            +
                  row('80x60!'),
         | 
| 35 | 
            +
                  row('80x'),
         | 
| 36 | 
            +
                  row('x60'),
         | 
| 37 | 
            +
                  row('80x60>'),
         | 
| 38 | 
            +
                  row('80x60<'),
         | 
| 39 | 
            +
                  row('50x50%'),
         | 
| 40 | 
            +
                  row('80x60^'),
         | 
| 41 | 
            +
                  row('2000@'),
         | 
| 42 | 
            +
                  row('80x60#'),
         | 
| 43 | 
            +
                  row('80x60#ne'),
         | 
| 44 | 
            +
                  row('80x60se'),
         | 
| 45 | 
            +
                  row('80x60+5+35')
         | 
| 46 | 
            +
                ].join}
         | 
| 47 | 
            +
                </table>
         | 
| 48 | 
            +
              )]
         | 
| 49 | 
            +
            ]}
         | 
    
        metadata
    CHANGED
    
    | @@ -2,7 +2,7 @@ | |
| 2 2 | 
             
            name: dragonfly
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version 
         | 
| 4 4 | 
             
              prerelease: 
         | 
| 5 | 
            -
              version: 0.9. | 
| 5 | 
            +
              version: 0.9.1
         | 
| 6 6 | 
             
            platform: ruby
         | 
| 7 7 | 
             
            authors: 
         | 
| 8 8 | 
             
            - Mark Evans
         | 
| @@ -10,7 +10,7 @@ autorequire: | |
| 10 10 | 
             
            bindir: bin
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 12 |  | 
| 13 | 
            -
            date: 2011- | 
| 13 | 
            +
            date: 2011-05-11 00:00:00 +01:00
         | 
| 14 14 | 
             
            default_executable: 
         | 
| 15 15 | 
             
            dependencies: 
         | 
| 16 16 | 
             
            - !ruby/object:Gem::Dependency 
         | 
| @@ -388,6 +388,7 @@ files: | |
| 388 388 | 
             
            - spec/support/argument_matchers.rb
         | 
| 389 389 | 
             
            - spec/support/image_matchers.rb
         | 
| 390 390 | 
             
            - spec/support/simple_matchers.rb
         | 
| 391 | 
            +
            - spec/test_imagemagick.ru
         | 
| 391 392 | 
             
            - yard/handlers/configurable_attr_handler.rb
         | 
| 392 393 | 
             
            - yard/setup.rb
         | 
| 393 394 | 
             
            - yard/templates/default/fulldoc/html/css/common.css
         | 
| @@ -408,7 +409,7 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 408 409 | 
             
              requirements: 
         | 
| 409 410 | 
             
              - - ">="
         | 
| 410 411 | 
             
                - !ruby/object:Gem::Version 
         | 
| 411 | 
            -
                  hash:  | 
| 412 | 
            +
                  hash: 3227660044973148793
         | 
| 412 413 | 
             
                  segments: 
         | 
| 413 414 | 
             
                  - 0
         | 
| 414 415 | 
             
                  version: "0"
         |