Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ronin compress command #184

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

AI-Mozi
Copy link
Member

@AI-Mozi AI-Mozi commented Jan 3, 2024

@AI-Mozi
Copy link
Member Author

AI-Mozi commented Jan 3, 2024

@postmodern 0.1 version of this PR.
Let me know if at least my idea is correct.
I'm not sure if I understand CommandKit correctly 😄

Copy link
Member

@postmodern postmodern left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Submitted a bunch of comments and suggestions. There's multiple ways we could implement this command. This is a good start!

#
# [FILE ...] Optional file(s) to compress
#
class Compress < StringMethodsCommand
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we only want to support processing files, then probably want to inherit from FileProcessorCommand, and define a process_input(io) method to compress the input File or from stdin.

If we do want to support accepting arbitrary Strings (ex: ronin compress --string "hello world"), then we should inherit from StringProcessorCommand.

StringMethodsCommand is more meant to be used by commands which simply map options to certain monkey-patch methods that are added to String (ex: ronin decode --base64 -> String#base64_decode).

option :gzip, short: '-g',
desc: 'gzip compresses the data' do
@format = :gzip
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're going to support --gzip, we should also support a --zlib option. ronin-support also happens to have a Ronin::Support::Compression::Zlib module we can use.

#
# The compression format.
#
# @return Symbol
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to explicitly list the possible values @return [:gzip, :tar, :zip].

lib/ronin/cli/commands/compress.rb Outdated Show resolved Hide resolved
end

def extension
return "gz" if @format == :gzip
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could probably set an @ext instance variable in the option blocks, along with @format. This way we follow the "Tell, don't ask" principle.

end

def process_file(file)
compression_class.send(@format, "#{file}.#{extension}") do |c|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename @format to @method or @compression_method if we're going to send() it.


def process_file(file)
compression_class.send(@format, "#{file}.#{extension}") do |c|
c.write File.read(file)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The files being compressed could be very very large. Probably more efficient to open the input file and read chunks of data from it using input.readpartial(4096) and write those to the output stream.

lib/ronin/cli/commands/compress.rb Show resolved Hide resolved
end

def process_file(file)
compression_class.send(@format, "#{file}.#{extension}") do |c|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So here's a question, if you gave this command the --zip option and multiple files, would the user expect each file to be compressed into it's own .zip file, or all files to be compressed into one zip file?

@postmodern
Copy link
Member

postmodern commented Jan 5, 2024

@AI-Mozi what do you think about adding separate ronin archive and ronin unarchive commands? This might help with the bifurcating logic that uses Ronin::Support::Compression or Ronin::Support::Archive. ronin archive/ronin unarchive would probably inherit from FileProcessorCommand, while ronin compress/ronin uncompress would inherit from StringProcessorCommand that could accept both --string values and FILEs to allow specifying a raw zlib/gzip compressed string or a .gz file.

@AI-Mozi
Copy link
Member Author

AI-Mozi commented Jan 5, 2024

It's definitely a good idea!

Comment on lines 58 to 64
option :name, short: '-n',
value: {
type: String
},
desc: 'compressed file name' do |name|
@compressed_file_name = name
end
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add an option for specifying compressed file name.

# The path to the file.
#
def process_file(file)
raise "Files can be compressed using gzip only" if @compression_method != :gzip
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we raise an error, or just print some message and return?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Call print_error and exit(1), or call print_error and return if you think the error can be printed but the command should continue processing arguments.

Comment on lines 131 to 132
rescue EOFError
rescue IOError => e
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rubocop does not like empty rescue :/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, there should be no IO errors when reading the file. I suspect file.eof? should make the rescues unnecessary.


Ronin::Support::Compression.gzip(compressed_file_name) do |gz|
files.each do |file|
File.open(file, 'rb') do |f|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid single letter block arguments. Rename file to like path or file_path, and f to file or io.

Comment on lines 131 to 132
rescue EOFError
rescue IOError => e
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, there should be no IO errors when reading the file. I suspect file.eof? should make the rescues unnecessary.

if files.empty?
super(*files)
else
raise "Files can be compressed using gzip only" if @compression_method != :gzip
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do a print_error and call exit(1) if invalid combinations of options or arguments are given.

# The compressed string.
#
def process_string(string)
string.send(@compression_method)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I try to avoid calling the monkey-patched methods when possible. Might be better to do Support::Compression.send(@compression_method,string).

# @return String
#
def compressed_file_name
@compressed_file_name || "ronin_compressed_#{Time.now.to_i}.gz"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd get rid of the timestamp, or maybe add an option for it. I think a ronin compress command should behave exactly like gzip and just append the .gz extension on the given file name.

# The path to the file.
#
def process_file(file)
raise "Files can be compressed using gzip only" if @compression_method != :gzip
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Call print_error and exit(1), or call print_error and return if you think the error can be printed but the command should continue processing arguments.

if files.empty?
super(*files)
else
raise "Files can be compressed using gzip only" if @compression_method != :gzip
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could support compressing files using SupportCompression.zlib_deflate. Just read the entire file using File.read, compress the String, and write it back out using File.binwrite.

else
raise "Files can be compressed using gzip only" if @compression_method != :gzip

Ronin::Support::Compression.gzip(compressed_file_name) do |gz|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also why are there two Ronin::Support::Compression.gzip(compressed_file_name) blocks? One in run and another in process_file?

# File arguments.
#
def run(*files)
if files.empty?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be unless files.empty? or if !files.empty??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about it like if there is file given as an argument, compress it, otherwise go to StringProcessorCommand#run else statement and process @input_values.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh.. now that I read it, it works different than i thought 😅

Copy link
Member

@postmodern postmodern Jan 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm maybe it might be easier to just write all files and strings into a common Support::Compression::GZip output stream? Although, we would have to have different logic for the Compression::Zlib code.

Another idea would be to define separate process_file and process_string methods. The process_file method would read the File and write the gziped/zlib'ed output to a .gz or .zlib file. The process_string method would instead gzip/zlib compress a String and write it to stdout; maybe also support a common --output option?

@AI-Mozi
Copy link
Member Author

AI-Mozi commented Apr 18, 2024

@postmodern It's been a while. I did some changes. Let me know what do you thing and we can discuss that later :D

@postmodern
Copy link
Member

@AI-Mozi I'm not sure about how the compression and uncompress commands should handle multiple files. Whether they should compress/uncompress each file individually, or concat the output into a single output file or print to stdout, etc. Unless we can come up with a useful and logical solution, maybe we should hold off on these commands.

I do however think there should be a single ronin archive command that has --list, --extract, --create options.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants