You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
200 lines
4.5 KiB
200 lines
4.5 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace pyxis.gemini.Protocol
|
|
{
|
|
public class Response
|
|
{
|
|
private const int MAX_HEADER_LENGTH = 1029;
|
|
|
|
public int Status { private set; get; }
|
|
public string Meta { private set; get; }
|
|
public byte[] Body { private set; get; }
|
|
|
|
public void setStatus(int status)
|
|
{
|
|
this.Status = status;
|
|
}
|
|
|
|
public void setMeta(string meta)
|
|
{
|
|
this.Meta = meta;
|
|
}
|
|
|
|
public void setBody(byte[] body)
|
|
{
|
|
this.Body = body;
|
|
}
|
|
|
|
public static int Parse(byte[] data, int length, out Response response)
|
|
{
|
|
int i;
|
|
bool found_cr = false, found_lf = false;
|
|
response = null;
|
|
|
|
// Minimum response length would be 6 bytes (status=2, space=1, meta=1, crlf=2)
|
|
// Anything less just bail early.
|
|
if (length < 6)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < length; i++)
|
|
{
|
|
// If we haven't found the header terminating crlf by this point, then it can't be a valid response
|
|
if (i >= MAX_HEADER_LENGTH)
|
|
{
|
|
throw new Exception.ProtocolViolationException(String.Format("Request header length exceeded maximum: %d", MAX_HEADER_LENGTH));
|
|
}
|
|
|
|
if (data[i] == '\r')
|
|
{
|
|
found_cr = true;
|
|
}
|
|
|
|
if (found_cr && data[i] == '\n')
|
|
{
|
|
found_lf = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
found_cr = false;
|
|
}
|
|
}
|
|
|
|
if (!(found_cr && found_lf))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int code;
|
|
string meta;
|
|
|
|
(code, meta) = Response.ParseHeader(data, 0, i);
|
|
|
|
response = new Response();
|
|
response.setStatus(code);
|
|
response.setMeta(meta);
|
|
|
|
return i + 1;
|
|
}
|
|
|
|
private static (int, string) ParseHeader(byte[] buffer, int start, int count)
|
|
{
|
|
int state = 0;
|
|
// 0 - reading code
|
|
// 1 - reading separator
|
|
// 2 - reading meta
|
|
// 3 - reading cr
|
|
// 4 - reading lf
|
|
|
|
int field_start = start;
|
|
|
|
string code = "";
|
|
string meta = "";
|
|
|
|
for (int i = start; i <= start + count; i++)
|
|
{
|
|
switch (state)
|
|
{
|
|
case 0: // reading code
|
|
if (buffer[i] == ' ')
|
|
{
|
|
if (field_start == i)
|
|
{
|
|
throw new Exception.ProtocolViolationException("Found separator in header before reading status code.");
|
|
}
|
|
code = Encoding.UTF8.GetString(buffer, field_start, i - field_start);
|
|
field_start = i;
|
|
state = 1;
|
|
i--;
|
|
}
|
|
else
|
|
{
|
|
if (i - field_start > 2)
|
|
{
|
|
throw new Exception.ProtocolViolationException("Status code too long");
|
|
}
|
|
}
|
|
break;
|
|
case 1: // reading separator
|
|
if (buffer[i] != ' ')
|
|
{
|
|
field_start = i;
|
|
state = 2;
|
|
i--;
|
|
}
|
|
else
|
|
{
|
|
if (i - field_start > 0)
|
|
{
|
|
throw new Exception.ProtocolViolationException("Found multiple spaces in header separator.");
|
|
}
|
|
}
|
|
break;
|
|
case 2: // reading meta
|
|
if (buffer[i] == '\r')
|
|
{
|
|
meta = Encoding.UTF8.GetString(buffer, field_start, i - field_start);
|
|
field_start = i;
|
|
state = 3;
|
|
i--;
|
|
}
|
|
else
|
|
{
|
|
if (i - field_start > 1024)
|
|
{
|
|
throw new Exception.ProtocolViolationException("Meta field too long.");
|
|
}
|
|
}
|
|
break;
|
|
case 3: // reading cr
|
|
if (buffer[i] == '\r')
|
|
{
|
|
state = 4;
|
|
}
|
|
else
|
|
{
|
|
throw new Exception.ProtocolViolationException("Could not find carriage return to terminate header.");
|
|
}
|
|
break;
|
|
case 4: // reading lf
|
|
if (buffer[i] == '\n')
|
|
{
|
|
state = 5;
|
|
}
|
|
else
|
|
{
|
|
throw new Exception.ProtocolViolationException("Could not find line feed to terminate header.");
|
|
}
|
|
break;
|
|
case 5: // shouldn't get here, read past \r\n
|
|
throw new Exception.ProtocolViolationException("Header contained data past terminated crlf.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (int.Parse(code), meta);
|
|
}
|
|
|
|
public byte[] Serialize()
|
|
{
|
|
// TODO: Validate Meta is <=1024 bytes
|
|
// TODO: Validate Meta is valid for specific response codes?
|
|
// TODO: Validate no response body unless Status >=20 && <=29
|
|
|
|
List<byte> data = new List<byte>();
|
|
data.AddRange(Encoding.UTF8.GetBytes(this.Status.ToString() + ' ' + this.Meta + "\r\n"));
|
|
if (this.Body != null)
|
|
{
|
|
data.AddRange(this.Body);
|
|
}
|
|
|
|
return data.ToArray();
|
|
}
|
|
}
|
|
}
|
|
|