private static string GetPartHeaderLine(YencHeader header) { long begin = header.PartOffset + 1; long end = header.PartOffset + header.PartSize; return($"{YencKeywords.YPart} {YencKeywords.Begin}={begin} {YencKeywords.End}={end}"); }
private static void ValidatePart(YencArticle article, ICollection <ValidationFailure> failures) { YencHeader header = article.Header; YencFooter footer = article.Footer; if (header.PartNumber != footer.PartNumber) { failures.Add(new ValidationFailure( YencValidationErrorCodes.PartMismatch, Resources.Yenc.PartMismatch, new { HeaderPart = header.PartNumber, FooterPart = footer.PartNumber })); } if (!(footer.PartSize == article.Data.Length && footer.PartSize == header.PartSize)) { failures.Add(new ValidationFailure( YencValidationErrorCodes.SizeMismatch, Resources.Yenc.PartSizeMismatch, new { DataSize = article.Data.Length, HeaderSize = header.PartSize, FooterSize = footer.PartSize })); } if (!footer.PartCrc32.HasValue) { failures.Add(new ValidationFailure( YencValidationErrorCodes.MissingChecksum, Resources.Yenc.MissingPartChecksum)); return; } uint calculatedCrc32 = Crc32.CalculateChecksum(article.Data); if (calculatedCrc32 != footer.PartCrc32.Value) { failures.Add(new ValidationFailure( YencValidationErrorCodes.ChecksumMismatch, Resources.Yenc.PartChecksumMismatch, new { CalculatedChecksum = calculatedCrc32, FooterChecksum = footer.PartCrc32 })); } }
private static string GetHeaderLine(YencHeader header) { var builder = new StringBuilder(YencKeywords.YBegin); if (header.IsFilePart) { builder.Append(' ').Append(YencKeywords.Part).Append('=').Append(header.PartNumber); builder.Append(' ').Append(YencKeywords.Total).Append('=').Append(header.TotalParts); } builder.Append(' ').Append(YencKeywords.Line).Append('=').Append(header.LineLength); builder.Append(' ').Append(YencKeywords.Size).Append('=').Append(header.FileSize); builder.Append(' ').Append(YencKeywords.Name).Append('=').Append(header.FileName); return(builder.ToString()); }
/// <summary> /// Decodes yEnc-encoded text into a <see cref="YencArticle"/> /// using the specified charcter encoding. /// </summary> /// <param name="encodedLines">The yEnc-encoded lines to decode.</param> /// <param name="encoding">The charcter encoding to use.</param> /// <returns>A <see cref="YencArticle"/> containing the decoded binary data and meta-data.</returns> public static YencArticle Decode(IEnumerable <string> encodedLines, Encoding encoding) { Guard.ThrowIfNull(encodedLines, nameof(encodedLines)); Guard.ThrowIfNull(encoding, nameof(encoding)); using (IEnumerator <string> enumerator = encodedLines.GetEnumerator()) { IDictionary <string, string> headers = YencMeta.GetHeaders(enumerator); int part = headers.GetAndConvert(YencKeywords.Part, int.Parse); if (part > 0) { headers.Merge(YencMeta.GetPartHeaders(enumerator), false); } YencHeader header = YencMeta.ParseHeader(headers); YencFooter footer = null; // create buffer for part or entire file if single part var decodedBytes = new byte[header.PartSize]; var decodedBytesIndex = 0; while (enumerator.MoveNext()) { if (enumerator.Current == null) { continue; } if (enumerator.Current.StartsWith(yEnd)) { footer = YencMeta.ParseFooter(YencMeta.ParseLine(enumerator.Current)); // skip remainder if there is some while (enumerator.MoveNext()) { } break; } byte[] encodedBytes = encoding.GetBytes(enumerator.Current); decodedBytesIndex += YencLineDecoder.Decode(encodedBytes, decodedBytes, decodedBytesIndex); } return(new YencArticle(header, footer, decodedBytes)); } }
private static string GetFooterLine(YencHeader header, uint checksum) { var builder = new StringBuilder(YencKeywords.YEnd); if (header.IsFilePart) { builder.Append(' ').Append(YencKeywords.Size).Append('=').Append(header.PartSize); builder.Append(' ').Append(YencKeywords.Part).Append('=').Append(header.PartNumber); builder.Append(' ').Append(YencKeywords.PartCrc32).Append('=').AppendFormat("{0:x}", checksum); } else { builder.Append(' ').Append(YencKeywords.Size).Append('=').Append(header.FileSize); builder.Append(' ').Append(YencKeywords.Crc32).Append('=').AppendFormat("{0:x}", checksum); } return(builder.ToString()); }
/// <summary> /// Validates the specified <see cref="YencArticle"/>. /// </summary> /// <param name="article">The yEnc-encoded article to validate.</param> /// <returns>A <see cref="ValidationResult"/> containing a list of 0 or more validation failures.</returns> public static ValidationResult Validate(YencArticle article) { var failures = new List <ValidationFailure>(); YencHeader header = article.Header; YencFooter footer = article.Footer; if (footer == null) { // nothing to validate return(new ValidationResult(failures)); } if (header.PartNumber > 0) { ValidatePart(article, failures); return(new ValidationResult(failures)); } if (footer.PartSize != article.Data.Length) { failures.Add(new ValidationFailure( YencValidationErrorCodes.SizeMismatch, Resources.Yenc.SizeMismatch, new { DataSize = article.Data.Length, FooterSize = footer.PartSize })); } if (!footer.Crc32.HasValue) { failures.Add(new ValidationFailure( YencValidationErrorCodes.MissingChecksum, Resources.Yenc.MissingChecksum)); return(new ValidationResult(failures)); } uint calculatedCrc32 = Crc32.CalculateChecksum(article.Data); if (calculatedCrc32 != footer.Crc32.Value) { failures.Add(new ValidationFailure( YencValidationErrorCodes.ChecksumMismatch, Resources.Yenc.ChecksumMismatch, new { CalculatedChecksum = calculatedCrc32, FooterChecksum = footer.Crc32.Value })); } return(new ValidationResult(failures)); }
/// <summary> /// Creates a new instance of the <see cref="YencArticle"/> class. /// </summary> /// <param name="header">The header of the Yenc-encoded article.</param> /// <param name="footer">The optional footer of the Yenc-encoded article.</param> /// <param name="data">The binary data obtained by decoding the Yenc-decoded article.</param> public YencArticle(YencHeader header, YencFooter footer, byte[] data) { Header = header.ThrowIfNull(nameof(header)); Footer = footer; Data = data.ThrowIfNull(nameof(data)); }
/// <summary> /// Creates a new instance of the <see cref="YencStream"/> class. /// </summary> /// <param name="header">The header of the Yenc-encoded article.</param> /// <param name="input">An enumeration of byte chunks from the decoded Yenc-encoded article.</param> public YencStream(YencHeader header, IEnumerable <byte[]> input) : base(input) { Guard.ThrowIfNull(header, nameof(header)); Guard.ThrowIfNull(input, nameof(input)); Header = header; }
/// <summary> /// Encodes the binary data in the specified stream into yEnc-encoded text /// using the specified character encoding. /// </summary> /// <param name="header">The yEnc header.</param> /// <param name="stream">The stream containing the binary data to encode.</param> /// <param name="encoding">The character encoding to use.</param> /// <returns>The yEnc-encoded text.</returns> public static IEnumerable <string> Encode(YencHeader header, Stream stream, Encoding encoding) { yield return(GetHeaderLine(header)); if (header.IsFilePart) { yield return(GetPartHeaderLine(header)); } var encodedBytes = new byte[1024]; var encodedOffset = 0; int lastCol = header.LineLength - 1; uint checksum = Crc32.Initialize(); for (var offset = 0; offset < header.PartSize; offset++) { int @byte = stream.ReadByte(); if (@byte == -1) { // end of stream break; } checksum = Crc32.Calculate(checksum, @byte); int val = (@byte + 42) % 256; // encode dot only in first column bool encodeDot = encodedOffset == 0; // encode white space only in first and last column bool encodeWhitespace = encodedOffset == 0 || encodedOffset == lastCol; // encode critical characters if (val == YencCharacters.Null || val == YencCharacters.Lf || val == YencCharacters.Cr || val == YencCharacters.Equal || val == YencCharacters.Dot && encodeDot || val == YencCharacters.Space && encodeWhitespace || val == YencCharacters.Tab && encodeWhitespace) { encodedBytes[encodedOffset++] = YencCharacters.Equal; val = (val + 64) % 256; } encodedBytes[encodedOffset++] = (byte)val; if (encodedOffset < header.LineLength) { continue; } // return encoded line yield return(encoding.GetString(encodedBytes, 0, encodedOffset)); // reset offset encodedOffset = 0; } if (encodedOffset > 0) { // return remainder yield return(encoding.GetString(encodedBytes, 0, encodedOffset)); } checksum = Crc32.Finalize(checksum); yield return(GetFooterLine(header, checksum)); }
/// <summary> /// Encodes the binary data in the specified stream into yEnc-encoded text /// using the default Usenet character encoding. /// </summary> /// <param name="header">The yEnc header.</param> /// <param name="stream">The stream containing the binary data to encode.</param> /// <returns>The yEnc-encoded text.</returns> public static IEnumerable <string> Encode(YencHeader header, Stream stream) => Encode(header, stream, UsenetEncoding.Default);