summaryrefslogtreecommitdiffstats
path: root/src/global/safe_ultostr.c
blob: 910c2ee68c5468e2df07ce85af7603ab8dc96909 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/*++
/* NAME
/*	safe_ultostr 3
/* SUMMARY
/*	convert unsigned long to safe string
/* SYNOPSIS
/*	#include <safe_ultostr.h>
/*
/*	char	*safe_ultostr(result, ulval, base, padlen, padchar)
/*	VSTRING	*result;
/*	unsigned long ulval;
/*	int	base;
/*	int	padlen;
/*	int	padchar;
/*
/*	unsigned long safe_strtoul(start, end, base)
/*	const char *start;
/*	char **end;
/*	int	base;
/* DESCRIPTION
/*	The functions in this module perform conversions between
/*	unsigned long values and "safe" alphanumerical strings
/*	(strings with digits, uppercase letters and lowercase
/*	letters, but without the vowels AEIOUaeiou). Specifically,
/*	the characters B-Z represent the numbers 10-30, and b-z
/*	represent 31-51.
/*
/*	safe_ultostr() converts an unsigned long value to a safe
/*	alphanumerical string. This is the reverse of safe_strtoul().
/*
/*	safe_strtoul() implements similar functionality as strtoul()
/*	except that it uses a safe alphanumerical string as input,
/*	and that it supports no signs or 0/0x prefixes.
/*
/*	Arguments:
/* .IP result
/*	Buffer for storage of the result of conversion to string.
/* .IP ulval
/*	Unsigned long value.
/* .IP base
/*	Value between 2 and 52 inclusive.
/* .IP padlen
/* .IP padchar
/*	Left-pad a short result with padchar characters to the
/*	specified length.  Specify padlen=0 to disable padding.
/* .IP start
/*	Pointer to the first character of the string to be converted.
/* .IP end
/*	On return, pointer to the first character not in the input
/*	alphabet, or to the string terminator.
/* DIAGNOSTICS
/*	Fatal: out of memory.
/*
/*	safe_strtoul() returns (0, EINVAL) when no conversion could
/*	be performed, and (ULONG_MAX, ERANGE) in case of overflow.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <ctype.h>

/* Utility library. */

#include <msg.h>
#include <vstring.h>
#include <mymalloc.h>

/* Global library. */

#include <safe_ultostr.h>

/* Application-specific. */

#define STR	vstring_str
#define END	vstring_end
#define SWAP(type, a, b) { type temp; temp = a; a = b; b = temp; }

static unsigned char safe_chars[] =
"0123456789BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz";

#define SAFE_MAX_BASE	(sizeof(safe_chars) - 1)
#define SAFE_MIN_BASE	(2)

/* safe_ultostr - convert unsigned long to safe alphanumerical string */

char   *safe_ultostr(VSTRING *buf, unsigned long ulval, int base,
		             int padlen, int padchar)
{
    const char *myname = "safe_ultostr";
    char   *start;
    char   *last;
    int     i;

    /*
     * Sanity check.
     */
    if (base < SAFE_MIN_BASE || base > SAFE_MAX_BASE)
	msg_panic("%s: bad base: %d", myname, base);

    /*
     * First accumulate the result, backwards.
     */
    VSTRING_RESET(buf);
    while (ulval != 0) {
	VSTRING_ADDCH(buf, safe_chars[ulval % base]);
	ulval /= base;
    }
    while (VSTRING_LEN(buf) < padlen)
	VSTRING_ADDCH(buf, padchar);
    VSTRING_TERMINATE(buf);

    /*
     * Then, reverse the result.
     */
    start = STR(buf);
    last = END(buf) - 1;
    for (i = 0; i < VSTRING_LEN(buf) / 2; i++)
	SWAP(int, start[i], last[-i]);
    return (STR(buf));
}

/* safe_strtoul - convert safe alphanumerical string to unsigned long */

unsigned long safe_strtoul(const char *start, char **end, int base)
{
    const char *myname = "safe_strtoul";
    static unsigned char *char_map = 0;
    unsigned char *cp;
    unsigned long sum;
    unsigned long div_limit;
    unsigned long mod_limit;
    int     char_val;

    /*
     * Sanity check.
     */
    if (base < SAFE_MIN_BASE || base > SAFE_MAX_BASE)
	msg_panic("%s: bad base: %d", myname, base);

    /*
     * One-time initialization. Assume 8-bit bytes.
     */
    if (char_map == 0) {
	char_map = (unsigned char *) mymalloc(256);
	for (char_val = 0; char_val < 256; char_val++)
	    char_map[char_val] = SAFE_MAX_BASE;
	for (char_val = 0; char_val < SAFE_MAX_BASE; char_val++)
	    char_map[safe_chars[char_val]] = char_val;
    }

    /*
     * Per-call initialization.
     */
    sum = 0;
    div_limit = ULONG_MAX / base;
    mod_limit = ULONG_MAX % base;

    /*
     * Skip leading whitespace. We don't implement sign/base prefixes.
     */
    if (end)
	*end = (char *) start;
    while (ISSPACE(*start))
	++start;

    /*
     * Start the conversion.
     */
    errno = 0;
    for (cp = (unsigned char *) start; (char_val = char_map[*cp]) < base; cp++) {
	/* Return (ULONG_MAX, ERANGE) if the result is too large. */
	if (sum > div_limit
	    || (sum == div_limit && char_val > mod_limit)) {
	    sum = ULONG_MAX;
	    errno = ERANGE;
	    /* Skip "valid" characters, per the strtoul() spec. */
	    while (char_map[*++cp] < base)
		 /* void */ ;
	    break;
	}
	sum = sum * base + char_val;
    }
    /* Return (0, EINVAL) after no conversion. Test moved here 20131209. */
    if (cp == (unsigned char *) start)
	errno = EINVAL;
    else if (end)
	*end = (char *) cp;
    return (sum);
}

#ifdef TEST

 /*
  * Proof-of-concept test program. Read a number from stdin, convert to
  * string, and print the result.
  */
#include <stdio.h>			/* sscanf */
#include <vstream.h>
#include <vstring_vstream.h>

int     main(int unused_argc, char **unused_argv)
{
    VSTRING *buf = vstring_alloc(100);
    char   *junk;
    unsigned long ulval;
    int     base;
    char    ch;
    unsigned long ulval2;

#ifdef MISSING_STRTOUL
#define strtoul strtol
#endif

    /*
     * Hard-coded string-to-number test.
     */
    ulval2 = safe_strtoul("  ", &junk, 10);
    if (*junk == 0 || errno != EINVAL)
	msg_warn("input=' ' result=%lu errno=%m", ulval2);

    /*
     * Configurable number-to-string-to-number test.
     */
    while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
	ch = 0;
	if (sscanf(STR(buf), "%lu %d%c", &ulval, &base, &ch) != 2 || ch) {
	    msg_warn("bad input %s", STR(buf));
	} else {
	    (void) safe_ultostr(buf, ulval, base, 5, '0');
	    vstream_printf("%lu = %s\n", ulval, STR(buf));
	    ulval2 = safe_strtoul(STR(buf), &junk, base);
	    if (*junk || (ulval2 == ULONG_MAX && errno == ERANGE))
		msg_warn("%s: %m", STR(buf));
	    if (ulval2 != ulval)
		msg_warn("%lu != %lu", ulval2, ulval);
	}
	vstream_fflush(VSTREAM_OUT);
    }
    vstring_free(buf);
    return (0);
}

#endif