hex-escape: (en|de)code strings to/from restricted character set
[notmuch] / util / hex-escape.c
1 /* hex-escape.c -  Manage encoding and decoding of byte strings into path names
2  *
3  * Copyright (c) 2011 David Bremner
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see http://www.gnu.org/licenses/ .
17  *
18  * Author: David Bremner <david@tethera.net>
19  */
20
21 #include <assert.h>
22 #include <string.h>
23 #include <talloc.h>
24 #include <ctype.h>
25 #include "error_util.h"
26 #include "hex-escape.h"
27
28 static const size_t default_buf_size = 1024;
29
30 static const char *output_charset =
31     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-_@=.,";
32
33 static const char escape_char = '%';
34
35 static int
36 is_output (char c)
37 {
38     return (strchr (output_charset, c) != NULL);
39 }
40
41 static int
42 maybe_realloc (void *ctx, size_t needed, char **out, size_t *out_size)
43 {
44     if (*out_size < needed) {
45
46         if (*out == NULL)
47             *out = talloc_size (ctx, needed);
48         else
49             *out = talloc_realloc (ctx, *out, char, needed);
50
51         if (*out == NULL)
52             return 0;
53
54         *out_size = needed;
55     }
56     return 1;
57 }
58
59 hex_status_t
60 hex_encode (void *ctx, const char *in, char **out, size_t *out_size)
61 {
62
63     const char *p;
64     char *q;
65
66     size_t needed = 1;  /* for the NUL */
67
68     assert (ctx); assert (in); assert (out); assert (out_size);
69
70     for (p = in; *p; p++) {
71         needed += is_output (*p) ? 1 : 3;
72     }
73
74     if (*out == NULL)
75         *out_size = 0;
76
77     if (!maybe_realloc (ctx, needed, out, out_size))
78         return HEX_OUT_OF_MEMORY;
79
80     q = *out;
81     p = in;
82
83     while (*p) {
84         if (is_output (*p)) {
85             *q++ = *p++;
86         } else {
87             sprintf (q, "%%%02x", (unsigned char)*p++);
88             q += 3;
89         }
90     }
91
92     *q = '\0';
93     return HEX_SUCCESS;
94 }
95
96 /* Hex decode 'in' to 'out'.
97  *
98  * This must succeed for in == out to support hex_decode_inplace().
99  */
100 static hex_status_t
101 hex_decode_internal (const char *in, unsigned char *out)
102 {
103     char buf[3];
104
105     while (*in) {
106         if (*in == escape_char) {
107             char *endp;
108
109             /* This also handles unexpected end-of-string. */
110             if (!isxdigit ((unsigned char) in[1]) ||
111                 !isxdigit ((unsigned char) in[2]))
112                 return HEX_SYNTAX_ERROR;
113
114             buf[0] = in[1];
115             buf[1] = in[2];
116             buf[2] = '\0';
117
118             *out = strtoul (buf, &endp, 16);
119
120             if (endp != buf + 2)
121                 return HEX_SYNTAX_ERROR;
122
123             in += 3;
124             out++;
125         } else {
126             *out++ = *in++;
127         }
128     }
129
130     *out = '\0';
131
132     return HEX_SUCCESS;
133 }
134
135 hex_status_t
136 hex_decode_inplace (char *s)
137 {
138     /* A decoded string is never longer than the encoded one, so it is
139      * safe to decode a string onto itself. */
140     return hex_decode_internal (s, (unsigned char *) s);
141 }
142
143 hex_status_t
144 hex_decode (void *ctx, const char *in, char **out, size_t * out_size)
145 {
146     const char *p;
147     size_t needed = 1;  /* for the NUL */
148
149     assert (ctx); assert (in); assert (out); assert (out_size);
150
151     for (p = in; *p; p++)
152         if ((p[0] == escape_char) && isxdigit (p[1]) && isxdigit (p[2]))
153             needed -= 1;
154         else
155             needed += 1;
156
157     if (!maybe_realloc (ctx, needed, out, out_size))
158         return HEX_OUT_OF_MEMORY;
159
160     return hex_decode_internal (in, (unsigned char *) *out);
161 }