blob: 8f5c0d30be4b8f3a5790ddfab30ff24fd61fb894 [file] [log] [blame]
/* dumpsexp.c - Dump S-expressions.
* Copyright (C) 2007 Free Software Foundation, Inc.
*
* Getrandom is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* Getrandom is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <stdarg.h>
#include <errno.h>
#define PGM "dumpsexp"
#define MYVERSION_LINE PGM " (Libgcrypt) " VERSION
#define BUGREPORT_LINE "\nReport bugs to <bug-libgcrypt@gnupg.org>.\n"
static int verbose; /* Verbose mode. */
static int decimal; /* Print addresses in decimal. */
static int assume_hex; /* Assume input is hexencoded. */
static void
print_version (int with_help)
{
fputs (MYVERSION_LINE "\n"
"Copyright (C) 2007 Free Software Foundation, Inc.\n"
"License GPLv2+: GNU GPL version 2 or later "
"<http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>\n"
"This is free software: you are free to change and redistribute it.\n"
"There is NO WARRANTY, to the extent permitted by law.\n",
stdout);
if (with_help)
fputs ("\n"
"Usage: " PGM " [OPTIONS] [file]\n"
"Debug tool for S-expressions\n"
"\n"
" --decimal Print offsets using decimal notation\n"
" --assume-hex Assume input is a hex dump\n"
" --verbose Show what we are doing\n"
" --version Print version of the program and exit\n"
" --help Display this help and exit\n"
BUGREPORT_LINE, stdout );
exit (0);
}
static int
print_usage (void)
{
fputs ("usage: " PGM " [OPTIONS] NBYTES\n", stderr);
fputs (" (use --help to display options)\n", stderr);
exit (1);
}
#define space_p(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t')
#define digit_p(a) ((a) >= '0' && (a) <= '9')
#define octdigit_p(a) ((a) >= '0' && (a) <= '7')
#define alpha_p(a) ( ((a) >= 'A' && (a) <= 'Z') \
|| ((a) >= 'a' && (a) <= 'z'))
#define hexdigit_p(a) (digit_p (a) \
|| ((a) >= 'A' && (a) <= 'F') \
|| ((a) >= 'a' && (a) <= 'f'))
#define xtoi_1(a) ((a) <= '9'? ((a)- '0'): \
(a) <= 'F'? ((a)-'A'+10):((a)-'a'+10))
/* Return true if P points to a byte containing a whitespace according
to the S-expressions definition. */
static inline int
whitespace_p (int c)
{
switch (c)
{
case ' ': case '\t': case '\v': case '\f': case '\r': case '\n': return 1;
default: return 0;
}
}
static void
logit (const char *format, ...)
{
va_list arg_ptr;
va_start (arg_ptr, format) ;
fputs (PGM ": ", stderr);
vfprintf (stderr, format, arg_ptr);
putc ('\n', stderr);
va_end (arg_ptr);
}
/* The raw data buffer and its current length */
static unsigned char databuffer[16];
static int databufferlen;
/* The number of bytes in databuffer which should be skipped at a flush. */
static int skipdatabufferlen;
/* The number of raw bytes printed on the last line. */
static int nbytesprinted;
/* The file offset of the current data buffer . */
static unsigned long databufferoffset;
static int
my_getc (FILE *fp)
{
int c1, c2;
if (!assume_hex)
return getc (fp);
while ( (c1=getc (fp)) != EOF && space_p (c1) )
;
if (c1 == EOF)
return EOF;
if (!hexdigit_p (c1))
{
logit ("non hex-digit encountered\n");
return EOF;
}
while ( (c2=getc (fp)) != EOF && space_p (c2) )
;
if (c2 == EOF)
{
logit ("error reading second hex nibble\n");
return EOF;
}
if (!hexdigit_p (c2))
{
logit ("second hex nibble is not a hex-digit\n");
return EOF;
}
return xtoi_1 (c1) * 16 + xtoi_1 (c2);
}
/* Flush the raw data buffer. */
static void
flushdatabuffer (void)
{
int i;
if (!databufferlen)
return;
nbytesprinted = 0;
if (decimal)
printf ("%08lu ", databufferoffset);
else
printf ("%08lx ", databufferoffset);
for (i=0; i < databufferlen; i++)
{
if (i == 8)
putchar (' ');
if (i < skipdatabufferlen)
fputs (" ", stdout);
else
{
printf (" %02x", databuffer[i]);
databufferoffset++;
}
nbytesprinted++;
}
for (; i < sizeof (databuffer); i++)
{
if (i == 8)
putchar (' ');
fputs (" ", stdout);
}
fputs (" |", stdout);
for (i=0; i < databufferlen; i++)
{
if (i < skipdatabufferlen)
putchar (' ');
else if (databuffer[i] >= ' ' && databuffer[i] <= '~'
&& databuffer[i] != '|')
putchar (databuffer[i]);
else
putchar ('.');
}
putchar ('|');
putchar ('\n');
databufferlen = 0;
skipdatabufferlen = 0;
}
/* Add C to the raw data buffer and flush as needed. */
static void
addrawdata (int c)
{
if ( databufferlen >= sizeof databuffer )
flushdatabuffer ();
databuffer[databufferlen++] = c;
}
static void
printcursor (int both)
{
int i;
flushdatabuffer ();
printf ("%8s ", "");
for (i=0; i < sizeof (databuffer); i++)
{
if (i == 8)
putchar (' ');
if (i+1 == nbytesprinted)
{
fputs (" ^ ", stdout);
if (!both)
break;
}
else
fputs (" ", stdout);
}
if (both)
{
fputs (" ", stdout);
for (i=0; i < nbytesprinted-1; i++)
putchar (' ');
putchar ('^');
}
databufferlen = skipdatabufferlen = nbytesprinted;
}
static void
printerr (const char *text)
{
printcursor (1);
printf ("\n Error: %s\n", text);
}
static void
printctl (const char *text)
{
if (verbose)
{
printcursor (0);
printf ("%s\n", text);
}
}
static void
printchr (int c)
{
(void)c;
}
static void
printhex (int c)
{
(void)c;
}
static int
parse_and_print (FILE *fp)
{
static const char tokenchars[] =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789-./_:*+=";
int c;
int level = 0;
int tokenc = 0;
int hexcount = 0;
int disphint = 0;
unsigned long datalen = 0;
char quote_buf[10];
int quote_idx = 0;
enum
{
INIT_STATE = 0, IN_NUMBER, PRE_DATA, IN_DATA, IN_STRING,
IN_ESCAPE, IN_OCT_ESC, IN_HEX_ESC,
CR_ESC, LF_ESC, IN_HEXFMT, IN_BASE64
}
state = INIT_STATE;
while ((c = my_getc (fp)) != EOF )
{
addrawdata (c);
switch (state)
{
case INIT_STATE:
if (tokenc)
{
if (strchr (tokenchars, c))
{
printchr (c);
continue;
}
tokenc = 0;
}
parse_init_state:
if (c == '(')
{
if (disphint)
{
printerr ("unmatched display hint");
disphint = 0;
}
printctl ("open");
level++;
}
else if (c == ')')
{
if (disphint)
{
printerr ("unmatched display hint");
disphint = 0;
}
printctl ("close");
level--;
}
else if (c == '\"')
{
state = IN_STRING;
printctl ("beginstring");
}
else if (c == '#')
{
state = IN_HEXFMT;
hexcount = 0;
printctl ("beginhex");
}
else if (c == '|')
{
state = IN_BASE64;
printctl ("beginbase64");
}
else if (c == '[')
{
if (disphint)
printerr ("nested display hint");
disphint = c;
}
else if (c == ']')
{
if (!disphint)
printerr ("no open display hint");
disphint = 0;
}
else if (c >= '0' && c <= '9')
{
if (c == '0')
printerr ("zero prefixed length");
state = IN_NUMBER;
datalen = (c - '0');
}
else if (strchr (tokenchars, c))
{
printchr (c);
tokenc = c;
}
else if (whitespace_p (c))
;
else if (c == '{')
{
printerr ("rescanning is not supported");
}
else if (c == '&' || c == '\\')
{
printerr ("reserved punctuation detected");
}
else
{
printerr ("bad character detected");
}
break;
case IN_NUMBER:
if (digit_p (c))
{
unsigned long tmp = datalen * 10 + (c - '0');
if (tmp < datalen)
{
printerr ("overflow in data length");
state = INIT_STATE;
datalen = 0;
}
else
datalen = tmp;
}
else if (c == ':')
{
if (!datalen)
{
printerr ("no data length");
state = INIT_STATE;
}
else
state = PRE_DATA;
}
else if (c == '\"' || c == '#' || c == '|' )
{
/* We ignore the optional length and divert to the init
state parser code. */
goto parse_init_state;
}
else
printerr ("invalid length specification");
break;
case PRE_DATA:
state = IN_DATA;
printctl ("begindata");
case IN_DATA:
if (datalen)
{
printhex (c);
datalen--;
}
if (!datalen)
{
state = INIT_STATE;
printctl ("enddata");
}
break;
case IN_STRING:
if (c == '\"')
{
printctl ("endstring");
state = INIT_STATE;
}
else if (c == '\\')
state = IN_ESCAPE;
else
printchr (c);
break;
case IN_ESCAPE:
switch (c)
{
case 'b': case 't': case 'v': case 'n': case 'f':
case 'r': case '"': case '\'': case '\\':
printhex (c);
state = IN_STRING;
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7':
state = IN_OCT_ESC;
quote_idx = 0;
quote_buf[quote_idx++] = c;
break;
case 'x':
state = IN_HEX_ESC;
quote_idx = 0;
break;
case '\r':
state = CR_ESC;
break;
case '\n':
state = LF_ESC;
break;
default:
printerr ("invalid escape sequence");
state = IN_STRING;
break;
}
case IN_OCT_ESC:
state = IN_STRING;
break;
case IN_HEX_ESC:
state = IN_STRING;
break;
case CR_ESC:
state = IN_STRING;
break;
case LF_ESC:
state = IN_STRING;
break;
case IN_HEXFMT:
if (hexdigit_p (c))
{
printchr (c);
hexcount++;
}
else if (c == '#')
{
if ((hexcount & 1))
printerr ("odd number of hex digits");
printctl ("endhex");
state = INIT_STATE;
}
else if (!whitespace_p (c))
printerr ("bad hex character");
break;
case IN_BASE64:
if (c == '|')
{
printctl ("endbase64");
state = INIT_STATE;
}
else
printchr (c);
break;
default:
logit ("invalid state %d detected", state);
exit (1);
}
}
flushdatabuffer ();
if (ferror (fp))
{
logit ("error reading input: %s\n", strerror (errno));
return -1;
}
return 0;
}
int
main (int argc, char **argv)
{
int rc;
if (argc)
{
argc--; argv++;
}
while (argc && **argv == '-' && (*argv)[1] == '-')
{
if (!(*argv)[2])
{
argc--; argv++;
break;
}
else if (!strcmp (*argv, "--version"))
print_version (0);
else if (!strcmp (*argv, "--help"))
print_version (1);
else if (!strcmp (*argv, "--verbose"))
{
argc--; argv++;
verbose = 1;
}
else if (!strcmp (*argv, "--decimal"))
{
argc--; argv++;
decimal = 1;
}
else if (!strcmp (*argv, "--assume-hex"))
{
argc--; argv++;
assume_hex = 1;
}
else
print_usage ();
}
if (!argc)
{
rc = parse_and_print (stdin);
}
else
{
for (; argc; argc--)
{
FILE *fp = fopen (*argv, "rb");
if (!fp)
{
logit ("can't open `%s': %s\n", *argv, strerror (errno));
rc = 1;
}
else
{
if ( parse_and_print (fp) )
rc = 1;
fclose (fp);
}
}
}
return !rc;
}