Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
- Every new public API member must have XML documentation matching the quality of the surrounding API.
- New protected members that define engine contracts or are intended for derived engine implementations must also have XML documentation.
- The documentation requirements do not apply to the `EPPlus45-FixCalcsEdition.MultiTarget` project or to `TestAndDemoExcelOps`; do not make documentation-only changes there.
- The HTML export API classes are currently excluded from documentation cleanup because they are tracked for a separate follow-up ticket.
- Historical documentation baselines should stay at zero. If a member is intentionally out of scope, add a narrow checker exclusion instead of raising a global allowance.
- Public enums and their values must be documented.
- Keep `<summary>` text short and focused, for example `Insert one or more columns.`.
Expand All @@ -43,6 +42,14 @@
- Static test workbooks belong in the appropriate `test_data` directories.
- When repository copy/clone scripts generate or synchronize shared source or test files, include the resulting copied files in the same change.

## Release Process

- Create releases only from the repository's primary integration branch, currently `main` or `master`.
- Do not create a release from a feature branch, ticket branch, Codex branch, or any other branch that has not been merged into the primary integration branch.
- Before creating a release, ensure the pull request has been created, reviewed as required, merged into the primary integration branch, and the build-and-test workflow for that branch has completed successfully.
- If the workflow is configured to run on pull requests and on pushes to the primary integration branch, wait for the relevant successful run after merge before creating the release.
- If a release is requested before these prerequisites are met, create or update the pull request first and explicitly tell the user that the release must wait for the successful build-and-test pipeline on the primary integration branch.

## File Encoding and Line Endings

- Save text files as UTF-8 with BOM and CRLF line endings, matching `.editorconfig`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,15 +407,34 @@ Namespace ExcelOps

''' <inheritdoc/>
Protected Overrides Sub LoadWorkbook(file As System.IO.FileInfo)
If Me.PasswordForOpening <> Nothing Then
Me._WorkbookPackage = New CompuMaster.Epplus4.ExcelPackage(file, Me.PasswordForOpening)
Else
Me._WorkbookPackage = New CompuMaster.Epplus4.ExcelPackage(file)
If IsUnsupportedBinaryXlsFile(file) Then
Throw New BinaryXlsFileNotSupportedException(file)
End If
Me._WorkbookPackage.Compatibility.IsWorksheets1Based = False
If IsUnsupportedBinaryXlsbFile(file) Then
Throw New BinaryXlsbFileNotSupportedException(file)
End If
Try
If Me.PasswordForOpening <> Nothing Then
Me._WorkbookPackage = New CompuMaster.Epplus4.ExcelPackage(file, Me.PasswordForOpening)
Else
Me._WorkbookPackage = New CompuMaster.Epplus4.ExcelPackage(file)
End If
Catch ex As Exception When IsUnsupportedBinaryXlsFile(file)
Throw New BinaryXlsFileNotSupportedException(file, ex)
Catch ex As Exception When IsPasswordProtectedFilePasswordMismatch(file)
Throw New FilePasswordProtectedMismatchException(file, ex)
End Try
If String.Equals(file.Extension, ".xlsb", StringComparison.OrdinalIgnoreCase) Then
Throw New BinaryXlsbFileNotSupportedException(file)
End If
Try
Me._WorkbookPackage.Compatibility.IsWorksheets1Based = False

'set workbook FullCalcOnLoad always to False since it's already triggered using property of Me.AutoCalculationOnLoad
Me.Workbook.FullCalcOnLoad = Me.AutoCalculationOnLoad 'unknown if executed after loading already completed or if it's a workbook setting with effect on opening as user in MS Excel, too
'set workbook FullCalcOnLoad always to False since it's already triggered using property of Me.AutoCalculationOnLoad
Me.Workbook.FullCalcOnLoad = Me.AutoCalculationOnLoad 'unknown if executed after loading already completed or if it's a workbook setting with effect on opening as user in MS Excel, too
Catch ex As System.Xml.XmlException
Throw New FileCorruptedOrInvalidFileFormatException(file, ex)
End Try
End Sub

''' <inheritdoc/>
Expand All @@ -428,17 +447,131 @@ Namespace ExcelOps

