Unverified Commit 7a44e456 authored by honfika's avatar honfika Committed by GitHub

Merge pull request #348 from justcoding121/develop

beta|develop
parents 83377610 46a0b972
......@@ -28,8 +28,7 @@ namespace Titanium.Web.Proxy.Examples.Basic.Helpers
var consoleHandle = GetStdHandle(STD_INPUT_HANDLE);
// get current console mode
uint consoleMode;
if (!GetConsoleMode(consoleHandle, out consoleMode))
if (!GetConsoleMode(consoleHandle, out uint consoleMode))
{
// ERROR: Unable to get console mode.
return false;
......
......@@ -28,7 +28,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
public SessionListItem SelectedSession
{
get { return selectedSession; }
get => selectedSession;
set
{
if (value != selectedSession)
......@@ -44,8 +44,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf
public int ClientConnectionCount
{
get { return (int)GetValue(ClientConnectionCountProperty); }
set { SetValue(ClientConnectionCountProperty, value); }
get => (int)GetValue(ClientConnectionCountProperty);
set => SetValue(ClientConnectionCountProperty, value);
}
public static readonly DependencyProperty ServerConnectionCountProperty = DependencyProperty.Register(
......@@ -53,8 +53,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf
public int ServerConnectionCount
{
get { return (int)GetValue(ServerConnectionCountProperty); }
set { SetValue(ServerConnectionCountProperty, value); }
get => (int)GetValue(ServerConnectionCountProperty);
set => SetValue(ServerConnectionCountProperty, value);
}
private readonly Dictionary<HttpWebClient, SessionListItem> sessionDictionary = new Dictionary<HttpWebClient, SessionListItem>();
......@@ -107,8 +107,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
{
await Dispatcher.InvokeAsync(() =>
{
SessionListItem item;
if (sessionDictionary.TryGetValue(e.WebSession, out item))
if (sessionDictionary.TryGetValue(e.WebSession, out var item))
{
item.Update();
}
......@@ -135,8 +134,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
SessionListItem item = null;
await Dispatcher.InvokeAsync(() =>
{
SessionListItem item2;
if (sessionDictionary.TryGetValue(e.WebSession, out item2))
if (sessionDictionary.TryGetValue(e.WebSession, out var item2))
{
item2.Update();
item = item2;
......@@ -177,8 +175,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
e.DataReceived += (sender, args) =>
{
var session = (SessionEventArgs)sender;
SessionListItem li;
if (sessionDictionary.TryGetValue(session.WebSession, out li))
if (sessionDictionary.TryGetValue(session.WebSession, out var li))
{
li.ReceivedDataCount += args.Count;
}
......@@ -187,8 +184,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
e.DataSent += (sender, args) =>
{
var session = (SessionEventArgs)sender;
SessionListItem li;
if (sessionDictionary.TryGetValue(session.WebSession, out li))
if (sessionDictionary.TryGetValue(session.WebSession, out var li))
{
li.SentDataCount += args.Count;
}
......
......@@ -26,50 +26,50 @@ namespace Titanium.Web.Proxy.Examples.Wpf
public string StatusCode
{
get { return statusCode; }
set { SetField(ref statusCode, value);}
get => statusCode;
set => SetField(ref statusCode, value);
}
public string Protocol
{
get { return protocol; }
set { SetField(ref protocol, value); }
get => protocol;
set => SetField(ref protocol, value);
}
public string Host
{
get { return host; }
set { SetField(ref host, value); }
get => host;
set => SetField(ref host, value);
}
public string Url
{
get { return url; }
set { SetField(ref url, value); }
get => url;
set => SetField(ref url, value);
}
public long BodySize
{
get { return bodySize; }
set { SetField(ref bodySize, value); }
get => bodySize;
set => SetField(ref bodySize, value);
}
public string Process
{
get { return process; }
set { SetField(ref process, value); }
get => process;
set => SetField(ref process, value);
}
public long ReceivedDataCount
{
get { return receivedDataCount; }
set { SetField(ref receivedDataCount, value); }
get => receivedDataCount;
set => SetField(ref receivedDataCount, value);
}
public long SentDataCount
{
get { return sentDataCount; }
set { SetField(ref sentDataCount, value); }
get => sentDataCount;
set => SetField(ref sentDataCount, value);
}
public event PropertyChangedEventHandler PropertyChanged;
......
......@@ -51,8 +51,8 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="StreamExtended, Version=1.0.81.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL">
<HintPath>..\..\packages\StreamExtended.1.0.81\lib\net45\StreamExtended.dll</HintPath>
<Reference Include="StreamExtended, Version=1.0.110.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL">
<HintPath>..\..\packages\StreamExtended.1.0.110-beta\lib\net45\StreamExtended.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
......
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="StreamExtended" version="1.0.81" targetFramework="net45" />
<package id="StreamExtended" version="1.0.110-beta" targetFramework="net45" />
</packages>
\ No newline at end of file
using System;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.EventArguments
{
public class MultipartRequestPartSentEventArgs : EventArgs
{
public string Boundary { get; }
public HeaderCollection Headers { get; }
public MultipartRequestPartSentEventArgs(string boundary, HeaderCollection headers)
{
Boundary = boundary;
Headers = headers;
}
}
}
\ No newline at end of file
......@@ -4,7 +4,6 @@ using System.IO;
using System.Net;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Decompression;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
......@@ -42,6 +41,8 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
internal ProxyClient ProxyClient { get; set; }
internal bool HasMulipartEventSubscribers => MultipartRequestPartSent != null;
/// <summary>
/// Returns a unique Id for this request/response session
/// same as RequestId of WebSession
......@@ -53,7 +54,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public bool ReRequest
{
get { return reRequest; }
get => reRequest;
set
{
if (WebSession.Response.StatusCode == 0)
......@@ -90,6 +91,11 @@ namespace Titanium.Web.Proxy.EventArguments
public event EventHandler<DataEventArgs> DataReceived;
/// <summary>
/// Occurs when multipart request part sent.
/// </summary>
public event EventHandler<MultipartRequestPartSentEventArgs> MultipartRequestPartSent;
public ProxyEndPoint LocalEndPoint;
/// <summary>
......@@ -189,6 +195,11 @@ namespace Titanium.Web.Proxy.EventArguments
DataReceived?.Invoke(this, new DataEventArgs(buffer, offset, count));
}
internal void OnMultipartRequestPartSent(string boundary, HeaderCollection headers)
{
MultipartRequestPartSent?.Invoke(this, new MultipartRequestPartSentEventArgs(boundary, headers));
}
/// <summary>
/// Read response body as byte[] for current response
/// </summary>
......
using System.Net;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.EventArguments
......
using System.Text;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Extensions
{
/// <summary>
/// Extensions on HttpWebSession object
/// </summary>
internal static class HttpWebRequestExtensions
{
/// <summary>
/// parse the character encoding of request from request headers
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
internal static Encoding GetEncoding(this Request request)
{
return HttpHelper.GetEncodingFromContentType(request.ContentType);
}
}
}
using System.Text;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Extensions
{
internal static class HttpWebResponseExtensions
{
/// <summary>
/// Gets the character encoding of response from response headers
/// </summary>
/// <param name="response"></param>
/// <returns></returns>
internal static Encoding GetResponseCharacterEncoding(this Response response)
{
return HttpHelper.GetEncodingFromContentType(response.ContentType);
}
}
}
using StreamExtended.Network;
using System;
using System;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
using StreamExtended.Network;
namespace Titanium.Web.Proxy.Extensions
{
......@@ -17,6 +17,7 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="input"></param>
/// <param name="output"></param>
/// <param name="onCopy"></param>
/// <param name="bufferSize"></param>
internal static async Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy, int bufferSize)
{
byte[] buffer = new byte[bufferSize];
......
using System.Net.Sockets;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Extensions
{
......
......@@ -8,7 +8,12 @@ namespace Titanium.Web.Proxy.Helpers
{
private static readonly Encoding defaultEncoding = Encoding.GetEncoding("ISO-8859-1");
public static Encoding GetEncodingFromContentType(string contentType)
/// <summary>
/// Gets the character encoding of request/response from content-type header
/// </summary>
/// <param name="contentType"></param>
/// <returns></returns>
internal static Encoding GetEncodingFromContentType(string contentType)
{
try
{
......@@ -22,10 +27,10 @@ namespace Titanium.Web.Proxy.Helpers
var parameters = contentType.Split(ProxyConstants.SemiColonSplit);
foreach (string parameter in parameters)
{
var encodingSplit = parameter.Split(ProxyConstants.EqualSplit, 2);
if (encodingSplit.Length == 2 && encodingSplit[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase))
var split = parameter.Split(ProxyConstants.EqualSplit, 2);
if (split.Length == 2 && split[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase))
{
string value = encodingSplit[1];
string value = split[1];
if (value.Equals("x-user-defined", StringComparison.OrdinalIgnoreCase))
{
//todo: what is this?
......@@ -51,6 +56,32 @@ namespace Titanium.Web.Proxy.Helpers
return defaultEncoding;
}
internal static string GetBoundaryFromContentType(string contentType)
{
if (contentType != null)
{
//extract the boundary
var parameters = contentType.Split(ProxyConstants.SemiColonSplit);
foreach (string parameter in parameters)
{
var split = parameter.Split(ProxyConstants.EqualSplit, 2);
if (split.Length == 2 && split[0].Trim().Equals("boundary", StringComparison.CurrentCultureIgnoreCase))
{
string value = split[1];
if (value.Length > 2 && value[0] == '"' && value[value.Length - 1] == '"')
{
value = value.Substring(1, value.Length - 2);
}
return value;
}
}
}
//return null if not specified
return null;
}
/// <summary>
/// Tries to get root domain from a given hostname
/// Adapted from below answer
......
using StreamExtended.Network;
using System;
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
......
......@@ -54,9 +54,7 @@ namespace Titanium.Web.Proxy.Helpers
{
localhost = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ipAddress;
if (IPAddress.TryParse(hostName, out ipAddress))
if (IPAddress.TryParse(hostName, out var ipAddress))
isLocalhost = localhost.AddressList.Any(x => x.Equals(ipAddress));
if (!isLocalhost)
......
......@@ -81,8 +81,7 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
public ExternalProxy GetProxy(Uri destination)
{
IList<string> proxies;
if (GetAutoProxies(destination, out proxies))
if (GetAutoProxies(destination, out var proxies))
{
if (proxies == null)
{
......
......@@ -227,8 +227,7 @@ namespace Titanium.Web.Proxy.Http
internal string GetHeaderValueOrNull(string headerName)
{
HttpHeader header;
if (Headers.TryGetValue(headerName, out header))
if (Headers.TryGetValue(headerName, out var header))
{
return header.Value;
}
......@@ -238,8 +237,7 @@ namespace Titanium.Web.Proxy.Http
internal string SetOrAddHeaderValue(string headerName, string value)
{
HttpHeader header;
if (Headers.TryGetValue(headerName, out header))
if (Headers.TryGetValue(headerName, out var header))
{
header.Value = value;
}
......
using StreamExtended.Network;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
......@@ -25,7 +26,7 @@ namespace Titanium.Web.Proxy.Http
{
nonUniqueResponseHeaders[newHeader.Name].Add(newHeader);
}
//if header is alread in unique header collection then move both to non-unique collection
//if header is already in unique header collection then move both to non-unique collection
else if (headers.ContainsKey(newHeader.Name))
{
var existing = headers[newHeader.Name];
......@@ -46,5 +47,17 @@ namespace Titanium.Web.Proxy.Http
}
}
}
/// <summary>
/// Increase size of buffer and copy existing content to new buffer
/// </summary>
/// <param name="buffer"></param>
/// <param name="size"></param>
private static void ResizeBuffer(ref byte[] buffer, long size)
{
var newBuffer = new byte[size];
Buffer.BlockCopy(buffer, 0, newBuffer, 0, buffer.Length);
buffer = newBuffer;
}
}
}
......@@ -136,10 +136,7 @@ namespace Titanium.Web.Proxy.Http
{
string httpStatus = await ServerConnection.StreamReader.ReadLineAsync();
Version version;
int responseStatusCode;
string responseStatusDescription;
Response.ParseResponseLine(httpStatus, out version, out responseStatusCode, out responseStatusDescription);
Response.ParseResponseLine(httpStatus, out _, out int responseStatusCode, out string responseStatusDescription);
//find if server is willing for expect continue
if (responseStatusCode == (int)HttpStatusCode.Continue
......@@ -180,10 +177,7 @@ namespace Titanium.Web.Proxy.Http
httpStatus = await ServerConnection.StreamReader.ReadLineAsync();
}
Version version;
int statusCode;
string statusDescription;
Response.ParseResponseLine(httpStatus, out version, out statusCode, out statusDescription);
Response.ParseResponseLine(httpStatus, out var version, out int statusCode, out string statusDescription);
Response.HttpVersion = version;
Response.StatusCode = statusCode;
......
......@@ -3,6 +3,7 @@ using System.ComponentModel;
using System.Text;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
......@@ -66,14 +67,8 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public string Host
{
get
{
return Headers.GetHeaderValueOrNull("host");
}
set
{
Headers.SetOrAddHeaderValue("host", value);
}
get => Headers.GetHeaderValueOrNull("host");
set => Headers.SetOrAddHeaderValue("host", value);
}
/// <summary>
......@@ -95,8 +90,7 @@ namespace Titanium.Web.Proxy.Http
return -1;
}
long contentLen;
long.TryParse(headerValue, out contentLen);
long.TryParse(headerValue, out long contentLen);
if (contentLen >= 0)
{
return contentLen;
......@@ -123,14 +117,8 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public string ContentType
{
get
{
return Headers.GetHeaderValueOrNull("content-type");
}
set
{
Headers.SetOrAddHeaderValue("content-type", value);
}
get => Headers.GetHeaderValueOrNull("content-type");
set => Headers.SetOrAddHeaderValue("content-type", value);
}
/// <summary>
......@@ -169,6 +157,8 @@ namespace Titanium.Web.Proxy.Http
}
}
public bool IsMultipartFormData => ContentType?.StartsWith("multipart/form-data") == true;
/// <summary>
/// Request Url
/// </summary>
......@@ -177,7 +167,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Encoding for this request
/// </summary>
public Encoding Encoding => this.GetEncoding();
public Encoding Encoding => HttpHelper.GetEncodingFromContentType(ContentType);
/// <summary>
/// Terminates the underlying Tcp Connection to client after current request
......
......@@ -2,6 +2,7 @@
using System.ComponentModel;
using System.Text;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
......@@ -36,7 +37,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Encoding used in response
/// </summary>
public Encoding Encoding => this.GetResponseCharacterEncoding();
public Encoding Encoding => HttpHelper.GetEncodingFromContentType(ContentType);
/// <summary>
/// Content encoding for this response
......@@ -118,8 +119,7 @@ namespace Titanium.Web.Proxy.Http
return -1;
}
long contentLen;
long.TryParse(headerValue, out contentLen);
long.TryParse(headerValue, out long contentLen);
if (contentLen >= 0)
{
return contentLen;
......
......@@ -28,7 +28,7 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
public string UserName
{
get { return UseDefaultCredentials ? defaultCredentials.Value.UserName : userName; }
get => UseDefaultCredentials ? defaultCredentials.Value.UserName : userName;
set
{
userName = value;
......@@ -45,7 +45,7 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
public string Password
{
get { return UseDefaultCredentials ? defaultCredentials.Value.Password : password; }
get => UseDefaultCredentials ? defaultCredentials.Value.Password : password;
set
{
password = value;
......
using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Org.BouncyCastle.Asn1;
......
......@@ -7,7 +7,6 @@ using System.Linq;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network.Certificate;
......@@ -36,7 +35,7 @@ namespace Titanium.Web.Proxy.Network
{
internal CertificateEngine Engine
{
get { return engine; }
get => engine;
set
{
//For Mono (or Non-Windows) only Bouncy Castle is supported
......@@ -83,7 +82,7 @@ namespace Titanium.Web.Proxy.Network
internal string Issuer
{
get { return issuer ?? defaultRootCertificateIssuer; }
get => issuer ?? defaultRootCertificateIssuer;
set
{
issuer = value;
......@@ -93,7 +92,7 @@ namespace Titanium.Web.Proxy.Network
internal string RootCertificateName
{
get { return rootCertificateName ?? defaultRootRootCertificateName; }
get => rootCertificateName ?? defaultRootRootCertificateName;
set
{
rootCertificateName = value;
......@@ -103,7 +102,7 @@ namespace Titanium.Web.Proxy.Network
internal X509Certificate2 RootCertificate
{
get { return rootCertificate; }
get => rootCertificate;
set
{
ClearRootCertificate();
......
......@@ -2,7 +2,6 @@
using System;
using System.Net;
using System.Net.Sockets;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp
......
......@@ -5,7 +5,6 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
......
using StreamExtended.Network;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
......@@ -8,8 +7,8 @@ using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Helpers.WinHttp;
using Titanium.Web.Proxy.Models;
......@@ -84,8 +83,8 @@ namespace Titanium.Web.Proxy
/// </summary>
public X509Certificate2 RootCertificate
{
get { return CertificateManager.RootCertificate; }
set { CertificateManager.RootCertificate = value; }
get => CertificateManager.RootCertificate;
set => CertificateManager.RootCertificate = value;
}
/// <summary>
......@@ -94,8 +93,8 @@ namespace Titanium.Web.Proxy
/// </summary>
public string RootCertificateIssuerName
{
get { return CertificateManager.Issuer; }
set { CertificateManager.Issuer = value; }
get => CertificateManager.Issuer;
set => CertificateManager.Issuer = value;
}
/// <summary>
......@@ -107,8 +106,8 @@ namespace Titanium.Web.Proxy
/// </summary>
public string RootCertificateName
{
get { return CertificateManager.RootCertificateName; }
set { CertificateManager.RootCertificateName = value; }
get => CertificateManager.RootCertificateName;
set => CertificateManager.RootCertificateName = value;
}
/// <summary>
......@@ -118,7 +117,7 @@ namespace Titanium.Web.Proxy
/// </summary>
public bool TrustRootCertificate
{
get { return trustRootCertificate; }
get => trustRootCertificate;
set
{
trustRootCertificate = value;
......@@ -136,8 +135,8 @@ namespace Titanium.Web.Proxy
/// </summary>
public CertificateEngine CertificateEngine
{
get { return CertificateManager.Engine; }
set { CertificateManager.Engine = value; }
get => CertificateManager.Engine;
set => CertificateManager.Engine = value;
}
/// <summary>
......@@ -242,8 +241,8 @@ namespace Titanium.Web.Proxy
/// </summary>
public Action<Exception> ExceptionFunc
{
get { return exceptionFunc ?? defaultExceptionFunc.Value; }
set { exceptionFunc = value; }
get => exceptionFunc ?? defaultExceptionFunc.Value;
set => exceptionFunc = value;
}
/// <summary>
......
This diff is collapsed.
......@@ -13,7 +13,7 @@
<ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.8.1.3" />
<PackageReference Include="StreamExtended" Version="1.0.81" />
<PackageReference Include="StreamExtended" Version="1.0.110-beta" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
......
......@@ -14,7 +14,7 @@
<copyright>Copyright &#x00A9; Titanium. All rights reserved.</copyright>
<tags></tags>
<dependencies>
<dependency id="StreamExtended" version="1.0.81" />
<dependency id="StreamExtended" version="1.0.110-beta" />
<dependency id="Portable.BouncyCastle" version="1.8.1.3" />
</dependencies>
</metadata>
......
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Portable.BouncyCastle" version="1.8.1.3" targetFramework="net45" />
<package id="StreamExtended" version="1.0.81" targetFramework="net45" />
<package id="StreamExtended" version="1.0.110-beta" targetFramework="net45" />
</packages>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment