streamy_csv 0.4.0 → 0.5.2

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.
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