/*
 * FastCGI protocol processing
 *
 * Copyright (C) 2019 HAProxy Technologies, Christopher Faulet <cfaulet@haproxy.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include <haproxy/buf.h>
#include <haproxy/fcgi.h>
#include <haproxy/istbuf.h>

/* Encodes header of a FCGI record into the chunk <out>. It returns non-zero on
 * success and 0 on failure (buffer full). <out> is a chunk, so the wrapping is
 * not handled by this function. It is the caller responsibility to ensure
 * enough contiguous space is available
 */
int fcgi_encode_record_hdr(struct buffer *out, const struct fcgi_header *h)
{
	size_t len = out->data;

	if (len + 8 >= b_size(out))
		return 0;

	out->area[len++] = h->vsn;
	out->area[len++] = h->type;
	out->area[len++] = ((h->id >> 8) & 0xff);
	out->area[len++] = (h->id & 0xff);
	out->area[len++] = ((h->len >> 8) & 0xff);
	out->area[len++] = (h->len & 0xff);
	out->area[len++] = h->padding;
	out->area[len++] = 0; /* rsv */

	out->data = len;
	return 1;
}

/* Decodes a FCGI record header from offset <o> of buffer <in> into descriptor
 * <h>. The buffer may wrap so each byte read must be checked. The header is
 * formed like this :
 *
 *     b0    b1     b2    b3    b4      b5     b6      b7
 *  +-----+------+-----+-----+------+------+--------+-----+
 *  | vsn | type | id1 | id0 | len1 | len0 | padlen | rsv |
 *  +-----+------+-----+-----+------+------+--------+-----+
 *
 * Returns zero if some bytes are missing, otherwise the number of read bytes.
 */
size_t fcgi_decode_record_hdr(const struct buffer *in, size_t o, struct fcgi_header *h)
{
	if (b_data(in) < o + 8)
		return 0;

	h->vsn     = (uint8_t)(*b_peek(in, o));
	h->type    = (uint8_t)(*b_peek(in, o+1));
	h->id      = ((uint8_t)(*b_peek(in, o+2)) << 8) + (uint8_t)(*b_peek(in, o+3));
	h->len     = ((uint8_t)(*b_peek(in, o+4)) << 8) + (uint8_t)(*b_peek(in, o+5));
	h->padding = (uint8_t)(*b_peek(in, o+6));
	/* ignore rsv */

	return 8;
}

/* Encodes the payload part of a BEGIN_REQUEST record into the chunk <out>. It
 * returns non-zero on success and 0 on failure (buffer full). <out> is a chunk,
 * so the wrapping is not handled by this function. It is the caller
 * responsibility to ensure enough contiguous space is available
 */
int fcgi_encode_begin_request(struct buffer *out, const struct fcgi_begin_request *r)
{
	size_t len = out->data;

	if (len + 8 >= b_size(out))
		return 0;

	out->area[len++] = ((r->role >> 8) & 0xff);
	out->area[len++] = (r->role & 0xff);
	out->area[len++] = r->flags;
	out->area[len++] = 0; /* rsv */
	out->area[len++] = 0;
	out->area[len++] = 0;
	out->area[len++] = 0;
	out->area[len++] = 0;

	out->data = len;
	return 1;
}

/* Encodes a parameter, part of the payload of a PARAM record, into the chunk
 * <out>. It returns non-zero on success and 0 on failure (buffer full). <out>
 * is a chunk, so the wrapping is not handled by this function. It is the caller
 * responsibility to ensure enough contiguous space is available. The
 * parameter's name is converted to upper case and non-alphanumeric character
 * are replaced by an underscore.
 */
int fcgi_encode_param(struct buffer *out, const struct fcgi_param *p)
{
	size_t off, len = out->data;
	int nbytes, vbytes;

	nbytes = (!(p->n.len >> 7) ? 1 : 4);
	vbytes = (!(p->v.len >> 7) ? 1 : 4);
	if ((len + nbytes + p->n.len + vbytes + p->v.len) >= b_size(out))
		return 0;

	if (nbytes == 1)
		out->area[len++] = (p->n.len & 0xff);
	else {
		out->area[len++] = (((p->n.len >> 24) & 0xff) | 0x80);
		out->area[len++] = ((p->n.len >> 16) & 0xff);
		out->area[len++] = ((p->n.len >> 8) & 0xff);
		out->area[len++] = (p->n.len & 0xff);
	}

	if (vbytes == 1)
		out->area[len++] = (p->v.len & 0xff);
	else {
		out->area[len++] = (((p->v.len >> 24) & 0xff) | 0x80);
		out->area[len++] = ((p->v.len >> 16) & 0xff);
		out->area[len++] = ((p->v.len >> 8) & 0xff);
		out->area[len++] = (p->v.len & 0xff);
	}

	for (off = 0; off < p->n.len; off++) {
		if (isalnum((unsigned char)p->n.ptr[off]))
			out->area[len++] = ist_uc[(unsigned char)p->n.ptr[off]];
		else
			out->area[len++] = '_';
	}
	if (p->v.len) {
		ist2bin(out->area + len, p->v);
		len += p->v.len;
	}

	out->data = len;
	return 1;
}

/* Decodes a parameter of a PARAM record from offset <o> of buffer <in> into the
 * FCGI param <p>. The buffer may wrap so each byte read must be checked.
 * Returns zero if some bytes are missing, otherwise the number of read bytes.
 */
size_t fcgi_decode_param(const struct buffer *in, size_t o, struct fcgi_param *p)
{
	size_t data = b_data(in);
	size_t nlen, vlen, len = 0;
	uint8_t b0, b1, b2, b3;

	if (data < o + 1)
		return 0;
	b0 = *b_peek(in, o++);
	if (!(b0 >> 7)) {
		nlen = b0;
		len++;
	}
	else {
		if (data < o + 3)
			return 0;
		b1 = *b_peek(in, o++);
		b2 = *b_peek(in, o++);
		b3 = *b_peek(in, o++);
		nlen = ((b0 & 0x7f) << 24) + (b1 << 16) + (b2 << 8) + b3;
		len += 4;
	}

	if (data < o + 1)
		return 0;
	b0 = *b_peek(in, o++);
	if (!(b0 >> 7)) {
		vlen = b0;
		len++;
	}
	else {
		if (data < o + 3)
			return 0;
		b1 = *b_peek(in, o++);
		b2 = *b_peek(in, o++);
		b3 = *b_peek(in, o++);
		vlen = ((b0 & 0x7f) << 24) + (b1 << 16) + (b2 << 8) + b3;
		len += 4;
	}

	if (data < o + nlen + vlen)
		return 0;

	p->n = ist2(b_peek(in, o), nlen);
	p->v = ist2(b_peek(in, o + nlen), vlen);
	len += nlen + vlen;

	return len;
}


/* Decodes a parameter of a PARAM record from offset <o> of buffer <in> into the
 * FCGI param <p>. To call this function, the buffer must not wrap. Returns zero
 * if some bytes are missing, otherwise the number of read bytes.
 */
size_t fcgi_aligned_decode_param(const struct buffer *in, size_t o, struct fcgi_param *p)
{
	size_t data = b_data(in);
	size_t nlen, vlen, len = 0;
	uint8_t b0, b1, b2, b3;

	if (data < o + 1)
		return 0;
	b0 = in->area[o++];
	if (!(b0 >> 7)) {
		nlen = b0;
		len++;
	}
	else {
		if (data < o + 3)
			return 0;
		b1 = in->area[o++];
		b2 = in->area[o++];
		b3 = in->area[o++];
		nlen = ((b0 & 0x7f) << 24) + (b1 << 16) + (b2 << 8) + b3;
		len += 4;
	}

	if (data < o + 1)
		return 0;
	b0 = in->area[o++];
	if (!(b0 >> 7)) {
		vlen = b0;
		len++;
	}
	else {
		if (data < o + 3)
			return 0;
		b1 = in->area[o++];
		b2 = in->area[o++];
		b3 = in->area[o++];
		vlen = ((b0 & 0x7f) << 24) + (b1 << 16) + (b2 << 8) + b3;
		len += 4;
	}

	if (data < o + nlen + vlen)
		return 0;

	p->n = ist2(in->area + o, nlen);
	p->v = ist2(in->area + o + nlen, vlen);
	len += nlen + vlen;

	return len;
}

/* Decodes payload of a END_REQUEST record from offset <o> of buffer <in> into
 * the FCGI param <p>. The buffer may wrap so each byte read must be
 * checked. Returns zero if some bytes are missing, otherwise the number of read
 * bytes.
 */
size_t fcgi_decode_end_request(const struct buffer *in, size_t o, struct fcgi_end_request *rec)
{
	uint8_t b0, b1, b2, b3;

	if (b_data(in) < o + 8)
		return 0;

	b0 = *b_peek(in, o++);
	b1 = *b_peek(in, o++);
	b2 = *b_peek(in, o++);
	b3 = *b_peek(in, o++);
	rec->status = ((b0 & 0x7f) << 24) + (b1 << 16) + (b2 << 8) + b3;
	rec->errcode = *b_peek(in, o++);
        o += 3; /* ignore rsv */

	return 8;
}

/*
 * Local variables:
 *  c-indent-level: 8
 *  c-basic-offset: 8
 * End:
 */
