aboutsummaryrefslogtreecommitdiff
path: root/src/lib/encoding
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/encoding')
-rw-r--r--src/lib/encoding/.may_include10
-rw-r--r--src/lib/encoding/binascii.c520
-rw-r--r--src/lib/encoding/binascii.h60
-rw-r--r--src/lib/encoding/confline.c402
-rw-r--r--src/lib/encoding/confline.h78
-rw-r--r--src/lib/encoding/cstring.c138
-rw-r--r--src/lib/encoding/cstring.h19
-rw-r--r--src/lib/encoding/include.am26
-rw-r--r--src/lib/encoding/keyval.c52
-rw-r--r--src/lib/encoding/keyval.h17
-rw-r--r--src/lib/encoding/pem.c106
-rw-r--r--src/lib/encoding/pem.h26
-rw-r--r--src/lib/encoding/time_fmt.c516
-rw-r--r--src/lib/encoding/time_fmt.h44
14 files changed, 2014 insertions, 0 deletions
diff --git a/src/lib/encoding/.may_include b/src/lib/encoding/.may_include
new file mode 100644
index 0000000000..7c2ef36929
--- /dev/null
+++ b/src/lib/encoding/.may_include
@@ -0,0 +1,10 @@
+orconfig.h
+lib/cc/*.h
+lib/ctime/*.h
+lib/encoding/*.h
+lib/intmath/*.h
+lib/log/*.h
+lib/malloc/*.h
+lib/string/*.h
+lib/testsupport/*.h
+lib/wallclock/*.h
diff --git a/src/lib/encoding/binascii.c b/src/lib/encoding/binascii.c
new file mode 100644
index 0000000000..bd063440d6
--- /dev/null
+++ b/src/lib/encoding/binascii.c
@@ -0,0 +1,520 @@
+/* Copyright (c) 2001, Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file binascii.c
+ *
+ * \brief Miscellaneous functions for encoding and decoding various things
+ * in base{16,32,64}.
+ */
+
+#include "orconfig.h"
+
+#include "lib/encoding/binascii.h"
+#include "lib/log/log.h"
+#include "lib/log/util_bug.h"
+#include "lib/cc/torint.h"
+#include "lib/string/compat_ctype.h"
+#include "lib/intmath/muldiv.h"
+#include "lib/malloc/malloc.h"
+
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+
+/** Return a pointer to a NUL-terminated hexadecimal string encoding
+ * the first <b>fromlen</b> bytes of <b>from</b>. (fromlen must be \<= 32.) The
+ * result does not need to be deallocated, but repeated calls to
+ * hex_str will trash old results.
+ */
+const char *
+hex_str(const char *from, size_t fromlen)
+{
+ static char buf[65];
+ if (fromlen>(sizeof(buf)-1)/2)
+ fromlen = (sizeof(buf)-1)/2;
+ base16_encode(buf,sizeof(buf),from,fromlen);
+ return buf;
+}
+
+/* Return the base32 encoded size in bytes using the source length srclen.
+ *
+ * (WATCH OUT: This API counts the terminating NUL byte, but
+ * base64_encode_size does not.)
+ */
+size_t
+base32_encoded_size(size_t srclen)
+{
+ size_t enclen;
+ tor_assert(srclen < SIZE_T_CEILING / 8);
+ enclen = BASE32_NOPAD_BUFSIZE(srclen);
+ tor_assert(enclen < INT_MAX && enclen > srclen);
+ return enclen;
+}
+
+/** Implements base32 encoding as in RFC 4648. */
+void
+base32_encode(char *dest, size_t destlen, const char *src, size_t srclen)
+{
+ unsigned int i, v, u;
+ size_t nbits = srclen * 8;
+ size_t bit;
+
+ /* We need enough space for the encoded data and the extra NUL byte. */
+ tor_assert(base32_encoded_size(srclen) <= destlen);
+ tor_assert(destlen < SIZE_T_CEILING);
+
+ /* Make sure we leave no uninitialized data in the destination buffer. */
+ memset(dest, 0, destlen);
+
+ for (i=0,bit=0; bit < nbits; ++i, bit+=5) {
+ /* set v to the 16-bit value starting at src[bits/8], 0-padded. */
+ size_t idx = bit / 8;
+ v = ((uint8_t)src[idx]) << 8;
+ if (idx+1 < srclen)
+ v += (uint8_t)src[idx+1];
+ /* set u to the 5-bit value at the bit'th bit of buf. */
+ u = (v >> (11-(bit%8))) & 0x1F;
+ dest[i] = BASE32_CHARS[u];
+ }
+ dest[i] = '\0';
+}
+
+/** Implements base32 decoding as in RFC 4648.
+ * Returns 0 if successful, -1 otherwise.
+ */
+int
+base32_decode(char *dest, size_t destlen, const char *src, size_t srclen)
+{
+ /* XXXX we might want to rewrite this along the lines of base64_decode, if
+ * it ever shows up in the profile. */
+ unsigned int i;
+ size_t nbits, j, bit;
+ char *tmp;
+ nbits = ((srclen * 5) / 8) * 8;
+
+ tor_assert(srclen < SIZE_T_CEILING / 5);
+ tor_assert((nbits/8) <= destlen); /* We need enough space. */
+ tor_assert(destlen < SIZE_T_CEILING);
+
+ /* Make sure we leave no uninitialized data in the destination buffer. */
+ memset(dest, 0, destlen);
+
+ /* Convert base32 encoded chars to the 5-bit values that they represent. */
+ tmp = tor_malloc_zero(srclen);
+ for (j = 0; j < srclen; ++j) {
+ if (src[j] > 0x60 && src[j] < 0x7B) tmp[j] = src[j] - 0x61;
+ else if (src[j] > 0x31 && src[j] < 0x38) tmp[j] = src[j] - 0x18;
+ else if (src[j] > 0x40 && src[j] < 0x5B) tmp[j] = src[j] - 0x41;
+ else {
+ log_warn(LD_GENERAL, "illegal character in base32 encoded string");
+ tor_free(tmp);
+ return -1;
+ }
+ }
+
+ /* Assemble result byte-wise by applying five possible cases. */
+ for (i = 0, bit = 0; bit < nbits; ++i, bit += 8) {
+ switch (bit % 40) {
+ case 0:
+ dest[i] = (((uint8_t)tmp[(bit/5)]) << 3) +
+ (((uint8_t)tmp[(bit/5)+1]) >> 2);
+ break;
+ case 8:
+ dest[i] = (((uint8_t)tmp[(bit/5)]) << 6) +
+ (((uint8_t)tmp[(bit/5)+1]) << 1) +
+ (((uint8_t)tmp[(bit/5)+2]) >> 4);
+ break;
+ case 16:
+ dest[i] = (((uint8_t)tmp[(bit/5)]) << 4) +
+ (((uint8_t)tmp[(bit/5)+1]) >> 1);
+ break;
+ case 24:
+ dest[i] = (((uint8_t)tmp[(bit/5)]) << 7) +
+ (((uint8_t)tmp[(bit/5)+1]) << 2) +
+ (((uint8_t)tmp[(bit/5)+2]) >> 3);
+ break;
+ case 32:
+ dest[i] = (((uint8_t)tmp[(bit/5)]) << 5) +
+ ((uint8_t)tmp[(bit/5)+1]);
+ break;
+ }
+ }
+
+ memset(tmp, 0, srclen); /* on the heap, this should be safe */
+ tor_free(tmp);
+ tmp = NULL;
+ return 0;
+}
+
+#define BASE64_OPENSSL_LINELEN 64
+
+/** Return the Base64 encoded size of <b>srclen</b> bytes of data in
+ * bytes.
+ *
+ * (WATCH OUT: This API <em>does not</em> count the terminating NUL byte,
+ * but base32_encoded_size does.)
+ *
+ * If <b>flags</b>&amp;BASE64_ENCODE_MULTILINE is true, return the size
+ * of the encoded output as multiline output (64 character, `\n' terminated
+ * lines).
+ */
+size_t
+base64_encode_size(size_t srclen, int flags)
+{
+ size_t enclen;
+
+ /* Use INT_MAX for overflow checking because base64_encode() returns int. */
+ tor_assert(srclen < INT_MAX);
+ tor_assert(CEIL_DIV(srclen, 3) < INT_MAX / 4);
+
+ enclen = BASE64_LEN(srclen);
+ if (flags & BASE64_ENCODE_MULTILINE)
+ enclen += CEIL_DIV(enclen, BASE64_OPENSSL_LINELEN);
+
+ tor_assert(enclen < INT_MAX && (enclen == 0 || enclen > srclen));
+ return enclen;
+}
+
+/** Internal table mapping 6 bit values to the Base64 alphabet. */
+static const char base64_encode_table[64] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3',
+ '4', '5', '6', '7', '8', '9', '+', '/'
+};
+
+/** Base64 encode <b>srclen</b> bytes of data from <b>src</b>. Write
+ * the result into <b>dest</b>, if it will fit within <b>destlen</b>
+ * bytes. Return the number of bytes written on success; -1 if
+ * destlen is too short, or other failure.
+ *
+ * If <b>flags</b>&amp;BASE64_ENCODE_MULTILINE is true, return encoded
+ * output in multiline format (64 character, `\n' terminated lines).
+ */
+int
+base64_encode(char *dest, size_t destlen, const char *src, size_t srclen,
+ int flags)
+{
+ const unsigned char *usrc = (unsigned char *)src;
+ const unsigned char *eous = usrc + srclen;
+ char *d = dest;
+ uint32_t n = 0;
+ size_t linelen = 0;
+ size_t enclen;
+ int n_idx = 0;
+
+ if (!src || !dest)
+ return -1;
+
+ /* Ensure that there is sufficient space, including the NUL. */
+ enclen = base64_encode_size(srclen, flags);
+ if (destlen < enclen + 1)
+ return -1;
+ if (destlen > SIZE_T_CEILING)
+ return -1;
+ if (enclen > INT_MAX)
+ return -1;
+
+ /* Make sure we leave no uninitialized data in the destination buffer. */
+ memset(dest, 0, destlen);
+
+ /* XXX/Yawning: If this ends up being too slow, this can be sped up
+ * by separating the multiline format case and the normal case, and
+ * processing 48 bytes of input at a time when newlines are desired.
+ */
+#define ENCODE_CHAR(ch) \
+ STMT_BEGIN \
+ *d++ = ch; \
+ if (flags & BASE64_ENCODE_MULTILINE) { \
+ if (++linelen % BASE64_OPENSSL_LINELEN == 0) { \
+ linelen = 0; \
+ *d++ = '\n'; \
+ } \
+ } \
+ STMT_END
+
+#define ENCODE_N(idx) \
+ ENCODE_CHAR(base64_encode_table[(n >> ((3 - idx) * 6)) & 0x3f])
+
+#define ENCODE_PAD() ENCODE_CHAR('=')
+
+ /* Iterate over all the bytes in src. Each one will add 8 bits to the
+ * value we're encoding. Accumulate bits in <b>n</b>, and whenever we
+ * have 24 bits, batch them into 4 bytes and flush those bytes to dest.
+ */
+ for ( ; usrc < eous; ++usrc) {
+ n = (n << 8) | *usrc;
+ if ((++n_idx) == 3) {
+ ENCODE_N(0);
+ ENCODE_N(1);
+ ENCODE_N(2);
+ ENCODE_N(3);
+ n_idx = 0;
+ n = 0;
+ }
+ }
+ switch (n_idx) {
+ case 0:
+ /* 0 leftover bits, no pading to add. */
+ break;
+ case 1:
+ /* 8 leftover bits, pad to 12 bits, write the 2 6-bit values followed
+ * by 2 padding characters.
+ */
+ n <<= 4;
+ ENCODE_N(2);
+ ENCODE_N(3);
+ ENCODE_PAD();
+ ENCODE_PAD();
+ break;
+ case 2:
+ /* 16 leftover bits, pad to 18 bits, write the 3 6-bit values followed
+ * by 1 padding character.
+ */
+ n <<= 2;
+ ENCODE_N(1);
+ ENCODE_N(2);
+ ENCODE_N(3);
+ ENCODE_PAD();
+ break;
+ // LCOV_EXCL_START -- we can't reach this point, because we enforce
+ // 0 <= ncov_idx < 3 in the loop above.
+ default:
+ /* Something went catastrophically wrong. */
+ tor_fragile_assert();
+ return -1;
+ // LCOV_EXCL_STOP
+ }
+
+#undef ENCODE_N
+#undef ENCODE_PAD
+#undef ENCODE_CHAR
+
+ /* Multiline output always includes at least one newline. */
+ if (flags & BASE64_ENCODE_MULTILINE && linelen != 0)
+ *d++ = '\n';
+
+ tor_assert(d - dest == (ptrdiff_t)enclen);
+
+ *d++ = '\0'; /* NUL terminate the output. */
+
+ return (int) enclen;
+}
+
+/** As base64_encode, but do not add any internal spaces or external padding
+ * to the output stream. */
+int
+base64_encode_nopad(char *dest, size_t destlen,
+ const uint8_t *src, size_t srclen)
+{
+ int n = base64_encode(dest, destlen, (const char*) src, srclen, 0);
+ if (n <= 0)
+ return n;
+ tor_assert((size_t)n < destlen && dest[n] == 0);
+ char *in, *out;
+ in = out = dest;
+ while (*in) {
+ if (*in == '=' || *in == '\n') {
+ ++in;
+ } else {
+ *out++ = *in++;
+ }
+ }
+ *out = 0;
+
+ tor_assert(out - dest <= INT_MAX);
+
+ return (int)(out - dest);
+}
+
+#undef BASE64_OPENSSL_LINELEN
+
+/** @{ */
+/** Special values used for the base64_decode_table */
+#define X 255
+#define SP 64
+#define PAD 65
+/** @} */
+/** Internal table mapping byte values to what they represent in base64.
+ * Numbers 0..63 are 6-bit integers. SPs are spaces, and should be
+ * skipped. Xs are invalid and must not appear in base64. PAD indicates
+ * end-of-string. */
+static const uint8_t base64_decode_table[256] = {
+ X, X, X, X, X, X, X, X, X, SP, SP, SP, X, SP, X, X, /* */
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+ SP, X, X, X, X, X, X, X, X, X, X, 62, X, X, X, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, X, X, X, PAD, X, X,
+ X, 0, 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, X, X, X, X, X,
+ X, 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, X, X, X, X, X,
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+};
+
+/** Base64 decode <b>srclen</b> bytes of data from <b>src</b>. Write
+ * the result into <b>dest</b>, if it will fit within <b>destlen</b>
+ * bytes. Return the number of bytes written on success; -1 if
+ * destlen is too short, or other failure.
+ *
+ * NOTE 1: destlen is checked conservatively, as though srclen contained no
+ * spaces or padding.
+ *
+ * NOTE 2: This implementation does not check for the correct number of
+ * padding "=" characters at the end of the string, and does not check
+ * for internal padding characters.
+ */
+int
+base64_decode(char *dest, size_t destlen, const char *src, size_t srclen)
+{
+ const char *eos = src+srclen;
+ uint32_t n=0;
+ int n_idx=0;
+ size_t di = 0;
+
+ if (destlen > INT_MAX)
+ return -1;
+
+ /* Make sure we leave no uninitialized data in the destination buffer. */
+ memset(dest, 0, destlen);
+
+ /* Iterate over all the bytes in src. Each one will add 0 or 6 bits to the
+ * value we're decoding. Accumulate bits in <b>n</b>, and whenever we have
+ * 24 bits, batch them into 3 bytes and flush those bytes to dest.
+ */
+ for ( ; src < eos; ++src) {
+ unsigned char c = (unsigned char) *src;
+ uint8_t v = base64_decode_table[c];
+ switch (v) {
+ case X:
+ /* This character isn't allowed in base64. */
+ return -1;
+ case SP:
+ /* This character is whitespace, and has no effect. */
+ continue;
+ case PAD:
+ /* We've hit an = character: the data is over. */
+ goto end_of_loop;
+ default:
+ /* We have an actual 6-bit value. Append it to the bits in n. */
+ n = (n<<6) | v;
+ if ((++n_idx) == 4) {
+ /* We've accumulated 24 bits in n. Flush them. */
+ if (destlen < 3 || di > destlen - 3)
+ return -1;
+ dest[di++] = (n>>16);
+ dest[di++] = (n>>8) & 0xff;
+ dest[di++] = (n) & 0xff;
+ n_idx = 0;
+ n = 0;
+ }
+ }
+ }
+ end_of_loop:
+ /* If we have leftover bits, we need to cope. */
+ switch (n_idx) {
+ case 0:
+ default:
+ /* No leftover bits. We win. */
+ break;
+ case 1:
+ /* 6 leftover bits. That's invalid; we can't form a byte out of that. */
+ return -1;
+ case 2:
+ /* 12 leftover bits: The last 4 are padding and the first 8 are data. */
+ if (destlen < 1 || di > destlen - 1)
+ return -1;
+ dest[di++] = n >> 4;
+ break;
+ case 3:
+ /* 18 leftover bits: The last 2 are padding and the first 16 are data. */
+ if (destlen < 2 || di > destlen - 2)
+ return -1;
+ dest[di++] = n >> 10;
+ dest[di++] = n >> 2;
+ }
+
+ tor_assert(di <= destlen);
+
+ return (int)di;
+}
+#undef X
+#undef SP
+#undef PAD
+
+/** Encode the <b>srclen</b> bytes at <b>src</b> in a NUL-terminated,
+ * uppercase hexadecimal string; store it in the <b>destlen</b>-byte buffer
+ * <b>dest</b>.
+ */
+void
+base16_encode(char *dest, size_t destlen, const char *src, size_t srclen)
+{
+ const char *end;
+ char *cp;
+
+ tor_assert(srclen < SIZE_T_CEILING / 2 - 1);
+ tor_assert(destlen >= BASE16_BUFSIZE(srclen));
+ tor_assert(destlen < SIZE_T_CEILING);
+
+ /* Make sure we leave no uninitialized data in the destination buffer. */
+ memset(dest, 0, destlen);
+
+ cp = dest;
+ end = src+srclen;
+ while (src<end) {
+ *cp++ = "0123456789ABCDEF"[ (*(const uint8_t*)src) >> 4 ];
+ *cp++ = "0123456789ABCDEF"[ (*(const uint8_t*)src) & 0xf ];
+ ++src;
+ }
+ *cp = '\0';
+}
+
+/** Given a hexadecimal string of <b>srclen</b> bytes in <b>src</b>, decode
+ * it and store the result in the <b>destlen</b>-byte buffer at <b>dest</b>.
+ * Return the number of bytes decoded on success, -1 on failure. If
+ * <b>destlen</b> is greater than INT_MAX or less than half of
+ * <b>srclen</b>, -1 is returned. */
+int
+base16_decode(char *dest, size_t destlen, const char *src, size_t srclen)
+{
+ const char *end;
+ char *dest_orig = dest;
+ int v1,v2;
+
+ if ((srclen % 2) != 0)
+ return -1;
+ if (destlen < srclen/2 || destlen > INT_MAX)
+ return -1;
+
+ /* Make sure we leave no uninitialized data in the destination buffer. */
+ memset(dest, 0, destlen);
+
+ end = src+srclen;
+ while (src<end) {
+ v1 = hex_decode_digit(*src);
+ v2 = hex_decode_digit(*(src+1));
+ if (v1<0||v2<0)
+ return -1;
+ *(uint8_t*)dest = (v1<<4)|v2;
+ ++dest;
+ src+=2;
+ }
+
+ tor_assert((dest-dest_orig) <= (ptrdiff_t) destlen);
+
+ return (int) (dest-dest_orig);
+}
diff --git a/src/lib/encoding/binascii.h b/src/lib/encoding/binascii.h
new file mode 100644
index 0000000000..7e3cc04f09
--- /dev/null
+++ b/src/lib/encoding/binascii.h
@@ -0,0 +1,60 @@
+/* Copyright (c) 2001, Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file binascii.h
+ *
+ * \brief Header for binascii.c
+ **/
+
+#ifndef TOR_BINASCII_H
+#define TOR_BINASCII_H
+
+#include "orconfig.h"
+#include <stddef.h>
+#include "lib/testsupport/testsupport.h"
+#include "lib/cc/torint.h"
+
+const char *hex_str(const char *from, size_t fromlen);
+
+/** @{ */
+/** These macros don't check for overflow. Use them only for constant inputs
+ * (like array declarations). The *_LEN macros are the raw encoding lengths
+ * (without terminating NUL), while the *_BUFSIZE macros count the terminating
+ * NUL. */
+#define BASE64_LEN(n) (CEIL_DIV((n), 3) * 4)
+#define BASE32_LEN(n) (CEIL_DIV((n), 5) * 8)
+#define BASE16_LEN(n) ((n) * 2)
+
+#define BASE64_BUFSIZE(n) (BASE64_LEN(n) + 1)
+#define BASE32_BUFSIZE(n) (BASE32_LEN(n) + 1)
+#define BASE16_BUFSIZE(n) (BASE16_LEN(n) + 1)
+
+#define BASE64_NOPAD_LEN(n) (CEIL_DIV((n) * 4, 3))
+#define BASE32_NOPAD_LEN(n) (CEIL_DIV((n) * 8, 5))
+
+#define BASE64_NOPAD_BUFSIZE(n) (BASE64_NOPAD_LEN(n) + 1)
+#define BASE32_NOPAD_BUFSIZE(n) (BASE32_NOPAD_LEN(n) + 1)
+/** @} */
+
+#define BASE64_ENCODE_MULTILINE 1
+size_t base64_encode_size(size_t srclen, int flags);
+int base64_encode(char *dest, size_t destlen, const char *src, size_t srclen,
+ int flags);
+int base64_decode(char *dest, size_t destlen, const char *src, size_t srclen);
+int base64_encode_nopad(char *dest, size_t destlen,
+ const uint8_t *src, size_t srclen);
+
+/** Characters that can appear (case-insensitively) in a base32 encoding. */
+#define BASE32_CHARS "abcdefghijklmnopqrstuvwxyz234567"
+void base32_encode(char *dest, size_t destlen, const char *src, size_t srclen);
+int base32_decode(char *dest, size_t destlen, const char *src, size_t srclen);
+size_t base32_encoded_size(size_t srclen);
+
+void base16_encode(char *dest, size_t destlen, const char *src, size_t srclen);
+int base16_decode(char *dest, size_t destlen, const char *src, size_t srclen);
+
+#endif /* !defined(TOR_UTIL_FORMAT_H) */
diff --git a/src/lib/encoding/confline.c b/src/lib/encoding/confline.c
new file mode 100644
index 0000000000..8110f3dd9c
--- /dev/null
+++ b/src/lib/encoding/confline.c
@@ -0,0 +1,402 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file confline.c
+ *
+ * \brief Functions to manipulate a linked list of key-value pairs, of the
+ * type used in Tor's configuration files.
+ *
+ * Tor uses the config_line_t type and its associated serialized format for
+ * human-readable key-value pairs in many places, including its configuration,
+ * its state files, its consensus cache, and so on.
+ **/
+
+#include "lib/encoding/confline.h"
+#include "lib/encoding/cstring.h"
+#include "lib/log/log.h"
+#include "lib/log/util_bug.h"
+#include "lib/malloc/malloc.h"
+#include "lib/string/compat_ctype.h"
+#include "lib/string/compat_string.h"
+#include "lib/string/util_string.h"
+
+#include <string.h>
+
+/** Helper: allocate a new configuration option mapping 'key' to 'val',
+ * append it to *<b>lst</b>. */
+void
+config_line_append(config_line_t **lst,
+ const char *key,
+ const char *val)
+{
+ tor_assert(lst);
+
+ config_line_t *newline;
+
+ newline = tor_malloc_zero(sizeof(config_line_t));
+ newline->key = tor_strdup(key);
+ newline->value = tor_strdup(val);
+ newline->next = NULL;
+ while (*lst)
+ lst = &((*lst)->next);
+
+ (*lst) = newline;
+}
+
+/** Helper: allocate a new configuration option mapping 'key' to 'val',
+ * and prepend it to *<b>lst</b> */
+void
+config_line_prepend(config_line_t **lst,
+ const char *key,
+ const char *val)
+{
+ tor_assert(lst);
+
+ config_line_t *newline;
+
+ newline = tor_malloc_zero(sizeof(config_line_t));
+ newline->key = tor_strdup(key);
+ newline->value = tor_strdup(val);
+ newline->next = *lst;
+ *lst = newline;
+}
+
+/** Return the first line in <b>lines</b> whose key is exactly <b>key</b>, or
+ * NULL if no such key exists.
+ *
+ * (In options parsing, this is for handling commandline-only options only;
+ * other options should be looked up in the appropriate data structure.) */
+const config_line_t *
+config_line_find(const config_line_t *lines,
+ const char *key)
+{
+ const config_line_t *cl;
+ for (cl = lines; cl; cl = cl->next) {
+ if (!strcmp(cl->key, key))
+ return cl;
+ }
+ return NULL;
+}
+
+/** Auxiliary function that does all the work of config_get_lines.
+ * <b>recursion_level</b> is the count of how many nested %includes we have.
+ * <b>opened_lst</b> will have a list of opened files if provided.
+ * Returns the a pointer to the last element of the <b>result</b> in
+ * <b>last</b>. */
+int
+config_get_lines_aux(const char *string, config_line_t **result, int extended,
+ int allow_include, int *has_include,
+ struct smartlist_t *opened_lst, int recursion_level,
+ config_line_t **last,
+ include_handler_fn handle_include)
+{
+ config_line_t *list = NULL, **next, *list_last = NULL;
+ char *k, *v;
+ const char *parse_err;
+ int include_used = 0;
+
+ if (recursion_level > MAX_INCLUDE_RECURSION_LEVEL) {
+ log_warn(LD_CONFIG, "Error while parsing configuration: more than %d "
+ "nested %%includes.", MAX_INCLUDE_RECURSION_LEVEL);
+ return -1;
+ }
+
+ next = &list;
+ do {
+ k = v = NULL;
+ string = parse_config_line_from_str_verbose(string, &k, &v, &parse_err);
+ if (!string) {
+ log_warn(LD_CONFIG, "Error while parsing configuration: %s",
+ parse_err?parse_err:"<unknown>");
+ config_free_lines(list);
+ tor_free(k);
+ tor_free(v);
+ return -1;
+ }
+ if (k && v) {
+ unsigned command = CONFIG_LINE_NORMAL;
+ if (extended) {
+ if (k[0] == '+') {
+ char *k_new = tor_strdup(k+1);
+ tor_free(k);
+ k = k_new;
+ command = CONFIG_LINE_APPEND;
+ } else if (k[0] == '/') {
+ char *k_new = tor_strdup(k+1);
+ tor_free(k);
+ k = k_new;
+ tor_free(v);
+ v = tor_strdup("");
+ command = CONFIG_LINE_CLEAR;
+ }
+ }
+
+ if (allow_include && !strcmp(k, "%include") && handle_include) {
+ tor_free(k);
+ include_used = 1;
+
+ config_line_t *include_list;
+ if (handle_include(v, recursion_level, extended, &include_list,
+ &list_last, opened_lst) < 0) {
+ log_warn(LD_CONFIG, "Error reading included configuration "
+ "file or directory: \"%s\".", v);
+ config_free_lines(list);
+ tor_free(v);
+ return -1;
+ }
+ log_notice(LD_CONFIG, "Included configuration file or "
+ "directory at recursion level %d: \"%s\".",
+ recursion_level, v);
+ *next = include_list;
+ if (list_last)
+ next = &list_last->next;
+ tor_free(v);
+ } else {
+ /* This list can get long, so we keep a pointer to the end of it
+ * rather than using config_line_append over and over and getting
+ * n^2 performance. */
+ *next = tor_malloc_zero(sizeof(**next));
+ (*next)->key = k;
+ (*next)->value = v;
+ (*next)->next = NULL;
+ (*next)->command = command;
+ list_last = *next;
+ next = &((*next)->next);
+ }
+ } else {
+ tor_free(k);
+ tor_free(v);
+ }
+ } while (*string);
+
+ if (last) {
+ *last = list_last;
+ }
+ if (has_include) {
+ *has_include = include_used;
+ }
+ *result = list;
+ return 0;
+}
+
+/** Same as config_get_lines_include but does not allow %include */
+int
+config_get_lines(const char *string, config_line_t **result, int extended)
+{
+ return config_get_lines_aux(string, result, extended, 0, NULL, NULL, 1,
+ NULL, NULL);
+}
+
+/**
+ * Free all the configuration lines on the linked list <b>front</b>.
+ */
+void
+config_free_lines_(config_line_t *front)
+{
+ config_line_t *tmp;
+
+ while (front) {
+ tmp = front;
+ front = tmp->next;
+
+ tor_free(tmp->key);
+ tor_free(tmp->value);
+ tor_free(tmp);
+ }
+}
+
+/** Return a newly allocated deep copy of the lines in <b>inp</b>. */
+config_line_t *
+config_lines_dup(const config_line_t *inp)
+{
+ return config_lines_dup_and_filter(inp, NULL);
+}
+
+/** Return a newly allocated deep copy of the lines in <b>inp</b>,
+ * but only the ones whose keys begin with <b>key</b> (case-insensitive).
+ * If <b>key</b> is NULL, do not filter. */
+config_line_t *
+config_lines_dup_and_filter(const config_line_t *inp,
+ const char *key)
+{
+ config_line_t *result = NULL;
+ config_line_t **next_out = &result;
+ while (inp) {
+ if (key && strcasecmpstart(inp->key, key)) {
+ inp = inp->next;
+ continue;
+ }
+ *next_out = tor_malloc_zero(sizeof(config_line_t));
+ (*next_out)->key = tor_strdup(inp->key);
+ (*next_out)->value = tor_strdup(inp->value);
+ inp = inp->next;
+ next_out = &((*next_out)->next);
+ }
+ (*next_out) = NULL;
+ return result;
+}
+
+/** Return true iff a and b contain identical keys and values in identical
+ * order. */
+int
+config_lines_eq(config_line_t *a, config_line_t *b)
+{
+ while (a && b) {
+ if (strcasecmp(a->key, b->key) || strcmp(a->value, b->value))
+ return 0;
+ a = a->next;
+ b = b->next;
+ }
+ if (a || b)
+ return 0;
+ return 1;
+}
+
+/** Return the number of lines in <b>a</b> whose key is <b>key</b>. */
+int
+config_count_key(const config_line_t *a, const char *key)
+{
+ int n = 0;
+ while (a) {
+ if (!strcasecmp(a->key, key)) {
+ ++n;
+ }
+ a = a->next;
+ }
+ return n;
+}
+
+/** Given a string containing part of a configuration file or similar format,
+ * advance past comments and whitespace and try to parse a single line. If we
+ * parse a line successfully, set *<b>key_out</b> to a new string holding the
+ * key portion and *<b>value_out</b> to a new string holding the value portion
+ * of the line, and return a pointer to the start of the next line. If we run
+ * out of data, return a pointer to the end of the string. If we encounter an
+ * error, return NULL and set *<b>err_out</b> (if provided) to an error
+ * message.
+ */
+const char *
+parse_config_line_from_str_verbose(const char *line, char **key_out,
+ char **value_out,
+ const char **err_out)
+{
+ /*
+ See torrc_format.txt for a description of the (silly) format this parses.
+ */
+ const char *key, *val, *cp;
+ int continuation = 0;
+
+ tor_assert(key_out);
+ tor_assert(value_out);
+
+ *key_out = *value_out = NULL;
+ key = val = NULL;
+ /* Skip until the first keyword. */
+ while (1) {
+ while (TOR_ISSPACE(*line))
+ ++line;
+ if (*line == '#') {
+ while (*line && *line != '\n')
+ ++line;
+ } else {
+ break;
+ }
+ }
+
+ if (!*line) { /* End of string? */
+ *key_out = *value_out = NULL;
+ return line;
+ }
+
+ /* Skip until the next space or \ followed by newline. */
+ key = line;
+ while (*line && !TOR_ISSPACE(*line) && *line != '#' &&
+ ! (line[0] == '\\' && line[1] == '\n'))
+ ++line;
+ *key_out = tor_strndup(key, line-key);
+
+ /* Skip until the value. */
+ while (*line == ' ' || *line == '\t')
+ ++line;
+
+ val = line;
+
+ /* Find the end of the line. */
+ if (*line == '\"') { // XXX No continuation handling is done here
+ if (!(line = unescape_string(line, value_out, NULL))) {
+ if (err_out)
+ *err_out = "Invalid escape sequence in quoted string";
+ return NULL;
+ }
+ while (*line == ' ' || *line == '\t')
+ ++line;
+ if (*line == '\r' && *(++line) == '\n')
+ ++line;
+ if (*line && *line != '#' && *line != '\n') {
+ if (err_out)
+ *err_out = "Excess data after quoted string";
+ return NULL;
+ }
+ } else {
+ /* Look for the end of the line. */
+ while (*line && *line != '\n' && (*line != '#' || continuation)) {
+ if (*line == '\\' && line[1] == '\n') {
+ continuation = 1;
+ line += 2;
+ } else if (*line == '#') {
+ do {
+ ++line;
+ } while (*line && *line != '\n');
+ if (*line == '\n')
+ ++line;
+ } else {
+ ++line;
+ }
+ }
+
+ if (*line == '\n') {
+ cp = line++;
+ } else {
+ cp = line;
+ }
+ /* Now back cp up to be the last nonspace character */
+ while (cp>val && TOR_ISSPACE(*(cp-1)))
+ --cp;
+
+ tor_assert(cp >= val);
+
+ /* Now copy out and decode the value. */
+ *value_out = tor_strndup(val, cp-val);
+ if (continuation) {
+ char *v_out, *v_in;
+ v_out = v_in = *value_out;
+ while (*v_in) {
+ if (*v_in == '#') {
+ do {
+ ++v_in;
+ } while (*v_in && *v_in != '\n');
+ if (*v_in == '\n')
+ ++v_in;
+ } else if (v_in[0] == '\\' && v_in[1] == '\n') {
+ v_in += 2;
+ } else {
+ *v_out++ = *v_in++;
+ }
+ }
+ *v_out = '\0';
+ }
+ }
+
+ if (*line == '#') {
+ do {
+ ++line;
+ } while (*line && *line != '\n');
+ }
+ while (TOR_ISSPACE(*line)) ++line;
+
+ return line;
+}
diff --git a/src/lib/encoding/confline.h b/src/lib/encoding/confline.h
new file mode 100644
index 0000000000..3d9ae8a662
--- /dev/null
+++ b/src/lib/encoding/confline.h
@@ -0,0 +1,78 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file confline.h
+ *
+ * \brief Header for confline.c
+ **/
+
+#ifndef TOR_CONFLINE_H
+#define TOR_CONFLINE_H
+
+struct smartlist_t;
+
+/** Ordinary configuration line. */
+#define CONFIG_LINE_NORMAL 0
+/** Appends to previous configuration for the same option, even if we
+ * would ordinary replace it. */
+#define CONFIG_LINE_APPEND 1
+/* Removes all previous configuration for an option. */
+#define CONFIG_LINE_CLEAR 2
+
+#define MAX_INCLUDE_RECURSION_LEVEL 31
+
+/** A linked list of lines in a config file, or elsewhere */
+typedef struct config_line_t {
+ char *key;
+ char *value;
+ struct config_line_t *next;
+
+ /** What special treatment (if any) does this line require? */
+ unsigned int command:2;
+ /** If true, subsequent assignments to this linelist should replace
+ * it, not extend it. Set only on the first item in a linelist in an
+ * or_options_t. */
+ unsigned int fragile:1;
+} config_line_t;
+
+void config_line_append(config_line_t **lst,
+ const char *key, const char *val);
+void config_line_prepend(config_line_t **lst,
+ const char *key, const char *val);
+config_line_t *config_lines_dup(const config_line_t *inp);
+config_line_t *config_lines_dup_and_filter(const config_line_t *inp,
+ const char *key);
+const config_line_t *config_line_find(const config_line_t *lines,
+ const char *key);
+int config_lines_eq(config_line_t *a, config_line_t *b);
+int config_count_key(const config_line_t *a, const char *key);
+void config_free_lines_(config_line_t *front);
+#define config_free_lines(front) \
+ do { \
+ config_free_lines_(front); \
+ (front) = NULL; \
+ } while (0)
+const char *parse_config_line_from_str_verbose(const char *line,
+ char **key_out, char **value_out,
+ const char **err_out);
+
+int config_get_lines(const char *string, struct config_line_t **result,
+ int extended);
+
+typedef int (*include_handler_fn)(const char *, int, int,
+ struct config_line_t **,
+ struct config_line_t **,
+ struct smartlist_t *);
+
+int config_get_lines_aux(const char *string, struct config_line_t **result,
+ int extended,
+ int allow_include, int *has_include,
+ struct smartlist_t *opened_lst, int recursion_level,
+ config_line_t **last,
+ include_handler_fn handle_include);
+
+#endif /* !defined(TOR_CONFLINE_H) */
diff --git a/src/lib/encoding/cstring.c b/src/lib/encoding/cstring.c
new file mode 100644
index 0000000000..29d3714126
--- /dev/null
+++ b/src/lib/encoding/cstring.c
@@ -0,0 +1,138 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file cstring.c
+ *
+ * \brief Decode data that has been written as a C literal.
+ **/
+
+#include "lib/encoding/cstring.h"
+#include "lib/log/log.h"
+#include "lib/log/util_bug.h"
+#include "lib/malloc/malloc.h"
+#include "lib/string/compat_ctype.h"
+
+#include <string.h>
+
+#define TOR_ISODIGIT(c) ('0' <= (c) && (c) <= '7')
+
+/** Given a c-style double-quoted escaped string in <b>s</b>, extract and
+ * decode its contents into a newly allocated string. On success, assign this
+ * string to *<b>result</b>, assign its length to <b>size_out</b> (if
+ * provided), and return a pointer to the position in <b>s</b> immediately
+ * after the string. On failure, return NULL.
+ */
+const char *
+unescape_string(const char *s, char **result, size_t *size_out)
+{
+ const char *cp;
+ char *out;
+ if (s[0] != '\"')
+ return NULL;
+ cp = s+1;
+ while (1) {
+ switch (*cp) {
+ case '\0':
+ case '\n':
+ return NULL;
+ case '\"':
+ goto end_of_loop;
+ case '\\':
+ if (cp[1] == 'x' || cp[1] == 'X') {
+ if (!(TOR_ISXDIGIT(cp[2]) && TOR_ISXDIGIT(cp[3])))
+ return NULL;
+ cp += 4;
+ } else if (TOR_ISODIGIT(cp[1])) {
+ cp += 2;
+ if (TOR_ISODIGIT(*cp)) ++cp;
+ if (TOR_ISODIGIT(*cp)) ++cp;
+ } else if (cp[1] == 'n' || cp[1] == 'r' || cp[1] == 't' || cp[1] == '"'
+ || cp[1] == '\\' || cp[1] == '\'') {
+ cp += 2;
+ } else {
+ return NULL;
+ }
+ break;
+ default:
+ ++cp;
+ break;
+ }
+ }
+ end_of_loop:
+ out = *result = tor_malloc(cp-s + 1);
+ cp = s+1;
+ while (1) {
+ switch (*cp)
+ {
+ case '\"':
+ *out = '\0';
+ if (size_out) *size_out = out - *result;
+ return cp+1;
+
+ /* LCOV_EXCL_START -- we caught this in parse_config_from_line. */
+ case '\0':
+ tor_fragile_assert();
+ tor_free(*result);
+ return NULL;
+ /* LCOV_EXCL_STOP */
+ case '\\':
+ switch (cp[1])
+ {
+ case 'n': *out++ = '\n'; cp += 2; break;
+ case 'r': *out++ = '\r'; cp += 2; break;
+ case 't': *out++ = '\t'; cp += 2; break;
+ case 'x': case 'X':
+ {
+ int x1, x2;
+
+ x1 = hex_decode_digit(cp[2]);
+ x2 = hex_decode_digit(cp[3]);
+ if (x1 == -1 || x2 == -1) {
+ /* LCOV_EXCL_START */
+ /* we caught this above in the initial loop. */
+ tor_assert_nonfatal_unreached();
+ tor_free(*result);
+ return NULL;
+ /* LCOV_EXCL_STOP */
+ }
+
+ *out++ = ((x1<<4) + x2);
+ cp += 4;
+ }
+ break;
+ case '0': case '1': case '2': case '3': case '4': case '5':
+ case '6': case '7':
+ {
+ int n = cp[1]-'0';
+ cp += 2;
+ if (TOR_ISODIGIT(*cp)) { n = n*8 + *cp-'0'; cp++; }
+ if (TOR_ISODIGIT(*cp)) { n = n*8 + *cp-'0'; cp++; }
+ if (n > 255) { tor_free(*result); return NULL; }
+ *out++ = (char)n;
+ }
+ break;
+ case '\'':
+ case '\"':
+ case '\\':
+ case '\?':
+ *out++ = cp[1];
+ cp += 2;
+ break;
+
+ /* LCOV_EXCL_START */
+ default:
+ /* we caught this above in the initial loop. */
+ tor_assert_nonfatal_unreached();
+ tor_free(*result); return NULL;
+ /* LCOV_EXCL_STOP */
+ }
+ break;
+ default:
+ *out++ = *cp++;
+ }
+ }
+}
diff --git a/src/lib/encoding/cstring.h b/src/lib/encoding/cstring.h
new file mode 100644
index 0000000000..904a2c9c1c
--- /dev/null
+++ b/src/lib/encoding/cstring.h
@@ -0,0 +1,19 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file cstring.h
+ *
+ * \brief Header for cstring.c
+ **/
+
+#ifndef TOR_CSTRING_H
+#define TOR_CSTRING_H
+
+#include <stddef.h>
+const char *unescape_string(const char *s, char **result, size_t *size_out);
+
+#endif /* !defined(TOR_CSTRING_H) */
diff --git a/src/lib/encoding/include.am b/src/lib/encoding/include.am
new file mode 100644
index 0000000000..2d2aa3988a
--- /dev/null
+++ b/src/lib/encoding/include.am
@@ -0,0 +1,26 @@
+noinst_LIBRARIES += src/lib/libtor-encoding.a
+
+if UNITTESTS_ENABLED
+noinst_LIBRARIES += src/lib/libtor-encoding-testing.a
+endif
+
+src_lib_libtor_encoding_a_SOURCES = \
+ src/lib/encoding/binascii.c \
+ src/lib/encoding/confline.c \
+ src/lib/encoding/cstring.c \
+ src/lib/encoding/keyval.c \
+ src/lib/encoding/pem.c \
+ src/lib/encoding/time_fmt.c
+
+src_lib_libtor_encoding_testing_a_SOURCES = \
+ $(src_lib_libtor_encoding_a_SOURCES)
+src_lib_libtor_encoding_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS)
+src_lib_libtor_encoding_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
+
+noinst_HEADERS += \
+ src/lib/encoding/binascii.h \
+ src/lib/encoding/confline.h \
+ src/lib/encoding/cstring.h \
+ src/lib/encoding/keyval.h \
+ src/lib/encoding/pem.h \
+ src/lib/encoding/time_fmt.h
diff --git a/src/lib/encoding/keyval.c b/src/lib/encoding/keyval.c
new file mode 100644
index 0000000000..c5da5a0bfc
--- /dev/null
+++ b/src/lib/encoding/keyval.c
@@ -0,0 +1,52 @@
+/* Copyright (c) 2003, Roger Dingledine
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file keyval.c
+ *
+ * \brief Handle data encoded as a key=value pair.
+ **/
+
+#include "orconfig.h"
+#include "lib/encoding/keyval.h"
+#include "lib/log/escape.h"
+#include "lib/log/log.h"
+#include "lib/log/util_bug.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+/** Return true if <b>string</b> is a valid 'key=[value]' string.
+ * "value" is optional, to indicate the empty string. Log at logging
+ * <b>severity</b> if something ugly happens. */
+int
+string_is_key_value(int severity, const char *string)
+{
+ /* position of equal sign in string */
+ const char *equal_sign_pos = NULL;
+
+ tor_assert(string);
+
+ if (strlen(string) < 2) { /* "x=" is shortest args string */
+ tor_log(severity, LD_GENERAL, "'%s' is too short to be a k=v value.",
+ escaped(string));
+ return 0;
+ }
+
+ equal_sign_pos = strchr(string, '=');
+ if (!equal_sign_pos) {
+ tor_log(severity, LD_GENERAL, "'%s' is not a k=v value.", escaped(string));
+ return 0;
+ }
+
+ /* validate that the '=' is not in the beginning of the string. */
+ if (equal_sign_pos == string) {
+ tor_log(severity, LD_GENERAL, "'%s' is not a valid k=v value.",
+ escaped(string));
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/src/lib/encoding/keyval.h b/src/lib/encoding/keyval.h
new file mode 100644
index 0000000000..cd327b7a82
--- /dev/null
+++ b/src/lib/encoding/keyval.h
@@ -0,0 +1,17 @@
+/* Copyright (c) 2003, Roger Dingledine
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file keyval.h
+ *
+ * \brief Header for keyval.c
+ **/
+
+#ifndef TOR_KEYVAL_H
+#define TOR_KEYVAL_H
+
+int string_is_key_value(int severity, const char *string);
+
+#endif
diff --git a/src/lib/encoding/pem.c b/src/lib/encoding/pem.c
new file mode 100644
index 0000000000..24b238b130
--- /dev/null
+++ b/src/lib/encoding/pem.c
@@ -0,0 +1,106 @@
+/* Copyright (c) 2001, Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file pem.c
+ *
+ * \brief Implement a trivial version of PEM encoding, for use with NSS.
+ *
+ * We deliberately do not support any encryption here.
+ **/
+
+#include "orconfig.h"
+
+#include "lib/encoding/pem.h"
+
+#include "lib/ctime/di_ops.h"
+#include "lib/encoding/binascii.h"
+#include "lib/log/util_bug.h"
+#include "lib/malloc/malloc.h"
+#include "lib/string/printf.h"
+#include "lib/string/util_string.h"
+
+#include <string.h>
+
+/**
+ * Return the length of a <b>src_len</b>-byte object when tagged with
+ * <b>objtype</b> and PEM-encoded. Includes terminating NUL.
+ */
+size_t
+pem_encoded_size(size_t src_len, const char *objtype)
+{
+ return
+ strlen("-----BEGIN -----\n") +
+ strlen("-----END -----\n") +
+ strlen(objtype) * 2 +
+ base64_encode_size(src_len, BASE64_ENCODE_MULTILINE)
+ + 1;
+}
+
+/**
+ * PEM-encode the <b>srclen</b>-byte object at <b>src</b> into the
+ * <b>destlen<\b>-byte buffer at <b>dest</b>, tagging it with <b>objtype</b>.
+ * Return 0 on success and -1 on failure.
+ */
+int
+pem_encode(char *dest, size_t destlen, const uint8_t *src, size_t srclen,
+ const char *objtype)
+{
+ if (tor_snprintf(dest, destlen, "-----BEGIN %s-----\n", objtype) < 0)
+ return -1;
+
+ size_t offset = strlen(dest);
+
+ int n = base64_encode(dest + offset, destlen - offset,
+ (const char *)src, srclen, BASE64_ENCODE_MULTILINE);
+ if (n < 0)
+ return -1;
+ offset += n;
+ if (BUG(offset > destlen))
+ return -1;
+
+ if (tor_snprintf(dest + offset, destlen - offset,
+ "-----END %s-----\n", objtype) < 0)
+ return -1;
+
+ tor_assert(strlen(dest) + 1 <= pem_encoded_size(srclen, objtype));
+ return 0;
+}
+
+/**
+ * Given a PEM-encoded block of size <b>srclen</b> in <b>src</b>, if it has
+ * object type <b>objtype</b>, decode it into the <b>destlen</b>-byte buffer
+ * at <b>dest</b>. Return the number of characters decoded on success, or -1
+ * on failure.
+ */
+int
+pem_decode(uint8_t *dest, size_t destlen, const char *src, size_t srclen,
+ const char *objtype)
+{
+ const char *eos = src + srclen;
+
+ src = eat_whitespace_eos(src, eos);
+
+ char *tag = NULL;
+ tor_asprintf(&tag, "-----BEGIN %s-----\n", objtype);
+ if ((size_t)(eos-src) < strlen(tag) || fast_memneq(src, tag, strlen(tag))) {
+ tor_free(tag);
+ return -1;
+ }
+ src += strlen(tag);
+ tor_free(tag);
+
+ // NOTE lack of trailing \n. We do not enforce its presence.
+ tor_asprintf(&tag, "\n-----END %s-----", objtype);
+ const char *end_of_base64 = tor_memstr(src, eos-src, tag);
+ tor_free(tag);
+ if (end_of_base64 == NULL)
+ return -1;
+
+ /* Should we actually allow extra stuff at the end? */
+
+ return base64_decode((char*)dest, destlen, src, end_of_base64-src);
+}
diff --git a/src/lib/encoding/pem.h b/src/lib/encoding/pem.h
new file mode 100644
index 0000000000..0bbb06a794
--- /dev/null
+++ b/src/lib/encoding/pem.h
@@ -0,0 +1,26 @@
+/* Copyright (c) 2001, Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file pem.h
+ *
+ * \brief Header for pem.c
+ **/
+
+#ifndef TOR_PEM_H
+#define TOR_PEM_H
+
+#include "orconfig.h"
+#include <stddef.h>
+#include "lib/cc/torint.h"
+
+size_t pem_encoded_size(size_t src_len, const char *objtype);
+int pem_encode(char *dest, size_t destlen, const uint8_t *src, size_t srclen,
+ const char *objtype);
+int pem_decode(uint8_t *dest, size_t destlen, const char *src, size_t srclen,
+ const char *objtype);
+
+#endif
diff --git a/src/lib/encoding/time_fmt.c b/src/lib/encoding/time_fmt.c
new file mode 100644
index 0000000000..40543d41e0
--- /dev/null
+++ b/src/lib/encoding/time_fmt.c
@@ -0,0 +1,516 @@
+/* Copyright (c) 2001, Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file time_fmt.c
+ *
+ * \brief Encode and decode time in various formats.
+ *
+ * This module is higher-level than the conversion functions in "wallclock",
+ * and handles a larger variety of types. It converts between different time
+ * formats, and encodes and decodes them from strings.
+ **/
+
+#include "lib/encoding/time_fmt.h"
+#include "lib/log/log.h"
+#include "lib/log/escape.h"
+#include "lib/log/util_bug.h"
+#include "lib/malloc/malloc.h"
+#include "lib/string/printf.h"
+#include "lib/string/scanf.h"
+#include "lib/wallclock/time_to_tm.h"
+
+#include <string.h>
+#include <time.h>
+
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+
+#ifdef _WIN32
+/* For struct timeval */
+#include <winsock2.h>
+#endif
+
+/** As localtime_r, but defined for platforms that don't have it:
+ *
+ * Convert *<b>timep</b> to a struct tm in local time, and store the value in
+ * *<b>result</b>. Return the result on success, or NULL on failure.
+ *
+ * Treat malformatted inputs localtime outputs as a BUG.
+ */
+struct tm *
+tor_localtime_r(const time_t *timep, struct tm *result)
+{
+ char *err = NULL;
+ struct tm *r = tor_localtime_r_msg(timep, result, &err);
+ if (err) {
+ log_warn(LD_BUG, "%s", err);
+ tor_free(err);
+ }
+ return r;
+}
+
+/** As gmtime_r, but defined for platforms that don't have it:
+ *
+ * Convert *<b>timep</b> to a struct tm in UTC, and store the value in
+ * *<b>result</b>. Return the result on success, or NULL on failure.
+ *
+ * Treat malformatted inputs or gmtime outputs as a BUG.
+ */
+struct tm *
+tor_gmtime_r(const time_t *timep, struct tm *result)
+{
+ char *err = NULL;
+ struct tm *r = tor_gmtime_r_msg(timep, result, &err);
+ if (err) {
+ log_warn(LD_BUG, "%s", err);
+ tor_free(err);
+ }
+ return r;
+}
+
+/** Yield true iff <b>y</b> is a leap-year. */
+#define IS_LEAPYEAR(y) (!(y % 4) && ((y % 100) || !(y % 400)))
+/** Helper: Return the number of leap-days between Jan 1, y1 and Jan 1, y2. */
+static int
+n_leapdays(int year1, int year2)
+{
+ --year1;
+ --year2;
+ return (year2/4 - year1/4) - (year2/100 - year1/100)
+ + (year2/400 - year1/400);
+}
+/** Number of days per month in non-leap year; used by tor_timegm and
+ * parse_rfc1123_time. */
+static const int days_per_month[] =
+ { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+/** Compute a time_t given a struct tm. The result is given in UTC, and
+ * does not account for leap seconds. Return 0 on success, -1 on failure.
+ */
+int
+tor_timegm(const struct tm *tm, time_t *time_out)
+{
+ /* This is a pretty ironclad timegm implementation, snarfed from Python2.2.
+ * It's way more brute-force than fiddling with tzset().
+ *
+ * We use int64_t rather than time_t to avoid overflow on multiplication on
+ * platforms with 32-bit time_t. Since year is clipped to INT32_MAX, and
+ * since 365 * 24 * 60 * 60 is approximately 31 million, it's not possible
+ * for INT32_MAX years to overflow int64_t when converted to seconds. */
+ int64_t year, days, hours, minutes, seconds;
+ int i, invalid_year, dpm;
+
+ /* Initialize time_out to 0 for now, to avoid bad usage in case this function
+ fails and the caller ignores the return value. */
+ tor_assert(time_out);
+ *time_out = 0;
+
+ /* avoid int overflow on addition */
+ if (tm->tm_year < INT32_MAX-1900) {
+ year = tm->tm_year + 1900;
+ } else {
+ /* clamp year */
+ year = INT32_MAX;
+ }
+ invalid_year = (year < 1970 || tm->tm_year >= INT32_MAX-1900);
+
+ if (tm->tm_mon >= 0 && tm->tm_mon <= 11) {
+ dpm = days_per_month[tm->tm_mon];
+ if (tm->tm_mon == 1 && !invalid_year && IS_LEAPYEAR(tm->tm_year)) {
+ dpm = 29;
+ }
+ } else {
+ /* invalid month - default to 0 days per month */
+ dpm = 0;
+ }
+
+ if (invalid_year ||
+ tm->tm_mon < 0 || tm->tm_mon > 11 ||
+ tm->tm_mday < 1 || tm->tm_mday > dpm ||
+ tm->tm_hour < 0 || tm->tm_hour > 23 ||
+ tm->tm_min < 0 || tm->tm_min > 59 ||
+ tm->tm_sec < 0 || tm->tm_sec > 60) {
+ log_warn(LD_BUG, "Out-of-range argument to tor_timegm");
+ return -1;
+ }
+ days = 365 * (year-1970) + n_leapdays(1970,(int)year);
+ for (i = 0; i < tm->tm_mon; ++i)
+ days += days_per_month[i];
+ if (tm->tm_mon > 1 && IS_LEAPYEAR(year))
+ ++days;
+ days += tm->tm_mday - 1;
+ hours = days*24 + tm->tm_hour;
+
+ minutes = hours*60 + tm->tm_min;
+ seconds = minutes*60 + tm->tm_sec;
+ /* Check that "seconds" will fit in a time_t. On platforms where time_t is
+ * 32-bit, this check will fail for dates in and after 2038.
+ *
+ * We already know that "seconds" can't be negative because "year" >= 1970 */
+#if SIZEOF_TIME_T < 8
+ if (seconds < TIME_MIN || seconds > TIME_MAX) {
+ log_warn(LD_BUG, "Result does not fit in tor_timegm");
+ return -1;
+ }
+#endif /* SIZEOF_TIME_T < 8 */
+ *time_out = (time_t)seconds;
+ return 0;
+}
+
+/* strftime is locale-specific, so we need to replace those parts */
+
+/** A c-locale array of 3-letter names of weekdays, starting with Sun. */
+static const char *WEEKDAY_NAMES[] =
+ { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+/** A c-locale array of 3-letter names of months, starting with Jan. */
+static const char *MONTH_NAMES[] =
+ { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+/** Set <b>buf</b> to the RFC1123 encoding of the UTC value of <b>t</b>.
+ * The buffer must be at least RFC1123_TIME_LEN+1 bytes long.
+ *
+ * (RFC1123 format is "Fri, 29 Sep 2006 15:54:20 GMT". Note the "GMT"
+ * rather than "UTC".)
+ */
+void
+format_rfc1123_time(char *buf, time_t t)
+{
+ struct tm tm;
+
+ tor_gmtime_r(&t, &tm);
+
+ strftime(buf, RFC1123_TIME_LEN+1, "___, %d ___ %Y %H:%M:%S GMT", &tm);
+ tor_assert(tm.tm_wday >= 0);
+ tor_assert(tm.tm_wday <= 6);
+ memcpy(buf, WEEKDAY_NAMES[tm.tm_wday], 3);
+ tor_assert(tm.tm_mon >= 0);
+ tor_assert(tm.tm_mon <= 11);
+ memcpy(buf+8, MONTH_NAMES[tm.tm_mon], 3);
+}
+
+/** Parse the (a subset of) the RFC1123 encoding of some time (in UTC) from
+ * <b>buf</b>, and store the result in *<b>t</b>.
+ *
+ * Note that we only accept the subset generated by format_rfc1123_time above,
+ * not the full range of formats suggested by RFC 1123.
+ *
+ * Return 0 on success, -1 on failure.
+*/
+int
+parse_rfc1123_time(const char *buf, time_t *t)
+{
+ struct tm tm;
+ char month[4];
+ char weekday[4];
+ int i, m, invalid_year;
+ unsigned tm_mday, tm_year, tm_hour, tm_min, tm_sec;
+ unsigned dpm;
+
+ if (strlen(buf) != RFC1123_TIME_LEN)
+ return -1;
+ memset(&tm, 0, sizeof(tm));
+ if (tor_sscanf(buf, "%3s, %2u %3s %u %2u:%2u:%2u GMT", weekday,
+ &tm_mday, month, &tm_year, &tm_hour,
+ &tm_min, &tm_sec) < 7) {
+ char *esc = esc_for_log(buf);
+ log_warn(LD_GENERAL, "Got invalid RFC1123 time %s", esc);
+ tor_free(esc);
+ return -1;
+ }
+
+ m = -1;
+ for (i = 0; i < 12; ++i) {
+ if (!strcmp(month, MONTH_NAMES[i])) {
+ m = i;
+ break;
+ }
+ }
+ if (m<0) {
+ char *esc = esc_for_log(buf);
+ log_warn(LD_GENERAL, "Got invalid RFC1123 time %s: No such month", esc);
+ tor_free(esc);
+ return -1;
+ }
+ tm.tm_mon = m;
+
+ invalid_year = (tm_year >= INT32_MAX || tm_year < 1970);
+ tor_assert(m >= 0 && m <= 11);
+ dpm = days_per_month[m];
+ if (m == 1 && !invalid_year && IS_LEAPYEAR(tm_year)) {
+ dpm = 29;
+ }
+
+ if (invalid_year || tm_mday < 1 || tm_mday > dpm ||
+ tm_hour > 23 || tm_min > 59 || tm_sec > 60) {
+ char *esc = esc_for_log(buf);
+ log_warn(LD_GENERAL, "Got invalid RFC1123 time %s", esc);
+ tor_free(esc);
+ return -1;
+ }
+ tm.tm_mday = (int)tm_mday;
+ tm.tm_year = (int)tm_year;
+ tm.tm_hour = (int)tm_hour;
+ tm.tm_min = (int)tm_min;
+ tm.tm_sec = (int)tm_sec;
+
+ if (tm.tm_year < 1970) {
+ /* LCOV_EXCL_START
+ * XXXX I think this is dead code; we already checked for
+ * invalid_year above. */
+ tor_assert_nonfatal_unreached();
+ char *esc = esc_for_log(buf);
+ log_warn(LD_GENERAL,
+ "Got invalid RFC1123 time %s. (Before 1970)", esc);
+ tor_free(esc);
+ return -1;
+ /* LCOV_EXCL_STOP */
+ }
+ tm.tm_year -= 1900;
+
+ return tor_timegm(&tm, t);
+}
+
+/** Set <b>buf</b> to the ISO8601 encoding of the local value of <b>t</b>.
+ * The buffer must be at least ISO_TIME_LEN+1 bytes long.
+ *
+ * (ISO8601 format is 2006-10-29 10:57:20)
+ */
+void
+format_local_iso_time(char *buf, time_t t)
+{
+ struct tm tm;
+ strftime(buf, ISO_TIME_LEN+1, "%Y-%m-%d %H:%M:%S", tor_localtime_r(&t, &tm));
+}
+
+/** Set <b>buf</b> to the ISO8601 encoding of the GMT value of <b>t</b>.
+ * The buffer must be at least ISO_TIME_LEN+1 bytes long.
+ */
+void
+format_iso_time(char *buf, time_t t)
+{
+ struct tm tm;
+ strftime(buf, ISO_TIME_LEN+1, "%Y-%m-%d %H:%M:%S", tor_gmtime_r(&t, &tm));
+}
+
+/** As format_local_iso_time, but use the yyyy-mm-ddThh:mm:ss format to avoid
+ * embedding an internal space. */
+void
+format_local_iso_time_nospace(char *buf, time_t t)
+{
+ format_local_iso_time(buf, t);
+ buf[10] = 'T';
+}
+
+/** As format_iso_time, but use the yyyy-mm-ddThh:mm:ss format to avoid
+ * embedding an internal space. */
+void
+format_iso_time_nospace(char *buf, time_t t)
+{
+ format_iso_time(buf, t);
+ buf[10] = 'T';
+}
+
+/** As format_iso_time_nospace, but include microseconds in decimal
+ * fixed-point format. Requires that buf be at least ISO_TIME_USEC_LEN+1
+ * bytes long. */
+void
+format_iso_time_nospace_usec(char *buf, const struct timeval *tv)
+{
+ tor_assert(tv);
+ format_iso_time_nospace(buf, (time_t)tv->tv_sec);
+ tor_snprintf(buf+ISO_TIME_LEN, 8, ".%06d", (int)tv->tv_usec);
+}
+
+/** Given an ISO-formatted UTC time value (after the epoch) in <b>cp</b>,
+ * parse it and store its value in *<b>t</b>. Return 0 on success, -1 on
+ * failure. Ignore extraneous stuff in <b>cp</b> after the end of the time
+ * string, unless <b>strict</b> is set. If <b>nospace</b> is set,
+ * expect the YYYY-MM-DDTHH:MM:SS format. */
+int
+parse_iso_time_(const char *cp, time_t *t, int strict, int nospace)
+{
+ struct tm st_tm;
+ unsigned int year=0, month=0, day=0, hour=0, minute=0, second=0;
+ int n_fields;
+ char extra_char, separator_char;
+ n_fields = tor_sscanf(cp, "%u-%2u-%2u%c%2u:%2u:%2u%c",
+ &year, &month, &day,
+ &separator_char,
+ &hour, &minute, &second, &extra_char);
+ if (strict ? (n_fields != 7) : (n_fields < 7)) {
+ char *esc = esc_for_log(cp);
+ log_warn(LD_GENERAL, "ISO time %s was unparseable", esc);
+ tor_free(esc);
+ return -1;
+ }
+ if (separator_char != (nospace ? 'T' : ' ')) {
+ char *esc = esc_for_log(cp);
+ log_warn(LD_GENERAL, "ISO time %s was unparseable", esc);
+ tor_free(esc);
+ return -1;
+ }
+ if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31 ||
+ hour > 23 || minute > 59 || second > 60 || year >= INT32_MAX) {
+ char *esc = esc_for_log(cp);
+ log_warn(LD_GENERAL, "ISO time %s was nonsensical", esc);
+ tor_free(esc);
+ return -1;
+ }
+ st_tm.tm_year = (int)year-1900;
+ st_tm.tm_mon = month-1;
+ st_tm.tm_mday = day;
+ st_tm.tm_hour = hour;
+ st_tm.tm_min = minute;
+ st_tm.tm_sec = second;
+ st_tm.tm_wday = 0; /* Should be ignored. */
+
+ if (st_tm.tm_year < 70) {
+ /* LCOV_EXCL_START
+ * XXXX I think this is dead code; we already checked for
+ * year < 1970 above. */
+ tor_assert_nonfatal_unreached();
+ char *esc = esc_for_log(cp);
+ log_warn(LD_GENERAL, "Got invalid ISO time %s. (Before 1970)", esc);
+ tor_free(esc);
+ return -1;
+ /* LCOV_EXCL_STOP */
+ }
+ return tor_timegm(&st_tm, t);
+}
+
+/** Given an ISO-formatted UTC time value (after the epoch) in <b>cp</b>,
+ * parse it and store its value in *<b>t</b>. Return 0 on success, -1 on
+ * failure. Reject the string if any characters are present after the time.
+ */
+int
+parse_iso_time(const char *cp, time_t *t)
+{
+ return parse_iso_time_(cp, t, 1, 0);
+}
+
+/**
+ * As parse_iso_time, but parses a time encoded by format_iso_time_nospace().
+ */
+int
+parse_iso_time_nospace(const char *cp, time_t *t)
+{
+ return parse_iso_time_(cp, t, 1, 1);
+}
+
+/** Given a <b>date</b> in one of the three formats allowed by HTTP (ugh),
+ * parse it into <b>tm</b>. Return 0 on success, negative on failure. */
+int
+parse_http_time(const char *date, struct tm *tm)
+{
+ const char *cp;
+ char month[4];
+ char wkday[4];
+ int i;
+ unsigned tm_mday, tm_year, tm_hour, tm_min, tm_sec;
+
+ tor_assert(tm);
+ memset(tm, 0, sizeof(*tm));
+
+ /* First, try RFC1123 or RFC850 format: skip the weekday. */
+ if ((cp = strchr(date, ','))) {
+ ++cp;
+ if (*cp != ' ')
+ return -1;
+ ++cp;
+ if (tor_sscanf(cp, "%2u %3s %4u %2u:%2u:%2u GMT",
+ &tm_mday, month, &tm_year,
+ &tm_hour, &tm_min, &tm_sec) == 6) {
+ /* rfc1123-date */
+ tm_year -= 1900;
+ } else if (tor_sscanf(cp, "%2u-%3s-%2u %2u:%2u:%2u GMT",
+ &tm_mday, month, &tm_year,
+ &tm_hour, &tm_min, &tm_sec) == 6) {
+ /* rfc850-date */
+ } else {
+ return -1;
+ }
+ } else {
+ /* No comma; possibly asctime() format. */
+ if (tor_sscanf(date, "%3s %3s %2u %2u:%2u:%2u %4u",
+ wkday, month, &tm_mday,
+ &tm_hour, &tm_min, &tm_sec, &tm_year) == 7) {
+ tm_year -= 1900;
+ } else {
+ return -1;
+ }
+ }
+ tm->tm_mday = (int)tm_mday;
+ tm->tm_year = (int)tm_year;
+ tm->tm_hour = (int)tm_hour;
+ tm->tm_min = (int)tm_min;
+ tm->tm_sec = (int)tm_sec;
+ tm->tm_wday = 0; /* Leave this unset. */
+
+ month[3] = '\0';
+ /* Okay, now decode the month. */
+ /* set tm->tm_mon to dummy value so the check below fails. */
+ tm->tm_mon = -1;
+ for (i = 0; i < 12; ++i) {
+ if (!strcasecmp(MONTH_NAMES[i], month)) {
+ tm->tm_mon = i;
+ }
+ }
+
+ if (tm->tm_year < 0 ||
+ tm->tm_mon < 0 || tm->tm_mon > 11 ||
+ tm->tm_mday < 1 || tm->tm_mday > 31 ||
+ tm->tm_hour < 0 || tm->tm_hour > 23 ||
+ tm->tm_min < 0 || tm->tm_min > 59 ||
+ tm->tm_sec < 0 || tm->tm_sec > 60)
+ return -1; /* Out of range, or bad month. */
+
+ return 0;
+}
+
+/** Given an <b>interval</b> in seconds, try to write it to the
+ * <b>out_len</b>-byte buffer in <b>out</b> in a human-readable form.
+ * Returns a non-negative integer on success, -1 on failure.
+ */
+int
+format_time_interval(char *out, size_t out_len, long interval)
+{
+ /* We only report seconds if there's no hours. */
+ long sec = 0, min = 0, hour = 0, day = 0;
+
+ /* -LONG_MIN is LONG_MAX + 1, which causes signed overflow */
+ if (interval < -LONG_MAX)
+ interval = LONG_MAX;
+ else if (interval < 0)
+ interval = -interval;
+
+ if (interval >= 86400) {
+ day = interval / 86400;
+ interval %= 86400;
+ }
+ if (interval >= 3600) {
+ hour = interval / 3600;
+ interval %= 3600;
+ }
+ if (interval >= 60) {
+ min = interval / 60;
+ interval %= 60;
+ }
+ sec = interval;
+
+ if (day) {
+ return tor_snprintf(out, out_len, "%ld days, %ld hours, %ld minutes",
+ day, hour, min);
+ } else if (hour) {
+ return tor_snprintf(out, out_len, "%ld hours, %ld minutes", hour, min);
+ } else if (min) {
+ return tor_snprintf(out, out_len, "%ld minutes, %ld seconds", min, sec);
+ } else {
+ return tor_snprintf(out, out_len, "%ld seconds", sec);
+ }
+}
diff --git a/src/lib/encoding/time_fmt.h b/src/lib/encoding/time_fmt.h
new file mode 100644
index 0000000000..0ddeca57fc
--- /dev/null
+++ b/src/lib/encoding/time_fmt.h
@@ -0,0 +1,44 @@
+/* Copyright (c) 2001, Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file time_fmt.h
+ *
+ * \brief Header for time_fmt.c
+ **/
+
+#ifndef TOR_TIME_FMT_H
+#define TOR_TIME_FMT_H
+
+#include "orconfig.h"
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+struct tm;
+struct timeval;
+
+struct tm *tor_localtime_r(const time_t *timep, struct tm *result);
+struct tm *tor_gmtime_r(const time_t *timep, struct tm *result);
+int tor_timegm(const struct tm *tm, time_t *time_out);
+
+#define RFC1123_TIME_LEN 29
+void format_rfc1123_time(char *buf, time_t t);
+int parse_rfc1123_time(const char *buf, time_t *t);
+#define ISO_TIME_LEN 19
+#define ISO_TIME_USEC_LEN (ISO_TIME_LEN+7)
+void format_local_iso_time(char *buf, time_t t);
+void format_iso_time(char *buf, time_t t);
+void format_local_iso_time_nospace(char *buf, time_t t);
+void format_iso_time_nospace(char *buf, time_t t);
+void format_iso_time_nospace_usec(char *buf, const struct timeval *tv);
+int parse_iso_time_(const char *cp, time_t *t, int strict, int nospace);
+int parse_iso_time(const char *buf, time_t *t);
+int parse_iso_time_nospace(const char *cp, time_t *t);
+int parse_http_time(const char *buf, struct tm *tm);
+int format_time_interval(char *out, size_t out_len, long interval);
+
+#endif