iostreams 0.9.1 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|