iostreams 0.9.1 → 0.10.0
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 +4 -4
- data/Rakefile +4 -10
- data/lib/io_streams/delimited/reader.rb +28 -31
- data/lib/io_streams/delimited/writer.rb +18 -22
- data/lib/io_streams/io_streams.rb +5 -3
- data/lib/io_streams/pgp.rb +213 -0
- data/lib/io_streams/pgp/reader.rb +50 -0
- data/lib/io_streams/pgp/writer.rb +93 -0
- data/lib/io_streams/sftp/reader.rb +67 -0
- data/lib/io_streams/sftp/writer.rb +68 -0
- data/lib/io_streams/streams.rb +1 -1
- data/lib/io_streams/version.rb +1 -1
- data/lib/io_streams/xlsx/reader.rb +1 -5
- data/lib/io_streams/zip/reader.rb +1 -5
- data/lib/io_streams/zip/writer.rb +3 -9
- data/lib/iostreams.rb +5 -0
- data/test/pgp_reader_test.rb +44 -0
- data/test/pgp_writer_test.rb +91 -0
- data/test/test_helper.rb +9 -0
- data/test/zip_writer_test.rb +1 -1
- metadata +16 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e653b5b68990dd8c8c6442c160a109a072e33a14
|
4
|
+
data.tar.gz: f30e9b16508e24ed1040f36bf16192cc2c5e7e1a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 63d58edaa9e53bc878bfca723772f0373791cea7411fe5be74b37db6e56d5a79003f06ccd0a95962a2e6d754c55f0a8af6031a68c0a70aef71ed83328e50000b
|
7
|
+
data.tar.gz: 28be995afd5b6627758599c6c50d2e21293bef90e1df9babb413a82152953237e5cf25191bb8d15a7aedf782c162e635a4d822878dde183a40c61a2f621ad37d
|
data/Rakefile
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
require 'rake/clean'
|
2
1
|
require 'rake/testtask'
|
3
|
-
|
4
2
|
require_relative 'lib/io_streams/version'
|
5
3
|
|
6
4
|
task :gem do
|
@@ -14,14 +12,10 @@ task :publish => :gem do
|
|
14
12
|
system "rm iostreams-#{IOStreams::VERSION}.gem"
|
15
13
|
end
|
16
14
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
t.verbose = true
|
22
|
-
end
|
23
|
-
|
24
|
-
Rake::Task['functional'].invoke
|
15
|
+
Rake::TestTask.new(:test) do |t|
|
16
|
+
t.pattern = 'test/**/*_test.rb'
|
17
|
+
t.verbose = true
|
18
|
+
t.warning = true
|
25
19
|
end
|
26
20
|
|
27
21
|
task :default => :test
|
@@ -4,12 +4,12 @@ module IOStreams
|
|
4
4
|
attr_accessor :delimiter, :buffer_size, :encoding, :strip_non_printable
|
5
5
|
|
6
6
|
# Read from a file or stream
|
7
|
-
def self.open(file_name_or_io,
|
7
|
+
def self.open(file_name_or_io, delimiter: nil, buffer_size: 65536, encoding: UTF8_ENCODING, strip_non_printable: false)
|
8
8
|
if IOStreams.reader_stream?(file_name_or_io)
|
9
|
-
|
9
|
+
yield new(file_name_or_io, delimiter: delimiter, buffer_size: buffer_size, encoding: encoding, strip_non_printable: strip_non_printable)
|
10
10
|
else
|
11
11
|
::File.open(file_name_or_io, 'rb') do |io|
|
12
|
-
|
12
|
+
yield new(io, delimiter: delimiter, buffer_size: buffer_size, encoding: encoding, strip_non_printable: strip_non_printable)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -24,38 +24,35 @@ module IOStreams
|
|
24
24
|
# input_stream
|
25
25
|
# The input stream that implements #read
|
26
26
|
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
# delimiter for all subsequent records
|
27
|
+
# delimiter: [String]
|
28
|
+
# Line / Record delimiter to use to break the stream up into records
|
29
|
+
# Any string to break the stream up by
|
30
|
+
# The records when saved will not include this delimiter
|
31
|
+
# Default: nil
|
32
|
+
# Automatically detect line endings and break up by line
|
33
|
+
# Searches for the first "\r\n" or "\n" and then uses that as the
|
34
|
+
# delimiter for all subsequent records
|
36
35
|
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
36
|
+
# buffer_size: [Integer]
|
37
|
+
# Maximum size of the buffer into which to read the stream into for
|
38
|
+
# processing.
|
39
|
+
# Must be large enough to hold the entire first line and its delimiter(s)
|
40
|
+
# Default: 65536 ( 64K )
|
42
41
|
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
42
|
+
# strip_non_printable: [true|false]
|
43
|
+
# Strip all non-printable characters read from the file
|
44
|
+
# Default: false
|
46
45
|
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
def initialize(input_stream,
|
46
|
+
# encoding:
|
47
|
+
# Force encoding to this encoding for all data being read
|
48
|
+
# Default: UTF8_ENCODING
|
49
|
+
# Set to nil to disable encoding
|
50
|
+
def initialize(input_stream, delimiter: nil, buffer_size: 65536, encoding: UTF8_ENCODING, strip_non_printable: false)
|
52
51
|
@input_stream = input_stream
|
53
|
-
|
54
|
-
@
|
55
|
-
@
|
56
|
-
@
|
57
|
-
@strip_non_printable = options.delete(:strip_non_printable) || false
|
58
|
-
raise ArgumentError.new("Unknown IOStreams::Delimited::Reader#initialize options: #{options.inspect}") if options.size > 0
|
52
|
+
@delimiter = delimiter
|
53
|
+
@buffer_size = buffer_size
|
54
|
+
@encoding = encoding
|
55
|
+
@strip_non_printable = strip_non_printable
|
59
56
|
|
60
57
|
@delimiter.force_encoding(UTF8_ENCODING) if @delimiter && @encoding
|
61
58
|
@buffer = ''
|
@@ -4,12 +4,12 @@ module IOStreams
|
|
4
4
|
attr_accessor :delimiter
|
5
5
|
|
6
6
|
# Write delimited records/lines to a file or stream
|
7
|
-
def self.open(file_name_or_io,
|
7
|
+
def self.open(file_name_or_io, delimiter: $/, encoding: UTF8_ENCODING, strip_non_printable: false)
|
8
8
|
if IOStreams.writer_stream?(file_name_or_io)
|
9
|
-
|
9
|
+
yield new(file_name_or_io, delimiter: delimiter, encoding: encoding, strip_non_printable: strip_non_printable)
|
10
10
|
else
|
11
11
|
::File.open(file_name_or_io, 'wb') do |io|
|
12
|
-
|
12
|
+
yield new(io, delimiter: delimiter, encoding: encoding, strip_non_printable: strip_non_printable)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -26,28 +26,24 @@ module IOStreams
|
|
26
26
|
# output_stream
|
27
27
|
# The output stream that implements #write
|
28
28
|
#
|
29
|
-
#
|
30
|
-
# delimiter
|
31
|
-
#
|
32
|
-
#
|
33
|
-
# Default: OS Specific. Linux: "\n"
|
29
|
+
# delimiter: [String]
|
30
|
+
# Add the specified delimiter after every record when writing it
|
31
|
+
# to the output stream
|
32
|
+
# Default: OS Specific. Linux: "\n"
|
34
33
|
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
34
|
+
# encoding:
|
35
|
+
# Force encoding to this encoding for all data being read
|
36
|
+
# Default: UTF8_ENCODING
|
37
|
+
# Set to nil to disable encoding
|
39
38
|
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
def initialize(output_stream,
|
39
|
+
# strip_non_printable: [true|false]
|
40
|
+
# Strip all non-printable characters read from the file
|
41
|
+
# Default: false
|
42
|
+
def initialize(output_stream, delimiter: $/, encoding: UTF8_ENCODING, strip_non_printable: false)
|
44
43
|
@output_stream = output_stream
|
45
|
-
|
46
|
-
@
|
47
|
-
@
|
48
|
-
@strip_non_printable = options.delete(:strip_non_printable)
|
49
|
-
@strip_non_printable = @strip_non_printable.nil? && (@encoding == UTF8_ENCODING)
|
50
|
-
raise ArgumentError.new("Unknown IOStreams::Delimited::Writer#initialize options: #{options.inspect}") if options.size > 0
|
44
|
+
@delimiter = delimiter.dup
|
45
|
+
@encoding = encoding
|
46
|
+
@strip_non_printable = strip_non_printable
|
51
47
|
@delimiter.force_encoding(UTF8_ENCODING) if @delimiter
|
52
48
|
end
|
53
49
|
|
@@ -51,7 +51,7 @@ module IOStreams
|
|
51
51
|
# # MyXls::Reader and MyXls::Writer must implement .open
|
52
52
|
# register_extension(:xls, MyXls::Reader, MyXls::Writer)
|
53
53
|
def self.register_extension(extension, reader_class, writer_class)
|
54
|
-
raise "Invalid extension #{extension.inspect}" unless extension.to_s =~ /\A\w+\Z/
|
54
|
+
raise(ArgumentError, "Invalid extension #{extension.inspect}") unless extension.to_s =~ /\A\w+\Z/
|
55
55
|
@@extensions[extension.to_sym] = Extension.new(reader_class, writer_class)
|
56
56
|
end
|
57
57
|
|
@@ -62,7 +62,7 @@ module IOStreams
|
|
62
62
|
# Example:
|
63
63
|
# register_extension(:xls)
|
64
64
|
def self.deregister_extension(extension)
|
65
|
-
raise "Invalid extension #{extension.inspect}" unless extension.to_s =~ /\A\w+\Z/
|
65
|
+
raise(ArgumentError, "Invalid extension #{extension.inspect}") unless extension.to_s =~ /\A\w+\Z/
|
66
66
|
@@extensions.delete(extension.to_sym)
|
67
67
|
end
|
68
68
|
|
@@ -239,7 +239,7 @@ module IOStreams
|
|
239
239
|
stream_struct.klass.open(file_name_or_io, stream_struct.options, &block)
|
240
240
|
else
|
241
241
|
# Daisy chain multiple streams together
|
242
|
-
last = stream_structs.inject(block) { |inner,
|
242
|
+
last = stream_structs.inject(block) { |inner, ss| -> io { ss.klass.open(io, ss.options, &inner) } }
|
243
243
|
last.call(file_name_or_io)
|
244
244
|
end
|
245
245
|
end
|
@@ -283,5 +283,7 @@ module IOStreams
|
|
283
283
|
register_extension(:delimited, IOStreams::Delimited::Reader, IOStreams::Delimited::Writer)
|
284
284
|
register_extension(:xlsx, IOStreams::Xlsx::Reader, nil)
|
285
285
|
register_extension(:xlsm, IOStreams::Xlsx::Reader, nil)
|
286
|
+
register_extension(:pgp, IOStreams::Pgp::Reader, IOStreams::Pgp::Writer)
|
287
|
+
register_extension(:gpg, IOStreams::Pgp::Reader, IOStreams::Pgp::Writer)
|
286
288
|
#register_extension(:csv, IOStreams::CSV::Reader, IOStreams::CSV::Writer)
|
287
289
|
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
require 'open3'
|
2
|
+
module IOStreams
|
3
|
+
# Read/Write PGP/GPG file or stream.
|
4
|
+
#
|
5
|
+
# Example Setup:
|
6
|
+
#
|
7
|
+
# 1. Install OpenPGP
|
8
|
+
# Mac OSX (homebrew) : `brew install gpg2`
|
9
|
+
# Redhat Linux: `rpm install gpg2`
|
10
|
+
#
|
11
|
+
# 2. # Generate senders private and public key
|
12
|
+
# IOStreams::Pgp.generate_key(name: 'Sender', email: 'sender@example.org', passphrase: 'sender_passphrase')
|
13
|
+
#
|
14
|
+
# 3. # Generate receivers private and public key
|
15
|
+
# IOStreams::Pgp.generate_key(name: 'Receiver', email: 'receiver@example.org', passphrase: 'receiver_passphrase')
|
16
|
+
#
|
17
|
+
# Example 1:
|
18
|
+
#
|
19
|
+
# # Generate encrypted file for a specific recipient and sign it with senders credentials
|
20
|
+
# data = %w(this is some data that should be encrypted using pgp)
|
21
|
+
# IOStreams::Pgp::Writer.open('secure.gpg', recipient: 'receiver@example.org', signer: 'sender@example.org', signer_passphrase: 'sender_passphrase') do |output|
|
22
|
+
# data.each { |word| output.puts(word) }
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# # Decrypt the file sent to `receiver@example.org` using its private key
|
26
|
+
# # Recipient must also have the senders public key to verify the signature
|
27
|
+
# IOStreams::Pgp::Reader.open('secure.gpg', passphrase: 'receiver_passphrase') do |stream|
|
28
|
+
# while !stream.eof?
|
29
|
+
# ap stream.read(10)
|
30
|
+
# puts
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# Example 2:
|
35
|
+
#
|
36
|
+
# # Default user and passphrase to sign the output file:
|
37
|
+
# IOStreams::Pgp::Writer.default_signer = 'sender@example.org'
|
38
|
+
# IOStreams::Pgp::Writer.default_signer_passphrase = 'sender_passphrase'
|
39
|
+
#
|
40
|
+
# # Default passphrase for decrypting recipients files.
|
41
|
+
# # Note: Usually this would be the senders passphrase, but in this example
|
42
|
+
# # it is decrypting the file intended for the recipient.
|
43
|
+
# IOStreams::Pgp::Reader.default_passphrase = 'receiver_passphrase'
|
44
|
+
#
|
45
|
+
# # Generate encrypted file for a specific recipient and sign it with senders credentials
|
46
|
+
# data = %w(this is some data that should be encrypted using pgp)
|
47
|
+
# IOStreams.writer('secure.gpg', pgp: {recipient: 'receiver@example.org'}) do |output|
|
48
|
+
# data.each { |word| output.puts(word) }
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# # Decrypt the file sent to `receiver@example.org` using its private key
|
52
|
+
# # Recipient must also have the senders public key to verify the signature
|
53
|
+
# IOStreams.reader('secure.gpg') do |stream|
|
54
|
+
# while data = stream.read(10)
|
55
|
+
# ap data
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# FAQ:
|
60
|
+
# - If you get not trusted errors
|
61
|
+
# gpg --edit-key sender@example.org
|
62
|
+
# Select highest level: 5
|
63
|
+
#
|
64
|
+
# Delete test keys:
|
65
|
+
# IOStreams::Pgp.delete_keys(email: 'sender@example.org', secret: true)
|
66
|
+
# IOStreams::Pgp.delete_keys(email: 'receiver@example.org', secret: true)
|
67
|
+
#
|
68
|
+
# Limitations
|
69
|
+
# - Designed for processing larger files since a process is spawned for each file processed.
|
70
|
+
# - For small in memory files or individual emails, use the 'opengpgme' library.
|
71
|
+
#
|
72
|
+
# Compression Performance:
|
73
|
+
# Running tests on an Early 2015 Macbook Pro Dual Core with Ruby v2.3.1
|
74
|
+
#
|
75
|
+
# Input file: test.log 3.6GB
|
76
|
+
# :none: size: 3.6GB write: 52s read: 45s
|
77
|
+
# :zip: size: 411MB write: 75s read: 31s
|
78
|
+
# :zlib: size: 241MB write: 66s read: 23s ( 756KB Memory )
|
79
|
+
# :bzip2: size: 129MB write: 430s read: 130s ( 5MB Memory )
|
80
|
+
module Pgp
|
81
|
+
autoload :Reader, 'io_streams/pgp/reader'
|
82
|
+
autoload :Writer, 'io_streams/pgp/writer'
|
83
|
+
|
84
|
+
class Failure < StandardError
|
85
|
+
end
|
86
|
+
|
87
|
+
# Generate a new ultimate trusted local public and private key
|
88
|
+
# Returns [String] the key id for the generated key
|
89
|
+
# Raises an exception if it fails to generate the key
|
90
|
+
#
|
91
|
+
# name: [String]
|
92
|
+
# Name of who owns the key, such as organization
|
93
|
+
#
|
94
|
+
# email: [String]
|
95
|
+
# Email address for the key
|
96
|
+
#
|
97
|
+
# comment: [String]
|
98
|
+
# Optional comment to add to the generated key
|
99
|
+
#
|
100
|
+
# passphrase [String]
|
101
|
+
# Optional passphrase to secure the key with.
|
102
|
+
# Highly Recommended.
|
103
|
+
# To generate a good passphrase:
|
104
|
+
# `SecureRandom.urlsafe_base64(128)`
|
105
|
+
#
|
106
|
+
# See `man gpg` for the remaining options
|
107
|
+
def self.generate_key(name:, email:, comment: nil, passphrase: nil, key_type: 'RSA', key_length: 4096, subkey_type: 'RSA', subkey_length: key_length, expire_date: nil)
|
108
|
+
Open3.popen2e('gpg --batch --gen-key') do |stdin, out, waith_thr|
|
109
|
+
stdin.puts "Key-Type: #{key_type}" if key_type
|
110
|
+
stdin.puts "Key-Length: #{key_length}" if key_length
|
111
|
+
stdin.puts "Subkey-Type: #{subkey_type}" if subkey_type
|
112
|
+
stdin.puts "Subkey-Length: #{subkey_length}" if subkey_length
|
113
|
+
stdin.puts "Name-Real: #{name}" if name
|
114
|
+
stdin.puts "Name-Comment: #{comment}" if comment
|
115
|
+
stdin.puts "Name-Email: #{email}" if email
|
116
|
+
stdin.puts "Expire-Date: #{expire_date}" if expire_date
|
117
|
+
stdin.puts "Passphrase: #{passphrase}" if passphrase
|
118
|
+
stdin.puts '%commit'
|
119
|
+
stdin.close
|
120
|
+
if waith_thr.value.success?
|
121
|
+
key_id = nil
|
122
|
+
out.each_line do |line|
|
123
|
+
if (line = line.chomp) =~ /^gpg: key ([0-9A-F]+) marked as ultimately trusted/
|
124
|
+
key_id = $1.to_i(16)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
key_id
|
128
|
+
else
|
129
|
+
raise(Pgp::Failure, "GPG Failed to generate key: #{out.read.chomp}")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Delete a secret and public keys using its email
|
135
|
+
# Returns false if no key was found
|
136
|
+
# Raises an exception if it fails to delete the key
|
137
|
+
#
|
138
|
+
# email: [String] Email address for the key
|
139
|
+
#
|
140
|
+
# public: [true|false]
|
141
|
+
# Whether to delete the public key
|
142
|
+
# Default: true
|
143
|
+
#
|
144
|
+
# secret: [true|false]
|
145
|
+
# Whether to delete the secret key
|
146
|
+
# Default: false
|
147
|
+
def self.delete_keys(email:, public: true, secret: false)
|
148
|
+
cmd = "for i in `gpg --with-colons --fingerprint #{email} | grep \"^fpr\" | cut -d: -f10`; do\n"
|
149
|
+
cmd << "gpg --batch --delete-secret-keys \"$i\" ;\n" if secret
|
150
|
+
cmd << "gpg --batch --delete-keys \"$i\" ;\n" if public
|
151
|
+
cmd << 'done'
|
152
|
+
Open3.popen2e(cmd) do |stdin, out, waith_thr|
|
153
|
+
output = out.read.chomp
|
154
|
+
if waith_thr.value.success?
|
155
|
+
return false if output =~ /(public key not found|No public key)/i
|
156
|
+
raise(Pgp::Failure, "GPG Failed to delete keys for #{email}: #{output}") if output.include?('error')
|
157
|
+
true
|
158
|
+
else
|
159
|
+
raise(Pgp::Failure, "GPG Failed calling gpg to delete secret keys for #{email}: #{output}")
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.has_key?(email:)
|
165
|
+
Open3.popen2e("gpg --list-keys --with-colons #{email}") do |stdin, out, waith_thr|
|
166
|
+
output = out.read.chomp
|
167
|
+
if waith_thr.value.success?
|
168
|
+
output.each_line do |line|
|
169
|
+
return true if line.match(/\Auid.*::([^\:]*):\Z/)
|
170
|
+
end
|
171
|
+
false
|
172
|
+
else
|
173
|
+
return false if output =~ /(public key not found|No public key)/i
|
174
|
+
raise(Pgp::Failure, "GPG Failed calling gpg to list keys for #{email}: #{output}")
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Returns [String] the key for the supplied email address
|
180
|
+
#
|
181
|
+
# email: [String] Email address for requested key
|
182
|
+
#
|
183
|
+
# ascii: [true|false]
|
184
|
+
# Whether to export as ASCII text instead of binary format
|
185
|
+
# Default: true
|
186
|
+
#
|
187
|
+
# secret: [true|false]
|
188
|
+
# Whether to export the private key
|
189
|
+
# Default: false
|
190
|
+
def self.export(email:, ascii: true, secret: false)
|
191
|
+
armor = ascii ? ' --armor' : nil
|
192
|
+
cmd = secret ? '--export-secret-keys' : '--export'
|
193
|
+
out, err, status = Open3.capture3("gpg#{armor} #{cmd} #{email}", binmode: true)
|
194
|
+
if status.success? && out.length > 0
|
195
|
+
out
|
196
|
+
else
|
197
|
+
raise(Pgp::Failure, "GPG Failed reading key: #{email}: #{err} #{out}")
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Imports the supplied public/private key
|
202
|
+
# Returns [String] the output returned from the import command
|
203
|
+
def self.import(key)
|
204
|
+
out, err, status = Open3.capture3('gpg --import', binmode: true, stdin_data: key)
|
205
|
+
if status.success? && out.length > 0
|
206
|
+
out
|
207
|
+
else
|
208
|
+
raise(Pgp::Failure, "GPG Failed importing key: #{err} #{out}")
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module IOStreams
|
4
|
+
module Pgp
|
5
|
+
class Reader
|
6
|
+
# Passphrase to use to open the private key to decrypt the received file
|
7
|
+
def self.default_passphrase=(default_passphrase)
|
8
|
+
@default_passphrase = default_passphrase
|
9
|
+
end
|
10
|
+
|
11
|
+
# Read from a PGP / GPG file or stream, decompressing the contents as it is read
|
12
|
+
# file_name_or_io: [String|IO]
|
13
|
+
# Name of file to read from
|
14
|
+
# Or, the IO stream to receive the decrypted contents
|
15
|
+
# passphrase: [String]
|
16
|
+
# Pass phrase for private key to decrypt the file with
|
17
|
+
def self.open(file_name_or_io, passphrase: self.default_passphrase, binary: true)
|
18
|
+
raise(ArgumentError, 'Missing both passphrase and IOStreams::Pgp::Reader.default_passphrase') unless passphrase
|
19
|
+
|
20
|
+
if IOStreams.reader_stream?(file_name_or_io)
|
21
|
+
raise(NotImplementedError, 'Can only PGP Decrypt directly from a file name. Input streams are not yet supported.')
|
22
|
+
else
|
23
|
+
# Read decrypted contents from stdout
|
24
|
+
Open3.popen3("gpg --batch --no-tty --yes --decrypt --passphrase-fd 0 #{file_name_or_io}") do |stdin, stdout, stderr, waith_thr|
|
25
|
+
stdin.puts(passphrase) if passphrase
|
26
|
+
stdin.close
|
27
|
+
result =
|
28
|
+
begin
|
29
|
+
stdout.binmode if binary
|
30
|
+
yield(stdout)
|
31
|
+
rescue Errno::EPIPE
|
32
|
+
# Ignore broken pipe because gpg terminates early due to an error
|
33
|
+
raise(Pgp::Failure, "GPG Failed reading from encrypted file: #{file_name_or_io}: #{stderr.read.chomp}")
|
34
|
+
end
|
35
|
+
raise(Pgp::Failure, "GPG Failed to decrypt file: #{file_name_or_io}: #{stderr.read.chomp}") unless waith_thr.value.success?
|
36
|
+
result
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
@default_passphrase = nil
|
44
|
+
|
45
|
+
def self.default_passphrase
|
46
|
+
@default_passphrase
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module IOStreams
|
4
|
+
module Pgp
|
5
|
+
class Writer
|
6
|
+
# Sign all encrypted files with this users key.
|
7
|
+
# Default: Do not sign encyrpted files.
|
8
|
+
def self.default_signer=(default_signer)
|
9
|
+
@default_signer = default_signer
|
10
|
+
end
|
11
|
+
|
12
|
+
# Passphrase to use to open the private key when signing the file.
|
13
|
+
# Default: None.
|
14
|
+
def self.default_signer_passphrase=(default_signer_passphrase)
|
15
|
+
@default_signer_passphrase = default_signer_passphrase
|
16
|
+
end
|
17
|
+
|
18
|
+
# Write to a PGP / GPG file or stream, encrypting the contents as it is written
|
19
|
+
#
|
20
|
+
# file_name_or_io: [String|IO]
|
21
|
+
# Name of file to write to.
|
22
|
+
# Or, the IO stream to write the encrypted contents to.
|
23
|
+
#
|
24
|
+
# recipient: [String]
|
25
|
+
# Email of user for which to encypt the file.
|
26
|
+
#
|
27
|
+
# signer: [String]
|
28
|
+
# Name of user with which to sign the encypted file.
|
29
|
+
# Default: default_signer or do not sign.
|
30
|
+
#
|
31
|
+
# signer_passphrase: [String]
|
32
|
+
# Passphrase to use to open the private key when signing the file.
|
33
|
+
# Default: default_signer_passphrase
|
34
|
+
#
|
35
|
+
# binary: [true|false]
|
36
|
+
# Whether to write binary data.
|
37
|
+
# Default: true
|
38
|
+
#
|
39
|
+
# compression: [:none|:zip|:zlib|:bzip2]
|
40
|
+
# Note: Standard PGP only supports :zip.
|
41
|
+
# :zlib is better than zip.
|
42
|
+
# :bzip2 is best, but uses a lot of memory.
|
43
|
+
# Default: :zip
|
44
|
+
#
|
45
|
+
# compress_level: [Integer]
|
46
|
+
# Compression level
|
47
|
+
# Default: 6
|
48
|
+
def self.open(file_name_or_io, recipient:, signer: default_signer, signer_passphrase: default_signer_passphrase, binary: true, compression: :zip, compress_level: 6)
|
49
|
+
compress_level = 0 if compression == :none
|
50
|
+
if IOStreams.writer_stream?(file_name_or_io)
|
51
|
+
raise(NotImplementedError, 'Can only PGP Encrypt directly to a file name. Output to streams are not yet supported.')
|
52
|
+
else
|
53
|
+
# Write to stdin, with encrypted contents being written to the file
|
54
|
+
cmd = "gpg --batch --no-tty --yes --encrypt"
|
55
|
+
cmd << " --sign --local-user \"#{signer}\"" if signer
|
56
|
+
cmd << " --passphrase \"#{signer_passphrase}\"" if signer_passphrase
|
57
|
+
cmd << " -z #{compress_level}" if compress_level != 6
|
58
|
+
cmd << " --compress-algo #{compression}" unless compression == :none
|
59
|
+
cmd << " --recipient \"#{recipient}\" -o \"#{file_name_or_io}\""
|
60
|
+
Open3.popen2e(cmd) do |stdin, out, waith_thr|
|
61
|
+
begin
|
62
|
+
stdin.binmode if binary
|
63
|
+
yield(stdin)
|
64
|
+
stdin.close
|
65
|
+
rescue Errno::EPIPE
|
66
|
+
# Ignore broken pipe because gpg terminates early due to an error
|
67
|
+
::File.delete(file_name_or_io)
|
68
|
+
raise(Pgp::Failure, "GPG Failed writing to encrypted file: #{file_name_or_io}: #{out.read.chomp}")
|
69
|
+
end
|
70
|
+
unless waith_thr.value.success?
|
71
|
+
::File.delete(file_name_or_io) if ::File.exist?(file_name_or_io)
|
72
|
+
raise(Pgp::Failure, "GPG Failed to create encrypted file: #{file_name_or_io}: #{out.read.chomp}")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
@default_signer_passphrase = nil
|
81
|
+
@default_signer = nil
|
82
|
+
|
83
|
+
def self.default_signer_passphrase
|
84
|
+
@default_signer_passphrase
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.default_signer
|
88
|
+
@default_signer
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module IOStreams
|
2
|
+
# Example:
|
3
|
+
# IOStreams::SFTP::Reader.open(
|
4
|
+
# 'file.txt',
|
5
|
+
# user: 'jbloggs',
|
6
|
+
# password: 'secret',
|
7
|
+
# host: 'example.org'
|
8
|
+
# ) do |input|
|
9
|
+
# puts input.read
|
10
|
+
# end
|
11
|
+
module SFTP
|
12
|
+
class Reader
|
13
|
+
include SemanticLogger::Loggable if defined?(SemanticLogger)
|
14
|
+
|
15
|
+
# Stream to a remote file over sftp.
|
16
|
+
#
|
17
|
+
# file_name: [String]
|
18
|
+
# Name of file to write to.
|
19
|
+
#
|
20
|
+
# user: [String]
|
21
|
+
# Name of user to login with.
|
22
|
+
#
|
23
|
+
# password: [String]
|
24
|
+
# Password for the user.
|
25
|
+
#
|
26
|
+
# host: [String]
|
27
|
+
# Name of the host to connect to.
|
28
|
+
#
|
29
|
+
# port: [Integer]
|
30
|
+
# Port to connect to at the above host.
|
31
|
+
#
|
32
|
+
# binary [true|false]
|
33
|
+
# Whether to write in binary mode
|
34
|
+
# Default: true
|
35
|
+
#
|
36
|
+
# options: [Hash]
|
37
|
+
# Any options supported by Net::SSH.start
|
38
|
+
#
|
39
|
+
# Note:
|
40
|
+
# - Net::SFTP::StatusException means the file could not be read
|
41
|
+
def self.open(file_name, user:, password:, host:, port: 22, binary: true, options: {}, &block)
|
42
|
+
raise(NotImplementedError, 'Can only SFTP directly to a file name, not another stream.') if IOStreams.writer_stream?(file_name)
|
43
|
+
|
44
|
+
begin
|
45
|
+
require 'net/sftp' unless defined?(Net::SFTP)
|
46
|
+
rescue LoadError => e
|
47
|
+
raise(LoadError, "Please install the 'net-sftp' gem for SFTP streaming support. #{e.message}")
|
48
|
+
end
|
49
|
+
|
50
|
+
options = options.dup
|
51
|
+
options[:logger] ||= self.logger if defined?(SemanticLogger)
|
52
|
+
options[:port] ||= 22
|
53
|
+
options[:max_pkt_size] ||= 65536
|
54
|
+
options[:password] = password
|
55
|
+
options[:port] = port
|
56
|
+
mode = binary ? 'rb' : 'r'
|
57
|
+
|
58
|
+
result = nil
|
59
|
+
Net::SFTP.start(host, user, options) do |sftp|
|
60
|
+
result = sftp.file.open(file_name, mode, &block)
|
61
|
+
end
|
62
|
+
result
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module IOStreams
|
2
|
+
# Example:
|
3
|
+
# IOStreams::SFTP::Writer.open('file.txt',
|
4
|
+
# user: 'jbloggs',
|
5
|
+
# password: 'secret',
|
6
|
+
# host: 'example.org',
|
7
|
+
# options: {compression: false}
|
8
|
+
# ) do |output|
|
9
|
+
# output.write('Hello World')
|
10
|
+
# end
|
11
|
+
module SFTP
|
12
|
+
class Writer
|
13
|
+
include SemanticLogger::Loggable if defined?(SemanticLogger)
|
14
|
+
|
15
|
+
# Stream to a remote file over sftp.
|
16
|
+
#
|
17
|
+
# file_name: [String]
|
18
|
+
# Name of file to write to.
|
19
|
+
#
|
20
|
+
# user: [String]
|
21
|
+
# Name of user to login with.
|
22
|
+
#
|
23
|
+
# password: [String]
|
24
|
+
# Password for the user.
|
25
|
+
#
|
26
|
+
# host: [String]
|
27
|
+
# Name of the host to connect to.
|
28
|
+
#
|
29
|
+
# port: [Integer]
|
30
|
+
# Port to connect to at the above host.
|
31
|
+
#
|
32
|
+
# mkdir [true|false]
|
33
|
+
# Whether to create the output directory on the target system before writing the file.
|
34
|
+
# The path is created recursively if any portions of the path that are missing.
|
35
|
+
# Default: false
|
36
|
+
#
|
37
|
+
# binary [true|false]
|
38
|
+
# Whether to write in binary mode
|
39
|
+
# Default: true
|
40
|
+
#
|
41
|
+
# options: [Hash]
|
42
|
+
# Any options supported by Net::SSH.start
|
43
|
+
def self.open(file_name, user:, password:, host:, port: 22, mkdir: false, binary: true, options: {}, &block)
|
44
|
+
raise(NotImplementedError, 'Can only SFTP directly to a file name, not another stream.') if IOStreams.writer_stream?(file_name)
|
45
|
+
|
46
|
+
begin
|
47
|
+
require 'net/sftp' unless defined?(Net::SFTP)
|
48
|
+
rescue LoadError => e
|
49
|
+
raise(LoadError, "Please install the 'net-sftp' gem for SFTP streaming support. #{e.message}")
|
50
|
+
end
|
51
|
+
|
52
|
+
options = options.dup
|
53
|
+
options[:logger] ||= logger if defined?(SemanticLogger)
|
54
|
+
options[:port] ||= 22
|
55
|
+
options[:max_pkt_size] ||= 65536
|
56
|
+
options[:password] = password
|
57
|
+
options[:port] = port
|
58
|
+
mode = binary ? 'wb' : 'w'
|
59
|
+
|
60
|
+
Net::SFTP.start(host, user, options) do |sftp|
|
61
|
+
sftp.session.exec!("mkdir -p '#{File.dirname(file_name)}'") if mkdir
|
62
|
+
sftp.file.open(file_name, mode, & block)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/io_streams/streams.rb
CHANGED
@@ -92,7 +92,7 @@ module IOStreams
|
|
92
92
|
# RocketJob::Formatter::Formats.streams_for_file_name('myfile.csv')
|
93
93
|
# => [ :file ]
|
94
94
|
def streams_for_file_name(file_name)
|
95
|
-
raise ArgumentError.new("RocketJob Cannot detect file format when uploading to stream: #{file_name.inspect}") if
|
95
|
+
raise ArgumentError.new("RocketJob Cannot detect file format when uploading to stream: #{file_name.inspect}") if reader_stream?(file_name)
|
96
96
|
|
97
97
|
parts = file_name.split('.')
|
98
98
|
extensions = []
|
data/lib/io_streams/version.rb
CHANGED
@@ -13,17 +13,13 @@ module IOStreams
|
|
13
13
|
# puts line
|
14
14
|
# end
|
15
15
|
# end
|
16
|
-
def self.open(file_name_or_io,
|
16
|
+
def self.open(file_name_or_io, buffer_size: 65536, &block)
|
17
17
|
begin
|
18
18
|
require 'creek' unless defined?(Creek::Book)
|
19
19
|
rescue LoadError => e
|
20
20
|
raise(LoadError, "Please install the 'creek' gem for xlsx streaming support. #{e.message}")
|
21
21
|
end
|
22
22
|
|
23
|
-
options = options.dup
|
24
|
-
buffer_size = options.delete(:buffer_size) || 65536
|
25
|
-
raise(ArgumentError, "Unknown IOStreams::Xlsx::Reader option: #{options.inspect}") if options.size > 0
|
26
|
-
|
27
23
|
if IOStreams.reader_stream?(file_name_or_io)
|
28
24
|
temp_file = Tempfile.new('rocket_job_xlsx')
|
29
25
|
file_name = temp_file.to_path
|
@@ -12,11 +12,7 @@ module IOStreams
|
|
12
12
|
# puts data
|
13
13
|
# end
|
14
14
|
# end
|
15
|
-
def self.open(file_name_or_io,
|
16
|
-
options = options.dup
|
17
|
-
buffer_size = options.delete(:buffer_size) || 65536
|
18
|
-
raise(ArgumentError, "Unknown IOStreams::Zip::Reader option: #{options.inspect}") if options.size > 0
|
19
|
-
|
15
|
+
def self.open(file_name_or_io, buffer_size: 65536, &block)
|
20
16
|
if !defined?(JRuby) && !defined?(::Zip)
|
21
17
|
# MRI needs Ruby Zip, since it only has native support for GZip
|
22
18
|
begin
|
@@ -7,8 +7,7 @@ module IOStreams
|
|
7
7
|
# file_name_or_io [String]
|
8
8
|
# Full path and filename for the output zip file
|
9
9
|
#
|
10
|
-
#
|
11
|
-
# :file_name [String]
|
10
|
+
# zip_file_name: [String]
|
12
11
|
# Name of the file within the Zip Stream
|
13
12
|
#
|
14
13
|
# The stream supplied to the block only responds to #write
|
@@ -22,14 +21,9 @@ module IOStreams
|
|
22
21
|
# Notes:
|
23
22
|
# - Since Zip cannot write to streams, if a stream is supplied, a temp file
|
24
23
|
# is automatically created under the covers
|
25
|
-
def self.open(file_name_or_io,
|
26
|
-
options = options.dup
|
27
|
-
zip_file_name = options.delete(:file_name) || options.delete(:zip_file_name)
|
28
|
-
buffer_size = options.delete(:buffer_size) || 65536
|
29
|
-
raise(ArgumentError, "Unknown IOStreams::Zip::Writer option: #{options.inspect}") if options.size > 0
|
30
|
-
|
24
|
+
def self.open(file_name_or_io, zip_file_name: nil, buffer_size: 65536, &block)
|
31
25
|
# Default the name of the file within the zip to the supplied file_name without the zip extension
|
32
|
-
zip_file_name = file_name_or_io.to_s[0..-5] if zip_file_name.nil? && !
|
26
|
+
zip_file_name = file_name_or_io.to_s[0..-5] if zip_file_name.nil? && !IOStreams.writer_stream?(file_name_or_io) && (file_name_or_io =~ /\.(zip)\z/)
|
33
27
|
zip_file_name ||= 'file'
|
34
28
|
|
35
29
|
if !defined?(JRuby) && !defined?(::Zip)
|
data/lib/iostreams.rb
CHANGED
@@ -12,6 +12,11 @@ module IOStreams
|
|
12
12
|
autoload :Reader, 'io_streams/gzip/reader'
|
13
13
|
autoload :Writer, 'io_streams/gzip/writer'
|
14
14
|
end
|
15
|
+
autoload :Pgp, 'io_streams/pgp'
|
16
|
+
module SFTP
|
17
|
+
autoload :Reader, 'io_streams/sftp/reader'
|
18
|
+
autoload :Writer, 'io_streams/sftp/writer'
|
19
|
+
end
|
15
20
|
module Zip
|
16
21
|
autoload :Reader, 'io_streams/zip/reader'
|
17
22
|
autoload :Writer, 'io_streams/zip/writer'
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
module Streams
|
4
|
+
class PgpReaderTest < Minitest::Test
|
5
|
+
describe IOStreams::Pgp::Reader do
|
6
|
+
before do
|
7
|
+
file_name = File.join(File.dirname(__FILE__), 'files', 'text.txt')
|
8
|
+
@data = File.read(file_name)
|
9
|
+
@temp_file = Tempfile.new('iostreams')
|
10
|
+
@file_name = @temp_file.to_path
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
@temp_file.delete if @temp_file
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '.open' do
|
18
|
+
it 'reads encrypted file' do
|
19
|
+
IOStreams::Pgp::Writer.open(@file_name, recipient: 'receiver@example.org') do |io|
|
20
|
+
io.write(@data)
|
21
|
+
end
|
22
|
+
|
23
|
+
result = IOStreams::Pgp::Reader.open(@file_name, passphrase: 'receiver_passphrase') { |file| file.read }
|
24
|
+
assert_equal @data, result
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'fails with bad passphrase' do
|
28
|
+
assert_raises IOStreams::Pgp::Failure do
|
29
|
+
IOStreams::Pgp::Reader.open(@file_name, passphrase: 'BAD') { |file| file.read }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'fails with stream input' do
|
34
|
+
io = StringIO.new
|
35
|
+
assert_raises NotImplementedError do
|
36
|
+
IOStreams::Pgp::Reader.open(io, passphrase: 'BAD') { |file| file.read }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
module Streams
|
4
|
+
class PgpWriterTest < Minitest::Test
|
5
|
+
describe IOStreams::Pgp::Writer do
|
6
|
+
before do
|
7
|
+
file_name = File.join(File.dirname(__FILE__), 'files', 'text.txt')
|
8
|
+
@data = File.read(file_name)
|
9
|
+
|
10
|
+
@temp_file = Tempfile.new('iostreams')
|
11
|
+
@file_name = @temp_file.to_path
|
12
|
+
end
|
13
|
+
|
14
|
+
after do
|
15
|
+
@temp_file.delete if @temp_file
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '.open' do
|
19
|
+
it 'writes encrypted text file' do
|
20
|
+
IOStreams::Pgp::Writer.open(@file_name, recipient: 'receiver@example.org', binary: false) do |io|
|
21
|
+
io.write(@data)
|
22
|
+
end
|
23
|
+
|
24
|
+
result = IOStreams::Pgp::Reader.open(@file_name, passphrase: 'receiver_passphrase', binary: false) { |file| file.read }
|
25
|
+
assert_equal @data, result
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'writes encrypted binary file' do
|
29
|
+
binary_file_name = File.join(File.dirname(__FILE__), 'files', 'spreadsheet.xlsx')
|
30
|
+
binary_data = File.open(binary_file_name, 'rb') { |file| file.read }
|
31
|
+
|
32
|
+
File.open(binary_file_name, 'rb') do |input|
|
33
|
+
IOStreams::Pgp::Writer.open(@file_name, recipient: 'receiver@example.org') do |output|
|
34
|
+
IOStreams.copy(input, output, 65535)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
result = IOStreams::Pgp::Reader.open(@file_name, passphrase: 'receiver_passphrase') { |file| file.read }
|
39
|
+
assert_equal binary_data, result
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'writes and signs encrypted file' do
|
43
|
+
IOStreams::Pgp::Writer.open(@file_name, recipient: 'receiver@example.org', signer: 'sender@example.org', signer_passphrase: 'sender_passphrase') do |io|
|
44
|
+
io.write(@data)
|
45
|
+
end
|
46
|
+
|
47
|
+
result = IOStreams::Pgp::Reader.open(@file_name, passphrase: 'receiver_passphrase') { |file| file.read }
|
48
|
+
assert_equal @data, result
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'fails with bad signer passphrase' do
|
52
|
+
assert_raises IOStreams::Pgp::Failure do
|
53
|
+
IOStreams::Pgp::Writer.open(@file_name, recipient: 'receiver@example.org', signer: 'sender@example.org', signer_passphrase: 'BAD') do |io|
|
54
|
+
io.write(@data)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'fails with bad recipient' do
|
60
|
+
assert_raises IOStreams::Pgp::Failure do
|
61
|
+
IOStreams::Pgp::Writer.open(@file_name, recipient: 'BAD@example.org', signer: 'sender@example.org', signer_passphrase: 'sender_passphrase') do |io|
|
62
|
+
io.write(@data)
|
63
|
+
# Allow process to terminate
|
64
|
+
sleep 1
|
65
|
+
io.write(@data)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'fails with bad signer' do
|
71
|
+
assert_raises IOStreams::Pgp::Failure do
|
72
|
+
IOStreams::Pgp::Writer.open(@file_name, recipient: 'receiver@example.org', signer: 'BAD@example.org', signer_passphrase: 'sender_passphrase') do |io|
|
73
|
+
io.write(@data)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'fails with stream output' do
|
79
|
+
io = StringIO.new
|
80
|
+
assert_raises NotImplementedError do
|
81
|
+
IOStreams::Pgp::Writer.open(io, recipient: 'receiver@example.org') do |io|
|
82
|
+
io.write(@data)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -17,3 +17,12 @@ SymmetricEncryption.cipher = SymmetricEncryption::Cipher.new(
|
|
17
17
|
encoding: :base64strict
|
18
18
|
)
|
19
19
|
|
20
|
+
# Test PGP Keys
|
21
|
+
unless IOStreams::Pgp.has_key?(email: 'sender@example.org')
|
22
|
+
puts 'Generating test PGP key: sender@example.org'
|
23
|
+
IOStreams::Pgp.generate_key(name: 'Sender', email: 'sender@example.org', passphrase: 'sender_passphrase', key_length: 2048)
|
24
|
+
end
|
25
|
+
unless IOStreams::Pgp.has_key?(email: 'receiver@example.org')
|
26
|
+
puts 'Generating test PGP key: receiver@example.org'
|
27
|
+
IOStreams::Pgp.generate_key(name: 'Receiver', email: 'receiver@example.org', passphrase: 'receiver_passphrase', key_length: 2048)
|
28
|
+
end
|
data/test/zip_writer_test.rb
CHANGED
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iostreams
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Reid Morrison
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: symmetric-encryption
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '3.0'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '3.0'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: concurrent-ruby
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -56,6 +42,11 @@ files:
|
|
56
42
|
- lib/io_streams/gzip/reader.rb
|
57
43
|
- lib/io_streams/gzip/writer.rb
|
58
44
|
- lib/io_streams/io_streams.rb
|
45
|
+
- lib/io_streams/pgp.rb
|
46
|
+
- lib/io_streams/pgp/reader.rb
|
47
|
+
- lib/io_streams/pgp/writer.rb
|
48
|
+
- lib/io_streams/sftp/reader.rb
|
49
|
+
- lib/io_streams/sftp/writer.rb
|
59
50
|
- lib/io_streams/streams.rb
|
60
51
|
- lib/io_streams/version.rb
|
61
52
|
- lib/io_streams/xlsx/reader.rb
|
@@ -76,11 +67,13 @@ files:
|
|
76
67
|
- test/files/text.zip
|
77
68
|
- test/gzip_reader_test.rb
|
78
69
|
- test/gzip_writer_test.rb
|
70
|
+
- test/pgp_reader_test.rb
|
71
|
+
- test/pgp_writer_test.rb
|
79
72
|
- test/test_helper.rb
|
80
73
|
- test/xlsx_reader_test.rb
|
81
74
|
- test/zip_reader_test.rb
|
82
75
|
- test/zip_writer_test.rb
|
83
|
-
homepage: https://github.com/rocketjob/
|
76
|
+
homepage: https://github.com/rocketjob/iostreams
|
84
77
|
licenses:
|
85
78
|
- Apache-2.0
|
86
79
|
metadata: {}
|
@@ -92,7 +85,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
92
85
|
requirements:
|
93
86
|
- - ">="
|
94
87
|
- !ruby/object:Gem::Version
|
95
|
-
version: '
|
88
|
+
version: '2.1'
|
96
89
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
90
|
requirements:
|
98
91
|
- - ">="
|
@@ -100,10 +93,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
93
|
version: '0'
|
101
94
|
requirements: []
|
102
95
|
rubyforge_project:
|
103
|
-
rubygems_version: 2.
|
96
|
+
rubygems_version: 2.5.1
|
104
97
|
signing_key:
|
105
98
|
specification_version: 4
|
106
|
-
summary: Ruby
|
99
|
+
summary: Ruby file streaming. Supports Text, Zip, Gzip, Xlsx, csv, PGP / GPG and Symmetric
|
100
|
+
Encryption.
|
107
101
|
test_files:
|
108
102
|
- test/csv_reader_test.rb
|
109
103
|
- test/csv_writer_test.rb
|
@@ -119,6 +113,8 @@ test_files:
|
|
119
113
|
- test/files/text.zip
|
120
114
|
- test/gzip_reader_test.rb
|
121
115
|
- test/gzip_writer_test.rb
|
116
|
+
- test/pgp_reader_test.rb
|
117
|
+
- test/pgp_writer_test.rb
|
122
118
|
- test/test_helper.rb
|
123
119
|
- test/xlsx_reader_test.rb
|
124
120
|
- test/zip_reader_test.rb
|