122 lines
3.6 KiB
C#
122 lines
3.6 KiB
C#
|
using System.Net.Security;
|
||
|
using System.Net.Sockets;
|
||
|
using System.Security.Authentication;
|
||
|
using System.Security.Cryptography.X509Certificates;
|
||
|
using System.Text;
|
||
|
|
||
|
namespace Shoko;
|
||
|
|
||
|
class Gemini
|
||
|
{
|
||
|
public Uri Url { get; set; }
|
||
|
public X509CertificateCollection ClientCertificates = null;
|
||
|
public X509Certificate ServerCertificate
|
||
|
{
|
||
|
get => _serverCertificate;
|
||
|
}
|
||
|
X509Certificate _serverCertificate = null;
|
||
|
TcpClient client;
|
||
|
public SslStream sslStream;
|
||
|
public Gemini(string url, X509CertificateCollection certificates)
|
||
|
{
|
||
|
Url = new Uri(url);
|
||
|
ClientCertificates = certificates;
|
||
|
}
|
||
|
public Gemini(Uri url, X509CertificateCollection certificates)
|
||
|
{
|
||
|
Url = url;
|
||
|
ClientCertificates = certificates;
|
||
|
}
|
||
|
public Gemini(string url)
|
||
|
{
|
||
|
Url = new Uri(url);
|
||
|
}
|
||
|
public Gemini(Uri url)
|
||
|
{
|
||
|
Url = url;
|
||
|
}
|
||
|
bool ValidateServerCertificate(
|
||
|
object sender,
|
||
|
X509Certificate certificate,
|
||
|
X509Chain chain,
|
||
|
SslPolicyErrors sslPolicyErrors)
|
||
|
{
|
||
|
/* if (sslPolicyErrors == SslPolicyErrors.None)
|
||
|
return true; */
|
||
|
// TODO: validate user certificates
|
||
|
//Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
|
||
|
_serverCertificate = certificate;
|
||
|
return true;
|
||
|
}
|
||
|
public void Connect()
|
||
|
{
|
||
|
var port = Url.Port;
|
||
|
if(port < 0) port = 1965;
|
||
|
client = new TcpClient(Url.DnsSafeHost, port);
|
||
|
sslStream = new SslStream(client.GetStream(), false,
|
||
|
new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
|
||
|
try
|
||
|
{
|
||
|
sslStream.AuthenticateAsClient(new SslClientAuthenticationOptions{
|
||
|
TargetHost = Url.Host,
|
||
|
ClientCertificates = ClientCertificates,
|
||
|
EnabledSslProtocols = SslProtocols.Tls12,
|
||
|
//ApplicationProtocols = { new SslApplicationProtocol(), new SslApplicationProtocol("gemini") }
|
||
|
});
|
||
|
}
|
||
|
catch(AuthenticationException)
|
||
|
{
|
||
|
client.Close();
|
||
|
throw;
|
||
|
}
|
||
|
byte[] message = Encoding.UTF8.GetBytes(Url.AbsoluteUri+"\r\n");
|
||
|
|
||
|
sslStream.Write(message);
|
||
|
sslStream.Flush();
|
||
|
}
|
||
|
public string ReadHeader()
|
||
|
{
|
||
|
StringBuilder message = new StringBuilder();
|
||
|
int b;
|
||
|
do
|
||
|
{
|
||
|
b = sslStream.ReadByte();
|
||
|
message.Append((char)b);
|
||
|
} while(b != 10 && b != -1);
|
||
|
return message.ToString();
|
||
|
}
|
||
|
public int Read(byte[] buffer, int offset, int count) => sslStream.Read(buffer, offset, count);
|
||
|
public string ReadAll()
|
||
|
{
|
||
|
byte[] buffer = new byte[2048];
|
||
|
StringBuilder messageData = new StringBuilder();
|
||
|
int bytes = -1;
|
||
|
do
|
||
|
{
|
||
|
bytes = Read(buffer, 0, buffer.Length);
|
||
|
|
||
|
Decoder decoder = Encoding.UTF8.GetDecoder();
|
||
|
char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
|
||
|
decoder.GetChars(buffer, 0, bytes, chars, 0);
|
||
|
messageData.Append(chars);
|
||
|
} while (bytes != 0);
|
||
|
|
||
|
return messageData.ToString();
|
||
|
}
|
||
|
public string ReadBase64()
|
||
|
{
|
||
|
IEnumerable<byte> array = new byte[0];
|
||
|
int bytes = -1;
|
||
|
byte[] buffer = new byte[2048];
|
||
|
do
|
||
|
{
|
||
|
bytes = Read(buffer, 0, buffer.Length);
|
||
|
var buffer2 = new byte[bytes];
|
||
|
Array.Copy(buffer, buffer2, bytes);
|
||
|
array = array.Concat(buffer2);
|
||
|
} while (bytes != 0);
|
||
|
|
||
|
return Convert.ToBase64String(array.ToArray());
|
||
|
}
|
||
|
public void Close() => client.Close();
|
||
|
}
|