App-OpenHAP

 view release on metacpan or  search on metacpan

lib/Protocol/HAP/HTTP.pm  view on Meta::CPAN

# ex:ts=8 sw=4:
# $OpenBSD$
#
# Copyright (c) 2026 Dick Olsson <hi@senzilla.io>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

use v5.36;

package Protocol::HAP::HTTP;
our $VERSION = '0.1.0';

# Protocol::HAP::HTTP - one HTTP/1.1 codec, for both ends of a connection.
#
# The module is functions over strings. It opens no socket, holds no
# connection state, and never logs. A server calls parse_request and
# build_response; a client calls build_request and parse_response.
#
# message_complete is the framing. A stream socket gives a reader
# whatever arrived, which is not a message: a request can span two
# reads, and two requests can share one. A reader that parses each
# read as a whole message drops the second request and mangles the
# split one. The caller keeps a buffer and asks this function how much
# of it is a message.
#
# The module knows nothing about any protocol built on HTTP. A status
# code that belongs to one application, and the header defaults that
# one application wants, are arguments.

# The bound on a message that a caller does not set for itself. The
# value is generous for a request and small enough that an unpaired
# client cannot make a server hold megabytes.
use constant DEFAULT_MAX_SIZE => 65536;

# The status texts of the codes that RFC 9110 defines. A caller with a
# code of its own passes the text.
my %STATUS_TEXT = (
	200 => 'OK',
	201 => 'Created',
	204 => 'No Content',
	207 => 'Multi-Status',
	304 => 'Not Modified',
	400 => 'Bad Request',
	401 => 'Unauthorized',
	403 => 'Forbidden',
	404 => 'Not Found',
	405 => 'Method Not Allowed',
	408 => 'Request Timeout',
	413 => 'Content Too Large',
	500 => 'Internal Server Error',
	503 => 'Service Unavailable',
);

# message_complete($buffer, %args):
#	Report how many bytes at the front of $buffer form one whole
#	message.
#
#	%args:
#		max_size => $bytes	refuse a message larger than this
#
#	The function returns the length of the message, 0 when more
#	bytes are necessary, and undef when the message is over the
#	limit. A caller that gets undef closes the connection: the peer
#	is either broken or hostile, and no further byte can make the
#	message valid.
sub message_complete ( $buffer, %args )
{
	my $max_size = $args{max_size} // DEFAULT_MAX_SIZE;

	my $end = index $buffer, "\r\n\r\n";
	if ( $end < 0 ) {

		# The header block alone is already over the limit, so
		# no body can make it fit
		return if length($buffer) > $max_size;
		return 0;
	}

	my $head_length = $end + 4;
	my $head        = substr $buffer, 0, $end;

	# The trailing \r is part of the line terminator, and $ under /m
	# matches before the \n only. A class that omits it makes the
	# header match only when it is the last one in the block.
	my ($length) = $head =~ /^Content-Length:[ \t]*(\d+)[ \t\r]*$/mi;
	$length //= 0;

	my $total = $head_length + $length;
	return   if $total > $max_size;
	return 0 if length($buffer) < $total;

	return $total;
}

# parse_request($data):
#	Parse one request message. The function returns a hashref with
#	method, path, version, headers and body. Header names are
#	lowercase, because a peer chooses their case and a caller must
#	not.
#
#	The function returns undef when the request line is not a
#	request line.
sub parse_request ($data)
{
	my ( $head, $body ) = _split_message($data);
	my @lines = split /\r?\n/, $head;

	my $request_line = shift @lines // '';



( run in 3.754 seconds using v1.01-cache-2.11-cpan-85d3896f969 )