Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redirect to URIs when the file extension is missing. #258

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion app/controllers/iiif_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions app/models/stacks_identifier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
92 changes: 58 additions & 34 deletions spec/controllers/iiif_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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 '<http://iiif.io/api/image/2/level2.json>;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 '<http://iiif.io/api/image/2/level2.json>;rel="profile"'
end
end
end
end
4 changes: 2 additions & 2 deletions spec/requests/iiif_auth_request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
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' }
let(:quality) { 'default' }
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
Expand Down
12 changes: 6 additions & 6 deletions spec/requests/iiif_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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
2 changes: 1 addition & 1 deletion spec/requests/remote_iiif_image_delivery_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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