704 lines
24 KiB
Python
704 lines
24 KiB
Python
"""GLB and ZIP fixture construction, mutation, and case records."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import binascii
|
|
import struct
|
|
import zlib
|
|
from copy import deepcopy
|
|
from typing import Callable
|
|
|
|
from simplecadapi.scene import (
|
|
canonical_json_bytes,
|
|
canonical_zip_bytes,
|
|
parse_strict_json,
|
|
preflight_glb,
|
|
preflight_zip_bytes,
|
|
)
|
|
|
|
from .common import JsonObject, b64
|
|
|
|
|
|
LOCAL = struct.Struct("<IHHHHHIIIHH")
|
|
CENTRAL = struct.Struct("<IHHHHHHIIIHHHHHII")
|
|
EOCD = struct.Struct("<IHHHHIIH")
|
|
|
|
|
|
def _f32(value: float) -> float:
|
|
result = struct.unpack("<f", struct.pack("<f", value))[0]
|
|
return 0.0 if result == 0 else result
|
|
|
|
|
|
def glb(
|
|
kind: str,
|
|
positions: list[tuple[float, float, float]],
|
|
indices: list[int],
|
|
normals: list[tuple[float, float, float]] | None = None,
|
|
) -> bytes:
|
|
positions = [tuple(_f32(component) for component in point) for point in positions]
|
|
position_bytes = b"".join(struct.pack("<fff", *point) for point in positions)
|
|
chunks = [position_bytes]
|
|
views = [
|
|
{"buffer": 0, "byteLength": len(position_bytes), "byteOffset": 0, "target": 34962}
|
|
]
|
|
accessors = [
|
|
{
|
|
"bufferView": 0,
|
|
"componentType": 5126,
|
|
"count": len(positions),
|
|
"max": [max(point[axis] for point in positions) for axis in range(3)],
|
|
"min": [min(point[axis] for point in positions) for axis in range(3)],
|
|
"type": "VEC3",
|
|
}
|
|
]
|
|
if kind == "triangle":
|
|
assert normals is not None
|
|
normals = [tuple(_f32(component) for component in vector) for vector in normals]
|
|
normal_bytes = b"".join(struct.pack("<fff", *vector) for vector in normals)
|
|
views.append(
|
|
{
|
|
"buffer": 0,
|
|
"byteLength": len(normal_bytes),
|
|
"byteOffset": len(position_bytes),
|
|
"target": 34962,
|
|
}
|
|
)
|
|
accessors.append(
|
|
{
|
|
"bufferView": 1,
|
|
"componentType": 5126,
|
|
"count": len(normals),
|
|
"type": "VEC3",
|
|
}
|
|
)
|
|
chunks.append(normal_bytes)
|
|
index_view = 2
|
|
attributes = {"NORMAL": 1, "POSITION": 0}
|
|
mode = 4
|
|
else:
|
|
index_view = 1
|
|
attributes = {"POSITION": 0}
|
|
mode = 1
|
|
prefix = b"".join(chunks)
|
|
prefix += b"\0" * ((-len(prefix)) % 4)
|
|
component_type = 5123 if len(positions) <= 65536 else 5125
|
|
index_format = "H" if component_type == 5123 else "I"
|
|
index_bytes = struct.pack("<" + index_format * len(indices), *indices)
|
|
views.append(
|
|
{
|
|
"buffer": 0,
|
|
"byteLength": len(index_bytes),
|
|
"byteOffset": len(prefix),
|
|
"target": 34963,
|
|
}
|
|
)
|
|
accessors.append(
|
|
{
|
|
"bufferView": index_view,
|
|
"componentType": component_type,
|
|
"count": len(indices),
|
|
"type": "SCALAR",
|
|
}
|
|
)
|
|
unpadded_bin = prefix + index_bytes
|
|
document = {
|
|
"accessors": accessors,
|
|
"asset": {"generator": "SimpleCAD Scene GLB Profile 1", "version": "2.0"},
|
|
"bufferViews": views,
|
|
"buffers": [{"byteLength": len(unpadded_bin)}],
|
|
"meshes": [
|
|
{"primitives": [{"attributes": attributes, "indices": index_view, "mode": mode}]}
|
|
],
|
|
"nodes": [{"mesh": 0}],
|
|
"scene": 0,
|
|
"scenes": [{"nodes": [0]}],
|
|
}
|
|
json_bytes = canonical_json_bytes(document)
|
|
json_chunk = json_bytes + b" " * ((-len(json_bytes)) % 4)
|
|
bin_chunk = unpadded_bin + b"\0" * ((-len(unpadded_bin)) % 4)
|
|
total_length = 12 + 8 + len(json_chunk) + 8 + len(bin_chunk)
|
|
return b"".join(
|
|
(
|
|
struct.pack("<III", 0x46546C67, 2, total_length),
|
|
struct.pack("<II", len(json_chunk), 0x4E4F534A),
|
|
json_chunk,
|
|
struct.pack("<II", len(bin_chunk), 0x004E4942),
|
|
bin_chunk,
|
|
)
|
|
)
|
|
|
|
|
|
def replace_glb_json(data: bytes, mutate: Callable[[JsonObject], None]) -> bytes:
|
|
json_length = struct.unpack_from("<I", data, 12)[0]
|
|
document = parse_strict_json(data[20 : 20 + json_length].rstrip(b" "))
|
|
mutate(document)
|
|
replacement = canonical_json_bytes(document)
|
|
replacement += b" " * ((-len(replacement)) % 4)
|
|
bin_header = 20 + json_length
|
|
bin_chunk = data[bin_header:]
|
|
return b"".join(
|
|
(
|
|
struct.pack(
|
|
"<III", 0x46546C67, 2, 12 + 8 + len(replacement) + len(bin_chunk)
|
|
),
|
|
struct.pack("<II", len(replacement), 0x4E4F534A),
|
|
replacement,
|
|
bin_chunk,
|
|
)
|
|
)
|
|
|
|
|
|
def _deflate_zip(members: dict[str, bytes]) -> bytes:
|
|
local_parts: list[bytes] = []
|
|
central_parts: list[bytes] = []
|
|
local_offset = 0
|
|
for name in sorted(members, key=lambda value: value.encode("ascii")):
|
|
name_bytes = name.encode("ascii")
|
|
payload = members[name]
|
|
compressor = zlib.compressobj(level=9, wbits=-zlib.MAX_WBITS)
|
|
compressed = compressor.compress(payload) + compressor.flush()
|
|
crc = binascii.crc32(payload) & 0xFFFFFFFF
|
|
local_header = LOCAL.pack(
|
|
0x04034B50,
|
|
20,
|
|
0x0800,
|
|
8,
|
|
0,
|
|
0x0021,
|
|
crc,
|
|
len(compressed),
|
|
len(payload),
|
|
len(name_bytes),
|
|
0,
|
|
)
|
|
local_parts.extend((local_header, name_bytes, compressed))
|
|
central_parts.extend(
|
|
(
|
|
CENTRAL.pack(
|
|
0x02014B50,
|
|
0x0314,
|
|
20,
|
|
0x0800,
|
|
8,
|
|
0,
|
|
0x0021,
|
|
crc,
|
|
len(compressed),
|
|
len(payload),
|
|
len(name_bytes),
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0x81A40000,
|
|
local_offset,
|
|
),
|
|
name_bytes,
|
|
)
|
|
)
|
|
local_offset += len(local_header) + len(name_bytes) + len(compressed)
|
|
central = b"".join(central_parts)
|
|
return b"".join(local_parts) + central + EOCD.pack(
|
|
0x06054B50,
|
|
0,
|
|
0,
|
|
len(members),
|
|
len(members),
|
|
len(central),
|
|
local_offset,
|
|
0,
|
|
)
|
|
|
|
|
|
def _replace_u16(data: bytes, offset: int, value: int) -> bytes:
|
|
result = bytearray(data)
|
|
struct.pack_into("<H", result, offset, value)
|
|
return bytes(result)
|
|
|
|
|
|
def _replace_u32(data: bytes, offset: int, value: int) -> bytes:
|
|
result = bytearray(data)
|
|
struct.pack_into("<I", result, offset, value)
|
|
return bytes(result)
|
|
|
|
|
|
def _glb_bin_start(data: bytes) -> int:
|
|
return 28 + struct.unpack_from("<I", data, 12)[0]
|
|
|
|
|
|
def _replace_glb_bin_u32(data: bytes, relative_offset: int, value: int) -> bytes:
|
|
return _replace_u32(data, _glb_bin_start(data) + relative_offset, value)
|
|
|
|
|
|
def _set_path(value: object, path: tuple[object, ...], replacement: object) -> None:
|
|
current = value
|
|
for part in path[:-1]:
|
|
current = current[part] # type: ignore[index]
|
|
current[path[-1]] = replacement # type: ignore[index]
|
|
|
|
|
|
def _add_path(value: object, path: tuple[object, ...], key: str, item: object) -> None:
|
|
current = value
|
|
for part in path:
|
|
current = current[part] # type: ignore[index]
|
|
current[key] = item # type: ignore[index]
|
|
|
|
|
|
def _append_path(value: object, path: tuple[object, ...], item: object) -> None:
|
|
current = value
|
|
for part in path:
|
|
current = current[part] # type: ignore[index]
|
|
current.append(item) # type: ignore[attr-defined]
|
|
|
|
|
|
def _nonminimal_glb_json_padding(data: bytes) -> bytes:
|
|
json_length = struct.unpack_from("<I", data, 12)[0]
|
|
json_end = 20 + json_length
|
|
result = data[:json_end] + b" " + data[json_end:]
|
|
result = _replace_u32(result, 8, len(result))
|
|
return _replace_u32(result, 12, json_length + 4)
|
|
|
|
|
|
def _append_glb_bin_word(data: bytes, value: int = 0) -> bytes:
|
|
json_length = struct.unpack_from("<I", data, 12)[0]
|
|
bin_header = 20 + json_length
|
|
bin_length = struct.unpack_from("<I", data, bin_header)[0]
|
|
result = data + struct.pack("<I", value)
|
|
result = _replace_u32(result, 8, len(result))
|
|
return _replace_u32(result, bin_header, bin_length + 4)
|
|
|
|
|
|
def _f32_from_bits(value: int) -> float:
|
|
return struct.unpack("<f", struct.pack("<I", value))[0]
|
|
|
|
|
|
def _glb_case(
|
|
name: str,
|
|
payload: bytes,
|
|
expected_kind: str,
|
|
valid: bool,
|
|
) -> dict[str, object]:
|
|
try:
|
|
info = preflight_glb(payload, expected_kind=expected_kind)
|
|
error = None
|
|
actual_valid = True
|
|
actual_kind = info.kind
|
|
except ValueError as exc:
|
|
error = str(exc)
|
|
actual_valid = False
|
|
actual_kind = None
|
|
assert actual_valid == valid, name
|
|
return {
|
|
"error": error,
|
|
"expected_kind": expected_kind,
|
|
"kind": actual_kind,
|
|
"name": name,
|
|
"payload_base64": b64(payload),
|
|
"valid": valid,
|
|
}
|
|
|
|
|
|
def _zip_case(
|
|
name: str,
|
|
payload: bytes,
|
|
valid: bool,
|
|
used_deflate: bool | None,
|
|
) -> dict[str, object]:
|
|
try:
|
|
info = preflight_zip_bytes(payload)
|
|
error = None
|
|
actual_valid = True
|
|
actual_deflate = info.used_deflate
|
|
except ValueError as exc:
|
|
error = str(exc)
|
|
actual_valid = False
|
|
actual_deflate = None
|
|
assert actual_valid == valid, name
|
|
assert actual_deflate == used_deflate, name
|
|
return {
|
|
"error": error,
|
|
"name": name,
|
|
"payload_base64": b64(payload),
|
|
"used_deflate": used_deflate,
|
|
"valid": valid,
|
|
}
|
|
|
|
|
|
def _zip_layout(data: bytes) -> tuple[list[tuple[int, int, int]], int, int]:
|
|
eocd = len(data) - EOCD.size
|
|
count = struct.unpack_from("<H", data, eocd + 10)[0]
|
|
central = struct.unpack_from("<I", data, eocd + 16)[0]
|
|
entries: list[tuple[int, int, int]] = []
|
|
cursor = central
|
|
for _index in range(count):
|
|
name_length = struct.unpack_from("<H", data, cursor + 28)[0]
|
|
extra_length = struct.unpack_from("<H", data, cursor + 30)[0]
|
|
comment_length = struct.unpack_from("<H", data, cursor + 32)[0]
|
|
local_offset = struct.unpack_from("<I", data, cursor + 42)[0]
|
|
entries.append((cursor, local_offset, name_length))
|
|
cursor += CENTRAL.size + name_length + extra_length + comment_length
|
|
return entries, central, eocd
|
|
|
|
|
|
def _replace_zip_name(data: bytes, index: int, name: bytes) -> bytes:
|
|
entries, _central, _eocd = _zip_layout(data)
|
|
central_offset, local_offset, name_length = entries[index]
|
|
assert len(name) == name_length
|
|
assert struct.unpack_from("<H", data, local_offset + 26)[0] == name_length
|
|
result = bytearray(data)
|
|
result[local_offset + LOCAL.size : local_offset + LOCAL.size + name_length] = name
|
|
result[central_offset + CENTRAL.size : central_offset + CENTRAL.size + name_length] = name
|
|
return bytes(result)
|
|
|
|
|
|
def _zip_with_prefix(data: bytes) -> bytes:
|
|
entries, central, eocd = _zip_layout(data)
|
|
result = bytearray(b"x" + data)
|
|
for central_offset, local_offset, _name_length in entries:
|
|
struct.pack_into("<I", result, central_offset + 1 + 42, local_offset + 1)
|
|
struct.pack_into("<I", result, eocd + 1 + 16, central + 1)
|
|
return bytes(result)
|
|
|
|
|
|
def _zip_with_local_gap(data: bytes) -> bytes:
|
|
_entries, central, eocd = _zip_layout(data)
|
|
result = bytearray(data[:central] + b"x" + data[central:])
|
|
struct.pack_into("<I", result, eocd + 1 + 16, central + 1)
|
|
return bytes(result)
|
|
|
|
|
|
def build_binary_cases(
|
|
triangle_glb: bytes,
|
|
line_glb: bytes,
|
|
members: dict[str, bytes],
|
|
) -> tuple[bytes, bytes, list[dict[str, object]], list[dict[str, object]]]:
|
|
canonical_zip = canonical_zip_bytes(members)
|
|
deflate_zip = _deflate_zip(members)
|
|
string_bounds_glb = replace_glb_json(
|
|
triangle_glb,
|
|
lambda document: document["accessors"][0]["min"].__setitem__(0, "0"),
|
|
)
|
|
boolean_scene_glb = replace_glb_json(
|
|
triangle_glb, lambda document: document.__setitem__("scene", False)
|
|
)
|
|
simple_triangle = glb(
|
|
"triangle",
|
|
[(0, 0, 0), (1, 0, 0), (0, 1, 0)],
|
|
[0, 1, 2],
|
|
[(0, 0, 1)] * 3,
|
|
)
|
|
float_extreme_line = glb(
|
|
"line",
|
|
[
|
|
(
|
|
_f32_from_bits(0xFF7FFFFF),
|
|
_f32_from_bits(0x80000001),
|
|
_f32_from_bits(0x007FFFFF),
|
|
),
|
|
(
|
|
_f32_from_bits(0x80800000),
|
|
0.0,
|
|
_f32_from_bits(0x00000001),
|
|
),
|
|
(
|
|
_f32_from_bits(0x00800000),
|
|
_f32_from_bits(0x007FFFFF),
|
|
_f32_from_bits(0x7F7FFFFF),
|
|
),
|
|
],
|
|
[0, 1, 1, 2],
|
|
)
|
|
|
|
glb_payloads: list[tuple[str, bytes, str, bool]] = [
|
|
("valid_triangle", triangle_glb, "triangle", True),
|
|
("valid_line", line_glb, "line", True),
|
|
("valid_float32_finite_extremes", float_extreme_line, "line", True),
|
|
("string_accessor_bounds", string_bounds_glb, "triangle", False),
|
|
("boolean_scene_index", boolean_scene_glb, "triangle", False),
|
|
]
|
|
for name, base, path, key, value in (
|
|
("root_extensions_used", triangle_glb, (), "extensionsUsed", []),
|
|
("asset_extras", triangle_glb, ("asset",), "extras", {}),
|
|
("node_name", triangle_glb, ("nodes", 0), "name", "node"),
|
|
("scene_name", triangle_glb, ("scenes", 0), "name", "scene"),
|
|
("mesh_name", triangle_glb, ("meshes", 0), "name", "mesh"),
|
|
(
|
|
"primitive_material",
|
|
triangle_glb,
|
|
("meshes", 0, "primitives", 0),
|
|
"material",
|
|
0,
|
|
),
|
|
("buffer_uri", triangle_glb, ("buffers", 0), "uri", "buffer.bin"),
|
|
("buffer_view_byte_stride", triangle_glb, ("bufferViews", 0), "byteStride", 12),
|
|
("position_accessor_byte_offset", triangle_glb, ("accessors", 0), "byteOffset", 0),
|
|
("normal_accessor_normalized", triangle_glb, ("accessors", 1), "normalized", False),
|
|
("index_accessor_sparse", triangle_glb, ("accessors", 2), "sparse", {}),
|
|
):
|
|
glb_payloads.append(
|
|
(
|
|
name,
|
|
replace_glb_json(
|
|
base,
|
|
lambda document, path=path, key=key, value=value: _add_path(
|
|
document, path, key, value
|
|
),
|
|
),
|
|
"triangle",
|
|
False,
|
|
)
|
|
)
|
|
for name, base, path, value, kind in (
|
|
("empty_position_accessor", line_glb, ("accessors", 0, "count"), 0, "line"),
|
|
("empty_index_accessor", line_glb, ("accessors", 1, "count"), 0, "line"),
|
|
(
|
|
"wrong_triangle_mode",
|
|
triangle_glb,
|
|
("meshes", 0, "primitives", 0, "mode"),
|
|
1,
|
|
"triangle",
|
|
),
|
|
(
|
|
"wrong_position_component_type",
|
|
triangle_glb,
|
|
("accessors", 0, "componentType"),
|
|
5125,
|
|
"triangle",
|
|
),
|
|
("wrong_normal_count", triangle_glb, ("accessors", 1, "count"), 1, "triangle"),
|
|
(
|
|
"wrong_index_component_type",
|
|
triangle_glb,
|
|
("accessors", 2, "componentType"),
|
|
5125,
|
|
"triangle",
|
|
),
|
|
("buffer_view_gap", triangle_glb, ("bufferViews", 1, "byteOffset"), 4, "triangle"),
|
|
(
|
|
"buffer_view_wrong_target",
|
|
triangle_glb,
|
|
("bufferViews", 0, "target"),
|
|
34963,
|
|
"triangle",
|
|
),
|
|
(
|
|
"buffer_view_wrong_length",
|
|
triangle_glb,
|
|
("bufferViews", 0, "byteLength"),
|
|
4,
|
|
"triangle",
|
|
),
|
|
):
|
|
glb_payloads.append(
|
|
(
|
|
name,
|
|
replace_glb_json(
|
|
base,
|
|
lambda document, path=path, value=value: _set_path(
|
|
document, path, value
|
|
),
|
|
),
|
|
kind,
|
|
False,
|
|
)
|
|
)
|
|
glb_payloads.extend(
|
|
(
|
|
("bad_glb_magic", _replace_u32(triangle_glb, 0, 0), "triangle", False),
|
|
("bad_glb_version", _replace_u32(triangle_glb, 4, 1), "triangle", False),
|
|
(
|
|
"bad_glb_total_length",
|
|
_replace_u32(triangle_glb, 8, len(triangle_glb) + 4),
|
|
"triangle",
|
|
False,
|
|
),
|
|
("bad_json_chunk_type", _replace_u32(triangle_glb, 16, 0), "triangle", False),
|
|
(
|
|
"nonminimal_json_padding",
|
|
_nonminimal_glb_json_padding(triangle_glb),
|
|
"triangle",
|
|
False,
|
|
),
|
|
(
|
|
"bad_bin_chunk_type",
|
|
_replace_u32(
|
|
triangle_glb,
|
|
24 + struct.unpack_from("<I", triangle_glb, 12)[0],
|
|
0,
|
|
),
|
|
"triangle",
|
|
False,
|
|
),
|
|
(
|
|
"extra_accessor",
|
|
replace_glb_json(
|
|
triangle_glb,
|
|
lambda document: _append_path(
|
|
document,
|
|
("accessors",),
|
|
deepcopy(document["accessors"][0]),
|
|
),
|
|
),
|
|
"triangle",
|
|
False,
|
|
),
|
|
(
|
|
"buffer_unpadded_length",
|
|
replace_glb_json(
|
|
triangle_glb,
|
|
lambda document: _set_path(
|
|
document,
|
|
("buffers", 0, "byteLength"),
|
|
document["buffers"][0]["byteLength"] + 4,
|
|
),
|
|
),
|
|
"triangle",
|
|
False,
|
|
),
|
|
("bin_extra_zero_word", _append_glb_bin_word(triangle_glb), "triangle", False),
|
|
(
|
|
"bin_nonzero_padding",
|
|
simple_triangle[:-1] + b"\x01",
|
|
"triangle",
|
|
False,
|
|
),
|
|
(
|
|
"position_negative_zero",
|
|
_replace_glb_bin_u32(simple_triangle, 0, 0x80000000),
|
|
"triangle",
|
|
False,
|
|
),
|
|
(
|
|
"position_positive_infinity",
|
|
_replace_glb_bin_u32(simple_triangle, 0, 0x7F800000),
|
|
"triangle",
|
|
False,
|
|
),
|
|
(
|
|
"position_negative_infinity",
|
|
_replace_glb_bin_u32(simple_triangle, 0, 0xFF800000),
|
|
"triangle",
|
|
False,
|
|
),
|
|
(
|
|
"position_quiet_nan",
|
|
_replace_glb_bin_u32(simple_triangle, 0, 0x7FC00000),
|
|
"triangle",
|
|
False,
|
|
),
|
|
(
|
|
"normal_negative_zero",
|
|
_replace_glb_bin_u32(simple_triangle, 36, 0x80000000),
|
|
"triangle",
|
|
False,
|
|
),
|
|
(
|
|
"normal_nonfinite",
|
|
_replace_glb_bin_u32(simple_triangle, 44, 0x7FC00000),
|
|
"triangle",
|
|
False,
|
|
),
|
|
(
|
|
"normal_norm_below_lower_limit",
|
|
_replace_glb_bin_u32(simple_triangle, 44, 0x3F7FFFEF),
|
|
"triangle",
|
|
False,
|
|
),
|
|
(
|
|
"normal_norm_above_upper_limit",
|
|
_replace_glb_bin_u32(simple_triangle, 44, 0x3F800009),
|
|
"triangle",
|
|
False,
|
|
),
|
|
)
|
|
)
|
|
valid_normal_boundaries = _replace_glb_bin_u32(simple_triangle, 44, 0x3F7FFFF0)
|
|
valid_normal_boundaries = _replace_glb_bin_u32(valid_normal_boundaries, 56, 0x3F800008)
|
|
glb_payloads.append(
|
|
("valid_normal_norm_inner_neighbors", valid_normal_boundaries, "triangle", True)
|
|
)
|
|
|
|
tiny_zip = canonical_zip_bytes({"other.json": b"x", "scene.json": b"{}"})
|
|
tiny_entries, tiny_central, tiny_eocd = _zip_layout(tiny_zip)
|
|
first_central, first_local, _first_name_length = tiny_entries[0]
|
|
first_payload = first_local + LOCAL.size + struct.unpack_from("<H", tiny_zip, first_local + 26)[0]
|
|
crc_zip = bytearray(tiny_zip)
|
|
crc_zip[first_payload] ^= 1
|
|
exact_ratio_zip = _deflate_zip({"scene.json": b"a" * 1200})
|
|
over_ratio_zip = _deflate_zip({"scene.json": b"a" * 1201})
|
|
zip_payloads: list[tuple[str, bytes, bool, bool | None]] = [
|
|
("canonical_stored", canonical_zip, True, False),
|
|
("allowlisted_deflate", deflate_zip, True, True),
|
|
("exact_member_compression_ratio", exact_ratio_zip, True, True),
|
|
("over_member_compression_ratio", over_ratio_zip, False, None),
|
|
("crc_mismatch", bytes(crc_zip), False, None),
|
|
("trailing_data", tiny_zip + b"x", False, None),
|
|
("leading_prefix", _zip_with_prefix(tiny_zip), False, None),
|
|
("local_record_gap", _zip_with_local_gap(tiny_zip), False, None),
|
|
("case_collision", _replace_zip_name(tiny_zip, 0, b"SCENE.JSON"), False, None),
|
|
("path_traversal", _replace_zip_name(tiny_zip, 0, b"a/../b.txt"), False, None),
|
|
("backslash_name", _replace_zip_name(tiny_zip, 0, b"other\\json"), False, None),
|
|
("nul_name", _replace_zip_name(tiny_zip, 0, b"other.\0son"), False, None),
|
|
("bad_eocd_signature", _replace_u32(tiny_zip, tiny_eocd, 0), False, None),
|
|
("eocd_comment", _replace_u16(tiny_zip, tiny_eocd + 20, 1), False, None),
|
|
("multi_disk", _replace_u16(tiny_zip, tiny_eocd + 4, 1), False, None),
|
|
(
|
|
"member_count_over_limit",
|
|
_replace_u16(
|
|
_replace_u16(tiny_zip, tiny_eocd + 8, 50_001),
|
|
tiny_eocd + 10,
|
|
50_001,
|
|
),
|
|
False,
|
|
None,
|
|
),
|
|
(
|
|
"central_offset_mismatch",
|
|
_replace_u32(tiny_zip, tiny_eocd + 16, tiny_central + 1),
|
|
False,
|
|
None,
|
|
),
|
|
("bad_central_signature", _replace_u32(tiny_zip, first_central, 0), False, None),
|
|
("central_made_by", _replace_u16(tiny_zip, first_central + 4, 20), False, None),
|
|
("central_needed", _replace_u16(tiny_zip, first_central + 6, 45), False, None),
|
|
("central_flags", _replace_u16(tiny_zip, first_central + 8, 0x0808), False, None),
|
|
("central_method", _replace_u16(tiny_zip, first_central + 10, 99), False, None),
|
|
("central_time", _replace_u16(tiny_zip, first_central + 12, 1), False, None),
|
|
("central_date", _replace_u16(tiny_zip, first_central + 14, 0), False, None),
|
|
("central_extra", _replace_u16(tiny_zip, first_central + 30, 1), False, None),
|
|
("central_comment", _replace_u16(tiny_zip, first_central + 32, 1), False, None),
|
|
("central_disk_start", _replace_u16(tiny_zip, first_central + 34, 1), False, None),
|
|
("central_internal_attr", _replace_u16(tiny_zip, first_central + 36, 1), False, None),
|
|
("central_external_attr", _replace_u32(tiny_zip, first_central + 38, 0), False, None),
|
|
("central_crc", _replace_u32(tiny_zip, first_central + 16, 0), False, None),
|
|
(
|
|
"central_compressed_size",
|
|
_replace_u32(tiny_zip, first_central + 20, 2),
|
|
False,
|
|
None,
|
|
),
|
|
(
|
|
"central_uncompressed_size",
|
|
_replace_u32(tiny_zip, first_central + 24, 2),
|
|
False,
|
|
None,
|
|
),
|
|
("central_local_offset", _replace_u32(tiny_zip, first_central + 42, 1), False, None),
|
|
("local_signature", _replace_u32(tiny_zip, first_local, 0), False, None),
|
|
("local_needed", _replace_u16(tiny_zip, first_local + 4, 45), False, None),
|
|
("local_flags", _replace_u16(tiny_zip, first_local + 6, 0), False, None),
|
|
("local_method", _replace_u16(tiny_zip, first_local + 8, 8), False, None),
|
|
("local_time", _replace_u16(tiny_zip, first_local + 10, 1), False, None),
|
|
("local_date", _replace_u16(tiny_zip, first_local + 12, 0), False, None),
|
|
("local_crc", _replace_u32(tiny_zip, first_local + 14, 0), False, None),
|
|
("local_compressed_size", _replace_u32(tiny_zip, first_local + 18, 2), False, None),
|
|
("local_uncompressed_size", _replace_u32(tiny_zip, first_local + 22, 2), False, None),
|
|
("local_extra", _replace_u16(tiny_zip, first_local + 28, 1), False, None),
|
|
]
|
|
return (
|
|
canonical_zip,
|
|
deflate_zip,
|
|
[_glb_case(*case) for case in glb_payloads],
|
|
[_zip_case(*case) for case in zip_payloads],
|
|
)
|