text: /*-
   * Copyright 2015-2017 UPLEX - Nils Goroll Systemoptimierung
   * All rights reserved.
   *
   * Authors: Nils Goroll <nils.goroll@uplex.de>
   *          Geoffrey Simmons <geoffrey.simmons@uplex.de>
   *
   * SPDX-License-Identifier: BSD-2-Clause
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   * 1. Redistributions of source code must retain the above copyright notice,
   *    this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright notice,
   *    this list of conditions and the following disclaimer in the documentation
   *    and/or other materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
   * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *
   */
  
  #include "config.h"
  #include <string.h>
  
  #include "cache/cache.h"
  
  #include "vcc_blob_if.h"
  #include "vmod_blob.h"
  
  #define VMOD_BLOB_TYPE 0xfade4faa
  
  struct vmod_blob_blob {
  	unsigned magic;
  #define VMOD_BLOB_MAGIC 0xfade4fa9
  	struct vrt_blob blob;
  	void *freeptr;
  	char *encoding[__MAX_ENCODING][2];
  	pthread_mutex_t lock;
  };
  
  #define B64_FUNCS				   \
  		.decode_l	= base64_decode_l, \
  		.decode		= base64_decode,   \
  		.encode		= base64_encode
  
  static const struct vmod_blob_fptr {
  	len_f		*const decode_l;
  	decode_f	*const decode;
  	len_f		*const encode_l;
  	encode_f	*const encode;
  } func[__MAX_ENCODING] = {
  	[IDENTITY] = {
  		.decode_l	= id_decode_l,
  		.decode		= id_decode,
  		.encode_l	= id_encode_l,
  		.encode		= id_encode
  	},
  	[BASE64] = {
  		B64_FUNCS,
  		.encode_l	= base64_encode_l
  	},
  	[BASE64URL] = {
  		B64_FUNCS,
  		.encode_l	= base64_encode_l
  	},
  	[BASE64URLNOPAD] = {
  		B64_FUNCS,
  		.encode_l	= base64nopad_encode_l
  	},
  	[HEX] = {
  		.decode_l	= hex_decode_l,
  		.decode		= hex_decode,
  		.encode_l	= hex_encode_l,
  		.encode		= hex_encode
  	},
  	[URL] = {
  		.decode_l	= url_decode_l,
  		.decode		= url_decode,
  		.encode_l	= url_encode_l,
  		.encode		= url_encode
  	},
  };
  
  #undef B64_FUNCS
  
  #define ERR(ctx, msg) \
  	VRT_fail((ctx), "vmod blob error: " msg)
  
  #define VERR(ctx, fmt, ...) \
  	VRT_fail((ctx), "vmod blob error: " fmt, __VA_ARGS__)
  
  #define ERRINVAL(ctx, enc) \
  	VERR((ctx), "cannot decode, illegal encoding beginning with \"%s\"", \
  	     (enc))
  
  #define VERRNOMEM(ctx, fmt, ...) \
  	VERR((ctx), fmt ", out of space", __VA_ARGS__)
  
  #define ERRNOMEM(ctx, msg) \
  	ERR((ctx), msg ", out of space")
  
  static char empty[1] = { '\0' };
  
  static const struct vrt_blob null_blob[1] = {{
  #define VMOD_BLOB_NULL_TYPE 0xfade4fa0
  	.type = VMOD_BLOB_NULL_TYPE,
  	.len = 0,
  	.blob = empty,
  }};
  
  static enum encoding
  parse_encoding(VCL_ENUM e)
  {
  #define VMODENUM(n) if (e == VENUM(n)) return (n);
  #include "tbl_encodings.h"
  	WRONG("illegal encoding enum");
  }
  
  static enum case_e
  parse_case(VCL_ENUM e)
  {
  #define VMODENUM(n) if (e == VENUM(n)) return (n);
  #include "tbl_case.h"
  	WRONG("illegal case enum");
  }
  
  
  static inline size_t
  decode_l(enum encoding dec, VCL_STRANDS s)
  {
  	size_t len = 0;
  
  	AENC(dec);
  
  	for (int i = 0; i < s->n; i++)
  		if (s->p[i] != NULL && *s->p[i] != '\0')
  			len += strlen(s->p[i]);
  
  	return (func[dec].decode_l(len));
  }
  
  static void
  err_decode(VRT_CTX, const char *enc)
  {
  	switch (errno) {
  	case EINVAL:
  		ERRINVAL(ctx, enc);
  		break;
  	case ENOMEM:
  		ERRNOMEM(ctx, "cannot decode");
  		break;
  	default:
  		WRONG("invalid errno");
  	}
  }
  
  static inline int
  encodes_hex(enum encoding enc)
  {
  	return (enc == HEX || enc == URL);
  }
  
  /* Require case DEFAULT for all encodings besides HEX and URL. */
  
  static inline int
  check_enc_case(VRT_CTX, VCL_ENUM encs, VCL_ENUM case_s, enum encoding enc,
      enum case_e kase)
  {
  	if (!encodes_hex(enc) && kase != DEFAULT) {
  		VERR(ctx, "case %s is illegal with encoding %s", case_s, encs);
  		return (0);
  	}
  	return (1);
  }
  
  /* Objects */
  
  VCL_VOID v_matchproto_(td_blob_blob__init)
  vmod_blob__init(VRT_CTX, struct vmod_blob_blob **blobp, const char *vcl_name,
      VCL_ENUM decs, VCL_STRANDS strings)
  {
  	struct vmod_blob_blob *b;
  	enum encoding dec = parse_encoding(decs);
  	void *buf;
  	ssize_t len;
  
  	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
  	AN(blobp);
  	AZ(*blobp);
  	AN(vcl_name);
  	AENC(dec);
  	AN(strings);
  
  	ALLOC_OBJ(b, VMOD_BLOB_MAGIC);
  	AN(b);
  	*blobp = b;
  	AZ(pthread_mutex_init(&b->lock, NULL));
  
  	b->blob.type = VMOD_BLOB_TYPE;
  
  	len = decode_l(dec, strings);
  	if (len == 0)
  		return;
  
  	assert(len > 0);
  
  	buf = malloc(len);
  	if (buf == NULL) {
  		VERRNOMEM(ctx, "cannot create blob %s", vcl_name);
  		return;
  	}
  
  	errno = 0;
  	len = func[dec].decode(dec, buf, len, -1, strings);
  
  	if (len == -1) {
  		assert(errno == EINVAL);
  		free(buf);
  		VERR(ctx, "cannot create blob %s, illegal encoding beginning "
  		    "with \"%s\"", vcl_name, strings->p[0]);
  		return;
  	}
  	if (len == 0) {
  		free(buf);
  		memcpy(&b->blob, null_blob, sizeof b->blob);
  		return;
  	}
  	b->blob.len = len;
  	b->blob.blob = b->freeptr = buf;
  }
  
  VCL_BLOB v_matchproto_(td_blob_blob_get)
  vmod_blob_get(VRT_CTX, struct vmod_blob_blob *b)
  {
  	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
  	CHECK_OBJ_NOTNULL(b, VMOD_BLOB_MAGIC);
  	return (&b->blob);
  }
  
  VCL_STRING v_matchproto_(td_blob_blob_encode)
  vmod_blob_encode(VRT_CTX, struct vmod_blob_blob *b, VCL_ENUM encs,
      VCL_ENUM case_s)
  {
  	enum encoding enc = parse_encoding(encs);
  	AENC(enc);
  	enum case_e kase = parse_case(case_s);
  
  	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
  	CHECK_OBJ_NOTNULL(b, VMOD_BLOB_MAGIC);
  
  	if (!check_enc_case(ctx, encs, case_s, enc, kase))
  		return (NULL);
  	if (b->blob.len == 0)
  		return ("");
  	if (kase == DEFAULT)
  		kase = LOWER;
  
  	if (b->encoding[enc][kase] == NULL) {
  		AZ(pthread_mutex_lock(&b->lock));
  		if (b->encoding[enc][kase] == NULL) {
  			ssize_t len = func[enc].encode_l(b->blob.len);
  
  			assert(len >= 0);
  			if (len == 0)
  				b->encoding[enc][kase] = empty;
  			else {
  				b->encoding[enc][kase] = malloc(len);
  				if (b->encoding[enc][kase] == NULL)
  					ERRNOMEM(ctx, "cannot encode");
  				else {
  					char *s = b->encoding[enc][kase];
  					len =
  						func[enc].encode(
  							enc, kase, s, len,
  							b->blob.blob,
  							b->blob.len);
  					assert(len >= 0);
  					if (len == 0) {
  						free(s);
  						b->encoding[enc][kase] = empty;
  					}
  					else
  						s[len] = '\0';
  				}
  			}
  		}
  		AZ(pthread_mutex_unlock(&b->lock));
  	}
  	return (b->encoding[enc][kase]);
  }
  
  VCL_VOID v_matchproto_(td_blob_blob__fini)
  vmod_blob__fini(struct vmod_blob_blob **blobp)
  {
  	struct vmod_blob_blob *b;
  	char *s;
  	int i, j;
  
  	TAKE_OBJ_NOTNULL(b, blobp, VMOD_BLOB_MAGIC);
  
  	if (b->freeptr != NULL) {
  		free(b->freeptr);
  		b->blob.blob = NULL;
  	}
  
  	for (i = 0; i < __MAX_ENCODING; i++)
  		for (j = 0; j < 2; j++) {
  			s = b->encoding[i][j];
  			if (s != NULL && s != empty) {
  				free(s);
  				b->encoding[i][j] = NULL;
  			}
  		}
  
  	AZ(pthread_mutex_destroy(&b->lock));
  	FREE_OBJ(b);
  }
  
  /* Functions */
  
  VCL_BLOB v_matchproto_(td_blob_decode)
  vmod_decode(VRT_CTX, VCL_ENUM decs, VCL_INT length, VCL_STRANDS strings)
  {
  	enum encoding dec = parse_encoding(decs);
  	char *buf;
  	ssize_t len;
  	unsigned space;
  
  	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
  	AENC(dec);
  	AN(strings);
  	CHECK_OBJ_NOTNULL(ctx->ws, WS_MAGIC);
  
  	buf = WS_Front(ctx->ws);
  	space = WS_ReserveAll(ctx->ws);
  
  	if (length <= 0)
  		length = -1;
  	errno = 0;
  	len = func[dec].decode(dec, buf, space, length, strings);
  
  	if (len == -1) {
  		err_decode(ctx, strings->p[0]);
  		WS_Release(ctx->ws, 0);
  		return (NULL);
  	}
  	if (len == 0) {
  		WS_Release(ctx->ws, 0);
  		return (null_blob);
  	}
  	WS_Release(ctx->ws, len);
  
  	assert(len > 0);
  
  	return (VRT_blob(ctx, "blob.decode", buf, len, VMOD_BLOB_TYPE));
  }
  
  static VCL_STRING
  encode(VRT_CTX, enum encoding enc, enum case_e kase, VCL_BLOB b)
  {
  	ssize_t len;
  	char *buf;
  	uintptr_t snap;
  	unsigned space;
  
  	AENC(enc);
  
  	if (b == NULL)
  		return (NULL);
  
  	CHECK_OBJ_NOTNULL(ctx->ws, WS_MAGIC);
  	snap = WS_Snapshot(ctx->ws);
  	buf = WS_Front(ctx->ws);
  	space = WS_ReserveAll(ctx->ws);
  
  	len = func[enc].encode(enc, kase, buf, space, b->blob, b->len);
  
  	if (len == -1) {
  		ERRNOMEM(ctx, "cannot encode");
  		WS_Release(ctx->ws, 0);
  		WS_Reset(ctx->ws, snap);
  		return (NULL);
  	}
  	if (len == 0) {
  		WS_Release(ctx->ws, 0);
  		WS_Reset(ctx->ws, snap);
  		return ("");
  	}
  	buf[len] = '\0';
  	WS_Release(ctx->ws, len + 1);
  	return (buf);
  }
  
  VCL_STRING v_matchproto_(td_blob_encode)
  vmod_encode(VRT_CTX, VCL_ENUM encs, VCL_ENUM case_s, VCL_BLOB b)
  {
  	enum encoding enc = parse_encoding(encs);
  	enum case_e kase = parse_case(case_s);
  
  	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
  	if (!check_enc_case(ctx, encs, case_s, enc, kase))
  		return (NULL);
  	return (encode(ctx, enc, kase, b));
  }
  
  VCL_STRING v_matchproto_(td_blob_transcode)
  vmod_transcode(VRT_CTX, VCL_ENUM decs, VCL_ENUM encs, VCL_ENUM case_s,
  	       VCL_INT length, VCL_STRANDS strings)
  {
  	enum encoding dec = parse_encoding(decs);
  	enum encoding enc = parse_encoding(encs);
  	enum case_e kase = parse_case(case_s);
  	struct vrt_blob b;
  	VCL_STRING r;
  	size_t l;
  	ssize_t len;
  
  	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
  	CHECK_OBJ_NOTNULL(ctx->ws, WS_MAGIC);
  	AN(strings);
  
  	AENC(dec);
  	AENC(enc);
  
  	if (!check_enc_case(ctx, encs, case_s, enc, kase))
  		return (NULL);
  
  	/*
  	 * Allocate space for the decoded blob on the stack
  	 * ignoring the limitation imposed by n
  	 */
  	l = decode_l(dec, strings);
  	if (l == 0)
  		return ("");
  
  	/* XXX: handle stack overflow? */
  	char buf[l];
  
  	if (length <= 0)
  		length = -1;
  	errno = 0;
  	len = func[dec].decode(dec, buf, l, length, strings);
  
  	if (len < 0) {
  		err_decode(ctx, strings->p[0]);
  		return (NULL);
  	}
  
  	b.len = len;
  	b.blob = buf;
  
  	/*
  	 * If the encoding and decoding are the same, and the decoding was
  	 * legal, just return the concatenated string.
  	 * For encodings with hex digits, we cannot assume the same result.
  	 * since the call may specify upper- or lower-case that differs
  	 * from the encoded string.
  	 */
  	if (length == -1 && enc == dec && !encodes_hex(enc))
  		/*
  		 * Returns NULL and invokes VCL failure on workspace
  		 * overflow. If there is only one string already in the
  		 * workspace, then it is re-used.
  		 */
  		return (VRT_CollectStrands(ctx, strings));
  
  	r = encode(ctx, enc, kase, &b);
  	return (r);
  }
  
  VCL_BOOL v_matchproto_(td_blob_same)
  vmod_same(VRT_CTX, VCL_BLOB b1, VCL_BLOB b2)
  {
  
  	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
  	if (b1 == b2)
  		return (1);
  	if (b1 == NULL || b2 == NULL)
  		return (0);
  	return (b1->len == b2->len && b1->blob == b2->blob);
  }
  
  VCL_BOOL v_matchproto_(td_blob_equal)
  vmod_equal(VRT_CTX, VCL_BLOB b1, VCL_BLOB b2)
  {
  
  	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
  	if (b1 == b2)
  		return (1);
  	if (b1 == NULL || b2 == NULL)
  		return (0);
  	if (b1->len != b2->len)
  		return (0);
  	if (b1->blob == b2->blob)
  		return (1);
  	if (b1->blob == NULL || b2->blob == NULL)
  		return (0);
  	return (memcmp(b1->blob, b2->blob, b1->len) == 0);
  }
  
  VCL_INT v_matchproto_(td_blob_length)
  vmod_length(VRT_CTX, VCL_BLOB b)
  {
  
  	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
  	if (b == NULL)
  		return (0);
  	return (b->len);
  }
  
  VCL_BLOB v_matchproto_(td_blob_sub)
  vmod_sub(VRT_CTX, VCL_BLOB b, VCL_BYTES n, VCL_BYTES off)
  {
  
  	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
  	assert(n >= 0);
  	assert(off >= 0);
  
  	if (b == NULL || b->len == 0 || b->blob == NULL) {
  		ERR(ctx, "blob is empty in blob.sub()");
  		return (NULL);
  	}
  
  	assert(b->len > 0);
  
  	// XXX check for > SIZE_MAX ?
  	if (off < 0 || n < 0) {
  		ERR(ctx, "size or offset negative in blob.sub()");
  		return (NULL);
  	}
  
  	if ((size_t)off > b->len || (size_t)n > b->len ||
  	    (size_t)off + (size_t)n > b->len) {
  		VERR(ctx, "size %jd from offset %jd requires more bytes than "
  		    "blob length %zd in blob.sub()",
  		    (intmax_t)n, (intmax_t)off, b->len);
  		return (NULL);
  	}
  
  	if (n == 0)
  		return (null_blob);
  
  
  	return (VRT_blob(ctx, "blob.sub",
  	    (const char *)b->blob + off, n, b->type));
  }
  