streamy_csv 0.4.0 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 24325070ca75478285318b9b6e0123ef9984cca3
4
- data.tar.gz: 47cda2132e14791c76233c269ad967d906b81cdb
3
+ metadata.gz: d675a642b3b39ce706e4f8e5ec0cd67809db6f43
4
+ data.tar.gz: 6a68f7897d3070e18e45f02200685bbc4c3c2d89
5
5
  SHA512:
6
- metadata.gz: b816608ce3cac24b1f930b44b05229fcaf1400b36dd673b47632f4c7df258971bcb1132357a2b0311be5a1bcc357baf362835a8de2ce440fecf81d5b30c9cc27
7
- data.tar.gz: 201fa74a9696bacdbfa0928c27674a58b30f65cae3c13f2a50b41bef7741d1c13bbd194e1686b77a77ad30903a8e25f5efa3465805e08f4108560b500e5132fa
6
+ metadata.gz: 6cf3a9f43c25f5a077453be47c80f46d75e2265fbac842274f5a11316533c1628fe6e09dd77af76b394d61ed7b1b0736366111a1704c5ef41acd3fb71219a587
7
+ data.tar.gz: 52f04bddb8b2d850fc5a3320eac6e8afde8c807b98314661c8818f9d483473ede3303064d41bb3866474b44625a0bbdb8912a76ec0730a967bd0c442cc9826c5
@@ -0,0 +1,18 @@
1
+ module StreamyCsv
2
+ class InjectionSanitizer
3
+ PREFIXES_TO_ESCAPE=%w(= @ + - |)
4
+ ESCAPE_CHAR="'"
5
+
6
+ def self.sanitize_csv_row(row)
7
+ if row.is_a?(CSV::Row)
8
+ sanitized_row = row.dup
9
+ row.each do |title, value|
10
+ if value.to_s.start_with?(*PREFIXES_TO_ESCAPE)
11
+ sanitized_row[title] = "#{ESCAPE_CHAR}#{value}"
12
+ end
13
+ end
14
+ end
15
+ row
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module StreamyCsv
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.2"
3
3
  end
data/lib/streamy_csv.rb CHANGED
@@ -1,8 +1,7 @@
1
1
  require "streamy_csv/version"
2
+ require "streamy_csv/injection_sanitizer"
2
3
 
3
4
  module StreamyCsv
4
- CSV_OPERATORS = ['+','-','=','@','%']
5
- UNESCAPED_PIPES_RGX = /(?<!\\)(?:\\{2})*\K\|/
6
5
 
7
6
  # stream_csv('data.csv', MyModel.header_row) do |rows|
8
7
  # MyModel.find_each do |my_model|
@@ -12,7 +11,7 @@ module StreamyCsv
12
11
  #
13
12
  #
14
13
 
15
- def stream_csv(file_name, header_row, sanitize = true, &block)
14
+ def stream_csv(file_name, header_row, sanitize=true, &block)
16
15
  set_streaming_headers
17
16
  set_file_headers(file_name)
18
17
 
@@ -30,30 +29,16 @@ module StreamyCsv
30
29
  end
31
30
 
32
31
  def csv_lines(header_row, sanitize, &block)
33
- Enumerator.new do |yielder|
34
- rows = appendHeader([], header_row, sanitize)
35
- block.call(rows)
36
- rows.each do |row|
37
- sanitize!(row) if sanitize
38
- yielder.yield row
32
+ Enumerator.new do |rows|
33
+ if sanitize
34
+ def rows.<<(row)
35
+ super StreamyCsv::InjectionSanitizer.sanitize_csv_row(row).to_s
36
+ end
39
37
  end
38
+ rows << header_row if header_row
39
+ block.call(rows)
40
40
  end
41
- end
42
-
43
- def appendHeader(rows, header_row, sanitize)
44
- if header_row && header_row.any?
45
- sanitize! header_row if sanitize
46
- rows << header_row.to_s
47
- end
48
- rows
49
- end
50
41
 
51
- def sanitize!(enumerable)
52
- return unless enumerable && enumerable.is_a?(Enumerable)
53
- enumerable = enumerable.fields if enumerable.is_a?(CSV::Row)
54
- enumerable.each do |field|
55
- field.gsub!(UNESCAPED_PIPES_RGX,'\|') if field.is_a?(String) && field.start_with?(*CSV_OPERATORS)
56
- end
57
42
  end
