emf 0.1.2 → 0.2.0
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 +4 -4
- data/README.adoc +224 -19
- data/exe/emf +12 -0
- data/lib/emf/backend.rb +60 -0
- data/lib/emf/binary/class_field_accessors.rb +49 -0
- data/lib/emf/binary/float32_le.rb +54 -0
- data/lib/emf/binary/types.rb +40 -6
- data/lib/emf/conformance/context.rb +36 -0
- data/lib/emf/conformance/diagnostic.rb +18 -0
- data/lib/emf/conformance/manifest/emf_bitmap_misc.yml +622 -0
- data/lib/emf/conformance/manifest/emf_control_buffers.yml +61 -0
- data/lib/emf/conformance/manifest/emf_core.yml +117 -0
- data/lib/emf/conformance/manifest/emf_corpus_rules.yml +21 -0
- data/lib/emf/conformance/manifest/emf_objects_geometry.yml +229 -0
- data/lib/emf/conformance/manifest/emf_plus.yml +357 -0
- data/lib/emf/conformance/manifest/emf_plus_core.yml +33 -0
- data/lib/emf/conformance/manifest/emf_state_path.yml +403 -0
- data/lib/emf/conformance/manifest/emf_text.yml +97 -0
- data/lib/emf/conformance/manifest/wmf.yml +874 -0
- data/lib/emf/conformance/manifest.rb +79 -0
- data/lib/emf/conformance/profile.rb +35 -0
- data/lib/emf/conformance/record_types.rb +24 -0
- data/lib/emf/conformance/registry.rb +76 -0
- data/lib/emf/conformance/report.rb +83 -0
- data/lib/emf/conformance/rule.rb +16 -0
- data/lib/emf/conformance/rules/emf_bitmap.rb +357 -0
- data/lib/emf/conformance/rules/emf_comment_escape.rb +309 -0
- data/lib/emf/conformance/rules/emf_control_buffers.rb +137 -0
- data/lib/emf/conformance/rules/emf_corpus_named.rb +83 -0
- data/lib/emf/conformance/rules/emf_framing.rb +94 -0
- data/lib/emf/conformance/rules/emf_geometry.rb +282 -0
- data/lib/emf/conformance/rules/emf_header.rb +114 -0
- data/lib/emf/conformance/rules/emf_objects.rb +291 -0
- data/lib/emf/conformance/rules/emf_parse_recovery.rb +97 -0
- data/lib/emf/conformance/rules/emf_path_clip.rb +165 -0
- data/lib/emf/conformance/rules/emf_plus.rb +75 -0
- data/lib/emf/conformance/rules/emf_plus_common.rb +58 -0
- data/lib/emf/conformance/rules/emf_plus_drawing.rb +78 -0
- data/lib/emf/conformance/rules/emf_plus_effects.rb +104 -0
- data/lib/emf/conformance/rules/emf_plus_fields.rb +169 -0
- data/lib/emf/conformance/rules/emf_plus_fields_b.rb +102 -0
- data/lib/emf/conformance/rules/emf_plus_framing.rb +172 -0
- data/lib/emf/conformance/rules/emf_plus_objects.rb +97 -0
- data/lib/emf/conformance/rules/emf_plus_payloads.rb +320 -0
- data/lib/emf/conformance/rules/emf_plus_properties.rb +96 -0
- data/lib/emf/conformance/rules/emf_plus_state.rb +77 -0
- data/lib/emf/conformance/rules/emf_state.rb +244 -0
- data/lib/emf/conformance/rules/emf_text.rb +221 -0
- data/lib/emf/conformance/rules/emf_transform.rb +85 -0
- data/lib/emf/conformance/rules/wmf_buffers.rb +259 -0
- data/lib/emf/conformance/rules/wmf_framing.rb +146 -0
- data/lib/emf/conformance/rules/wmf_layout_records.rb +112 -0
- data/lib/emf/conformance/rules/wmf_objects.rb +168 -0
- data/lib/emf/conformance/rules/wmf_records.rb +191 -0
- data/lib/emf/conformance/rules/wmf_rejections.rb +184 -0
- data/lib/emf/conformance/runner.rb +61 -0
- data/lib/emf/conformance/ternary_raster_operations.rb +52 -0
- data/lib/emf/conformance.rb +31 -0
- data/lib/emf/emf_plus/effects/blur.rb +24 -0
- data/lib/emf/emf_plus/effects/brightness_contrast.rb +22 -0
- data/lib/emf/emf_plus/effects/color_balance.rb +24 -0
- data/lib/emf/emf_plus/effects/color_curve.rb +24 -0
- data/lib/emf/emf_plus/effects/color_lookup_table.rb +26 -0
- data/lib/emf/emf_plus/effects/color_matrix.rb +23 -0
- data/lib/emf/emf_plus/effects/effect.rb +50 -0
- data/lib/emf/emf_plus/effects/hue_saturation_lightness.rb +24 -0
- data/lib/emf/emf_plus/effects/levels.rb +24 -0
- data/lib/emf/emf_plus/effects/opaque.rb +19 -0
- data/lib/emf/emf_plus/effects/red_eye_correction.rb +35 -0
- data/lib/emf/emf_plus/effects/sharpen.rb +24 -0
- data/lib/emf/emf_plus/effects/tint.rb +22 -0
- data/lib/emf/emf_plus/effects.rb +45 -0
- data/lib/emf/emf_plus/frame.rb +41 -0
- data/lib/emf/emf_plus/links/effect_link.rb +16 -0
- data/lib/emf/emf_plus/links/get_dc_span.rb +18 -0
- data/lib/emf/emf_plus/links/link_error.rb +15 -0
- data/lib/emf/emf_plus/links.rb +77 -0
- data/lib/emf/emf_plus/object_chain.rb +108 -0
- data/lib/emf/emf_plus/object_table.rb +182 -0
- data/lib/emf/emf_plus/objects/brush.rb +296 -0
- data/lib/emf/emf_plus/objects/custom_line_cap.rb +106 -0
- data/lib/emf/emf_plus/objects/font.rb +47 -0
- data/lib/emf/emf_plus/objects/image.rb +68 -0
- data/lib/emf/emf_plus/objects/image_attributes.rb +28 -0
- data/lib/emf/emf_plus/objects/opaque.rb +19 -0
- data/lib/emf/emf_plus/objects/parts.rb +103 -0
- data/lib/emf/emf_plus/objects/path.rb +21 -0
- data/lib/emf/emf_plus/objects/pen.rb +132 -0
- data/lib/emf/emf_plus/objects/region.rb +39 -0
- data/lib/emf/emf_plus/objects/string_format.rb +55 -0
- data/lib/emf/emf_plus/objects/typed.rb +50 -0
- data/lib/emf/emf_plus/objects.rb +46 -0
- data/lib/emf/emf_plus/parser.rb +64 -2
- data/lib/emf/emf_plus/records/base.rb +107 -0
- data/lib/emf/emf_plus/records/begin_container.rb +31 -0
- data/lib/emf/emf_plus/records/begin_container_no_params.rb +20 -0
- data/lib/emf/emf_plus/records/body.rb +16 -0
- data/lib/emf/emf_plus/records/clear.rb +22 -0
- data/lib/emf/emf_plus/records/clip_transform_flags.rb +26 -0
- data/lib/emf/emf_plus/records/comment.rb +21 -0
- data/lib/emf/emf_plus/records/draw_arc.rb +33 -0
- data/lib/emf/emf_plus/records/draw_beziers.rb +31 -0
- data/lib/emf/emf_plus/records/draw_closed_curve.rb +34 -0
- data/lib/emf/emf_plus/records/draw_curve.rb +38 -0
- data/lib/emf/emf_plus/records/draw_driver_string.rb +61 -0
- data/lib/emf/emf_plus/records/draw_ellipse.rb +28 -0
- data/lib/emf/emf_plus/records/draw_image.rb +42 -0
- data/lib/emf/emf_plus/records/draw_image_points.rb +62 -0
- data/lib/emf/emf_plus/records/draw_lines.rb +34 -0
- data/lib/emf/emf_plus/records/draw_path.rb +23 -0
- data/lib/emf/emf_plus/records/draw_pie.rb +33 -0
- data/lib/emf/emf_plus/records/draw_rects.rb +30 -0
- data/lib/emf/emf_plus/records/draw_string.rb +41 -0
- data/lib/emf/emf_plus/records/end_container.rb +20 -0
- data/lib/emf/emf_plus/records/end_of_file.rb +17 -0
- data/lib/emf/emf_plus/records/fill_brush.rb +21 -0
- data/lib/emf/emf_plus/records/fill_closed_curve.rb +38 -0
- data/lib/emf/emf_plus/records/fill_ellipse.rb +29 -0
- data/lib/emf/emf_plus/records/fill_path.rb +23 -0
- data/lib/emf/emf_plus/records/fill_pie.rb +34 -0
- data/lib/emf/emf_plus/records/fill_polygon.rb +32 -0
- data/lib/emf/emf_plus/records/fill_rects.rb +31 -0
- data/lib/emf/emf_plus/records/fill_region.rb +23 -0
- data/lib/emf/emf_plus/records/get_dc.rb +18 -0
- data/lib/emf/emf_plus/records/header.rb +35 -0
- data/lib/emf/emf_plus/records/multiply_world_transform.rb +24 -0
- data/lib/emf/emf_plus/records/object.rb +32 -0
- data/lib/emf/emf_plus/records/offset_clip.rb +24 -0
- data/lib/emf/emf_plus/records/primitives/argb.rb +20 -0
- data/lib/emf/emf_plus/records/primitives/bitmap.rb +32 -0
- data/lib/emf/emf_plus/records/primitives/bitmap_data.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/blend_colors.rb +22 -0
- data/lib/emf/emf_plus/records/primitives/blend_factors.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/bounded_array.rb +38 -0
- data/lib/emf/emf_plus/records/primitives/byte_reader.rb +25 -0
- data/lib/emf/emf_plus/records/primitives/character_range.rb +20 -0
- data/lib/emf/emf_plus/records/primitives/compressed_image.rb +17 -0
- data/lib/emf/emf_plus/records/primitives/focus_scale_data.rb +20 -0
- data/lib/emf/emf_plus/records/primitives/graphics_version.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/language_identifier.rb +22 -0
- data/lib/emf/emf_plus/records/primitives/metafile.rb +19 -0
- data/lib/emf/emf_plus/records/primitives/palette.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/path.rb +89 -0
- data/lib/emf/emf_plus/records/primitives/path_point_type.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/path_point_type_rle.rb +23 -0
- data/lib/emf/emf_plus/records/primitives/point.rb +18 -0
- data/lib/emf/emf_plus/records/primitives/point_array.rb +42 -0
- data/lib/emf/emf_plus/records/primitives/point_f.rb +19 -0
- data/lib/emf/emf_plus/records/primitives/point_r.rb +19 -0
- data/lib/emf/emf_plus/records/primitives/rect.rb +20 -0
- data/lib/emf/emf_plus/records/primitives/rect_array.rb +24 -0
- data/lib/emf/emf_plus/records/primitives/rect_f.rb +21 -0
- data/lib/emf/emf_plus/records/primitives/region_node.rb +68 -0
- data/lib/emf/emf_plus/records/primitives/region_node_child_nodes.rb +22 -0
- data/lib/emf/emf_plus/records/primitives/region_node_path.rb +37 -0
- data/lib/emf/emf_plus/records/primitives/relative_coordinate.rb +32 -0
- data/lib/emf/emf_plus/records/primitives/string_format_data.rb +23 -0
- data/lib/emf/emf_plus/records/primitives/transform_matrix.rb +23 -0
- data/lib/emf/emf_plus/records/primitives.rb +41 -0
- data/lib/emf/emf_plus/records/reset_clip.rb +17 -0
- data/lib/emf/emf_plus/records/reset_world_transform.rb +17 -0
- data/lib/emf/emf_plus/records/restore.rb +20 -0
- data/lib/emf/emf_plus/records/rotate_world_transform.rb +24 -0
- data/lib/emf/emf_plus/records/save.rb +20 -0
- data/lib/emf/emf_plus/records/scale_world_transform.rb +26 -0
- data/lib/emf/emf_plus/records/serializable_object.rb +32 -0
- data/lib/emf/emf_plus/records/set_anti_alias_mode.rb +25 -0
- data/lib/emf/emf_plus/records/set_clip_path.rb +20 -0
- data/lib/emf/emf_plus/records/set_clip_rect.rb +24 -0
- data/lib/emf/emf_plus/records/set_clip_region.rb +20 -0
- data/lib/emf/emf_plus/records/set_compositing_mode.rb +21 -0
- data/lib/emf/emf_plus/records/set_compositing_quality.rb +21 -0
- data/lib/emf/emf_plus/records/set_interpolation_mode.rb +21 -0
- data/lib/emf/emf_plus/records/set_page_transform.rb +25 -0
- data/lib/emf/emf_plus/records/set_pixel_offset_mode.rb +21 -0
- data/lib/emf/emf_plus/records/set_rendering_origin.rb +22 -0
- data/lib/emf/emf_plus/records/set_text_contrast.rb +24 -0
- data/lib/emf/emf_plus/records/set_text_rendering_hint.rb +21 -0
- data/lib/emf/emf_plus/records/set_ts_clip.rb +40 -0
- data/lib/emf/emf_plus/records/set_ts_graphics.rb +88 -0
- data/lib/emf/emf_plus/records/set_world_transform.rb +22 -0
- data/lib/emf/emf_plus/records/shape_flags.rb +23 -0
- data/lib/emf/emf_plus/records/translate_world_transform.rb +26 -0
- data/lib/emf/emf_plus/records/unknown.rb +20 -0
- data/lib/emf/emf_plus/records.rb +107 -0
- data/lib/emf/emf_plus/serializer.rb +9 -2
- data/lib/emf/emf_plus/stream/conformance.rb +68 -0
- data/lib/emf/emf_plus/stream/editor.rb +21 -0
- data/lib/emf/emf_plus/stream/emf_gap.rb +20 -0
- data/lib/emf/emf_plus/stream/link_index.rb +30 -0
- data/lib/emf/emf_plus/stream/rebuild.rb +47 -0
- data/lib/emf/emf_plus/stream/record_handle.rb +26 -0
- data/lib/emf/emf_plus/stream/segment.rb +70 -0
- data/lib/emf/emf_plus/stream.rb +153 -0
- data/lib/emf/emf_plus.rb +9 -0
- data/lib/emf/emr/binary/dib_header.rb +142 -0
- data/lib/emf/emr/binary/ext_text_out_fields.rb +43 -0
- data/lib/emf/emr/binary/extended_pen_buffers.rb +23 -0
- data/lib/emf/emr/binary/fast_fill.rb +1164 -0
- data/lib/emf/emr/binary/font.rb +159 -0
- data/lib/emf/emr/binary/lazy_utf16_array.rb +82 -0
- data/lib/emf/emr/binary/pack_plan.rb +144 -0
- data/lib/emf/emr/binary/pack_plan_check.rb +75 -0
- data/lib/emf/emr/binary/pack_plan_exact.rb +130 -0
- data/lib/emf/emr/binary/pack_plan_generate.rb +116 -0
- data/lib/emf/emr/binary/pack_plan_values.rb +92 -0
- data/lib/emf/emr/binary/palette_body.rb +19 -0
- data/lib/emf/emr/binary/palette_entry.rb +18 -0
- data/lib/emf/emr/binary/record.rb +33 -0
- data/lib/emf/emr/binary/records/alpha_blend.rb +43 -0
- data/lib/emf/emr/binary/records/angle_arc.rb +2 -2
- data/lib/emf/emr/binary/records/bit_blt.rb +14 -0
- data/lib/emf/emr/binary/records/color_correct_palette.rb +55 -1
- data/lib/emf/emr/binary/records/colormatch_to_target_w.rb +76 -1
- data/lib/emf/emr/binary/records/create_colorspace.rb +67 -1
- data/lib/emf/emr/binary/records/create_colorspace_w.rb +90 -1
- data/lib/emf/emr/binary/records/create_dib_pattern_brush_pt.rb +14 -0
- data/lib/emf/emr/binary/records/create_font_indirect_w.rb +6 -2
- data/lib/emf/emr/binary/records/create_mono_brush.rb +14 -0
- data/lib/emf/emr/binary/records/create_palette.rb +19 -1
- data/lib/emf/emr/binary/records/delete_colorspace.rb +22 -1
- data/lib/emf/emr/binary/records/draw_escape.rb +45 -1
- data/lib/emf/emr/binary/records/ellipse.rb +1 -1
- data/lib/emf/emr/binary/records/eof.rb +9 -0
- data/lib/emf/emr/binary/records/ext_create_pen.rb +80 -1
- data/lib/emf/emr/binary/records/ext_escape.rb +45 -1
- data/lib/emf/emr/binary/records/ext_flood_fill.rb +9 -2
- data/lib/emf/emr/binary/records/ext_select_clip_rgn.rb +9 -1
- data/lib/emf/emr/binary/records/ext_text_out_a.rb +16 -3
- data/lib/emf/emr/binary/records/ext_text_out_w.rb +14 -3
- data/lib/emf/emr/binary/records/fill_rgn.rb +11 -2
- data/lib/emf/emr/binary/records/force_ufi_mapping.rb +38 -1
- data/lib/emf/emr/binary/records/frame_rgn.rb +12 -2
- data/lib/emf/emr/binary/records/gls_bounded_record.rb +61 -1
- data/lib/emf/emr/binary/records/gls_record.rb +42 -1
- data/lib/emf/emr/binary/records/gradient_fill.rb +7 -0
- data/lib/emf/emr/binary/records/invert_rgn.rb +11 -3
- data/lib/emf/emr/binary/records/mask_blt.rb +66 -0
- data/lib/emf/emr/binary/records/named_escape.rb +78 -1
- data/lib/emf/emr/binary/records/paint_rgn.rb +11 -3
- data/lib/emf/emr/binary/records/pixel_format.rb +76 -1
- data/lib/emf/emr/binary/records/plg_blt.rb +66 -0
- data/lib/emf/emr/binary/records/poly_textout_a.rb +128 -1
- data/lib/emf/emr/binary/records/poly_textout_w.rb +128 -1
- data/lib/emf/emr/binary/records/raw.rb +3 -0
- data/lib/emf/emr/binary/records/realize_palette.rb +1 -0
- data/lib/emf/emr/binary/records/rectangle.rb +1 -1
- data/lib/emf/emr/binary/records/resize_palette.rb +16 -2
- data/lib/emf/emr/binary/records/round_rect.rb +2 -2
- data/lib/emf/emr/binary/records/select_palette.rb +1 -0
- data/lib/emf/emr/binary/records/set_color_adjustment.rb +136 -0
- data/lib/emf/emr/binary/records/set_colorspace.rb +22 -1
- data/lib/emf/emr/binary/records/set_dibits_to_device.rb +43 -0
- data/lib/emf/emr/binary/records/set_icm_profile_a.rb +71 -1
- data/lib/emf/emr/binary/records/set_icm_profile_w.rb +71 -1
- data/lib/emf/emr/binary/records/set_linked_ufis.rb +49 -1
- data/lib/emf/emr/binary/records/set_miter_limit.rb +1 -1
- data/lib/emf/emr/binary/records/set_palette_entries.rb +20 -2
- data/lib/emf/emr/binary/records/set_text_justification.rb +8 -2
- data/lib/emf/emr/binary/records/small_text_out.rb +55 -4
- data/lib/emf/emr/binary/records/stretch_blt.rb +14 -0
- data/lib/emf/emr/binary/records/stretch_dibits.rb +14 -0
- data/lib/emf/emr/binary/records/transparent_blt.rb +43 -0
- data/lib/emf/emr/binary/records.rb +4 -2
- data/lib/emf/emr/binary/region_data.rb +33 -0
- data/lib/emf/emr/binary/state_record_body.rb +33 -0
- data/lib/emf/emr/binary/thin_shell.rb +226 -0
- data/lib/emf/emr/binary/value_view/layout.rb +101 -0
- data/lib/emf/emr/binary/value_view/reader_source.rb +88 -0
- data/lib/emf/emr/binary/value_view.rb +173 -0
- data/lib/emf/emr/binary/variable_buffers.rb +87 -0
- data/lib/emf/emr/binary.rb +4 -0
- data/lib/emf/emr/filled_record.rb +77 -0
- data/lib/emf/emr/parser.rb +239 -50
- data/lib/emf/emr/serializer.rb +4 -5
- data/lib/emf/emr.rb +1 -0
- data/lib/emf/error.rb +24 -1
- data/lib/emf/model/diagnostic.rb +41 -0
- data/lib/emf/model/emr/header.rb +40 -1
- data/lib/emf/model/emr/records.rb +142 -6
- data/lib/emf/model/emr/wire_values.rb +43 -0
- data/lib/emf/model/emr.rb +1 -0
- data/lib/emf/model/geometry/color.rb +19 -1
- data/lib/emf/model/metafile.rb +22 -2
- data/lib/emf/model/wmf/bitmap_records.rb +155 -0
- data/lib/emf/model/wmf/brush_records.rb +45 -0
- data/lib/emf/model/wmf/dc_escape_records.rb +67 -0
- data/lib/emf/model/wmf/font_record.rb +40 -0
- data/lib/emf/model/wmf/geometry_records.rb +107 -0
- data/lib/emf/model/wmf/header.rb +36 -0
- data/lib/emf/model/wmf/object_records.rb +71 -0
- data/lib/emf/model/wmf/palette_records.rb +68 -0
- data/lib/emf/model/wmf/records.rb +247 -0
- data/lib/emf/model/wmf/region_records.rb +112 -0
- data/lib/emf/model/wmf/state_records.rb +188 -0
- data/lib/emf/model/wmf/text_records.rb +36 -0
- data/lib/emf/model/wmf.rb +11 -0
- data/lib/emf/model.rb +2 -0
- data/lib/emf/native/bitmap_fields.rb +58 -0
- data/lib/emf/native/diagnostic_contract.rb +39 -0
- data/lib/emf/native/diagnostics.rb +81 -0
- data/lib/emf/native/emf_plus_loader.rb +35 -0
- data/lib/emf/native/emf_plus_snapshot.rb +96 -0
- data/lib/emf/native/emf_plus_source.rb +20 -0
- data/lib/emf/native/emf_plus_stream.rb +150 -0
- data/lib/emf/native/fields.rb +369 -0
- data/lib/emf/native/font_fields.rb +87 -0
- data/lib/emf/native/gradient_fields.rb +23 -0
- data/lib/emf/native/header.rb +35 -0
- data/lib/emf/native/header_errors.rb +30 -0
- data/lib/emf/native/hydrator.rb +151 -0
- data/lib/emf/native/lazy_wire.rb +100 -0
- data/lib/emf/native/legacy_state_fields.rb +22 -0
- data/lib/emf/native/library_path.rb +68 -0
- data/lib/emf/native/loader.rb +120 -0
- data/lib/emf/native/palette_fields.rb +45 -0
- data/lib/emf/native/poly_text_fields.rb +116 -0
- data/lib/emf/native/resource_failure.rb +28 -0
- data/lib/emf/native/small_text_fields.rb +27 -0
- data/lib/emf/native/snapshot.rb +102 -0
- data/lib/emf/native/wmf_bitmap_fields.rb +58 -0
- data/lib/emf/native/wmf_brush_fields.rb +62 -0
- data/lib/emf/native/wmf_fields.rb +117 -0
- data/lib/emf/native/wmf_hydrator.rb +143 -0
- data/lib/emf/native/wmf_loading.rb +49 -0
- data/lib/emf/native/wmf_palette_fields.rb +60 -0
- data/lib/emf/native/wmf_region_fields.rb +92 -0
- data/lib/emf/native/wmf_rejection.rb +84 -0
- data/lib/emf/native/wmf_remaining_fields.rb +70 -0
- data/lib/emf/native/wmf_snapshot.rb +51 -0
- data/lib/emf/native/wmf_text_fields.rb +56 -0
- data/lib/emf/native.rb +61 -0
- data/lib/emf/parse_evidence.rb +57 -0
- data/lib/emf/resource_budget/bitmap.rb +66 -0
- data/lib/emf/resource_budget/gradient.rb +30 -0
- data/lib/emf/resource_budget/poly_text.rb +85 -0
- data/lib/emf/resource_budget.rb +382 -0
- data/lib/emf/resource_limits/emf_records.rb +130 -0
- data/lib/emf/resource_limits.rb +58 -0
- data/lib/emf/version.rb +1 -1
- data/lib/emf/visitors/dump.rb +4 -0
- data/lib/emf/visitors/stats.rb +6 -5
- data/lib/emf/wmf/binary.rb +150 -1
- data/lib/emf/wmf/bitmap_codec.rb +118 -0
- data/lib/emf/wmf/brush_codec.rb +102 -0
- data/lib/emf/wmf/dc_escape_codec.rb +75 -0
- data/lib/emf/wmf/dib.rb +187 -0
- data/lib/emf/wmf/embedded_emf.rb +137 -0
- data/lib/emf/wmf/escape_views.rb +89 -0
- data/lib/emf/wmf/evidence.rb +255 -0
- data/lib/emf/wmf/geometry_codec.rb +49 -0
- data/lib/emf/wmf/palette_codec.rb +85 -0
- data/lib/emf/wmf/parser.rb +147 -2
- data/lib/emf/wmf/recovery.rb +95 -0
- data/lib/emf/wmf/region_codec.rb +151 -0
- data/lib/emf/wmf/serializer.rb +164 -2
- data/lib/emf/wmf/text_codec.rb +123 -0
- data/lib/emf/wmf.rb +7 -0
- data/lib/emf.rb +13 -9
- metadata +287 -10
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
# Replays the native parser's admission charges without materializing decoded
|
|
5
|
+
# records. Malformed fatal headers return control to the existing parser.
|
|
6
|
+
class ResourceBudget # rubocop:disable Metrics/ClassLength
|
|
7
|
+
DEFAULT_LIMITS = {
|
|
8
|
+
input_bytes: 200 * 1024 * 1024,
|
|
9
|
+
record_bytes: 128 * 1024 * 1024,
|
|
10
|
+
record_count: 4 * 1024 * 1024,
|
|
11
|
+
allocated_bytes: 640 * 1024 * 1024,
|
|
12
|
+
work_units: 64 * 1024 * 1024
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
# Native accounting widths, checked by resource_budget_layout.rs. The font
|
|
16
|
+
# charge conservatively covers both 392- and 400-byte Rust layouts so budget
|
|
17
|
+
# admission is independent of compiler layout; native has a compile-time guard.
|
|
18
|
+
LAYOUT = {
|
|
19
|
+
record_span: 336, diagnostic: 32, font_record: 400,
|
|
20
|
+
poly_text_entry_a: 136, poly_text_entry_w: 136,
|
|
21
|
+
poly_text_span: 32, tri_vertex: 16
|
|
22
|
+
}.freeze
|
|
23
|
+
RECORD_SPAN_BYTES = LAYOUT.fetch(:record_span)
|
|
24
|
+
DIAGNOSTIC_BYTES = LAYOUT.fetch(:diagnostic)
|
|
25
|
+
FONT_RECORD_BYTES = LAYOUT.fetch(:font_record)
|
|
26
|
+
|
|
27
|
+
FIXED_PAYLOADS = {
|
|
28
|
+
9 => 8, 10 => 8, 11 => 8, 12 => 8, 13 => 8, 14 => 12, 15 => 12,
|
|
29
|
+
16 => 4, 17 => 4, 18 => 4, 19 => 4, 20 => 4, 21 => 4, 22 => 4,
|
|
30
|
+
24 => 4, 25 => 4, 26 => 8, 27 => 8, 28 => 0, 29 => 16, 30 => 16,
|
|
31
|
+
31 => 16, 32 => 16, 33 => 0, 34 => 4, 35 => 24, 36 => 28, 37 => 4,
|
|
32
|
+
38 => 20, 39 => 16, 40 => 4, 41 => 20, 42 => 16, 43 => 16, 44 => 24,
|
|
33
|
+
45 => 32, 46 => 32, 47 => 32, 48 => 4, 51 => 8, 52 => 0, 53 => 16, 54 => 8,
|
|
34
|
+
55 => 32, 57 => 4, 58 => 4, 59 => 0, 60 => 0, 61 => 0, 62 => 16,
|
|
35
|
+
63 => 16, 64 => 16, 65 => 0, 66 => 0, 67 => 4, 68 => 0, 98 => 4,
|
|
36
|
+
115 => 4, 120 => 8
|
|
37
|
+
}.freeze
|
|
38
|
+
|
|
39
|
+
# A borrowed payload; reading fields must not copy a potentially large body.
|
|
40
|
+
class Payload
|
|
41
|
+
attr_reader :length
|
|
42
|
+
alias bytesize length
|
|
43
|
+
|
|
44
|
+
def initialize(bytes, base, length)
|
|
45
|
+
@bytes = bytes
|
|
46
|
+
@base = base
|
|
47
|
+
@length = length
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def u32(offset)
|
|
51
|
+
@bytes.unpack1("V", offset: @base + offset)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def u16(offset)
|
|
55
|
+
@bytes.unpack1("v", offset: @base + offset)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def slice(offset, length)
|
|
59
|
+
self.class.new(@bytes, @base + offset, length)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.check!(bytes, limits: {}, &admission)
|
|
64
|
+
new(limits).replay!(bytes, &admission)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def initialize(limits = {})
|
|
68
|
+
@limits = DEFAULT_LIMITS.merge(limits)
|
|
69
|
+
@totals = { record_count: 0, allocated_bytes: 0, work_units: 0 }
|
|
70
|
+
@vectors = {}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def charge_alloc(bytes)
|
|
74
|
+
charge(:allocated_bytes, bytes)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def charge_work(units)
|
|
78
|
+
charge(:work_units, units)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def layout(name)
|
|
82
|
+
LAYOUT.fetch(name)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def vector(count, width)
|
|
86
|
+
charge_alloc(count * width)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def span(payload_length, offset, length, alignment, fixed, empty_boundary: false)
|
|
90
|
+
return if offset < 8 || offset % alignment != 0
|
|
91
|
+
|
|
92
|
+
start = offset - 8
|
|
93
|
+
start = [start, fixed].max if empty_boundary && length.zero?
|
|
94
|
+
finish = start + length
|
|
95
|
+
[start, finish] if start >= fixed && finish <= payload_length
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def replay!(bytes, &) # rubocop:disable Naming/PredicateMethod
|
|
99
|
+
check(:input_bytes, bytes.bytesize)
|
|
100
|
+
return true unless Detector.call(bytes) == :emf
|
|
101
|
+
|
|
102
|
+
input = Payload.new(bytes, 0, bytes.bytesize)
|
|
103
|
+
header_size = input.u32(4)
|
|
104
|
+
minimum = if header_size > 100 then 108
|
|
105
|
+
elsif header_size > 88 then 100
|
|
106
|
+
else 88
|
|
107
|
+
end
|
|
108
|
+
return true if header_size < minimum
|
|
109
|
+
|
|
110
|
+
check(:record_bytes, header_size)
|
|
111
|
+
charge_record(header_size)
|
|
112
|
+
return true if header_size > input.length
|
|
113
|
+
|
|
114
|
+
charge_work(ceil16(input.length))
|
|
115
|
+
charge_alloc(input.length)
|
|
116
|
+
replay_records(input, header_size, &)
|
|
117
|
+
true
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
def check(resource, actual)
|
|
123
|
+
limit = @limits.fetch(resource)
|
|
124
|
+
return if actual <= limit
|
|
125
|
+
|
|
126
|
+
raise ResourceLimitError.new(resource: resource, limit: limit, actual: actual)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def charge(resource, amount)
|
|
130
|
+
total = @totals.fetch(resource) + amount
|
|
131
|
+
check(resource, total)
|
|
132
|
+
@totals[resource] = total
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def ceil16(bytes)
|
|
136
|
+
(bytes + 15) / 16
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def charge_record(size)
|
|
140
|
+
charge(:record_count, 1)
|
|
141
|
+
charge_work(1 + ceil16(size))
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def reserve(name, width)
|
|
145
|
+
length, capacity = @vectors.fetch(name, [0, 0])
|
|
146
|
+
if length == capacity
|
|
147
|
+
capacity = [capacity * 2, 1].max
|
|
148
|
+
charge_alloc(capacity * width)
|
|
149
|
+
end
|
|
150
|
+
@vectors[name] = [length + 1, capacity]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def diagnostic
|
|
154
|
+
reserve(:diagnostics, DIAGNOSTIC_BYTES)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def replay_records(input, offset)
|
|
158
|
+
while offset < input.length
|
|
159
|
+
remaining = input.length - offset
|
|
160
|
+
if remaining < 8 || (size = input.u32(offset + 4)) < 8 || size > remaining
|
|
161
|
+
diagnostic
|
|
162
|
+
break
|
|
163
|
+
end
|
|
164
|
+
kind = input.u32(offset)
|
|
165
|
+
check(:record_bytes, size)
|
|
166
|
+
charge_record(size)
|
|
167
|
+
reserve(:records, RECORD_SPAN_BYTES)
|
|
168
|
+
payload = input.slice(offset + 8, size - 8)
|
|
169
|
+
typed = decode(kind, payload)
|
|
170
|
+
diagnostic if !typed && invalid_when_raw?(kind, payload.length)
|
|
171
|
+
yield kind, size, offset if block_given?
|
|
172
|
+
offset += size
|
|
173
|
+
break if kind == 14 && typed
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def invalid_when_raw?(kind, length)
|
|
178
|
+
prefix = if kind == 118 then 28
|
|
179
|
+
elsif Bitmap::SHAPES.key?(kind) then Bitmap::SHAPES.fetch(kind).first - 8
|
|
180
|
+
end
|
|
181
|
+
return length < prefix if prefix
|
|
182
|
+
return false if [23, 96, 97].include?(kind)
|
|
183
|
+
return length < 28 if kind == 108
|
|
184
|
+
|
|
185
|
+
(2..68).cover?(kind) || (70..98).cover?(kind) || (114..116).cover?(kind) || kind == 120
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def decode_delegated(kind, payload)
|
|
189
|
+
case kind
|
|
190
|
+
when 96, 97 then PolyText.decode(kind, payload, self)
|
|
191
|
+
when 118 then Gradient.decode(kind, payload, self)
|
|
192
|
+
else Bitmap.decode(kind, payload, self)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Keep native match-arm order visible for charge and recovery parity.
|
|
197
|
+
def decode(kind, payload) # rubocop:disable Metrics/CyclomaticComplexity
|
|
198
|
+
case kind
|
|
199
|
+
when 2..6, 85..89 then decode_poly?(payload, kind < 85 ? 8 : 4)
|
|
200
|
+
when 7, 8, 90, 91 then decode_multi_poly?(payload, kind < 90 ? 8 : 4)
|
|
201
|
+
when 56, 92 then decode_poly?(payload, (kind == 56 ? 8 : 4) + 1, 2)
|
|
202
|
+
when 49, 50 then decode_palette?(kind, payload)
|
|
203
|
+
when 70 then decode_comment?(payload)
|
|
204
|
+
when 71..75 then decode_region?(kind, payload)
|
|
205
|
+
when 82 then decode_font?(payload)
|
|
206
|
+
when 83, 84 then decode_text?(payload, kind == 83 ? 1 : 2)
|
|
207
|
+
when 95 then decode_pen?(payload)
|
|
208
|
+
when 23 then true
|
|
209
|
+
when 76..81, 93, 94, 96, 97, 114, 116, 118 then decode_delegated(kind, payload)
|
|
210
|
+
else
|
|
211
|
+
fixed = FIXED_PAYLOADS[kind]
|
|
212
|
+
fixed && payload.length >= fixed
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def decode_poly?(payload, width, work_multiplier = 1)
|
|
217
|
+
return false if payload.length < 20
|
|
218
|
+
|
|
219
|
+
count = payload.u32(16)
|
|
220
|
+
bytes = count * width
|
|
221
|
+
return false if bytes > payload.length - 20
|
|
222
|
+
|
|
223
|
+
charge_alloc(bytes)
|
|
224
|
+
charge_work(count * work_multiplier)
|
|
225
|
+
true
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def decode_multi_poly?(payload, width)
|
|
229
|
+
return false if payload.length < 24
|
|
230
|
+
|
|
231
|
+
polygons = payload.u32(16)
|
|
232
|
+
points = payload.u32(20)
|
|
233
|
+
bytes = (polygons * 4) + (points * width)
|
|
234
|
+
return false if bytes > payload.length - 24
|
|
235
|
+
|
|
236
|
+
charge_alloc(bytes)
|
|
237
|
+
charge_work(polygons + points)
|
|
238
|
+
true
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def decode_palette?(kind, payload)
|
|
242
|
+
fixed = kind == 49 ? 8 : 12
|
|
243
|
+
return false if payload.length < fixed
|
|
244
|
+
|
|
245
|
+
count = kind == 49 ? payload.u16(6) : payload.u32(8)
|
|
246
|
+
return false if count * 4 > payload.length - fixed
|
|
247
|
+
|
|
248
|
+
charge_alloc(count * 4)
|
|
249
|
+
charge_work(count)
|
|
250
|
+
true
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def decode_comment?(payload)
|
|
254
|
+
return false if payload.length < 4
|
|
255
|
+
|
|
256
|
+
count = payload.u32(0)
|
|
257
|
+
return false if count > payload.length - 4
|
|
258
|
+
|
|
259
|
+
charge_alloc(count)
|
|
260
|
+
charge_work(ceil16(count))
|
|
261
|
+
true
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def decode_region?(kind, payload)
|
|
265
|
+
fixed = { 71 => 24, 72 => 32, 73 => 20, 74 => 20, 75 => 8 }.fetch(kind)
|
|
266
|
+
return false if payload.length < fixed
|
|
267
|
+
|
|
268
|
+
count = payload.u32(kind == 75 ? 0 : 16)
|
|
269
|
+
return false if count > payload.length - fixed
|
|
270
|
+
return true if count.zero? && kind == 75
|
|
271
|
+
return false if count < 32
|
|
272
|
+
|
|
273
|
+
region = payload.slice(fixed, count)
|
|
274
|
+
rectangles = region.u32(8)
|
|
275
|
+
bytes = rectangles * 16
|
|
276
|
+
return false unless region.u32(0) == 32 && region.u32(12) == bytes && bytes <= count - 32
|
|
277
|
+
|
|
278
|
+
extra = count - 32 - bytes
|
|
279
|
+
charge_alloc(bytes)
|
|
280
|
+
charge_alloc(extra)
|
|
281
|
+
charge_work(rectangles)
|
|
282
|
+
charge_work(ceil16(extra))
|
|
283
|
+
true
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def decode_font?(payload)
|
|
287
|
+
return false if payload.length < 96
|
|
288
|
+
|
|
289
|
+
size = payload.length - 4
|
|
290
|
+
if size > 320
|
|
291
|
+
return false if size < 356
|
|
292
|
+
|
|
293
|
+
count = payload.u32(356)
|
|
294
|
+
return false unless payload.u32(352) == 0x08007664 && count <= 16 && count * 4 <= payload.length - 360
|
|
295
|
+
|
|
296
|
+
charge_alloc(count * 4)
|
|
297
|
+
charge_work(count)
|
|
298
|
+
end
|
|
299
|
+
charge_alloc(FONT_RECORD_BYTES)
|
|
300
|
+
true
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Span validation and charging follow native decode_text in order.
|
|
304
|
+
def decode_text?(payload, width) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
305
|
+
return false if payload.length < 52
|
|
306
|
+
|
|
307
|
+
count = payload.u32(36)
|
|
308
|
+
options = payload.u32(44)
|
|
309
|
+
fixed = options.nobits?(0x100) ? 68 : 52
|
|
310
|
+
return false if payload.length < fixed
|
|
311
|
+
|
|
312
|
+
string_len = count * width
|
|
313
|
+
dx_count = count * (options.nobits?(0x2000) ? 1 : 2)
|
|
314
|
+
string_offset = payload.u32(40)
|
|
315
|
+
dx_offset = payload.u32(fixed - 4)
|
|
316
|
+
string = span(payload.length, string_offset, string_len, width, fixed) unless string_offset.zero? && count.zero?
|
|
317
|
+
dx = span(payload.length, dx_offset, dx_count * 4, 4, fixed) unless dx_offset.zero?
|
|
318
|
+
return false if (!string_offset.zero? || !count.zero?) && !string
|
|
319
|
+
return false if !dx_offset.zero? && !dx
|
|
320
|
+
return false if string && dx && string[0] < dx[1] && dx[0] < string[1]
|
|
321
|
+
|
|
322
|
+
ranges = [string, dx].compact.sort_by(&:first)
|
|
323
|
+
cursor = fixed
|
|
324
|
+
ranges.each do |start, finish|
|
|
325
|
+
return false if start < cursor
|
|
326
|
+
|
|
327
|
+
charge_work(ceil16(start - cursor))
|
|
328
|
+
charge_alloc(start - cursor)
|
|
329
|
+
cursor = finish
|
|
330
|
+
end
|
|
331
|
+
charge_work(count)
|
|
332
|
+
charge_alloc(string_len)
|
|
333
|
+
if dx
|
|
334
|
+
charge_work(dx_count)
|
|
335
|
+
charge_alloc(dx_count * 4)
|
|
336
|
+
end
|
|
337
|
+
true
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Validate all spans before the first native pen allocation charge.
|
|
341
|
+
def decode_pen?(payload) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
342
|
+
return false if payload.length < 44
|
|
343
|
+
|
|
344
|
+
count = payload.u32(40)
|
|
345
|
+
fixed = 44 + (count * 4)
|
|
346
|
+
return false if fixed > payload.length
|
|
347
|
+
|
|
348
|
+
ranges = [[payload.u32(4), payload.u32(8)], [payload.u32(12), payload.u32(16)]].map do |offset, length|
|
|
349
|
+
next if offset.zero? && length.zero?
|
|
350
|
+
|
|
351
|
+
range = span(payload.length, offset, length, 4, fixed, empty_boundary: true)
|
|
352
|
+
return false unless range
|
|
353
|
+
|
|
354
|
+
range
|
|
355
|
+
end
|
|
356
|
+
info, bits = ranges
|
|
357
|
+
bits_first = bits && (!info || bits[0] < info[0] || (bits[0] == info[0] && bits[0] == bits[1]))
|
|
358
|
+
ranges = (bits_first ? [bits, info] : [info, bits]).compact
|
|
359
|
+
cursor = fixed
|
|
360
|
+
ranges.each do |start, finish|
|
|
361
|
+
return false if start < cursor
|
|
362
|
+
|
|
363
|
+
cursor = finish
|
|
364
|
+
end
|
|
365
|
+
charge_alloc(count * 4)
|
|
366
|
+
charge_work(count)
|
|
367
|
+
cursor = fixed
|
|
368
|
+
ranges.each do |start, finish|
|
|
369
|
+
[start - cursor, finish - start].each do |length|
|
|
370
|
+
charge_alloc(length)
|
|
371
|
+
charge_work(ceil16(length))
|
|
372
|
+
end
|
|
373
|
+
cursor = finish
|
|
374
|
+
end
|
|
375
|
+
true
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
require_relative "resource_budget/bitmap"
|
|
381
|
+
require_relative "resource_budget/poly_text"
|
|
382
|
+
require_relative "resource_budget/gradient"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emf
|
|
4
|
+
module ResourceLimits
|
|
5
|
+
# Admission follows the native framing rules without decoding a Ruby model.
|
|
6
|
+
# Corrupt spans are left to the selected parser's existing recovery path.
|
|
7
|
+
module EmfRecords
|
|
8
|
+
MAX_RECORD_BYTES = 128 * 1024 * 1024
|
|
9
|
+
MAX_RECORDS = 4 * 1024 * 1024
|
|
10
|
+
POINT_WIDTHS = { 2 => 8, 3 => 8, 4 => 8, 5 => 8, 6 => 8,
|
|
11
|
+
85 => 4, 86 => 4, 87 => 4, 88 => 4, 89 => 4,
|
|
12
|
+
56 => 9, 92 => 5 }.freeze
|
|
13
|
+
MULTI_WIDTHS = { 7 => 8, 8 => 8, 90 => 4, 91 => 4 }.freeze
|
|
14
|
+
REGION_OFFSETS = { 71 => 32, 72 => 40, 73 => 28, 74 => 28, 75 => 16 }.freeze
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
def check(bytes)
|
|
19
|
+
count = 0
|
|
20
|
+
each(bytes) do |type, size, base|
|
|
21
|
+
ResourceLimits.check!(:record_bytes, MAX_RECORD_BYTES, size)
|
|
22
|
+
count += 1
|
|
23
|
+
ResourceLimits.check!(:record_count, MAX_RECORDS, count)
|
|
24
|
+
check_arrays(bytes, type, size, base) if base.positive?
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def each(bytes)
|
|
29
|
+
return if bytes.bytesize < 8
|
|
30
|
+
|
|
31
|
+
size = bytes.unpack1("V", offset: 4)
|
|
32
|
+
minimum = if size > 100
|
|
33
|
+
108
|
|
34
|
+
else
|
|
35
|
+
size > 88 ? 100 : 88
|
|
36
|
+
end
|
|
37
|
+
return if size < minimum
|
|
38
|
+
|
|
39
|
+
# The native header checks its claimed size before reading its body.
|
|
40
|
+
yield 1, size, 0
|
|
41
|
+
base = size
|
|
42
|
+
while bytes.bytesize - base >= 8
|
|
43
|
+
type = bytes.unpack1("V", offset: base)
|
|
44
|
+
size = bytes.unpack1("V", offset: base + 4)
|
|
45
|
+
return if size < 8 || size > bytes.bytesize - base
|
|
46
|
+
|
|
47
|
+
yield type, size, base
|
|
48
|
+
return if type == 14 && size >= 20
|
|
49
|
+
|
|
50
|
+
base += size
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def check_arrays(bytes, type, size, base)
|
|
55
|
+
if (width = POINT_WIDTHS[type])
|
|
56
|
+
check_single(bytes, size, base, 24, 28, width)
|
|
57
|
+
elsif (width = MULTI_WIDTHS[type])
|
|
58
|
+
check_multi(bytes, size, base, width)
|
|
59
|
+
elsif type == 50
|
|
60
|
+
check_single(bytes, size, base, 16, 20, 4)
|
|
61
|
+
elsif type == 95
|
|
62
|
+
check_pen(bytes, size, base)
|
|
63
|
+
elsif REGION_OFFSETS.key?(type)
|
|
64
|
+
check_region(bytes, type, size, base)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def check_single(bytes, size, base, count_at, fixed, width)
|
|
69
|
+
return if size < fixed
|
|
70
|
+
|
|
71
|
+
count = bytes.unpack1("V", offset: base + count_at)
|
|
72
|
+
check_count(count) if fixed + (count * width) <= size
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def check_multi(bytes, size, base, width)
|
|
76
|
+
return if size < 32
|
|
77
|
+
|
|
78
|
+
polys, points = bytes.unpack("V2", offset: base + 24)
|
|
79
|
+
return if 32 + (polys * 4) + (points * width) > size
|
|
80
|
+
|
|
81
|
+
check_count(polys)
|
|
82
|
+
check_count(points)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def check_pen(bytes, size, base)
|
|
86
|
+
return if size < 52
|
|
87
|
+
|
|
88
|
+
count = bytes.unpack1("V", offset: base + 48)
|
|
89
|
+
fixed = 52 + (count * 4)
|
|
90
|
+
return if fixed > size
|
|
91
|
+
|
|
92
|
+
info, info_length, bits, bits_length = bytes.unpack("V4", offset: base + 12)
|
|
93
|
+
spans = [[info, info_length], [bits, bits_length]].map { |offset, length| pen_span(offset, length, fixed, size) }
|
|
94
|
+
return if spans.any?(&:nil?)
|
|
95
|
+
|
|
96
|
+
spans.sort_by! { |start, length| [start, length.zero? ? 0 : 1] }
|
|
97
|
+
return if spans.size == 2 && spans[0].sum > spans[1][0]
|
|
98
|
+
|
|
99
|
+
check_count(count)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def pen_span(offset, length, fixed, size)
|
|
103
|
+
return [fixed, 0] if offset.zero? && length.zero?
|
|
104
|
+
|
|
105
|
+
start = length.zero? ? [offset, fixed].max : offset
|
|
106
|
+
return unless offset >= 8 && (offset % 4).zero? && start >= fixed && start + length <= size
|
|
107
|
+
|
|
108
|
+
[start, length]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def check_region(bytes, type, size, base)
|
|
112
|
+
fixed = REGION_OFFSETS.fetch(type)
|
|
113
|
+
return if size < fixed
|
|
114
|
+
|
|
115
|
+
length = bytes.unpack1("V", offset: base + (type == 75 ? 8 : 24))
|
|
116
|
+
return if length < 32 || fixed + length > size
|
|
117
|
+
|
|
118
|
+
header, _kind, count, rectangle_bytes = bytes.unpack("V4", offset: base + fixed)
|
|
119
|
+
return unless header == 32 && count * 16 == rectangle_bytes && 32 + rectangle_bytes <= length
|
|
120
|
+
|
|
121
|
+
check_count(count)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def check_count(count, record_bytes: 0)
|
|
125
|
+
ResourceLimits.check!(:array_elements, Emr::Parser::MAX_SANE_ARRAY_LENGTH, count)
|
|
126
|
+
ResourceLimits.check!(:record_bytes, 0xFFFFFFFF, record_bytes)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "resource_limits/emf_records"
|
|
4
|
+
require_relative "resource_budget"
|
|
5
|
+
|
|
6
|
+
module Emf
|
|
7
|
+
# Shared admission checks run before either parser allocates its input model.
|
|
8
|
+
module ResourceLimits
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def check!(resource, limit, actual)
|
|
12
|
+
return if actual <= limit
|
|
13
|
+
|
|
14
|
+
error = ResourceLimitError.new(resource: resource, limit: limit, actual: actual)
|
|
15
|
+
error.with_evidence(yield) if block_given?
|
|
16
|
+
raise error
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def preflight(bytes, defer: false)
|
|
20
|
+
format = Detector.call(bytes)
|
|
21
|
+
limit = case format
|
|
22
|
+
when :wmf_apm, :wmf_standard then Wmf::Binary::MAX_INPUT_BYTES
|
|
23
|
+
when :emf then Emr::Parser::MAX_INPUT_BYTES
|
|
24
|
+
end
|
|
25
|
+
check!(:input_bytes, limit, bytes.bytesize) do
|
|
26
|
+
check = format == :emf ? :emf_input_too_large : :input_too_large
|
|
27
|
+
ParseEvidence.new(check: check, offset: 0, declared: limit, actual: bytes.bytesize)
|
|
28
|
+
end
|
|
29
|
+
return unless format == :emf
|
|
30
|
+
return :deferred if defer && bounded_emf?(bytes)
|
|
31
|
+
|
|
32
|
+
ResourceBudget.check!(bytes, limits: { input_bytes: limit,
|
|
33
|
+
record_bytes: EmfRecords::MAX_RECORD_BYTES,
|
|
34
|
+
record_count: EmfRecords::MAX_RECORDS }) do |type, size, base|
|
|
35
|
+
EmfRecords.check_arrays(bytes, type, size, base)
|
|
36
|
+
end
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Except for PolyText's potentially overlapping descriptors, native charges
|
|
41
|
+
# are bounded by input length. Geometric record/diagnostic reservations cost
|
|
42
|
+
# less than 184 bytes per input byte; input, font and payload copies fit in
|
|
43
|
+
# the remaining 72. Framing and payload work fit within four units per byte.
|
|
44
|
+
# The Ruby parser must request exact admission if it encounters PolyText.
|
|
45
|
+
def bounded_emf?(bytes)
|
|
46
|
+
size = bytes.bytesize
|
|
47
|
+
header = bytes.unpack1("V", offset: 4)
|
|
48
|
+
return false unless header <= size && (header == 88 || header == 100 || header >= 108)
|
|
49
|
+
|
|
50
|
+
limits = ResourceBudget::DEFAULT_LIMITS
|
|
51
|
+
size <= EmfRecords::MAX_RECORD_BYTES &&
|
|
52
|
+
size / 8 <= EmfRecords::MAX_RECORDS &&
|
|
53
|
+
size / 4 <= Emr::Parser::MAX_SANE_ARRAY_LENGTH &&
|
|
54
|
+
size * 256 <= limits.fetch(:allocated_bytes) &&
|
|
55
|
+
size * 4 <= limits.fetch(:work_units)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/emf/version.rb
CHANGED
data/lib/emf/visitors/dump.rb
CHANGED
|
@@ -35,6 +35,10 @@ module Emf
|
|
|
35
35
|
@lines << " rec[#{adapter.offset}] type_id=#{adapter.type_id} #{wire.class.name&.split('::')&.last}"
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
def visit_wmf_record(record)
|
|
39
|
+
@lines << " rec[#{record.offset}] type_id=#{record.type_id} #{record.class.name&.split('::')&.last}"
|
|
40
|
+
end
|
|
41
|
+
|
|
38
42
|
def method_missing(name, *args)
|
|
39
43
|
return super unless name.to_s.start_with?("visit_")
|
|
40
44
|
|
data/lib/emf/visitors/stats.rb
CHANGED
|
@@ -5,11 +5,8 @@ module Emf
|
|
|
5
5
|
# Produces a record-class histogram for a Metafile. Used by spec assertions
|
|
6
6
|
# (TODO 16) and as a quick sanity-check dump via `emf stats` (TODO 19).
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# Subclasses that override specific visit_* methods for richer behaviour
|
|
11
|
-
# still get tallied here because the record's accept calls the visit_*
|
|
12
|
-
# method, which (if not overridden) calls method_missing -> tally.
|
|
8
|
+
# Registered record families have explicit handlers; unregistered visit_*
|
|
9
|
+
# methods fall back to tallying the record's class name.
|
|
13
10
|
class Stats < Emf::Model::Visitor
|
|
14
11
|
def initialize
|
|
15
12
|
@counts = Hash.new(0)
|
|
@@ -48,6 +45,10 @@ module Emf
|
|
|
48
45
|
@counts["WireAdapter(#{wire_class})"] += 1
|
|
49
46
|
end
|
|
50
47
|
|
|
48
|
+
def visit_wmf_record(record)
|
|
49
|
+
@counts[record.class.name&.split("::")&.last || record.class.to_s] += 1
|
|
50
|
+
end
|
|
51
|
+
|
|
51
52
|
def method_missing(name, *args)
|
|
52
53
|
return super unless name.to_s.start_with?("visit_")
|
|
53
54
|
|