public Model(Stream stream, string path) { base._data = Data = new GifFormat(this, stream, path); // Arbitrary sanity limit. if (Data.FileSize > 50000000) { IssueModel.Add("File size insanely huge", Severity.Fatal); return; } Data.fBuf = new byte[(int)Data.FileSize]; stream.Position = 0; int got = stream.Read(Data.fBuf, 0, (int)Data.FileSize); if (got < Data.FileSize) { IssueModel.Add("Read failed.", Severity.Fatal); return; } Data.Version = Data.fBuf[4] == '7'? "87a" : "89a"; Data.Width = ConvertTo.FromLit16ToInt32(Data.fBuf, 6); Data.Height = ConvertTo.FromLit16ToInt32(Data.fBuf, 8); }
public Model(Stream stream, byte[] header, string path) { base._data = Data = new AviFormat(this, stream, path); ParseRiff(header); var buf = new byte[0xC0]; stream.Position = 0; int got = stream.Read(buf, 0, 0xC0); if (got != 0xC0) { IssueModel.Add("File is short", Severity.Fatal); return; } Data.StreamCount = ConvertTo.FromLit32ToInt32(buf, 0x38); Data.Width = ConvertTo.FromLit32ToInt32(buf, 0x40); Data.Height = ConvertTo.FromLit32ToInt32(buf, 0x44); Data.Codec = Encoding.ASCII.GetString(buf, 0xBC, 4).Trim(); CalcMark(); GetDiagsForMarkable(); }
private void GetDiagnostics() { if ((Data.Apps & JpegApps.Jfif) != 0) { // This spec is often violated. if (Data.DensityX == 0 || Data.DensityY == 0) { IssueModel.Add($"Invalid JFIF density of {Data.DensityX}x{Data.DensityY}", Severity.Trivia); } if ((int)Data.Units > 2) { IssueModel.Add($"Invalid JFIF units of {Data.Units}", Severity.Warning); } } if (Data.eoiPos == null) { IssueModel.Add("Missing end marker"); } else if (Data.ExcessSize == 0) { var unparsedSize = Data.FileSize - Data.ValidSize; if (unparsedSize > 0) { IssueModel.Add($"Possible watermark, size={unparsedSize}", Severity.Advisory); } } }
private void GetDiagnostics() { if (Data.storedHash == null) { IssueModel.Add("No signature.", Severity.Trivia, IssueTags.Fussy); } }
protected void GetDiagnostics() { if (Data.Moov == 0) { IssueModel.Add("Missing 'moov' section.", Severity.Error); } if (Data.Mdat == 0) { IssueModel.Add("Missing 'mdat' section.", Severity.Error); } // Wide boxes are rare. if (Data.Wide != 0) { IssueModel.Add($"Number of wide boxes={Data.Wide}.", Severity.Noise); } if (Data.ExcessSize == 0) { var unparsedSize = Data.FileSize - Data.ValidSize; if (unparsedSize != 0) { IssueModel.Add($"Possible watermark, size={unparsedSize}.", Severity.Advisory); } } }
protected void GetDiagnostics() { GetRiffDiagnostics(); if (Data.CompCode != (int)WaveCompression.PCM) { IssueModel.Add("Data is not PCM", Severity.Trivia, IssueTags.Substandard); } }
protected void ParseRiff(byte[] hdr) { var buf = new byte[8]; long chunkSize = ConvertTo.FromLit32ToUInt32(hdr, 4) + 8; if (chunkSize % 1 != 0) { IssueModel.Add("RIFF has odd sized chunk, some tools don't pad this correctly", Severity.Trivia); } do { if (Data.ValidSize + chunkSize > Data.FileSize) { IssueModel.Add("File truncated", Severity.Fatal); return; } ++Data.RiffChunkCount; Data.RiffSize = Data.ValidSize = Data.ValidSize + chunkSize; if (Data.ValidSize + 8 > Data.FileSize) { // Not enough bytes for a header. return; } try { Data.fbs.Position = Data.ValidSize; } catch (EndOfStreamException) { IssueModel.Add("File truncated or corrupt", Severity.Fatal); return; } var got = Data.fbs.Read(buf, 0, 8); if (got != 8) { IssueModel.Add("Read error", Severity.Fatal); return; } chunkSize = ConvertTo.FromLit32ToUInt32(buf, 4) + 8; }while (buf[0] == 'R' || buf[1] == 'I' || buf[2] == 'F' || buf[3] == 'F'); if (buf[0] == 'J' && buf[1] == 'U' && buf[2] == 'N' && buf[3] == 'K') { if (Data.ValidSize + chunkSize > Data.FileSize) { IssueModel.Add("File corrupt or truncated", Severity.Fatal); return; } Data.JunkSize = chunkSize; Data.ValidSize += Data.JunkSize; } }
public override void CalcHashes(Hashes hashFlags, Validations validationFlags) { if (base.Data.Issues.HasFatal) { return; } if ((validationFlags & Validations.Exists) != 0) { Data.MissingCount = 0; if (Data.Files.Items.Count != 1 || Data.Files.Items[0].Name != Data.IgnoredName) { for (int ix = 0; ix < Data.Files.Items.Count; ++ix) { FileItem item = Data.Files.Items[ix]; var name = item.Name; if (Data.AllowNonFile && (name.StartsWith("http:") || name.StartsWith("https:"))) { IssueModel.Add($"Ignoring URL '{name}'.", Severity.Trivia); } else { try { if (!System.IO.Path.IsPathRooted(name)) { name = Data.Files.RootDir + System.IO.Path.DirectorySeparatorChar + name; } else if (Data.ForbidRooted) { IssueModel.Add($"File is rooted: '{item.Name}'."); } } catch (ArgumentException ex) { IssueModel.Add($"Malformed file name '{name}': {ex.Message}"); FilesModel.SetIsFound(ix, false); ++Data.MissingCount; continue; } // Exists doesn't seem to throw any exceptions, so no try/catch. bool isFound = File.Exists(name); FilesModel.SetIsFound(ix, isFound); if (!isFound) { IssueModel.Add($"File '{item.Name}' not found.", Severity.Advisory); ++Data.MissingCount; } } } } } base.CalcHashes(hashFlags, validationFlags); }
public void CalcHashWebCheck() { if (Data.storedHash == null) { Severity sev = Data.EacVersionString != null && Data.EacVersionString.StartsWith("1")? Severity.Warning : Severity.Noise; Data.ShIssue = IssueModel.Add("EAC log self-hash not present.", sev, IssueTags.StrictErr); } else { string boundary = "---------------------------" + DateTime.Now.Ticks; string header = "Content-Disposition: form-data; name=\"LogFile\"; filename=\"" + "SubmittedByConDiags.log\"\r\nContent-Type: application/octet-stream\r\n\r\n"; byte[] bndBuf = Encoding.UTF8.GetBytes("\r\n--" + boundary + "\r\n"); byte[] hdrBuf = Encoding.UTF8.GetBytes(header); byte[] tlrBuf = Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n"); var req = (HttpWebRequest)WebRequest.Create("http://www.exactaudiocopy.de/log/check.aspx"); req.ContentType = "multipart/form-data; boundary=" + boundary; req.Method = "POST"; req.KeepAlive = true; req.Credentials = CredentialCache.DefaultCredentials; try { using (var qs = req.GetRequestStream()) { qs.Write(bndBuf, 0, bndBuf.Length); qs.Write(hdrBuf, 0, hdrBuf.Length); qs.Write(Data.fBuf, 0, Data.fBuf.Length); qs.Write(tlrBuf, 0, tlrBuf.Length); } using (WebResponse res = req.GetResponse()) using (Stream ps = res.GetResponseStream()) using (StreamReader rdr = new StreamReader(ps)) { string answer = rdr.ReadLine(); if (answer.Contains("is fine")) { Data.ShIssue = IssueModel.Add("EAC log self-hash verify successful.", Severity.Advisory, IssueTags.Success); } else if (answer.Contains("incorrect")) { Data.ShIssue = IssueModel.Add("EAC log self-hash mismatch, file has been modified.", Severity.Error, IssueTags.Failure); } else { Data.ShIssue = IssueModel.Add("EAC log self-hash verify attempt returned unknown result.", Severity.Advisory, IssueTags.StrictErr); } } } catch (Exception ex) { Data.ShIssue = IssueModel.Add("EAC log self-hash verify attempt failed: " + ex.Message.Trim(null), Severity.Warning, IssueTags.StrictErr); } } }
public Model(Stream stream, string path) { base._data = Data = new DbFormat(this, stream, path); // No content diagnostics at this time. if (Data.fbs.Length == 0) { IssueModel.Add("File is empty."); } }
public void CheckFlacRipTags(IList <FlacFormat> flacs) { int prevTrackNum = -1; foreach (FlacFormat flac in flacs) { var trackTag = flac.GetTagValue("TRACKNUMBER"); var integerRegex = new Regex("^([0-9]+)", RegexOptions.Compiled); MatchCollection reMatches = integerRegex.Matches(trackTag); string trackTagCapture = reMatches.Count == 1 ? reMatches[0].Groups[1].ToString() : trackTag; if (int.TryParse(trackTagCapture, out int trackNum)) { if (prevTrackNum >= 0 && trackNum != prevTrackNum + 1) { IssueModel.Add($"Gap in TRACKNUMBER tags near '{trackTag}'.", Severity.Error, IssueTags.BadTag); } prevTrackNum = trackNum; } } TagCheckTextIsSame(flacs, "TRACKTOTAL"); TagCheckTextIsSame(flacs, "DISCNUMBER"); TagCheckTextIsSame(flacs, "DISCTOTAL"); TagCheckTextIsSame(flacs, "DATE"); TagCheckTextIsSame(flacs, "RELEASE DATE"); bool?isSameAA = FlacFormat.IsFlacTagsAllSame(flacs, "ALBUMARTIST"); if (isSameAA == false) { IssueModel.Add("ALBUMARTIST tags are inconsistent.", Severity.Warning, IssueTags.BadTag | IssueTags.StrictErr); } else if (isSameAA == null) { bool?isSameArtist = FlacFormat.IsFlacTagsAllSame(flacs, "ARTIST"); if (isSameArtist == false) { IssueModel.Add("Inconsistent ARTIST tag or missing ALBUMARTIST tag.", Severity.Warning, IssueTags.BadTag); } else if (isSameArtist == null) { IssueModel.Add("ARTIST is missing.", Severity.Warning, IssueTags.BadTag); } } TagCheckTextIsSame(flacs, "ALBUM"); TagCheckTextIsSame(flacs, "ORGANIZATION"); TagCheckTextIsSame(flacs, "BARCODE"); TagCheckTextIsSame(flacs, "CATALOGNUMBER"); TagCheckTextIsSame(flacs, "ALBUMARTISTSORTORDER"); }
public Model(Stream stream, byte[] hdr, string path) { base._data = Data = new Mpeg2Format(this, stream, path); if (Data.FileSize < 12) { IssueModel.Add("File truncated near start", Severity.Error); return; } Data.IsVOB = hdr[3] == 0xBA; // TODO parse contents for watermark/truncation Data.ValidSize = Data.FileSize; long loopStop = Data.FileSize - 36; if (loopStop < 8) { loopStop = 8; } for (long ePos = Data.FileSize - 4; ePos > loopStop; --ePos) { Data.fbs.Position = ePos; var got = Data.fbs.Read(hdr, 0, 4); if (got != 4) { IssueModel.Add("Read error looking for trailer", Severity.Fatal); return; } if (hdr[0] == 0 && hdr[1] == 0 && hdr[2] == 1 && hdr[3] == 0xB9) { // 20 bytes of zero seem typical .mpg pad, so can't do this watermark calculation Data.trailerPos = ePos; Data.ValidSize = ePos + 4; break; } } if (Data.trailerPos < 0) { IssueModel.Add("No trailermark found.", Severity.Trivia); } else { long unparsedSize = Data.FileSize - Data.ValidSize; if (unparsedSize != 0) { IssueModel.Add($"Possible watermark, size={unparsedSize}", Severity.Trivia); } } }
protected void CalcMark(bool assumeProbable = false) { byte[] buf = null; long markSize = Data.FileSize - Data.ValidSize; if (markSize <= 0) { return; } // 1000 is somewhat arbitrary here. if (markSize > 1000) { Data.Watermark = Likeliness.Possible; } else { Data.fbs.Position = Data.ValidSize; buf = new byte[(int)markSize]; int got = Data.fbs.Read(buf, 0, (int)markSize); if (got != markSize) { IssueModel.Add("Read failure", Severity.Fatal); return; } Data.excess = null; Data.Watermark = Likeliness.Probable; if (!assumeProbable) { for (int ix = 0; ix < buf.Length; ++ix) { // Fuzzy ID of watermark bytes by checking if ASCII text. Purpose is to // exclude .AVI files that do not have correct encoded sizes - a common issue. var bb = buf[ix]; if (bb > 127 || (bb < 32 && bb != 0 && bb != 9 && bb != 0x0A && bb != 0x0D)) { Data.Watermark = Likeliness.Possible; break; } } } } if (Data.Watermark == Likeliness.Probable) { Data.excess = buf; var caption = Data.Watermark.ToString() + " watermark, size=" + Data.ExcessSize + "."; var prompt = "Trim probable watermark of " + Data.ExcessSize + " byte" + (Data.ExcessSize != 1? "s" : ""); IssueModel.Add(caption, Severity.Warning, IssueTags.None, prompt, TrimWatermark); } }
protected void GetRiffDiagnostics() { if (Data.RiffSize <= 12) { IssueModel.Add("Missing data", Severity.Error); } long unparsedSize = Data.FileSize - Data.ValidSize - Data.ExcessSize; if (unparsedSize != 0) { IssueModel.Add("Unrecognized bytes at end = " + unparsedSize, Severity.Warning); } }
private void TagCheckNumber(string key, bool optional = false) { string value = Data.GetTagValue(key); if (value == null) { if (!optional) { IssueModel.Add(key + " tag is missing.", Severity.Warning, IssueTags.BadTag | IssueTags.StrictErr); } } else if (value.Length == 0 || !Char.IsDigit(value[0])) { IssueModel.Add(key + " tag is not a number.", Severity.Warning, IssueTags.BadTag | IssueTags.StrictErr); } }
protected void GetDiagsForMarkable() { if (Data.RiffSize <= 12) { IssueModel.Add("Missing data", Severity.Error); } if (Data.ExcessSize == 0) { var unparsedSize = base.Data.FileSize - base.Data.ValidSize; if (unparsedSize > 0) { IssueModel.Add("Possible watermark, size=" + unparsedSize, Severity.Trivia); } } }
public Model(Stream stream, string path) : base(path) { base._data = Data = new CueFormat(this, stream, path); SetIgnoredName("Range.wav"); Data.fbs.Position = 0; TextReader tr = new StreamReader(Data.fbs, LogBuffer.cp1252); for (int line = 1; ; ++line) { var lx = tr.ReadLine(); if (lx == null) { break; } lx = lx.TrimStart(); if (lx.Length == 0) { continue; } if (lx.StartsWith("CATALOG ")) { Data.Catalog = lx.Substring(8).Trim(); if (Data.Catalog.Length != 13) { IssueModel.Add("Invalid CATALOG."); } continue; } if (lx.Length > 0 && lx.StartsWith("FILE ")) { var name = Data.GetQuotedField(lx, 5); if (name.Length == 0) { IssueModel.Add("Missing file name."); } else { FilesModel.Add(name); } } } }
public override void CalcHashes(Hashes hashFlags, Validations validationFlags) { if (Data.Issues.HasFatal) { return; } if (Data.Lame != null && (hashFlags & Hashes.Intrinsic) != 0 && Data.Lame.ActualDataCrc == null) { var hasher = new Crc16rHasher(); hasher.Append(Data.fBuf, (int)Data.mediaPosition, Data.Lame.LameHeaderSize); byte[] hash = hasher.GetHashAndReset(); LameModel.SetActualHeaderCrc(BitConverter.ToUInt16(hash, 0)); hasher.Append(Data.fBuf, (int)Data.mediaPosition + 0xC0, (int)Data.MediaCount - 0xC0); hash = hasher.GetHashAndReset(); LameModel.SetActualDataCrc(BitConverter.ToUInt16(hash, 0)); if (!Data.IsBadData && !Data.IsBadHeader) { Data.ChIssue = Data.CdIssue = IssueModel.Add("CRC-16 checks successful.", Severity.Noise, IssueTags.Success); } else { if (Data.IsBadHeader) { Data.ChIssue = IssueModel.Add("CRC-16 check failed on audio header.", Severity.Error, IssueTags.Failure); } else { Data.ChIssue = IssueModel.Add("CRC-16 check successful on audio header.", Severity.Noise, IssueTags.Success); } if (Data.IsBadData) { Data.CdIssue = IssueModel.Add("CRC-16 check failed on audio data.", Severity.Error, IssueTags.Failure); } else { Data.CdIssue = IssueModel.Add("CRC-16 check successful on audio data.", Severity.Noise, IssueTags.Success); } } } base.CalcHashes(hashFlags, validationFlags); }
public override void CalcHashes(Hashes hashFlags, Validations validationFlags) { if (Data.Issues.HasFatal) { return; } if ((hashFlags & Hashes.Intrinsic) != 0 && Data.badCrcCount == null) { Data.badCrcCount = 0; foreach (var master in Data.layout) { foreach (var cx in master.GetNodeTraces("CRC-32")) { var top = cx.Peek(); var node = top.Node.Nodes[top.Index] as EbmlNodeCRC; if (node.Count > 0) { var hasher = new Crc32rHasher(); hasher.Append(Data.fbs, node.Start, node.Count); var hash = hasher.GetHashAndReset(); node.ActualCRC32 = BitConverter.ToUInt32(hash, 0); if (node.StoredCRC32 != node.ActualCRC32) { ++Data.badCrcCount; } } } } if (Data.CrcCount > 0) { if (Data.badCrcCount == 0) { Data.CdIssue = IssueModel.Add("CRC checks successful.", Severity.Noise, IssueTags.Success); } else { Data.CdIssue = IssueModel.Add("CRC check failure.", Severity.Error, IssueTags.Failure); } } } base.CalcHashes(hashFlags, validationFlags); }
private void TagCheckDate(string key, bool optional = false) { string value = Data.GetTagValue(key); if (value == null) { if (!optional) { IssueModel.Add(key + " tag is missing.", Severity.Warning, IssueTags.BadTag | IssueTags.StrictErr); } } else if ((value.Length != 4 && value.Length != 10) || (!value.StartsWith("19") && !value.StartsWith("20")) || !Char.IsDigit(value[2]) || !Char.IsDigit(value[3])) { IssueModel.Add(key + " tag not like YYYY or YYYY-MM-DD with YYYY in 1900 to 2099.", Severity.Warning, IssueTags.BadTag | IssueTags.StrictErr); } }
public override void CalcHashes(Hashes hashFlags, Validations validationFlags) { if ((hashFlags & Hashes.Intrinsic) != 0 && Data.BadCrcCount == null) { Data.BadCrcCount = 0; var hasher = new Crc32rHasher(); int pos = 12; for (int ix = 0; ix < Data.Chunks.Items.Count; ++ix) { PngChunk chunk = Data.Chunks.Items[ix]; hasher.Append(Data.fBuf, pos, (int)chunk.Size + 4); byte[] hash = hasher.GetHashAndReset(); UInt32 actualCRC = BitConverter.ToUInt32(hash, 0); ChunksModel.SetActualCRC(ix, actualCRC); if (actualCRC != chunk.StoredCRC) { ++Data.BadCrcCount; } pos += (int)chunk.Size + 12; } var sb = new StringBuilder(); sb.Append("CRC checks on "); if (Data.BadCrcCount != 0) { sb.Append(Data.BadCrcCount); sb.Append(" of "); sb.Append(Data.Chunks.Items.Count); sb.Append(" chunks failed."); } else { sb.Append(Data.Chunks.Items.Count); sb.Append(" chunks successful."); } Data.CdIssue = IssueModel.Add(sb.ToString(), Data.BadCrcCount == 0? Severity.Noise : Severity.Error, Data.BadCrcCount == 0? IssueTags.Success : IssueTags.Failure); } base.CalcHashes(hashFlags, validationFlags); }
private string ParseTracks(string lx) { for (;;) { if (parser.EOF || lx == "No errors occured" || lx == "No errors occurred" || lx == "There were errors" || lx.Contains("accurate")) { break; } if (!lx.StartsWith("Track") || lx.StartsWith("Track quality")) { lx = parser.ReadLineLTrim(); continue; } bool success = Int32.TryParse(lx.Substring(6), out int num); if (!success) { Data.TkIssue = IssueModel.Add("Invalid track " + parser.GetPlace(), Severity.Fatal, IssueTags.Failure); break; } lx = parser.ReadLineLTrim(); if (!lx.StartsWith("Filename ")) { Data.TkIssue = IssueModel.Add($"Track {num}: Missing 'Filename'.", Severity.Error, IssueTags.Failure); } else { lx = ParseTrack(lx, num); } } return(lx); }
public override void CalcHashes(Hashes hashFlags, Validations validationFlags) { if (IssueModel.Data.HasFatal) { return; } if ((hashFlags & Hashes.PcmMD5) != 0 && Data.actualMediaMD5 == null) { try { var hasher = new Md5Hasher(); hasher.Append(Data.fbs, Data.mediaPosition, Data.MediaCount); Data.actualMediaMD5 = hasher.GetHashAndReset(); } catch (EndOfStreamException) { IssueModel.Add("File truncated near audio.", Severity.Fatal); } } if ((hashFlags & Hashes.PcmCRC32) != 0 && Data.ActualCRC32 == null) { try { var hasher = new Crc32rHasher(); hasher.Append(Data.fbs, Data.mediaPosition, Data.MediaCount); var hash = hasher.GetHashAndReset(); Data.ActualCRC32 = BitConverter.ToUInt32(hash, 0); } catch (EndOfStreamException) { IssueModel.Add("File truncated near audio.", Severity.Fatal); } } base.CalcHashes(hashFlags, validationFlags); }
public void Validate(Hashes hashFlags, IList <FlacFormat> flacs) { actualFlacs = flacs; if (flacs == null || flacs.Count == 0 || flacs.Max(s => s.Issues.MaxSeverity) >= Severity.Error) { GetDiagnostics(); } else if (flacs.Count == Data.Files.Items.Count) { var m = "Repair bad file reference"; if (Data.MissingCount != 1) { m += "s"; } GetDiagnostics(m, RepairMissing, (hashFlags & Hashes._Repairing) != 0); } else if (Data.Files.Items.Count != 1) { Data.FcIssue = IssueModel.Add($"Folder contains {flacs.Count} .flac file(s) yet .cue contains {Data.Files.Items.Count} file reference(s).", Severity.Error, IssueTags.Failure); } }
public Model(Stream stream, string path) { base._tracksModel = TracksModel = new LogEacTrack.Vector.Model(); base._data = Data = new LogEacFormat(this, stream, path); Data.AccurateRip = null; Data.RipDate = String.Empty; // Arbitrary limit. if (Data.FileSize > 250000) { IssueModel.Add("File insanely huge.", Severity.Fatal); return; } Data.fBuf = new byte[Data.FileSize]; Data.fbs.Position = 0; var got = Data.fbs.Read(Data.fBuf, 0, (int)Data.FileSize); if (got != Data.FileSize) { IssueModel.Add("Read failed", Severity.Fatal); return; } if (got < 2 || Data.fBuf[0] != 0xFF || Data.fBuf[1] != 0xFE) { Data.Codepage = Encoding.GetEncoding(1252); } else { Data.Codepage = Encoding.Unicode; } Parse(new LogBuffer(Data.fBuf, Data.Codepage)); GetDiagnostics(); }
protected void ReadPlaylist(Encoding codePage) { long fileSize = Data.FileSize; if (fileSize > 512 * 1024) { IssueModel.Add("Oversized file", Severity.Fatal); return; } Data.fBuf = new byte[fileSize]; Data.fbs.Position = 0; if (Data.fbs.Read(Data.fBuf, 0, (int)fileSize) != fileSize) { IssueModel.Add("Read error", Severity.Fatal); return; } using (TextReader reader = new StreamReader(new MemoryStream(Data.fBuf), codePage)) { for (;;) { var lx = reader.ReadLine(); if (lx == null) { break; } lx = lx.TrimStart(); if (lx.Length > 0 && lx[0] != '#') { FilesModel.Add(lx); } } } Data.Codepage = codePage; }
public override void CalcHashes(Hashes hashFlags, Validations validationFlags) { if (Data.Issues.HasFatal) { return; } if ((hashFlags & Hashes.Intrinsic) != 0 && Data.ActualAudioHeaderCRC8 == null) { var hasher1 = new Crc8Hasher(); hasher1.Append(Data.aHdr); byte[] hash1 = hasher1.GetHashAndReset(); Data.ActualAudioHeaderCRC8 = hash1[0]; try { var hasher2 = new Crc16nHasher(); hasher2.Append(Data.fbs, Data.mediaPosition, Data.FileSize - Data.mediaPosition - 2); byte[] hash2 = hasher2.GetHashAndReset(); Data.ActualAudioBlockCRC16 = BitConverter.ToUInt16(hash2, 0); } catch (EndOfStreamException ex) { IssueModel.Add("Read failed while checking audio CRC: " + ex.Message, Severity.Fatal); return; } if (!Data.IsBadDataCRC && !Data.IsBadHeaderCRC) { Data.ChIssue = Data.CdIssue = IssueModel.Add("CRC checks successful on audio header and data.", Severity.Noise, IssueTags.Success); } else { if (Data.IsBadHeaderCRC) { Data.ChIssue = IssueModel.Add("CRC-8 check failed on audio header.", Severity.Error, IssueTags.Failure); } else { Data.ChIssue = IssueModel.Add("CRC-8 check successful on audio header.", Severity.Noise, IssueTags.Success); } if (Data.IsBadDataCRC) { Data.CdIssue = IssueModel.Add("CRC-16 check failed on audio data.", Severity.Error, IssueTags.Failure); } else { Data.CdIssue = IssueModel.Add("CRC-16 check successful on audio data.", Severity.Noise, IssueTags.Success); } } } if ((hashFlags & Hashes.PcmMD5) != 0 && Data.actualAudioDataMD5 == null) { Process px = null; try { px = StartFlac(Data.Path); } catch (Exception ex) { IssueModel.Add("flac executable failed with '" + ex.Message.Trim(null) + "'."); } if (px != null) { using (var br = new BinaryReader(px.StandardOutput.BaseStream)) { try { var hasher = new Md5Hasher(); hasher.Append(br); var hash = hasher.GetHashAndReset(); Data.actualAudioDataMD5 = hash; } catch (EndOfStreamException ex) { IssueModel.Add("Read failed while verifying audio MD5: " + ex.Message, Severity.Fatal); } if (Data.IsBadDataMD5) { Data.CmIssue = IssueModel.Add("MD5 check failed on audio data.", Severity.Error, IssueTags.Failure); } else { Data.CmIssue = IssueModel.Add("MD5 check successful on audio data.", Severity.Noise, IssueTags.Success); } } } } if ((hashFlags & (Hashes.PcmCRC32 | Hashes._FlacMatch)) != 0 && Data.ActualPcmCRC32 == null) { Process px = null; try { px = StartFlac(Data.Path); } catch (Exception ex) { IssueModel.Add("flac executable failed with '" + ex.Message.Trim(null) + "'."); } if (px != null) { using (var br = new BinaryReader(px.StandardOutput.BaseStream)) { var hasher = new Crc32rHasher(); hasher.Append(br); byte[] hash = hasher.GetHashAndReset(); Data.ActualPcmCRC32 = BitConverter.ToUInt32(hash, 0); } } } if ((hashFlags & (Hashes._FlacTags)) != 0) { TagCheckNumber("TRACKNUMBER"); TagCheckNumber("TRACKTOTAL", optional: true); TagCheckNumber("DISCNUMBER", true); TagCheckNumber("DISCTOTAL", true); TagCheckDate("DATE"); TagCheckDate("RELEASE DATE", optional: true); TagCheckDate("ORIGINAL RELEASE DATE", true); TagCheckText("TITLE"); TagCheckText("ARTIST"); TagCheckText("ALBUM"); TagCheckText("ALBUMARTIST", optional: true); TagCheckText("ALBUMARTISTSORTORDER", true); TagCheckText("ORGANIZATION", true); TagCheckText("BARCODE", true); TagCheckText("CATALOGNUMBER", true); } base.CalcHashes(hashFlags, validationFlags); }
private void GetDiagnostics() { if (Data.MetadataBlockStreamInfoSize != 0x22) { IssueModel.Add($"Unexpected Metadata block size of {Data.MetadataBlockStreamInfoSize}", Severity.Advisory); } if (Data.MinBlockSize < 16) { IssueModel.Add("Minimum block size too low"); } if (Data.MinBlockSize > 65535) { IssueModel.Add("Maximum block size too high"); } if (Data.RawSampleRate == 0xF) { IssueModel.Add("Invalid sample rate"); } if (Data.RawSampleSize == 3 || Data.RawSampleSize == 7) { IssueModel.Add($"Use of sample size index {Data.RawSampleSize} is reserved"); } if (Data.RawChannelAssignment >= 0xB) { IssueModel.Add($"Use of reserved (undefined) channel assignment {Data.RawChannelAssignment}", Severity.Warning); } if (Data.RawBlockSize == 0) { IssueModel.Add("Block size index 0 use is reserved", Severity.Warning); } if (Data.RawSampleRate == 0xF) { IssueModel.Add("Sample rate index 15 use is invalid"); } if (Data.RawChannelAssignment >= 0xB) { IssueModel.Add($"Channel index {Data.RawChannelAssignment} use is reserved", Severity.Warning); } if (Data.Blocks.Tags.Lines.Count != Data.Blocks.Tags.StoredTagCount) { IssueModel.Add("Stored tag count wrong"); } foreach (var lx in Data.Blocks.Tags.Lines) { if (lx.IndexOf('=') < 0) { IssueModel.Add("Invalid tag line: " + lx); } // U+FFFD is substituted by .NET when malformed utf8 encountered. if (lx.Contains('\uFFFD')) { IssueModel.Add("Tag with malformed UTF-8 character encoding: " + lx); } if (lx.Any(cu => Char.IsSurrogate(cu))) { IssueModel.Add("Tag contains character(s) beyond the basic multilingual plane (may cause player issues): " + lx, Severity.Trivia); } } int picPlusPadSize = Data.Blocks.Items.Where(b => b.BlockType == FlacBlockType.Padding || b.BlockType == FlacBlockType.Picture).Sum(b => b.Size); int bloat = picPlusPadSize - MaxPicPlusPadSize; if (bloat > 0) { var msg = $"Artwork+padding consume {picPlusPadSize} bytes."; repairPadSize = Data.Blocks.PadBlock.Size - bloat; if (repairPadSize < 0) { IssueModel.Add(msg, Severity.Trivia, IssueTags.StrictErr); } else { if (repairPadSize > 4096) { repairPadSize = 4096; } IssueModel.Add(msg, Severity.Trivia, IssueTags.StrictWarn, $"Trim {Data.Blocks.PadBlock.Size-repairPadSize} bytes of excess padding", RepairArtPadBloat); } } }
public Model(Stream stream, byte[] hdr, string path) { base._data = Data = new FlacFormat(this, stream, path); Data.MetadataBlockStreamInfoSize = ConvertTo.FromBig24ToInt32(hdr, 5); if (Data.MetadataBlockStreamInfoSize < 34) { IssueModel.Add($"Bad metablock size of {Data.MetadataBlockStreamInfoSize}", Severity.Fatal); return; } var bb = new byte[Data.MetadataBlockStreamInfoSize]; Data.ValidSize = 8; Data.fbs.Position = Data.ValidSize; var got = Data.fbs.Read(bb, 0, Data.MetadataBlockStreamInfoSize); if (got != Data.MetadataBlockStreamInfoSize) { IssueModel.Add("File truncated", Severity.Fatal); return; } Data.MinBlockSize = ConvertTo.FromBig16ToInt32(bb, 0); Data.MinBlockSize = ConvertTo.FromBig16ToInt32(bb, 2); Data.MinFrameSize = ConvertTo.FromBig24ToInt32(bb, 4); Data.MaxFrameSize = ConvertTo.FromBig24ToInt32(bb, 7); Data.MetaSampleRate = bb[10] << 12 | bb[11] << 4 | bb[12] >> 4; Data.ChannelCount = ((bb[12] & 0x0E) >> 1) + 1; Data.BitsPerSample = (((bb[12] & 1) << 4) | (bb[13] >> 4)) + 1; Data.TotalSamples = ((bb[13] & 0x0F) << 32) | bb[14] << 24 | bb[15] << 16 | bb[16] << 8 | bb[17]; Data.storedAudioDataMD5 = new byte[16]; Array.Copy(bb, 18, Data.storedAudioDataMD5, 0, 16); Data.ValidSize += Data.MetadataBlockStreamInfoSize; for (;;) { bb = new byte[12]; try { Data.fbs.Position = Data.ValidSize; } catch (EndOfStreamException) { IssueModel.Add("File truncated near meta data", Severity.Fatal); return; } Data.fbs.Position = Data.ValidSize; got = Data.fbs.Read(bb, 0, 4); if (got != 4) { IssueModel.Add("File truncated near meta data", Severity.Fatal); return; } if (bb[0] == 0xFF) { break; } int blockSize = ConvertTo.FromBig24ToInt32(bb, 1); Data.ValidSize += 4; switch ((FlacBlockType)(bb[0] & 0x7F)) { case FlacBlockType.Padding: Data.Blocks.AddPad((int)Data.ValidSize, blockSize); break; case FlacBlockType.Application: got = Data.fbs.Read(bb, 0, 4); if (got != 4) { IssueModel.Add("File truncated near tags", Severity.Fatal); return; } int appId = ConvertTo.FromBig32ToInt32(bb, 0); Data.Blocks.AddApp((int)Data.ValidSize, blockSize, appId); break; case FlacBlockType.SeekTable: var st = new byte[blockSize]; got = Data.fbs.Read(st, 0, blockSize); if (got != blockSize) { IssueModel.Add("File truncated near seek table", Severity.Fatal); return; } Data.Blocks.AddSeekTable((int)Data.ValidSize, blockSize, st); break; case FlacBlockType.Tags: bb = new byte[blockSize]; Data.fbs.Position = Data.ValidSize; got = Data.fbs.Read(bb, 0, blockSize); if (got != blockSize) { IssueModel.Add("File truncated near tags", Severity.Fatal); return; } if (Data.Blocks.Tags != null) { IssueModel.Add("Contains multiple tag blocks", Severity.Error); } else { Data.Blocks.AddTags((int)Data.ValidSize, blockSize, bb); } break; case FlacBlockType.CueSheet: var sb = new byte[284]; got = Data.fbs.Read(sb, 0, 284); if (got != 284) { IssueModel.Add("File truncated near cuesheet", Severity.Fatal); return; } var isCD = (sb[24] & 0x80) != 0; int trackCount = sb[283]; Data.Blocks.AddCuesheet((int)Data.ValidSize, blockSize, isCD, trackCount); break; case FlacBlockType.Picture: var pb = new byte[blockSize]; got = Data.fbs.Read(pb, 0, blockSize); if (got != blockSize) { IssueModel.Add("File truncated near picture", Severity.Fatal); return; } var picType = (PicType)ConvertTo.FromBig32ToInt32(pb, 0); var mimeLen = ConvertTo.FromBig32ToInt32(pb, 4); var mime = Encoding.UTF8.GetString(pb, 8, mimeLen); var descLen = ConvertTo.FromBig32ToInt32(pb, mimeLen + 8); var desc = Encoding.UTF8.GetString(pb, mimeLen + 12, descLen); var width = ConvertTo.FromBig32ToInt32(pb, mimeLen + descLen + 12); var height = ConvertTo.FromBig32ToInt32(pb, mimeLen + descLen + 16); Data.Blocks.AddPic((int)Data.ValidSize, blockSize, picType, width, height); break; case FlacBlockType.Invalid: IssueModel.Add("Encountered invalid block type", Severity.Fatal); return; default: IssueModel.Add($"Encountered reserved block type '{bb[0]}'", Severity.Warning); break; } Data.ValidSize += blockSize; } try { Data.fbs.Position = Data.ValidSize; } catch (EndOfStreamException) { IssueModel.Add("File truncated near frame header", Severity.Fatal); return; } got = Data.fbs.Read(bb, 0, 4); if (got != 4) { IssueModel.Add("File truncated", Severity.Fatal); return; } // Detect frame header sync code if (bb[0] != 0xFF || (bb[1] & 0xFC) != 0xF8) { IssueModel.Add("Audio data not found", Severity.Fatal); return; } Data.mediaPosition = Data.ValidSize; Data.SampleOrFrameNumber = Data.fbs.ReadWobbly(out byte[] wtfBuf); if (Data.SampleOrFrameNumber < 0) { IssueModel.Add("File truncated or badly formed sample/frame number.", Severity.Fatal); return; } Array.Copy(wtfBuf, 0, bb, 4, wtfBuf.Length); int bPos = 4 + wtfBuf.Length; Data.RawBlockingStrategy = bb[1] & 1; Data.RawBlockSize = bb[2] >> 4; if (Data.RawBlockSize == 0) { Data.BlockSize = 0; } else if (Data.RawBlockSize == 1) { Data.BlockSize = 192; } else if (Data.RawBlockSize >= 2 && Data.RawBlockSize <= 5) { Data.BlockSize = 576 * (1 << (Data.RawBlockSize - 2)); } else if (Data.RawBlockSize == 6) { got = Data.fbs.Read(bb, bPos, 1); Data.BlockSize = bb[bPos] + 1; bPos += 1; } else if (Data.RawBlockSize == 7) { got = Data.fbs.Read(bb, bPos, 2); Data.BlockSize = (bb[bPos] << 8) + bb[bPos + 1] + 1; bPos += 2; } else { Data.BlockSize = 256 * (1 << (Data.RawBlockSize - 8)); } Data.RawSampleRate = bb[2] & 0xF; if (Data.RawSampleRate == 0xC) { got = Data.fbs.Read(bb, bPos, 1); Data.SampleRateText = bb[bPos] + "kHz"; bPos += 1; } else if (Data.RawSampleRate == 0xD || Data.RawSampleRate == 0xE) { got = Data.fbs.Read(bb, bPos, 2); Data.SampleRateText = (bb[bPos] << 8).ToString() + bb[bPos + 1] + (Data.RawSampleRate == 0xD? " Hz" : " kHz"); bPos += 2; } else if (Data.RawSampleRate == 0) { Data.SampleRateText = Data.MetaSampleRate.ToString() + " Hz"; } else { Data.SampleRateText = SampleRateMap[Data.RawSampleRate]; } Data.RawChannelAssignment = bb[3] >> 4; Data.RawSampleSize = (bb[3] & 0xE) >> 1; if (Data.RawSampleSize == 0) { Data.SampleSizeText = Data.BitsPerSample.ToString() + " bits"; } else { Data.SampleSizeText = SampleSizeMap[Data.RawSampleSize]; } Data.aHdr = new byte[bPos]; Array.Copy(bb, Data.aHdr, bPos); Data.ValidSize += bPos; Data.fbs.Position = Data.ValidSize; int octet = Data.fbs.ReadByte(); if (octet < 0) { IssueModel.Add("File truncated near CRC-8", Severity.Fatal); return; } Data.StoredAudioHeaderCRC8 = (Byte)octet; try { Data.fbs.Position = Data.mediaPosition; } catch (EndOfStreamException) { IssueModel.Add("File truncated near audio data", Severity.Fatal); return; } try { Data.fbs.Position = Data.FileSize - 2; } catch (EndOfStreamException) { IssueModel.Add("File truncated looking for end", Severity.Fatal); return; } bb = new byte[2]; if (Data.fbs.Read(bb, 0, 2) != 2) { IssueModel.Add("Read failed on audio block CRC-16", Severity.Fatal); return; } Data.StoredAudioBlockCRC16 = (UInt16)(bb[0] << 8 | bb[1]); Data.MediaCount = Data.FileSize - Data.mediaPosition; GetDiagnostics(); }
public Model(Stream stream, string path) { base._data = Data = new Mp3Format(this, stream, path); if (Data.FileSize > Int32.MaxValue) { IssueModel.Add("File is insanely large", Severity.Fatal); return; } int mediaPos32; int mediaCount32; byte[] fBuf; fBuf = Data.fBuf = new byte[Data.FileSize]; Data.fbs.Position = 0; if (Data.fbs.Read(fBuf, 0, (int)Data.FileSize) != Data.FileSize) { IssueModel.Add("Read error", Severity.Fatal); return; } // Detect ID3v2 tag block. if (fBuf[0] == 'I' && fBuf[1] == 'D' && fBuf[2] == '3') { Data.id3v2Pos = 0; if (Data.FileSize < 10) { IssueModel.Add("File truncated near ID3v2 header", Severity.Fatal); return; } Data.Id3v2Major = Data.fBuf[3]; Data.Id3v2Revision = Data.fBuf[4]; Data.storedId3v2DataSize = ((fBuf[6] & 0x7F) << 21) + ((fBuf[7] & 0x7F) << 14) + ((fBuf[8] & 0x7F) << 7) + (fBuf[9] & 0x7F); if (Data.storedId3v2DataSize == 0 || Data.storedId3v2DataSize + 12 > Data.FileSize) { IssueModel.Add("ID3v2 size of " + Data.storedId3v2DataSize + " is bad or file is truncated", Severity.Fatal); return; } if ((fBuf[6] & 0x80) != 0 || (fBuf[7] & 0x80) != 0 || (fBuf[8] & 0x80) != 0 || (Data.fBuf[9] & 0x80) != 0) { IssueModel.Add("Zero parity error on ID3v2 size"); } Data.actualId3v2DataSize = Data.storedId3v2DataSize; // Check for bug in old versions of EAC that 1.5% of time writes ID3v2 size over by 1. if (Data.Id3v2Major == 3) { if ((fBuf[10 + Data.storedId3v2DataSize] & 0xFE) == 0xFA) { if (fBuf[9 + Data.storedId3v2DataSize] == 0xFF) { --Data.actualId3v2DataSize; // Bug bite comfirmed. Data.Id3v2TagRepair = String.Format("0009=0x{0:X2}", Data.actualId3v2DataSize & 0x7F); if ((Data.actualId3v2DataSize & 0x7F) == 0x7F) { Data.Id3v2TagRepair = String.Format("0008=0x{0:X2}, ", ((Data.actualId3v2DataSize >> 7) & 0x7F)) + Data.Id3v2TagRepair; } } } } Data.ValidSize = 10 + Data.actualId3v2DataSize; } while (Data.ValidSize < Data.FileSize && fBuf[Data.ValidSize] == 0) { ++Data.ValidSize; ++Data.DeadBytes; } if (Data.FileSize - Data.ValidSize < 0xC0) { IssueModel.Add("File appears truncated.", Severity.Fatal); return; } Data.Header = new Mp3Header(fBuf, (int)Data.ValidSize); if (!Data.Header.IsMpegLayer3) { IssueModel.Add("ID3v2 tag present but no MP3 marker found.", Severity.Fatal); return; } if (Data.Header.MpegVersionBits == 1) { IssueModel.Add("MP3 marker found but MPEG version is not valid.", Severity.Fatal); return; } mediaPos32 = (int)Data.ValidSize; Data.ValidSize = Data.FileSize; // Keep the audio header. Data.aBuf = new byte[Data.Header.XingOffset + 0x9C]; Array.Copy(fBuf, mediaPos32, Data.aBuf, 0, Data.aBuf.Length); // Detect Xing/LAME encodes: XingModel = Mp3XingBlock.Create(Data.aBuf, Data.Header); if (XingModel != null) { LameModel = XingModel as Mp3LameBlock.Model; Data.Xing = XingModel.Data; Data.Lame = Data.Xing as Mp3LameBlock; } // Detect ID3v1 tag block: int ePos = (int)Data.FileSize; if (ePos >= 130 && fBuf[ePos - 128] == 'T' && fBuf[ePos - 127] == 'A' && fBuf[ePos - 126] == 'G') { ePos -= 128; Data.id3v1Block = new byte[128]; Array.Copy(fBuf, ePos, Data.id3v1Block, 0, 128); } // Detect obsolete Lyrics3v2 block: if (ePos >= 15) { if (fBuf[ePos - 9] == 'L' && fBuf[ePos - 8] == 'Y' && fBuf[ePos - 7] == 'R' && fBuf[ePos - 6] == 'I' && fBuf[ePos - 5] == 'C' && fBuf[ePos - 4] == 'S' && fBuf[ePos - 3] == '2' && fBuf[ePos - 2] == '0' && fBuf[ePos - 1] == '0') { int l3size = 0; for (var bi = ePos - 15; bi < ePos - 9; ++bi) { if (fBuf[bi] < '0' || fBuf[bi] > '9') { IssueModel.Add("Invalid Lyrics3v2 length digit."); return; } l3size = l3size * 10 + fBuf[bi] - '0'; } Data.Lyrics3Size = l3size + 15; ePos -= Data.Lyrics3Size; if (ePos < 2) { IssueModel.Add("Invalid Lyrics3v2 length."); return; } } } // Detect APE tag block: if (ePos >= 34) { int pos = ePos - 32; if (fBuf[pos] == 'A' && fBuf[pos + 1] == 'P' && fBuf[pos + 2] == 'E' && fBuf[pos + 3] == 'T' && fBuf[pos + 4] == 'A' && fBuf[pos + 5] == 'G' && fBuf[pos + 6] == 'E' && fBuf[pos + 7] == 'X') { Data.ApeSize = 32 + fBuf[pos + 12] + (fBuf[pos + 13] << 8) + (fBuf[pos + 14] << 16) + (fBuf[pos + 15] << 24); ePos -= Data.ApeSize; } } if (ePos <= mediaPos32) { IssueModel.Add("Missing audio", Severity.Fatal); return; } mediaCount32 = ePos - mediaPos32; // Detect 2nd, phantom ID3v1 tag block: if (Data.Lame != null && mediaCount32 == Data.Lame.LameSize + 128 && Data.HasId3v1 && !Data.HasLyrics3 && !Data.HasApe && ePos > 34) { if (fBuf[ePos - 128] == 'T' && fBuf[ePos - 127] == 'A' && fBuf[ePos - 126] == 'G') { ePos -= 128; mediaCount32 -= 128; Data.ValidSize -= 128; Data.excess = Data.id3v1Block; Data.id3v1Block = new byte[128]; Array.Copy(fBuf, ePos, Data.id3v1Block, 0, 128); Data.Watermark = Likeliness.Probable; } } Data.mediaPosition = mediaPos32; Data.MediaCount = mediaCount32; GetDiagnostics(); }