Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/sas/readstat_sas7bcat_read.c
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ static readstat_error_t sas7bcat_parse_block(const char *data, size_t data_size,
int string_offset = 0;
int payload_offset = 106;
uint16_t flags = 0;
char name[4*32+1];
char name[4*32+2]; /* room for the trailing dot */

if (data_size < payload_offset)
goto cleanup;
Expand Down Expand Up @@ -227,7 +227,7 @@ static readstat_error_t sas7bcat_parse_block(const char *data, size_t data_size,
goto cleanup;
}

if ((retval = readstat_convert(name, sizeof(name), &data[8], 8, ctx->converter)) != READSTAT_OK)
if ((retval = readstat_convert(name, sizeof(name) - 1, &data[8], 8, ctx->converter)) != READSTAT_OK)
goto cleanup;

if (pad) {
Expand All @@ -238,12 +238,15 @@ static readstat_error_t sas7bcat_parse_block(const char *data, size_t data_size,
if (data_size < payload_offset + pad + 32)
goto cleanup;

retval = readstat_convert(name, sizeof(name), &data[payload_offset+pad], 32, ctx->converter);
retval = readstat_convert(name, sizeof(name) - 1, &data[payload_offset+pad], 32, ctx->converter);
if (retval != READSTAT_OK)
goto cleanup;
pad += 32;
}

/* Present format names the way SAS does, e.g. "MYFMT." or "$MYFMT." */
strcat(name, ".");

if (data_size < payload_offset + pad)
goto cleanup;

Expand Down
58 changes: 51 additions & 7 deletions src/sas/readstat_sas7bcat_write.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,58 @@ typedef struct sas7bcat_block_s {
char data[1]; // Flexible array; use [1] for C++-98 compatibility
} sas7bcat_block_t;

/* Validates a catalog format name such as "MYFMT", "MYFMT.", "$MYFMT" or
* "$MYFMT." and returns the length of the name without the trailing dot.
* Widths and decimals are not part of a format's name in a catalog, so a name
* ending in a digit is rejected, as are names whose '$' prefix doesn't match
* the type of the label set. */
static readstat_error_t sas7bcat_validate_format_name(readstat_label_set_t *r_label_set,
size_t *name_len_out) {
const char *name = r_label_set->name;
size_t len = strlen(name);
size_t i = 0;

if (len && name[len-1] == '.')
len--;

if (len > 32)
return READSTAT_ERROR_BAD_FORMAT_STRING;

int is_string = (len > 0 && name[0] == '$');
if (is_string != (r_label_set->type == READSTAT_TYPE_STRING))
return READSTAT_ERROR_BAD_FORMAT_STRING;
if (is_string)
i++;

if (i == len)
return READSTAT_ERROR_BAD_FORMAT_STRING;

char c = name[i];
if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'))
return READSTAT_ERROR_BAD_FORMAT_STRING;

for (i++; i<len; i++) {
c = name[i];
if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') || c == '_'))
return READSTAT_ERROR_BAD_FORMAT_STRING;
}

if (name[len-1] >= '0' && name[len-1] <= '9')
return READSTAT_ERROR_BAD_FORMAT_STRING;

*name_len_out = len;
return READSTAT_OK;
}

static sas7bcat_block_t *sas7bcat_block_for_label_set(
readstat_label_set_t *r_label_set, readstat_writer_t *writer) {
readstat_label_set_t *r_label_set, size_t name_len, readstat_writer_t *writer) {
size_t len = 106;
size_t name_len = strlen(r_label_set->name);
int j;
char name[32];

if (name_len > 8) {
len += 32; // long name
if (name_len > 32) {
name_len = 32;
}
}
if (writer->is_64bit) {
len += 32;
Expand Down Expand Up @@ -147,11 +187,15 @@ static readstat_error_t sas7bcat_begin_data(void *writer_ctx) {

int i;
sas_header_info_t *hinfo = sas_header_info_init(writer, writer->is_64bit);
sas7bcat_block_t **blocks = malloc(writer->label_sets_count * sizeof(sas7bcat_block_t));
sas7bcat_block_t **blocks = calloc(writer->label_sets_count, sizeof(sas7bcat_block_t *));
char *page = malloc(hinfo->page_size);

for (i=0; i<writer->label_sets_count; i++) {
blocks[i] = sas7bcat_block_for_label_set(writer->label_sets[i], writer);
size_t name_len = 0;
retval = sas7bcat_validate_format_name(writer->label_sets[i], &name_len);
if (retval != READSTAT_OK)
goto cleanup;
blocks[i] = sas7bcat_block_for_label_set(writer->label_sets[i], name_len, writer);
}

hinfo->page_count = 4;
Expand Down
131 changes: 125 additions & 6 deletions src/test/test_list.h
Original file line number Diff line number Diff line change
Expand Up @@ -2287,7 +2287,7 @@ static rt_test_group_t _test_groups[] = {
.label_sets_count = 1,
.label_sets = {
{
.name = "LabelSet",
.name = "LabelSet.",
.type = READSTAT_TYPE_DOUBLE,
.value_labels_count = 2,
.value_labels = {
Expand All @@ -2310,7 +2310,7 @@ static rt_test_group_t _test_groups[] = {
.label_sets_count = 1,
.label_sets = {
{
.name = "$StringLabelSet",
.name = "$StringLabelSet.",
.type = READSTAT_TYPE_STRING,
.value_labels_count = 4,
.value_labels = {
Expand Down Expand Up @@ -2341,7 +2341,7 @@ static rt_test_group_t _test_groups[] = {
.label_sets_count = 2,
.label_sets = {
{
.name = "OneTwo",
.name = "OneTwo.",
.type = READSTAT_TYPE_DOUBLE,
.value_labels_count = 2,
.value_labels = {
Expand All @@ -2356,7 +2356,7 @@ static rt_test_group_t _test_groups[] = {
}
},
{
.name = "ThreeFour",
.name = "ThreeFour.",
.type = READSTAT_TYPE_DOUBLE,
.value_labels_count = 2,
.value_labels = {
Expand All @@ -2373,6 +2373,125 @@ static rt_test_group_t _test_groups[] = {
}
},

{
.label = "SAS string value labels with dotted long name",
.test_formats = RT_FORMAT_SAS7BCAT,
.label_sets_count = 1,
.label_sets = {
{
.name = "$ABCDEFGHIJKLMNOPQRSTUVWXYZABCDE.",
.type = READSTAT_TYPE_STRING,
.value_labels_count = 1,
.value_labels = {
{
.value = { .type = READSTAT_TYPE_STRING, .v = { .string_value = "A" } },
.label = "Apple"
}
}
}
}
},

{
.label = "SAS catalog format name with width",
.write_error = READSTAT_ERROR_BAD_FORMAT_STRING,
.test_formats = RT_FORMAT_SAS7BCAT,
.label_sets_count = 1,
.label_sets = {
{
.name = "MYFMT8.",
.type = READSTAT_TYPE_DOUBLE,
.value_labels_count = 1,
.value_labels = {
{
.value = { .type = READSTAT_TYPE_DOUBLE, .v = { .double_value = 1.0 } },
.label = "One"
}
}
}
}
},

{
.label = "SAS catalog format name with width and digits",
.write_error = READSTAT_ERROR_BAD_FORMAT_STRING,
.test_formats = RT_FORMAT_SAS7BCAT,
.label_sets_count = 1,
.label_sets = {
{
.name = "MYFMT8.2",
.type = READSTAT_TYPE_DOUBLE,
.value_labels_count = 1,
.value_labels = {
{
.value = { .type = READSTAT_TYPE_DOUBLE, .v = { .double_value = 1.0 } },
.label = "One"
}
}
}
}
},

{
.label = "SAS catalog numeric format with $ name",
.write_error = READSTAT_ERROR_BAD_FORMAT_STRING,
.test_formats = RT_FORMAT_SAS7BCAT,
.label_sets_count = 1,
.label_sets = {
{
.name = "$MYFMT.",
.type = READSTAT_TYPE_DOUBLE,
.value_labels_count = 1,
.value_labels = {
{
.value = { .type = READSTAT_TYPE_DOUBLE, .v = { .double_value = 1.0 } },
.label = "One"
}
}
}
}
},

{
.label = "SAS catalog string format without $ name",
.write_error = READSTAT_ERROR_BAD_FORMAT_STRING,
.test_formats = RT_FORMAT_SAS7BCAT,
.label_sets_count = 1,
.label_sets = {
{
.name = "MYFMT",
.type = READSTAT_TYPE_STRING,
.value_labels_count = 1,
.value_labels = {
{
.value = { .type = READSTAT_TYPE_STRING, .v = { .string_value = "A" } },
.label = "Apple"
}
}
}
}
},

{
.label = "Too long SAS catalog format name",
.write_error = READSTAT_ERROR_BAD_FORMAT_STRING,
.test_formats = RT_FORMAT_SAS7BCAT,
.label_sets_count = 1,
.label_sets = {
{
.name = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFG",
.type = READSTAT_TYPE_DOUBLE,
.value_labels_count = 1,
.value_labels = {
{
.value = { .type = READSTAT_TYPE_DOUBLE, .v = { .double_value = 1.0 } },
.label = "One"
}
}
}
}
},

{
.label = "DTA ancient value labels",
.write_error = READSTAT_ERROR_NUMERIC_VALUE_IS_OUT_OF_RANGE,
Expand Down Expand Up @@ -3914,7 +4033,7 @@ static rt_test_group_t _test_groups[] = {
.label_sets_count = 1,
.label_sets = {
{
.name = "$YESNO",
.name = "$YESNO.",
.type = READSTAT_TYPE_STRING,
.value_labels_count = 2,
.value_labels = {
Expand Down Expand Up @@ -3943,7 +4062,7 @@ static rt_test_group_t _test_groups[] = {
.label_sets_count = 1,
.label_sets = {
{
.name = "$YESNO",
.name = "$YESNO.",
.type = READSTAT_TYPE_STRING,
.value_labels_count = 2,
.value_labels = {
Expand Down
Loading