httpimagestore 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -69,9 +69,12 @@ module Configuration
69
69
  attr_reader :image_name
70
70
  attr_reader :path_spec
71
71
 
72
- def initialize(global, image_name, path_spec, matcher)
72
+ def initialize(global, image_name, scheme, host, port, path_spec, matcher)
73
73
  @global = global
74
74
  @image_name = image_name
75
+ @scheme = scheme
76
+ @host = host
77
+ @port = port
75
78
  @path_spec = path_spec
76
79
  inclusion_matcher matcher
77
80
  end
@@ -79,26 +82,42 @@ module Configuration
79
82
  def store_path(request_state)
80
83
  store_path = request_state.images[@image_name].store_path or raise StorePathNotSetForImage.new(@image_name)
81
84
  return store_path unless @path_spec
82
- rendered_path(store_path, request_state)
83
- end
84
85
 
85
- def store_url(request_state)
86
- store_url = request_state.images[@image_name].store_url or raise StoreURLNotSetForImage.new(@image_name)
87
- return store_url unless @path_spec
88
- uri = URI(store_url)
89
- uri.path = '/' + URI.encode(rendered_path(URI.decode(uri.path), request_state))
90
- uri
86
+ locals = {
87
+ image_name: @image_name,
88
+ path: store_path
89
+ }
90
+
91
+ rendered_path(request_state.with_locals(locals))
91
92
  end
92
93
 
93
- private
94
+ def store_url(request_state)
95
+ url = request_state.images[@image_name].store_url or raise StoreURLNotSetForImage.new(@image_name)
96
+ url = url.dup
94
97
 
95
- def rendered_path(store_path, request_state)
96
- path = @global.paths[@path_spec]
97
98
  locals = {
98
- path: store_path,
99
- image_name: @image_name
99
+ image_name: @image_name,
100
+ path: URI.utf_decode(url.path),
101
+ url: url.to_s
100
102
  }
101
- Pathname.new(path.render(request_state.with_locals(locals))).cleanpath.to_s
103
+ locals[:scheme] = url.scheme if url.scheme
104
+ locals[:host] = url.host if url.host
105
+ locals[:port] = url.port if url.port
106
+
107
+ request_state = request_state.with_locals(locals)
108
+
109
+ # optional rewrites
110
+ url.scheme = request_state.render_template(@scheme) if @scheme
111
+ url.host = request_state.render_template(@host) if @host
112
+ url.port = request_state.render_template(@port).to_i if @port
113
+ url.path = URI.encode(rendered_path(request_state)).tap{|path| path.replace('/' + path) if path[0] != '/'} if @path_spec
114
+
115
+ url
116
+ end
117
+
118
+ private
119
+ def rendered_path(request_state)
120
+ Pathname.new(@global.paths[@path_spec].render(request_state)).cleanpath.to_s
102
121
  end
103
122
  end
104
123
 
@@ -106,9 +125,9 @@ module Configuration
106
125
  nodes = node.values.empty? ? node.children : [node]
107
126
  output_specs = nodes.map do |node|
108
127
  image_name = node.grab_values('image name').first
109
- path_spec, if_image_name_on = *node.grab_attributes('path', 'if-image-name-on')
128
+ scheme, host, port, path_spec, if_image_name_on = *node.grab_attributes('scheme', 'host', 'port', 'path', 'if-image-name-on')
110
129
  matcher = InclusionMatcher.new(image_name, if_image_name_on)
111
- OutputSpec.new(configuration.global, image_name, path_spec, matcher)
130
+ OutputSpec.new(configuration.global, image_name, scheme, host, port, path_spec, matcher)
112
131
  end
113
132
 
114
133
  configuration.output and raise StatementCollisionError.new(node, 'output')
@@ -214,5 +233,24 @@ module Configuration
214
233
  end
215
234
  end
216
235
  Handler::register_node_parser OutputStoreURL