58
43
 
59
44
  def set_file_headers(file_name)
@@ -0,0 +1,39 @@
1
+ $: << File.join(File.dirname(__FILE__), "../../../lib")
2
+ require 'streamy_csv/injection_sanitizer'
3
+ require 'csv'
4
+
5
+ describe StreamyCsv::InjectionSanitizer do
6
+ describe 'sanitize_csv_row' do
7
+ it "returns the data as is in case it's not a csv row" do
8
+ StreamyCsv::InjectionSanitizer.sanitize_csv_row(10).should == 10
9
+ end
10
+
11
+ it 'escapes the leading escape leading | character on any column' do
12
+ row = CSV::Row.new([:x], ["|RAND()"])
13
+ sanitized_row = StreamyCsv::InjectionSanitizer.sanitize_csv_row(row)
14
+ sanitized_row.to_s.strip.should == "'|RAND()"
15
+ end
16
+
17
+ it 'escapes the leading escape leading + character on any column' do
18
+ row = CSV::Row.new([:x], ["-RAND()"])
19
+ sanitized_row = StreamyCsv::InjectionSanitizer.sanitize_csv_row(row)
20
+ sanitized_row.to_s.strip.should == "'-RAND()"
21
+ end
22
+
23
+ it 'escapes the leading escape leading + character on any column' do
24
+ row = CSV::Row.new([:x], ["+RAND()"])
25
+ sanitized_row = StreamyCsv::InjectionSanitizer.sanitize_csv_row(row)
26
+ sanitized_row.to_s.strip.should == "'+RAND()"
27
+ end
28
+ it 'escapes the leading escape leading @ character on any column' do
29
+ row = CSV::Row.new([:x], ["@RAND()"])
30
+ sanitized_row = StreamyCsv::InjectionSanitizer.sanitize_csv_row(row)
31
+ sanitized_row.to_s.strip.should == "'@RAND()"
32
+ end
33
+ it 'escapes the leading escape leading = character on any column' do
34
+ row = CSV::Row.new([:x], ["=RAND()"])
35
+ sanitized_row = StreamyCsv::InjectionSanitizer.sanitize_csv_row(row)
36
+ sanitized_row.to_s.strip.should == "'=RAND()"
37
+ end
38
+ end
39
+ end
@@ -34,14 +34,14 @@ describe StreamyCsv do
34
34
  it 'sets the streaming headers' do
35
35
  @controller.stream_csv('abc.csv', @header)
36
36
  @controller.headers.should include({'X-Accel-Buffering' => 'no',
37
- "Cache-Control" => "no-cache"
37
+ "Cache-Control" => "no-cache"
38
38
  })
39
39
  end
40
40
 
41
41
  it 'sets the file headers' do
42
42
  @controller.stream_csv('abc.csv', @header)
43
43
  @controller.headers.should include({"Content-Type" => "text/csv",
44
- "Content-disposition" => "attachment; filename=\"abc.csv\""
44
+ "Content-disposition" => "attachment; filename=\"abc.csv\""
45
45
  })
46
46
  end
47
47
 
@@ -59,40 +59,46 @@ describe StreamyCsv do
59
59
  @controller.response.status.should == 200
60
60
  @controller.response_body.is_a?(Enumerator).should == true
61
61
  end
62
+
62
63
  it 'sanitizes header and contents and streams the csv file' do
63
- row_1 = CSV::Row.new([:name, :title], ['AB', 'Mr'])
64
- row_2 = CSV::Row.new([:name, :title], ["=cmd|' /C", 'Pres'])
65
- header = [:name, "=cmd|' /C"]
66
- rows = [header, row_1]
64
+ header = CSV::Row.new([:name, :title], ['Name', "=cmd|' /C"], true)
67
65
 
68
- @controller.stream_csv('abc.csv', @header) do |rows|
69
- rows << row_1
70
- rows << row_2
66
+ @controller.stream_csv('abc.csv', header) do |rows|
67
+ rows << CSV::Row.new([:name, :title], ['AB', 'Mr'])
68
+ rows << CSV::Row.new([:name, :title], ["=cmd|' /C", '-Pres'])
69
+ rows << CSV::Row.new([:name, :title], ["@something", '+Pres'])
71
70
  end
