-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWebbe.cs
More file actions
205 lines (167 loc) · 7.61 KB
/
Webbe.cs
File metadata and controls
205 lines (167 loc) · 7.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text;
using System.Text.RegularExpressions;
namespace Teto {
/// <summary>
/// A better WebClient that can do multipart and also doesn't explode when it receives something that isn't a 200
/// </summary>
public partial class Webbe {
/// <summary>
/// The Random instance (used for multipart boundaries)
/// </summary>
private Random rng = new Random();
/// <summary>
/// The HTTP headers associated with this client.
/// </summary>
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
/// <summary>
/// Construct a new Webbe client.
/// </summary>
public Webbe() {
Headers["Connection"] = "close";
Headers["User-Agent"] = "Teto/Webbe";
}
/// <summary>
/// Generate a new random multipart-ready boundary.
/// </summary>
/// <returns>The multipart boundary.</returns>
private string GenerateMultipartBoundary() {
byte[] bytes = new byte[16];
rng.NextBytes(bytes);
string unfiltered = Convert.ToBase64String(bytes);
return "---------------------" + new Regex("[^a-zA-Z0-9]", RegexOptions.Compiled).Replace(unfiltered, "");
}
/// <summary>
/// Turn Parameters into a form body.
/// </summary>
/// <param name="parameters">The form parameters to convert.</param>
/// <returns>The bytes for the body.</returns>
private byte[] FormulateFormBody(Parameter[] parameters) {
string bodyString = "";
foreach (Parameter p in parameters) {
bodyString += Uri.EscapeDataString(p.Key) + "=" + Uri.EscapeDataString(p.String()) + "&";
}
return Encoding.UTF8.GetBytes(bodyString.Substring(0, bodyString.Length - 1));
}
/// <summary>
/// Turn Parameters into a multipart body.
/// </summary>
/// <param name="parameters">The form parameters to convert.</param>
/// <param name="boundary">The form boundary.</param>
/// <returns>The bytes for the body.</returns>
private byte[] FormulateMultipartBody(Parameter[] parameters, string boundary) {
List<byte> output = new List<byte>();
foreach (Parameter p in parameters) {
output.AddRange(Encoding.UTF8.GetBytes("--" + boundary + "\r\n"));
output.AddRange(Encoding.UTF8.GetBytes(p.MultipartHeader() + "\r\n\r\n"));
output.AddRange(p.ByteValue());
output.AddRange(Encoding.UTF8.GetBytes("\r\n"));
}
output.AddRange(Encoding.UTF8.GetBytes("--" + boundary + "--\r\n"));
return output.ToArray();
}
/// <summary>
/// Upload a Form to a server.
/// </summary>
/// <param name="method">The HTTP method to use.</param>
/// <param name="url">The URL to request to.</param>
/// <param name="parameters">The parameters to send.</param>
/// <returns>The server's response.</returns>
public Response UploadForm(string method, string url, Parameter[] parameters) {
Headers["Content-Type"] = "application/x-www-form-urlencoded";
byte[] body = FormulateFormBody(parameters);
return Upload(method, url, body);
}
/// <summary>
/// Upload a Multipart form to a server.
/// </summary>
/// <param name="method">The HTTP method to use.</param>
/// <param name="url">The URL to request to.</param>
/// <param name="parameters">The parameters to send.</param>
/// <returns>The server's response.</returns>
public Response UploadMultipart(string method, string url, Parameter[] parameters) {
string boundary = GenerateMultipartBoundary();
Headers["Content-Type"] = "multipart/form-data; boundary=" + boundary;
byte[] body = FormulateMultipartBody(parameters, boundary);
return Upload(method, url, body);
}
/// <summary>
/// Upload a byte body to a server.
/// </summary>
/// <param name="method">The HTTP method to use.</param>
/// <param name="url">The URL to request to.</param>
/// <param name="body">The body data to send.</param>
/// <returns>The server's response.</returns>
private Response Upload(string method, string url, byte[] body) {
Uri uri = new Uri(url);
Headers["Host"] = uri.Host;
Headers["Content-Length"] = body.LongLength.ToString();
using (TcpClient tc = new TcpClient()) {
tc.Connect(uri.Host, uri.Port);
using (Stream ss = uri.Scheme == "https" ? new SslStream(tc.GetStream()) : (Stream)tc.GetStream()) {
if (uri.Scheme == "https") {
((SslStream)ss).AuthenticateAsClient(uri.Host, null, SslProtocols.Tls12, true);
}
// Method header
ss.WriteAllBytes(Encoding.UTF8.GetBytes(method + " " + uri.AbsolutePath + " HTTP/1.1\r\n"));
// Headers
foreach (KeyValuePair<string, string> h in Headers) {
ss.WriteAllBytes(Encoding.UTF8.GetBytes(h.Key + ": " + h.Value + "\r\n"));
}
ss.WriteAllBytes(Encoding.UTF8.GetBytes("\r\n"));
// Body
ss.WriteAllBytes(body);
// Response's code header
string responseCodeString = ReadStreamLine(ss);
int responseCode = int.Parse(responseCodeString.Split(' ')[1]);
// Response headers
Dictionary<string, string> headers = new Dictionary<string, string>();
while (true) {
string header = ReadStreamLine(ss);
if (header == "") {
break;
}
string[] pcs = header.Split(new string[] { ": " }, StringSplitOptions.None);
headers[pcs[0]] = string.Join(": ", pcs.Skip(1));
}
// Response body
List<byte> responseBody = new List<byte>();
while (true) {
int ib = ss.ReadByte();
if (ib == -1) {
break;
}
responseBody.Add((byte)ib);
}
return new Response(responseCode, responseBody.ToArray(), headers);
}
}
}
/// <summary>
/// Read a line from a stream.
/// </summary>
/// <param name="s">The stream to read.</param>
/// <returns>The line read.</returns>
private string ReadStreamLine(Stream s) {
List<byte> output = new List<byte>();
while (true) {
int ib = s.ReadByte();
if (ib == -1) {
return Encoding.UTF8.GetString(output.ToArray());
}
byte b = (byte)ib;
output.Add(b);
string str = Encoding.UTF8.GetString(output.ToArray());
if (str.EndsWith("\r\n")) {
return str.Substring(0, str.Length - 2);
}
}
}
}
}