''' <inheritdoc/>
Protected Overrides Sub LoadWorkbook(data As System.IO.Stream)
If Me.PasswordForOpening <> Nothing Then
Me._WorkbookPackage = New CompuMaster.Epplus4.ExcelPackage(data, Me.PasswordForOpening)
Else
Me._WorkbookPackage = New CompuMaster.Epplus4.ExcelPackage(data)
If IsUnsupportedBinaryXlsData(data) Then
Throw New BinaryXlsFileNotSupportedException(CType(Nothing, String))
End If
Me._WorkbookPackage.Compatibility.IsWorksheets1Based = False
If IsUnsupportedBinaryXlsbData(data) Then
Throw New BinaryXlsbFileNotSupportedException(CType(Nothing, String))
End If
Try
If Me.PasswordForOpening <> Nothing Then
Me._WorkbookPackage = New CompuMaster.Epplus4.ExcelPackage(data, Me.PasswordForOpening)
Else
Me._WorkbookPackage = New CompuMaster.Epplus4.ExcelPackage(data)
End If
Catch ex As Exception When IsUnsupportedBinaryXlsData(data)
Throw New BinaryXlsFileNotSupportedException(CType(Nothing, String), ex)
Catch ex As Exception When IsPasswordProtectedDataPasswordMismatch(data)
Throw New FilePasswordProtectedMismatchException(CType(Nothing, String), ex)
End Try
Try
Me._WorkbookPackage.Compatibility.IsWorksheets1Based = False

'set workbook FullCalcOnLoad always to False since it's already triggered using property of Me.AutoCalculationOnLoad
Me.Workbook.FullCalcOnLoad = Me.AutoCalculationOnLoad 'unknown if executed after loading already completed or if it's a workbook setting with effect on opening as user in MS Excel, too
'set workbook FullCalcOnLoad always to False since it's already triggered using property of Me.AutoCalculationOnLoad
Me.Workbook.FullCalcOnLoad = Me.AutoCalculationOnLoad 'unknown if executed after loading already completed or if it's a workbook setting with effect on opening as user in MS Excel, too
Catch ex As System.Xml.XmlException
Throw New FileCorruptedOrInvalidFileFormatException(CType(Nothing, String), ex)
End Try
End Sub

Private Shared Function IsUnsupportedBinaryXlsFile(file As FileInfo) As Boolean
Using stream As FileStream = file.OpenRead()
Return IsUnsupportedBinaryXlsData(stream)
End Using
End Function

Private Shared Function IsUnsupportedBinaryXlsbFile(file As FileInfo) As Boolean
Using stream As FileStream = file.OpenRead()
Return IsUnsupportedBinaryXlsbData(stream)
End Using
End Function

Private Shared Function IsPasswordProtectedFilePasswordMismatch(file As FileInfo) As Boolean
Using stream As FileStream = file.OpenRead()
Return IsPasswordProtectedDataPasswordMismatch(stream)
End Using
End Function

Private Shared Function IsUnsupportedBinaryXlsData(stream As Stream) As Boolean
Return IsOleCompoundDocument(stream) AndAlso Not ContainsOleDirectoryName(stream, "EncryptedPackage")
End Function

Private Shared Function IsUnsupportedBinaryXlsbData(stream As Stream) As Boolean
Return ContainsZipEntryName(stream, "xl/workbook.bin")
End Function

Private Shared Function IsPasswordProtectedDataPasswordMismatch(stream As Stream) As Boolean
Return IsOleCompoundDocument(stream) AndAlso ContainsOleDirectoryName(stream, "EncryptedPackage")
End Function

Private Shared Function IsOleCompoundDocument(stream As Stream) As Boolean
Dim header = ReadHeader(stream, 8)
Dim oleHeader As Byte() = {&HD0, &HCF, &H11, &HE0, &HA1, &HB1, &H1A, &HE1}
Return ContainsBytes(header, oleHeader)
End Function

Private Shared Function ContainsOleDirectoryName(stream As Stream, name As String) As Boolean
Dim originalPosition As Long? = Nothing
If stream.CanSeek Then originalPosition = stream.Position
Try
If stream.CanSeek Then stream.Position = 0
Using copy As New MemoryStream()
stream.CopyTo(copy)
Dim data As Byte() = copy.ToArray()
Dim asciiNeedle As Byte() = Encoding.ASCII.GetBytes(name)
Dim unicodeNeedle As Byte() = Encoding.Unicode.GetBytes(name)
Return ContainsBytes(data, asciiNeedle) OrElse ContainsBytes(data, unicodeNeedle)
End Using
Finally
If originalPosition.HasValue Then stream.Position = originalPosition.Value
End Try
End Function

Private Shared Function ContainsZipEntryName(stream As Stream, name As String) As Boolean
Dim originalPosition As Long? = Nothing
If stream.CanSeek Then originalPosition = stream.Position
Try
If stream.CanSeek Then stream.Position = 0
Using copy As New MemoryStream()
stream.CopyTo(copy)
Dim data As Byte() = copy.ToArray()
Dim asciiNeedle As Byte() = Encoding.ASCII.GetBytes(name)
Return ContainsBytes(data, asciiNeedle)
End Using
Finally
If originalPosition.HasValue Then stream.Position = originalPosition.Value
End Try
End Function

Private Shared Function ReadHeader(stream As Stream, count As Integer) As Byte()
Dim originalPosition As Long? = Nothing
If stream.CanSeek Then originalPosition = stream.Position
Try
If stream.CanSeek Then stream.Position = 0
Dim buffer(count - 1) As Byte
Dim read As Integer = stream.Read(buffer, 0, buffer.Length)
If read <> buffer.Length Then Array.Resize(buffer, read)
Return buffer
Finally
If originalPosition.HasValue Then stream.Position = originalPosition.Value
End Try
End Function

Private Shared Function ContainsBytes(data As Byte(), pattern As Byte()) As Boolean
If pattern.Length = 0 OrElse data.Length < pattern.Length Then Return False
For index As Integer = 0 To data.Length - pattern.Length
Dim found As Boolean = True
For patternIndex As Integer = 0 To pattern.Length - 1
If data(index + patternIndex) <> pattern(patternIndex) Then
found = False
Exit For
End If
Next
If found Then Return True
Next
Return False
End Function

''' <inheritdoc/>
Public Overrides Function LookupLastCell(sheetName As String) As ExcelOps.ExcelCell
If sheetName = Nothing Then Throw New ArgumentNullException(NameOf(sheetName))
Expand Down
Loading
Loading