diff --git a/examples/sexp-conv.c b/examples/sexp-conv.c index 16abe234a6ebfb6b87cdfea056c5ec6745dbc7ed..7a96fff298314ef0261d88a0fe6c16b714a1d6c5 100644 --- a/examples/sexp-conv.c +++ b/examples/sexp-conv.c @@ -4,19 +4,24 @@ * syntax. */ #include "base64.h" +#include "buffer.h" + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> + enum sexp_mode { SEXP_CANONICAL = 0, SEXP_ADVANCED = 1, /* OR:ed with SEXP_CANONICAL or SEXP_ADVANCED when reading - * transport data. */ + * transport data */ SEXP_TRANSPORT = 2, }; enum sexp_token { - SEXP_LIST, SEXP_STRING, SEXP_DISPLAY_START, SEXP_DISPLAY_END, @@ -45,17 +50,38 @@ struct sexp_input unsigned level; }; +static void +sexp_input_init(struct sexp_input *input, FILE *f, enum sexp_mode mode) +{ + input->f = f; + input->mode = mode; + input->level = 0; +} + struct sexp_output { FILE *f; enum sexp_mode mode; + struct base64_encode_ctx base64; - unsigned indent; + /* Items at the head of the list */ + unsigned items; + unsigned pos; }; - +static void +sexp_output_init(struct sexp_output *output, FILE *f, enum sexp_mode mode) +{ + output->f = f; + output->mode = mode; + output->pos = 0; +} + + +/* Input */ + /* Returns 1 on success. On failure, return -1. For special tokens, * return 0 and set input->token accordingly. */ static int @@ -73,7 +99,7 @@ sexp_get_char(struct sexp_input *input, uint8_t *out) if (c == '}') { - if (base64_decode_status(&input->ctx)) + if (base64_decode_status(&input->base64)) { input->token = SEXP_TRANSPORT_END; return 0; @@ -82,7 +108,7 @@ sexp_get_char(struct sexp_input *input, uint8_t *out) return -1; } - done = base64_decode_single(&input->ctx, out, c); + done = base64_decode_single(&input->base64, out, c); if (done) return 1; } @@ -133,7 +159,7 @@ sexp_get_token_char(struct sexp_input *input) if (c >= 0 && TOKEN_CHAR(c)) return c; - ungetc(input->f, c); + ungetc(c, input->f); return 0; } @@ -145,14 +171,13 @@ sexp_unget_char(struct sexp_input *input, uint8_t c) assert(input->mode == SEXP_ADVANCED); ungetc(c, input->f); } -#endif static int sexp_get_string_char(struct sexp_input *input) { assert(input->mode == SEXP_ADVANCED); - } +#endif static int sexp_get_quoted_char(struct sexp_input *input, uint8_t *c) @@ -252,7 +277,7 @@ sexp_get_base64_string(struct sexp_input *input) } static int -sexp_get_atom_string(struct sexp_input *input, uint8_t c) +sexp_get_token_string(struct sexp_input *input, uint8_t c) { assert(input->mode == SEXP_ADVANCED); @@ -313,7 +338,7 @@ sexp_get_string_length(struct sexp_input *input, unsigned length) if (sexp_get_char(input, &c) <= 0) return 0; - if (c < 0 || < > 9) + if (c < '0' || c > '9') break; /* FIXME: Check for overflow? */ @@ -371,10 +396,15 @@ sexp_get_token(struct sexp_input *input) return sexp_get_string_length(input, c - '0'); case '(': - input->token = SEXP_START_LIST; + input->token = SEXP_LIST_START; return 1; case ')': - input->token = SEXP_END_LIST; + input->token = SEXP_LIST_END; + + if (!input->level) + return 0; + + input->level--; return 1; case '[': @@ -420,69 +450,158 @@ sexp_get_token(struct sexp_input *input) return (input->mode == SEXP_ADVANCED) && sexp_get_string(input, c); } - } + } + abort(); } + + +/* Output routines */ + +#define LINE_WIDTH 60 + static int -sexp_put_newline(struct sexp_output *output) +sexp_put_newline(struct sexp_output *output, + unsigned indent) { unsigned i; if (putc('\n', output->f) < 0) return 0; - for(i = 0; i<output->indent) + for(i = 0; i < indent; i++) if (putc(' ', output->f) < 0) return 0; - output->pos = output->indent; + output->pos = indent; return 1; } static int -sexp_put_char(struct sexp_output *output, +sexp_put_char(struct sexp_output *output, unsigned indent, uint8_t c) { - output->pos++; - return fputc(c, output->file) >= 0; -} - -static int -sexp_put_data(struct sexp_output *output, - unsigned length, uint8_t data) -{ - if (fwrite(data, 1, length, output->f) - == length) + if (output->mode & SEXP_TRANSPORT) { - output->pos += length; + uint8_t encoded[2]; + unsigned done; + unsigned i; + + done = base64_encode_single(&output->base64, encoded, c); + + assert(done <= sizeof(encoded)); + + for (i = 0; i<done; i++) + { + if (indent && + output->pos > LINE_WIDTH + && output->pos > (indent + 10)) + if (!sexp_put_newline(output, indent)) + return 0; + + if (putc(encoded[i], output->f) < 0) + return 0; + + output->pos++; + } return 1; } else + { + output->pos++; + return putc(c, output->f) >= 0; + } +} + +static int +sexp_put_data(struct sexp_output *output, unsigned indent, + unsigned length, const uint8_t *data) +{ + unsigned i; + + for (i = 0; i<length; i++) + if (!sexp_put_char(output, indent, data[i])) + return 0; + + return 1; +} + +static int +sexp_puts(struct sexp_output *output, unsigned indent, + const uint8_t *s) +{ + while (*s) + if (!sexp_put_char(output, indent, *s++)) + return 0; + + return 1; +} + +static int +sexp_put_length(struct sexp_output *output, unsigned indent, + unsigned length) +{ + unsigned digit = 1; + + while (digit < length) + digit *= 10; + + for (; digit; length %= digit, digit /= 10) + if (!sexp_put_char(output, indent, '0' + length / digit)) + return 0; + + return 1; +} + +static int +sexp_put_base64_start(struct sexp_output *output, uint8_t c) +{ + assert(! (output->mode & SEXP_TRANSPORT)); + + if (!sexp_put_char(output, 0, c)) return 0; + + base64_encode_init(&output->base64); + output->mode |= SEXP_TRANSPORT; + + return 1; } +static int +sexp_put_base64_end(struct sexp_output *output, uint8_t c) +{ + uint8_t encoded[BASE64_ENCODE_FINAL_LENGTH]; + unsigned done; + + assert(output->mode & SEXP_TRANSPORT); + + done = base64_encode_final(&output->base64, encoded); + + assert(done < sizeof(encoded)); + + output->mode &= ~ SEXP_TRANSPORT; + + return sexp_put_data(output, 0, done, encoded) + && sexp_put_char(output, 0, c); +} static int -sexp_put_string(struct sexp_output *output, +sexp_put_string(struct sexp_output *output, unsigned indent, struct nettle_buffer *string) { if (!string->size) - { - const char *s = (output->mode == SEXP_ADVANCED) ? "\"\"": "0:"; - output->pos += 2; - - return 2 == fputs(s , output->f); - } + return sexp_puts(output, indent, + (output->mode == SEXP_ADVANCED) ? "\"\"": "0:"); if (output->mode == SEXP_ADVANCED) { unsigned i; - int token = (string.buffer[0] < '0' || string.buffer[0] > '9'); + int token = (string->contents[0] < '0' || string->contents[0] > '9'); int quote_friendly = 1; - for (i = 0; i<string.size; i++) + for (i = 0; i<string->size; i++) { - uint8_t c = string.buffer[i]; + uint8_t c = string->contents[i]; if (token & !TOKEN_CHAR(c)) token = 0; @@ -492,64 +611,186 @@ sexp_put_string(struct sexp_output *output, } if (token) - return sexp_put_buffer(output, string.size, string.buffer); + return sexp_put_data(output, indent, string->size, string->contents); else if (quote_friendly) { - output->pos += 2; - - return sexp_put_char(output, '"') - && sexp_put_buffer(output, string.size, string.buffer) - && sexp_put_char(output, '"'); + return sexp_put_char(output, indent, '"') + && sexp_put_data(output, indent, string->size, string->contents) + && sexp_put_char(output, indent, '"'); } else - { -#define DATA_PER_LINE 40 - uint8_t line[BASE64_ENCODE_LENGTH(DATA_PER_LINE) - + BASE64_ENCODE_FINAL_LENGTH]; - struct base64_encode_ctx ctx; + return (sexp_put_base64_start(output, '|') + && sexp_put_data(output, output->pos, + string->size, string->contents) + && sexp_put_base64_end(output, '|')); + } + else + return sexp_put_length(output, indent, string->size) + && sexp_put_char(output, indent, ':') + && sexp_put_data(output, indent, string->size, string->contents); +} - unsigned old_indent = output->indent; - unsigned i; - - output->indent = output->pos + 1; - if (!sexp_put_char(output, '|')) - return 0; - - base64_encode_init(&ctx); - for (i = 0; i + DATA_PER_LINE < string.size) +static int +sexp_put_list_start(struct sexp_output *output, unsigned indent) +{ + if (!sexp_put_char(output, indent, '(')) + return 0; + + output->items = 0; + + return 1; +} + +static int +sexp_put_list_end(struct sexp_output *output, unsigned indent) +{ + return sexp_put_char(output, indent, ')') ; +} + +static int +sexp_put_display_start(struct sexp_output *output, unsigned indent) +{ + return sexp_put_char(output, indent, '['); +} + +static int +sexp_put_display_end(struct sexp_output *output, unsigned indent) +{ + return sexp_put_char(output, indent, ']') ; +} + +static int +sexp_convert_string(struct sexp_input *input, struct sexp_output *output, + unsigned indent) +{ + return (sexp_get_token(input) + && input->token == SEXP_STRING + && sexp_put_string(output, indent, &input->string)); +} + +static int +sexp_convert_item(struct sexp_input *input, struct sexp_output *output, + unsigned indent); + +static int +sexp_convert_list(struct sexp_input *input, struct sexp_output *output, + unsigned indent) +{ + if (!sexp_get_token(input)) + return 0; + + switch (sexp_convert_item(input, output, indent)) + { + case 0: + return 1; + case -1: + return 0; + case 1: + break; + } + + indent = output->pos; + + for (;;) + { + if (!sexp_get_token(input)) + return 0; + + if (input->token == SEXP_LIST_END + || input->token == SEXP_EOF + || input->token == SEXP_TRANSPORT_END) + return 1; + + sexp_put_newline(output, indent); + sexp_convert_item(input, output, indent); + } +} + +/* Returns 1 on success, -1 on error, and 0 at end of list/file. + * + * Should be called after getting the first token. */ +static int +sexp_convert_item(struct sexp_input *input, struct sexp_output *output, + unsigned indent) +{ + switch(input->token) + { + case SEXP_LIST_START: + input->level++; + + if (sexp_put_list_start(output, indent) + && sexp_convert_list(input, output, indent) + && sexp_put_list_end(output, indent)) + { + if (input->level) { - unsigned done = base64_encode_update(&ctx, - line, - DATA_PER_LINE, - string->buffer + i); - - assert(done <= BASE64_ENCODE_LENGTH(DATA_PER_LINE)); - - sexp_put_data(output, done, line); - sexp_put_newline(output); + input->level--; + if (input->token == SEXP_LIST_END) + return 1; } + else if (input->token == SEXP_EOF) + return 1; + } + return -1; + + case SEXP_LIST_END: + if (!input->level) + return -1; + + input->level--; + return 1; - output->indent = old_indent; - - done = base64_encode_update(&ctx, line, - string.size - i, string->buffer + i); - done += base64_encode_final(&ctx, line + done); - assert(done <= sizeof(line)); + case SEXP_EOF: + return input->level ? -1 : 1; + + case SEXP_STRING: + return sexp_put_string(output, indent, &input->string) ? 1 : -1; + + case SEXP_DISPLAY_START: + return (sexp_put_display_start(output, indent) + && sexp_convert_string(input, output, indent) + && sexp_put_display_end(output, indent) + && sexp_convert_string(input, output, indent)) ? 1 : -1; - return sexp_put_data(output, done, line) - && sexp_put_char(output, '}'); + case SEXP_TRANSPORT_START: + if (input->mode != SEXP_ADVANCED) + return -1; + else + { + unsigned old_level = input->level; + input->mode = SEXP_TRANSPORT; + input->level = 0; + + base64_decode_init(&input->base64); + + if (!sexp_convert_list(input, output, indent)) + return -1; + + input->mode = SEXP_ADVANCED; + input->level = old_level; + return 1; } + case SEXP_TRANSPORT_END: + if (input->mode != SEXP_TRANSPORT + || input->level || !base64_decode_status(&input->base64)) + return -1; + + return 0; + default: + return -1; } - else - /* FIXME: Support transport mode */ - return fprintf(output->f, "%d:", string.length) > 0 - && sexp_put_string(output, string.size, string.buffer); + abort(); } -static void -sexp_put_start_list(struct sexp_output *output) +int +main(int argc, char **argv) { -} + struct sexp_input input; + struct sexp_output output; + sexp_input_init(&input, stdin, SEXP_ADVANCED); + sexp_output_init(&output, stdout, SEXP_ADVANCED); + return sexp_convert_list(&input, &output, 0) ? EXIT_SUCCESS : EXIT_FAILURE; +}