Livt packages
Livt.Web
Version 1.0.0
Bounded HTTP routing, request parsing, response encoding, and Ethernet application composition for Livt.
Published files (63)
- HttpAllowedMethods.lvt
- HttpApplication.lvt
- HttpApplicationState.lvt
- HttpContentType.lvt
- HttpHeaderName.lvt
- HttpMatchResult.lvt
- HttpMethod.lvt
- HttpNetworkEndpoint.lvt
- HttpNetworkResponse.lvt
- HttpPath.lvt
- HttpPreparation.lvt
- HttpRequestCursor.lvt
- HttpRequestHeaders.lvt
- HttpRequestLine.lvt
- HttpRequestParser.lvt
- HttpRequestResult.lvt
- HttpRequestSyntax.lvt
- HttpResponse.lvt
- HttpResponseEncoder.lvt
- HttpResponseSyntax.lvt
- HttpRouteFallback.lvt
- HttpRouteMethods.lvt
- HttpRouter.lvt
- HttpServer.lvt
- HttpStatus.lvt
- HttpTcpFrame.lvt
- HttpTcpSequence.lvt
- HttpTcpState.lvt
- HttpTcpTransport.lvt
- HttpTransportPayload.lvt
- HttpTransportState.lvt
- HttpVersion.lvt
- IHttpApplication.lvt
- IHttpHandler.lvt
- IHttpRequest.lvt
- IHttpResponse.lvt
- IHttpRoute.lvt
- IHttpRouteMatcher.lvt
- Route.lvt
- RouteChain.lvt
- RouteDispatch.lvt
- RouteSelection.lvt
- StaticContent.lvt
src/http/HttpRequestHeaders.lvt
namespace Livt.Web.Http
using Livt.Net
/**
* Internal header scanner with bounded offset/length metadata, no payload copy.
*/
component HttpRequestHeaders<S: IPacketData, MAX_BYTES: int, MAX_HEADERS: int>
{
static assert MAX_HEADERS > 0, "Header count bound must be positive"
// Lowercase header-name literals used by the bounded classifier.
const HOST[4] = [0x68, 0x6F, 0x73, 0x74]
const LENGTH[14] = [0x63, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2D, 0x6C, 0x65, 0x6E, 0x67, 0x74, 0x68]
const TRANSFER[17] = [0x74, 0x72, 0x61, 0x6E, 0x73, 0x66, 0x65, 0x72, 0x2D, 0x65, 0x6E, 0x63, 0x6F, 0x64, 0x69, 0x6E, 0x67]
const EXPECT[6] = [0x65, 0x78, 0x70, 0x65, 0x63, 0x74]
// Include the end-of-input position as well as every valid byte offset.
const RANGE_BITS: int = Bits.RequiredFor(MAX_BYTES)
// Borrowed cursor positioned immediately after the request line.
cursor: HttpRequestCursor<S, MAX_BYTES>
// Owned bounded descriptors; offsets refer to borrowed source bytes.
nameOffsets: logic[MAX_HEADERS, RANGE_BITS]
nameLengths: logic[MAX_HEADERS, RANGE_BITS]
valueOffsets: logic[MAX_HEADERS, RANGE_BITS]
valueLengths: logic[MAX_HEADERS, RANGE_BITS]
kinds: HttpHeaderName[MAX_HEADERS]
count: int
// Framing and duplicate-field state for the current parse.
hostSeen: bool
lengthSeen: bool
transferSeen: bool
expectSeen: bool
bodyPresent: bool
/**
* Borrows an exclusive cursor; Parse replaces the previous header descriptors.
*/
new(cursor: HttpRequestCursor<S, MAX_BYTES>)
{
this.cursor = cursor
}
/**
* Resets header descriptors and consumes fields through the empty CRLF line.
* Ready means valid syntax; IsSupported separately checks body/framing policy.
*/
public fn Parse() HttpRequestResult
{
count = 0
hostSeen = false
lengthSeen = false
transferSeen = false
expectSeen = false
bodyPresent = false
var value: byte = 0x00
while (true) {
if (!cursor.Next(value)) {
return cursor.GetResult()
}
if (value == 0x0D) {
if (!cursor.Next(value)) {
return cursor.GetResult()
}
if (value != 0x0A) {
return HttpRequestResult.Malformed
}
return HttpRequestResult.Ready
}
if (count >= MAX_HEADERS) {
return HttpRequestResult.TooLarge
}
var result = this.ParseField(value)
if (result != HttpRequestResult.Ready) {
return result
}
result = this.CheckField()
if (result != HttpRequestResult.Ready) {
return result
}
count = count + 1
}
}
/**
* Consumes a field whose first byte is already read; records name and trimmed
* value ranges in slot count without copying source bytes.
*/
fn ParseField(first: byte) HttpRequestResult
{
var value = first
var nameOffset = cursor.GetPosition() - 1
var nameLength = 0
while (value != 0x3A) {
if (!HttpRequestSyntax.IsToken(value)) {
return HttpRequestResult.Malformed
}
nameLength = nameLength + 1
if (!cursor.Next(value)) {
return cursor.GetResult()
}
}
if (nameLength == 0) {
return HttpRequestResult.Malformed
}
nameOffsets[count] = (nameOffset as uint) as logic[RANGE_BITS]
nameLengths[count] = (nameLength as uint) as logic[RANGE_BITS]
var start = cursor.GetPosition()
var end = start
var contentStarted = false
while (true) {
if (!cursor.Next(value)) {
return cursor.GetResult()
}
if (value == 0x0D) {
break
}
if ((value < 0x20 && value != 0x09) || value == 0x7F) {
return HttpRequestResult.Malformed
}
if (!HttpRequestSyntax.IsWhitespace(value)) {
if (!contentStarted) {
start = cursor.GetPosition() - 1
}
contentStarted = true
end = cursor.GetPosition()
}
}
if (!cursor.Next(value)) {
return cursor.GetResult()
}
if (value != 0x0A) {
return HttpRequestResult.Malformed
}
valueOffsets[count] = (start as uint) as logic[RANGE_BITS]
valueLengths[count] = ((end - start) as uint) as logic[RANGE_BITS]
return HttpRequestResult.Ready
}
/**
* Compares the current field name case-insensitively against supported names.
*/
fn Classify() HttpHeaderName
{
var length = (nameLengths[count] as uint) as int
var offset = (nameOffsets[count] as uint) as int
var kind = HttpHeaderName.Other
if (length == 4) {
kind = HttpHeaderName.Host
}
elif (length == 14) {
kind = HttpHeaderName.ContentLength
}
elif (length == 17) {
kind = HttpHeaderName.TransferEncoding
}
elif (length == 6) {
kind = HttpHeaderName.Expect
}
else {
return kind
}
for (var i = 0; i < length; i++) {
var value: byte = 0x00
if (!cursor.ReadAt(offset + i, value)) {
return HttpHeaderName.Other
}
// Separate table selection avoids duplicate generated states (compiler #542).
var expected = this.GetExpectedNameByte(kind, i)
var normalized = HttpRequestSyntax.Lower(value)
if (normalized != expected) {
return HttpHeaderName.Other
}
}
return kind
}
/**
* Selects a fixed spelling; callers supply its validated kind and byte bound.
*/
fn GetExpectedNameByte(kind: HttpHeaderName, index: int) byte
{
if (kind == HttpHeaderName.Host) {
return HOST[index]
}
if (kind == HttpHeaderName.ContentLength) {
return LENGTH[index]
}
if (kind == HttpHeaderName.TransferEncoding) {
return TRANSFER[index]
}
return EXPECT[index]
}
/**
* Checks duplicate/framing rules for the current field and records policy flags.
*/
fn CheckField() HttpRequestResult
{
var kind = this.Classify()
var valueOffset = (valueOffsets[count] as uint) as int
var valueLength = (valueLengths[count] as uint) as int
if (cursor.GetResult() != HttpRequestResult.Ready) {
return cursor.GetResult()
}
kinds[count] = kind
if (kind == HttpHeaderName.Host) {
if (hostSeen || valueLength == 0) {
return HttpRequestResult.Malformed
}
hostSeen = true
return this.CheckHost()
}
if (kind == HttpHeaderName.ContentLength) {
if (lengthSeen || valueLength == 0) {
return HttpRequestResult.Malformed
}
lengthSeen = true
for (var i = 0; i < valueLength; i++) {
var value: byte = 0x00
if (!cursor.ReadAt(valueOffset + i, value)) {
return cursor.GetResult()
}
if (!HttpRequestSyntax.IsDigit(value)) {
return HttpRequestResult.Malformed
}
if (value != 0x30) {
bodyPresent = true
}
}
}
if (kind == HttpHeaderName.TransferEncoding) {
if (transferSeen || valueLength == 0) {
return HttpRequestResult.Malformed
}
transferSeen = true
}
if (kind == HttpHeaderName.Expect) {
if (expectSeen || valueLength == 0) {
return HttpRequestResult.Malformed
}
expectSeen = true
}
if (lengthSeen && transferSeen) {
return HttpRequestResult.Malformed
}
return HttpRequestResult.Ready
}
/**
* Validates the current nonempty Host value and its optional numeric port.
*/
fn CheckHost() HttpRequestResult
{
// Restricted host subset: DNS-style name or dotted IPv4, optional decimal port.
var valueOffset = (valueOffsets[count] as uint) as int
var valueLength = (valueLengths[count] as uint) as int
var port = false
var portDigits = 0
var portValue = 0
var labelLength = 0
var previous: byte = 0x00
for (var i = 0; i < valueLength; i++) {
var value: byte = 0x00
if (!cursor.ReadAt(valueOffset + i, value)) {
return cursor.GetResult()
}
if (port) {
if (!HttpRequestSyntax.IsDigit(value)) {
return HttpRequestResult.Malformed
}
if (portValue > 6553 || (portValue == 6553 && value > 0x35)) {
return HttpRequestResult.Malformed
}
portValue = portValue * 10 + ((value as int) - 0x30)
portDigits = portDigits + 1
}
elif (value == 0x3A) {
if (i == 0 || previous == 0x2D) {
return HttpRequestResult.Malformed
}
port = true
}
elif (value == 0x2E) {
if (labelLength == 0 || previous == 0x2D) {
return HttpRequestResult.Malformed
}
labelLength = 0
}
elif (HttpRequestSyntax.IsAlpha(value) || HttpRequestSyntax.IsDigit(value) || value == 0x2D) {
if (labelLength == 0 && value == 0x2D) {
return HttpRequestResult.Malformed
}
labelLength = labelLength + 1
}
else {
return HttpRequestResult.Unsupported
}
previous = value
}
if (previous == 0x2D || (port && portDigits == 0)) {
return HttpRequestResult.Malformed
}
return HttpRequestResult.Ready
}
/**
* Reports whether the parsed headers require no body, transfer coding or Expect handling.
*/
public fn IsSupported() bool
{
return !bodyPresent && !transferSeen && !expectSeen
}
/**
* Reports whether a valid Host field was found during the last parse.
*/
public fn HasHost() bool
{
return hostSeen
}
/**
* Returns the number of completely parsed field descriptors.
*/
public fn GetCount() int
{
return count
}
/**
* Returns the cached name classification. Requires 0 <= header < GetCount().
*/
public fn GetKind(header: int) HttpHeaderName
{
return kinds[header]
}
/**
* Returns the absolute source-byte name offset. Requires a valid header index.
*/
public fn GetNameOffset(header: int) int
{
return (nameOffsets[header] as uint) as int
}
/**
* Returns the original field-name byte length. Requires a valid header index.
*/
public fn GetNameLength(header: int) int
{
return (nameLengths[header] as uint) as int
}
/**
* Returns the absolute trimmed-value start. Requires a valid header index.
*/
public fn GetValueOffset(header: int) int
{
return (valueOffsets[header] as uint) as int
}
/**
* Returns the trimmed field-value byte length. Requires a valid header index.
*/
public fn GetValueLength(header: int) int
{
return (valueLengths[header] as uint) as int
}
}