LEFT | RIGHT |
1 # -*- coding: utf-8 -*- | 1 # -*- coding: utf-8 -*- |
2 """The CUPS IPP files parser. | 2 """The CUPS IPP files parser. |
3 | 3 |
4 CUPS IPP version 1.0: | 4 CUPS IPP version 1.0: |
5 * http://tools.ietf.org/html/rfc2565 | 5 * http://tools.ietf.org/html/rfc2565 |
6 * http://tools.ietf.org/html/rfc2566 | 6 * http://tools.ietf.org/html/rfc2566 |
7 * http://tools.ietf.org/html/rfc2567 | 7 * http://tools.ietf.org/html/rfc2567 |
8 * http://tools.ietf.org/html/rfc2568 | 8 * http://tools.ietf.org/html/rfc2568 |
9 * http://tools.ietf.org/html/rfc2569 | 9 * http://tools.ietf.org/html/rfc2569 |
10 * http://tools.ietf.org/html/rfc2639 | 10 * http://tools.ietf.org/html/rfc2639 |
11 | 11 |
12 CUPS IPP version 1.1: | 12 CUPS IPP version 1.1: |
13 * http://tools.ietf.org/html/rfc2910 | 13 * http://tools.ietf.org/html/rfc2910 |
14 * http://tools.ietf.org/html/rfc2911 | 14 * http://tools.ietf.org/html/rfc2911 |
15 * http://tools.ietf.org/html/rfc3196 | 15 * http://tools.ietf.org/html/rfc3196 |
16 * http://tools.ietf.org/html/rfc3510 | 16 * http://tools.ietf.org/html/rfc3510 |
17 | 17 |
18 CUPS IPP version 2.0: | 18 CUPS IPP version 2.0: |
19 * N/A | 19 * N/A |
20 """ | 20 """ |
21 | 21 |
22 from __future__ import unicode_literals | 22 from __future__ import unicode_literals |
23 | 23 |
24 import os | 24 import os |
25 | 25 |
26 from dfdatetime import posix_time as dfdatetime_posix_time | 26 from dfdatetime import posix_time as dfdatetime_posix_time |
27 from dfdatetime import rfc2579_date_time as dfdatetime_rfc2579_date_time | 27 from dfdatetime import rfc2579_date_time as dfdatetime_rfc2579_date_time |
28 | 28 |
29 from dtfabric.runtime import fabric as dtfabric_fabric | |
30 | |
31 from plaso.containers import events | 29 from plaso.containers import events |
32 from plaso.containers import time_events | 30 from plaso.containers import time_events |
33 from plaso.lib import errors | 31 from plaso.lib import errors |
34 from plaso.lib import definitions | 32 from plaso.lib import definitions |
35 from plaso.parsers import data_formats | 33 from plaso.parsers import dtfabric_parser |
36 from plaso.parsers import logger | 34 from plaso.parsers import logger |
37 from plaso.parsers import manager | 35 from plaso.parsers import manager |
38 | 36 |
39 | 37 |
40 # TODO: RFC Pendings types: resolution, dateTime, rangeOfInteger. | 38 # TODO: RFC Pendings types: resolution, dateTime, rangeOfInteger. |
41 # "dateTime" is not used by Mac OS, instead it uses integer types. | 39 # "dateTime" is not used by Mac OS, instead it uses integer types. |
42 # TODO: Only tested against CUPS IPP MacOS. | 40 # TODO: Only tested against CUPS IPP MacOS. |
43 | 41 |
44 | 42 |
45 class CupsIppEventData(events.EventData): | 43 class CupsIppEventData(events.EventData): |
(...skipping 26 matching lines...) Expand all Loading... |
72 self.data_dict = None | 70 self.data_dict = None |
73 self.doc_type = None | 71 self.doc_type = None |
74 self.job_id = None | 72 self.job_id = None |
75 self.job_name = None | 73 self.job_name = None |
76 self.owner = None | 74 self.owner = None |
77 self.printer_id = None | 75 self.printer_id = None |
78 self.uri = None | 76 self.uri = None |
79 self.user = None | 77 self.user = None |
80 | 78 |
81 | 79 |
82 class CupsIppParser(data_formats.DataFormatParser): | 80 class CupsIppParser(dtfabric_parser.DtFabricBaseParser): |
83 """Parser for CUPS IPP files.""" | 81 """Parser for CUPS IPP files.""" |
84 | 82 |
85 NAME = 'cups_ipp' | 83 NAME = 'cups_ipp' |
86 DESCRIPTION = 'Parser for CUPS IPP files.' | 84 DESCRIPTION = 'Parser for CUPS IPP files.' |
87 | 85 |
88 _DATA_TYPE_FABRIC_DEFINITION_FILE = os.path.join( | 86 _DEFINITION_FILE = 'cups_ipp.yaml' |
89 os.path.dirname(__file__), 'cups_ipp.yaml') | |
90 | |
91 with open(_DATA_TYPE_FABRIC_DEFINITION_FILE, 'rb') as file_object: | |
92 _DATA_TYPE_FABRIC_DEFINITION = file_object.read() | |
93 | |
94 _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric( | |
95 yaml_definition=_DATA_TYPE_FABRIC_DEFINITION) | |
96 | |
97 _HEADER = _DATA_TYPE_FABRIC.CreateDataTypeMap('cups_ipp_header') | |
98 | |
99 _HEADER_SIZE = _HEADER.GetByteSize() | |
100 | |
101 _TAG_VALUE = _DATA_TYPE_FABRIC.CreateDataTypeMap('int8') | |
102 _TAG_VALUE_SIZE = _TAG_VALUE.GetByteSize() | |
103 | |
104 _ATTRIBUTE = _DATA_TYPE_FABRIC.CreateDataTypeMap('cups_ipp_attribute') | |
105 | |
106 _DATETIME_VALUE = _DATA_TYPE_FABRIC.CreateDataTypeMap( | |
107 'cups_ipp_datetime_value') | |
108 | |
109 _INTEGER_VALUE = _DATA_TYPE_FABRIC.CreateDataTypeMap('int32be') | |
110 | 87 |
111 _SUPPORTED_FORMAT_VERSIONS = ('1.0', '1.1', '2.0') | 88 _SUPPORTED_FORMAT_VERSIONS = ('1.0', '1.1', '2.0') |
112 | 89 |
113 # TODO: add descriptive names. | 90 _DELIMITER_TAG_OPERATION_ATTRIBUTES = 0x01 |
114 _DELIMITER_TAGS = (0x01, 0x02, 0x04, 0x05) | 91 _DELIMITER_TAG_JOB_ATTRIBUTES = 0x02 |
| 92 _DELIMITER_TAG_END_OF_ATTRIBUTES = 0x03 |
| 93 _DELIMITER_TAG_PRINTER_ATTRIBUTES = 0x04 |
| 94 _DELIMITER_TAG_UNSUPPORTED_ATTRIBUTES = 0x05 |
| 95 |
| 96 _DELIMITER_TAGS = frozenset([ |
| 97 _DELIMITER_TAG_OPERATION_ATTRIBUTES, |
| 98 _DELIMITER_TAG_JOB_ATTRIBUTES, |
| 99 _DELIMITER_TAG_PRINTER_ATTRIBUTES, |
| 100 _DELIMITER_TAG_UNSUPPORTED_ATTRIBUTES]) |
| 101 |
| 102 _TAG_VALUE_INTEGER = 0x21 |
| 103 _TAG_VALUE_BOOLEAN = 0x22 |
| 104 _TAG_VALUE_ENUM = 0x23 |
| 105 |
| 106 _TAG_VALUE_DATE_TIME = 0x31 |
| 107 _TAG_VALUE_RESOLUTION = 0x32 |
| 108 |
| 109 _TAG_VALUE_TEXT_WITHOUT_LANGUAGE = 0x41 |
| 110 _TAG_VALUE_NAME_WITHOUT_LANGUAGE = 0x42 |
| 111 |
| 112 _TAG_VALUE_KEYWORD = 0x44 |
| 113 _TAG_VALUE_URI = 0x45 |
| 114 _TAG_VALUE_URI_SCHEME = 0x46 |
| 115 _TAG_VALUE_CHARSET = 0x47 |
| 116 _TAG_VALUE_NATURAL_LANGUAGE = 0x48 |
| 117 _TAG_VALUE_MEDIA_TYPE = 0x49 |
| 118 |
| 119 _INTEGER_TAG_VALUES = frozenset([ |
| 120 _TAG_VALUE_INTEGER, _TAG_VALUE_ENUM]) |
| 121 |
| 122 _ASCII_STRING_VALUES = frozenset([ |
| 123 _TAG_VALUE_KEYWORD, |
| 124 _TAG_VALUE_URI, |
| 125 _TAG_VALUE_URI_SCHEME, |
| 126 _TAG_VALUE_CHARSET, |
| 127 _TAG_VALUE_NATURAL_LANGUAGE, |
| 128 _TAG_VALUE_MEDIA_TYPE]) |
| 129 |
| 130 _INTEGER_TAG_VALUES = frozenset([ |
| 131 _TAG_VALUE_INTEGER, _TAG_VALUE_ENUM]) |
| 132 |
| 133 _STRING_WITHOUT_LANGUAGE_VALUES = frozenset([ |
| 134 _TAG_VALUE_TEXT_WITHOUT_LANGUAGE, |
| 135 _TAG_VALUE_NAME_WITHOUT_LANGUAGE]) |
115 | 136 |
116 _DATE_TIME_VALUES = { | 137 _DATE_TIME_VALUES = { |
117 'date-time-at-creation': definitions.TIME_DESCRIPTION_CREATION, | 138 'date-time-at-creation': definitions.TIME_DESCRIPTION_CREATION, |
118 'date-time-at-processing': definitions.TIME_DESCRIPTION_START, | 139 'date-time-at-processing': definitions.TIME_DESCRIPTION_START, |
119 'date-time-at-completed': definitions.TIME_DESCRIPTION_END} | 140 'date-time-at-completed': definitions.TIME_DESCRIPTION_END} |
120 | 141 |
121 _POSIX_TIME_VALUES = { | 142 _POSIX_TIME_VALUES = { |
122 'time-at-creation': definitions.TIME_DESCRIPTION_CREATION, | 143 'time-at-creation': definitions.TIME_DESCRIPTION_CREATION, |
123 'time-at-processing': definitions.TIME_DESCRIPTION_START, | 144 'time-at-processing': definitions.TIME_DESCRIPTION_START, |
124 'time-at-completed': definitions.TIME_DESCRIPTION_END} | 145 'time-at-completed': definitions.TIME_DESCRIPTION_END} |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
168 Args: | 189 Args: |
169 file_object (dfvfs.FileIO): file-like object. | 190 file_object (dfvfs.FileIO): file-like object. |
170 | 191 |
171 Returns: | 192 Returns: |
172 tuple[str, object]: attribute name and value. | 193 tuple[str, object]: attribute name and value. |
173 | 194 |
174 Raises: | 195 Raises: |
175 ParseError: if the attribute cannot be parsed. | 196 ParseError: if the attribute cannot be parsed. |
176 """ | 197 """ |
177 file_offset = file_object.tell() | 198 file_offset = file_object.tell() |
| 199 attribute_map = self._GetDataTypeMap('cups_ipp_attribute') |
178 | 200 |
179 try: | 201 try: |
180 attribute, _ = self._ReadStructureWithSizeHint( | 202 attribute, _ = self._ReadStructureFromFileObject( |
181 file_object, file_offset, self._ATTRIBUTE, 'attribute') | 203 file_object, file_offset, attribute_map) |
182 except (ValueError, errors.ParseError) as exception: | 204 except (ValueError, errors.ParseError) as exception: |
183 raise errors.ParseError( | 205 raise errors.ParseError( |
184 'Unable to parse attribute with error: {0!s}'.format(exception)) | 206 'Unable to parse attribute with error: {0!s}'.format(exception)) |
185 | 207 |
186 value = None | 208 value = None |
187 if attribute.tag_value in (0x21, 0x23): | 209 if attribute.tag_value in self._INTEGER_TAG_VALUES: |
188 file_offset = file_object.tell() | 210 # TODO: correct file offset to point to the start of value_data. |
189 try: | 211 value = self._ParseIntegerValue(attribute.value_data, file_offset) |
190 value = self._ReadStructureFromByteStream( | 212 |
191 attribute.value_data, file_offset, self._INTEGER_VALUE, | 213 elif attribute.tag_value == self._TAG_VALUE_BOOLEAN: |
192 'integer value') | 214 value = self._ParseBooleanValue(attribute.value_data) |
193 except (ValueError, errors.ParseError) as exception: | 215 |
194 raise errors.ParseError( | 216 elif attribute.tag_value == self._TAG_VALUE_DATE_TIME: |
195 'Unable to parse integer value with error: {0!s}'.format(exception)) | |
196 | |
197 elif attribute.tag_value == 0x22: | |
198 if attribute.value_data == b'\x00': | |
199 value = False | |
200 elif attribute.value_data == b'\x01': | |
201 value = True | |
202 else: | |
203 raise errors.ParseError('Unsupported boolean value.') | |
204 | |
205 elif attribute.tag_value == 0x31: | |
206 # TODO: correct file offset to point to the start of value_data. | 217 # TODO: correct file offset to point to the start of value_data. |
207 value = self._ParseDateTimeValue(attribute.value_data, file_offset) | 218 value = self._ParseDateTimeValue(attribute.value_data, file_offset) |
208 | 219 |
209 elif attribute.tag_value in (0x41, 0x42): | 220 elif attribute.tag_value in self._STRING_WITHOUT_LANGUAGE_VALUES: |
210 value = attribute.value_data.decode(self._last_charset_attribute) | 221 value = attribute.value_data.decode(self._last_charset_attribute) |
211 | 222 |
212 elif attribute.tag_value in (0x44, 0x45, 0x46, 0x47, 0x48, 0x49): | 223 elif attribute.tag_value in self._ASCII_STRING_VALUES: |
213 value = attribute.value_data.decode('ascii') | 224 value = attribute.value_data.decode('ascii') |
214 | 225 |
215 if attribute.tag_value == 0x47: | 226 if attribute.tag_value == self._TAG_VALUE_CHARSET: |
216 self._last_charset_attribute = value | 227 self._last_charset_attribute = value |
217 | 228 |
218 else: | 229 else: |
219 value = attribute.value_data | 230 value = attribute.value_data |
220 | 231 |
221 return attribute.name, value | 232 return attribute.name, value |
222 | 233 |
223 def _ParseAttributesGroup(self, file_object): | 234 def _ParseAttributesGroup(self, file_object): |
224 """Parses a CUPS IPP attributes group from a file-like object. | 235 """Parses a CUPS IPP attributes group from a file-like object. |
225 | 236 |
226 Args: | 237 Args: |
227 file_object (dfvfs.FileIO): file-like object. | 238 file_object (dfvfs.FileIO): file-like object. |
228 | 239 |
229 Yields: | 240 Yields: |
230 tuple[str, object]: attribute name and value. | 241 tuple[str, object]: attribute name and value. |
231 | 242 |
232 Raises: | 243 Raises: |
233 ParseError: if the attributes group cannot be parsed. | 244 ParseError: if the attributes group cannot be parsed. |
234 """ | 245 """ |
| 246 tag_value_map = self._GetDataTypeMap('int8') |
235 tag_value = 0 | 247 tag_value = 0 |
236 | 248 |
237 while tag_value != 0x03: | 249 while tag_value != self._DELIMITER_TAG_END_OF_ATTRIBUTES: |
238 file_offset = file_object.tell() | 250 file_offset = file_object.tell() |
239 tag_value = self._ReadStructure( | 251 |
240 file_object, file_offset, self._TAG_VALUE_SIZE, self._TAG_VALUE, | 252 tag_value, _ = self._ReadStructureFromFileObject( |
241 'tag value') | 253 file_object, file_offset, tag_value_map) |
242 | 254 |
243 if tag_value < 0x10: | 255 if tag_value >= 0x10: |
244 if tag_value != 0x03 and tag_value not in self._DELIMITER_TAGS: | |
245 raise errors.ParseError(( | |
246 'Unsupported attributes groups start tag value: ' | |
247 '0x{0:02x}.').format(tag_value)) | |
248 | |
249 else: | |
250 file_object.seek(file_offset, os.SEEK_SET) | 256 file_object.seek(file_offset, os.SEEK_SET) |
251 | 257 |
252 yield self._ParseAttribute(file_object) | 258 yield self._ParseAttribute(file_object) |
| 259 |
| 260 elif (tag_value != self._DELIMITER_TAG_END_OF_ATTRIBUTES and |
| 261 tag_value not in self._DELIMITER_TAGS): |
| 262 raise errors.ParseError(( |
| 263 'Unsupported attributes groups start tag value: ' |
| 264 '0x{0:02x}.').format(tag_value)) |
| 265 |
| 266 def _ParseBooleanValue(self, byte_stream): |
| 267 """Parses a boolean value. |
| 268 |
| 269 Args: |
| 270 byte_stream (bytes): byte stream. |
| 271 |
| 272 Returns: |
| 273 bool: boolean value. |
| 274 |
| 275 Raises: |
| 276 ParseError: when the boolean value cannot be parsed. |
| 277 """ |
| 278 if byte_stream == b'\x00': |
| 279 return False |
| 280 |
| 281 if byte_stream == b'\x01': |
| 282 return True |
| 283 |
| 284 raise errors.ParseError('Unsupported boolean value.') |
253 | 285 |
254 def _ParseDateTimeValue(self, byte_stream, file_offset): | 286 def _ParseDateTimeValue(self, byte_stream, file_offset): |
255 """Parses a CUPS IPP RFC2579 date-time value from a byte stream. | 287 """Parses a CUPS IPP RFC2579 date-time value from a byte stream. |
256 | 288 |
257 Args: | 289 Args: |
258 byte_stream (bytes): byte stream. | 290 byte_stream (bytes): byte stream. |
259 file_offset (int): offset of the data relative from the start of | 291 file_offset (int): offset of the attribute data relative to the start of |
260 the file-like object. | 292 the file-like object. |
261 | 293 |
262 Returns: | 294 Returns: |
263 dfdatetime.RFC2579DateTime: RFC2579 date-time stored in the value. | 295 dfdatetime.RFC2579DateTime: RFC2579 date-time stored in the value. |
264 | 296 |
265 Raises: | 297 Raises: |
266 ParseError: when the RFC2579 date-time value cannot be parsed. | 298 ParseError: when the RFC2579 date-time value cannot be parsed. |
267 """ | 299 """ |
| 300 datetime_value_map = self._GetDataTypeMap('cups_ipp_datetime_value') |
| 301 |
268 try: | 302 try: |
269 value = self._ReadStructureFromByteStream( | 303 value = self._ReadStructureFromByteStream( |
270 byte_stream, file_offset, self._DATETIME_VALUE, 'date-time value') | 304 byte_stream, file_offset, datetime_value_map) |
271 except (ValueError, errors.ParseError) as exception: | 305 except (ValueError, errors.ParseError) as exception: |
272 raise errors.ParseError( | 306 raise errors.ParseError( |
273 'Unable to parse datetime value with error: {0!s}'.format(exception)) | 307 'Unable to parse datetime value with error: {0!s}'.format(exception)) |
274 | 308 |
275 rfc2579_date_time_tuple = ( | 309 rfc2579_date_time_tuple = ( |
276 value.year, value.month, value.day, | 310 value.year, value.month, value.day, |
277 value.hours, value.minutes, value.seconds, value.deciseconds, | 311 value.hours, value.minutes, value.seconds, value.deciseconds, |
278 value.direction_from_utc, value.hours_from_utc, value.minutes_from_utc) | 312 value.direction_from_utc, value.hours_from_utc, value.minutes_from_utc) |
279 return dfdatetime_rfc2579_date_time.RFC2579DateTime( | 313 return dfdatetime_rfc2579_date_time.RFC2579DateTime( |
280 rfc2579_date_time_tuple=rfc2579_date_time_tuple) | 314 rfc2579_date_time_tuple=rfc2579_date_time_tuple) |
281 | 315 |
| 316 def _ParseIntegerValue(self, byte_stream, file_offset): |
| 317 """Parses an integer value. |
| 318 |
| 319 Args: |
| 320 byte_stream (bytes): byte stream. |
| 321 file_offset (int): offset of the attribute data relative to the start of |
| 322 the file-like object. |
| 323 |
| 324 Returns: |
| 325 int: integer value. |
| 326 |
| 327 Raises: |
| 328 ParseError: when the integer value cannot be parsed. |
| 329 """ |
| 330 data_type_map = self._GetDataTypeMap('int32be') |
| 331 |
| 332 try: |
| 333 return self._ReadStructureFromByteStream( |
| 334 byte_stream, file_offset, data_type_map) |
| 335 except (ValueError, errors.ParseError) as exception: |
| 336 raise errors.ParseError( |
| 337 'Unable to parse integer value with error: {0!s}'.format(exception)) |
| 338 |
282 def _ParseHeader(self, parser_mediator, file_object): | 339 def _ParseHeader(self, parser_mediator, file_object): |
283 """Parses a CUPS IPP header from a file-like object. | 340 """Parses a CUPS IPP header from a file-like object. |
284 | 341 |
285 Args: | 342 Args: |
286 parser_mediator (ParserMediator): mediates interactions between parsers | 343 parser_mediator (ParserMediator): mediates interactions between parsers |
287 and other components, such as storage and dfvfs. | 344 and other components, such as storage and dfvfs. |
288 file_object (dfvfs.FileIO): file-like object. | 345 file_object (dfvfs.FileIO): file-like object. |
289 | 346 |
290 Raises: | 347 Raises: |
291 UnableToParseFile: when the header cannot be parsed. | 348 UnableToParseFile: when the header cannot be parsed. |
292 """ | 349 """ |
| 350 header_map = self._GetDataTypeMap('cups_ipp_header') |
| 351 |
293 try: | 352 try: |
294 header = self._ReadStructure( | 353 header, _ = self._ReadStructureFromFileObject(file_object, 0, header_map) |
295 file_object, 0, self._HEADER_SIZE, self._HEADER, 'header') | |
296 except (ValueError, errors.ParseError) as exception: | 354 except (ValueError, errors.ParseError) as exception: |
297 raise errors.UnableToParseFile( | 355 raise errors.UnableToParseFile( |
298 'Unable to parse CUPS IPP header with error: {0!s}'.format(exception)) | 356 '[{0:s}] Unable to parse header with error: {1!s}'.format( |
| 357 self.NAME, exception)) |
299 | 358 |
300 format_version = '{0:d}.{1:d}'.format( | 359 format_version = '{0:d}.{1:d}'.format( |
301 header.major_version, header.minor_version) | 360 header.major_version, header.minor_version) |
302 if format_version not in self._SUPPORTED_FORMAT_VERSIONS: | 361 if format_version not in self._SUPPORTED_FORMAT_VERSIONS: |
303 raise errors.UnableToParseFile( | 362 raise errors.UnableToParseFile( |
304 '[{0:s}] Unsupported format version {1:s}.'.format( | 363 '[{0:s}] Unsupported format version {1:s}.'.format( |
305 self.NAME, format_version)) | 364 self.NAME, format_version)) |
306 | 365 |
307 if header.operation_identifier != 5: | 366 if header.operation_identifier != 5: |
308 # TODO: generate ExtractionWarning instead of printing debug output. | 367 # TODO: generate ExtractionWarning instead of printing debug output. |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
363 parser_mediator.ProduceEventWithEventData(event, event_data) | 422 parser_mediator.ProduceEventWithEventData(event, event_data) |
364 | 423 |
365 for name, usage in iter(self._POSIX_TIME_VALUES.items()): | 424 for name, usage in iter(self._POSIX_TIME_VALUES.items()): |
366 for time_value in time_dict.get(name, []): | 425 for time_value in time_dict.get(name, []): |
367 date_time = dfdatetime_posix_time.PosixTime(timestamp=time_value) | 426 date_time = dfdatetime_posix_time.PosixTime(timestamp=time_value) |
368 event = time_events.DateTimeValuesEvent(date_time, usage) | 427 event = time_events.DateTimeValuesEvent(date_time, usage) |
369 parser_mediator.ProduceEventWithEventData(event, event_data) | 428 parser_mediator.ProduceEventWithEventData(event, event_data) |
370 | 429 |
371 | 430 |
372 manager.ParsersManager.RegisterParser(CupsIppParser) | 431 manager.ParsersManager.RegisterParser(CupsIppParser) |
LEFT | RIGHT |