Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  yaa_extractor.py

  Sprache: Python
 

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

#!/usr/bin/env python

import os
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Optional

TAG_RE = re.compile(b"^[A-Z0-9]{4}$")
YAA_MAGIC = b"YAA1"
TAG_LENGTH = 4
HEADER_LENGTH_BYTES = 2
HEADER_MINIMUM_LENGTH = 6
TYPE_LENGTH_BYTES = 1
PATH_LENGTH_BYTES = 2
DATA_LENGTH_BYTES = 2
DATA_LENGTH_BYTES_LONG = 4
LINK_LENGTH_BYTES = 2
RESYNC_SEARCH_WINDOW = 64 * 1024
ATTRIBUTE_SIZE_MAP = {
    b"UID1"1,
    b"GID1"1,
    b"UID2"2,
    b"GID2"2,
    b"MOD1"1,
    b"MOD2"2,
    b"FLG1"1,
    b"FLG2"2,
    b"FLG4"4,
    b"MTMS"8,
    b"MTMT"12,
    b"FLI4"4,
    b"AFT1"1,
    b"AFR2"2,
    b"AFT2"2,
    b"AFR4"4,
}


@dataclass
class YAAEntry:
    path: str
    file_type: str
    data_length: int


def read_exact(f, n):
    b = f.read(n)
    if not b or len(b) != n:
        return None
    return b


def read_header_length(f, file_size):
    raw = read_exact(f, HEADER_LENGTH_BYTES)
    if not raw:
        raise SystemExit(
            "Failed to read YAA1 header length (file truncated or invalid)."
        )

    header_len = int.from_bytes(raw, "little")
    if header_len <= HEADER_MINIMUM_LENGTH or header_len > file_size:
        raise SystemExit("Invalid header length")

    return header_len


def parse_attrs_in_header(f, header_end) -> Optional[YAAEntry]:
    path: Optional[str] = None
    file_type: Optional[str] = None
    data_len: Optional[int] = None

    while f.tell() + TAG_LENGTH <= header_end:
        tag = read_exact(f, TAG_LENGTH)
        if not tag or not TAG_RE.match(tag):
            if tag:
                f.seek(f.tell() - len(tag))
            break

        if tag == b"TYP1":
            entry_type = read_exact(f, TYPE_LENGTH_BYTES)
            if not entry_type:
                break
            file_type = entry_type.decode("utf-8")

        elif tag == b"PATP":
            length_bytes = read_exact(f, PATH_LENGTH_BYTES)
            if not length_bytes:
                break
            plen = int.from_bytes(length_bytes, "little")
            path_bytes = read_exact(f, plen) or b""
            try:
                path = path_bytes.decode("utf-8")
            except UnicodeDecodeError:
                path = None

        elif tag == b"DATA":
            length_bytes = read_exact(f, DATA_LENGTH_BYTES)
            if not length_bytes:
                break
            data_len = int.from_bytes(length_bytes, "little")

        elif tag == b"DATB":
            length_bytes = read_exact(f, DATA_LENGTH_BYTES_LONG)
            if not length_bytes:
                break
            data_len = int.from_bytes(length_bytes, "little")

        elif tag == b"LNKP":
            length_bytes = read_exact(f, LINK_LENGTH_BYTES)
            if not length_bytes:
                break
            l = int.from_bytes(length_bytes, "little")
            _ = read_exact(f, l)

        else:
            skip = ATTRIBUTE_SIZE_MAP.get(tag)
            if skip is None:
                f.seek(f.tell() - TAG_LENGTH)
                break
            _ = read_exact(f, skip)

    f.seek(header_end)

    if not path or not file_type or data_len is None:
        return None
    return YAAEntry(path=path, file_type=file_type, data_length=data_len)


def normalize_allowed_prefixes(allowed_prefixes):
    if not allowed_prefixes:
        return None

    normalized = []
    for p in allowed_prefixes:
        rp = Path(p)
        if not rp.is_absolute():
            rp = Path("/") / rp
        normalized.append(rp)
    return normalized


def file_length(f):
    f.seek(0, os.SEEK_END)
    size = f.tell()
    f.seek(0)
    return size


def scan_and_extract(f, outdir, allowed_prefixes=None, file_filter=None):
    norm_allowed = normalize_allowed_prefixes(allowed_prefixes)
    file_size = file_length(f)

    while f.tell() < file_size:
        pos = f.tell()
        magic = read_exact(f, TAG_LENGTH)
        if magic is None:
            break

        # If not YAA1, search forward for next occurrence
        if magic != YAA_MAGIC:
            # Move back to not miss overlapping occurrences
            back_pos = max(0, pos - (len(YAA_MAGIC) - 1))
            f.seek(back_pos)
            window = f.read(RESYNC_SEARCH_WINDOW)
            if not window:
                break
            rel = window.find(YAA_MAGIC)
            if rel == -1:
                break
            new_off = back_pos + rel
            f.seek(new_off + len(YAA_MAGIC))

        try:
            header_len = read_header_length(f, file_size)
        except SystemExit:
            break

        header_start = f.tell()
        header_end = header_start + (header_len - HEADER_MINIMUM_LENGTH)

        if header_end > file_size:
            break

        f.seek(header_start)
        entry = parse_attrs_in_header(f, header_end)
        path = entry.path if entry else None
        ftype = entry.file_type if entry else None
        data_len = entry.data_length if entry else None
        data_start = header_end

        allowed = True
        if path and norm_allowed is not None:
            full_path = Path("/") / path.lstrip("/")
            allowed = any(
                full_path == root or root in full_path.parents for root in norm_allowed
            )

        if (
            allowed
            and path
            and ftype == "F"
            and data_len
            and 0 < data_len <= file_size - data_start
        ):
            rel = Path(path.lstrip("/"))
            out_path = outdir / rel
            out_path.parent.mkdir(parents=True, exist_ok=True)

            f.seek(data_start)
            if file_filter:
                head = read_exact(f, min(4, data_len))
                if head is None:
                    break
                if not file_filter(path, head, data_len):
                    f.seek(data_start + data_len)
                else:
                    remaining = read_exact(f, data_len - len(head)) or b""
                    with out_path.open("wb"as w:
                        w.write(head)
                        w.write(remaining)
            else:
                payload = read_exact(f, data_len)
                if payload is not None:
                    with out_path.open("wb"as w:
                        w.write(payload)

        next_off = header_end + (data_len or 0)
        if next_off <= pos:
            break
        f.seek(next_off)


def expand(archive: Path, outdir: Path, allowed_prefixes=None, file_filter=None):
    outdir.mkdir(parents=True, exist_ok=True)
    with archive.open("rb"as f:
        scan_and_extract(
            f,
            outdir,
            allowed_prefixes=allowed_prefixes,
            file_filter=file_filter,
        )

Messung V0.5 in Prozent
C=98 H=78 G=88

¤ Dauer der Verarbeitung: 0.26 Sekunden  (vorverarbeitet am  2026-08-26) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Statistik
#Sources=277311
#Domains=655579