236
+
237
+ class OutputStoreURI < OutputMultiBase
238
+ def self.match(node)
239
+ node.name == 'output_store_uri'
240
+ end
241
+
242
+ def realize(request_state)
243
+ urls = @output_specs.select do |output_spec|
244
+ output_spec.included?(request_state)
245
+ end.map do |output_spec|
246
+ output_spec.store_url(request_state).path
247
+ end
248
+
249
+ request_state.output do
250
+ write_url_list 200, urls
251
+ end
252
+ end
253
+ end
254
+ Handler::register_node_parser OutputStoreURI
217
255
  end
218
256
 
@@ -177,11 +177,11 @@ module Configuration
177
177
  end
178
178
 
179
179
  def private_url
180
- s3_object.url_for(:read, expires: 60 * 60 * 24 * 365 * 20).to_s # expire in 20 years
180
+ s3_object.url_for(:read, expires: 60 * 60 * 24 * 365 * 20) # expire in 20 years
181
181
  end
182
182
 
183
183
  def public_url
184
- s3_object.public_url.to_s
184
+ s3_object.public_url
185
185
  end
186
186
 
187
187
  def content_type
@@ -237,11 +237,19 @@ module Configuration
237
237
  end
238
238
 
239
239
  def private_url
240
- @cache_file.header['private_url'] ||= (dirty! :private_url; super)
240
+ url = @cache_file.header['private_url'] and return URI(url)
241
+ dirty! :private_url
242
+ url = super
243
+ @cache_file.header['private_url'] = url.to_s
244
+ url
241
245
  end
242
246
 
243
247
  def public_url
244
- @cache_file.header['public_url'] ||= (dirty! :public_url; super)
248
+ url = @cache_file.header['public_url'] and return URI(url)
249
+ dirty! :public_url
250
+ url = super
251
+ @cache_file.header['public_url'] = url.to_s
252
+ url
245
253
  end
246
254
 
247
255
  def content_type
@@ -1,10 +1,9 @@
1
1
  require_relative 'spec_helper'
2
2
  require 'httpimagestore/configuration'
3
- Configuration::Scope.logger = Logger.new('/dev/null')
3
+ MemoryLimit.logger = Configuration::Scope.logger = RootLogger.new('/dev/null')
4
4
 
5
5
  require 'httpimagestore/configuration/file'
6
6
  require 'httpimagestore/configuration/output'
7
- MemoryLimit.logger = Logger.new('/dev/null')
8
7
 
9
8
  describe Configuration do
10
9
  let :state do
@@ -54,7 +53,7 @@ describe Configuration do
54
53
  subject.handlers[0].sources[0].realize(state)
55
54
 
56
55
  state.images['original'].source_path.should == "test.in"
57
- state.images['original'].source_url.should == "file://test.in"
56
+ state.images['original'].source_url.to_s.should == "file:/test.in"
58
57
  end
59
58
 
60
59
  describe 'context locals' do
@@ -220,7 +219,7 @@ describe Configuration do
220
219
  subject.handlers[0].stores[0].realize(state)
221
220
 
222
221
  state.images['input'].store_path.should == "test.out"
223
- state.images['input'].store_url.should == "file://test.out"
222
+ state.images['input'].store_url.to_s.should == "file:/test.out"
224
223
  end
225
224
 
226
225
  describe 'conditional inclusion support' do
@@ -2,11 +2,10 @@ require_relative 'spec_helper'
2
2
  require_relative 'support/cuba_response_env'
3
3
 
4
4
  require 'httpimagestore/configuration'
5
- Configuration::Scope.logger = Logger.new('/dev/null')
5
+ MemoryLimit.logger = Configuration::Scope.logger = RootLogger.new('/dev/null')
6
6
 
7
7
  require 'httpimagestore/configuration/handler'
8
8
  require 'httpimagestore/configuration/output'
9
- MemoryLimit.logger = Logger.new('/dev/null')
10
9
 
11
10
  describe Configuration do
12
11
  describe Configuration::Handler do
@@ -1,11 +1,10 @@
1
1
  require_relative 'spec_helper'
2
2
  require 'httpimagestore/configuration'
3
- Configuration::Scope.logger = Logger.new('/dev/null')
3
+ MemoryLimit.logger = Configuration::Scope.logger = RootLogger.new('/dev/null')
4
4
 
5
5
  require 'httpimagestore/configuration/output'
6
6
  require 'httpimagestore/configuration/thumbnailer'
