From 62a8831388c083cd829cfb15726118e91116320f Mon Sep 17 00:00:00 2001 From: Bradley Van Fleet Date: Wed, 13 Jan 2021 16:42:13 -0700 Subject: [PATCH] Fix edge cse in ReadNextTransaction where EOF may cause an invalid X12FlatTransaction to be returned. Fix string documentation in X12Parser. --- .github/ISSUE_TEMPLATE/bug_report.md | 28 -- .github/ISSUE_TEMPLATE/feature_request.md | 17 - .gitignore | 1 + src/X12.Parsing/X12Parser.cs | 4 +- src/X12.Parsing/X12StreamReader.cs | 394 +++++++++++----------- 5 files changed, 202 insertions(+), 242 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 2fed4529..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. Windows 10, Windows 8.1, etc] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 066b2d92..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.gitignore b/.gitignore index 63690913..2a6d4059 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ +[Ll]ib/ # Visual Studio 2015/2017 cache/options directory .vs/ diff --git a/src/X12.Parsing/X12Parser.cs b/src/X12.Parsing/X12Parser.cs index 14b21ffe..1b1627cc 100644 --- a/src/X12.Parsing/X12Parser.cs +++ b/src/X12.Parsing/X12Parser.cs @@ -2,7 +2,6 @@ { using System; using System.Collections.Generic; - using System.Globalization; using System.IO; using System.Linq; using System.Text; @@ -151,6 +150,9 @@ public List ParseMultiple(Stream stream) /// Stream encoding for reading data /// collection parsed from X12 /// Thrown on any missing segments or parent IDs + /// + /// Thrown if segment cannot be identified and validated against the current specification + /// public List ParseMultiple(Stream stream, Encoding encoding) { var envelopes = new List(); diff --git a/src/X12.Parsing/X12StreamReader.cs b/src/X12.Parsing/X12StreamReader.cs index 22413d4a..6169fa35 100644 --- a/src/X12.Parsing/X12StreamReader.cs +++ b/src/X12.Parsing/X12StreamReader.cs @@ -1,229 +1,231 @@ namespace X12.Parsing { - using System; - using System.IO; - using System.Linq; - using System.Text; + using System; + using System.IO; + using System.Linq; + using System.Text; + + using X12.Parsing.Properties; + using X12.Shared.Models; + + /// + /// Represents a for reading an X12 file + /// + public class X12StreamReader : IDisposable + { + private readonly StreamReader reader; + private readonly char[] ignoredChars; - using X12.Parsing.Properties; - using X12.Shared.Models; + /// + /// Initializes a new instance of the class + /// + /// used for reading + /// used for properly reading the stream + /// Array of characters to be ignored while reading + public X12StreamReader(Stream stream, Encoding encoding, char[] ignoredChars) + { + this.reader = new StreamReader(stream, encoding); + var header = new char[106]; + if (this.reader.Read(header, 0, 106) < 106) + { + throw new ArgumentException(Resources.X12ReaderInvalidHeader); + } + + this.Delimiters = new X12DelimiterSet(header); + this.CurrentIsaSegment = new string(header); + this.ignoredChars = ignoredChars; + } /// - /// Represents a for reading an X12 file + /// Initializes a new instance of the class /// - public class X12StreamReader : IDisposable + /// used for reading + /// used for properly reading the stream + public X12StreamReader(Stream stream, Encoding encoding) + : this(stream, encoding, new char[] { }) { - private readonly StreamReader reader; - private readonly char[] ignoredChars; - - /// - /// Initializes a new instance of the class - /// - /// used for reading - /// used for properly reading the stream - /// Array of characters to be ignored while reading - public X12StreamReader(Stream stream, Encoding encoding, char[] ignoredChars) - { - this.reader = new StreamReader(stream, encoding); - var header = new char[106]; - if (this.reader.Read(header, 0, 106) < 106) - { - throw new ArgumentException(Resources.X12ReaderInvalidHeader); - } + } - this.Delimiters = new X12DelimiterSet(header); - this.CurrentIsaSegment = new string(header); - this.ignoredChars = ignoredChars; - } + /// + /// Gets the X12 Delimiters + /// + public X12DelimiterSet Delimiters { get; } + + /// + /// Gets the current ISA segment + /// + public string CurrentIsaSegment { get; private set; } + + /// + /// Gets the current GS segment + /// + public string CurrentGsSegment { get; private set; } + + /// + /// Gets the last transaction code + /// + public string LastTransactionCode { get; private set; } + + /// + /// Gets the segment id for the current segment + /// + /// Segment string with id to extract + /// The current segment id + public string ReadSegmentId(string segmentString) + { + int index = segmentString.IndexOf(this.Delimiters.ElementSeparator); + return index >= 0 ? segmentString.Substring(0, index) : null; + } + + /// + /// Splits the current segment string + /// + /// Segment string to split + /// Array of segment parts + public string[] SplitSegment(string segmentString) + { + int endSegmentIndex = segmentString.IndexOf(this.Delimiters.SegmentTerminator); + return endSegmentIndex >= 0 + ? segmentString.Substring(0, endSegmentIndex).Split(this.Delimiters.ElementSeparator) + : segmentString.Split(this.Delimiters.ElementSeparator); + } + + /// + /// Checks if the provided segment id is contained in the transaction + /// + /// Transaction to test + /// Segment id to check for + /// True if the segment id is present; otherwise, false + public bool TransactionContainsSegment(string transaction, string segmentId) + { + var segments = transaction.Split(this.Delimiters.SegmentTerminator).ToList(); + return segments.Exists(s => s.StartsWith(segmentId + this.Delimiters.ElementSeparator)); + } - /// - /// Initializes a new instance of the class - /// - /// used for reading - /// used for properly reading the stream - public X12StreamReader(Stream stream, Encoding encoding) - : this(stream, encoding, new char[] { }) + /// + /// Reads the next segment in the stream + /// + /// Segment string read from stream + public string ReadNextSegment() + { + bool isBinary = false; + var sb = new StringBuilder(); + var one = new char[1]; + while (this.reader.Read(one, 0, 1) == 1) + { + if (this.ignoredChars.Contains(one[0]) + || (one[0] == this.Delimiters.SegmentTerminator && sb.ToString().Trim().Length == 0)) { + continue; } - /// - /// Gets the X12 Delimiters - /// - public X12DelimiterSet Delimiters { get; } - - /// - /// Gets the current ISA segment - /// - public string CurrentIsaSegment { get; private set; } - - /// - /// Gets the current GS segment - /// - public string CurrentGsSegment { get; private set; } - - /// - /// Gets the last transaction code - /// - public string LastTransactionCode { get; private set; } - - /// - /// Gets the segment id for the current segment - /// - /// Segment string with id to extract - /// The current segment id - public string ReadSegmentId(string segmentString) + if (one[0] == this.Delimiters.SegmentTerminator) { - int index = segmentString.IndexOf(this.Delimiters.ElementSeparator); - return index >= 0 ? segmentString.Substring(0, index) : null; + break; } - /// - /// Splits the current segment string - /// - /// Segment string to split - /// Array of segment parts - public string[] SplitSegment(string segmentString) + if (one[0] != 0) { - int endSegmentIndex = segmentString.IndexOf(this.Delimiters.SegmentTerminator); - return endSegmentIndex >= 0 - ? segmentString.Substring(0, endSegmentIndex).Split(this.Delimiters.ElementSeparator) - : segmentString.Split(this.Delimiters.ElementSeparator); + sb.Append(one); } - /// - /// Checks if the provided segment id is contained in the transaction - /// - /// Transaction to test - /// Segment id to check for - /// True if the segment id is present; otherwise, false - public bool TransactionContainsSegment(string transaction, string segmentId) + if (isBinary && one[0] == this.Delimiters.ElementSeparator) { - var segments = transaction.Split(this.Delimiters.SegmentTerminator).ToList(); - return segments.Exists(s => s.StartsWith(segmentId + this.Delimiters.ElementSeparator)); + int binarySize = 0; + string[] elements = sb.ToString().Split(this.Delimiters.ElementSeparator); + if (elements[0] == "BIN" && elements.Length >= 2) + { + int.TryParse(sb.ToString().Split(this.Delimiters.ElementSeparator)[1], out binarySize); + } + + if (elements[0] == "BDS" && elements.Length >= 3) + { + int.TryParse(sb.ToString().Split(this.Delimiters.ElementSeparator)[2], out binarySize); + } + + if (binarySize > 0) + { + var buffer = new char[binarySize]; + this.reader.Read(buffer, 0, binarySize); + sb.Append(buffer); + break; + } } - /// - /// Reads the next segment in the stream - /// - /// Segment string read from stream - public string ReadNextSegment() + if (!isBinary && (sb.ToString() == "BIN" + this.Delimiters.ElementSeparator + || sb.ToString() == "BDS" + this.Delimiters.ElementSeparator)) { - bool isBinary = false; - var sb = new StringBuilder(); - var one = new char[1]; - while (this.reader.Read(one, 0, 1) == 1) - { - if (this.ignoredChars.Contains(one[0]) - || (one[0] == this.Delimiters.SegmentTerminator && sb.ToString().Trim().Length == 0)) - { - continue; - } - - if (one[0] == this.Delimiters.SegmentTerminator) - { - break; - } - - if (one[0] != 0) - { - sb.Append(one); - } - - if (isBinary && one[0] == this.Delimiters.ElementSeparator) - { - int binarySize = 0; - string[] elements = sb.ToString().Split(this.Delimiters.ElementSeparator); - if (elements[0] == "BIN" && elements.Length >= 2) - { - int.TryParse(sb.ToString().Split(this.Delimiters.ElementSeparator)[1], out binarySize); - } - - if (elements[0] == "BDS" && elements.Length >= 3) - { - int.TryParse(sb.ToString().Split(this.Delimiters.ElementSeparator)[2], out binarySize); - } - - if (binarySize > 0) - { - var buffer = new char[binarySize]; - this.reader.Read(buffer, 0, binarySize); - sb.Append(buffer); - break; - } - } - - if (!isBinary && (sb.ToString() == "BIN" + this.Delimiters.ElementSeparator - || sb.ToString() == "BDS" + this.Delimiters.ElementSeparator)) - { - isBinary = true; - } - } - - return sb.ToString().TrimStart(); + isBinary = true; } + } - /// - /// Returns the next transaction read from the stream. If no transaction is read, then null is returned - /// - /// Transaction read from the stream, if found; otherwise, null - public X12FlatTransaction ReadNextTransaction() - { - var segments = new StringBuilder(); + return sb.ToString().TrimStart(); + } - string segmentString; - string segmentId; - do + /// + /// Returns the next transaction read from the stream. If no transaction is read, then null is returned + /// + /// Transaction read from the stream, if found; otherwise, null + public X12FlatTransaction ReadNextTransaction() + { + var segments = new StringBuilder(); + + string segmentString; + string segmentId; + do + { + segmentString = this.ReadNextSegment(); + segmentId = this.ReadSegmentId(segmentString); + switch (segmentId) + { + case "ISA": + this.CurrentIsaSegment = segmentString + this.Delimiters.SegmentTerminator; + break; + case "GS": + this.CurrentGsSegment = segmentString + this.Delimiters.SegmentTerminator; + break; + case "IEA": + case "GE": + break; + case null: + // This case is added to handle where EOF is reached with no segments read. + break; + default: + if (segmentId == "ST") { - segmentString = this.ReadNextSegment(); - segmentId = this.ReadSegmentId(segmentString); - switch (segmentId) - { - case "ISA": - this.CurrentIsaSegment = segmentString + this.Delimiters.SegmentTerminator; - break; - case "GS": - this.CurrentGsSegment = segmentString + this.Delimiters.SegmentTerminator; - break; - case "IEA": - case "GE": - break; - default: - if (segmentId == "ST") - { - this.LastTransactionCode = this.SplitSegment(segmentString)[1]; - } - - segments.Append(segmentString); - segments.Append(this.Delimiters.SegmentTerminator); - break; - } + this.LastTransactionCode = this.SplitSegment(segmentString)[1]; } - while (!string.IsNullOrEmpty(segmentString) && segmentId != "SE"); - return segments.Length > 0 - ? new X12FlatTransaction(this.CurrentIsaSegment, this.CurrentGsSegment, segments.ToString()) - : null; + segments.Append(segmentString); + segments.Append(this.Delimiters.SegmentTerminator); + break; } + } while (!string.IsNullOrEmpty(segmentString) && segmentId != "SE"); - /// - /// Releases unmanaged resources - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } + return segments.Length > 0 + ? new X12FlatTransaction(this.CurrentIsaSegment, this.CurrentGsSegment, segments.ToString()) + : null; + } - /// - /// Releases unmanaged resources if disposing is true - /// - /// Flag indicating if object is being disposed - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - this.reader?.Dispose(); - } - } + /// + /// Releases unmanaged resources + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged resources if disposing is true + /// + /// Flag indicating if object is being disposed + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + this.reader?.Dispose(); + } } + } }