Request refactor.

This commit is contained in:
ruri
2019-11-04 03:29:55 +01:00
parent 88a6d2ac45
commit 005a94fd67
9 changed files with 568 additions and 376 deletions
@@ -1,4 +1,5 @@
using RuriLib;
using RuriLib.Functions.Files;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
@@ -33,11 +34,11 @@ namespace OpenBullet
{
// Check if name is ok
if (nameTextbox.Text.Trim() == "") { MessageBox.Show("The name cannot be blank"); return; }
else if (nameTextbox.Text != BlockBase.MakeValidFileName(nameTextbox.Text)) { MessageBox.Show("The name contains invalid characters"); return; }
else if (nameTextbox.Text != Files.MakeValidFileName(nameTextbox.Text)) { MessageBox.Show("The name contains invalid characters"); return; }
// Check if category is ok
if (categoryCombobox.Text.Trim() == "") categoryCombobox.Text = "Default";
else if (categoryCombobox.Text != BlockBase.MakeValidFileName(categoryCombobox.Text)) { MessageBox.Show("The category contains invalid characters"); return; }
else if (categoryCombobox.Text != Files.MakeValidFileName(categoryCombobox.Text)) { MessageBox.Show("The category contains invalid characters"); return; }
((ConfigManager)Caller).CreateConfig(nameTextbox.Text, categoryCombobox.Text, authorTextbox.Text);
}
+1 -80
View File
@@ -361,86 +361,7 @@ namespace RuriLib
}
#region File Utilities
/// <summary>
/// Saves a Selenium screenshot to a file with automatically generated name.
/// </summary>
/// <param name="screenshot">The Selenium screenshot</param>
/// <param name="data">The BotData used for path creation</param>
public static void SaveScreenshot(OpenQA.Selenium.Screenshot screenshot, BotData data)
{
var path = MakeScreenshotPath(data);
data.Screenshots.Add(path);
screenshot.SaveAsFile(path);
}
/// <summary>
/// Saves a screenshot to a file with automatically generated name.
/// </summary>
/// <param name="screenshot">The bitmap image</param>
/// <param name="data">The BotData used for path creation</param>
public static void SaveScreenshot(Bitmap screenshot, BotData data)
{
var path = MakeScreenshotPath(data);
data.Screenshots.Add(path);
screenshot.Save(path);
}
/// <summary>
/// Saves a stream to a file with automatically generated name.
/// </summary>
/// <param name="stream">The input stream</param>
/// <param name="data">The BotData used for path creation</param>
public static void SaveScreenshot(MemoryStream stream, BotData data)
{
var path = MakeScreenshotPath(data);
using (var fileStream = File.Create(path)) { stream.CopyTo(fileStream); }
data.Screenshots.Add(path);
}
/// <summary>
/// Builds the path for the screenshot file.
/// </summary>
/// <param name="data">The BotData for path creation</param>
/// <returns>The path of the file to save the screenshot to</returns>
private static string MakeScreenshotPath(BotData data)
{
var folderName = MakeValidFileName(data.ConfigSettings.Name);
var originalFilename = MakeValidFileName(data.Data.Data);
// Check if you have to make the folder
if (!Directory.Exists($"Screenshots\\{folderName}")) Directory.CreateDirectory($"Screenshots\\{folderName}");
// Save the file inside the folder
var filename = GetFirstAvailableFileName($"Screenshots\\{folderName}\\", originalFilename, "bmp");
return $"Screenshots\\{folderName}\\{filename}";
}
/// <summary>
/// Gets the first available name in the given folder by incrementing a number at the end of the filename.
/// </summary>
/// <param name="basePath">The path to the folder</param>
/// <param name="fileName">The name of the file without numbers at the end</param>
/// <param name="extension">The extension of the file</param>
/// <returns>The first available filename (including extension)</returns>
public static string GetFirstAvailableFileName(string basePath, string fileName, string extension)
{
int i;
for (i = 1; File.Exists(basePath + fileName + i + "." + extension); i++) { }
return fileName + i + "." + extension;
}
/// <summary>
/// Fixes the filename to be compatible with the filesystem indicization.
/// </summary>
/// <param name="name">The name of the file</param>
/// <param name="underscore">Whether to replace the unallowed characters with an underscore instead of removing them</param>
/// <returns>The valid filename ready to be saved to disk</returns>
public static string MakeValidFileName(string name, bool underscore = true)
{
string invalidChars = Regex.Escape(new string(Path.GetInvalidFileNameChars()));
string invalidRegStr = string.Format(@"([{0}]*\.+$)|([{0}]+)", invalidChars);
return Regex.Replace(name, invalidRegStr, underscore ? "_" : "");
}
/// <summary>
/// Truncates a string to fit a given size and adds ' [...]' (5 characters) at the end to display that the string would be longer.
+50 -290
View File
@@ -10,6 +10,9 @@ using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Media;
using RuriLib.Functions.Requests;
using RuriLib.Functions.Files;
using MultipartContent = RuriLib.Functions.Requests.MultipartContent;
namespace RuriLib
{
@@ -287,7 +290,7 @@ namespace RuriLib
break;
case RequestType.Standard:
if (CanContainBody(method))
if (Request.CanContainBody(method))
{
writer
.Token("CONTENT")
@@ -358,289 +361,86 @@ namespace RuriLib
{
base.Process(data);
#region Request
// Set base URL
var localUrl = ReplaceValues(url, data);
var cType = ReplaceValues(contentType, data);
var oldJar = data.Cookies;
// Setup
var request = new Request();
request.Setup(data.GlobalSettings, AutoRedirect, data.ConfigSettings.MaxRedirects, AcceptEncoding);
// Create request
HttpRequest request = new HttpRequest();
// Setup options
var timeout = data.GlobalSettings.General.RequestTimeout * 1000;
request.IgnoreProtocolErrors = true;
request.AllowAutoRedirect = autoRedirect;
request.EnableEncodingContent = acceptEncoding;
request.ReadWriteTimeout = timeout;
request.ConnectTimeout = timeout;
request.KeepAlive = true;
request.MaximumAutomaticRedirections = data.ConfigSettings.MaxRedirects;
// Check if it has GET parameters
if (ParseQuery && localUrl.Contains('?') && localUrl.Contains('='))
{
// Remove the query from the base URL
localUrl = ReplaceValues(url.Split('?')[0], data);
data.Log(new LogEntry($"Calling Base URL: {localUrl}", Colors.MediumTurquoise));
// Parse the GET parameters
var getParams = ReplaceValues(url.Split('?')[1], data);
var paramList = getParams.Split('&');
// Build the query, first replace variables in them and encode the parameters
foreach (var par in paramList)
{
var split = par.Split('=');
// Encode them if needed
if (split[0].Contains('%')) split[0] = Uri.EscapeDataString(split[0]);
if (split[1].Contains('%')) split[1] = Uri.EscapeDataString(split[1]);
// Add them to the query
request.AddUrlParam(split[0], split[1]);
data.Log(new LogEntry($"Added Query Parameter: {split[0]} = {split[1]}", Colors.MediumTurquoise));
}
}
else
{
data.Log(new LogEntry($"Calling URL: {localUrl}", Colors.MediumTurquoise));
}
// Set up the Content and Content-Type
HttpContent content = null;
switch (requestType)
var localUrl = ReplaceValues(Url, data);
data.Log(new LogEntry($"Calling URL: {localUrl}", Colors.MediumTurquoise));
// Set content
switch (RequestType)
{
case RequestType.Standard:
var pData = ReplaceValues(Regex.Replace(postData, @"(?<!\\)\\n", Environment.NewLine).Replace(@"\\n", @"\n"), data);
request.SetStandardContent(ReplaceValues(PostData, data), ReplaceValues(ContentType, data), Method, EncodeContent, data.LogBuffer);
break;
if (CanContainBody(method))
{
if (encodeContent)
{
// Very dirty but it works
var nonce = data.Random.Next(1000000, 9999999);
pData = pData.Replace("&", $"{nonce}&{nonce}").Replace("=", $"{nonce}={nonce}");
pData = string.Join("", BlockFunction.SplitInChunks(pData, 2080)
.Select(s => Uri.EscapeDataString(s)))
.Replace($"{nonce}%26{nonce}", "&").Replace($"{nonce}%3D{nonce}", "=");
}
content = new StringContent(pData);
content.ContentType = cType;
data.Log(new LogEntry($"Post Data: {pData}", Colors.MediumTurquoise));
}
case RequestType.BasicAuth:
request.SetBasicAuth(ReplaceValues(AuthUser, data), ReplaceValues(AuthPass, data));
break;
case RequestType.Multipart:
var bdry = multipartBoundary != "" ? ReplaceValues(multipartBoundary, data) : GenerateMultipartBoundary();
content = new Extreme.Net.MultipartContent(bdry);
var mContent = content as Extreme.Net.MultipartContent;
data.Log(new LogEntry($"Content-Type: multipart/form-data; boundary={bdry}", Colors.MediumTurquoise));
data.Log(new LogEntry("Multipart Data:", Colors.MediumTurquoise));
data.Log(new LogEntry(bdry, Colors.MediumTurquoise));
foreach (var c in MultipartContents)
{
var rValue = ReplaceValues(c.Value, data);
var rName = ReplaceValues(c.Name, data);
var rContentType = ReplaceValues(c.ContentType, data);
if (c.Type == MultipartContentType.String)
var contents = MultipartContents.Select(m =>
new MultipartContent()
{
mContent.Add(new StringContent(rValue), rName);
data.Log(new LogEntry($"Content-Disposition: form-data; name=\"{rName}\"{Environment.NewLine}{Environment.NewLine}{rValue}", Colors.MediumTurquoise));
}
else if (c.Type == MultipartContentType.File)
{
mContent.Add(new FileContent(rValue), rName, rValue, rContentType);
data.Log(new LogEntry($"Content-Disposition: form-data; name=\"{rName}\"; filename=\"{rValue}\"{Environment.NewLine}Content-Type: {rContentType}{Environment.NewLine}{Environment.NewLine}[FILE CONTENT OMITTED]", Colors.MediumTurquoise));
}
data.Log(new LogEntry(bdry, Colors.MediumTurquoise));
}
break;
default:
Name = ReplaceValues(m.Name, data),
Value = ReplaceValues(m.Value, data),
ContentType = ReplaceValues(m.Value, data),
Type = m.Type
});
request.SetMultipartContent(contents, ReplaceValues(MultipartBoundary, data), data.LogBuffer);
break;
}
// Set proxy
if (data.UseProxies)
{
request.Proxy = data.Proxy.GetClient();
try
{
request.Proxy.ReadWriteTimeout = timeout;
request.Proxy.ConnectTimeout = timeout;
request.Proxy.Username = data.Proxy.Username;
request.Proxy.Password = data.Proxy.Password;
}
catch { }
request.SetProxy(data.Proxy);
}
// Set headers
data.Log(new LogEntry("Sent Headers:", Colors.DarkTurquoise));
// var fixedNames = Enum.GetNames(typeof(HttpHeader)).Select(n => n.ToLower());
foreach (var header in CustomHeaders)
{
try
{
var key = ReplaceValues(header.Key, data);
var replacedKey = key.Replace("-", "").ToLower(); // Used to compare with the HttpHeader enum
var val = ReplaceValues(header.Value, data);
var headers = CustomHeaders.Select( h =>
new KeyValuePair<string, string> (ReplaceValues(h.Key, data), ReplaceValues(h.Value, data))
).ToDictionary(h => h.Key, h => h.Value);
request.SetHeaders(CustomHeaders, AcceptEncoding, data.LogBuffer);
if (replacedKey == "contenttype" && content != null) { continue; } // Disregard additional Content-Type headers
if (replacedKey == "acceptencoding" && acceptEncoding) { continue; } // Disregard additional Accept-Encoding headers
// else if (fixedNames.Contains(replacedKey)) request.AddHeader((HttpHeader)Enum.Parse(typeof(HttpHeader), replacedKey, true), val);
else request.AddHeader(key, val);
// Set cookies
data.Log(new LogEntry("Sent Cookies:", Colors.MediumTurquoise));
data.Log(new LogEntry(key + ": " + val, Colors.MediumTurquoise));
}
catch { }
}
// Add the authorization header on a Basic Auth request
if (requestType == RequestType.BasicAuth)
{
var usr = ReplaceValues(authUser, data);
var pwd = ReplaceValues(authPass, data);
var auth = "Basic " + (usr + ":" + pwd).ToBase64();
request.AddHeader("Authorization", auth);
data.Log(new LogEntry($"Authorization: {auth}", Colors.MediumTurquoise));
}
// Add the content-type header
if (CanContainBody(method) && content != null && requestType == RequestType.Standard)
{
data.Log(new LogEntry($"Content-Type: {cType}", Colors.MediumTurquoise));
}
// Add new user-defined custom cookies to the bot's cookie jar
request.Cookies = new CookieDictionary();
foreach (var cookie in CustomCookies)
foreach (var cookie in CustomCookies) // Add new user-defined custom cookies to the bot's cookie jar
data.Cookies[ReplaceValues(cookie.Key, data)] = ReplaceValues(cookie.Value, data);
// Set cookies from the bot's cookie jar to the request's CookieDictionary
data.Log(new LogEntry("Sent Cookies:", Colors.MediumTurquoise));
foreach (var cookie in data.Cookies)
{
request.Cookies.Add(cookie.Key, cookie.Value);
data.Log(new LogEntry($"{cookie.Key}: {cookie.Value}", Colors.MediumTurquoise));
}
request.SetCookies(data.Cookies, data.LogBuffer);
// End the request part
data.LogNewLine();
#endregion
#region Response
// Create the response
HttpResponse response = null;
try
// Perform the request
(data.Address, data.ResponseCode, data.ResponseHeaders, data.Cookies) = request.Perform(localUrl, Method, data.ConfigSettings.IgnoreResponseErrors, data.LogBuffer);
// Save the response content
switch (ResponseType)
{
// Get response
response = request.Raw(method, localUrl, content);
var responseString = "";
case ResponseType.String:
data.ResponseSource = request.SaveString(ReadResponseSource, data.ResponseHeaders, data.LogBuffer);
break;
// Get address
data.Address = response.Address.ToString();
data.Log(new LogEntry("Address: " + data.Address, Colors.Cyan));
// Get code
data.ResponseCode = ((int)response.StatusCode).ToString();
data.Log(new LogEntry($"Response code: {data.ResponseCode} ({response.StatusCode})", Colors.Cyan));
// Get headers
data.Log(new LogEntry("Received headers:", Colors.DeepPink));
var headerList = new List<KeyValuePair<string, string>>();
var receivedHeaders = response.EnumerateHeaders();
data.ResponseHeaders.Clear();
while (receivedHeaders.MoveNext())
{
var header = receivedHeaders.Current;
data.ResponseHeaders.Add(header.Key, header.Value);
data.Log(new LogEntry($"{header.Key}: {header.Value}", Colors.LightPink));
}
if (!response.ContainsHeader(HttpHeader.ContentLength) && ResponseType != ResponseType.File)
{
responseString = response.ToString(); // Read the stream
if (data.ResponseHeaders.ContainsKey("Content-Encoding") && data.ResponseHeaders["Content-Encoding"].Contains("gzip"))
case ResponseType.File:
if (SaveAsScreenshot)
{
data.ResponseHeaders["Content-Length"] = GZip.Zip(responseString).Length.ToString();
Files.SaveScreenshot(request.GetResponseStream(), data); // Read the stream
data.Log(new LogEntry("File saved as screenshot", Colors.Green));
}
else
{
data.ResponseHeaders["Content-Length"] = responseString.Length.ToString();
request.SaveFile(ReplaceValues(DownloadPath, data), data.LogBuffer);
}
break;
data.Log(new LogEntry($"Content-Length: {data.ResponseHeaders["Content-Length"]}", Colors.LightPink));
}
// Get cookies
data.Log(new LogEntry("Received cookies:", Colors.Goldenrod));
data.Cookies = response.Cookies;
foreach (var cookie in response.Cookies)
{
// If the cookie was already present before, don't log it
if (oldJar.ContainsKey(cookie.Key) && oldJar[cookie.Key] == cookie.Value) continue;
data.Log(new LogEntry($"{cookie.Key}: {cookie.Value}", Colors.LightGoldenrodYellow));
}
// Save the response content
switch (responseType)
{
case ResponseType.String:
data.Log(new LogEntry("Response Source:", Colors.Green));
if (readResponseSource)
{
if (responseString == "") responseString = response.ToString(); // Read the stream if you didn't already read it
data.ResponseSource = responseString;
data.Log(new LogEntry(data.ResponseSource, Colors.GreenYellow));
}
else
{
data.ResponseSource = "";
data.Log(new LogEntry("[SKIPPED]", Colors.GreenYellow));
}
break;
case ResponseType.File:
if (SaveAsScreenshot)
{
SaveScreenshot(response.ToMemoryStream(), data); // Read the stream
data.Log(new LogEntry("File saved as screenshot", Colors.Green));
}
else
{
var filePath = ReplaceValues(downloadPath, data);
var dirName = Path.GetDirectoryName(filePath);
if (dirName != "") dirName += Path.DirectorySeparatorChar.ToString();
var fileName = Path.GetFileNameWithoutExtension(filePath);
var fileExtension = Path.GetExtension(filePath);
var sanitizedPath = $"{dirName}{MakeValidFileName(fileName)}{fileExtension}";
using (var stream = File.Create(sanitizedPath)) { response.ToMemoryStream().CopyTo(stream); } // Read the stream
data.Log(new LogEntry("File saved as " + sanitizedPath, Colors.Green));
}
break;
default:
break;
}
default:
break;
}
catch (Exception ex)
{
data.Log(new LogEntry(ex.Message, Colors.White));
if (ex.GetType() == typeof(HttpException))
{
data.ResponseCode = ((HttpException)ex).HttpStatusCode.ToString();
data.Log(new LogEntry("Status code: " + data.ResponseCode, Colors.Cyan));
}
if (!data.ConfigSettings.IgnoreResponseErrors) throw;
}
#endregion
}
#region Custom Cookies, Headers and Multipart Contents
@@ -746,45 +546,5 @@ namespace RuriLib
}
#endregion
/// <summary>
/// Generates a random string to be used for boundary.
/// </summary>
/// <returns>The random 16-character string</returns>
internal static string GenerateMultipartBoundary()
{
StringBuilder builder = new StringBuilder();
Random random = new Random();
char ch;
for (int i = 0; i < 16; i++)
{
ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));
builder.Append(ch);
}
return $"------WebKitFormBoundary{builder.ToString().ToLower()}";
}
private static bool CanContainBody(HttpMethod method)
{
return method == HttpMethod.POST || method == HttpMethod.PUT || method == HttpMethod.DELETE;
}
}
/// <summary>
/// Represents a Multipart Content
/// </summary>
public class MultipartContent
{
/// <summary>The type of multipart content.</summary>
public MultipartContentType Type { get; set; } = MultipartContentType.String;
/// <summary>The name of the multipart content.</summary>
public string Name { get; set; } = "";
/// <summary>The value of the multipart content (a string value or a file path).</summary>
public string Value { get; set; } = "";
/// <summary>The Content-Type of the file content.</summary>
public string ContentType { get; set; } = "";
}
}
+2 -1
View File
@@ -2,6 +2,7 @@
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Interactions;
using RuriLib.Functions.Files;
using RuriLib.LS;
using RuriLib.ViewModels;
using System;
@@ -217,7 +218,7 @@ namespace RuriLib
case BrowserAction.Screenshot:
var image = data.Driver.GetScreenshot();
SaveScreenshot(image, data);
Files.SaveScreenshot(image, data);
break;
case BrowserAction.SwitchToTab:
+2 -1
View File
@@ -1,4 +1,5 @@
using OpenQA.Selenium;
using RuriLib.Functions.Files;
using RuriLib.LS;
using System;
using System.Collections.Generic;
@@ -299,7 +300,7 @@ namespace RuriLib
case ElementAction.Screenshot:
var image = GetElementScreenShot(data.Driver, element);
SaveScreenshot(image, data);
Files.SaveScreenshot(image, data);
break;
case ElementAction.SwitchToFrame:
+3 -2
View File
@@ -1,5 +1,6 @@
using Extreme.Net;
using Newtonsoft.Json;
using RuriLib.Functions.Requests;
using System;
using System.Drawing;
using System.Drawing.Imaging;
@@ -46,7 +47,7 @@ namespace RuriLib.CaptchaServices
// Create task
HttpRequest request = new HttpRequest();
request.AddHeader(HttpHeader.Accept, "application/json");
var content = new Extreme.Net.MultipartContent(BlockRequest.GenerateMultipartBoundary());
var content = new Extreme.Net.MultipartContent(Request.GenerateMultipartBoundary());
content.Add(new Extreme.Net.StringContent(User), "username");
content.Add(new Extreme.Net.StringContent(Pass), "password");
content.Add(new Extreme.Net.StringContent("4"), "type");
@@ -97,7 +98,7 @@ namespace RuriLib.CaptchaServices
// Create task
HttpRequest request = new HttpRequest();
request.AddHeader(HttpHeader.Accept, "application/json");
Extreme.Net.MultipartContent content = new Extreme.Net.MultipartContent(BlockRequest.GenerateMultipartBoundary());
Extreme.Net.MultipartContent content = new Extreme.Net.MultipartContent(Request.GenerateMultipartBoundary());
content.Add(new Extreme.Net.StringContent(User), "username");
content.Add(new Extreme.Net.StringContent(Pass), "password");
content.Add(new Extreme.Net.StringContent($"base64:{GetBase64(bitmap, ImageFormat.Jpeg)}"), "captchafile");
+98
View File
@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace RuriLib.Functions.Files
{
/// <summary>
/// Provides methods to work with files.
/// </summary>
public static class Files
{
/// <summary>
/// Saves a Selenium screenshot to a file with automatically generated name.
/// </summary>
/// <param name="screenshot">The Selenium screenshot</param>
/// <param name="data">The BotData used for path creation</param>
public static void SaveScreenshot(OpenQA.Selenium.Screenshot screenshot, BotData data)
{
var path = MakeScreenshotPath(data);
data.Screenshots.Add(path);
screenshot.SaveAsFile(path);
}
/// <summary>
/// Saves a screenshot to a file with automatically generated name.
/// </summary>
/// <param name="screenshot">The bitmap image</param>
/// <param name="data">The BotData used for path creation</param>
public static void SaveScreenshot(Bitmap screenshot, BotData data)
{
var path = MakeScreenshotPath(data);
data.Screenshots.Add(path);
screenshot.Save(path);
}
/// <summary>
/// Saves a stream to a file with automatically generated name.
/// </summary>
/// <param name="stream">The input stream</param>
/// <param name="data">The BotData used for path creation</param>
public static void SaveScreenshot(MemoryStream stream, BotData data)
{
var path = MakeScreenshotPath(data);
using (var fileStream = File.Create(path)) { stream.CopyTo(fileStream); }
data.Screenshots.Add(path);
}
/// <summary>
/// Builds the path for the screenshot file.
/// </summary>
/// <param name="data">The BotData for path creation</param>
/// <returns>The path of the file to save the screenshot to</returns>
private static string MakeScreenshotPath(BotData data)
{
var folderName = MakeValidFileName(data.ConfigSettings.Name);
var originalFilename = MakeValidFileName(data.Data.Data);
// Check if you have to make the folder
if (!Directory.Exists($"Screenshots\\{folderName}")) Directory.CreateDirectory($"Screenshots\\{folderName}");
// Save the file inside the folder
var filename = GetFirstAvailableFileName($"Screenshots\\{folderName}\\", originalFilename, "bmp");
return $"Screenshots\\{folderName}\\{filename}";
}
/// <summary>
/// Gets the first available name in the given folder by incrementing a number at the end of the filename.
/// </summary>
/// <param name="basePath">The path to the folder</param>
/// <param name="fileName">The name of the file without numbers at the end</param>
/// <param name="extension">The extension of the file</param>
/// <returns>The first available filename (including extension)</returns>
public static string GetFirstAvailableFileName(string basePath, string fileName, string extension)
{
int i;
for (i = 1; File.Exists(basePath + fileName + i + "." + extension); i++) { }
return fileName + i + "." + extension;
}
/// <summary>
/// Fixes the filename to be compatible with the filesystem indicization.
/// </summary>
/// <param name="name">The name of the file</param>
/// <param name="underscore">Whether to replace the unallowed characters with an underscore instead of removing them</param>
/// <returns>The valid filename ready to be saved to disk</returns>
public static string MakeValidFileName(string name, bool underscore = true)
{
string invalidChars = Regex.Escape(new string(Path.GetInvalidFileNameChars()));
string invalidRegStr = string.Format(@"([{0}]*\.+$)|([{0}]+)", invalidChars);
return Regex.Replace(name, invalidRegStr, underscore ? "_" : "");
}
}
}
+406
View File
@@ -0,0 +1,406 @@
using Extreme.Net;
using RuriLib.Functions.Formats;
using RuriLib.Models;
using RuriLib.ViewModels;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using RuriLib.Functions.Files;
using System.Windows.Media;
namespace RuriLib.Functions.Requests
{
/// <summary>
/// Provides methods to easily perform Extreme.NET requests.
/// </summary>
public class Request
{
private HttpRequest request = new HttpRequest();
private HttpContent content = null;
private Dictionary<string, string> oldCookies = new Dictionary<string, string>();
private int timeout = 60000;
private string url = "";
private string contentType = "";
private string authorization = "";
private HttpResponse response = null;
private bool hasContentLength = true;
private bool isGZipped = false;
/// <summary>
/// Sets up the request options.
/// </summary>
/// <param name="settings">The RuriLib settings</param>
/// <param name="autoRedirect">Whether to perform automatic redirection</param>
/// <param name="acceptEncoding"></param>
/// <param name="maxRedirects"></param>
/// <returns></returns>
public Request Setup(RLSettingsViewModel settings, bool autoRedirect = true, int maxRedirects = 8, bool acceptEncoding = true)
{
// Setup options
timeout = settings.General.RequestTimeout * 1000;
request.IgnoreProtocolErrors = true;
request.AllowAutoRedirect = autoRedirect;
request.EnableEncodingContent = acceptEncoding;
request.ReadWriteTimeout = timeout;
request.ConnectTimeout = timeout;
request.KeepAlive = true;
request.MaximumAutomaticRedirections = maxRedirects;
return this;
}
/// <summary>
/// Sets a standard content for the request.
/// </summary>
/// <param name="postData">The content string</param>
/// <param name="contentType">The content type</param>
/// <param name="method">The HTTP method</param>
/// <param name="encodeContent">Whether to URLencode the content automatically</param>
/// <param name="log">The log (if any)</param>
/// <returns>The request itself</returns>
public Request SetStandardContent(string postData, string contentType,
HttpMethod method = HttpMethod.POST, bool encodeContent = false, List<LogEntry> log = null)
{
this.contentType = contentType;
var pData = Regex.Replace(postData, @"(?<!\\)\\n", Environment.NewLine).Replace(@"\\n", @"\n");
if (CanContainBody(method))
{
if (encodeContent)
{
// Very dirty but it works
Random rand = new Random();
var nonce = rand.Next(1000000, 9999999);
pData = pData.Replace("&", $"{nonce}&{nonce}").Replace("=", $"{nonce}={nonce}");
pData = string.Join("", BlockFunction.SplitInChunks(pData, 2080)
.Select(s => Uri.EscapeDataString(s)))
.Replace($"{nonce}%26{nonce}", "&").Replace($"{nonce}%3D{nonce}", "=");
}
content = new StringContent(pData);
content.ContentType = contentType;
if (log != null) log.Add(new LogEntry($"Post Data: {pData}", Colors.MediumTurquoise));
}
return this;
}
/// <summary>
/// Sets the authorization header for basic authorization.
/// </summary>
/// <param name="username">The username</param>
/// <param name="password">The password</param>
/// <returns>The request itself</returns>
public Request SetBasicAuth(string username, string password)
{
authorization = "Basic " + (username + ":" + password).ToBase64();
return this;
}
/// <summary>
/// Sets a multipart content collection for the request.
/// </summary>
/// <param name="contents">A collection of multipart contents</param>
/// <param name="boundary">A boundary (if empty, one will be generated automatically)</param>
/// <param name="log">The log (if any)</param>
/// <returns>The request itself</returns>
public Request SetMultipartContent(IEnumerable<MultipartContent> contents, string boundary = "", List<LogEntry> log = null)
{
var bdry = boundary != "" ? boundary : GenerateMultipartBoundary();
content = new Extreme.Net.MultipartContent(bdry);
var mContent = content as Extreme.Net.MultipartContent;
if (log != null)
{
log.Add(new LogEntry($"Content-Type: multipart/form-data; boundary={bdry}", Colors.MediumTurquoise));
log.Add(new LogEntry("Multipart Data:", Colors.MediumTurquoise));
log.Add(new LogEntry(bdry, Colors.MediumTurquoise));
}
foreach (var c in contents)
{
if (c.Type == MultipartContentType.String)
{
mContent.Add(new StringContent(c.Value), c.Name);
if (log != null) log.Add(new LogEntry($"Content-Disposition: form-data; name=\"{c.Name}\"{Environment.NewLine}{Environment.NewLine}{c.Value}", Colors.MediumTurquoise));
}
else if (c.Type == MultipartContentType.File)
{
mContent.Add(new FileContent(c.Value), c.Name, c.Value, c.ContentType);
if (log != null) log.Add(new LogEntry($"Content-Disposition: form-data; name=\"{c.Name}\"; filename=\"{c.Value}\"{Environment.NewLine}Content-Type: {c.ContentType}{Environment.NewLine}{Environment.NewLine}[FILE CONTENT OMITTED]", Colors.MediumTurquoise));
}
if (log != null) log.Add(new LogEntry(bdry, Colors.MediumTurquoise));
}
return this;
}
/// <summary>
/// Sets a proxy to be used during the request.
/// </summary>
/// <param name="proxy">The proxy</param>
/// <returns>The request itself</returns>
public Request SetProxy(CProxy proxy)
{
request.Proxy = proxy.GetClient();
request.Proxy.ReadWriteTimeout = timeout;
request.Proxy.ConnectTimeout = timeout;
request.Proxy.Username = proxy.Username;
request.Proxy.Password = proxy.Password;
return this;
}
/// <summary>
/// Sets the cookies to be sent in the request.
/// </summary>
/// <param name="cookies">The cookie dictionary</param>
/// <param name="log">The log (if any)</param>
/// <returns>The request itself</returns>
public Request SetCookies(Dictionary<string, string> cookies, List<LogEntry> log = null)
{
oldCookies = cookies;
request.Cookies = new CookieDictionary();
foreach (var cookie in cookies)
{
request.Cookies.Add(cookie.Key, cookie.Value);
log.Add(new LogEntry($"{cookie.Key}: {cookie.Value}", Colors.MediumTurquoise));
}
return this;
}
/// <summary>
/// Sets the headers to be sent in the request.
/// </summary>
/// <param name="headers">The headers dictionary</param>
/// <param name="acceptEncoding">Whether to set the Accept-Encoding header automatically</param>
/// <param name="log">The log (if any)</param>
/// <returns>The request itself</returns>
public Request SetHeaders(Dictionary<string, string> headers, bool acceptEncoding = true, List<LogEntry> log = null)
{
// Set headers
foreach (var header in headers)
{
try
{
var replacedKey = header.Key.Replace("-", "").ToLower(); // Used to compare with the HttpHeader enum
if (replacedKey == "contenttype" && content != null) { continue; } // Disregard additional Content-Type headers
if (replacedKey == "acceptencoding" && acceptEncoding) { continue; } // Disregard additional Accept-Encoding headers
// else if (fixedNames.Contains(replacedKey)) request.AddHeader((HttpHeader)Enum.Parse(typeof(HttpHeader), replacedKey, true), val);
else request.AddHeader(header.Key, header.Value);
if (log != null) log.Add(new LogEntry($"{header.Key}: {header.Value}", Colors.MediumTurquoise));
}
catch { }
}
// Add the authorization header on a Basic Auth request
if (authorization != "")
{
request.AddHeader("Authorization", authorization);
if (log != null) log.Add(new LogEntry($"Authorization: {authorization}", Colors.MediumTurquoise));
}
// Add the content-type header
if (contentType != "")
{
log.Add(new LogEntry($"Content-Type: {contentType}", Colors.MediumTurquoise));
}
return this;
}
/// <summary>
/// Performs a request.
/// </summary>
/// <param name="url">The URL</param>
/// <param name="method">The HTTP method</param>
/// <param name="ignoreErrors">Whether to ignore response errors</param>
/// <param name="log">The log (if any)</param>
/// <returns>A 4-tuple containing Address, Response code, Headers and Cookies.</returns>
public (string, string, Dictionary<string, string>, Dictionary<string, string>) Perform(string url, HttpMethod method,
bool ignoreErrors = false, List<LogEntry> log = null)
{
var address = "";
var responseCode = "0";
var headers = new Dictionary<string, string>();
var cookies = oldCookies;
try
{
// Get response
HttpResponse response = request.Raw(method, url, content);
// Get address
address = response.Address.ToString();
if (log != null) log.Add(new LogEntry("Address: " + address, Colors.Cyan));
// Get code
responseCode = ((int)response.StatusCode).ToString();
if (log != null) log.Add(new LogEntry($"Response code: {responseCode} ({response.StatusCode})", Colors.Cyan));
// Get headers
if (log != null) log.Add(new LogEntry("Received headers:", Colors.DeepPink));
var headersList = new List<KeyValuePair<string, string>>();
var receivedHeaders = response.EnumerateHeaders();
while (receivedHeaders.MoveNext())
{
var header = receivedHeaders.Current;
headersList.Add(new KeyValuePair<string, string>(header.Key, header.Value));
if (log != null) log.Add(new LogEntry($"{header.Key}: {header.Value}", Colors.LightPink));
}
headers = headersList.ToDictionary(h => h.Key, h => h.Value);
hasContentLength = headers.ContainsKey("Content-Length");
isGZipped = headers.ContainsKey("Content-Encoding") && headers["Content-Encoding"].Contains("gzip");
// Get cookies
cookies = response.Cookies;
if (log != null)
{
log.Add(new LogEntry("Received cookies:", Colors.Goldenrod));
foreach (var cookie in response.Cookies)
{
// If the cookie was already present before, don't log it
if (oldCookies.ContainsKey(cookie.Key) && oldCookies[cookie.Key] == cookie.Value) continue;
log.Add(new LogEntry($"{cookie.Key}: {cookie.Value}", Colors.LightGoldenrodYellow));
}
}
}
catch (Exception ex)
{
if (log != null) log.Add(new LogEntry(ex.Message, Colors.White));
if (ex.GetType() == typeof(HttpException))
{
responseCode = ((HttpException)ex).HttpStatusCode.ToString();
if (log != null) log.Add(new LogEntry("Status code: " + responseCode, Colors.Cyan));
}
if (!ignoreErrors) throw;
}
return (address, responseCode, headers, cookies);
}
/// <summary>
/// Saves the response to a string.
/// </summary>
/// <param name="readResponseSource"></param>
/// <param name="headers">The headers, to add the Content-Length header (if needed)</param>
/// <param name="log">The log (if any)</param>
/// <returns>The response source as a string</returns>
public string SaveString(bool readResponseSource, Dictionary<string, string> headers = null, List<LogEntry> log = null)
{
var source = "";
var responseString = response.ToString();
if (log != null) log.Add(new LogEntry("Response Source:", Colors.Green));
if (readResponseSource)
{
source = responseString;
if (log != null) log.Add(new LogEntry(source, Colors.GreenYellow));
}
else
{
if (log != null) log.Add(new LogEntry("[SKIPPED]", Colors.GreenYellow));
}
if (!hasContentLength && headers != null)
{
if (isGZipped)
{
headers["Content-Length"] = GZip.Zip(responseString).Length.ToString();
}
else
{
headers["Content-Length"] = responseString.Length.ToString();
}
if (log != null) log.Add(new LogEntry($"Calculated header: Content-Length: {headers["Content-Length"]}", Colors.LightPink));
}
return source;
}
/// <summary>
/// Reads the response to a MemoryStream.
/// </summary>
/// <returns>The MemoryStream</returns>
public MemoryStream GetResponseStream()
{
return response.ToMemoryStream();
}
/// <summary>
/// Saves the response content to a file.
/// </summary>
/// <param name="path">The file path on disk</param>
/// <param name="log">The log (if any)</param>
public void SaveFile(string path, List<LogEntry> log = null)
{
var dirName = Path.GetDirectoryName(path);
if (dirName != "") dirName += Path.DirectorySeparatorChar.ToString();
var fileName = Path.GetFileNameWithoutExtension(path);
var fileExtension = Path.GetExtension(path);
var sanitizedPath = $"{dirName}{Files.Files.MakeValidFileName(fileName)}{fileExtension}";
using (var stream = File.Create(sanitizedPath)) { response.ToMemoryStream().CopyTo(stream); } // Read the stream
if (log != null) log.Add(new LogEntry("File saved as " + sanitizedPath, Colors.Green));
}
/// <summary>
/// Generates a random string to be used for boundary.
/// </summary>
/// <returns>The random 16-character string</returns>
internal static string GenerateMultipartBoundary()
{
StringBuilder builder = new StringBuilder();
Random random = new Random();
char ch;
for (int i = 0; i < 16; i++)
{
ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));
builder.Append(ch);
}
return $"------WebKitFormBoundary{builder.ToString().ToLower()}";
}
/// <summary>
/// Checks if an HTTP method can have a body.
/// </summary>
/// <param name="method">The HTTP method</param>
/// <returns>True if the method allows a body</returns>
public static bool CanContainBody(HttpMethod method)
{
return method == HttpMethod.POST || method == HttpMethod.PUT || method == HttpMethod.DELETE;
}
}
/// <summary>
/// Represents a Multipart Content
/// </summary>
public struct MultipartContent
{
/// <summary>The type of multipart content.</summary>
public MultipartContentType Type;
/// <summary>The name of the multipart content.</summary>
public string Name;
/// <summary>The value of the multipart content (a string value or a file path).</summary>
public string Value;
/// <summary>The Content-Type of the file content.</summary>
public string ContentType;
}
}
+3
View File
@@ -188,6 +188,7 @@
<Private>True</Private>
<Private>True</Private>
</Reference>
<Reference Include="System.Web" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
@@ -216,6 +217,8 @@
<Compile Include="Functions\Crypto\Crypto.cs" />
<Compile Include="Functions\Download\Download.cs" />
<Compile Include="Functions\Encoding\Encode.cs" />
<Compile Include="Functions\Files\Files.cs" />
<Compile Include="Functions\Requests\Request.cs" />
<Compile Include="Functions\Time\Time.cs" />
<Compile Include="GZip.cs" />
<Compile Include="LoliScript\Exceptions\BlockProcessingException.cs" />