7
7
  require 'httpimagestore/configuration/identify'
8
- MemoryLimit.logger = Logger.new('/dev/null')
9
8
 
10
9
  describe Configuration do
11
10
  describe 'identify' do
@@ -2,11 +2,10 @@ require_relative 'spec_helper'
2
2
  require_relative 'support/cuba_response_env'
3
3
 
4
4
  require 'httpimagestore/configuration'
5
- Configuration::Scope.logger = Logger.new('/dev/null')
5
+ MemoryLimit.logger = Configuration::Scope.logger = RootLogger.new('/dev/null')
6
6
 
7
7
  require 'httpimagestore/configuration/output'
8
8
  require 'httpimagestore/configuration/file'
9
- MemoryLimit.logger = Logger.new('/dev/null')
10
9
 
11
10
  describe Configuration do
12
11
  let :state do
@@ -159,6 +158,10 @@ describe Configuration do
159
158
  end
160
159
 
161
160
  describe 'output store paths and URLs' do
161
+ let :utf_string do
162
+ (support_dir + 'utf_string.txt').read.strip
163
+ end
164
+
162
165
  let :in_file do
163
166
  Pathname.new("/tmp/test.in")
164
167
  end
@@ -179,8 +182,12 @@ describe Configuration do
179
182
  Pathname.new('/tmp/abc/t e s t.out')
180
183
  end
181
184
 
185
+ let :utf_test_file do
186
+ Pathname.new("/tmp/abc/#{utf_string}.out")
187
+ end
188
+
182
189
  before :each do
183
- test_file.dirname.mkdir
190
+ test_file.dirname.mkdir unless test_file.dirname.directory?
184
191
  test_file.open('w'){|io| io.write('abc')}
185
192
  space_test_file.open('w'){|io| io.write('abc')}
186
193
  in_file.open('w'){|io| io.write('abc')}
@@ -191,6 +198,7 @@ describe Configuration do
191
198
  after :each do
192
199
  test_file.exist? and test_file.unlink
193
200
  space_test_file.exist? and space_test_file.unlink
201
+ utf_test_file.exist? and utf_test_file.unlink
194
202
  test_file.dirname.exist? and test_file.dirname.rmdir
195
203
  out_file.unlink if out_file.exist?
196
204
  out2_file.unlink if out2_file.exist?
@@ -391,7 +399,7 @@ describe Configuration do
391
399
 
392
400
  env.instance_eval &state.output_callback
393
401
  env.res['Content-Type'].should == 'text/uri-list'
394
- env.res.data.should == "file://test.out\r\n"
402
+ env.res.data.should == "file:/test.out\r\n"
395
403
  end
396
404
 
397
405
  it 'should provide multiple file store URLs' do
@@ -423,7 +431,7 @@ describe Configuration do
423
431
 
424
432
  env.instance_eval &state.output_callback
425
433
  env.res['Content-Type'].should == 'text/uri-list'
426
- env.res.data.should == "file://test.out\r\nfile://test.out2\r\n"
434
+ env.res.data.should == "file:/test.out\r\nfile:/test.out2\r\n"
427
435
  end
428
436
 
429
437
  describe 'conditional inclusion support' do
@@ -468,12 +476,12 @@ describe Configuration do
468
476
 
469
477
  env.instance_eval &state.output_callback
470
478
  env.res['Content-Type'].should == 'text/uri-list'
471
- env.res.data.should == "file://test.out1\r\nfile://test.out3\r\n"
479
+ env.res.data.should == "file:/test.out1\r\nfile:/test.out3\r\n"
472
480
  end
473
481
  end
474
482
 
475
- describe 'custom formatting' do
476
- it 'should provide formatted file store URL' do
483
+ describe 'URL rewrites' do
484
+ it 'should allow using path spec to rewrite URL path component' do
477
485
  subject = Configuration.read(<<-'EOF')
478
486
  path "out" "abc/test.out"
479
487
 
@@ -492,7 +500,93 @@ describe Configuration do
492
500
 
493
501
  env.instance_eval &state.output_callback
494
502
  env.res['Content-Type'].should == 'text/uri-list'
495
- env.res.data.should == "file://abc/hello/world/test-xyz.out\r\n"
503
+ env.res.data.should == "file:/hello/abc/world/test-xyz.out\r\n"
504
+ end
505
+
506
+ it 'should allow rewriting scheme component' do
507
+ subject = Configuration.read(<<-'EOF')
508
+ path "out" "abc/test.out"
509
+
510
+ post "single" {
511
+ store_file "input" root="/tmp" path="out"
512
+
513
+ output_store_url "input" scheme="ftp"
514
+ }
515
+ EOF
516
+
517
+ subject.handlers[0].sources[0].realize(state)
518
+ subject.handlers[0].stores[0].realize(state)
519
+ subject.handlers[0].output.realize(state)
520
+
521
+ env.instance_eval &state.output_callback
522
+ env.res['Content-Type'].should == 'text/uri-list'
523
+ env.res.data.should == "ftp:/abc/test.out\r\n"
524
+ end
525
+
526
+ it 'should allow rewriting host component' do
527
+ subject = Configuration.read(<<-'EOF')
528
+ path "out" "abc/test.out"
529
+
530
+ post "single" {
531
+ store_file "input" root="/tmp" path="out"
532
+
533
+ output_store_url "input" host="localhost"
534
+ }
535
+ EOF
536
+
537
+ subject.handlers[0].sources[0].realize(state)
538
+ subject.handlers[0].stores[0].realize(state)
539
+ subject.handlers[0].output.realize(state)
540
+
541
+ env.instance_eval &state.output_callback
542
+ env.res['Content-Type'].should == 'text/uri-list'
543
+ env.res.data.should == "file://localhost/abc/test.out\r\n"
544
+ end
545
+
546
+ it 'should allow rewriting port component' do
547
+ subject = Configuration.read(<<-'EOF')
548
+ path "out" "abc/test.out"
549
+
550
+ post "single" {
551
+ store_file "input" root="/tmp" path="out"
552
+
553
+ output_store_url "input" port="21"
554
+ }
555
+ EOF
556
+
557
+ subject.handlers[0].sources[0].realize(state)
558
+ subject.handlers[0].stores[0].realize(state)
559
+ subject.handlers[0].output.realize(state)
560
+
561
+ env.instance_eval &state.output_callback
562
+ env.res['Content-Type'].should == 'text/uri-list'
563
+ env.res.data.should == "file::21/abc/test.out\r\n"
564
+ end
565
+
566
+ it 'should allow using variables for all supported rewrites' do
567
+ state = Configuration::RequestState.new('abc',
568
+ remote: 'example.com',
569
+ remote_port: 21,
570
+ proto: 'ftp'
571
+ )
572
+ subject = Configuration.read(<<-'EOF')
573
+ path "out" "abc/test.out"
574
+ path "formatted" "hello/#{dirname}/world/#{basename}-xyz.#{extension}"
575
+
576
+ post "single" {
577
+ store_file "input" root="/tmp" path="out"
578
+
579
+ output_store_url "input" scheme="#{proto}" host="#{remote}" port="#{remote_port}" path="formatted"
580
+ }
581
+ EOF
582
+
583
+ subject.handlers[0].sources[0].realize(state)
584
+ subject.handlers[0].stores[0].realize(state)
585
+ subject.handlers[0].output.realize(state)
586
+
587
+ env.instance_eval &state.output_callback
588
+ env.res['Content-Type'].should == 'text/uri-list'
589
+ env.res.data.should == "ftp://example.com:21/hello/abc/world/test-xyz.out\r\n"
496
590
  end
497
591
  end
498
592
 
@@ -518,7 +612,7 @@ describe Configuration do
518
612
 
519
613
  env.instance_eval &state.output_callback
520
614
  env.res['Content-Type'].should == 'text/uri-list'
521
- env.res.data.should == "file://abc/t%20e%20s%20t.out\r\nfile://abc/hello/world/t%20e%20s%20t-xyz.out\r\n"
615
+ env.res.data.should == "file:/abc/t%20e%20s%20t.out\r\nfile:/hello/abc/world/t%20e%20s%20t-xyz.out\r\n"
522
616
  end
523
617
  end
524
618
 
@@ -538,5 +632,190 @@ describe Configuration do
538
632
  end
