1313import shutil
1414import stat
1515import tarfile
16+ import tempfile
1617import re
1718
1819from concurrent .futures import ProcessPoolExecutor
@@ -54,15 +55,15 @@ class SoSObfuscationArchive():
5455 class. All report-level operations should be contained within this class.
5556 """
5657
57- file_sub_list = []
58+ files_obfuscated_count = 0
5859 total_sub_count = 0
5960 removed_file_count = 0
6061 type_name = 'undetermined'
6162 description = 'undetermined'
6263 is_nested = False
6364 prep_files = {}
6465
65- def __init__ (self , archive_path , tmpdir ):
66+ def __init__ (self , archive_path , tmpdir , keep_binary_files ):
6667 self .archive_path = archive_path
6768 self .final_archive_path = self .archive_path
6869 self .tmpdir = tmpdir
@@ -74,10 +75,144 @@ def __init__(self, archive_path, tmpdir):
7475 self .is_extracted = False
7576 self ._load_self ()
7677 self .archive_root = ''
78+ self .keep_binary_files = keep_binary_files
79+ self .parsers = ()
7780 self .log_info (
7881 f"Loaded { self .archive_path } as type { self .description } "
7982 )
8083
84+ def obfuscate_string (self , string_data ):
85+ for parser in self .parsers :
86+ try :
87+ string_data = parser .parse_string_for_keys (string_data )
88+ except Exception as err :
89+ self .log_info (f"Error obfuscating string data: { err } " )
90+ return string_data
91+
92+ # TODO: merge content to obfuscate_arc_files as that is the only place we
93+ # call obfuscate_filename ?
94+ def obfuscate_filename (self , short_name , filename ):
95+ _ob_short_name = self .obfuscate_string (short_name .split ('/' )[- 1 ])
96+ _ob_filename = short_name .replace (short_name .split ('/' )[- 1 ],
97+ _ob_short_name )
98+
99+ if _ob_filename != short_name :
100+ arc_path = filename .split (short_name )[0 ]
101+ _ob_path = os .path .join (arc_path , _ob_filename )
102+ # ensure that any plugin subdirs that contain obfuscated strings
103+ # get created with obfuscated counterparts
104+ if not os .path .islink (filename ):
105+ os .rename (filename , _ob_path )
106+ else :
107+ # generate the obfuscated name of the link target
108+ _target_ob = self .obfuscate_string (os .readlink (filename ))
109+ # remove the unobfuscated original symlink first, in case the
110+ # symlink name hasn't changed but the target has
111+ os .remove (filename )
112+ # create the newly obfuscated symlink, pointing to the
113+ # obfuscated target name, which may not exist just yet, but
114+ # when the actual file is obfuscated, will be created
115+ os .symlink (_target_ob , _ob_path )
116+
117+ def set_parsers (self , parsers ):
118+ self .parsers = parsers # TODO: include this in __init__?
119+
120+ def load_parser_entries (self ):
121+ for parser in self .parsers :
122+ parser .load_map_entries ()
123+
124+ def obfuscate_line (self , line , parsers = None ):
125+ """Run a line through each of the obfuscation parsers, keeping a
126+ cumulative total of substitutions done on that particular line.
127+
128+ Positional arguments:
129+
130+ :param line str: The raw line as read from the file being
131+ processed
132+ :param parsers: A list of parser objects to obfuscate
133+ with. If None, use all.
134+
135+ Returns the fully obfuscated line and the number of substitutions made
136+ """
137+ # don't iterate over blank lines, but still write them to the tempfile
138+ # to maintain the same structure when we write a scrubbed file back
139+ count = 0
140+ if not line .strip ():
141+ return line , count
142+ if parsers is None :
143+ parsers = self .parsers
144+ for parser in parsers :
145+ try :
146+ line , _count = parser .parse_line (line )
147+ count += _count
148+ except Exception as err :
149+ self .log_debug (f"failed to parse line: { err } " , parser .name )
150+ return line , count
151+
152+ def obfuscate_arc_files (self , flist ):
153+ for filename in flist :
154+ self .log_debug (f" pid={ os .getpid ()} : obfuscating { filename } " )
155+ try :
156+ short_name = filename .split (self .archive_name + '/' )[1 ]
157+ if self .should_skip_file (short_name ):
158+ continue
159+ if (not self .keep_binary_files and
160+ self .should_remove_file (short_name )):
161+ # We reach this case if the option --keep-binary-files
162+ # was not used, and the file is in a list to be removed
163+ self .remove_file (short_name )
164+ continue
165+ if (self .keep_binary_files and
166+ (file_is_binary (filename ) or
167+ self .should_remove_file (short_name ))):
168+ # We reach this case if the option --keep-binary-files
169+ # is used. In this case we want to make sure
170+ # the cleaner doesn't try to clean a binary file
171+ continue
172+ if os .path .islink (filename ):
173+ # don't run the obfuscation on the link, but on the actual
174+ # file at some other point.
175+ continue
176+ _parsers = [
177+ _p for _p in self .parsers if not
178+ any (
179+ _skip .match (short_name ) for _skip in _p .skip_patterns
180+ )
181+ ]
182+ if not _parsers :
183+ self .log_debug (
184+ f"Skipping obfuscation of { short_name or filename } "
185+ f"due to matching file skip pattern"
186+ )
187+ continue
188+ self .log_debug (f"Obfuscating { short_name or filename } " )
189+ subs = 0
190+ with tempfile .NamedTemporaryFile (mode = 'w' , dir = self .tmpdir ) \
191+ as tfile :
192+ with open (filename , 'r' , encoding = 'utf-8' ,
193+ errors = 'replace' ) as fname :
194+ for line in fname :
195+ try :
196+ line , cnt = self .obfuscate_line (line , _parsers )
197+ subs += cnt
198+ tfile .write (line )
199+ except Exception as err :
200+ self .log_debug (f"Unable to obfuscate "
201+ f"{ short_name } : { err } " )
202+ tfile .seek (0 )
203+ if subs :
204+ shutil .copyfile (tfile .name , filename )
205+ self .update_sub_count (subs )
206+
207+ self .obfuscate_filename (short_name , filename )
208+
209+ except Exception as err :
210+ self .log_debug (f" pid={ os .getpid ()} : caught exception on "
211+ f"obfuscating file { filename } : { err } " )
212+
213+ return (self .files_obfuscated_count , self .total_sub_count ,
214+ self .removed_file_count )
215+
81216 @classmethod
82217 def check_is_type (cls , arc_path ):
83218 """Check if the archive is a well-known type we directly support"""
@@ -120,14 +255,18 @@ def report_msg(self, msg):
120255 """Helper to easily format ui messages on a per-report basis"""
121256 self .ui_log .info (f"{ self .ui_name + ' :' :<50} { msg } " )
122257
123- def _fmt_log_msg (self , msg ):
124- return f"[cleaner:{ self .archive_name } ] { msg } "
258+ def _fmt_log_msg (self , msg , caller = None ):
259+ return f"[cleaner{ f':{ caller } ' if caller else '' } " \
260+ f"[{ self .archive_name } ]] { msg } "
261+
262+ def log_debug (self , msg , caller = None ):
263+ self .soslog .debug (self ._fmt_log_msg (msg , caller ))
125264
126- def log_debug (self , msg ):
127- self .soslog .debug (self ._fmt_log_msg (msg ))
265+ def log_info (self , msg , caller = None ):
266+ self .soslog .info (self ._fmt_log_msg (msg , caller ))
128267
129- def log_info (self , msg ):
130- self .soslog .info (self ._fmt_log_msg (msg ))
268+ def log_error (self , msg , caller = None ):
269+ self .soslog .error (self ._fmt_log_msg (msg , caller ))
131270
132271 def _load_skip_list (self ):
133272 """Provide a list of files and file regexes to skip obfuscation on
@@ -201,6 +340,7 @@ def extract(self, quiet=False):
201340 self .report_msg ("Extracting..." )
202341 self .extracted_path = self .extract_self ()
203342 self .is_extracted = True
343+ self .tarobj = None # we can't pickle this & not further needed
204344 else :
205345 self .extracted_path = self .archive_path
206346 # if we're running as non-root (e.g. collector), then we can have a
@@ -326,7 +466,7 @@ def get_symlinks(self):
326466 if os .path .islink (_fname ):
327467 yield _fname
328468
329- def get_file_list (self ):
469+ def get_files (self ):
330470 """Iterator for a list of files in the archive, to allow clean to
331471 iterate over.
332472
@@ -345,11 +485,11 @@ def get_directory_list(self):
345485 dir_list .append (dirname )
346486 return dir_list
347487
348- def update_sub_count (self , fname , count ):
488+ def update_sub_count (self , count ):
349489 """Called when a file has finished being parsed and used to track
350490 total substitutions made and number of files that had changes made
351491 """
352- self .file_sub_list . append ( fname )
492+ self .files_obfuscated_count += 1
353493 self .total_sub_count += count
354494
355495 def get_file_path (self , fname ):
0 commit comments