71
+
72
72
  @controller.response.status.should == 200
73
73
  @controller.response_body.is_a?(Enumerator).should == true
74
- @controller.response_body.take(1)[0].to_s[4].bytes == '\\'.bytes
75
- @controller.response_body.take(1)[0].to_s[5].bytes == '|'.bytes
76
- @controller.response_body.take(3)[2].to_s[4].bytes == '\\'.bytes
77
- @controller.response_body.take(3)[2].to_s[5].bytes == '|'.bytes
74
+ body = @controller.response_body.to_a
75
+ body.size.should == 4
76
+
77
+ body[0].to_s.strip.should == "Name,'=cmd|' /C"
78
+ body[1].to_s.strip.should == "AB,Mr"
79
+ body[2].to_s.strip.should == "'=cmd|' /C,'-Pres"
80
+ body[3].to_s.strip.should == "'@something,'+Pres"
78
81
  end
79
- it 'does not sanitize the csv if the option provided' do
80
- row_1 = CSV::Row.new([:name, :title], ['AB', 'Mr'])
81
- row_2 = CSV::Row.new([:name, :title], ["=cmd|' /C", 'Pres'])
82
- header = [:name, "=cmd|' /C"]
83
- rows = [header, row_1]
84
82
 
85
- @controller.stream_csv('abc.csv', @header, false) do |rows|
86
- rows << row_1
87
- rows << row_2
83
+ it 'does not sanitize header and contents if specified and streams the csv file' do
84
+ header = CSV::Row.new([:name, :title], ['Name', "=cmd|' /C"], true)
85
+
86
+ @controller.stream_csv('abc.csv', header, false) do |rows|
87
+ rows << CSV::Row.new([:name, :title], ['AB', 'Mr'])
88
+ rows << CSV::Row.new([:name, :title], ["=cmd|' /C", '-Pres'])
89
+ rows << CSV::Row.new([:name, :title], ["@something", '+Pres'])
88
90
  end
91
+
89
92
  @controller.response.status.should == 200
90
93
  @controller.response_body.is_a?(Enumerator).should == true
91
- @controller.response_body.take(1)[0].to_s[4].bytes == 'd'.bytes
92
- @controller.response_body.take(1)[0].to_s[5].bytes == '|'.bytes
93
- @controller.response_body.take(3)[2].to_s[4].bytes == 'd'.bytes
94
- @controller.response_body.take(3)[2].to_s[5].bytes == '|'.bytes
94
+ body = @controller.response_body.to_a
95
+ body.size.should == 4
96
+
97
+ body[0].to_s.strip.should == "Name,=cmd|' /C"
98
+ body[1].to_s.strip.should == "AB,Mr"
99
+ body[2].to_s.strip.should == "=cmd|' /C,-Pres"
100
+ body[3].to_s.strip.should == "@something,+Pres"
95
101
  end
96
102
  end
97
103
 
98
- end
104
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: streamy_csv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - smsohan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-08-02 00:00:00.000000000 Z
11
+ date: 2017-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -39,7 +39,9 @@ files:
39
39
  - README.md
40
40
  - Rakefile
41
41
  - lib/streamy_csv.rb
42
+ - lib/streamy_csv/injection_sanitizer.rb
42
43
  - lib/streamy_csv/version.rb
44
+ - spec/lib/streamy_csv/injection_sanitizer_spec.rb
43
45
  - spec/streamy_csv_spec.rb
44
46
  - streamy_csv.gemspec
45
47
  homepage: https://github.com/smsohan/streamy_csv
@@ -61,10 +63,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
61
63
  version: '0'
62
64
  requirements: []
63
65
  rubyforge_project:
64
- rubygems_version: 2.4.5
66
+ rubygems_version: 2.5.2.1
65
67
  signing_key:
66
68
  specification_version: 4
67
69
  summary: Provides a simple API for your controllers to stream CSV files one row at
68
70
  a time
69
71
  test_files:
72
+ - spec/lib/streamy_csv/injection_sanitizer_spec.rb
70
73
  - spec/streamy_csv_spec.rb