Skip to content

Instantly share code, notes, and snippets.

@mkaschenko
Last active November 25, 2023 14:15
Show Gist options
  • Save mkaschenko/10949393 to your computer and use it in GitHub Desktop.
Save mkaschenko/10949393 to your computer and use it in GitHub Desktop.
A class developed via TDD
# Year 2014
# encoding: UTF-8
require 'export'
require 'export/secure_name'
require 'active_support/core_ext/object/blank'
describe Export::SecureName do
subject { described_class.new('name', '.ext') }
it '#initialize' do
expect(subject.name).to eql('name')
expect(subject.ext).to eql('.ext')
instance = described_class.new('name')
expect(instance.ext).to eql('')
end
it 'supports reading and writing a name' do
subject.name = 'foo'
expect(subject.name).to eql('foo')
end
it 'supports reading and writing an ext' do
expect { subject.ext = nil }.to raise_error(ArgumentError, "ext can't be nil")
subject.ext = 'txt'
expect(subject.ext).to eql('.txt')
subject.ext = '.txt'
expect(subject.ext).to eql('.txt')
subject.ext = 'tar.gz'
expect(subject.ext).to eql('.tar.gz')
subject.ext = ''
expect(subject.ext).to eql('')
end
context '#make' do
it 'sanitizes output name' do
subject.name = '/foobar/abc \/:*?"<>|.rb'; subject.ext = '.txt'
expect(subject.make).to eql('_foobar_abc _________.rb.txt')
subject.name = '...'; subject.ext = ''
expect(subject.make).to eql('_...')
subject.name = ''; subject.ext = ''
expect(subject.make).to eql('unnamed')
end
it 'limits length of output name' do
too_long_name = 'l' * 300
good_enough_name = 'l' * 255
good_enough_name_with_ext = 'l' * 251 + '.txt'
subject.name = too_long_name; subject.ext = ''
expect(subject.make).to eql(good_enough_name)
subject.name = too_long_name; subject.ext = '.txt'
expect(subject.make).to eql(good_enough_name_with_ext)
# http://en.wikipedia.org/wiki/UTF-8#Codepage_layout
# "\u0580\u3042" => "րあ", "րあ".bytesize => 5
too_many_bytes_within_name = "\u0580\u3042" * 60
enough_bytes_within_name = "\u0580\u3042" * 51
subject.name = too_many_bytes_within_name; subject.ext = ''
expect(subject.make).to eql(enough_bytes_within_name)
too_long_cyrillic_name = 'д' * 150
good_enough_cyrillic_name = 'д' * 127
subject.name = too_long_cyrillic_name; subject.ext = ''
expect(subject.make).to eql(good_enough_cyrillic_name)
end
it 'sets right extension to output name' do
subject.name = 'File Name.txt'; subject.ext = ''
expect(subject.make).to eql('File Name.txt')
subject.name = '1.1 File Name.rb'; subject.ext = '.txt'
expect(subject.make).to eql('1.1 File Name.rb.txt')
subject.name = '1.1 File Name.rb.txt'; subject.ext = '.txt'
expect(subject.make).to eql('1.1 File Name.rb.txt')
end
end
end
class Export::SecureName
MAX_BYTE_NAME_LENGTH = 255
attr_accessor :name, :ext
def initialize(name, ext = '')
self.name = name
self.ext = ext
end
def ext=(value)
raise(ArgumentError, "ext can't be nil") if value.nil?
@ext = (value.empty? || value.start_with?('.')) ? value : ".#{value}"
end
def make
name = sanitize(self.name)
name = File.basename(name, ext)
name = name.byteslice(0, MAX_BYTE_NAME_LENGTH - ext.length)
name = name.encode('UTF-16', :invalid => :replace, :replace => '').encode('UTF-8')
name + ext
end
private
def sanitize(name)
name = name.gsub(sanitize_regexp, '_')
name = "_#{name}" if name =~ /\A\.+\z/
name = 'unnamed' if name.size == 0
name
end
def sanitize_regexp
/[\\\/:"\*\?<>\|]/ # Invalid filesystem characters
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment