diff --git a/mitmproxy/contentviews/image/image_parser.py b/mitmproxy/contentviews/image/image_parser.py index b104d1054..1ff3cff7d 100644 --- a/mitmproxy/contentviews/image/image_parser.py +++ b/mitmproxy/contentviews/image/image_parser.py @@ -5,6 +5,7 @@ from kaitaistruct import KaitaiStream from mitmproxy.contrib.kaitaistruct import png from mitmproxy.contrib.kaitaistruct import gif +from mitmproxy.contrib.kaitaistruct import jpeg Metadata = typing.List[typing.Tuple[str, str]] @@ -55,3 +56,25 @@ def parse_gif(data: bytes) -> Metadata: if comment is not b'': parts.append(('comment', str(comment))) return parts + + +def parse_jpeg(data: bytes) -> Metadata: + img = jpeg.Jpeg(KaitaiStream(io.BytesIO(data))) + parts = [ + ('Format', 'JPEG (ISO 10918)') + ] + for segment in img.segments: + if segment.marker._name_ == 'sof0': + parts.append(('Size', "{0} x {1} px".format(segment.data.image_width, segment.data.image_height))) + if segment.marker._name_ == 'app0': + parts.append(('jfif_version', "({0}, {1})".format(segment.data.version_major, segment.data.version_minor))) + parts.append(('jfif_density', "({0}, {1})".format(segment.data.density_x, segment.data.density_y))) + parts.append(('jfif_unit', str(segment.data.density_units._value_))) + if segment.marker._name_ == 'com': + parts.append(('comment', str(segment.data))) + if segment.marker._name_ == 'app1': + if hasattr(segment.data, 'body'): + for field in segment.data.body.data.body.ifd0.fields: + if field.data is not None: + parts.append((field.tag._name_, field.data.decode('UTF-8').strip('\x00'))) + return parts diff --git a/mitmproxy/contentviews/image/view.py b/mitmproxy/contentviews/image/view.py index 9caf9a6c8..8fdb26e95 100644 --- a/mitmproxy/contentviews/image/view.py +++ b/mitmproxy/contentviews/image/view.py @@ -1,7 +1,6 @@ import io import imghdr -from PIL import ExifTags from PIL import Image from mitmproxy.types import multidict @@ -33,6 +32,11 @@ class ViewImage(base.View): parts = image_parser.parse_gif(data) fmt = base.format_dict(multidict.MultiDict(parts)) return "%s image" % f, fmt + elif image_type == 'jpeg': + f = "JPEG" + parts = image_parser.parse_jpeg(data) + fmt = base.format_dict(multidict.MultiDict(parts)) + return "%s image" % f, fmt try: img = Image.open(io.BytesIO(data)) except IOError: @@ -47,13 +51,5 @@ class ViewImage(base.View): parts.append( (str(i), str(img.info[i])) ) - if hasattr(img, "_getexif"): - ex = img._getexif() - if ex: - for i in sorted(ex.keys()): - tag = ExifTags.TAGS.get(i, i) - parts.append( - (str(tag), str(ex[i])) - ) fmt = base.format_dict(multidict.MultiDict(parts)) return "%s image" % img.format, fmt diff --git a/mitmproxy/contrib/kaitaistruct/exif.py b/mitmproxy/contrib/kaitaistruct/exif.py new file mode 100644 index 000000000..6236a7086 --- /dev/null +++ b/mitmproxy/contrib/kaitaistruct/exif.py @@ -0,0 +1,24 @@ +# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild +# The source was exif.ksy from here - https://github.com/kaitai-io/kaitai_struct_formats/blob/24e2d00048b8084ceec30a187a79cb87a79a48ba/image/exif.ksy + +import array +import struct +import zlib +from enum import Enum + +from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO + + +from .exif_le import ExifLe +from .exif_be import ExifBe +class Exif(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.endianness = self._io.read_u2le() + _on = self.endianness + if _on == 18761: + self.body = ExifLe(self._io) + elif _on == 19789: + self.body = ExifBe(self._io) diff --git a/mitmproxy/contrib/kaitaistruct/exif_be.py b/mitmproxy/contrib/kaitaistruct/exif_be.py new file mode 100644 index 000000000..7980a9e8f --- /dev/null +++ b/mitmproxy/contrib/kaitaistruct/exif_be.py @@ -0,0 +1,571 @@ +# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild +# The source was exif_be.ksy from here - https://github.com/kaitai-io/kaitai_struct_formats/blob/24e2d00048b8084ceec30a187a79cb87a79a48ba/image/exif_be.ksy + +import array +import struct +import zlib +from enum import Enum + +from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO + + +class ExifBe(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.version = self._io.read_u2be() + self.ifd0_ofs = self._io.read_u4be() + + class Ifd(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.num_fields = self._io.read_u2be() + self.fields = [None] * (self.num_fields) + for i in range(self.num_fields): + self.fields[i] = self._root.IfdField(self._io, self, self._root) + + self.next_ifd_ofs = self._io.read_u4be() + + @property + def next_ifd(self): + if hasattr(self, '_m_next_ifd'): + return self._m_next_ifd if hasattr(self, '_m_next_ifd') else None + + if self.next_ifd_ofs != 0: + _pos = self._io.pos() + self._io.seek(self.next_ifd_ofs) + self._m_next_ifd = self._root.Ifd(self._io, self, self._root) + self._io.seek(_pos) + + return self._m_next_ifd if hasattr(self, '_m_next_ifd') else None + + + class IfdField(KaitaiStruct): + + class FieldTypeEnum(Enum): + byte = 1 + ascii_string = 2 + word = 3 + dword = 4 + rational = 5 + + class TagEnum(Enum): + image_width = 256 + image_height = 257 + bits_per_sample = 258 + compression = 259 + photometric_interpretation = 262 + thresholding = 263 + cell_width = 264 + cell_length = 265 + fill_order = 266 + document_name = 269 + image_description = 270 + make = 271 + model = 272 + strip_offsets = 273 + orientation = 274 + samples_per_pixel = 277 + rows_per_strip = 278 + strip_byte_counts = 279 + min_sample_value = 280 + max_sample_value = 281 + x_resolution = 282 + y_resolution = 283 + planar_configuration = 284 + page_name = 285 + x_position = 286 + y_position = 287 + free_offsets = 288 + free_byte_counts = 289 + gray_response_unit = 290 + gray_response_curve = 291 + t4_options = 292 + t6_options = 293 + resolution_unit = 296 + page_number = 297 + color_response_unit = 300 + transfer_function = 301 + software = 305 + modify_date = 306 + artist = 315 + host_computer = 316 + predictor = 317 + white_point = 318 + primary_chromaticities = 319 + color_map = 320 + halftone_hints = 321 + tile_width = 322 + tile_length = 323 + tile_offsets = 324 + tile_byte_counts = 325 + bad_fax_lines = 326 + clean_fax_data = 327 + consecutive_bad_fax_lines = 328 + sub_ifd = 330 + ink_set = 332 + ink_names = 333 + numberof_inks = 334 + dot_range = 336 + target_printer = 337 + extra_samples = 338 + sample_format = 339 + s_min_sample_value = 340 + s_max_sample_value = 341 + transfer_range = 342 + clip_path = 343 + x_clip_path_units = 344 + y_clip_path_units = 345 + indexed = 346 + jpeg_tables = 347 + opi_proxy = 351 + global_parameters_ifd = 400 + profile_type = 401 + fax_profile = 402 + coding_methods = 403 + version_year = 404 + mode_number = 405 + decode = 433 + default_image_color = 434 + t82_options = 435 + jpeg_tables2 = 437 + jpeg_proc = 512 + thumbnail_offset = 513 + thumbnail_length = 514 + jpeg_restart_interval = 515 + jpeg_lossless_predictors = 517 + jpeg_point_transforms = 518 + jpegq_tables = 519 + jpegdc_tables = 520 + jpegac_tables = 521 + y_cb_cr_coefficients = 529 + y_cb_cr_sub_sampling = 530 + y_cb_cr_positioning = 531 + reference_black_white = 532 + strip_row_counts = 559 + application_notes = 700 + uspto_miscellaneous = 999 + related_image_file_format = 4096 + related_image_width = 4097 + related_image_height = 4098 + rating = 18246 + xp_dip_xml = 18247 + stitch_info = 18248 + rating_percent = 18249 + sony_raw_file_type = 28672 + light_falloff_params = 28722 + chromatic_aberration_corr_params = 28725 + distortion_corr_params = 28727 + image_id = 32781 + wang_tag1 = 32931 + wang_annotation = 32932 + wang_tag3 = 32933 + wang_tag4 = 32934 + image_reference_points = 32953 + region_xform_tack_point = 32954 + warp_quadrilateral = 32955 + affine_transform_mat = 32956 + matteing = 32995 + data_type = 32996 + image_depth = 32997 + tile_depth = 32998 + image_full_width = 33300 + image_full_height = 33301 + texture_format = 33302 + wrap_modes = 33303 + fov_cot = 33304 + matrix_world_to_screen = 33305 + matrix_world_to_camera = 33306 + model2 = 33405 + cfa_repeat_pattern_dim = 33421 + cfa_pattern2 = 33422 + battery_level = 33423 + kodak_ifd = 33424 + copyright = 33432 + exposure_time = 33434 + f_number = 33437 + md_file_tag = 33445 + md_scale_pixel = 33446 + md_color_table = 33447 + md_lab_name = 33448 + md_sample_info = 33449 + md_prep_date = 33450 + md_prep_time = 33451 + md_file_units = 33452 + pixel_scale = 33550 + advent_scale = 33589 + advent_revision = 33590 + uic1_tag = 33628 + uic2_tag = 33629 + uic3_tag = 33630 + uic4_tag = 33631 + iptc_naa = 33723 + intergraph_packet_data = 33918 + intergraph_flag_registers = 33919 + intergraph_matrix = 33920 + ingr_reserved = 33921 + model_tie_point = 33922 + site = 34016 + color_sequence = 34017 + it8_header = 34018 + raster_padding = 34019 + bits_per_run_length = 34020 + bits_per_extended_run_length = 34021 + color_table = 34022 + image_color_indicator = 34023 + background_color_indicator = 34024 + image_color_value = 34025 + background_color_value = 34026 + pixel_intensity_range = 34027 + transparency_indicator = 34028 + color_characterization = 34029 + hc_usage = 34030 + trap_indicator = 34031 + cmyk_equivalent = 34032 + sem_info = 34118 + afcp_iptc = 34152 + pixel_magic_jbig_options = 34232 + jpl_carto_ifd = 34263 + model_transform = 34264 + wb_grgb_levels = 34306 + leaf_data = 34310 + photoshop_settings = 34377 + exif_offset = 34665 + icc_profile = 34675 + tiff_fx_extensions = 34687 + multi_profiles = 34688 + shared_data = 34689 + t88_options = 34690 + image_layer = 34732 + geo_tiff_directory = 34735 + geo_tiff_double_params = 34736 + geo_tiff_ascii_params = 34737 + jbig_options = 34750 + exposure_program = 34850 + spectral_sensitivity = 34852 + gps_info = 34853 + iso = 34855 + opto_electric_conv_factor = 34856 + interlace = 34857 + time_zone_offset = 34858 + self_timer_mode = 34859 + sensitivity_type = 34864 + standard_output_sensitivity = 34865 + recommended_exposure_index = 34866 + iso_speed = 34867 + iso_speed_latitudeyyy = 34868 + iso_speed_latitudezzz = 34869 + fax_recv_params = 34908 + fax_sub_address = 34909 + fax_recv_time = 34910 + fedex_edr = 34929 + leaf_sub_ifd = 34954 + exif_version = 36864 + date_time_original = 36867 + create_date = 36868 + google_plus_upload_code = 36873 + offset_time = 36880 + offset_time_original = 36881 + offset_time_digitized = 36882 + components_configuration = 37121 + compressed_bits_per_pixel = 37122 + shutter_speed_value = 37377 + aperture_value = 37378 + brightness_value = 37379 + exposure_compensation = 37380 + max_aperture_value = 37381 + subject_distance = 37382 + metering_mode = 37383 + light_source = 37384 + flash = 37385 + focal_length = 37386 + flash_energy = 37387 + spatial_frequency_response = 37388 + noise = 37389 + focal_plane_x_resolution = 37390 + focal_plane_y_resolution = 37391 + focal_plane_resolution_unit = 37392 + image_number = 37393 + security_classification = 37394 + image_history = 37395 + subject_area = 37396 + exposure_index = 37397 + tiff_ep_standard_id = 37398 + sensing_method = 37399 + cip3_data_file = 37434 + cip3_sheet = 37435 + cip3_side = 37436 + sto_nits = 37439 + maker_note = 37500 + user_comment = 37510 + sub_sec_time = 37520 + sub_sec_time_original = 37521 + sub_sec_time_digitized = 37522 + ms_document_text = 37679 + ms_property_set_storage = 37680 + ms_document_text_position = 37681 + image_source_data = 37724 + ambient_temperature = 37888 + humidity = 37889 + pressure = 37890 + water_depth = 37891 + acceleration = 37892 + camera_elevation_angle = 37893 + xp_title = 40091 + xp_comment = 40092 + xp_author = 40093 + xp_keywords = 40094 + xp_subject = 40095 + flashpix_version = 40960 + color_space = 40961 + exif_image_width = 40962 + exif_image_height = 40963 + related_sound_file = 40964 + interop_offset = 40965 + samsung_raw_pointers_offset = 40976 + samsung_raw_pointers_length = 40977 + samsung_raw_byte_order = 41217 + samsung_raw_unknown = 41218 + flash_energy2 = 41483 + spatial_frequency_response2 = 41484 + noise2 = 41485 + focal_plane_x_resolution2 = 41486 + focal_plane_y_resolution2 = 41487 + focal_plane_resolution_unit2 = 41488 + image_number2 = 41489 + security_classification2 = 41490 + image_history2 = 41491 + subject_location = 41492 + exposure_index2 = 41493 + tiff_ep_standard_id2 = 41494 + sensing_method2 = 41495 + file_source = 41728 + scene_type = 41729 + cfa_pattern = 41730 + custom_rendered = 41985 + exposure_mode = 41986 + white_balance = 41987 + digital_zoom_ratio = 41988 + focal_length_in35mm_format = 41989 + scene_capture_type = 41990 + gain_control = 41991 + contrast = 41992 + saturation = 41993 + sharpness = 41994 + device_setting_description = 41995 + subject_distance_range = 41996 + image_unique_id = 42016 + owner_name = 42032 + serial_number = 42033 + lens_info = 42034 + lens_make = 42035 + lens_model = 42036 + lens_serial_number = 42037 + gdal_metadata = 42112 + gdal_no_data = 42113 + gamma = 42240 + expand_software = 44992 + expand_lens = 44993 + expand_film = 44994 + expand_filter_lens = 44995 + expand_scanner = 44996 + expand_flash_lamp = 44997 + pixel_format = 48129 + transformation = 48130 + uncompressed = 48131 + image_type = 48132 + image_width2 = 48256 + image_height2 = 48257 + width_resolution = 48258 + height_resolution = 48259 + image_offset = 48320 + image_byte_count = 48321 + alpha_offset = 48322 + alpha_byte_count = 48323 + image_data_discard = 48324 + alpha_data_discard = 48325 + oce_scanjob_desc = 50215 + oce_application_selector = 50216 + oce_id_number = 50217 + oce_image_logic = 50218 + annotations = 50255 + print_im = 50341 + original_file_name = 50547 + uspto_original_content_type = 50560 + dng_version = 50706 + dng_backward_version = 50707 + unique_camera_model = 50708 + localized_camera_model = 50709 + cfa_plane_color = 50710 + cfa_layout = 50711 + linearization_table = 50712 + black_level_repeat_dim = 50713 + black_level = 50714 + black_level_delta_h = 50715 + black_level_delta_v = 50716 + white_level = 50717 + default_scale = 50718 + default_crop_origin = 50719 + default_crop_size = 50720 + color_matrix1 = 50721 + color_matrix2 = 50722 + camera_calibration1 = 50723 + camera_calibration2 = 50724 + reduction_matrix1 = 50725 + reduction_matrix2 = 50726 + analog_balance = 50727 + as_shot_neutral = 50728 + as_shot_white_xy = 50729 + baseline_exposure = 50730 + baseline_noise = 50731 + baseline_sharpness = 50732 + bayer_green_split = 50733 + linear_response_limit = 50734 + camera_serial_number = 50735 + dng_lens_info = 50736 + chroma_blur_radius = 50737 + anti_alias_strength = 50738 + shadow_scale = 50739 + sr2_private = 50740 + maker_note_safety = 50741 + raw_image_segmentation = 50752 + calibration_illuminant1 = 50778 + calibration_illuminant2 = 50779 + best_quality_scale = 50780 + raw_data_unique_id = 50781 + alias_layer_metadata = 50784 + original_raw_file_name = 50827 + original_raw_file_data = 50828 + active_area = 50829 + masked_areas = 50830 + as_shot_icc_profile = 50831 + as_shot_pre_profile_matrix = 50832 + current_icc_profile = 50833 + current_pre_profile_matrix = 50834 + colorimetric_reference = 50879 + s_raw_type = 50885 + panasonic_title = 50898 + panasonic_title2 = 50899 + camera_calibration_sig = 50931 + profile_calibration_sig = 50932 + profile_ifd = 50933 + as_shot_profile_name = 50934 + noise_reduction_applied = 50935 + profile_name = 50936 + profile_hue_sat_map_dims = 50937 + profile_hue_sat_map_data1 = 50938 + profile_hue_sat_map_data2 = 50939 + profile_tone_curve = 50940 + profile_embed_policy = 50941 + profile_copyright = 50942 + forward_matrix1 = 50964 + forward_matrix2 = 50965 + preview_application_name = 50966 + preview_application_version = 50967 + preview_settings_name = 50968 + preview_settings_digest = 50969 + preview_color_space = 50970 + preview_date_time = 50971 + raw_image_digest = 50972 + original_raw_file_digest = 50973 + sub_tile_block_size = 50974 + row_interleave_factor = 50975 + profile_look_table_dims = 50981 + profile_look_table_data = 50982 + opcode_list1 = 51008 + opcode_list2 = 51009 + opcode_list3 = 51022 + noise_profile = 51041 + time_codes = 51043 + frame_rate = 51044 + t_stop = 51058 + reel_name = 51081 + original_default_final_size = 51089 + original_best_quality_size = 51090 + original_default_crop_size = 51091 + camera_label = 51105 + profile_hue_sat_map_encoding = 51107 + profile_look_table_encoding = 51108 + baseline_exposure_offset = 51109 + default_black_render = 51110 + new_raw_image_digest = 51111 + raw_to_preview_gain = 51112 + default_user_crop = 51125 + padding = 59932 + offset_schema = 59933 + owner_name2 = 65000 + serial_number2 = 65001 + lens = 65002 + kdc_ifd = 65024 + raw_file = 65100 + converter = 65101 + white_balance2 = 65102 + exposure = 65105 + shadows = 65106 + brightness = 65107 + contrast2 = 65108 + saturation2 = 65109 + sharpness2 = 65110 + smoothness = 65111 + moire_filter = 65112 + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.tag = self._root.IfdField.TagEnum(self._io.read_u2be()) + self.field_type = self._root.IfdField.FieldTypeEnum(self._io.read_u2be()) + self.length = self._io.read_u4be() + self.ofs_or_data = self._io.read_u4be() + + @property + def type_byte_length(self): + if hasattr(self, '_m_type_byte_length'): + return self._m_type_byte_length if hasattr(self, '_m_type_byte_length') else None + + self._m_type_byte_length = (2 if self.field_type == self._root.IfdField.FieldTypeEnum.word else (4 if self.field_type == self._root.IfdField.FieldTypeEnum.dword else 1)) + return self._m_type_byte_length if hasattr(self, '_m_type_byte_length') else None + + @property + def byte_length(self): + if hasattr(self, '_m_byte_length'): + return self._m_byte_length if hasattr(self, '_m_byte_length') else None + + self._m_byte_length = (self.length * self.type_byte_length) + return self._m_byte_length if hasattr(self, '_m_byte_length') else None + + @property + def is_immediate_data(self): + if hasattr(self, '_m_is_immediate_data'): + return self._m_is_immediate_data if hasattr(self, '_m_is_immediate_data') else None + + self._m_is_immediate_data = self.byte_length <= 4 + return self._m_is_immediate_data if hasattr(self, '_m_is_immediate_data') else None + + @property + def data(self): + if hasattr(self, '_m_data'): + return self._m_data if hasattr(self, '_m_data') else None + + if not self.is_immediate_data: + io = self._root._io + _pos = io.pos() + io.seek(self.ofs_or_data) + self._m_data = io.read_bytes(self.byte_length) + io.seek(_pos) + + return self._m_data if hasattr(self, '_m_data') else None + + + @property + def ifd0(self): + if hasattr(self, '_m_ifd0'): + return self._m_ifd0 if hasattr(self, '_m_ifd0') else None + + _pos = self._io.pos() + self._io.seek(self.ifd0_ofs) + self._m_ifd0 = self._root.Ifd(self._io, self, self._root) + self._io.seek(_pos) + return self._m_ifd0 if hasattr(self, '_m_ifd0') else None diff --git a/mitmproxy/contrib/kaitaistruct/exif_le.py b/mitmproxy/contrib/kaitaistruct/exif_le.py new file mode 100644 index 000000000..207b3bebe --- /dev/null +++ b/mitmproxy/contrib/kaitaistruct/exif_le.py @@ -0,0 +1,571 @@ +# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild +# The source was exif_le.ksy from here - https://github.com/kaitai-io/kaitai_struct_formats/blob/24e2d00048b8084ceec30a187a79cb87a79a48ba/image/exif_le.ksy + +import array +import struct +import zlib +from enum import Enum + +from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO + + +class ExifLe(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.version = self._io.read_u2le() + self.ifd0_ofs = self._io.read_u4le() + + class Ifd(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.num_fields = self._io.read_u2le() + self.fields = [None] * (self.num_fields) + for i in range(self.num_fields): + self.fields[i] = self._root.IfdField(self._io, self, self._root) + + self.next_ifd_ofs = self._io.read_u4le() + + @property + def next_ifd(self): + if hasattr(self, '_m_next_ifd'): + return self._m_next_ifd if hasattr(self, '_m_next_ifd') else None + + if self.next_ifd_ofs != 0: + _pos = self._io.pos() + self._io.seek(self.next_ifd_ofs) + self._m_next_ifd = self._root.Ifd(self._io, self, self._root) + self._io.seek(_pos) + + return self._m_next_ifd if hasattr(self, '_m_next_ifd') else None + + + class IfdField(KaitaiStruct): + + class FieldTypeEnum(Enum): + byte = 1 + ascii_string = 2 + word = 3 + dword = 4 + rational = 5 + + class TagEnum(Enum): + image_width = 256 + image_height = 257 + bits_per_sample = 258 + compression = 259 + photometric_interpretation = 262 + thresholding = 263 + cell_width = 264 + cell_length = 265 + fill_order = 266 + document_name = 269 + image_description = 270 + make = 271 + model = 272 + strip_offsets = 273 + orientation = 274 + samples_per_pixel = 277 + rows_per_strip = 278 + strip_byte_counts = 279 + min_sample_value = 280 + max_sample_value = 281 + x_resolution = 282 + y_resolution = 283 + planar_configuration = 284 + page_name = 285 + x_position = 286 + y_position = 287 + free_offsets = 288 + free_byte_counts = 289 + gray_response_unit = 290 + gray_response_curve = 291 + t4_options = 292 + t6_options = 293 + resolution_unit = 296 + page_number = 297 + color_response_unit = 300 + transfer_function = 301 + software = 305 + modify_date = 306 + artist = 315 + host_computer = 316 + predictor = 317 + white_point = 318 + primary_chromaticities = 319 + color_map = 320 + halftone_hints = 321 + tile_width = 322 + tile_length = 323 + tile_offsets = 324 + tile_byte_counts = 325 + bad_fax_lines = 326 + clean_fax_data = 327 + consecutive_bad_fax_lines = 328 + sub_ifd = 330 + ink_set = 332 + ink_names = 333 + numberof_inks = 334 + dot_range = 336 + target_printer = 337 + extra_samples = 338 + sample_format = 339 + s_min_sample_value = 340 + s_max_sample_value = 341 + transfer_range = 342 + clip_path = 343 + x_clip_path_units = 344 + y_clip_path_units = 345 + indexed = 346 + jpeg_tables = 347 + opi_proxy = 351 + global_parameters_ifd = 400 + profile_type = 401 + fax_profile = 402 + coding_methods = 403 + version_year = 404 + mode_number = 405 + decode = 433 + default_image_color = 434 + t82_options = 435 + jpeg_tables2 = 437 + jpeg_proc = 512 + thumbnail_offset = 513 + thumbnail_length = 514 + jpeg_restart_interval = 515 + jpeg_lossless_predictors = 517 + jpeg_point_transforms = 518 + jpegq_tables = 519 + jpegdc_tables = 520 + jpegac_tables = 521 + y_cb_cr_coefficients = 529 + y_cb_cr_sub_sampling = 530 + y_cb_cr_positioning = 531 + reference_black_white = 532 + strip_row_counts = 559 + application_notes = 700 + uspto_miscellaneous = 999 + related_image_file_format = 4096 + related_image_width = 4097 + related_image_height = 4098 + rating = 18246 + xp_dip_xml = 18247 + stitch_info = 18248 + rating_percent = 18249 + sony_raw_file_type = 28672 + light_falloff_params = 28722 + chromatic_aberration_corr_params = 28725 + distortion_corr_params = 28727 + image_id = 32781 + wang_tag1 = 32931 + wang_annotation = 32932 + wang_tag3 = 32933 + wang_tag4 = 32934 + image_reference_points = 32953 + region_xform_tack_point = 32954 + warp_quadrilateral = 32955 + affine_transform_mat = 32956 + matteing = 32995 + data_type = 32996 + image_depth = 32997 + tile_depth = 32998 + image_full_width = 33300 + image_full_height = 33301 + texture_format = 33302 + wrap_modes = 33303 + fov_cot = 33304 + matrix_world_to_screen = 33305 + matrix_world_to_camera = 33306 + model2 = 33405 + cfa_repeat_pattern_dim = 33421 + cfa_pattern2 = 33422 + battery_level = 33423 + kodak_ifd = 33424 + copyright = 33432 + exposure_time = 33434 + f_number = 33437 + md_file_tag = 33445 + md_scale_pixel = 33446 + md_color_table = 33447 + md_lab_name = 33448 + md_sample_info = 33449 + md_prep_date = 33450 + md_prep_time = 33451 + md_file_units = 33452 + pixel_scale = 33550 + advent_scale = 33589 + advent_revision = 33590 + uic1_tag = 33628 + uic2_tag = 33629 + uic3_tag = 33630 + uic4_tag = 33631 + iptc_naa = 33723 + intergraph_packet_data = 33918 + intergraph_flag_registers = 33919 + intergraph_matrix = 33920 + ingr_reserved = 33921 + model_tie_point = 33922 + site = 34016 + color_sequence = 34017 + it8_header = 34018 + raster_padding = 34019 + bits_per_run_length = 34020 + bits_per_extended_run_length = 34021 + color_table = 34022 + image_color_indicator = 34023 + background_color_indicator = 34024 + image_color_value = 34025 + background_color_value = 34026 + pixel_intensity_range = 34027 + transparency_indicator = 34028 + color_characterization = 34029 + hc_usage = 34030 + trap_indicator = 34031 + cmyk_equivalent = 34032 + sem_info = 34118 + afcp_iptc = 34152 + pixel_magic_jbig_options = 34232 + jpl_carto_ifd = 34263 + model_transform = 34264 + wb_grgb_levels = 34306 + leaf_data = 34310 + photoshop_settings = 34377 + exif_offset = 34665 + icc_profile = 34675 + tiff_fx_extensions = 34687 + multi_profiles = 34688 + shared_data = 34689 + t88_options = 34690 + image_layer = 34732 + geo_tiff_directory = 34735 + geo_tiff_double_params = 34736 + geo_tiff_ascii_params = 34737 + jbig_options = 34750 + exposure_program = 34850 + spectral_sensitivity = 34852 + gps_info = 34853 + iso = 34855 + opto_electric_conv_factor = 34856 + interlace = 34857 + time_zone_offset = 34858 + self_timer_mode = 34859 + sensitivity_type = 34864 + standard_output_sensitivity = 34865 + recommended_exposure_index = 34866 + iso_speed = 34867 + iso_speed_latitudeyyy = 34868 + iso_speed_latitudezzz = 34869 + fax_recv_params = 34908 + fax_sub_address = 34909 + fax_recv_time = 34910 + fedex_edr = 34929 + leaf_sub_ifd = 34954 + exif_version = 36864 + date_time_original = 36867 + create_date = 36868 + google_plus_upload_code = 36873 + offset_time = 36880 + offset_time_original = 36881 + offset_time_digitized = 36882 + components_configuration = 37121 + compressed_bits_per_pixel = 37122 + shutter_speed_value = 37377 + aperture_value = 37378 + brightness_value = 37379 + exposure_compensation = 37380 + max_aperture_value = 37381 + subject_distance = 37382 + metering_mode = 37383 + light_source = 37384 + flash = 37385 + focal_length = 37386 + flash_energy = 37387 + spatial_frequency_response = 37388 + noise = 37389 + focal_plane_x_resolution = 37390 + focal_plane_y_resolution = 37391 + focal_plane_resolution_unit = 37392 + image_number = 37393 + security_classification = 37394 + image_history = 37395 + subject_area = 37396 + exposure_index = 37397 + tiff_ep_standard_id = 37398 + sensing_method = 37399 + cip3_data_file = 37434 + cip3_sheet = 37435 + cip3_side = 37436 + sto_nits = 37439 + maker_note = 37500 + user_comment = 37510 + sub_sec_time = 37520 + sub_sec_time_original = 37521 + sub_sec_time_digitized = 37522 + ms_document_text = 37679 + ms_property_set_storage = 37680 + ms_document_text_position = 37681 + image_source_data = 37724 + ambient_temperature = 37888 + humidity = 37889 + pressure = 37890 + water_depth = 37891 + acceleration = 37892 + camera_elevation_angle = 37893 + xp_title = 40091 + xp_comment = 40092 + xp_author = 40093 + xp_keywords = 40094 + xp_subject = 40095 + flashpix_version = 40960 + color_space = 40961 + exif_image_width = 40962 + exif_image_height = 40963 + related_sound_file = 40964 + interop_offset = 40965 + samsung_raw_pointers_offset = 40976 + samsung_raw_pointers_length = 40977 + samsung_raw_byte_order = 41217 + samsung_raw_unknown = 41218 + flash_energy2 = 41483 + spatial_frequency_response2 = 41484 + noise2 = 41485 + focal_plane_x_resolution2 = 41486 + focal_plane_y_resolution2 = 41487 + focal_plane_resolution_unit2 = 41488 + image_number2 = 41489 + security_classification2 = 41490 + image_history2 = 41491 + subject_location = 41492 + exposure_index2 = 41493 + tiff_ep_standard_id2 = 41494 + sensing_method2 = 41495 + file_source = 41728 + scene_type = 41729 + cfa_pattern = 41730 + custom_rendered = 41985 + exposure_mode = 41986 + white_balance = 41987 + digital_zoom_ratio = 41988 + focal_length_in35mm_format = 41989 + scene_capture_type = 41990 + gain_control = 41991 + contrast = 41992 + saturation = 41993 + sharpness = 41994 + device_setting_description = 41995 + subject_distance_range = 41996 + image_unique_id = 42016 + owner_name = 42032 + serial_number = 42033 + lens_info = 42034 + lens_make = 42035 + lens_model = 42036 + lens_serial_number = 42037 + gdal_metadata = 42112 + gdal_no_data = 42113 + gamma = 42240 + expand_software = 44992 + expand_lens = 44993 + expand_film = 44994 + expand_filter_lens = 44995 + expand_scanner = 44996 + expand_flash_lamp = 44997 + pixel_format = 48129 + transformation = 48130 + uncompressed = 48131 + image_type = 48132 + image_width2 = 48256 + image_height2 = 48257 + width_resolution = 48258 + height_resolution = 48259 + image_offset = 48320 + image_byte_count = 48321 + alpha_offset = 48322 + alpha_byte_count = 48323 + image_data_discard = 48324 + alpha_data_discard = 48325 + oce_scanjob_desc = 50215 + oce_application_selector = 50216 + oce_id_number = 50217 + oce_image_logic = 50218 + annotations = 50255 + print_im = 50341 + original_file_name = 50547 + uspto_original_content_type = 50560 + dng_version = 50706 + dng_backward_version = 50707 + unique_camera_model = 50708 + localized_camera_model = 50709 + cfa_plane_color = 50710 + cfa_layout = 50711 + linearization_table = 50712 + black_level_repeat_dim = 50713 + black_level = 50714 + black_level_delta_h = 50715 + black_level_delta_v = 50716 + white_level = 50717 + default_scale = 50718 + default_crop_origin = 50719 + default_crop_size = 50720 + color_matrix1 = 50721 + color_matrix2 = 50722 + camera_calibration1 = 50723 + camera_calibration2 = 50724 + reduction_matrix1 = 50725 + reduction_matrix2 = 50726 + analog_balance = 50727 + as_shot_neutral = 50728 + as_shot_white_xy = 50729 + baseline_exposure = 50730 + baseline_noise = 50731 + baseline_sharpness = 50732 + bayer_green_split = 50733 + linear_response_limit = 50734 + camera_serial_number = 50735 + dng_lens_info = 50736 + chroma_blur_radius = 50737 + anti_alias_strength = 50738 + shadow_scale = 50739 + sr2_private = 50740 + maker_note_safety = 50741 + raw_image_segmentation = 50752 + calibration_illuminant1 = 50778 + calibration_illuminant2 = 50779 + best_quality_scale = 50780 + raw_data_unique_id = 50781 + alias_layer_metadata = 50784 + original_raw_file_name = 50827 + original_raw_file_data = 50828 + active_area = 50829 + masked_areas = 50830 + as_shot_icc_profile = 50831 + as_shot_pre_profile_matrix = 50832 + current_icc_profile = 50833 + current_pre_profile_matrix = 50834 + colorimetric_reference = 50879 + s_raw_type = 50885 + panasonic_title = 50898 + panasonic_title2 = 50899 + camera_calibration_sig = 50931 + profile_calibration_sig = 50932 + profile_ifd = 50933 + as_shot_profile_name = 50934 + noise_reduction_applied = 50935 + profile_name = 50936 + profile_hue_sat_map_dims = 50937 + profile_hue_sat_map_data1 = 50938 + profile_hue_sat_map_data2 = 50939 + profile_tone_curve = 50940 + profile_embed_policy = 50941 + profile_copyright = 50942 + forward_matrix1 = 50964 + forward_matrix2 = 50965 + preview_application_name = 50966 + preview_application_version = 50967 + preview_settings_name = 50968 + preview_settings_digest = 50969 + preview_color_space = 50970 + preview_date_time = 50971 + raw_image_digest = 50972 + original_raw_file_digest = 50973 + sub_tile_block_size = 50974 + row_interleave_factor = 50975 + profile_look_table_dims = 50981 + profile_look_table_data = 50982 + opcode_list1 = 51008 + opcode_list2 = 51009 + opcode_list3 = 51022 + noise_profile = 51041 + time_codes = 51043 + frame_rate = 51044 + t_stop = 51058 + reel_name = 51081 + original_default_final_size = 51089 + original_best_quality_size = 51090 + original_default_crop_size = 51091 + camera_label = 51105 + profile_hue_sat_map_encoding = 51107 + profile_look_table_encoding = 51108 + baseline_exposure_offset = 51109 + default_black_render = 51110 + new_raw_image_digest = 51111 + raw_to_preview_gain = 51112 + default_user_crop = 51125 + padding = 59932 + offset_schema = 59933 + owner_name2 = 65000 + serial_number2 = 65001 + lens = 65002 + kdc_ifd = 65024 + raw_file = 65100 + converter = 65101 + white_balance2 = 65102 + exposure = 65105 + shadows = 65106 + brightness = 65107 + contrast2 = 65108 + saturation2 = 65109 + sharpness2 = 65110 + smoothness = 65111 + moire_filter = 65112 + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.tag = self._root.IfdField.TagEnum(self._io.read_u2le()) + self.field_type = self._root.IfdField.FieldTypeEnum(self._io.read_u2le()) + self.length = self._io.read_u4le() + self.ofs_or_data = self._io.read_u4le() + + @property + def type_byte_length(self): + if hasattr(self, '_m_type_byte_length'): + return self._m_type_byte_length if hasattr(self, '_m_type_byte_length') else None + + self._m_type_byte_length = (2 if self.field_type == self._root.IfdField.FieldTypeEnum.word else (4 if self.field_type == self._root.IfdField.FieldTypeEnum.dword else 1)) + return self._m_type_byte_length if hasattr(self, '_m_type_byte_length') else None + + @property + def byte_length(self): + if hasattr(self, '_m_byte_length'): + return self._m_byte_length if hasattr(self, '_m_byte_length') else None + + self._m_byte_length = (self.length * self.type_byte_length) + return self._m_byte_length if hasattr(self, '_m_byte_length') else None + + @property + def is_immediate_data(self): + if hasattr(self, '_m_is_immediate_data'): + return self._m_is_immediate_data if hasattr(self, '_m_is_immediate_data') else None + + self._m_is_immediate_data = self.byte_length <= 4 + return self._m_is_immediate_data if hasattr(self, '_m_is_immediate_data') else None + + @property + def data(self): + if hasattr(self, '_m_data'): + return self._m_data if hasattr(self, '_m_data') else None + + if not self.is_immediate_data: + io = self._root._io + _pos = io.pos() + io.seek(self.ofs_or_data) + self._m_data = io.read_bytes(self.byte_length) + io.seek(_pos) + + return self._m_data if hasattr(self, '_m_data') else None + + + @property + def ifd0(self): + if hasattr(self, '_m_ifd0'): + return self._m_ifd0 if hasattr(self, '_m_ifd0') else None + + _pos = self._io.pos() + self._io.seek(self.ifd0_ofs) + self._m_ifd0 = self._root.Ifd(self._io, self, self._root) + self._io.seek(_pos) + return self._m_ifd0 if hasattr(self, '_m_ifd0') else None diff --git a/mitmproxy/contrib/kaitaistruct/gif.py b/mitmproxy/contrib/kaitaistruct/gif.py index 3a847d540..61499cc73 100644 --- a/mitmproxy/contrib/kaitaistruct/gif.py +++ b/mitmproxy/contrib/kaitaistruct/gif.py @@ -69,18 +69,18 @@ class Gif(KaitaiStruct): @property def has_color_table(self): if hasattr(self, '_m_has_color_table'): - return self._m_has_color_table + return self._m_has_color_table if hasattr(self, '_m_has_color_table') else None self._m_has_color_table = (self.flags & 128) != 0 - return self._m_has_color_table + return self._m_has_color_table if hasattr(self, '_m_has_color_table') else None @property def color_table_size(self): if hasattr(self, '_m_color_table_size'): - return self._m_color_table_size + return self._m_color_table_size if hasattr(self, '_m_color_table_size') else None self._m_color_table_size = (2 << (self.flags & 7)) - return self._m_color_table_size + return self._m_color_table_size if hasattr(self, '_m_color_table_size') else None class LocalImageDescriptor(KaitaiStruct): @@ -103,34 +103,34 @@ class Gif(KaitaiStruct): @property def has_color_table(self): if hasattr(self, '_m_has_color_table'): - return self._m_has_color_table + return self._m_has_color_table if hasattr(self, '_m_has_color_table') else None self._m_has_color_table = (self.flags & 128) != 0 - return self._m_has_color_table + return self._m_has_color_table if hasattr(self, '_m_has_color_table') else None @property def has_interlace(self): if hasattr(self, '_m_has_interlace'): - return self._m_has_interlace + return self._m_has_interlace if hasattr(self, '_m_has_interlace') else None self._m_has_interlace = (self.flags & 64) != 0 - return self._m_has_interlace + return self._m_has_interlace if hasattr(self, '_m_has_interlace') else None @property def has_sorted_color_table(self): if hasattr(self, '_m_has_sorted_color_table'): - return self._m_has_sorted_color_table + return self._m_has_sorted_color_table if hasattr(self, '_m_has_sorted_color_table') else None self._m_has_sorted_color_table = (self.flags & 32) != 0 - return self._m_has_sorted_color_table + return self._m_has_sorted_color_table if hasattr(self, '_m_has_sorted_color_table') else None @property def color_table_size(self): if hasattr(self, '_m_color_table_size'): - return self._m_color_table_size + return self._m_color_table_size if hasattr(self, '_m_color_table_size') else None self._m_color_table_size = (2 << (self.flags & 7)) - return self._m_color_table_size + return self._m_color_table_size if hasattr(self, '_m_color_table_size') else None class Block(KaitaiStruct): @@ -162,7 +162,7 @@ class Gif(KaitaiStruct): self._io = _io self._parent = _parent self._root = _root if _root else self - self.magic = self._io.ensure_fixed_contents(3, struct.pack('3b', 71, 73, 70)) + self.magic = self._io.ensure_fixed_contents(struct.pack('3b', 71, 73, 70)) self.version = self._io.read_bytes(3) @@ -171,27 +171,27 @@ class Gif(KaitaiStruct): self._io = _io self._parent = _parent self._root = _root if _root else self - self.block_size = self._io.ensure_fixed_contents(1, struct.pack('1b', 4)) + self.block_size = self._io.ensure_fixed_contents(struct.pack('1b', 4)) self.flags = self._io.read_u1() self.delay_time = self._io.read_u2le() self.transparent_idx = self._io.read_u1() - self.terminator = self._io.ensure_fixed_contents(1, struct.pack('1b', 0)) + self.terminator = self._io.ensure_fixed_contents(struct.pack('1b', 0)) @property def transparent_color_flag(self): if hasattr(self, '_m_transparent_color_flag'): - return self._m_transparent_color_flag + return self._m_transparent_color_flag if hasattr(self, '_m_transparent_color_flag') else None self._m_transparent_color_flag = (self.flags & 1) != 0 - return self._m_transparent_color_flag + return self._m_transparent_color_flag if hasattr(self, '_m_transparent_color_flag') else None @property def user_input_flag(self): if hasattr(self, '_m_user_input_flag'): - return self._m_user_input_flag + return self._m_user_input_flag if hasattr(self, '_m_user_input_flag') else None self._m_user_input_flag = (self.flags & 2) != 0 - return self._m_user_input_flag + return self._m_user_input_flag if hasattr(self, '_m_user_input_flag') else None class Subblock(KaitaiStruct): diff --git a/mitmproxy/contrib/kaitaistruct/jpeg.py b/mitmproxy/contrib/kaitaistruct/jpeg.py new file mode 100644 index 000000000..08e382a97 --- /dev/null +++ b/mitmproxy/contrib/kaitaistruct/jpeg.py @@ -0,0 +1,206 @@ +# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild +# The source was jpeg.ksy from here - https://github.com/kaitai-io/kaitai_struct_formats/blob/24e2d00048b8084ceec30a187a79cb87a79a48ba/image/jpeg.ksy + +import array +import struct +import zlib +from enum import Enum + +from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO + + +from .exif import Exif +class Jpeg(KaitaiStruct): + + class ComponentId(Enum): + y = 1 + cb = 2 + cr = 3 + i = 4 + q = 5 + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.segments = [] + while not self._io.is_eof(): + self.segments.append(self._root.Segment(self._io, self, self._root)) + + + class Segment(KaitaiStruct): + + class MarkerEnum(Enum): + tem = 1 + sof0 = 192 + sof1 = 193 + sof2 = 194 + sof3 = 195 + dht = 196 + sof5 = 197 + sof6 = 198 + sof7 = 199 + soi = 216 + eoi = 217 + sos = 218 + dqt = 219 + dnl = 220 + dri = 221 + dhp = 222 + app0 = 224 + app1 = 225 + app2 = 226 + app3 = 227 + app4 = 228 + app5 = 229 + app6 = 230 + app7 = 231 + app8 = 232 + app9 = 233 + app10 = 234 + app11 = 235 + app12 = 236 + app13 = 237 + app14 = 238 + app15 = 239 + com = 254 + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.magic = self._io.ensure_fixed_contents(struct.pack('1b', -1)) + self.marker = self._root.Segment.MarkerEnum(self._io.read_u1()) + if ((self.marker != self._root.Segment.MarkerEnum.soi) and (self.marker != self._root.Segment.MarkerEnum.eoi)) : + self.length = self._io.read_u2be() + + if ((self.marker != self._root.Segment.MarkerEnum.soi) and (self.marker != self._root.Segment.MarkerEnum.eoi)) : + _on = self.marker + if _on == self._root.Segment.MarkerEnum.sos: + self._raw_data = self._io.read_bytes((self.length - 2)) + io = KaitaiStream(BytesIO(self._raw_data)) + self.data = self._root.SegmentSos(io, self, self._root) + elif _on == self._root.Segment.MarkerEnum.app1: + self._raw_data = self._io.read_bytes((self.length - 2)) + io = KaitaiStream(BytesIO(self._raw_data)) + self.data = self._root.SegmentApp1(io, self, self._root) + elif _on == self._root.Segment.MarkerEnum.sof0: + self._raw_data = self._io.read_bytes((self.length - 2)) + io = KaitaiStream(BytesIO(self._raw_data)) + self.data = self._root.SegmentSof0(io, self, self._root) + elif _on == self._root.Segment.MarkerEnum.app0: + self._raw_data = self._io.read_bytes((self.length - 2)) + io = KaitaiStream(BytesIO(self._raw_data)) + self.data = self._root.SegmentApp0(io, self, self._root) + else: + self.data = self._io.read_bytes((self.length - 2)) + + if self.marker == self._root.Segment.MarkerEnum.sos: + self.image_data = self._io.read_bytes_full() + + + + class SegmentSos(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.num_components = self._io.read_u1() + self.components = [None] * (self.num_components) + for i in range(self.num_components): + self.components[i] = self._root.SegmentSos.Component(self._io, self, self._root) + + self.start_spectral_selection = self._io.read_u1() + self.end_spectral = self._io.read_u1() + self.appr_bit_pos = self._io.read_u1() + + class Component(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.id = self._root.ComponentId(self._io.read_u1()) + self.huffman_table = self._io.read_u1() + + + + class SegmentApp1(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.magic = self._io.read_strz("ASCII", 0, False, True, True) + _on = self.magic + if _on == u"Exif": + self.body = self._root.ExifInJpeg(self._io, self, self._root) + + + class SegmentSof0(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.bits_per_sample = self._io.read_u1() + self.image_height = self._io.read_u2be() + self.image_width = self._io.read_u2be() + self.num_components = self._io.read_u1() + self.components = [None] * (self.num_components) + for i in range(self.num_components): + self.components[i] = self._root.SegmentSof0.Component(self._io, self, self._root) + + + class Component(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.id = self._root.ComponentId(self._io.read_u1()) + self.sampling_factors = self._io.read_u1() + self.quantization_table_id = self._io.read_u1() + + @property + def sampling_x(self): + if hasattr(self, '_m_sampling_x'): + return self._m_sampling_x if hasattr(self, '_m_sampling_x') else None + + self._m_sampling_x = ((self.sampling_factors & 240) >> 4) + return self._m_sampling_x if hasattr(self, '_m_sampling_x') else None + + @property + def sampling_y(self): + if hasattr(self, '_m_sampling_y'): + return self._m_sampling_y if hasattr(self, '_m_sampling_y') else None + + self._m_sampling_y = (self.sampling_factors & 15) + return self._m_sampling_y if hasattr(self, '_m_sampling_y') else None + + + + class ExifInJpeg(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.extra_zero = self._io.ensure_fixed_contents(struct.pack('1b', 0)) + self._raw_data = self._io.read_bytes_full() + io = KaitaiStream(BytesIO(self._raw_data)) + self.data = Exif(io) + + + class SegmentApp0(KaitaiStruct): + + class DensityUnit(Enum): + no_units = 0 + pixels_per_inch = 1 + pixels_per_cm = 2 + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.magic = self._io.read_str_byte_limit(5, "ASCII") + self.version_major = self._io.read_u1() + self.version_minor = self._io.read_u1() + self.density_units = self._root.SegmentApp0.DensityUnit(self._io.read_u1()) + self.density_x = self._io.read_u2be() + self.density_y = self._io.read_u2be() + self.thumbnail_x = self._io.read_u1() + self.thumbnail_y = self._io.read_u1() + self.thumbnail = self._io.read_bytes(((self.thumbnail_x * self.thumbnail_y) * 3)) diff --git a/mitmproxy/contrib/kaitaistruct/png.py b/mitmproxy/contrib/kaitaistruct/png.py index 5b0e3ca37..2f3c1a5c3 100644 --- a/mitmproxy/contrib/kaitaistruct/png.py +++ b/mitmproxy/contrib/kaitaistruct/png.py @@ -25,9 +25,9 @@ class Png(KaitaiStruct): self._io = _io self._parent = _parent self._root = _root if _root else self - self.magic = self._io.ensure_fixed_contents(8, struct.pack('8b', -119, 80, 78, 71, 13, 10, 26, 10)) - self.ihdr_len = self._io.ensure_fixed_contents(4, struct.pack('4b', 0, 0, 0, 13)) - self.ihdr_type = self._io.ensure_fixed_contents(4, struct.pack('4b', 73, 72, 68, 82)) + self.magic = self._io.ensure_fixed_contents(struct.pack('8b', -119, 80, 78, 71, 13, 10, 26, 10)) + self.ihdr_len = self._io.ensure_fixed_contents(struct.pack('4b', 0, 0, 0, 13)) + self.ihdr_type = self._io.ensure_fixed_contents(struct.pack('4b', 73, 72, 68, 82)) self.ihdr = self._root.IhdrChunk(self._io, self, self._root) self.ihdr_crc = self._io.read_bytes(4) self.chunks = [] @@ -117,18 +117,18 @@ class Png(KaitaiStruct): @property def x(self): if hasattr(self, '_m_x'): - return self._m_x + return self._m_x if hasattr(self, '_m_x') else None self._m_x = (self.x_int / 100000.0) - return self._m_x + return self._m_x if hasattr(self, '_m_x') else None @property def y(self): if hasattr(self, '_m_y'): - return self._m_y + return self._m_y if hasattr(self, '_m_y') else None self._m_y = (self.y_int / 100000.0) - return self._m_y + return self._m_y if hasattr(self, '_m_y') else None class BkgdGreyscale(KaitaiStruct): @@ -186,7 +186,7 @@ class Png(KaitaiStruct): self._io = _io self._parent = _parent self._root = _root if _root else self - self.render_intent = self._root.Intent(self._io.read_u1()) + self.render_intent = self._root.SrgbChunk.Intent(self._io.read_u1()) class CompressedTextChunk(KaitaiStruct): @@ -220,10 +220,10 @@ class Png(KaitaiStruct): @property def gamma_ratio(self): if hasattr(self, '_m_gamma_ratio'): - return self._m_gamma_ratio + return self._m_gamma_ratio if hasattr(self, '_m_gamma_ratio') else None self._m_gamma_ratio = (100000.0 / self.gamma_int) - return self._m_gamma_ratio + return self._m_gamma_ratio if hasattr(self, '_m_gamma_ratio') else None class BkgdChunk(KaitaiStruct): diff --git a/setup.py b/setup.py index 7f3e7186a..0e41539a8 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,7 @@ setup( "html2text>=2016.1.8, <=2016.9.19", "hyperframe>=4.0.1, <5", "jsbeautifier>=1.6.3, <1.7", - "kaitaistruct>=0.5, <0.6", + "kaitaistruct>=0.6, <0.7", "Pillow>=3.2, <4.1", "passlib>=1.6.5, <1.8", "pyasn1>=0.1.9, <0.3", diff --git a/test/mitmproxy/contentviews/test_image.py b/test/mitmproxy/contentviews/test_image.py index 9e7e28f56..e3dfb714a 100644 --- a/test/mitmproxy/contentviews/test_image.py +++ b/test/mitmproxy/contentviews/test_image.py @@ -8,7 +8,7 @@ def test_view_image(): for img in [ "mitmproxy/data/image.png", "mitmproxy/data/image.gif", - "mitmproxy/data/image-err1.jpg", + "mitmproxy/data/all.jpeg", "mitmproxy/data/image.ico" ]: with open(tutils.test_data.path(img), "rb") as f: diff --git a/test/mitmproxy/contentviews/test_image_parser.py b/test/mitmproxy/contentviews/test_image_parser.py index 4241a1bb3..3c8bfdf7f 100644 --- a/test/mitmproxy/contentviews/test_image_parser.py +++ b/test/mitmproxy/contentviews/test_image_parser.py @@ -104,3 +104,66 @@ def test_parse_png(filename, metadata): def test_parse_gif(filename, metadata): with open(tutils.test_data.path(filename), 'rb') as f: assert metadata == image_parser.parse_gif(f.read()) + + +@pytest.mark.parametrize("filename, metadata", { + # check app0 + "mitmproxy/data/image_parser/example.jpg": [ + ('Format', 'JPEG (ISO 10918)'), + ('jfif_version', '(1, 1)'), + ('jfif_density', '(96, 96)'), + ('jfif_unit', '1'), + ('Size', '256 x 256 px') + ], + # check com + "mitmproxy/data/image_parser/comment.jpg": [ + ('Format', 'JPEG (ISO 10918)'), + ('jfif_version', '(1, 1)'), + ('jfif_density', '(96, 96)'), + ('jfif_unit', '1'), + ('comment', "b'mitmproxy test image'"), + ('Size', '256 x 256 px') + ], + # check app1 + "mitmproxy/data/image_parser/app1.jpeg": [ + ('Format', 'JPEG (ISO 10918)'), + ('jfif_version', '(1, 1)'), + ('jfif_density', '(72, 72)'), + ('jfif_unit', '1'), + ('make', 'Canon'), + ('model', 'Canon PowerShot A60'), + ('modify_date', '2004:07:16 18:46:04'), + ('Size', '717 x 558 px') + ], + # check multiple segments + "mitmproxy/data/image_parser/all.jpeg": [ + ('Format', 'JPEG (ISO 10918)'), + ('jfif_version', '(1, 1)'), + ('jfif_density', '(300, 300)'), + ('jfif_unit', '1'), + ('comment', 'b\'BARTOLOMEO DI FRUOSINO\\r\\n(b. ca. 1366, Firenze, d. 1441, ' + 'Firenze)\\r\\n\\r\\nInferno, from the Divine Comedy by Dante (Folio 1v)' + '\\r\\n1430-35\\r\\nTempera, gold, and silver on parchment, 365 x 265 mm' + '\\r\\nBiblioth\\xe8que Nationale, Paris\\r\\n\\r\\nThe codex in Paris ' + 'contains the text of the Inferno, the first of three books of the Divine ' + 'Comedy, the masterpiece of the Florentine poet Dante Alighieri (1265-1321).' + ' The codex begins with two full-page illuminations. On folio 1v Dante and ' + 'Virgil stand within the doorway of Hell at the upper left and observe its ' + 'nine different zones. Dante and Virgil are to wade through successive ' + 'circles teeming with images of the damned. The gates of Hell appear in ' + 'the middle, a scarlet row of open sarcophagi before them. Devils orchestrate' + ' the movements of the wretched souls.\\r\\n\\r\\nThe vision of the fiery ' + 'inferno follows a convention established by ' + 'Nardo di Cione\\\'s fresco in the church of Santa Maria Novella, Florence.' + ' Of remarkable vivacity and intensity of expression, the illumination is ' + 'executed in Bartolomeo\\\'s late style.\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n' + '--- Keywords: --------------\\r\\n\\r\\nAuthor: BARTOLOMEO DI FRUOSINO' + '\\r\\nTitle: Inferno, from the Divine Comedy by Dante (Folio 1v)\\r\\nTime-line:' + ' 1401-1450\\r\\nSchool: Italian\\r\\nForm: illumination\\r\\nType: other\\r\\n\''), + ('Size', '750 x 1055 px') + ], +}.items()) +def test_parse_jpeg(filename, metadata): + with open(tutils.test_data.path(filename), 'rb') as f: + assert metadata == image_parser.parse_jpeg(f.read()) diff --git a/test/mitmproxy/data/all.jpeg b/test/mitmproxy/data/all.jpeg new file mode 100644 index 000000000..ea5d8d0fd Binary files /dev/null and b/test/mitmproxy/data/all.jpeg differ diff --git a/test/mitmproxy/data/image-err1.jpg b/test/mitmproxy/data/image-err1.jpg deleted file mode 100644 index 1b251e6ea..000000000 Binary files a/test/mitmproxy/data/image-err1.jpg and /dev/null differ diff --git a/test/mitmproxy/data/image_parser/README.md b/test/mitmproxy/data/image_parser/README.md new file mode 100644 index 000000000..c23409bf3 --- /dev/null +++ b/test/mitmproxy/data/image_parser/README.md @@ -0,0 +1,17 @@ +# Sources of the images used + +## PNG + +`aspect.png` - http://pngimg.com/upload/water_PNG3290.png +All other PNGs are from the [PNGTestSuite](http://www.schaik.com/pngsuite/) + +# GIF + +All the GIFs are from the Pillow repository [here](https://github.com/python-pillow/Pillow/tree/master/Tests/images) + +# JPEG + +`app1.jpeg` - https://commons.wikimedia.org/wiki/File:PT05_ubt.jpeg +`all.jpeg` - https://commons.wikimedia.org/wiki/Category:Dante's_Inferno#/media/File:Bartolomeo_Di_Fruosino_-_Inferno,_from_the_Divine_Comedy_by_Dante_(Folio_1v)_-_WGA01339.jpg +`comment.jpg` has been taken from [here](https://commons.wikimedia.org/wiki/File:JPEG_example_image.jpg) and has a comment added locally +All other JPEGs are from the Pillow repository [here](https://github.com/python-pillow/Pillow/tree/master/Tests/images) diff --git a/test/mitmproxy/data/image_parser/all.jpeg b/test/mitmproxy/data/image_parser/all.jpeg new file mode 100644 index 000000000..ea5d8d0fd Binary files /dev/null and b/test/mitmproxy/data/image_parser/all.jpeg differ diff --git a/test/mitmproxy/data/image_parser/app1.jpeg b/test/mitmproxy/data/image_parser/app1.jpeg new file mode 100644 index 000000000..baa77dfe5 Binary files /dev/null and b/test/mitmproxy/data/image_parser/app1.jpeg differ diff --git a/test/mitmproxy/data/image_parser/comment.jpg b/test/mitmproxy/data/image_parser/comment.jpg new file mode 100644 index 000000000..74d443ddd Binary files /dev/null and b/test/mitmproxy/data/image_parser/comment.jpg differ diff --git a/test/mitmproxy/data/image_parser/example.jpg b/test/mitmproxy/data/image_parser/example.jpg new file mode 100644 index 000000000..415ee4b28 Binary files /dev/null and b/test/mitmproxy/data/image_parser/example.jpg differ