539
633
  end
540
634
  end
635
+
636
+ describe Configuration::OutputStoreURI do
637
+ it 'should provide file store URI' do
638
+ subject = Configuration.read(<<-EOF)
639
+ path {
640
+ "out" "test.out"
641
+ }
642
+
643
+ post "single" {
644
+ store_file "input" root="/tmp" path="out"
645
+
646
+ output_store_uri "input"
647
+ }
648
+ EOF
649
+
650
+ subject.handlers[0].sources[0].realize(state)
651
+ subject.handlers[0].stores[0].realize(state)
652
+ subject.handlers[0].output.realize(state)
653
+
654
+ env.instance_eval &state.output_callback
655
+ env.res['Content-Type'].should == 'text/uri-list'
656
+ env.res.data.should == "/test.out\r\n"
657
+ end
658
+
659
+ it 'should provide multiple file store URIs' do
660
+ subject = Configuration.read(<<-EOF)
661
+ path {
662
+ "in" "test.in"
663
+ "out" "test.out"
664
+ "out2" "test.out2"
665
+ }
666
+
667
+ post "multi" {
668
+ source_file "original" root="/tmp" path="in"
669
+
670
+ store_file "input" root="/tmp" path="out"
671
+ store_file "original" root="/tmp" path="out2"
672
+
673
+ output_store_uri {
674
+ "input"
675
+ "original"
676
+ }
677
+ }
678
+ EOF
679
+
680
+ subject.handlers[0].sources[0].realize(state)
681
+ subject.handlers[0].sources[1].realize(state)
682
+ subject.handlers[0].stores[0].realize(state)
683
+ subject.handlers[0].stores[1].realize(state)
684
+ subject.handlers[0].output.realize(state)
685
+
686
+ env.instance_eval &state.output_callback
687
+ env.res['Content-Type'].should == 'text/uri-list'
688
+ env.res.data.should == "/test.out\r\n/test.out2\r\n"
689
+ end
690
+
691
+ describe 'conditional inclusion support' do
692
+ let :state do
693
+ Configuration::RequestState.new('abc', list: 'input,image2')
694
+ end
695
+
696
+ subject do
697
+ Configuration.read(<<-'EOF')
698
+ path {
699
+ "in" "test.in"
700
+ "out1" "test.out1"
701
+ "out2" "test.out2"
702
+ "out3" "test.out3"
703
+ }
704
+
705
+ post "multi" {
706
+ source_file "image1" root="/tmp" path="in"
707
+ source_file "image2" root="/tmp" path="in"
708
+
709
+ store_file "input" root="/tmp" path="out1"
710
+ store_file "image1" root="/tmp" path="out2"
711
+ store_file "image2" root="/tmp" path="out3"
712
+
713
+ output_store_uri {
714
+ "input" if-image-name-on="#{list}"
715
+ "image1" if-image-name-on="#{list}"
716
+ "image2" if-image-name-on="#{list}"
717
+ }
718
+ }
719
+ EOF
720
+ end
721
+
722
+ it 'should output store url only for images that names match if-image-name-on list' do
723
+ subject.handlers[0].sources[0].realize(state)
724
+ subject.handlers[0].sources[1].realize(state)
725
+ subject.handlers[0].sources[2].realize(state)
726
+ subject.handlers[0].stores[0].realize(state)
727
+ subject.handlers[0].stores[1].realize(state)
728
+ subject.handlers[0].stores[2].realize(state)
729
+ subject.handlers[0].output.realize(state)
730
+
731
+ env.instance_eval &state.output_callback
732
+ env.res['Content-Type'].should == 'text/uri-list'
733
+ env.res.data.should == "/test.out1\r\n/test.out3\r\n"
734
+ end
735
+ end
736
+
737
+ describe 'URI rewrites' do
738
+ it 'should allow using path spec to rewrite URI path' do
739
+ subject = Configuration.read(<<-'EOF')
740
+ path "out" "abc/test.out"
741
+
742
+ path "formatted" "hello/#{dirname}/world/#{basename}-xyz.#{extension}"
743
+
744
+ post "single" {
745
+ store_file "input" root="/tmp" path="out"
746
+
747
+ output_store_uri "input" path="formatted"
748
+ }
749
+ EOF
750
+
751
+ subject.handlers[0].sources[0].realize(state)
752
+ subject.handlers[0].stores[0].realize(state)
753
+ subject.handlers[0].output.realize(state)
754
+
755
+ env.instance_eval &state.output_callback
756
+ env.res['Content-Type'].should == 'text/uri-list'
757
+ env.res.data.should == "/hello/abc/world/test-xyz.out\r\n"
758
+ end
759
+ end
760
+
761
+ describe 'URI encoding' do
762
+ let :subject do
763
+ Configuration.read(<<-'EOF')
764
+ path "out" "abc/#{name}.out"
765
+ path "formatted" "hello/#{dirname}/world/#{basename}-xyz.#{extension}"
766
+
767
+ post "single" {
768
+ store_file "input" root="/tmp" path="out"
769
+
770
+ output_store_uri {
771
+ "input"
772
+ "input" path="formatted"
773
+ }
774
+ }
775
+ EOF
776
+ end
777
+
778
+ it 'should provide properly encoded file store URI' do
779
+ state = Configuration::RequestState.new('abc', name: 't e s t')
780
+
781
+ subject.handlers[0].sources[0].realize(state)
782
+ subject.handlers[0].stores[0].realize(state)
783
+ subject.handlers[0].output.realize(state)
784
+
785
+ env.instance_eval &state.output_callback
786
+ env.res['Content-Type'].should == 'text/uri-list'
787
+ env.res.data.should == "/abc/t%20e%20s%20t.out\r\n/hello/abc/world/t%20e%20s%20t-xyz.out\r\n"
788
+ end
789
+
790
+ it 'should handle UTF-8 characters' do
791
+ state = Configuration::RequestState.new('abc', name: utf_string)
792
+ subject.handlers[0].sources[0].realize(state)
793
+ subject.handlers[0].stores[0].realize(state)
794
+ subject.handlers[0].output.realize(state)
795
+
796
+ env.instance_eval &state.output_callback
797
+ env.res['Content-Type'].should == 'text/uri-list'
798
+ l1, l2 = *env.res.data.split("\r\n")
799
+ URI.utf_decode(l1).should == "/abc/#{utf_string}.out"
800
+ URI.utf_decode(l2).should == "/hello/abc/world/#{utf_string}-xyz.out"
801
+ end
802
+ end
803
+
804
+ describe 'error handling' do
805
+ it 'should raise StoreURLNotSetForImage for output of not stored image' do
806
+ subject = Configuration.read(<<-EOF)
807
+ post "single" {
808
+ output_store_uri "input"
809
+ }
810
+ EOF
811
+
812
+ subject.handlers[0].sources[0].realize(state)
813
+
814
+ expect {
815
+ subject.handlers[0].output.realize(state)
816
+ }.to raise_error Configuration::StoreURLNotSetForImage, %{store URL not set for image 'input'}
817
+ end
818
+ end
819
+ end
541
820
  end
542
821
  end
@@ -1,10 +1,9 @@
1
1
  require_relative 'spec_helper'
2
2
  require 'httpimagestore/configuration'
3
- Configuration::Scope.logger = Logger.new('/dev/null')
3
+ MemoryLimit.logger = Configuration::Scope.logger = RootLogger.new('/dev/null')
4
4
 
5
5
  require 'httpimagestore/configuration/handler'
6
6
  require 'httpimagestore/configuration/path'
7
- MemoryLimit.logger = Logger.new('/dev/null')
8
7
 
9
8
  describe Configuration do
10
9
  describe 'path rendering' do
@@ -62,7 +61,7 @@ describe Configuration do
62
61
 
63
62
  expect {
64
63
  subject.paths['test'].render
65
- }.to raise_error Configuration::NoValueForPathTemplatePlaceholerError, %q{cannot generate path 'test' from template '#{abc}#{xyz}': no value for '#{abc}'}
64
+ }.to raise_error Configuration::NoValueForPathTemplatePlaceholerError, %q{cannot generate path 'test' from template '#{abc}#{xyz}': no value for '#{abc}'}
66
65
  end
67
66
  end
68
67