From a7f80072a753e4e410e0b5f2a8792f64ae6c6b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20P=C3=B6ttker?= <25299532+hpoettker@users.noreply.github.com> Date: Thu, 12 Nov 2026 20:23:50 +0100 Subject: [PATCH] Replace sas7bdat mix page padding hack --- src/sas/readstat_sas.c | 32 ++------------------------------ src/sas/readstat_sas.h | 8 -------- src/sas/readstat_sas7bdat_read.c | 22 +++++++++------------- 3 files changed, 11 insertions(+), 51 deletions(-) diff --git a/src/sas/readstat_sas.c b/src/sas/readstat_sas.c index c1933723..cf0b201a 100644 --- a/src/sas/readstat_sas.c +++ b/src/sas/readstat_sas.c @@ -318,14 +318,8 @@ readstat_error_t sas_read_header(readstat_io_t *io, sas_header_info_t *hinfo, retval = READSTAT_ERROR_READ; goto cleanup; } - char major, revision_tag, revision; - int minor; - /* The release field is not NUL-terminated in the file; copy it into a - * terminated buffer before handing it to sscanf, which calls strlen on it. */ - char release[sizeof(header_end.release)+1]; - memcpy(release, header_end.release, sizeof(header_end.release)); - release[sizeof(header_end.release)] = '\0'; - if (sscanf(release, "%c.%04d%c%c", &major, &minor, &revision_tag, &revision) != 4) { + char major = header_end.release[0]; + if (header_end.release[1] != '.') { retval = READSTAT_ERROR_PARSE; goto cleanup; } @@ -340,29 +334,7 @@ readstat_error_t sas_read_header(readstat_io_t *io, sas_header_info_t *hinfo, retval = READSTAT_ERROR_PARSE; goto cleanup; } - // revision_tag is usually M, but J has been observed in the wild (not created with SAS?) - // files have also been observed with TK in place of the revision details. - if ((major == '8' || major == '9') && revision_tag != 'M' && revision_tag != 'J' && revision_tag != 'T') { - retval = READSTAT_ERROR_PARSE; - goto cleanup; - } - hinfo->minor_version = minor; - if (revision >= '0' && revision <= '9') { - hinfo->revision = revision - '0'; - } else if (revision_tag == 'T' && revision == 'K') { - // files have been observed with TK in place of the revision details. - hinfo->revision = 0; - } else { - retval = READSTAT_ERROR_PARSE; - goto cleanup; - } - if ((major == '8' || major == '9') && minor == 0 && hinfo->revision == 0) { - /* A bit of a hack, but most SAS installations are running a minor update */ - hinfo->vendor = READSTAT_VENDOR_STAT_TRANSFER; - } else { - hinfo->vendor = READSTAT_VENDOR_SAS; - } if (io->seek(hinfo->header_size, READSTAT_SEEK_SET, io->io_ctx) == -1) { retval = READSTAT_ERROR_SEEK; if (error_handler) { diff --git a/src/sas/readstat_sas.h b/src/sas/readstat_sas.h index d10e3510..9670a9c6 100644 --- a/src/sas/readstat_sas.h +++ b/src/sas/readstat_sas.h @@ -36,10 +36,7 @@ typedef struct sas_header_end_s { typedef struct sas_header_info_s { int little_endian; int u64; - int vendor; int major_version; - int minor_version; - int revision; int pad1; int64_t page_size; int64_t page_header_size; @@ -54,11 +51,6 @@ typedef struct sas_header_info_s { char *encoding; } sas_header_info_t; -enum { - READSTAT_VENDOR_STAT_TRANSFER, - READSTAT_VENDOR_SAS -}; - typedef struct sas_text_ref_s { uint16_t index; uint16_t offset; diff --git a/src/sas/readstat_sas7bdat_read.c b/src/sas/readstat_sas7bdat_read.c index 9eeb5a70..a8572ac8 100644 --- a/src/sas/readstat_sas7bdat_read.c +++ b/src/sas/readstat_sas7bdat_read.c @@ -42,7 +42,6 @@ typedef struct sas7bdat_ctx_s { int little_endian; int u64; - int vendor; void *user_ctx; readstat_io_t *io; int bswap; @@ -70,6 +69,7 @@ typedef struct sas7bdat_ctx_s { uint64_t page_header_size; uint64_t subheader_signature_size; uint64_t subheader_pointer_size; + int mix_page_padding; int text_blob_count; size_t *text_blob_lengths; @@ -309,6 +309,8 @@ static readstat_error_t sas7bdat_parse_row_size_subheader(const char *subheader, if (ctx->row_limit == 0 || total_row_count_after_skipping < ctx->row_limit) ctx->row_limit = total_row_count_after_skipping; + ctx->mix_page_padding = (unsigned char)subheader[len-13] - 1; + cleanup: return retval; } @@ -1224,18 +1226,12 @@ static readstat_error_t sas7bdat_parse_page_pass2(const char *page, size_t page_ } if ((page_type & SAS_PAGE_TYPE_MASK) == SAS_PAGE_TYPE_MIX) { - /* HACK - this is supposed to obey 8-byte boundaries but - * some files created by Stat/Transfer don't. So verify that the - * padding is { 0, 0, 0, 0 } or { ' ', ' ', ' ', ' ' } (or that - * the file is not from Stat/Transfer) before skipping it */ - if ((shp-page)%8 == 4 && shp + 4 <= page + page_size && - (*(uint32_t *)shp == 0x00000000 || - *(uint32_t *)shp == 0x20202020 || - ctx->vendor != READSTAT_VENDOR_STAT_TRANSFER)) { - data = shp + 4; - } else { - data = shp; + if ((ctx->mix_page_padding != 0 && ctx->mix_page_padding != 4) || + shp + ctx->mix_page_padding > page + page_size) { + retval = READSTAT_ERROR_PARSE; + goto cleanup; } + data = shp + ctx->mix_page_padding; } } if (data) { @@ -1438,6 +1434,7 @@ readstat_error_t readstat_parse_sas7bdat(readstat_parser_t *parser, const char * ctx->user_ctx = user_ctx; ctx->io = parser->io; ctx->row_limit = parser->row_limit; + ctx->mix_page_padding = -1; ctx->moved_page_index = -1; if (parser->row_offset > 0) ctx->row_offset = parser->row_offset; @@ -1471,7 +1468,6 @@ readstat_error_t readstat_parse_sas7bdat(readstat_parser_t *parser, const char * ctx->u64 = hinfo->u64; ctx->little_endian = hinfo->little_endian; - ctx->vendor = hinfo->vendor; ctx->bswap = machine_is_little_endian() ^ hinfo->little_endian; ctx->header_size = hinfo->header_size; ctx->page_count = hinfo->page_count;