using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Contracts; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Text.RegularExpressions; namespace PleaseIgnore.IntelMap { /// /// Series of helper methods to assist with the construction of /// classes within PleaseIgnore.IntelMap. /// /// internal static class IntelExtensions { // The Unix time epoc private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); // The 'unreserved' characters from RFC 3986 private const string Unreserved = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~"; // Convert a number 0...15 to hex private const string HexUpperString = "0123456789ABCDEF"; private const string HexLowerString = "0123456789abcdef"; // Category for Network Tracing public const string WebTraceCategory = "PleaseIgnore.IntelMap"; // Downtime in ticks from beginning of day private const Int64 DowntimeTicks = 11L * TimeSpan.TicksPerHour; // Priority list of IntelChannelStatus private static readonly IntelStatus[] StatusPriority = new IntelStatus[] { IntelStatus.FatalError, IntelStatus.AuthenticationError, IntelStatus.NetworkError, IntelStatus.InvalidPath, IntelStatus.Active, IntelStatus.Waiting }; // The base URL for requests on the intel map server public readonly static Uri BaseUrl = new Uri("http://map.pleaseignore.com/"); // The URL for quering the channel list public readonly static Uri ChannelsUrl = new Uri(BaseUrl, "intelchannels.pl"); // The URL for reporting intel public readonly static Uri ReportUrl = new Uri(BaseUrl, "report.pl"); /// /// Gets the time and date of the most recent scheduled Tranquility /// downtime. /// public static DateTime LastDowntime { get { var nowTicks = DateTime.UtcNow.Ticks; var eveTicks = nowTicks - DowntimeTicks; return new DateTime( eveTicks - eveTicks % TimeSpan.TicksPerDay + DowntimeTicks, DateTimeKind.Utc); } } /// /// Gets the time and date of the next scheduled Tranquility /// downtime. /// public static DateTime NextDowntime { get { return LastDowntime + new TimeSpan(TimeSpan.TicksPerDay); } } /// /// Runs an action against each member of a collection. /// /// /// The type of the elements of . /// /// /// An to process. /// /// /// A function to execute on each member of . /// /// /// The collection after /// has been executed on each member. /// public static IEnumerable ForEach(this IEnumerable source, Action action) { Contract.Requires(source != null, "source"); Contract.Requires(action != null, "action"); foreach (var item in source) { action(item); } return source; } /// /// Converts the value of the current /// object to a standard Unix timestamp. /// /// /// The object to convert /// /// /// The representation of as a Unix /// timestamp, specifically the number of seconds elapsed since /// midnight, 1 Jan 1970 GMT. /// [Pure] public static double ToUnixTime(this DateTime timestamp) { Contract.Ensures(!double.IsInfinity(Contract.Result())); Contract.Ensures(!double.IsNaN(Contract.Result())); return Math.Floor((timestamp.ToUniversalTime() - Epoch).TotalSeconds); } /// /// Picks the highest priority status out of an array of /// . /// /// /// An array of values. /// /// /// The highest priority status from . /// [Pure] public static IntelStatus Combine(params IntelStatus[] array) { Contract.Requires(array != null); Contract.Requires(array.Length > 0); foreach (var status in StatusPriority) { if (array.Any(x => x == status)) { return status; } } return array[0]; } /// /// Tests if a value of the /// enumeration refers to a "running" state. /// /// /// Value of to test. /// /// /// if the value of /// refers to a normal operating state; otherwise, /// if it's in a stopped state. /// [Pure] public static bool IsRunning(this IntelStatus status) { switch (status) { case IntelStatus.Active: case IntelStatus.InvalidPath: case IntelStatus.Starting: case IntelStatus.Waiting: case IntelStatus.NetworkError: case IntelStatus.AuthenticationError: return true; default: return false; } } /// /// Tests if a value of the /// enumeration refers to an "error" state. /// /// /// Value of to test. /// /// /// if the value of /// refers to an error state; otherwise, /// if it's in a stopped state. /// [Pure] public static bool IsError(this IntelStatus status) { switch (status) { case IntelStatus.NetworkError: case IntelStatus.AuthenticationError: case IntelStatus.FatalError: case IntelStatus.InvalidPath: return true; default: return false; } } /// /// Submits a standard POST to an HTTP(S) server. /// /// /// The instance of to use when submitting /// the HTTP POST. /// /// /// The "application/x-www-form-urlencoded" encoded payload to /// send as the POST content. /// /// /// The instance of providing the server's /// response to the POST. /// public static WebResponse Post(this WebRequest webRequest, byte[] payload) { Contract.Requires(webRequest != null, "webRequest"); Contract.Requires(payload != null, "payload"); Contract.Ensures(Contract.Result() != null); return Post(webRequest, payload, 0, payload.Length); } /// /// Submits a standard POST to an HTTP(S) server. /// /// /// The instance of to use when submitting /// the HTTP POST. /// /// /// The "application/x-www-form-urlencoded" encoded payload to /// send as the POST content. /// /// /// The zero-based byte offset in at /// which to begin copying bytes to the server. /// /// /// The number of bytes to be sent to the server. /// /// /// The instance of providing the server's /// response to the POST. /// public static WebResponse Post(this WebRequest webRequest, byte[] payload, int offset, int count) { Contract.Requires(webRequest != null, "webRequest"); Contract.Requires(payload != null, "payload"); Contract.Requires(offset >= 0, "offset"); Contract.Requires(count >= 0, "count"); Contract.Requires(offset + count <= payload.Length); Contract.Ensures(Contract.Result() != null); try { Trace.WriteLine("<< " + Encoding.UTF8.GetString(payload, offset, count), WebTraceCategory); webRequest.Method = "POST"; webRequest.ContentLength = count; webRequest.ContentType = "application/x-www-form-urlencoded"; using (var stream = webRequest.GetRequestStream()) { stream.Write(payload, offset, count); } return webRequest.GetResponse(); } catch (Exception e) { Trace.WriteLine("!! " + e.Message, WebTraceCategory); throw; } } /// /// Submits a standard POST to an HTTP(S) server after encoding /// the name-value pairs. /// /// /// The instance of to use when submitting /// the HTTP POST. /// /// /// A list of name-value pairs to send to the server. /// /// /// The instance of providing the server's /// response to the POST. /// /// /// The name-value pairs provided by /// will be encoded as per the method described in the HTML /// specification (part 17.13.4) after being converted to /// strings by calling . /// public static WebResponse Post(this WebRequest webRequest, IEnumerable> variables) { Contract.Requires(webRequest != null, "webRequest"); Contract.Requires(variables != null, "variables"); Contract.Requires(Contract.ForAll(variables, x => !String.IsNullOrEmpty(x.Key))); Contract.Ensures(Contract.Result() != null); // Compute an upper bounds on the payload length var maxLength = variables.Sum(x => 2 + 9 * x.Key.Length + 9 * (x.Value ?? String.Empty).Length); // Build up the POST payload using (var stream = new MemoryStream(maxLength)) { bool first = true; foreach (var keypair in variables) { // Write a '&' between each variable if (!first) { stream.WriteByte((byte)'&'); } else { first = false; } // Write the variable name WriteUriEncoded(stream, keypair.Key); // Write the '=' between the name and value stream.WriteByte((byte)'='); // Write the variable value WriteUriEncoded(stream, keypair.Value); } return Post(webRequest, stream.ToArray()); } } /// /// Reads the entirety of the response body of a /// and then disposes the instances. /// /// /// The instance of to use when reading /// the response payload. /// /// /// The response payload parsed as a string. /// public static string ReadContent(this WebResponse webResponse) { Contract.Requires(webResponse != null, "webResponse"); Contract.Ensures(Contract.Result() != null); try { using (var stream = webResponse.GetResponseStream()) { using (var reader = new StreamReader(stream)) { var responseData = reader.ReadToEnd(); Trace.WriteLine(">> " + responseData, WebTraceCategory); return responseData; } } } catch (Exception e) { Trace.WriteLine("!! " + e.Message, WebTraceCategory); throw; } finally { webResponse.Close(); } } /// /// Writes the HTML form url encoded form a string to a /// . /// /// /// The instance of to write the data /// string to. /// /// /// The to be encoded. /// public static void WriteUriEncoded(this Stream stream, string dataString) { Contract.Requires(stream != null, "stream"); if (!String.IsNullOrEmpty(dataString)) { var bytes = Encoding.UTF8.GetBytes(dataString); foreach (var current in bytes) { if (current == ' ') { // Escape space as '+' stream.WriteByte((byte)'+'); } else if (Unreserved.Contains((char)current)) { // Characters that should not be escaped stream.WriteByte(current); } else { // Everything else stream.WriteByte((byte)'%'); stream.WriteByte((byte)HexUpperString[current / 16]); stream.WriteByte((byte)HexUpperString[current % 16]); } } } } /// /// Converts a byte array into a hex string using lower-case /// characters for A-F. /// /// /// Byte array to convert to a hex string. /// /// /// The hex string representation of . /// [Pure] public static string ToLowerHexString(this byte[] array) { Contract.Requires(array != null, "array"); Contract.Ensures(Contract.Result() != null); Contract.Ensures(Contract.Result().Length == array.Length * 2); if (array.Length == 0) { return String.Empty; } else { StringBuilder builder = new StringBuilder(array.Length * 2); foreach (var current in array) { builder.Append(HexLowerString[current / 16]); builder.Append(HexLowerString[current % 16]); } return builder.ToString(); } } /// /// Parse an integer found in a Regular Expression match. /// /// /// Regular expression to parse. /// /// /// The integer represented by the string matched by /// . /// /// /// decodes /// according to the invariant culture. /// [Pure] public static int ToInt32(this Capture capture) { Contract.Requires(capture != null, "capture"); return int.Parse(capture.Value, CultureInfo.InvariantCulture); } } }