diff --git a/AS1024.CommunityDocumentationPage/Controllers/HomeController.cs b/AS1024.CommunityDocumentationPage/Controllers/HomeController.cs index 8fa29aa..59028f8 100644 --- a/AS1024.CommunityDocumentationPage/Controllers/HomeController.cs +++ b/AS1024.CommunityDocumentationPage/Controllers/HomeController.cs @@ -1,7 +1,6 @@ using AS1024.CommunityDocumentationPage.Models; using Microsoft.AspNetCore.Mvc; using System.Diagnostics; -using NetBox; using System.Net.Http; using System.Net.Http.Headers; using Newtonsoft.Json; @@ -31,15 +30,16 @@ namespace AS1024.CommunityDocumentationPage.Controllers return View(filtered); } - private static async Task ReadAPI(string token) + [NonAction] + private async Task ReadAPI(string token) { try { HttpClient client = new(); client.DefaultRequestHeaders.Clear(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", token); - var result = await client.GetAsync("https://netbox.as1024.net/api/ipam/route-targets/"); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", token); + var result = await client.GetAsync(BuildNetBoxURI()); var stringResult = await result.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(stringResult); } catch @@ -48,6 +48,18 @@ namespace AS1024.CommunityDocumentationPage.Controllers } } + [NonAction] + protected Uri BuildNetBoxURI() + { + var endUrl = new UriBuilder(); + + endUrl.Path = "/api/ipam/route-targets"; + endUrl.Host = Configuration["NetBoxHost"]; + endUrl.Scheme = "https"; + + return endUrl.Uri; + } + public IActionResult Privacy() { return View(); diff --git a/AS1024.CommunityDocumentationPage/Models/NetBox.cs b/AS1024.CommunityDocumentationPage/Models/NetBox.cs deleted file mode 100644 index 2c26527..0000000 --- a/AS1024.CommunityDocumentationPage/Models/NetBox.cs +++ /dev/null @@ -1,2939 +0,0 @@ -/* - _ _ _ ____ -| \ | | ___| |_| __ ) _____ __ -| \| |/ _ \ __| _ \ / _ \ \/ / -| |\ | __/ |_| |_) | (_) > < -|_| \_|\___|\__|____/ \___/_/\_\ v4.1.4 by @aloneguid - -https://github.com/aloneguid/netbox -*/ - - - -// FILE: src/NetBox/WebUtility.cs - -namespace NetBox -{ - using global::System; - using global::System.Text; - - /// - /// This class is ported from .NET 4.6 to support URL encoding/decoding functionality which is missing in .NET Standard - /// - static class WebUtility - { - public static string UrlEncode(string value) - { - if (value == null) - return null; - - byte[] bytes = Encoding.UTF8.GetBytes(value); - return Encoding.UTF8.GetString(UrlEncode(bytes, 0, bytes.Length, false /* alwaysCreateNewReturnValue */)); - } - - public static string UrlDecode(string encodedValue) - { - if (encodedValue == null) - return null; - - return UrlDecodeInternal(encodedValue, Encoding.UTF8); - } - - private static byte[] UrlEncode(byte[] bytes, int offset, int count, bool alwaysCreateNewReturnValue) - { - byte[] encoded = UrlEncode(bytes, offset, count); - - return (alwaysCreateNewReturnValue && (encoded != null) && (encoded == bytes)) - ? (byte[])encoded.Clone() - : encoded; - } - - private static byte[] UrlEncode(byte[] bytes, int offset, int count) - { - if (!ValidateUrlEncodingParameters(bytes, offset, count)) - { - return null; - } - - int cSpaces = 0; - int cUnsafe = 0; - - // count them first - for (int i = 0; i < count; i++) - { - char ch = (char)bytes[offset + i]; - - if (ch == ' ') - cSpaces++; - else if (!IsUrlSafeChar(ch)) - cUnsafe++; - } - - // nothing to expand? - if (cSpaces == 0 && cUnsafe == 0) - { - // DevDiv 912606: respect "offset" and "count" - if (0 == offset && bytes.Length == count) - { - return bytes; - } - else - { - byte[] subarray = new byte[count]; - Buffer.BlockCopy(bytes, offset, subarray, 0, count); - return subarray; - } - } - - // expand not 'safe' characters into %XX, spaces to +s - byte[] expandedBytes = new byte[count + cUnsafe * 2]; - int pos = 0; - - for (int i = 0; i < count; i++) - { - byte b = bytes[offset + i]; - char ch = (char)b; - - if (IsUrlSafeChar(ch)) - { - expandedBytes[pos++] = b; - } - else if (ch == ' ') - { - expandedBytes[pos++] = (byte)'+'; - } - else - { - expandedBytes[pos++] = (byte)'%'; - expandedBytes[pos++] = (byte)IntToHex((b >> 4) & 0xf); - expandedBytes[pos++] = (byte)IntToHex(b & 0x0f); - } - } - - return expandedBytes; - } - - private static bool ValidateUrlEncodingParameters(byte[] bytes, int offset, int count) - { - if (bytes == null && count == 0) - return false; - if (bytes == null) - { - throw new ArgumentNullException("bytes"); - } - if (offset < 0 || offset > bytes.Length) - { - throw new ArgumentOutOfRangeException("offset"); - } - if (count < 0 || offset + count > bytes.Length) - { - throw new ArgumentOutOfRangeException("count"); - } - - return true; - } - - private static bool IsUrlSafeChar(char ch) - { - if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9') - return true; - - switch (ch) - { - case '-': - case '_': - case '.': - case '!': - case '*': - case '(': - case ')': - return true; - } - - return false; - } - - private static char IntToHex(int n) - { - if (n <= 9) - return (char)(n + (int)'0'); - else - return (char)(n - 10 + (int)'A'); - } - - private static string UrlDecodeInternal(string value, Encoding encoding) - { - if (value == null) - { - return null; - } - - int count = value.Length; - UrlDecoder helper = new UrlDecoder(count, encoding); - - // go through the string's chars collapsing %XX and - // appending each char as char, with exception of %XX constructs - // that are appended as bytes - - for (int pos = 0; pos < count; pos++) - { - char ch = value[pos]; - - if (ch == '+') - { - ch = ' '; - } - else if (ch == '%' && pos < count - 2) - { - int h1 = HexToInt(value[pos + 1]); - int h2 = HexToInt(value[pos + 2]); - - if (h1 >= 0 && h2 >= 0) - { // valid 2 hex chars - byte b = (byte)((h1 << 4) | h2); - pos += 2; - - // don't add as char - helper.AddByte(b); - continue; - } - } - - if ((ch & 0xFF80) == 0) - helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode - else - helper.AddChar(ch); - } - - return helper.GetString(); - } - - private class UrlDecoder - { - private int _bufferSize; - - // Accumulate characters in a special array - private int _numChars; - private char[] _charBuffer; - - // Accumulate bytes for decoding into characters in a special array - private int _numBytes; - private byte[] _byteBuffer; - - // Encoding to convert chars to bytes - private Encoding _encoding; - - private void FlushBytes() - { - if (_numBytes > 0) - { - _numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars); - _numBytes = 0; - } - } - - internal UrlDecoder(int bufferSize, Encoding encoding) - { - _bufferSize = bufferSize; - _encoding = encoding; - - _charBuffer = new char[bufferSize]; - // byte buffer created on demand - } - - internal void AddChar(char ch) - { - if (_numBytes > 0) - FlushBytes(); - - _charBuffer[_numChars++] = ch; - } - - internal void AddByte(byte b) - { - if (_byteBuffer == null) - _byteBuffer = new byte[_bufferSize]; - - _byteBuffer[_numBytes++] = b; - } - - internal String GetString() - { - if (_numBytes > 0) - FlushBytes(); - - if (_numChars > 0) - return new String(_charBuffer, 0, _numChars); - else - return String.Empty; - } - } - - private static int HexToInt(char h) - { - return (h >= '0' && h <= '9') ? h - '0' : - (h >= 'a' && h <= 'f') ? h - 'a' + 10 : - (h >= 'A' && h <= 'F') ? h - 'A' + 10 : - -1; - } - } -} - - -// FILE: src/NetBox/ByteFormat.cs - -namespace NetBox -{ - using global::System; - - static class ByteFormat - { - //http://en.wikipedia.org/wiki/Kibibyte - - private const long Kb = 1000; //kilobyte - private const long KiB = 1024; //kikibyte - private const long Mb = Kb * 1000; //megabyte - private const long MiB = KiB * 1024; //memibyte - private const long Gb = Mb * 1000; //gigabyte - private const long GiB = MiB * 1024; //gigibyte - private const long Tb = Gb * 1000; //terabyte - private const long TiB = GiB * 1024; //tebibyte - private const long Pb = Tb * 1024; //petabyte - private const long PiB = TiB * 1024; //pepibyte - - public enum Standard - { - /// - /// International System of Units - /// - Si, - - /// - /// International Electrotechnical Commission - /// - Iec - } - - /// - /// Returns the best formatted string representation of a byte value - /// - /// number of bytes - /// - /// formatted string - private static string ToString(long bytes, Standard st = Standard.Iec) - { - return ToString(bytes, st, null); - } - - /// - /// Returns the best formatted string representation of a byte value - /// - /// number of bytes - /// - /// Defines a custom numerical format for the conversion. - /// If this parameters is null or empty the default format will be used 0.00 - /// formatted string - public static string ToString(long bytes, Standard st, string customFormat) - { - if (bytes == 0) return "0"; - - if (string.IsNullOrEmpty(customFormat)) - customFormat = "0.00"; - - string result; - bool isNegative = bytes < 0; - bytes = Math.Abs(bytes); - - if (st == Standard.Si) - { - if (bytes < Mb) result = BytesToKb(bytes, customFormat); - - else if (bytes < Gb) result = BytesToMb(bytes, customFormat); - - else if (bytes < Tb) result = BytesToGb(bytes, customFormat); - - else if (bytes < Pb) result = BytesToTb(bytes, customFormat); - - else result = BytesToPb(bytes, customFormat); - } - else - { - if (bytes < MiB) result = BytesToKib(bytes, customFormat); - - else if (bytes < GiB) result = BytesToMib(bytes, customFormat); - - else if (bytes < TiB) result = BytesToGib(bytes, customFormat); - - else if (bytes < PiB) result = BytesToTib(bytes, customFormat); - - else result = BytesToPib(bytes, customFormat); - } - - return isNegative ? ("-" + result) : (result); - } - - private static string BytesToPb(long bytes, string customFormat) - { - double tb = bytes / ((double)Pb); - return tb.ToString(customFormat) + " PB"; - } - private static string BytesToPib(long bytes, string customFormat) - { - double tb = bytes / ((double)PiB); - return tb.ToString(customFormat) + " PiB"; - } - - private static string BytesToTb(long bytes, string customFormat) - { - double tb = bytes / ((double)Tb); - return tb.ToString(customFormat) + " TB"; - } - private static string BytesToTib(long bytes, string customFormat) - { - double tb = bytes / ((double)TiB); - return tb.ToString(customFormat) + " TiB"; - } - - private static string BytesToGb(long bytes, string customFormat) - { - double gb = bytes / ((double)Gb); - return gb.ToString(customFormat) + " GB"; - } - private static string BytesToGib(long bytes, string customFormat) - { - double gb = bytes / ((double)GiB); - return gb.ToString(customFormat) + " GiB"; - } - - private static string BytesToMb(long bytes, string customFormat) - { - double mb = bytes / ((double)Mb); - return mb.ToString(customFormat) + " MB"; - } - private static string BytesToMib(long bytes, string customFormat) - { - double mb = bytes / ((double)MiB); - return mb.ToString(customFormat) + " MiB"; - } - - private static string BytesToKb(long bytes, string customFormat) - { - double kb = bytes / ((double)Kb); - return kb.ToString(customFormat) + " KB"; - } - private static string BytesToKib(long bytes, string customFormat) - { - double kb = bytes / ((double)KiB); - return kb.ToString(customFormat) + " KiB"; - } - } -} - - -// FILE: src/NetBox/Ascii85.cs - -namespace NetBox -{ - using global::System; - using global::System.IO; - using global::System.Text; - - /// - /// C# implementation of ASCII85 encoding. - /// Based on C code from http://www.stillhq.com/cgi-bin/cvsweb/ascii85/ - /// - /// - /// Jeff Atwood - /// http://www.codinghorror.com/blog/archives/000410.html - /// - class Ascii85 - { - /// - /// Prefix mark that identifies an encoded ASCII85 string - /// - public string PrefixMark = "<~"; - /// - /// Suffix mark that identifies an encoded ASCII85 string - /// - public string SuffixMark = "~>"; - /// - /// Maximum line length for encoded ASCII85 string; - /// set to zero for one unbroken line. - /// - public int LineLength = 75; - - private const int _asciiOffset = 33; - private byte[] _encodedBlock = new byte[5]; - private byte[] _decodedBlock = new byte[4]; - private uint _tuple = 0; - private int _linePos = 0; - - private uint[] pow85 = { 85 * 85 * 85 * 85, 85 * 85 * 85, 85 * 85, 85, 1 }; - - private static Ascii85 instance = new Ascii85(); - - public static Ascii85 Instance => instance; - - /// - /// Decodes an ASCII85 encoded string into the original binary data - /// - /// ASCII85 encoded string - /// enforce marks - /// byte array of decoded binary data - public byte[] Decode(string s, bool enforceMarks) - { - if (enforceMarks) - { - if (!s.StartsWith(PrefixMark) | !s.EndsWith(SuffixMark)) - { - throw new Exception("ASCII85 encoded data should begin with '" + PrefixMark + - "' and end with '" + SuffixMark + "'"); - } - } - - // strip prefix and suffix if present - if (s.StartsWith(PrefixMark)) - { - s = s.Substring(PrefixMark.Length); - } - if (s.EndsWith(SuffixMark)) - { - s = s.Substring(0, s.Length - SuffixMark.Length); - } - - MemoryStream ms = new MemoryStream(); - int count = 0; - bool processChar = false; - - foreach (char c in s) - { - switch (c) - { - case 'z': - if (count != 0) - { - throw new Exception("The character 'z' is invalid inside an ASCII85 block."); - } - _decodedBlock[0] = 0; - _decodedBlock[1] = 0; - _decodedBlock[2] = 0; - _decodedBlock[3] = 0; - ms.Write(_decodedBlock, 0, _decodedBlock.Length); - processChar = false; - break; - case '\n': - case '\r': - case '\t': - case '\0': - case '\f': - case '\b': - processChar = false; - break; - default: - if (c < '!' || c > 'u') - { - throw new Exception("Bad character '" + c + "' found. ASCII85 only allows characters '!' to 'u'."); - } - processChar = true; - break; - } - - if (processChar) - { - _tuple += ((uint)(c - _asciiOffset) * pow85[count]); - count++; - if (count == _encodedBlock.Length) - { - DecodeBlock(); - ms.Write(_decodedBlock, 0, _decodedBlock.Length); - _tuple = 0; - count = 0; - } - } - } - - // if we have some bytes left over at the end.. - if (count != 0) - { - if (count == 1) - { - throw new Exception("The last block of ASCII85 data cannot be a single byte."); - } - count--; - _tuple += pow85[count]; - DecodeBlock(count); - for (int i = 0; i < count; i++) - { - ms.WriteByte(_decodedBlock[i]); - } - } - - return ms.ToArray(); - } - - /// - /// Encodes binary data into a plaintext ASCII85 format string - /// - /// binary data to encode - /// enforce marks - /// ASCII85 encoded string - public string Encode(byte[] ba, bool enforceMarks) - { - StringBuilder sb = new StringBuilder(ba.Length * (_encodedBlock.Length / _decodedBlock.Length)); - _linePos = 0; - - if (enforceMarks) - { - AppendString(sb, PrefixMark); - } - - int count = 0; - _tuple = 0; - foreach (byte b in ba) - { - if (count >= _decodedBlock.Length - 1) - { - _tuple |= b; - if (_tuple == 0) - { - AppendChar(sb, 'z'); - } - else - { - EncodeBlock(sb); - } - _tuple = 0; - count = 0; - } - else - { - _tuple |= (uint)(b << (24 - (count * 8))); - count++; - } - } - - // if we have some bytes left over at the end.. - if (count > 0) - { - EncodeBlock(count + 1, sb); - } - - if (enforceMarks) - { - AppendString(sb, SuffixMark); - } - return sb.ToString(); - } - - private void EncodeBlock(StringBuilder sb) - { - EncodeBlock(_encodedBlock.Length, sb); - } - - private void EncodeBlock(int count, StringBuilder sb) - { - for (int i = _encodedBlock.Length - 1; i >= 0; i--) - { - _encodedBlock[i] = (byte)((_tuple % 85) + _asciiOffset); - _tuple /= 85; - } - - for (int i = 0; i < count; i++) - { - char c = (char)_encodedBlock[i]; - AppendChar(sb, c); - } - - } - - private void DecodeBlock() - { - DecodeBlock(_decodedBlock.Length); - } - - private void DecodeBlock(int bytes) - { - for (int i = 0; i < bytes; i++) - { - _decodedBlock[i] = (byte)(_tuple >> 24 - (i * 8)); - } - } - - private void AppendString(StringBuilder sb, string s) - { - if (LineLength > 0 && (_linePos + s.Length > LineLength)) - { - _linePos = 0; - sb.Append('\n'); - } - else - { - _linePos += s.Length; - } - sb.Append(s); - } - - private void AppendChar(StringBuilder sb, char c) - { - sb.Append(c); - _linePos++; - if (LineLength > 0 && (_linePos >= LineLength)) - { - _linePos = 0; - sb.Append('\n'); - } - } - - } -} - - -// FILE: src/NetBox/TempFile.cs - -namespace NetBox -{ - using global::System; - using global::System.IO; - - /// - /// Represents a temporary file that is deleted on dispose. The files are created in user's temp directory. - /// - class TempFile : IDisposable - { - /// - /// - /// - /// Optional extension, defaults to .tmp - public TempFile(string ext = null) - { - if (ext == null) ext = ".tmp"; - - if (!ext.StartsWith(".")) ext = "." + ext; - - string name = Guid.NewGuid().ToString() + ext; - - FullPath = Path.Combine(Path.GetTempPath(), name); - } - - /// - /// Full path to the temp file. It won't exist on disk. - /// - public string FullPath { get; } - - /// - /// Implicit conversion to string (full path). - /// - /// - public static implicit operator string(TempFile tf) => tf.FullPath; - - public override string ToString() => FullPath; - - public void Dispose() - { - if(File.Exists(FullPath)) - { - File.Delete(FullPath); - } - } - } -} - - -// FILE: src/NetBox/Performance/TimeMeasure.cs - -namespace NetBox.Performance -{ - using global::System; - using global::System.Diagnostics; - - /// - /// Measures a time slice as precisely as possible - /// - class TimeMeasure : IDisposable - { - private readonly Stopwatch _sw = new Stopwatch(); - - /// - /// Creates the measure object - /// - public TimeMeasure() - { - _sw.Start(); - } - - /// - /// Returns number of elapsed ticks since the start of measure. - /// The measuring process will continue running. - /// - public long ElapsedTicks => _sw.ElapsedTicks; - - /// - /// Returns number of elapsed milliseconds since the start of measure. - /// The measuring process will continue running. - /// - public long ElapsedMilliseconds => _sw.ElapsedMilliseconds; - - - /// - /// Gets time elapsed from the time this measure was created - /// - public TimeSpan Elapsed => _sw.Elapsed; - - /// - /// Stops measure object if still running - /// - public void Dispose() - { - if (_sw.IsRunning) - { - _sw.Stop(); - } - } - } -} - - -// FILE: src/NetBox/System/ByteArrayExtensions.cs - -namespace System -{ - using Crypto = System.Security.Cryptography; - - /// - /// Byte array extensions methods - /// - static class ByteArrayExtensions - { - private static readonly char[] LowerCaseHexAlphabet = "0123456789abcdef".ToCharArray(); - private static readonly char[] UpperCaseHexAlphabet = "0123456789ABCDEF".ToCharArray(); - private static readonly Crypto.MD5 _md5 = Crypto.MD5.Create(); - private static readonly Crypto.SHA256 _sha256 = Crypto.SHA256.Create(); - - - /// - /// Converts byte array to hexadecimal string - /// - public static string ToHexString(this byte[] bytes) - { - return ToHexString(bytes, true); - } - - private static string ToHexString(this byte[] bytes, bool lowerCase) - { - if (bytes == null) return null; - - char[] alphabet = lowerCase ? LowerCaseHexAlphabet : UpperCaseHexAlphabet; - - int len = bytes.Length; - char[] result = new char[len * 2]; - - int i = 0; - int j = 0; - - while (i < len) - { - byte b = bytes[i++]; - result[j++] = alphabet[b >> 4]; - result[j++] = alphabet[b & 0xF]; - } - - return new string(result); - } - - public static byte[] MD5(this byte[] bytes) - { - if (bytes == null) return null; - - return _md5.ComputeHash(bytes); - } - - public static byte[] SHA256(this byte[] bytes) - { - if (bytes == null) return null; - - return _sha256.ComputeHash(bytes); - } - - public static byte[] HMACSHA256(this byte[] data, byte[] key) - { - if (data == null) return null; - - var alg = Crypto.KeyedHashAlgorithm.Create("HmacSHA256"); - alg.Key = key; - return alg.ComputeHash(data); - } - } -} - - -// FILE: src/NetBox/System/EnumerableExtensions.cs - -namespace System -{ - using global::System.Collections.Generic; - using global::System.Linq; - - /// - /// extension methods - /// - static class EnumerableExtensions - { - /// - /// Split sequence in batches of specified size - /// - /// Element type - /// Enumeration source - /// Size of the batch chunk - /// - public static IEnumerable> Chunk(this IEnumerable source, int chunkSize) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - - while (source.Any()) - { - yield return source.Take(chunkSize); - source = source.Skip(chunkSize); - } - } - - /// - /// Performs a specific action on each element of the sequence - /// - public static IEnumerable ForEach(this IEnumerable source, Action action) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (action == null) throw new ArgumentNullException(nameof(action)); - - foreach (T element in source) - { - action(element); - - yield return element; - } - } - } -} - - -// FILE: src/NetBox/System/TaskExtensions.cs - -namespace System -{ - using global::System.Threading.Tasks; - using System.Diagnostics.CodeAnalysis; - - /// - /// Task utility methods - /// - static class TaskExtensions - { - /// - /// Fire-and-forget without compiler warnings - /// - /// - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "task")] - public static void Forget(this Task task) - { - } - } -} - - -// FILE: src/NetBox/System/GuidExtensions.cs - - -namespace System -{ - using NetBox; - - public static class GuidExtensions - { - public static string ToShortest(this Guid g) - { - return Ascii85.Instance.Encode(g.ToByteArray(), true); - } - } -} - - -// FILE: src/NetBox/System/StringExtensions.cs - -namespace System -{ - using System.Net.Http; - using global::System.Threading.Tasks; - using NetBox; - using global::System.Collections.Generic; - using global::System.IO; - using global::System.Linq; - using global::System.Text; - using global::System.Text.RegularExpressions; - - /// - /// String extensions. - /// - static class StringExtensions - { - private const string HtmlStripPattern = @"<(.|\n)*?>"; - - static readonly char[] Invalid = Path.GetInvalidFileNameChars(); - - /// - /// Converts hex string to byte array - /// - /// - /// - public static byte[] FromHexToBytes(this string hex) - { - if (hex == null) return null; - - byte[] raw = new byte[hex.Length / 2]; - for (int i = 0; i < raw.Length; i++) - { - try - { - raw[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16); - } - catch (FormatException) - { - return null; - } - } - return raw; - } - - #region [ HTML Helpers ] - - /// - /// Strips HTML string from any tags leaving text only. - /// - /// - /// - public static string StripHtml(this string s) - { - if (s == null) return null; - - //This pattern Matches everything found inside html tags; - //(.|\n) - > Look for any character or a new line - // *? -> 0 or more occurences, and make a non-greedy search meaning - //That the match will stop at the first available '>' it sees, and not at the last one - //(if it stopped at the last one we could have overlooked - //nested HTML tags inside a bigger HTML tag..) - - return Regex.Replace(s, HtmlStripPattern, string.Empty); - } - - #endregion - - #region [ Encoding ] - - /// - /// Encodes a string to BASE64 format - /// - public static string Base64Encode(this string s) - { - if (s == null) return null; - - return Convert.ToBase64String(Encoding.UTF8.GetBytes(s)); - } - - /// - /// Decodes a BASE64 encoded string - /// - public static string Base64Decode(this string s) - { - if (s == null) return null; - - byte[] data = Convert.FromBase64String(s); - - return Encoding.UTF8.GetString(data, 0, data.Length); - } - - - /// - /// Decodes a BASE64 encoded string to byte array - /// - /// String to decode - /// Byte array - public static byte[] Base64DecodeAsBytes(this string s) - { - if (s == null) return null; - - return Convert.FromBase64String(s); - } - - /// - /// Converts shortest guid representation back to Guid. See - /// on how to convert Guid to string. - /// - public static Guid FromShortestGuid(this string s) - { - byte[] guidBytes = Ascii85.Instance.Decode(s, false); - return new Guid(guidBytes); - } - - /// - /// URL-encodes input string - /// - /// String to encode - /// Encoded string - public static string UrlEncode(this string value) - { - return WebUtility.UrlEncode(value); - } - - /// - /// URL-decodes input string - /// - /// String to decode - /// Decoded string - public static string UrlDecode(this string value) - { - return WebUtility.UrlDecode(value); - } - - public static byte[] UTF8Bytes(this string s) - { - if (s == null) return null; - - return Encoding.UTF8.GetBytes(s); - } - - #endregion - - #region [ Hashing ] - - public static string MD5(this string s) - { - if (s == null) return null; - - return Encoding.UTF8.GetBytes(s).MD5().ToHexString(); - } - - public static string SHA256(this string s) - { - if (s == null) return null; - - return Encoding.UTF8.GetBytes(s).SHA256().ToHexString(); - } - - public static byte[] HMACSHA256(this string s, byte[] key) - { - if (s == null) return null; - - return Encoding.UTF8.GetBytes(s).HMACSHA256(key); - } - - - #endregion - - #region [ Stream Conversion ] - - /// - /// Converts to MemoryStream with a specific encoding - /// - public static MemoryStream ToMemoryStream(this string s, Encoding encoding) - { - if (s == null) return null; - if (encoding == null) encoding = Encoding.UTF8; - - return new MemoryStream(encoding.GetBytes(s)); - } - - /// - /// Converts to MemoryStream in UTF-8 encoding - /// - public static MemoryStream ToMemoryStream(this string s) - { - // ReSharper disable once IntroduceOptionalParameters.Global - return ToMemoryStream(s, null); - } - - #endregion - - #region [ String Manipulation ] - - private static bool FindTagged(ref string s, ref string startToken, ref string endToken, bool includeOuterTokens, out int startIdx, out int length) - { - int idx0 = s.IndexOf(startToken, StringComparison.Ordinal); - - if (idx0 != -1) - { - int idx1 = s.IndexOf(endToken, idx0 + startToken.Length, StringComparison.Ordinal); - - if (idx1 != -1) - { - startIdx = includeOuterTokens ? idx0 : idx0 + startToken.Length; - length = includeOuterTokens - ? (idx1 - idx0 + endToken.Length) - : (idx1 - idx0 - startToken.Length); - - return true; - } - } - - startIdx = length = -1; - return false; - } - - /// - /// Looks for and followed in sequence and when found returns the text between them. - /// - /// Input string - /// Start tag - /// End tag - /// When set to true, returns the complete phrase including start and end tag value, - /// otherwise only inner text returned - /// - public static string FindTagged(this string s, string startTag, string endTag, bool includeOuterTags) - { - if (startTag == null) throw new ArgumentNullException(nameof(startTag)); - if (endTag == null) throw new ArgumentNullException(nameof(endTag)); - - int start, length; - if (FindTagged(ref s, ref startTag, ref endTag, includeOuterTags, out start, out length)) - { - return s.Substring(start, length); - } - - return null; - } - - /// - /// Looks for and followed in sequence, and if found - /// performs a replacement of text inside them with - /// - /// Input string - /// Start tag - /// End tag - /// Replacement text - /// When set to true, not only the text between tags is replaced, but the whole - /// phrase with , text between tags and - /// - public static string ReplaceTagged(this string s, string startTag, string endTag, string replacementText, bool replaceOuterTokens) - { - if (startTag == null) throw new ArgumentNullException(nameof(startTag)); - if (endTag == null) throw new ArgumentNullException(nameof(endTag)); - //if (replacementText == null) throw new ArgumentNullException("replacementText"); - - int start, length; - - if (FindTagged(ref s, ref startTag, ref endTag, replaceOuterTokens, out start, out length)) - { - s = s.Remove(start, length); - - if (replacementText != null) - s = s.Insert(start, replacementText); - } - - return s; - } - - /// - /// Converts a string with spaces to a camel case version, for example - /// "The camel string" is converted to "TheCamelString" - /// - public static string SpacedToCamelCase(this string s) - { - if (s == null) return null; - - string[] parts = s.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - - var b = new StringBuilder(); - foreach (string part in parts) - { - string uc = part.Capitalize(); - b.Append(uc); - } - return b.ToString(); - } - - /// - /// Transforms the string so that the first letter is uppercase and the rest of them are lowercase - /// - public static string Capitalize(this string s) - { - if (s == null) return null; - var b = new StringBuilder(); - - for (int i = 0; i < s.Length; i++) - { - b.Append(i == 0 ? char.ToUpper(s[i]) : char.ToLower(s[i])); - } - - return b.ToString(); - } - - /// - /// Pythonic approach to slicing strings - /// - /// Input string - /// Is the start index to slice from. It can be either positive or negative. - /// Negative value indicates that the index is taken from the end of the string. - /// Is the index to slice to. It can be either positive or negative. - /// Negative value indicates that the index is taken from the end of the string. - /// Sliced string - public static string Slice(this string s, int? start, int? end) - { - if (s == null) return null; - if (start == null && end == null) return s; - - int si = start ?? 0; - int ei = end ?? s.Length; - - if (si < 0) - { - si = s.Length + si; - if (si < 0) si = 0; - } - - if (si > s.Length) - { - si = s.Length - 1; - } - - if (ei < 0) - { - ei = s.Length + ei; - if (ei < 0) ei = 0; - } - - if (ei > s.Length) - { - ei = s.Length; - } - - return s.Substring(si, ei - si); - } - - /// - /// Splits the string into key and value using the provided delimiter values. Both key and value are trimmed as well. - /// - /// Input string. When null returns null immediately. - /// List of delmiters between key and value. This method searches for all the provided - /// delimiters, and splits by the first left-most one. - /// A tuple of two values where the first one is the key and second is the value. If none of the delimiters - /// are found the second value of the tuple is null and the first value is the input string - public static Tuple SplitByDelimiter(this string s, params string[] delimiter) - { - if (s == null) return null; - - string key, value; - - if (delimiter == null || delimiter.Length == 0) - { - key = s.Trim(); - value = null; - } - else - { - - List indexes = delimiter.Where(d => d != null).Select(d => s.IndexOf(d)).Where(d => d != -1).ToList(); - - if (indexes.Count == 0) - { - key = s.Trim(); - value = null; - } - else - { - int idx = indexes.OrderBy(i => i).First(); - key = s.Substring(0, idx); - value = s.Substring(idx + 1); - } - } - - return new Tuple(key, value); - } - - /// - /// Splits text line by line and removes lines containing specific substring - /// - public static string RemoveLinesContaining(this string input, string substring, StringComparison stringComparison = StringComparison.CurrentCulture) - { - if (string.IsNullOrEmpty(input)) return input; - - var result = new StringBuilder(); - - using (var sr = new StringReader(input)) - { - string line; - while ((line = sr.ReadLine()) != null) - { - if (line.IndexOf(substring, stringComparison) != -1) continue; - - result.AppendLine(line); - } - } - - return result.ToString(); - } - - - #endregion - - #region [ JSON ] - - /// - /// Escapes a string for JSON encoding - /// - /// - /// - public static string ToEscapedJsonValueString(this string s) - { - if (s == null) return null; - - string result = string.Empty; - for (int i = 0; i < s.Length; i++) - { - char c = s[i]; - string ec; - - switch (c) - { - case '\t': - ec = "\\t"; - break; - case '\n': - ec = "\\n"; - break; - case '\r': - ec = "\\r"; - break; - case '\f': - ec = "\\f"; - break; - case '\b': - ec = "\\b"; - break; - case '\\': - ec = "\\\\"; - break; - case '\u0085': // Next Line - ec = "\\u0085"; - break; - case '\u2028': // Line Separator - ec = "\\u2028"; - break; - case '\u2029': // Paragraph Separator - ec = "\\u2029"; - break; - default: - ec = new string(c, 1); - break; - } - - result += ec; - } - - return result; - } - - #endregion - - #region [ Networking ] - - /// - /// Treat this string as URL and download it as stream - /// - /// - /// - public static async Task DownloadUrlToTempFile(this string uri) - { - if (string.IsNullOrEmpty(uri)) return null; - - var tf = new TempFile(); - - using (Stream src = await new HttpClient().GetStreamAsync(uri)) - { - using(Stream tgt = File.Create(tf)) - { - await src.CopyToAsync(tgt); - } - } - - return tf; - } - - #endregion - } -} - - -// FILE: src/NetBox/System/NumericExtensions.cs - -namespace System -{ - using NetBox; - - /// - /// extension methods - /// - static class LongExtensions - { - /// - /// Converts number to readable size string in IEC format, i.e. 1024 converts to "1.02 KiB" - /// - public static string ToFileSizeString(this long number) - { - return ByteFormat.ToString(number, ByteFormat.Standard.Iec, null); - } - - /// - /// Converts number to readable size string in SI format, i.e. 1024 converts to "1.02 KB" - /// - public static string ToFileSizeUiString(this long number) - { - return ByteFormat.ToString(number, ByteFormat.Standard.Si, null); - } - } - - static class IntExtensions - { - /// - /// Converts number to readable size string in IEC format, i.e. 1024 converts to "1.02 KiB" - /// - public static string ToFileSizeString(this int number) - { - return ByteFormat.ToString(number, ByteFormat.Standard.Iec, null); - } - - /// - /// Converts number to readable size string in SI format, i.e. 1024 converts to "1.02 KB" - /// - public static string ToFileSizeUiString(this int number) - { - return ByteFormat.ToString(number, ByteFormat.Standard.Si, null); - } - - /// - /// Converts number to seconds - /// - /// Number of seconds - /// Timespan values - public static TimeSpan Seconds(this int number) - { - return TimeSpan.FromSeconds(number); - } - - /// - /// Converts number to minutes - /// - /// Number of minutes - /// Timespan value - public static TimeSpan Minutes(this int number) - { - return TimeSpan.FromMinutes(number); - } - - /// - /// Converts number to hours - /// - /// Number of hours - /// Timespan value - public static TimeSpan Hours(this int number) - { - return TimeSpan.FromHours(number); - } - } -} - - -// FILE: src/NetBox/System/StreamExtensions.cs - -namespace System -{ - using global::System.Collections.Generic; - using global::System.IO; - using global::System.Text; - - /// - /// extension - /// - public static class StreamExtensions - { - #region [ General ] - - /// - /// Attemps to get the size of this stream by reading the Length property, otherwise returns 0. - /// - public static bool TryGetSize(this Stream s, out long size) - { - try - { - size = s.Length; - return true; - } - catch (NotSupportedException) - { - - } - catch (ObjectDisposedException) - { - - } - - size = 0; - return false; - } - - /// - /// Attemps to get the size of this stream by reading the Length property, otherwise returns 0. - /// - public static long? TryGetSize(this Stream s) - { - long size; - if (TryGetSize(s, out size)) - { - return size; - } - - return null; - } - - #endregion - - #region [ Seek and Read ] - - /// - /// Reads the stream until a specified sequence of bytes is reached. - /// - /// Bytes before the stop sequence - public static byte[] ReadUntil(this Stream s, byte[] stopSequence) - { - byte[] buf = new byte[1]; - var result = new List(50); - int charsMatched = 0; - - while (s.Read(buf, 0, 1) == 1) - { - byte b = buf[0]; - result.Add(b); - - if (b == stopSequence[charsMatched]) - { - if (++charsMatched == stopSequence.Length) - { - break; - } - } - else - { - charsMatched = 0; - } - - } - return result.ToArray(); - } - - #endregion - - #region [ Stream Conversion ] - - /// - /// Reads all stream in memory and returns as byte array - /// - public static byte[] ToByteArray(this Stream stream) - { - if (stream == null) return null; - using (var ms = new MemoryStream()) - { - stream.CopyTo(ms); - return ms.ToArray(); - } - } - - /// - /// Converts the stream to string using specified encoding. This is done by reading the stream into - /// byte array first, then applying specified encoding on top. - /// - public static string ToString(this Stream stream, Encoding encoding) - { - if (stream == null) return null; - if (encoding == null) throw new ArgumentNullException(nameof(encoding)); - - using (StreamReader reader = new StreamReader(stream, encoding)) - { - return reader.ReadToEnd(); - } - } - - #endregion - - } -} - - -// FILE: src/NetBox/System/DateTimeExtensions.cs - -namespace System -{ - /// - /// extension methods - /// - static class DateTimeExtensions - { - /// - /// Strips time from the date structure - /// - public static DateTime RoundToDay(this DateTime time) - { - return new DateTime(time.Year, time.Month, time.Day); - } - - /// - /// Changes to the end of day time, i.e. hours, minutes and seconds are changed to 23:59:59 - /// - /// - /// - public static DateTime EndOfDay(this DateTime time) - { - return new DateTime(time.Year, time.Month, time.Day, 23, 59, 59); - } - - /// - /// Rounds to the closest minute - /// - /// Input date - /// Closest minute i.e. 15, 30, 45 etc. - /// Whether to use minimum or maximum value. For example - /// when time is 13:14 and rounding is to every 15 minutes, when this parameter is true - /// the result it 13:00, otherwise 13:15 - /// - public static DateTime RoundToMinute(this DateTime time, int round, bool roundLeft) - { - int minute = time.Minute; - int leftover = minute % round; - if (leftover == 0) return time; - int addHours = 0; - minute -= leftover; - - if (!roundLeft) minute += round; - if (minute > 59) - { - minute = minute % 60; - addHours = 1; - } - - return new DateTime(time.Year, time.Month, time.Day, time.Hour + addHours, minute, 0); - } - - /// - /// Strips off details after seconds - /// - /// - /// - public static DateTime RoundToSecond(this DateTime time) - { - return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, time.Second, time.Kind); - } - - /// - /// Returns true if the date is today's date. - /// - public static bool IsToday(this DateTime time) - { - DateTime now = DateTime.UtcNow; - - return now.Year == time.Year && - now.Month == time.Month && - now.Day == time.Day; - } - - /// - /// Returns true if the date is tomorrow's date. - /// - public static bool IsTomorrow(this DateTime time) - { - TimeSpan diff = DateTime.UtcNow - time; - - return diff.TotalDays >= 1 && diff.TotalDays < 2; - } - - /// - /// Returns date in "HH:mm" format - /// - public static string ToHourMinuteString(this DateTime time) - { - return time.ToString("HH:mm"); - } - - /// - /// Formats date in ISO 8601 format - /// - public static string ToIso8601DateString(this DateTime time) - { - return time.ToString("o"); - } - } -} - - -// FILE: src/NetBox/IO/NonCloseableStream.cs - -namespace NetBox.IO -{ - using global::System; - using global::System.IO; - - /// - /// Represents a stream that ignores operations i.e. cannot be closed by the client - /// - class NonCloseableStream : DelegatedStream - { - /// - /// Creates an instance of this class - /// - /// Master stream to delegate operations to - public NonCloseableStream(Stream master) : base(master) - { - - } - - /// - /// Overrides this call to do nothing - /// - /// - protected override void Dispose(bool disposing) - { - //does nothing on purpose - } - } -} - - -// FILE: src/NetBox/IO/DelegatedStream.cs - -namespace NetBox.IO -{ - using global::System; - using global::System.IO; - - /// - /// Makes stream members virtual instead of abstract, allowing to override only specific behaviors. - /// - class DelegatedStream : Stream - { - private readonly Stream _master; - - /// - /// Creates an instance of non-closeable stream - /// - /// - public DelegatedStream(Stream master) - { - _master = master ?? throw new ArgumentNullException(nameof(master)); - } - - /// - /// Calls - /// - public override bool CanRead => GetCanRead(); - - /// - /// Delegates to master by default - /// - /// - protected virtual bool GetCanRead() - { - return _master.CanRead; - } - - /// - /// Calls - /// - public override bool CanSeek => GetCanSeek(); - - /// - /// Delegates to master by default - /// - /// - protected virtual bool GetCanSeek() - { - return _master.CanSeek; - } - - /// - /// Calls - /// - public override bool CanWrite => GetCanWrite(); - - /// - /// Delegates to master by default - /// - /// - protected virtual bool GetCanWrite() - { - return _master.CanWrite; - } - - /// - /// Calls - /// - public override long Length => _master.Length; - - /// - /// Delegates to master by default - /// - /// - protected virtual long GetLength() - { - return _master.Length; - } - - /// - /// Delegates to master by default - /// - /// - public override long Position { get => _master.Position; set => _master.Position = value; } - - /// - /// Delegates to master by default - /// - /// - public override void Flush() - { - _master.Flush(); - } - - /// - /// Delegates to master by default - /// - /// - public override int Read(byte[] buffer, int offset, int count) - { - return _master.Read(buffer, offset, count); - } - - /// - /// Delegates to master by default - /// - /// - public override long Seek(long offset, SeekOrigin origin) - { - return _master.Seek(offset, origin); - } - - /// - /// Delegates to master by default - /// - /// - public override void SetLength(long value) - { - _master.SetLength(value); - } - - /// - /// Delegates to master by default - /// - /// - public override void Write(byte[] buffer, int offset, int count) - { - _master.Write(buffer, offset, count); - } - } -} - - -// FILE: src/NetBox/FileFormats/Ini/IniKeyValue.cs - -namespace NetBox.FileFormats.Ini -{ - using global::System; - - internal class IniKeyValue : IniEntity - { - public const string KeyValueSeparator = "="; - - public IniKeyValue(string key, string value, string comment) - { - if(key == null) throw new ArgumentNullException(nameof(key)); - Key = key; - Value = value; - Comment = comment == null ? null : new IniComment(comment); - } - - public string Key { get; } - - public string Value { get; set; } - - public IniComment Comment { get; } - - public static IniKeyValue FromLine(string line, bool parseInlineComments) - { - int idx = line.IndexOf(KeyValueSeparator, StringComparison.CurrentCulture); - if(idx == -1) return null; - - string key = line.Substring(0, idx).Trim(); - string value = line.Substring(idx + 1).Trim(); - string comment = null; - - if (parseInlineComments) - { - idx = value.LastIndexOf(IniComment.CommentSeparator, StringComparison.CurrentCulture); - if (idx != -1) - { - comment = value.Substring(idx + 1).Trim(); - value = value.Substring(0, idx).Trim(); - } - } - - return new IniKeyValue(key, value, comment); - } - - public override string ToString() - { - return $"{Value}"; - } - } -} - - -// FILE: src/NetBox/FileFormats/Ini/IniEntity.cs - -namespace NetBox.FileFormats.Ini -{ - abstract class IniEntity - { - } -} - - -// FILE: src/NetBox/FileFormats/Ini/IniSection.cs - -namespace NetBox.FileFormats.Ini -{ - using global::System; - using global::System.Collections.Generic; - using global::System.IO; - - class IniSection - { - public const string SectionKeySeparator = "."; - - private readonly List _entities = new List(); - private readonly Dictionary _keyToValue = new Dictionary(); - - /// - /// Section name - /// - public string Name { get; set; } - - /// - /// - /// - /// Pass null to work with global section - public IniSection(string name) - { - if(name != null) - { - if (name.StartsWith("[")) name = name.Substring(1); - if (name.EndsWith("]")) name = name.Substring(0, name.Length - 1); - } - - Name = name; - } - - public void Add(IniEntity entity) - { - _entities.Add(entity); - - IniKeyValue ikv = entity as IniKeyValue; - if(ikv != null) - { - _keyToValue[ikv.Key] = ikv; - } - } - - public IniKeyValue Set(string key, string value) - { - if(value == null) - { - IniKeyValue ikv; - if(_keyToValue.TryGetValue(key, out ikv)) - { - _keyToValue.Remove(key); - return ikv; - } - return null; - } - else - { - IniKeyValue ikv; - if(_keyToValue.TryGetValue(key, out ikv)) - { - ikv.Value = value; - } - else - { - ikv = new IniKeyValue(key, value, null); - Add(ikv); - } - return ikv; - } - } - - public static void SplitKey(string fullKey, out string sectionName, out string keyName) - { - int idx = fullKey.IndexOf(SectionKeySeparator, StringComparison.CurrentCulture); - - if(idx == -1) - { - sectionName = null; - keyName = fullKey; - } - else - { - sectionName = fullKey.Substring(0, idx); - keyName = fullKey.Substring(idx + 1); - } - } - - public void WriteTo(StreamWriter writer) - { - foreach(IniEntity entity in _entities) - { - IniKeyValue ikv = entity as IniKeyValue; - if(ikv != null) - { - writer.Write($"{ikv.Key}{IniKeyValue.KeyValueSeparator}{ikv.Value}"); - if(ikv.Comment != null) - { - writer.Write(" "); - writer.Write(IniComment.CommentSeparator); - writer.Write(ikv.Comment.Value); - } - writer.WriteLine(); - continue; - } - - IniComment comment = entity as IniComment; - if(comment != null) - { - writer.Write(IniComment.CommentSeparator); - writer.WriteLine(comment.Value); - } - } - } - - public override string ToString() - { - return Name; - } - } -} - - -// FILE: src/NetBox/FileFormats/Ini/StructuredIniFile.cs - -namespace NetBox.FileFormats.Ini -{ - using global::System; - using global::System.Collections.Generic; - using global::System.IO; - using global::System.Linq; - using global::System.Text; - - class StructuredIniFile - { - private const string _sectionBegin = "["; - private const string _sectionEnd = "]"; - private static readonly char[] _sectionTrims = {'[', ']'}; - - private readonly IniSection _globalSection; - private readonly List _sections = new List(); - private readonly Dictionary _fullKeyNameToValue = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - - public StructuredIniFile() - { - _globalSection = new IniSection(null); - _sections.Add(_globalSection); - } - - public string this[string key] - { - get - { - if(key == null) return null; - - IniKeyValue value; - return !_fullKeyNameToValue.TryGetValue(key, out value) ? null : value.Value; - } - set - { - if(key == null) return; - - string sectionName; - string keyName; - IniSection.SplitKey(key, out sectionName, out keyName); - IniSection section = sectionName == null - ? _globalSection - : _sections.FirstOrDefault(s => s.Name == sectionName); - if(section == null) - { - section = new IniSection(sectionName); - _sections.Add(section); - } - IniKeyValue ikv = section.Set(keyName, value); - - //update the local cache - if(ikv != null) - { - if(value == null) - { - _fullKeyNameToValue.Remove(key); - } - else - { - _fullKeyNameToValue[key] = ikv; - } - } - } - } - - public static StructuredIniFile FromString(string content, bool parseInlineComments = true) - { - using (Stream input = new MemoryStream(Encoding.UTF8.GetBytes(content))) - { - return FromStream(input, parseInlineComments); - } - } - - public static StructuredIniFile FromStream(Stream inputStream, bool parseInlineComments = true) - { - if(inputStream == null) throw new ArgumentNullException(nameof(inputStream)); - - var file = new StructuredIniFile(); - - using(var reader = new StreamReader(inputStream)) - { - IniSection section = file._globalSection; - - string line; - while((line = reader.ReadLine()) != null) - { - line = line.Trim(); - - if(line.StartsWith(_sectionBegin)) - { - //start new section - line = line.Trim(); - section = new IniSection(line); - file._sections.Add(section); - } - else if(line.StartsWith(IniComment.CommentSeparator)) - { - //whole line is a comment - string comment = line.Substring(1).Trim(); - section.Add(new IniComment(comment)); - } - else - { - IniKeyValue ikv = IniKeyValue.FromLine(line, parseInlineComments); - if(ikv == null) continue; - - section.Add(ikv); - string fullKey = section.Name == null - ? ikv.Key - : $"{section.Name}{IniSection.SectionKeySeparator}{ikv.Key}"; - file._fullKeyNameToValue[fullKey] = ikv; - - } - } - } - - return file; - } - - public void WriteTo(Stream outputStream) - { - if(outputStream == null) throw new ArgumentNullException(nameof(outputStream)); - - using(var writer = new StreamWriter(outputStream)) - { - foreach(IniSection section in _sections) - { - if(section.Name != null) - { - writer.WriteLine(); - writer.WriteLine($"{_sectionBegin}{section.Name}{_sectionEnd}"); - } - - section.WriteTo(writer); - } - } - } - - //private static - } -} - - -// FILE: src/NetBox/FileFormats/Ini/IniComment.cs - -namespace NetBox.FileFormats.Ini -{ - class IniComment : IniEntity - { - public const string CommentSeparator = ";"; - - public IniComment(string value) - { - Value = value; - } - - public string Value { get; set; } - - public override string ToString() => Value; - } -} - - -// FILE: src/NetBox/FileFormats/Csv/CsvReader.cs - -namespace NetBox.FileFormats.Csv -{ - using global::System.Collections.Generic; - using global::System.IO; - using global::System.Linq; - using global::System.Runtime.CompilerServices; - using global::System.Text; - - /// - /// Reads data from a CSV file. Fast and reliable, supports: - /// - newline characters - /// - double quotes - /// - commas - /// - class CsvReader - { - private readonly StreamReader _reader; - private char[] _buffer; - private const int BufferSize = 1024 * 10; //10k buffer - private int _pos; - private int _size = -1; - private readonly List _chars = new List(); - private readonly List _row = new List(); - private ValueState _lastState = ValueState.None; - - private enum ValueState - { - None, - HasMore, - EndOfLine, - EndOfFile - } - - /// - /// Creates an instance from an open stream and encoding - /// - public CsvReader(Stream stream, Encoding encoding) - { - _reader = new StreamReader(stream, encoding); - _buffer = new char[BufferSize]; - } - - /// - /// Reads all file as a dictionary of column name to list of values - /// - /// File content - /// When true, the first line of the file includes columns - /// Dictionary mapping the column name to the list of values - public static Dictionary> ReadAllFromContent(string content, bool hasColumns = true) - { - var result = new Dictionary>(); - - using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(content))) - { - var reader = new CsvReader(ms, Encoding.UTF8); - - string[] columnNames = hasColumns ? reader.ReadNextRow() : null; - - string[] values; - while ((values = reader.ReadNextRow()) != null) - { - if (columnNames == null) - { - columnNames = Enumerable.Range(1, values.Length).Select(v => v.ToString()).ToArray(); - } - - - for (int i = 0; i < values.Length; i++) - { - if(!result.TryGetValue(columnNames[i], out List list)) - { - list = new List(); - result[columnNames[i]] = list; - } - - list.Add(values[i]); - } - - } - } - - return result; - } - - /// - /// Reads next row of data if available. - /// - /// Null when end of file is reached, or array of strings for each column. - public string[] ReadNextRow() - { - if (ValueState.EndOfFile == _lastState) return null; - - _row.Clear(); - _chars.Clear(); - - while (ValueState.HasMore == (_lastState = ReadNextValue())) - { - _row.Add(Str()); - _chars.Clear(); - } - - _row.Add(Str()); - - return _row.ToArray(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private string Str() - { - return new string(_chars.ToArray()); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ValueState ReadNextValue() - { - int curr, next; - bool quoted = false; - short state = 0; - while (NextChars(out curr, out next)) - { - switch (state) - { - case 0: //value start - if (curr == CsvFormat.ValueQuote) - { - //if the value starts with quote it: - // - ends with quote - // - double quote must be transformed into single quote - // - column separator (usuallly ',') can be contained within the value - // - line separator '\r' can be inside the value and must be transforted to a proper line feed - quoted = true; - state = 1; - } - else if (IsLineEndChar(curr)) - { - while (IsLineEndChar(next)) - { - NextChars(out curr, out next); - } - - return next == -1 ? ValueState.EndOfFile : ValueState.EndOfLine; - } - else if (CsvFormat.ValueSeparator == curr) - { - //start from value separator, meaning it's an empty value - return next == -1 ? ValueState.EndOfFile : ValueState.HasMore; - } - else - { - //if the value doesn't start with quote: - // - it can't contain column separator or quote characters inside - // - it can't contain line separators - _chars.Add((char)curr); - - if (CsvFormat.ValueSeparator == next) - { - state = 2; - } - else - { - state = 1; - } - } - break; - - case 1: //reading value - if (quoted) - { - switch (curr) - { - case CsvFormat.ValueQuote: - if (next == CsvFormat.ValueQuote) - { - //escaped quote, make a single one - _chars.Add(CsvFormat.ValueQuote); - - //fast-forward to the next character - _pos++; - } - else if (next == CsvFormat.ValueSeparator || next == '\r' || next == '\n') - { - //this is the end of value - state = 2; - } - else - { - throw new IOException($"unexpected character {next} after {curr} at position {_pos}"); - } - break; - case '\r': - _chars.Add('\r'); - _chars.Add('\n'); - break; - default: - _chars.Add((char)curr); - break; - } - } - else - { - _chars.Add((char)curr); - - //simple and most common case - if (next == CsvFormat.ValueSeparator || next == '\r' || next == '\n') - { - state = 2; - } - } - break; - - case 2: //end of value - //if the character after end of value (curr) is a value separator it's not the end of line - bool hasMore = (curr == CsvFormat.ValueSeparator); - - if (!hasMore) - { - while (IsLineEndChar(next)) - { - NextChars(out curr, out next); - } - } - - return hasMore - ? ValueState.HasMore - : (next == -1 ? ValueState.EndOfFile : ValueState.EndOfLine); - } - - } - - return ValueState.EndOfFile; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool NextChars(out int curr, out int next) - { - if (_pos >= _size) - { - NextBlock(); - - if (_size == 0) - { - curr = next = -1; - return false; - } - } - curr = _buffer[_pos++]; - - - if (_pos >= _size) - { - NextBlock(); - - if (_size == 0) - { - next = -1; - return true; - } - } - next = _buffer[_pos]; - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool NextBlock() - { - _size = _reader.ReadBlock(_buffer, 0, BufferSize); - _pos = 0; - return _size > 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsLineEndChar(int ch) - { - return ch == '\r' || ch == '\n'; - } - } -} - - -// FILE: src/NetBox/FileFormats/Csv/CsvWriter.cs - -namespace NetBox.FileFormats.Csv -{ - using global::System; - using global::System.Collections.Generic; - using global::System.IO; - using global::System.Text; - - /// - /// Writes data to a CSV file. Fast and reliable, supports: - /// - newline characters - /// - double quotes - /// - commas - /// - class CsvWriter - { - private readonly Stream _destination; - private readonly Encoding _encoding; - private readonly byte[] _newLine; - private readonly byte[] _separator; - private bool _firstRowWritten; - - /// - /// Creates a new instance of CsvWriter which uses UTF8 encoding - /// - /// - public CsvWriter(Stream destination) - : this(destination, Encoding.UTF8) - { - - } - - /// - /// Creates a new instance of CsvWriter on disk with UTF8 encoding - /// - /// File name or path - public CsvWriter(string fileName) - : this(File.Create(fileName), Encoding.UTF8) - { - - } - - /// - /// Creates a new instance of CsvWriter and allows to specify the writer encoding - /// - /// - /// - public CsvWriter(Stream destination, Encoding encoding) - { - if (destination == null) throw new ArgumentNullException("destination"); - if (encoding == null) throw new ArgumentNullException("encoding"); - if (!destination.CanWrite) throw new ArgumentException("must be writeable", "destination"); - - _destination = destination; - _encoding = encoding; - _separator = new byte[] { (byte)CsvFormat.ValueSeparator }; - _newLine = _encoding.GetBytes(CsvFormat.NewLine); - } - - /// - /// Writes a row of data - /// - public void Write(params string[] values) - { - Write((IEnumerable)values); - } - - /// - /// Writes a row of data - /// - public void Write(IEnumerable values) - { - if (values == null) return; - - if (_firstRowWritten) _destination.Write(_newLine, 0, _newLine.Length); - - int i = 0; - foreach (string column in values) - { - if (i != 0) _destination.Write(_separator, 0, _separator.Length); - - byte[] escaped = _encoding.GetBytes(CsvFormat.EscapeValue(column)); - _destination.Write(escaped, 0, escaped.Length); - i++; - } - - _firstRowWritten = true; - } - } -} - - -// FILE: src/NetBox/FileFormats/Csv/CsvFormat.cs - -namespace NetBox.FileFormats.Csv -{ - static class CsvFormat - { - public const char ValueSeparator = ','; - public const char ValueQuote = '"'; - public static readonly string ValueQuoteStr = "\""; - public static readonly string ValueQuoteStrStr = "\"\""; - private static readonly char[] QuoteMark = new[] { ValueSeparator, ValueQuote, '\r', '\n' }; - - public static readonly char[] NewLine = { '\r', '\n' }; - - private const string ValueLeftBracket = "\""; - private const string ValueRightBracket = "\""; - - private const string ValueEscapeFind = "\""; - private const string ValueEscapeValue = "\"\""; - - /// - /// Implemented according to RFC4180 http://tools.ietf.org/html/rfc4180 - /// - /// - /// - public static string EscapeValue(string value) - { - if (string.IsNullOrEmpty(value)) - { - return string.Empty; - } - - //the values have to be quoted if they contain either quotes themselves, - //value separators, or newline characters - if (value.IndexOfAny(QuoteMark) == -1) - { - return value; - } - - return ValueQuoteStr + - value - .Replace(ValueQuoteStr, ValueQuoteStrStr) - .Replace("\r\n", "\r") - .Replace("\n", "\r") + - ValueQuoteStr; - } - - public static string UnescapeValue(string value) - { - if (value == null) return null; - - return value; - } - } -} - - -// FILE: src/NetBox/Generator/RandomGenerator.cs - -namespace NetBox.Generator -{ - using global::System; - using global::System.IO; - using global::System.Security.Cryptography; - using global::System.Text; - - /// - /// Generates random data using for increased security - /// - public static class RandomGenerator - { - private static readonly RandomNumberGenerator Rnd = RandomNumberGenerator.Create(); - - //get a cryptographically strong double between 0 and 1 - private static double NextCryptoDouble() - { - //fill-in array with 8 random bytes - byte[] b = new byte[sizeof(double)]; - Rnd.GetBytes(b); - - //i don't understand this - ulong ul = BitConverter.ToUInt64(b, 0) / (1 << 11); - double d = ul / (double)(1UL << 53); - return d; - } - - private static int NextCryptoInt() - { - byte[] b = new byte[sizeof(int)]; - Rnd.GetBytes(b); - return BitConverter.ToInt32(b, 0); - } - - /// - /// Generates a random boolean - /// - public static bool RandomBool - { - get - { - return NextCryptoDouble() >= 0.5d; - } - } - - /// - /// Generates a random long number between 0 and max - /// - public static long RandomLong => GetRandomLong(0, long.MaxValue); - - /// - /// Generates a random integer between 0 and max - /// - public static int RandomInt - { - get - { - return NextCryptoInt(); - } - } - - /// - /// Returns random double - /// - public static double RandomDouble - { - get - { - return NextCryptoDouble(); - } - } - - /// - /// Generates a random integer until max parameter - /// - /// Maximum integer value, excluding - /// - public static int GetRandomInt(int max) - { - return GetRandomInt(0, max); - } - - /// - /// Generates a random integer number in range - /// - /// Minimum value, including - /// Maximum value, excluding - public static int GetRandomInt(int min, int max) - { - return (int)Math.Round(NextCryptoDouble() * (max - min - 1)) + min; - } - - /// - /// Generates a random long number in range - /// - /// Minimum value, including - /// Maximum value, excluding - public static long GetRandomLong(long min, long max) - { - double d = NextCryptoDouble(); - return (long)Math.Round(d * (max - min - 1)) + min; - } - - /// - /// Generates a random enum value by type - /// - public static Enum RandomEnum(Type t) - { - Array values = Enum.GetValues(t); - - object value = values.GetValue(GetRandomInt(values.Length)); - - return (Enum)value; - } - -#if !NETSTANDARD16 - /// - /// Generates a random enum value - /// - /// Enumeration type - public static T GetRandomEnum() where T : struct - { - //can't limit generics to enum http://connect.microsoft.com/VisualStudio/feedback/details/386194/allow-enum-as-generic-constraint-in-c - - if (!typeof(T).IsEnum) throw new ArgumentException("T must be an enum"); - - return (T)(object)RandomEnum(typeof(T)); - } -#endif - - /// - /// Generates a random date in range - /// - /// Minimum date, including - /// Maximum date, excluding - public static DateTime GetRandomDate(DateTime minValue, DateTime maxValue) - { - long randomTicks = GetRandomLong(minValue.Ticks, maxValue.Ticks); - - return new DateTime(randomTicks); - } - - /// - /// Generates a random date value - /// - public static DateTime RandomDate - { - get { return GetRandomDate(DateTime.MinValue, DateTime.MaxValue); } - } - - /// - /// Generates a random string. Never returns null. - /// - public static string RandomString - { - get - { - string path = Path.GetRandomFileName(); - path = path.Replace(".", ""); - return path; - } - } - - /// - /// Generates a random string - /// - /// string length - /// Whether to allow to return null values - public static string GetRandomString(int length, bool allowNulls) - { - if (allowNulls && RandomLong % 2 == 0) return null; - - var builder = new StringBuilder(); - char ch; - for (int i = 0; i < length; i++) - { - ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * RandomDouble + 65))); - builder.Append(ch); - } - - return builder.ToString(); - } - - /// - /// Generates a random URL in format "http://random.com/random.random - /// - /// Whether to allow to return nulls - public static Uri GetRandomUri(bool allowNulls) - { - if (allowNulls && RandomLong % 2 == 0) return null; - - return new Uri($"http://{RandomString}.com/{RandomString}.{GetRandomString(3, false)}"); - } - - /// - /// Generates a random URL in format "http://random.com/random.random. Never returns null values. - /// - public static Uri RandomUri - { - get { return GetRandomUri(false); } - } - - /// - /// Generates a random sequence of bytes of a specified size - /// - public static byte[] GetRandomBytes(int minSize, int maxSize) - { - int size = minSize == maxSize ? minSize : GetRandomInt(minSize, maxSize); - byte[] data = new byte[size]; - Rnd.GetBytes(data); - return data; - } - - } -}