mirror of
https://github.com/AlexMacocian/MTSC.git
synced 2026-07-25 08:22:17 +00:00
Implemented form data handling for multipart form-data for text and files
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MTSC.Common.Http;
|
||||
using MTSC.Common.Http.Forms;
|
||||
using System.Text;
|
||||
|
||||
namespace MTSC.UnitTests
|
||||
@@ -7,9 +8,30 @@ namespace MTSC.UnitTests
|
||||
[TestClass]
|
||||
public class HttpRequestTests
|
||||
{
|
||||
private const string postWithHeader = "POST /test/demo_form.php HTTP/1.1\n\rHost: w3schools.com\n\r\n\r";
|
||||
private const string postWithHeader = "POST /test/demo_form.php HTTP/1.1\r\nHost: w3schools.com\r\n\r\n";
|
||||
|
||||
private const string emptyGet = "GET / HTTP/1.1\n\r\n\r";
|
||||
private const string postWithMultipart = "POST / HTTP/1.1\r\nHost: localhost:8000\r\n" +
|
||||
"User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:29.0) Gecko/20100101 Firefox/29.0\r\n" +
|
||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
|
||||
"Accept-Language: en-US,en;q=0.5\r\n" +
|
||||
"Accept-Encoding: gzip, deflate\r\n" +
|
||||
"Connection: keep-alive\r\n" +
|
||||
"Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266 \r\n" +
|
||||
"Content-Length: 554\r\r\n\n" +
|
||||
"-----------------------------9051914041544843365972754266\r\n" +
|
||||
"Content-Disposition: form-data; name=\"text\"\r\n\r\n" +
|
||||
"text default\r\n" +
|
||||
"-----------------------------9051914041544843365972754266\r\n" +
|
||||
"Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\r\n" +
|
||||
"Content-Type: text/plain\r\n\r\n" +
|
||||
"Content of a.txt.\r\n\r\n" +
|
||||
"-----------------------------9051914041544843365972754266\r\n" +
|
||||
"Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"\r\n" +
|
||||
"Content-Type: text/html\r\n\r\n" +
|
||||
"<!DOCTYPE html><title>Content of a.html.</title>\r\n\r\n" +
|
||||
"-----------------------------9051914041544843365972754266--";
|
||||
|
||||
private const string emptyGet = "GET / HTTP/1.1\r\n\r\n";
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(postWithHeader)]
|
||||
@@ -34,5 +56,16 @@ namespace MTSC.UnitTests
|
||||
HttpRequest request = HttpRequest.FromBytes(ASCIIEncoding.ASCII.GetBytes(postWithHeader));
|
||||
Assert.AreEqual(request.Headers[HttpMessage.RequestHeaders.Host], "w3schools.com");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RequestShouldParse()
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(postWithMultipart);
|
||||
var request = HttpRequest.FromBytes(bytes);
|
||||
|
||||
Assert.IsNotNull(request.Form.GetValue<FileContentType>("file2"));
|
||||
Assert.IsNotNull(request.Form.GetValue<TextContentType>("text"));
|
||||
Assert.IsNotNull(request.Form.GetValue<FileContentType>("file1"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ namespace MTSC.Client.Handlers
|
||||
/// Send a request to the server.
|
||||
/// </summary>
|
||||
/// <param name="request">Request to be sent.</param>
|
||||
public void SendRequest(Client client, HttpMessage request)
|
||||
public void SendRequest(Client client, HttpRequest request)
|
||||
{
|
||||
client.QueueMessage(request.BuildRequest());
|
||||
client.QueueMessage(request.GetPackedRequest());
|
||||
}
|
||||
/// <summary>
|
||||
/// Add a http module.
|
||||
@@ -55,8 +55,7 @@ namespace MTSC.Client.Handlers
|
||||
/// <returns>False.</returns>
|
||||
bool IHandler.HandleReceivedMessage(Client client, Message message)
|
||||
{
|
||||
HttpMessage httpMessage = new HttpMessage();
|
||||
httpMessage.ParseResponse(message.MessageBytes);
|
||||
HttpResponse httpMessage = new HttpResponse(message.MessageBytes);
|
||||
foreach(IHttpModule httpModule in httpModules)
|
||||
{
|
||||
if (httpModule.HandleResponse(this, httpMessage))
|
||||
|
||||
@@ -72,12 +72,11 @@ namespace MTSC.Client.Handlers
|
||||
{
|
||||
if(state == SocketState.Handshaking)
|
||||
{
|
||||
HttpMessage response = new HttpMessage();
|
||||
response.ParseResponse(message.MessageBytes);
|
||||
HttpResponse response = new HttpResponse(message.MessageBytes);
|
||||
if(response.StatusCode == HttpMessage.StatusCodes.SwitchingProtocols &&
|
||||
response["Upgrade"] == "websocket" &&
|
||||
response[HttpMessage.GeneralHeaders.Connection].ToLower() == "upgrade" &&
|
||||
response[WebsocketHeaderAcceptKey].Trim() == expectedguid)
|
||||
response.Headers["Upgrade"] == "websocket" &&
|
||||
response.Headers[HttpMessage.GeneralHeaders.Connection].ToLower() == "upgrade" &&
|
||||
response.Headers[WebsocketHeaderAcceptKey].Trim() == expectedguid)
|
||||
{
|
||||
state = SocketState.Established;
|
||||
foreach (IWebsocketModule websocketModule in websocketModules)
|
||||
@@ -123,16 +122,16 @@ namespace MTSC.Client.Handlers
|
||||
string handshakeGuid = Guid.NewGuid().ToString();
|
||||
string handshakeKey = handshakeGuid+ GlobalUniqueIdentifier;
|
||||
expectedguid = Convert.ToBase64String(sha1Provider.ComputeHash(Encoding.UTF8.GetBytes(handshakeKey)));
|
||||
HttpMessage beginRequest = new HttpMessage();
|
||||
HttpRequest beginRequest = new HttpRequest();
|
||||
beginRequest.Method = HttpMessage.HttpMethods.Get;
|
||||
beginRequest.RequestURI = WebsocketURI;
|
||||
beginRequest[HttpMessage.RequestHeaders.Host] = client.Address;
|
||||
beginRequest[HttpMessage.GeneralHeaders.Connection] = "Upgrade";
|
||||
beginRequest[WebsocketHeaderKey] = handshakeGuid;
|
||||
beginRequest["Origin"] = client.Address;
|
||||
beginRequest[WebsocketProtocolKey] = "chat";
|
||||
beginRequest[WebsocketProtocolVersionKey] = "13";
|
||||
client.QueueMessage(beginRequest.BuildRequest());
|
||||
beginRequest.Headers[HttpMessage.RequestHeaders.Host] = client.Address;
|
||||
beginRequest.Headers[HttpMessage.GeneralHeaders.Connection] = "Upgrade";
|
||||
beginRequest.Headers[WebsocketHeaderKey] = handshakeGuid;
|
||||
beginRequest.Headers["Origin"] = client.Address;
|
||||
beginRequest.Headers[WebsocketProtocolKey] = "chat";
|
||||
beginRequest.Headers[WebsocketProtocolVersionKey] = "13";
|
||||
client.QueueMessage(beginRequest.GetPackedRequest());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
using MTSC.Client.Handlers;
|
||||
using MTSC.Common.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace MTSC.Common.Http.ClientModules
|
||||
{
|
||||
@@ -15,6 +10,6 @@ namespace MTSC.Common.Http.ClientModules
|
||||
/// <param name="handler">Handler that operates on the response.</param>
|
||||
/// <param name="response">Response message.</param>
|
||||
/// <returns>True if no other module should operate on the response.</returns>
|
||||
bool HandleResponse(IHandler handler, HttpMessage response);
|
||||
bool HandleResponse(IHandler handler, HttpResponse response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace MTSC.Common.Http.Forms
|
||||
{
|
||||
public abstract class ContentTypeBase
|
||||
{
|
||||
public string ContentType { get; private set; }
|
||||
|
||||
public ContentTypeBase(string contentType)
|
||||
{
|
||||
this.ContentType = contentType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace MTSC.Common.Http.Forms
|
||||
{
|
||||
public class FileContentType : ContentTypeBase
|
||||
{
|
||||
public string FileName { get; private set; }
|
||||
|
||||
public byte[] Data { get; private set; }
|
||||
|
||||
public FileContentType(string contentType, string fileName, byte[] data) : base(contentType)
|
||||
{
|
||||
this.FileName = fileName;
|
||||
this.Data = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MTSC.Common.Http.Forms
|
||||
{
|
||||
public class Form
|
||||
{
|
||||
private Dictionary<string, ContentTypeBase> dictionary = new Dictionary<string, ContentTypeBase>();
|
||||
|
||||
public void SetValue(string key, ContentTypeBase value)
|
||||
{
|
||||
dictionary[key] = value;
|
||||
}
|
||||
|
||||
public T GetValue<T>(string key) where T : ContentTypeBase
|
||||
{
|
||||
return dictionary[key] as T;
|
||||
}
|
||||
|
||||
public ContentTypeBase GetValue(string key)
|
||||
{
|
||||
return dictionary[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace MTSC.Common.Http.Forms
|
||||
{
|
||||
public class TextContentType : ContentTypeBase
|
||||
{
|
||||
public string Value { get; private set; }
|
||||
|
||||
public TextContentType(string contentType, string value) : base(contentType)
|
||||
{
|
||||
this.Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using MTSC.Exceptions;
|
||||
using MTSC.Common.Http.Forms;
|
||||
using MTSC.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
@@ -7,6 +8,10 @@ namespace MTSC.Common.Http
|
||||
{
|
||||
public sealed class HttpMessage
|
||||
{
|
||||
private const string contentDisposition = "Content-Disposition";
|
||||
private const string formData = "form-data";
|
||||
private const string contentType = "Content-Type";
|
||||
|
||||
public enum StatusCodes
|
||||
{
|
||||
Continue = 100,
|
||||
@@ -124,761 +129,5 @@ namespace MTSC.Common.Http
|
||||
|
||||
private Dictionary<string, string> headers = new Dictionary<string, string>();
|
||||
private List<Cookie> cookies = new List<Cookie>();
|
||||
|
||||
#region Properties
|
||||
public HttpMethods Method { get; set; }
|
||||
public string RequestURI { get; set; }
|
||||
public string RequestQuery { get; set; }
|
||||
public byte[] Body { get; set; }
|
||||
public StatusCodes StatusCode { get; set; }
|
||||
#endregion
|
||||
#region Constructors
|
||||
public HttpMessage()
|
||||
{
|
||||
RequestQuery = string.Empty;
|
||||
RequestURI = "/";
|
||||
Method = HttpMethods.Get;
|
||||
}
|
||||
#endregion
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// Headers dictionary.
|
||||
/// </summary>
|
||||
/// <param name="headerKey">Key of the header.</param>
|
||||
/// <returns>Value of the header.</returns>
|
||||
public string this[string headerKey] { get => headers[headerKey]; set => headers[headerKey] = value; }
|
||||
/// <summary>
|
||||
/// Headers dictionary.
|
||||
/// </summary>
|
||||
/// <param name="headerKey">Key of the header.</param>
|
||||
/// <returns>Value of the header.</returns>
|
||||
public string this[GeneralHeaders headerKey] { get => headers[HttpHeaders.GeneralHeaders[(int)headerKey]]; set => headers[HttpHeaders.GeneralHeaders[(int)headerKey]] = value; }
|
||||
/// <summary>
|
||||
/// Headers dictionary.
|
||||
/// </summary>
|
||||
/// <param name="headerKey">Key of the header.</param>
|
||||
/// <returns>Value of the header.</returns>
|
||||
public string this[ResponseHeaders headerKey] { get => headers[HttpHeaders.ResponseHeaders[(int)headerKey]]; set => headers[HttpHeaders.ResponseHeaders[(int)headerKey]] = value; }
|
||||
/// <summary>
|
||||
/// Headers dictionary.
|
||||
/// </summary>
|
||||
/// <param name="headerKey">Key of the header.</param>
|
||||
/// <returns>Value of the header.</returns>
|
||||
public string this[RequestHeaders headerKey] { get => headers[HttpHeaders.RequestHeaders[(int)headerKey]]; set => headers[HttpHeaders.RequestHeaders[(int)headerKey]] = value; }
|
||||
/// <summary>
|
||||
/// Headers dictionary.
|
||||
/// </summary>
|
||||
/// <param name="headerKey">Key of the header.</param>
|
||||
/// <returns>Value of the header.</returns>
|
||||
public string this[EntityHeaders headerKey] { get => headers[HttpHeaders.EntityHeaders[(int)headerKey]]; set => headers[HttpHeaders.EntityHeaders[(int)headerKey]] = value; }
|
||||
/// <summary>
|
||||
/// List of cookies.
|
||||
/// </summary>
|
||||
public List<Cookie> Cookies { get => cookies; }
|
||||
/// <summary>
|
||||
/// Add a general header to the message.
|
||||
/// </summary>
|
||||
/// <param name="header">Header key.</param>
|
||||
/// <param name="value">Header value.</param>
|
||||
public void AddGeneralHeader(GeneralHeaders header, string value)
|
||||
{
|
||||
headers[HttpHeaders.GeneralHeaders[(int)header]] = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// Add a request header to the message.
|
||||
/// </summary>
|
||||
/// <param name="requestHeader">Header key.</param>
|
||||
/// <param name="value">Header value.</param>
|
||||
public void AddRequestHeader(RequestHeaders requestHeader, string value)
|
||||
{
|
||||
headers[HttpHeaders.RequestHeaders[(int)requestHeader]] = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// Add a response header to the message.
|
||||
/// </summary>
|
||||
/// <param name="responseHeader">Header key.</param>
|
||||
/// <param name="value">Header value.</param>
|
||||
public void AddResponseHeader(ResponseHeaders responseHeader, string value)
|
||||
{
|
||||
headers[HttpHeaders.ResponseHeaders[(int)responseHeader]] = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// Add an entity header to the message.
|
||||
/// </summary>
|
||||
/// <param name="entityHeader">Header key.</param>
|
||||
/// <param name="value">Header value.</param>
|
||||
public void AddEntityHeaders(EntityHeaders entityHeader, string value)
|
||||
{
|
||||
headers[HttpHeaders.EntityHeaders[(int)entityHeader]] = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// Build the request bytes based on the message contents.
|
||||
/// </summary>
|
||||
/// <returns>Array of bytes.</returns>
|
||||
public byte[] BuildRequest()
|
||||
{
|
||||
StringBuilder requestString = new StringBuilder();
|
||||
requestString.Append(HttpHeaders.Methods[(int)Method]).Append(HttpHeaders.SP).Append(RequestURI).Append('?').Append(RequestQuery).Append(HttpHeaders.SP).Append(HttpHeaders.HTTPVER).Append(HttpHeaders.CRLF);
|
||||
foreach(KeyValuePair<string, string> header in headers)
|
||||
{
|
||||
requestString.Append(header.Key).Append(':').Append(HttpHeaders.SP).Append(header.Value).Append(HttpHeaders.CRLF);
|
||||
}
|
||||
requestString.Append(HttpHeaders.CRLF);
|
||||
if(Cookies.Count > 0)
|
||||
{
|
||||
requestString.Append(HttpHeaders.RequestCookieHeader).Append(':').Append(HttpHeaders.SP);
|
||||
for(int i = 0; i < Cookies.Count; i++)
|
||||
{
|
||||
Cookie cookie = Cookies[i];
|
||||
requestString.Append(cookie.BuildCookieString());
|
||||
if(i < Cookies.Count - 1)
|
||||
{
|
||||
requestString.Append(';');
|
||||
}
|
||||
}
|
||||
}
|
||||
byte[] request = new byte[requestString.Length + (Body == null ? 0 : Body.Length)];
|
||||
byte[] requestBytes = ASCIIEncoding.ASCII.GetBytes(requestString.ToString());
|
||||
Array.Copy(requestBytes, 0, request, 0, requestBytes.Length);
|
||||
if (Body != null)
|
||||
{
|
||||
Array.Copy(Body, 0, request, requestBytes.Length, Body.Length);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
/// <summary>
|
||||
/// Parse the received bytes and populate the message contents.
|
||||
/// </summary>
|
||||
/// <param name="requestBytes">Message bytes to be parsed.</param>
|
||||
public void ParseRequest(byte[] requestBytes)
|
||||
{
|
||||
/*
|
||||
* Parse the bytes one by one, respecting the reference manual.
|
||||
*/
|
||||
StringBuilder parseBuffer = new StringBuilder();
|
||||
/*
|
||||
* Keep the index of the byte array, to identify the message body.
|
||||
* Step value indicates at what point the parsing algorithm currently is.
|
||||
* Step 0 - Method, 1 - URI, 2 - Query, 3 - HTTPVer, 4 - Header, 5 - Value
|
||||
*/
|
||||
int step = 0;
|
||||
string headerKey = string.Empty;
|
||||
string headerValue = string.Empty;
|
||||
int bodyIndex = 0;
|
||||
for(int i = 0; i < requestBytes.Length; i++)
|
||||
{
|
||||
if(step == 0)
|
||||
{
|
||||
Method = ParseMethod(requestBytes, ref i);
|
||||
step++;
|
||||
}
|
||||
else if(step == 1)
|
||||
{
|
||||
RequestURI = ParseRequestURI(requestBytes, ref i);
|
||||
if(requestBytes[i] == '?')
|
||||
{
|
||||
step++;
|
||||
}
|
||||
else
|
||||
{
|
||||
step += 2;
|
||||
}
|
||||
}
|
||||
else if(step == 2)
|
||||
{
|
||||
RequestQuery = ParseRequestQuery(requestBytes, ref i);
|
||||
step++;
|
||||
}
|
||||
else if (step == 3)
|
||||
{
|
||||
ParseHTTPVer(requestBytes, ref i);
|
||||
step++;
|
||||
}
|
||||
else if (step == 4)
|
||||
{
|
||||
if(requestBytes[i] == HttpHeaders.CRLF[0])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if(requestBytes[i] == HttpHeaders.CRLF[1])
|
||||
{
|
||||
bodyIndex = i;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
headerKey = ParseHeaderKey(requestBytes, ref i);
|
||||
step++;
|
||||
}
|
||||
}
|
||||
else if (step == 5)
|
||||
{
|
||||
if (requestBytes[i] == HttpHeaders.CRLF[0])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (requestBytes[i] == HttpHeaders.CRLF[1])
|
||||
{
|
||||
bodyIndex = i;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
headerValue = ParseHeaderValue(requestBytes, ref i);
|
||||
if (headerKey == HttpHeaders.RequestCookieHeader)
|
||||
{
|
||||
Cookies.Add(new Cookie(headerValue));
|
||||
}
|
||||
else
|
||||
{
|
||||
headers.Add(headerKey, headerValue);
|
||||
}
|
||||
step--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(requestBytes.Length - bodyIndex > 1)
|
||||
{
|
||||
/*
|
||||
* If the message contains a body, copy it into a different array
|
||||
* and save it into the HTTP message;
|
||||
*/
|
||||
this.Body = new byte[requestBytes.Length - bodyIndex];
|
||||
Array.Copy(requestBytes, bodyIndex, this.Body, 0, this.Body.Length);
|
||||
}
|
||||
return;
|
||||
}
|
||||
/// <summary>
|
||||
/// Build the response bytes based on the message contents.
|
||||
/// </summary>
|
||||
/// <param name="includeContentLengthHeader">
|
||||
/// If set to true, add an extra Content-Length header specifying the length of the body.
|
||||
/// </param>
|
||||
/// <returns>Array of bytes.</returns>
|
||||
public byte[] BuildResponse(bool includeContentLengthHeader)
|
||||
{
|
||||
if (includeContentLengthHeader)
|
||||
{
|
||||
/*
|
||||
* If there is a body, include the size of the body. If there is no body,
|
||||
* set the value of the content length to 0.
|
||||
*/
|
||||
this[EntityHeaders.ContentLength] = Body == null ? "0" : Body.Length.ToString();
|
||||
}
|
||||
StringBuilder responseString = new StringBuilder();
|
||||
responseString.Append(HttpHeaders.HTTPVER).Append(HttpHeaders.SP).Append((int)this.StatusCode).Append(HttpHeaders.SP).Append(this.StatusCode.ToString()).Append(HttpHeaders.CRLF);
|
||||
foreach (KeyValuePair<string, string> header in headers)
|
||||
{
|
||||
responseString.Append(header.Key).Append(':').Append(HttpHeaders.SP).Append(header.Value).Append(HttpHeaders.CRLF);
|
||||
}
|
||||
responseString.Append(HttpHeaders.CRLF);
|
||||
foreach(Cookie cookie in Cookies)
|
||||
{
|
||||
responseString.Append(HttpHeaders.ResponseCookieHeader).Append(':').Append(HttpHeaders.SP).Append(cookie.BuildCookieString()).Append(HttpHeaders.CRLF);
|
||||
}
|
||||
byte[] response = new byte[responseString.Length + (Body == null ? 0 : Body.Length)];
|
||||
byte[] responseBytes = ASCIIEncoding.ASCII.GetBytes(responseString.ToString());
|
||||
Array.Copy(responseBytes, 0, response, 0, responseBytes.Length);
|
||||
if (Body != null)
|
||||
{
|
||||
Array.Copy(Body, 0, response, responseBytes.Length, Body.Length);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
/// <summary>
|
||||
/// Parse the received bytes and populate the message contents.
|
||||
/// </summary>
|
||||
/// <param name="responseBytes">Array of bytes to be parsed.</param>
|
||||
public void ParseResponse(byte[] responseBytes)
|
||||
{
|
||||
/*
|
||||
* Parse the bytes one by one, respecting the reference manual.
|
||||
*/
|
||||
StringBuilder parseBuffer = new StringBuilder();
|
||||
/*
|
||||
* Keep the index of the byte array, to identify the message body.
|
||||
* Step value indicates at what point the parsing algorithm currently is.
|
||||
* Step 0 - HTTPVer, 1 - StatusCodeInt, 2 - StatusCodeString, 3 - Header, 4 - Value
|
||||
*/
|
||||
int step = 0;
|
||||
string headerKey = string.Empty;
|
||||
string headerValue = string.Empty;
|
||||
int bodyIndex = 0;
|
||||
for (int i = 0; i < responseBytes.Length; i++)
|
||||
{
|
||||
if (step == 0)
|
||||
{
|
||||
ParseHTTPVer(responseBytes, ref i);
|
||||
step++;
|
||||
}
|
||||
else if (step == 1)
|
||||
{
|
||||
StatusCode = (StatusCodes)ParseResponseCode(responseBytes, ref i);
|
||||
step++;
|
||||
}
|
||||
else if (step == 2)
|
||||
{
|
||||
if(StatusCode != ParseResponseCodeString(responseBytes, ref i))
|
||||
{
|
||||
throw new InvalidStatusCodeException("Status code value and text do not match!");
|
||||
}
|
||||
step++;
|
||||
}
|
||||
else if (step == 3)
|
||||
{
|
||||
if (responseBytes[i] == HttpHeaders.CRLF[0])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (responseBytes[i] == HttpHeaders.CRLF[1])
|
||||
{
|
||||
bodyIndex = i;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
headerKey = ParseHeaderKey(responseBytes, ref i);
|
||||
step++;
|
||||
}
|
||||
}
|
||||
else if (step == 4)
|
||||
{
|
||||
if (responseBytes[i] == HttpHeaders.CRLF[0])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (responseBytes[i] == HttpHeaders.CRLF[1])
|
||||
{
|
||||
bodyIndex = i;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
headerValue = ParseHeaderValue(responseBytes, ref i);
|
||||
if (headerKey == HttpHeaders.ResponseCookieHeader)
|
||||
{
|
||||
Cookies.Add(new Cookie(headerValue));
|
||||
}
|
||||
else
|
||||
{
|
||||
headers.Add(headerKey, headerValue);
|
||||
}
|
||||
step--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (responseBytes.Length - bodyIndex > 1)
|
||||
{
|
||||
/*
|
||||
* If the message contains a body, copy it into a different array
|
||||
* and save it into the HTTP message;
|
||||
*/
|
||||
this.Body = new byte[responseBytes.Length - bodyIndex - 1];
|
||||
Array.Copy(responseBytes, bodyIndex, this.Body, 0, this.Body.Length);
|
||||
}
|
||||
return;
|
||||
}
|
||||
/// <summary>
|
||||
/// Check if the message contains a header.
|
||||
/// </summary>
|
||||
/// <param name="header">Key of the header.</param>
|
||||
/// <returns>True if the message contains a header with the provided key.</returns>
|
||||
public bool ContainsHeader(ResponseHeaders header)
|
||||
{
|
||||
return ContainsHeader(HttpHeaders.ResponseHeaders[(int)header]);
|
||||
}
|
||||
/// <summary>
|
||||
/// Check if the message contains a header.
|
||||
/// </summary>
|
||||
/// <param name="header">Key of the header.</param>
|
||||
/// <returns>True if the message contains a header with the provided key.</returns>
|
||||
public bool ContainsHeader(RequestHeaders header)
|
||||
{
|
||||
return ContainsHeader(HttpHeaders.RequestHeaders[(int)header]);
|
||||
}
|
||||
/// <summary>
|
||||
/// Check if the message contains a header.
|
||||
/// </summary>
|
||||
/// <param name="header">Key of the header.</param>
|
||||
/// <returns>True if the message contains a header with the provided key.</returns>
|
||||
public bool ContainsHeader(GeneralHeaders header)
|
||||
{
|
||||
return ContainsHeader(HttpHeaders.GeneralHeaders[(int)header]);
|
||||
}
|
||||
/// <summary>
|
||||
/// Check if the message contains a header.
|
||||
/// </summary>
|
||||
/// <param name="header">Key of the header.</param>
|
||||
/// <returns>True if the message contains a header with the provided key.</returns>
|
||||
public bool ContainsHeader(EntityHeaders header)
|
||||
{
|
||||
return ContainsHeader(HttpHeaders.EntityHeaders[(int)header]);
|
||||
}
|
||||
/// <summary>
|
||||
/// Check if the message contains a header.
|
||||
/// </summary>
|
||||
/// <param name="header">Key of the header.</param>
|
||||
/// <returns>True if the message contains a header with the provided key.</returns>
|
||||
public bool ContainsHeader(string header)
|
||||
{
|
||||
return headers.ContainsKey(header);
|
||||
}
|
||||
/// <summary>
|
||||
/// Parse the body into a posted from respecting the reference manual.
|
||||
/// </summary>
|
||||
/// <returns>Dictionary with posted from.</returns>
|
||||
public Dictionary<string, string> GetPostForm()
|
||||
{
|
||||
if (ContainsHeader("Content-Type") && Body != null)
|
||||
{
|
||||
if(this["Content-Type"] == "application/x-www-form-urlencoded")
|
||||
{
|
||||
Dictionary<string, string> returnDictionary = new Dictionary<string, string>();
|
||||
/*
|
||||
* Walk through the buffer and get the form contents.
|
||||
* Step 0 - key, 1 - value.
|
||||
*/
|
||||
string formKey = string.Empty;
|
||||
int step = 0;
|
||||
for(int i = 0; i < Body.Length; i++)
|
||||
{
|
||||
if(step == 0)
|
||||
{
|
||||
formKey = GetField(Body, ref i);
|
||||
step++;
|
||||
}
|
||||
else
|
||||
{
|
||||
returnDictionary[formKey] = GetValue(Body, ref i);
|
||||
step--;
|
||||
}
|
||||
}
|
||||
return returnDictionary;
|
||||
}
|
||||
else if (this["Content-Type"].Contains("multipart/form-data"))
|
||||
{
|
||||
throw new NotImplementedException("Multipart posting not implemented");
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#region Private Methods
|
||||
private HttpMethods GetMethod(string methodString)
|
||||
{
|
||||
int index = Array.IndexOf(HttpHeaders.Methods, methodString.ToUpper());
|
||||
return (HttpMethods)index;
|
||||
}
|
||||
|
||||
private HttpMethods ParseMethod(byte[] buffer, ref int index)
|
||||
{
|
||||
/*
|
||||
* Get each character one by one. When meeting a SP character, parse the method, clear the buffer
|
||||
* and continue with parsing the next step.
|
||||
*/
|
||||
StringBuilder parseBuffer = new StringBuilder();
|
||||
for (; index < buffer.Length; index++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (buffer[index] == (byte)HttpHeaders.SP)
|
||||
{
|
||||
string methodString = parseBuffer.ToString();
|
||||
return GetMethod(methodString);
|
||||
}
|
||||
else
|
||||
{
|
||||
parseBuffer.Append((char)buffer[index]);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidMethodException("Invalid request method. Buffer: " + parseBuffer.ToString(), e);
|
||||
}
|
||||
}
|
||||
throw new InvalidMethodException("Invalid request method. Buffer: " + parseBuffer.ToString());
|
||||
}
|
||||
|
||||
private string ParseRequestURI(byte[] buffer, ref int index)
|
||||
{
|
||||
/*
|
||||
* Get each character one by one. When meeting a SP character, parse the URI and clear the buffer.
|
||||
*/
|
||||
StringBuilder parseBuffer = new StringBuilder();
|
||||
for (; index < buffer.Length; index++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (buffer[index] == (byte)HttpHeaders.SP)
|
||||
{
|
||||
return parseBuffer.ToString();
|
||||
}
|
||||
if (buffer[index] == '?')
|
||||
{
|
||||
return parseBuffer.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
parseBuffer.Append((char)buffer[index]);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidRequestURIException("Invalid request URI. Buffer: " + parseBuffer.ToString(), e);
|
||||
}
|
||||
}
|
||||
throw new InvalidRequestURIException("Invalid request URI. Buffer: " + parseBuffer.ToString());
|
||||
}
|
||||
|
||||
private string ParseRequestQuery(byte[] buffer, ref int index)
|
||||
{
|
||||
/*
|
||||
* Get each character one by one. When meeting a SP character, parse the URI and clear the buffer.
|
||||
*/
|
||||
StringBuilder parseBuffer = new StringBuilder();
|
||||
for (; index < buffer.Length; index++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (buffer[index] == (byte)HttpHeaders.SP)
|
||||
{
|
||||
return parseBuffer.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
parseBuffer.Append((char)buffer[index]);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidRequestURIException("Invalid request query. Buffer: " + parseBuffer.ToString(), e);
|
||||
}
|
||||
}
|
||||
throw new InvalidRequestURIException("Invalid request query. Buffer: " + parseBuffer.ToString());
|
||||
}
|
||||
|
||||
private void ParseHTTPVer(byte[] buffer, ref int index)
|
||||
{
|
||||
/*
|
||||
* Get each character one by one. When meeting a LF character, parse the HTTPVer.
|
||||
* Check if the HTTPVer matches the implementation version.
|
||||
* If not, throw an exception.
|
||||
*/
|
||||
StringBuilder parseBuffer = new StringBuilder();
|
||||
for (; index < buffer.Length; index++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (buffer[index] == HttpHeaders.CRLF[1] || buffer[index] == HttpHeaders.SP)
|
||||
{
|
||||
string httpVer = parseBuffer.ToString();
|
||||
if (httpVer != HttpHeaders.HTTPVER)
|
||||
{
|
||||
throw new InvalidHttpVersionException("Invalid HTTP version. Buffer: " + parseBuffer.ToString());
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if(buffer[index] == HttpHeaders.CRLF[0])
|
||||
{
|
||||
/*
|
||||
* If a termination character is detected, ignore it and wait for the full terminator.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
parseBuffer.Append((char)buffer[index]);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidHttpVersionException("Invalid HTTP version. Buffer: " + parseBuffer.ToString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string ParseHeaderKey(byte[] buffer, ref int index)
|
||||
{
|
||||
/*
|
||||
* Get each character one by one. When meeting a ':' character, parse the header key.
|
||||
*/
|
||||
StringBuilder parseBuffer = new StringBuilder();
|
||||
for (; index < buffer.Length; index++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (buffer[index] == ':')
|
||||
{
|
||||
return parseBuffer.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
parseBuffer.Append((char)buffer[index]);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidHeaderException("Invalid Header key. Buffer: " + parseBuffer.ToString(), e);
|
||||
}
|
||||
}
|
||||
throw new InvalidHeaderException("Invalid Header key. Buffer: " + parseBuffer.ToString());
|
||||
}
|
||||
|
||||
private string ParseHeaderValue(byte[] buffer, ref int index)
|
||||
{
|
||||
/*
|
||||
* Get each character one by one. When meeting a LF character, parse the value.
|
||||
*/
|
||||
StringBuilder parseBuffer = new StringBuilder();
|
||||
for (; index < buffer.Length; index++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (buffer[index] == HttpHeaders.CRLF[1])
|
||||
{
|
||||
return parseBuffer.ToString().Trim();
|
||||
}
|
||||
else if (buffer[index] == HttpHeaders.CRLF[0])
|
||||
{
|
||||
/*
|
||||
* If a termination character is detected, ignore it and wait for the full terminator.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
parseBuffer.Append((char)buffer[index]);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidHeaderException("Invalid header value. Buffer: " + parseBuffer.ToString(), e);
|
||||
}
|
||||
}
|
||||
throw new InvalidHeaderException("Invalid header value. Buffer: " + parseBuffer.ToString());
|
||||
}
|
||||
|
||||
private int ParseResponseCode(byte[] buffer, ref int index)
|
||||
{
|
||||
/*
|
||||
* Get each character one by one. When meeting a SP character, parse the value.
|
||||
*/
|
||||
StringBuilder parseBuffer = new StringBuilder();
|
||||
for (; index < buffer.Length; index++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (buffer[index] == HttpHeaders.SP)
|
||||
{
|
||||
return int.Parse(parseBuffer.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
parseBuffer.Append((char)buffer[index]);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidStatusCodeException("Invalid status code. Buffer: " + parseBuffer.ToString(), e);
|
||||
}
|
||||
}
|
||||
throw new InvalidStatusCodeException("Invalid status code. Buffer: " + parseBuffer.ToString());
|
||||
}
|
||||
|
||||
private StatusCodes ParseResponseCodeString(byte[] buffer, ref int index)
|
||||
{
|
||||
/*
|
||||
* Get each character one by one. When meeting a LF character, parse the value.
|
||||
*/
|
||||
StringBuilder parseBuffer = new StringBuilder();
|
||||
for (; index < buffer.Length; index++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (buffer[index] == HttpHeaders.CRLF[1])
|
||||
{
|
||||
return (StatusCodes)Enum.Parse(typeof(StatusCodes), parseBuffer.ToString().Trim());
|
||||
}
|
||||
else if (buffer[index] == HttpHeaders.CRLF[0])
|
||||
{
|
||||
/*
|
||||
* If a termination character is detected, ignore it and wait for the full terminator.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
parseBuffer.Append((char)buffer[index]);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidStatusCodeException("Invalid status code. Buffer: " + parseBuffer.ToString(), e);
|
||||
}
|
||||
}
|
||||
throw new InvalidHeaderException("Invalid status code. Buffer: " + parseBuffer.ToString());
|
||||
}
|
||||
|
||||
private string GetField(byte[] buffer, ref int index)
|
||||
{
|
||||
/*
|
||||
* Get each character one by one. When meeting a LF character, parse the value.
|
||||
*/
|
||||
StringBuilder parseBuffer = new StringBuilder();
|
||||
for (; index < buffer.Length; index++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (buffer[index] == '=')
|
||||
{
|
||||
return parseBuffer.ToString().Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
parseBuffer.Append((char)buffer[index]);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidPostFormException("Invalid form field. Buffer: " + parseBuffer.ToString(), e);
|
||||
}
|
||||
}
|
||||
throw new InvalidHeaderException("Invalid form field. Buffer: " + parseBuffer.ToString());
|
||||
}
|
||||
|
||||
private string GetValue(byte[] buffer, ref int index)
|
||||
{
|
||||
/*
|
||||
* Get each character one by one. When meeting a LF character, parse the value.
|
||||
*/
|
||||
StringBuilder parseBuffer = new StringBuilder();
|
||||
for (; index < buffer.Length; index++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (buffer[index] == '&')
|
||||
{
|
||||
return parseBuffer.ToString().Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
parseBuffer.Append((char)buffer[index]);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidPostFormException("Invalid form field. Buffer: " + parseBuffer.ToString(), e);
|
||||
}
|
||||
}
|
||||
return parseBuffer.ToString().Trim();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using MTSC.Exceptions;
|
||||
using MTSC.Common.Http.Forms;
|
||||
using MTSC.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using static MTSC.Common.Http.HttpMessage;
|
||||
|
||||
@@ -19,7 +21,7 @@ namespace MTSC.Common.Http
|
||||
/// </summary>
|
||||
public List<Cookie> Cookies { get; } = new List<Cookie>();
|
||||
|
||||
public Dictionary<string, string> Form { get; } = new Dictionary<string, string>();
|
||||
public Form Form { get; } = new Form();
|
||||
public HttpMethods Method { get; set; }
|
||||
public string RequestURI { get; set; }
|
||||
public string RequestQuery { get; set; }
|
||||
@@ -450,17 +452,17 @@ namespace MTSC.Common.Http
|
||||
/// Parse the body into a posted from respecting the reference manual.
|
||||
/// </summary>
|
||||
/// <returns>Dictionary with posted from.</returns>
|
||||
private Dictionary<string, string> GetPostForm()
|
||||
private Form GetPostForm()
|
||||
{
|
||||
if (Headers.ContainsHeader("Content-Type") && Body != null)
|
||||
{
|
||||
if (Headers["Content-Type"] == "application/x-www-form-urlencoded")
|
||||
{
|
||||
Dictionary<string, string> returnDictionary = new Dictionary<string, string>();
|
||||
/*
|
||||
* Walk through the buffer and get the form contents.
|
||||
* Step 0 - key, 1 - value.
|
||||
*/
|
||||
Form form = new Form();
|
||||
string formKey = string.Empty;
|
||||
int step = 0;
|
||||
for (int i = 0; i < Body.Length; i++)
|
||||
@@ -472,15 +474,15 @@ namespace MTSC.Common.Http
|
||||
}
|
||||
else
|
||||
{
|
||||
returnDictionary[formKey] = GetValue(Body, ref i);
|
||||
form.SetValue(formKey, new TextContentType("text/plain", GetValue(Body, ref i)));
|
||||
step--;
|
||||
}
|
||||
}
|
||||
return returnDictionary;
|
||||
return form;
|
||||
}
|
||||
else if (Headers["Content-Type"].Contains("multipart/form-data"))
|
||||
{
|
||||
throw new NotImplementedException("Multipart posting not implemented");
|
||||
return GetMultipartForm();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -492,6 +494,204 @@ namespace MTSC.Common.Http
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private Form GetMultipartForm()
|
||||
{
|
||||
string boundary = this.Headers["Content-Type"].Substring(this.Headers["Content-Type"].IndexOf("=") + 1);
|
||||
Form form = new Form();
|
||||
|
||||
int bodyIndex = 0;
|
||||
while (true)
|
||||
{
|
||||
string contentType = "text/plain";
|
||||
|
||||
if (!MatchesTwoHyphens(bodyIndex))
|
||||
{
|
||||
throw new InvalidPostFormException("No boundary where expected");
|
||||
}
|
||||
bodyIndex += 2;
|
||||
|
||||
if (!MatchesString(bodyIndex, boundary))
|
||||
{
|
||||
throw new InvalidPostFormException("No boundary where expected");
|
||||
}
|
||||
bodyIndex += boundary.Length;
|
||||
|
||||
if (!MatchesCRLF(bodyIndex))
|
||||
{
|
||||
if (MatchesTwoHyphens(bodyIndex))
|
||||
{
|
||||
/*
|
||||
* Reached the end of the multipart message
|
||||
*/
|
||||
break;
|
||||
}
|
||||
throw new InvalidPostFormException("No new line after boundary");
|
||||
}
|
||||
bodyIndex += 2;
|
||||
|
||||
if (!MatchesString(bodyIndex, "Content-Disposition: form-data"))
|
||||
{
|
||||
throw new InvalidPostFormException("No Content-Disposition header");
|
||||
}
|
||||
bodyIndex += 30;
|
||||
|
||||
Dictionary<string, string> keys = new Dictionary<string, string>();
|
||||
while (Body[bodyIndex] == ';')
|
||||
{
|
||||
bodyIndex += 2;
|
||||
var keyName = GetMultipartKeyName(bodyIndex);
|
||||
bodyIndex += keyName.Length + 1;
|
||||
var keyNameField = GetMultipartKeyNameField(bodyIndex);
|
||||
bodyIndex += keyNameField.Length + 2;
|
||||
keys[keyName] = keyNameField;
|
||||
}
|
||||
|
||||
if (!MatchesCRLF(bodyIndex))
|
||||
{
|
||||
throw new InvalidPostFormException("No new line after boundary");
|
||||
}
|
||||
bodyIndex += 2;
|
||||
|
||||
if (MatchesString(bodyIndex, "Content-Type: "))
|
||||
{
|
||||
bodyIndex += 14;
|
||||
contentType = GetContentType(bodyIndex);
|
||||
bodyIndex += contentType.Length + 2;
|
||||
}
|
||||
|
||||
if (!MatchesCRLF(bodyIndex))
|
||||
{
|
||||
throw new InvalidPostFormException("No new line after boundary");
|
||||
}
|
||||
bodyIndex += 2;
|
||||
|
||||
var bytes = GetMultipartValue(bodyIndex, boundary);
|
||||
if (keys.Keys.Contains("filename"))
|
||||
{
|
||||
form.SetValue(keys["name"], new FileContentType(contentType, keys["filename"], bytes));
|
||||
}
|
||||
else
|
||||
{
|
||||
form.SetValue(keys["name"], new TextContentType(contentType, Encoding.UTF8.GetString(bytes)));
|
||||
}
|
||||
bodyIndex += bytes.Length + 2;
|
||||
}
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
private bool MatchesCRLF(int index)
|
||||
{
|
||||
if (index + 1 < Body.Length)
|
||||
{
|
||||
if (Body[index] == HttpHeaders.CRLF[0] && Body[index + 1] == HttpHeaders.CRLF[1])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private bool MatchesTwoHyphens(int index)
|
||||
{
|
||||
if (index + 1 < Body.Length)
|
||||
{
|
||||
if (Body[index] == '-' && Body[index + 1] == '-')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private bool MatchesString(int index, string s)
|
||||
{
|
||||
for (int i = 0; i < s.Length; i++)
|
||||
{
|
||||
if (index + i < Body.Length)
|
||||
{
|
||||
if ((char)Body[index + i] != s[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private string GetMultipartKeyName(int index)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (Body[index] != '=')
|
||||
{
|
||||
sb.Append((char)Body[index]);
|
||||
index++;
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
private string GetMultipartKeyNameField(int index)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (Body[index] != '\"')
|
||||
{
|
||||
throw new InvalidPostFormException($"Expected [\"] but found {Body[index]}");
|
||||
}
|
||||
index++;
|
||||
|
||||
while (Body[index] != '\"')
|
||||
{
|
||||
sb.Append((char)Body[index]);
|
||||
index++;
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
private byte[] GetMultipartValue(int bodyIndex, string boundary)
|
||||
{
|
||||
int startIndex = bodyIndex;
|
||||
bool gatheringData = true;
|
||||
while (true)
|
||||
{
|
||||
if (Body[bodyIndex] == '-')
|
||||
{
|
||||
/*
|
||||
* Possible boundary detected. Try and see if it matches.
|
||||
*/
|
||||
if (Body[bodyIndex + 1] == '-' && MatchesString(bodyIndex + 2, boundary))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
bodyIndex++;
|
||||
}
|
||||
byte[] newBytes = new byte[bodyIndex - startIndex - 2];
|
||||
Array.Copy(Body, startIndex, newBytes, 0, bodyIndex - startIndex - 2);
|
||||
return newBytes;
|
||||
}
|
||||
private string GetContentType(int bodyIndex)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (!MatchesCRLF(bodyIndex))
|
||||
{
|
||||
sb.Append((char)Body[bodyIndex]);
|
||||
bodyIndex++;
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
private string GetField(byte[] buffer, ref int index)
|
||||
{
|
||||
/*
|
||||
|
||||
+3
-3
@@ -5,12 +5,12 @@
|
||||
<TargetFrameworks>netcoreapp2.1;net48;netstandard2.0;netcoreapp3.0</TargetFrameworks>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<Version>2.1.8</Version>
|
||||
<Version>2.2</Version>
|
||||
<Authors>Alexandru-Victor Macocian</Authors>
|
||||
<Product>MTSC</Product>
|
||||
<Description>Modular TCP Server and Client</Description>
|
||||
<AssemblyVersion>0.2.1.8</AssemblyVersion>
|
||||
<FileVersion>0.2.1.8</FileVersion>
|
||||
<AssemblyVersion>0.2.2.0</AssemblyVersion>
|
||||
<FileVersion>0.2.2.0</FileVersion>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<PackageProjectUrl>https://github.com/AlexMacocian/MTSC</PackageProjectUrl>
|
||||
|
||||
Reference in New Issue
Block a user