diff --git a/app/controllers/iiif_controller.rb b/app/controllers/iiif_controller.rb index 28506185..43950aa3 100644 --- a/app/controllers/iiif_controller.rb +++ b/app/controllers/iiif_controller.rb @@ -4,6 +4,7 @@ # API for delivering IIIF-compatible images and image tiles class IiifController < ApplicationController before_action :ensure_valid_identifier + before_action :ensure_identifier_has_extension before_action :add_iiif_profile_header # Follow the interface of Riiif @@ -137,7 +138,7 @@ def transformation end def stacks_identifier - @stacks_identifier ||= StacksIdentifier.new(escaped_identifier.sub(/^degraded_/, '') + '.jp2') + @stacks_identifier ||= StacksIdentifier.new(escaped_identifier.sub(/^degraded_/, '')) end def canonical_params @@ -168,6 +169,14 @@ def degraded? !can?(:download, current_image) && current_image.readable_by?(stanford_generic_user) end + # Stacks used to allow requesting images without an extension. However this was + # different than how requests for media or file downloads worked. Now we prefer the + # full file name for consistency across all endpoints. + def ensure_identifier_has_extension + return if stacks_identifier.has_file_name_extension? + redirect_to identifier: stacks_identifier.to_s + '.jp2' # halts the request cycle + end + def ensure_valid_identifier raise ActionController::RoutingError, "invalid identifer" unless stacks_identifier.valid? end diff --git a/app/models/stacks_identifier.rb b/app/models/stacks_identifier.rb index 0a753f68..a2c4971f 100644 --- a/app/models/stacks_identifier.rb +++ b/app/models/stacks_identifier.rb @@ -32,6 +32,10 @@ def file_name_without_ext File.basename(file_name, '.*') end + def has_file_name_extension? + File.extname(file_name).present? + end + def treeified_path File.join(druid_parts[1..4], file_name) end diff --git a/config/routes.rb b/config/routes.rb index f41a6b4a..f99fb8e3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -22,7 +22,7 @@ constraints identifier: %r{[^/]+}, size: %r{[^/]+} do get '/image/iiif/:identifier', to: redirect('/image/iiif/%{identifier}/info.json', status: 303), as: :iiif_base - get '/image/iiif/:identifier/:region/:size/:rotation/:quality' => 'iiif#show', as: :iiif + get '/image/iiif/:identifier/:region/:size/:rotation/:quality.:format' => 'iiif#show', as: :iiif get '/image/iiif/:identifier/info.json' => 'iiif#metadata', as: :iiif_metadata match '/image/iiif/:identifier/info.json' => 'iiif#metadata_options', via: [:options] get '/image/iiif/app/:identifier/:region/:size/:rotation/:quality' => 'webauth#login_iiif' diff --git a/spec/controllers/iiif_controller_spec.rb b/spec/controllers/iiif_controller_spec.rb index 52fcdeaa..0c672a9c 100644 --- a/spec/controllers/iiif_controller_spec.rb +++ b/spec/controllers/iiif_controller_spec.rb @@ -2,7 +2,7 @@ RSpec.describe IiifController do describe '#show' do - let(:identifier) { 'nr349ct7889%2Fnr349ct7889_00_0001' } + let(:identifier) { 'nr349ct7889%2Fnr349ct7889_00_0001.jp2' } let(:image_response) { StringIO.new } let(:projection) { instance_double(Projection, response: image_response, valid?: true) } let(:image) do @@ -51,12 +51,22 @@ end end + context "when the file name doesn't have an extension" do + let(:identifier) { 'nr349ct7889%2Fnr349ct7889_00_0001' } + it 'redirects to the url with an extension' do + subject + expect(response).to redirect_to( + 'http://test.host/image/iiif/nr349ct7889%252Fnr349ct7889_00_0001.jp2/0,640,2552,2552/100,100/0/default.jpg' + ) + end + end + it 'loads the image' do subject expect(assigns(:image)).to eq image expect(StacksImage).to have_received(:new).with( id: StacksIdentifier.new(druid: "nr349ct7889", file_name: 'nr349ct7889_00_0001.jp2'), - canonical_url: "http://test.host/image/iiif/nr349ct7889%252Fnr349ct7889_00_0001" + canonical_url: "http://test.host/image/iiif/nr349ct7889%252Fnr349ct7889_00_0001.jp2" ) end @@ -93,41 +103,55 @@ end describe '#metadata' do - let(:image) do - instance_double(StacksImage, - valid?: true, - exist?: true, - etag: nil, - mtime: nil) + context "when the file name doesn't have an extension" do + let(:identifier) { 'nr349ct7889%2Fnr349ct7889_00_0001' } + + it 'redirects to the url with an extension' do + get :metadata, params: { identifier: identifier, format: :json } + expect(response).to redirect_to( + 'http://test.host/image/iiif/nr349ct7889%252Fnr349ct7889_00_0001.jp2/info.json' + ) + end end - let(:anon_user) { instance_double(User) } - before do - # for the cache headers - allow(controller).to receive(:anonymous_locatable_user).and_return(anon_user) - allow(image).to receive(:accessable_by?).with(anon_user).and_return(false) - # for info.json generation - allow(controller.send(:anonymous_ability)).to receive(:can?).with(:download, image).and_return(false) - # for current_image - allow(controller).to receive(:can?).with(:download, image).and_return(true) - # for degraded? - allow(controller).to receive(:can?).with(:access, image).and_return(true) - # In the metadata method itself - allow(controller).to receive(:authorize!).with(:read_metadata, image).and_return(true) - - allow(StacksImage).to receive(:new).and_return(image) - - allow(IiifInfoService).to receive(:info) - .with(image, false, controller) - .and_return(height: '999') - end + context "on the happy path" do + let(:identifier) { 'nr349ct7889%2Fnr349ct7889_00_0001.jp2' } + let(:image) do + instance_double(StacksImage, + valid?: true, + exist?: true, + etag: nil, + mtime: nil) + end + let(:anon_user) { instance_double(User) } + + before do + # for the cache headers + allow(controller).to receive(:anonymous_locatable_user).and_return(anon_user) + allow(image).to receive(:accessable_by?).with(anon_user).and_return(false) + # for info.json generation + allow(controller.send(:anonymous_ability)).to receive(:can?).with(:download, image).and_return(false) + # for current_image + allow(controller).to receive(:can?).with(:download, image).and_return(true) + # for degraded? + allow(controller).to receive(:can?).with(:access, image).and_return(true) + # In the metadata method itself + allow(controller).to receive(:authorize!).with(:read_metadata, image).and_return(true) + + allow(StacksImage).to receive(:new).and_return(image) + + allow(IiifInfoService).to receive(:info) + .with(image, false, controller) + .and_return(height: '999') + end - it 'provides iiif info.json responses' do - get :metadata, params: { identifier: 'nr349ct7889%2Fnr349ct7889_00_0001', format: :json } - expect(controller.content_type).to eq 'application/json' - expect(response).to be_successful - expect(controller.response_body.first).to eq "{\n \"height\": \"999\"\n}" - expect(controller.headers['Link']).to eq ';rel="profile"' + it 'provides iiif info.json responses' do + get :metadata, params: { identifier: identifier, format: :json } + expect(controller.content_type).to eq 'application/json' + expect(response).to be_successful + expect(controller.response_body.first).to eq "{\n \"height\": \"999\"\n}" + expect(controller.headers['Link']).to eq ';rel="profile"' + end end end end diff --git a/spec/requests/iiif_auth_request_spec.rb b/spec/requests/iiif_auth_request_spec.rb index 24d0892b..eaf76fb2 100644 --- a/spec/requests/iiif_auth_request_spec.rb +++ b/spec/requests/iiif_auth_request_spec.rb @@ -9,7 +9,7 @@ let(:user_webauth_stanford_no_loc) { User.new(webauth_user: true, ldap_groups: %w(stanford:stanford)) } let(:user_webauth_stanford_loc) { User.new(webauth_user: true, ldap_groups: %w(stanford:stanford), ip_address: allowed_loc) } let(:user_webauth_no_stanford_loc) { User.new(webauth_user: true, ip_address: allowed_loc) } - let(:identifier) { StacksIdentifier.new('nr349ct7889%2Fnr349ct7889_00_0001') } + let(:identifier) { StacksIdentifier.new('nr349ct7889%2Fnr349ct7889_00_0001.jp2') } let(:region) { '0,640,2552,2552' } let(:size) { '100,100' } let(:rotation) { '0' } @@ -17,7 +17,7 @@ let(:format) { 'jpg' } let(:params_hash) { { id: identifier, transformation: transformation } } let(:transformation) { IIIF::Image::Transformation.new region: region, size: size, rotation: rotation, quality: quality, format: format } - let(:path) { "/stacks/nr/349/ct/7889/nr349ct7889_00_0001" } + let(:path) { "/stacks/nr/349/ct/7889/nr349ct7889_00_0001.jp2" } let(:perms) { nil } before(:each) do diff --git a/spec/requests/iiif_spec.rb b/spec/requests/iiif_spec.rb index 1805a1b5..35721eea 100644 --- a/spec/requests/iiif_spec.rb +++ b/spec/requests/iiif_spec.rb @@ -38,7 +38,7 @@ end it 'handles JSON-LD requests' do - get '/image/iiif/nr349ct7889%2Fnr349ct7889_00_0001/info.json', headers: { HTTP_ACCEPT: 'application/ld+json' } + get '/image/iiif/nr349ct7889%2Fnr349ct7889_00_0001.jp2/info.json', headers: { HTTP_ACCEPT: 'application/ld+json' } expect(response.content_type).to eq 'application/ld+json' json = JSON.parse(response.body) @@ -51,9 +51,9 @@ end it 'redirects requests to the degraded info.json' do - get '/image/iiif/nr349ct7889%2Fnr349ct7889_00_0001/info.json' + get '/image/iiif/nr349ct7889%2Fnr349ct7889_00_0001.jp2/info.json' expect(response).to have_http_status :redirect - expect(response).to redirect_to('/image/iiif/degraded_nr349ct7889%252Fnr349ct7889_00_0001/info.json') + expect(response).to redirect_to('/image/iiif/degraded_nr349ct7889%252Fnr349ct7889_00_0001.jp2/info.json') expect(response.headers['Cache-Control']).to match(/max-age=0/) end @@ -71,7 +71,7 @@ stub_rights_xml(world_no_download_xml) end it 'serves up regular info.json (no degraded)' do - get '/image/iiif/nr349ct7889%2Fnr349ct7889_00_0001/info.json' + get '/image/iiif/nr349ct7889%2Fnr349ct7889_00_0001.jp2/info.json' expect(response).to have_http_status :ok end end @@ -81,9 +81,9 @@ stub_rights_xml(stanford_only_no_download_xml) end it 'redirects to degraded version' do - get '/image/iiif/nr349ct7889%2Fnr349ct7889_00_0001/info.json' + get '/image/iiif/nr349ct7889%2Fnr349ct7889_00_0001.jp2/info.json' expect(response).to have_http_status :redirect - expect(response).to redirect_to('/image/iiif/degraded_nr349ct7889%252Fnr349ct7889_00_0001/info.json') + expect(response).to redirect_to('/image/iiif/degraded_nr349ct7889%252Fnr349ct7889_00_0001.jp2/info.json') end end end diff --git a/spec/requests/remote_iiif_image_delivery_spec.rb b/spec/requests/remote_iiif_image_delivery_spec.rb index 1ba7c218..bd38e524 100644 --- a/spec/requests/remote_iiif_image_delivery_spec.rb +++ b/spec/requests/remote_iiif_image_delivery_spec.rb @@ -27,7 +27,7 @@ end it 'returns an image' do - get '/image/iiif/nr349ct7889%2Fimage/full/max/0/default.jpg' + get '/image/iiif/nr349ct7889%2Fimage.jp2/full/max/0/default.jpg' expect(response.body).to eq 'image contents' end end