ndr_support 3.1.1
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 +15 -0
- data/.gitignore +14 -0
- data/.rubocop.yml +27 -0
- data/.ruby-version +1 -0
- data/.travis.yml +22 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/Guardfile +16 -0
- data/LICENSE.txt +21 -0
- data/README.md +91 -0
- data/Rakefile +12 -0
- data/code_safety.yml +258 -0
- data/gemfiles/Gemfile.rails32 +6 -0
- data/gemfiles/Gemfile.rails32.lock +108 -0
- data/gemfiles/Gemfile.rails41 +6 -0
- data/gemfiles/Gemfile.rails41.lock +111 -0
- data/gemfiles/Gemfile.rails42 +6 -0
- data/gemfiles/Gemfile.rails42.lock +111 -0
- data/lib/ndr_support.rb +21 -0
- data/lib/ndr_support/array.rb +52 -0
- data/lib/ndr_support/concerns/working_days.rb +94 -0
- data/lib/ndr_support/date_and_time_extensions.rb +103 -0
- data/lib/ndr_support/daterange.rb +196 -0
- data/lib/ndr_support/fixnum/calculations.rb +15 -0
- data/lib/ndr_support/fixnum/julian_date_conversions.rb +14 -0
- data/lib/ndr_support/hash.rb +52 -0
- data/lib/ndr_support/integer.rb +12 -0
- data/lib/ndr_support/nil.rb +38 -0
- data/lib/ndr_support/ourdate.rb +97 -0
- data/lib/ndr_support/ourtime.rb +51 -0
- data/lib/ndr_support/regexp_range.rb +65 -0
- data/lib/ndr_support/safe_file.rb +185 -0
- data/lib/ndr_support/safe_path.rb +268 -0
- data/lib/ndr_support/string/cleaning.rb +136 -0
- data/lib/ndr_support/string/conversions.rb +137 -0
- data/lib/ndr_support/tasks.rb +1 -0
- data/lib/ndr_support/time/conversions.rb +13 -0
- data/lib/ndr_support/utf8_encoding.rb +72 -0
- data/lib/ndr_support/utf8_encoding/control_characters.rb +53 -0
- data/lib/ndr_support/utf8_encoding/force_binary.rb +44 -0
- data/lib/ndr_support/utf8_encoding/object_support.rb +31 -0
- data/lib/ndr_support/version.rb +5 -0
- data/lib/ndr_support/yaml/serialization_migration.rb +65 -0
- data/lib/tasks/audit_code.rake +423 -0
- data/ndr_support.gemspec +39 -0
- data/test/array_test.rb +20 -0
- data/test/concerns/working_days_test.rb +122 -0
- data/test/daterange_test.rb +194 -0
- data/test/fixnum/calculations_test.rb +28 -0
- data/test/hash_test.rb +84 -0
- data/test/integer_test.rb +14 -0
- data/test/nil_test.rb +40 -0
- data/test/ourdate_test.rb +27 -0
- data/test/ourtime_test.rb +27 -0
- data/test/regexp_range_test.rb +135 -0
- data/test/resources/filesystem_paths.yml +37 -0
- data/test/safe_file_test.rb +597 -0
- data/test/safe_path_test.rb +168 -0
- data/test/string/cleaning_test.rb +176 -0
- data/test/string/conversions_test.rb +353 -0
- data/test/test_helper.rb +41 -0
- data/test/time/conversions_test.rb +15 -0
- data/test/utf8_encoding/control_characters_test.rb +84 -0
- data/test/utf8_encoding/force_binary_test.rb +64 -0
- data/test/utf8_encoding_test.rb +170 -0
- data/test/yaml/serialization_test.rb +145 -0
- metadata +295 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'active_support/time'
|
2
|
+
require 'ndr_support/ourdate'
|
3
|
+
|
4
|
+
# Convert a string into a time value (timestamp)
|
5
|
+
# (helped by String.thetime)
|
6
|
+
class Ourtime
|
7
|
+
attr_reader :thetime
|
8
|
+
|
9
|
+
def initialize(x = nil)
|
10
|
+
if x.is_a?(Time)
|
11
|
+
@thetime = x
|
12
|
+
elsif x.is_a?(Date)
|
13
|
+
@thetime = x.to_time
|
14
|
+
elsif x.is_a?(String)
|
15
|
+
self.source = x
|
16
|
+
else
|
17
|
+
@thetime = nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
@thetime ? @thetime.to_time.to_s(:ui) : ''
|
23
|
+
end
|
24
|
+
|
25
|
+
def empty?
|
26
|
+
# An unspecified time will be empty. A valid or invalid time will not.
|
27
|
+
@thetime.nil? && @source.blank?
|
28
|
+
end
|
29
|
+
|
30
|
+
def source=(s)
|
31
|
+
begin
|
32
|
+
# Re-parse our own timestamps [+- seconds] without swapping month / day
|
33
|
+
@thetime = DateTime.strptime(s, '%d.%m.%Y %H:%M:%S').to_time
|
34
|
+
rescue ArgumentError
|
35
|
+
begin
|
36
|
+
@thetime = DateTime.strptime(s, '%d.%m.%Y %H:%M').to_time
|
37
|
+
rescue ArgumentError
|
38
|
+
@thetime = Time.parse(s)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
# Apply timezone correction for daylight saving
|
42
|
+
if @thetime
|
43
|
+
@thetime = Ourdate.build_datetime(@thetime.year, @thetime.month,
|
44
|
+
@thetime.day, @thetime.hour,
|
45
|
+
@thetime.min, @thetime.sec,
|
46
|
+
@thetime.instance_of?(Time) ? @thetime.usec : 0).to_time
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private :source=
|
51
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# This class provides the ability to define a range using numbers or regular expressions
|
2
|
+
# and when provided with an array, will return a normal ruby Range object based on the matching
|
3
|
+
# elements in the array. NOTE this class is has the same attributes as Range and is identical
|
4
|
+
# when serialized (except for the class declaration obviously), but it is NOT a substitute for
|
5
|
+
# Range (only a facade).
|
6
|
+
class RegexpRange
|
7
|
+
class PatternMatchError < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :begin, :end, :excl
|
11
|
+
|
12
|
+
def initialize(range_start, range_end, exclusive = false)
|
13
|
+
@begin = range_start
|
14
|
+
@end = range_end
|
15
|
+
@excl = exclusive
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_range(lines)
|
19
|
+
start_line_number = @begin
|
20
|
+
if start_line_number.is_a?(Regexp)
|
21
|
+
lines.each_with_index do |line, i|
|
22
|
+
if line.match(start_line_number)
|
23
|
+
start_line_number = i
|
24
|
+
break
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if start_line_number.is_a?(Regexp)
|
29
|
+
fail PatternMatchError, "begin pattern #{start_line_number.inspect} not found"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end_line_number = @end
|
34
|
+
if end_line_number.is_a?(Regexp)
|
35
|
+
start_scan_line = start_line_number + 1
|
36
|
+
lines[start_scan_line..-1].each_with_index do |line, i|
|
37
|
+
# puts "##{start_scan_line + i}: #{line}"
|
38
|
+
if line.match(end_line_number)
|
39
|
+
end_line_number = start_scan_line + i
|
40
|
+
break
|
41
|
+
end
|
42
|
+
end
|
43
|
+
if end_line_number.is_a?(Regexp)
|
44
|
+
fail PatternMatchError,
|
45
|
+
"end pattern #{end_line_number.inspect} not found on or after line #{start_scan_line}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Range.new(start_line_number, end_line_number, @excl)
|
50
|
+
end
|
51
|
+
|
52
|
+
# `other` is equal to self if it is a RegexpRange with the same state.
|
53
|
+
def ==(other)
|
54
|
+
other.is_a?(RegexpRange) && other.state == state
|
55
|
+
end
|
56
|
+
alias_method :eql?, :==
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
# Used by other RegexpRange objects, as well as Hashes, during equality checks:
|
61
|
+
def state
|
62
|
+
[@begin, @end, @excl]
|
63
|
+
end
|
64
|
+
delegate :hash, :to => :state # Used for Hash key lookup
|
65
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
class SafeFile
|
2
|
+
def initialize(*args)
|
3
|
+
a = self.class.get_fname_mode_prms(*args)
|
4
|
+
fname = a[0]
|
5
|
+
mode = a[1]
|
6
|
+
prms = a[2]
|
7
|
+
|
8
|
+
if prms
|
9
|
+
@file = File.new(fname, mode, prms)
|
10
|
+
else
|
11
|
+
@file = File.new(fname, mode)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Just in case better clone the object
|
15
|
+
# Ruby object are passed by reference
|
16
|
+
@file_name = fname.clone
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.open(*args)
|
20
|
+
return SafeFile.new(*args) unless block_given?
|
21
|
+
|
22
|
+
f = SafeFile.new(*args)
|
23
|
+
yield f
|
24
|
+
f.close
|
25
|
+
end
|
26
|
+
|
27
|
+
def close
|
28
|
+
@file.close
|
29
|
+
end
|
30
|
+
|
31
|
+
def read
|
32
|
+
verify @file_name, 'r'
|
33
|
+
@file.read
|
34
|
+
end
|
35
|
+
|
36
|
+
def write(data)
|
37
|
+
verify @file_name, 'w'
|
38
|
+
@file.write(data)
|
39
|
+
end
|
40
|
+
|
41
|
+
def path
|
42
|
+
@file_name.clone
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.extname(file_name)
|
46
|
+
verify file_name
|
47
|
+
File.extname(file_name)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.read(file_name)
|
51
|
+
verify file_name, 'r'
|
52
|
+
File.read(file_name)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.readlines(*args)
|
56
|
+
fail ArgumentError, "Incorrect number of arguments - #{args.length}" if args.length > 2 or args.length == 0
|
57
|
+
verify args[0], 'r'
|
58
|
+
File.readlines(*args)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.directory?(file_name)
|
62
|
+
verify file_name
|
63
|
+
File.directory?(file_name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.exist?(file_name)
|
67
|
+
self.exists?(file_name)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.exists?(file_name)
|
71
|
+
verify file_name
|
72
|
+
File.exist?(file_name)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.file?(file_name)
|
76
|
+
verify file_name
|
77
|
+
File.file?(file_name)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.zero?(file_name)
|
81
|
+
verify file_name
|
82
|
+
File.zero?(file_name)
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.basename(file_name, suffix = :none)
|
86
|
+
verify file_name
|
87
|
+
if suffix == :none
|
88
|
+
File.basename(file_name)
|
89
|
+
else
|
90
|
+
File.basename(file_name, suffix)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.safepath_to_string(fname)
|
95
|
+
verify fname
|
96
|
+
fname.to_s
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.basename_file
|
100
|
+
# SECURE: 02-08-2012 TPG Can't assign to __FILE__
|
101
|
+
File.basename(__FILE__)
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.dirname(path)
|
105
|
+
verify path
|
106
|
+
res = path.clone
|
107
|
+
res.path = File.dirname(path)
|
108
|
+
res
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.delete(*list)
|
112
|
+
verify list, 'w'
|
113
|
+
|
114
|
+
list.each do |file|
|
115
|
+
File.delete(file) if File.exist?(file)
|
116
|
+
end.length
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def verify(file_names, prm = nil)
|
122
|
+
self.class.verify(file_names, prm)
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.verify(file_names, prm = nil)
|
126
|
+
[file_names].flatten.each do |file_name|
|
127
|
+
fail ArgumentError, "file_name should be of type SafePath, but it is #{file_name.class}" unless file_name.class == SafePath
|
128
|
+
|
129
|
+
if prm
|
130
|
+
[prm].flatten.each do |p|
|
131
|
+
fail SecurityError, "Permissions denied. Cannot access the file #{file_name} with permissions #{prm}. The permissions are #{file_name.permissions}" unless file_name.permissions.include?(p)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.verify_mode(file_name, mode)
|
138
|
+
if mode.match(/\A(r\+)|(w\+)|(a\+)\Z/)
|
139
|
+
verify file_name, ['w', 'r']
|
140
|
+
elsif mode.match(/\Aw|a\Z/)
|
141
|
+
verify file_name, ['w']
|
142
|
+
elsif mode.match(/\Ar\Z/)
|
143
|
+
verify file_name, ['r']
|
144
|
+
else
|
145
|
+
fail ArgumentError, "Incorrect mode. It should be one of: 'r', 'w', 'r+', 'w+', 'a', 'a+'"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.get_fname_mode_prms(*args)
|
150
|
+
case args.length
|
151
|
+
when 1
|
152
|
+
verify_mode(args[0], 'r')
|
153
|
+
fname = args[0]
|
154
|
+
mode = 'r'
|
155
|
+
prms = nil
|
156
|
+
|
157
|
+
when 2
|
158
|
+
fail ArgimentError if args[1].class != Fixnum and args[1].class != String
|
159
|
+
|
160
|
+
if args[1].class == Fixnum
|
161
|
+
verify_mode(args[0], 'r')
|
162
|
+
mode = 'r'
|
163
|
+
prms = args[1]
|
164
|
+
else
|
165
|
+
verify_mode(args[0], args[1])
|
166
|
+
mode = args[1]
|
167
|
+
prms = nil
|
168
|
+
end
|
169
|
+
|
170
|
+
fname = args[0]
|
171
|
+
|
172
|
+
when 3
|
173
|
+
fail ArgimentError if args[1].class != String or args[2].class != Fixnum
|
174
|
+
verify_mode(args[0], args[1])
|
175
|
+
|
176
|
+
fname = args[0]
|
177
|
+
mode = args[1]
|
178
|
+
prms = args[2]
|
179
|
+
else
|
180
|
+
fail ArgumentError, "Incorrect number of arguments #{args.length}"
|
181
|
+
end
|
182
|
+
|
183
|
+
[fname, mode, prms]
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
# Ruby has a built-in SecurityError class
|
4
|
+
#class SecurityError < StandardError
|
5
|
+
#end
|
6
|
+
|
7
|
+
# = SafePath
|
8
|
+
#
|
9
|
+
# SafePath is a class which contains path to a file or directory. It also holds "path space" and
|
10
|
+
# permissions. The path space is a directory. Everything in this directory and all the
|
11
|
+
# subdirectories can be accessed with the permissions given to the constructor. The instance of
|
12
|
+
# the class checks whether the path constructed points to a directory, whcih is in the
|
13
|
+
# "path space". The idea is to limit the access of the program to given directory.
|
14
|
+
#
|
15
|
+
# Example of usage is :
|
16
|
+
# sp = SafePath("dbs_inbox")
|
17
|
+
#
|
18
|
+
# The root directory of the pathspace is in the file config/filesystem_paths.yml . In this case dbs_inbox has root
|
19
|
+
# /mounts/ron/dbs_inbox . Every path which starts with /mount/ron/dbs_inbox is considered as safe. If a path is constructed
|
20
|
+
# which is /mounts/ron/dbs_inbox/../../../etc/passwd for example then the class will evaluate the path and it will
|
21
|
+
# raise exception SecurityError.
|
22
|
+
#
|
23
|
+
# The paths can be constructed by using +, join or join!.
|
24
|
+
# Example:
|
25
|
+
# This:
|
26
|
+
# sp = SafePath("dbs_inbox")
|
27
|
+
# sp + "/my_dir"
|
28
|
+
# Points to:
|
29
|
+
# /mounts/ron/dbs_inbox/my_dir
|
30
|
+
#
|
31
|
+
#
|
32
|
+
# The functions join and join! work in similar way. The difference between
|
33
|
+
# join and join! is that join creates new instance of the class SafePath and
|
34
|
+
# return it and join! doesn't create new instance, but works in-place and after
|
35
|
+
# that it returns reference to the current instance.
|
36
|
+
# The both operators can be used like that:
|
37
|
+
# sp.join("/my_dir") #this is the same as sp + "my_dir"
|
38
|
+
# sp.join!("/my_dir") #this is NOT the same as sp "my_dir"
|
39
|
+
#
|
40
|
+
# Warning the function sp.path = "some_path" will treat some_path as absolute path
|
41
|
+
# and if it doesn't point to the root it will raise exception. The danger is that
|
42
|
+
# it returns the argument on the right hand side. So if it is a string the operator
|
43
|
+
# will return a string. This is the way ruby works. If it is used properly it shouldn't
|
44
|
+
# be a problem. The best way to use it is:
|
45
|
+
# sp.path = sp.root + "my_dir"
|
46
|
+
# sp.root returns SafePath and after that + is called which also returns SafePath. So the
|
47
|
+
# right hand side of the expression is SafePath and the = will return SafePath.
|
48
|
+
#
|
49
|
+
class SafePath
|
50
|
+
|
51
|
+
# Returns the list of safe 'root' filesystem locations, or raises
|
52
|
+
# a SecurityError if no configuration has been provided.
|
53
|
+
def self.fs_paths
|
54
|
+
if defined?(@@fs_paths)
|
55
|
+
@@fs_paths
|
56
|
+
else
|
57
|
+
fail SecurityError, 'SafePath not configured!'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Takes the path the filesystem_paths.yml file that
|
62
|
+
# should be used. Attempting to reconfigure with
|
63
|
+
# new settings will raise a security error.
|
64
|
+
def self.configure!(filepath)
|
65
|
+
if defined?(@@fs_paths)
|
66
|
+
fail SecurityError, 'Attempt to re-assign SafePath config!'
|
67
|
+
else
|
68
|
+
File.open(filepath, 'r') do |file|
|
69
|
+
@@fs_paths = YAML.load(ERB.new(file.read).result)
|
70
|
+
@@fs_paths.freeze
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Takes:
|
76
|
+
# * path - This is a path to a directory. Usually a string.
|
77
|
+
# * path_space - This is identifier of the path space in whichi the system should work.
|
78
|
+
# it is a string. To find list of path spaces with their roots, please
|
79
|
+
# see config/filesystem_paths.yml
|
80
|
+
#
|
81
|
+
# Raises:
|
82
|
+
# * ArgumentError
|
83
|
+
# * SecurityError
|
84
|
+
#
|
85
|
+
def initialize(path_space, root_suffix = '')
|
86
|
+
# The class has to use different path definitions during test
|
87
|
+
|
88
|
+
fs_paths = self.class.fs_paths
|
89
|
+
|
90
|
+
platform = fs_paths.keys.select do |key|
|
91
|
+
RUBY_PLATFORM.match key
|
92
|
+
end[0]
|
93
|
+
|
94
|
+
root_path_regexp = if platform == /mswin32|mingw32/
|
95
|
+
/\A([A-Z]:[\\\/]|\/\/)/
|
96
|
+
else
|
97
|
+
/\A\//
|
98
|
+
end
|
99
|
+
|
100
|
+
fail ArgumentError, "The space #{path_space} doesn't exist. Please choose one of: #{fs_paths[platform].keys.inspect}" unless fs_paths[platform][path_space]
|
101
|
+
fail ArgumentError, "The space #{path_space} is broken. The root path should be absolute path but it was #{fs_paths[platform][path_space]['root']}" unless fs_paths[platform][path_space]['root'].match(root_path_regexp)
|
102
|
+
fail ArgumentError, "The space #{path_space} is broken. No permissions specified}" unless fs_paths[platform][path_space]['prms']
|
103
|
+
|
104
|
+
# The function verify uses @root. Therefore first assign the root path
|
105
|
+
# specified in the yaml file. After that verify that the root path
|
106
|
+
# specified from the contructor is subpath of the path specified in the
|
107
|
+
# yaml file. If it is not raise exception before anything else is done.
|
108
|
+
#
|
109
|
+
# The reason to assign @root 2 times is that it is better to have the
|
110
|
+
# logic verifing the path in only one function. This way it is going
|
111
|
+
# to be easier to maintain it and keep it secure.
|
112
|
+
#
|
113
|
+
|
114
|
+
@root = File.expand_path fs_paths[platform][path_space]['root']
|
115
|
+
@root = verify(File.join(fs_paths[platform][path_space]['root'], root_suffix)) unless root_suffix.blank?
|
116
|
+
|
117
|
+
@path_space = path_space
|
118
|
+
@maximum_prms = fs_paths[platform][path_space]['prms']
|
119
|
+
|
120
|
+
self.path = @root
|
121
|
+
end
|
122
|
+
|
123
|
+
def ==(other)
|
124
|
+
other.class == SafePath and other.root.to_s == @root and other.permissions == self.permissions and other.to_s == self.to_s
|
125
|
+
end
|
126
|
+
|
127
|
+
# WARNING: do not use sp.to_s + from_attacker . This is unsafe!
|
128
|
+
#
|
129
|
+
# Returns:
|
130
|
+
# String
|
131
|
+
#
|
132
|
+
def to_s
|
133
|
+
@path
|
134
|
+
end
|
135
|
+
|
136
|
+
# WARNING: do not use s.to_s + "my_path" . This is unsafe!
|
137
|
+
#
|
138
|
+
# Returns:
|
139
|
+
# String
|
140
|
+
#
|
141
|
+
def to_str
|
142
|
+
self.to_s
|
143
|
+
end
|
144
|
+
|
145
|
+
# Getter for permissions.
|
146
|
+
#
|
147
|
+
# Returns:
|
148
|
+
# Array
|
149
|
+
def permissions
|
150
|
+
if @prm
|
151
|
+
@prm
|
152
|
+
else
|
153
|
+
@maximum_prms
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Getter for path space identifier.
|
158
|
+
#
|
159
|
+
# Returns:
|
160
|
+
# Array
|
161
|
+
#
|
162
|
+
def path_space
|
163
|
+
@path_space
|
164
|
+
end
|
165
|
+
|
166
|
+
# Getter for the root of the path space.
|
167
|
+
#
|
168
|
+
# Returns:
|
169
|
+
# SafePath
|
170
|
+
#
|
171
|
+
def root
|
172
|
+
r = self.clone
|
173
|
+
r.path = @root
|
174
|
+
r.permissions = self.permissions
|
175
|
+
r
|
176
|
+
end
|
177
|
+
|
178
|
+
# The permissions are specified in the yml file, but this function
|
179
|
+
# can select subset of these permissions. It cannot select permission,
|
180
|
+
# which is not specified in the yml file.
|
181
|
+
#
|
182
|
+
# Takes:
|
183
|
+
# * Array of permission or single permission - If it is array then
|
184
|
+
# tha array could contain duplicates and it also can be nested.
|
185
|
+
# All the duplicates will be removed and the array will be flattened
|
186
|
+
#
|
187
|
+
# Returns:
|
188
|
+
# Array - this is the reuslt of the assignment. Note it is the right hand side of the expression
|
189
|
+
#
|
190
|
+
# Raises:
|
191
|
+
# * ArgumentError
|
192
|
+
#
|
193
|
+
def permissions=(permissions)
|
194
|
+
err_mess = "permissions has to be one or more of the values: #{@maximum_prms.inspect}\n but it was #{permissions.inspect}"
|
195
|
+
|
196
|
+
@prm = [permissions].flatten.each do |prm|
|
197
|
+
fail ArgumentError, err_mess unless @maximum_prms.include?(prm)
|
198
|
+
end.uniq
|
199
|
+
end
|
200
|
+
|
201
|
+
# Setter for path.
|
202
|
+
#
|
203
|
+
# Takes:
|
204
|
+
# * path - The path.
|
205
|
+
#
|
206
|
+
# Returns:
|
207
|
+
# Array - this is the result of the assignment. Note it is the right hand side of the expression
|
208
|
+
#
|
209
|
+
# Raises:
|
210
|
+
# * SecurityError
|
211
|
+
#
|
212
|
+
# Warning avoid using this in expressions like this (safe_path_new = (safe_path.path = safe_path.root.to_s + "/test")) + path_from_attacker
|
213
|
+
# This is unsafe!
|
214
|
+
#
|
215
|
+
def path=(path)
|
216
|
+
@path = verify(path)
|
217
|
+
self
|
218
|
+
end
|
219
|
+
|
220
|
+
# Another name for join
|
221
|
+
def +(path)
|
222
|
+
self.join(path)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Used to construct path. It joins the current path with the given one.
|
226
|
+
# Takes:
|
227
|
+
# * path - path to be concatenated
|
228
|
+
#
|
229
|
+
# Returns:
|
230
|
+
# New instance of SafePath which contains the new path.
|
231
|
+
#
|
232
|
+
# Raises:
|
233
|
+
# * SecurityError
|
234
|
+
#
|
235
|
+
def join(path)
|
236
|
+
r = self.clone
|
237
|
+
r.path = File.join(@path, path)
|
238
|
+
r
|
239
|
+
end
|
240
|
+
|
241
|
+
# Used to construct path. It joins the current path with the given one.
|
242
|
+
# Takes:
|
243
|
+
# * path - path to be concatenated
|
244
|
+
#
|
245
|
+
# Returns:
|
246
|
+
# Reference to the current instance. It works in-place
|
247
|
+
#
|
248
|
+
# Raises:
|
249
|
+
# * SecurityError
|
250
|
+
#
|
251
|
+
def join!(path)
|
252
|
+
self.path = File.join(@path, path)
|
253
|
+
self
|
254
|
+
end
|
255
|
+
|
256
|
+
def length()
|
257
|
+
@path.length
|
258
|
+
end
|
259
|
+
|
260
|
+
private
|
261
|
+
|
262
|
+
# Verifies whether the path is safe.
|
263
|
+
def verify(path)
|
264
|
+
epath = File.expand_path(path)
|
265
|
+
fail SecurityError, "The given path is insecure. The path should point at #{@root}, but it points at #{epath}" unless epath.match(/\A#{Regexp.quote(@root)}/)
|
266
|
+
epath
|
267
|
+
end
|
268
|
+
end
|