diff --git a/examples/sexp-conv.c b/examples/sexp-conv.c new file mode 100644 index 0000000000000000000000000000000000000000..16abe234a6ebfb6b87cdfea056c5ec6745dbc7ed --- /dev/null +++ b/examples/sexp-conv.c @@ -0,0 +1,555 @@ +/* sexp-conv.c + * + * Conversion tool for handling the different flavours of sexp + * syntax. */ + +#include "base64.h" + +enum sexp_mode + { + SEXP_CANONICAL = 0, + SEXP_ADVANCED = 1, + /* OR:ed with SEXP_CANONICAL or SEXP_ADVANCED when reading + * transport data. */ + SEXP_TRANSPORT = 2, + }; + +enum sexp_token + { + SEXP_LIST, + SEXP_STRING, + SEXP_DISPLAY_START, + SEXP_DISPLAY_END, + SEXP_LIST_START, + SEXP_LIST_END, + SEXP_TRANSPORT_START, + SEXP_TRANSPORT_END, + SEXP_EOF, + }; + +struct sexp_input +{ + FILE *f; + + enum sexp_mode mode; + /* Used in transport mode */ + struct base64_decode_ctx base64; + + /* Type of current token */ + enum sexp_token token; + + /* Current token */ + struct nettle_buffer string; + + /* Nesting level */ + unsigned level; +}; + +struct sexp_output +{ + FILE *f; + + enum sexp_mode mode; + + unsigned indent; + unsigned pos; +}; + + +/* Returns 1 on success. On failure, return -1. For special tokens, + * return 0 and set input->token accordingly. */ +static int +sexp_get_char(struct sexp_input *input, uint8_t *out) +{ + if (input->mode & SEXP_TRANSPORT) + { + /* Base64 decode */ + for (;;) + { + int done; + int c = getc(input->f); + if (c < 0) + return -1; + + if (c == '}') + { + if (base64_decode_status(&input->ctx)) + { + input->token = SEXP_TRANSPORT_END; + return 0; + } + else + return -1; + } + + done = base64_decode_single(&input->ctx, out, c); + if (done) + return 1; + } + } + else + for (;;) + { + int c = getc(input->f); + + if (c < 0) + { + if (ferror(input->f)) + return -1; + + input->token = SEXP_EOF; + return 0; + } + } +} + +static const char +token_chars[0x80] = + { + /* 0, ... 0x1f */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + /* SPC ! " # $ % & ' ( ) * + , - . / */ + 0,0,0,0,0,0,0,0, 0,0,1,1,0,1,1,1, + /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */ + 1,1,1,1,1,1,1,1, 1,1,1,0,0,1,0,0, + /* @ A ... O */ + 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + /* P ... Z [ \ ] ^ _ */ + 1,1,1,1,1,1,1,1, 1,1,1,0,0,0,0,1, + /* ` a, ... o */ + 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + /* p ... z { | } ~ DEL */ + 1,1,1,1,1,1,1,1, 1,1,1,0,0,0,0,0, + }; + +#define TOKEN_CHAR(c) ((c) < 0x80 && token_chars[(c)]) + +/* Returns 0 at end of token */ +static uint8_t +sexp_get_token_char(struct sexp_input *input) +{ + int c = getc(input->f); + if (c >= 0 && TOKEN_CHAR(c)) + return c; + + ungetc(input->f, c); + return 0; +} + + +#if 0 +static void +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); + +} + +static int +sexp_get_quoted_char(struct sexp_input *input, uint8_t *c) +{ + if (sexp_get_char(input, c) <= 0) + return -1; + + for (;;) + switch (*c) + { + case '\"': + return 0; + case '\\': + if (sexp_get_char(input, c) <= 0) + return -1; + switch (*c) + { + case 'b': *c = '\b'; return 1; + case 't': *c = '\t'; return 1; + case 'n': *c = '\n'; return 1; + case 'f': *c = '\f'; return 1; + case 'r': *c = '\r'; return 1; + case '\\': *c = '\\'; return 1; + case 'o': + case 'x': + /* Not implemnted */ + abort(); + case '\n': + if (sexp_get_char(input, c) <= 0) + return -1; + if (*c == '\r' && sexp_get_char(input, c) <= 0) + return -1; + break; + case '\r': + if (sexp_get_char(input, c) <= 0) + return -1; + if (*c == '\n' && sexp_get_char(input, c) <= 0) + return -1; + break; + } + } +} + +static int +sexp_get_quoted_string(struct sexp_input *input) +{ + assert(input->mode == SEXP_ADVANCED); + + for (;;) + { + uint8_t c; + + switch (sexp_get_quoted_char(input, &c)) + { + case 0: + return 1; + case -1: + return 0; + default: + if (!NETTLE_BUFFER_PUTC(&input->string, c)) + return 0; + } + } +} + +static int +sexp_get_hex_string(struct sexp_input *input) +{ + /* Not implemented */ + abort(); +} + +static int +sexp_get_base64_string(struct sexp_input *input) +{ + struct base64_decode_ctx ctx; + + assert(input->mode == SEXP_ADVANCED); + + base64_decode_init(&ctx); + + for (;;) + { + uint8_t c; + uint8_t decoded; + + if (sexp_get_char(input, &c) <= 0) + return -1; + + if (c == '|') + return base64_decode_status(&ctx); + + if (base64_decode_single(&ctx, &decoded, c)) + if (!NETTLE_BUFFER_PUTC(&input->string, decoded)) + return 0; + } +} + +static int +sexp_get_atom_string(struct sexp_input *input, uint8_t c) +{ + assert(input->mode == SEXP_ADVANCED); + + if (!TOKEN_CHAR(c) || ! NETTLE_BUFFER_PUTC(&input->string, c)) + return 0; + + while ( (c = sexp_get_token_char(input)) > 0) + { + if (!NETTLE_BUFFER_PUTC(&input->string, c)) + return 0; + } + + assert (input->string.size); + return 1; +} + +static int +sexp_get_string(struct sexp_input *input, uint8_t c) +{ + assert(input->mode == SEXP_ADVANCED); + input->string.size = 0; + input->token = SEXP_STRING; + + switch (c) + { + case '\"': + return sexp_get_quoted_string(input); + + case '#': + return sexp_get_hex_string(input); + + case '|': + return sexp_get_base64_string(input); + + default: + return sexp_get_token_string(input, c); + } +} + +static int +sexp_get_string_length(struct sexp_input *input, unsigned length) +{ + uint8_t c; + + input->string.size = 0; + input->token = SEXP_STRING; + + if (!length) + { + /* There must ne no more digits */ + if (sexp_get_char(input, &c) <= 0) + return 0; + } + else + /* Get rest of digits */ + for (;;) + { + if (sexp_get_char(input, &c) <= 0) + return 0; + + if (c < 0 || < > 9) + break; + + /* FIXME: Check for overflow? */ + length = length * 10 + c - '0'; + } + + switch(c) + { + case ':': + /* Verbatim */ + for (; length; length--) + if (sexp_get_char(input, &c) <= 0 + || !NETTLE_BUFFER_PUTC(&input->string, c)) + return 0; + + return 1; + + case '"': + if (input->mode != SEXP_ADVANCED) + return 0; + + for (; length; length--) + if (sexp_get_quoted_char(input, &c) != 1 + || !NETTLE_BUFFER_PUTC(&input->string, c)) + return 0; + + return sexp_get_quoted_char(input, &c) == 0; + + case '#': + case '|': + /* Not yet implemented */ + abort(); + + default: + return 0; + } +} + +/* Returns 1 on success, zero on failure */ +static int +sexp_get_token(struct sexp_input *input) +{ + uint8_t c; + switch (sexp_get_char(input, &c)) + { + case -1: + return 0; + case 0: + return 1; + case 1: + switch(c) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + return sexp_get_string_length(input, c - '0'); + + case '(': + input->token = SEXP_START_LIST; + return 1; + case ')': + input->token = SEXP_END_LIST; + return 1; + + case '[': + input->token = SEXP_DISPLAY_START; + return 1; + + case ']': + input->token = SEXP_DISPLAY_END; + return 1; + + case ' ': /* SPC, TAB, LF, CR */ + case '\t': + case '\n': + case '\r': + if (input->mode != SEXP_ADVANCED) + return 0; + break; + + case ';': /* Comments */ + if (input->mode != SEXP_ADVANCED) + return 0; + + for (;;) + { + int c = getc(input->f); + if (c < 0) + { + if (ferror(input->f)) + return 0; + else + { + input->token = SEXP_EOF; + return 1; + } + } + if (c != '\n') + break; + } + break; + + default: + /* Ought to be a string */ + return (input->mode == SEXP_ADVANCED) + && sexp_get_string(input, c); + } + } +} + +static int +sexp_put_newline(struct sexp_output *output) +{ + unsigned i; + if (putc('\n', output->f) < 0) + return 0; + + for(i = 0; i<output->indent) + if (putc(' ', output->f) < 0) + return 0; + + output->pos = output->indent; + + return 1; +} + +static int +sexp_put_char(struct sexp_output *output, + 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) + { + output->pos += length; + return 1; + } + else + return 0; +} + + +static int +sexp_put_string(struct sexp_output *output, + struct nettle_buffer *string) +{ + if (!string->size) + { + const char *s = (output->mode == SEXP_ADVANCED) ? "\"\"": "0:"; + output->pos += 2; + + return 2 == fputs(s , output->f); + } + + if (output->mode == SEXP_ADVANCED) + { + unsigned i; + int token = (string.buffer[0] < '0' || string.buffer[0] > '9'); + int quote_friendly = 1; + + for (i = 0; i<string.size; i++) + { + uint8_t c = string.buffer[i]; + + if (token & !TOKEN_CHAR(c)) + token = 0; + + if (quote_friendly && (c < 0x20 || c >= 0x7f)) + quote_friendly = 0; + } + + if (token) + return sexp_put_buffer(output, string.size, string.buffer); + + else if (quote_friendly) + { + output->pos += 2; + + return sexp_put_char(output, '"') + && sexp_put_buffer(output, string.size, string.buffer) + && sexp_put_char(output, '"'); + } + else + { +#define DATA_PER_LINE 40 + uint8_t line[BASE64_ENCODE_LENGTH(DATA_PER_LINE) + + BASE64_ENCODE_FINAL_LENGTH]; + struct base64_encode_ctx ctx; + + 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) + { + 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); + } + + 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)); + + return sexp_put_data(output, done, line) + && sexp_put_char(output, '}'); + } + } + else + /* FIXME: Support transport mode */ + return fprintf(output->f, "%d:", string.length) > 0 + && sexp_put_string(output, string.size, string.buffer); +} + +static void +sexp_put_start_list(struct sexp_output *output) +{ +} + +