@@ -612,5 +612,97 @@ public void Save()
612612
613613 /// <inheritdoc/>
614614 public override IFeatureCollection Features => _features ??= new PackageFeatureCollection ( this ) ;
615+
616+ /// <summary>
617+ /// Determines whether the provided stream represents an encrypted Office Open XML file.
618+ /// </summary>
619+ /// <param name="inputStream">The <see cref="Stream"/> to check. The stream must be seekable and not null.</param>
620+ /// <returns>
621+ /// <c>true</c> if the stream is an encrypted Office file (either OLE Compound File or contains an encrypted package part); otherwise, <c>false</c>.
622+ /// </returns>
623+ /// <exception cref="ArgumentNullException">Thrown if <paramref name="inputStream"/> is null.</exception>
624+ /// <exception cref="ArgumentException">Thrown if <paramref name="inputStream"/> is not seekable.</exception>
625+ /// <remarks>
626+ /// This method checks for the OLE Compound File signature at the start of the stream, which is used for encrypted Office files.
627+ /// If not found, it attempts to open the stream as an OPC package and checks for the presence of an encrypted package part.
628+ /// The stream position is restored after the check.
629+ /// </remarks>
630+ public static bool IsEncryptedOfficeFile ( Stream inputStream )
631+ {
632+ if ( inputStream is null )
633+ {
634+ throw new ArgumentNullException ( nameof ( inputStream ) ) ;
635+ }
636+
637+ if ( ! inputStream . CanSeek )
638+ {
639+ throw new ArgumentException ( "Stream must be seekable." ) ;
640+ }
641+
642+ long originalPosition = inputStream . Position ;
643+
644+ try
645+ {
646+ byte [ ] header = new byte [ 8 ] ;
647+ inputStream . Seek ( 0 , SeekOrigin . Begin ) ;
648+ int read = inputStream . Read ( header , 0 , header . Length ) ;
649+ inputStream . Seek ( originalPosition , SeekOrigin . Begin ) ;
650+
651+ // OLE Compound File signature for encrypted Office files
652+ if ( read == 8 && header . SequenceEqual ( new byte [ ] { 0xD0 , 0xCF , 0x11 , 0xE0 , 0xA1 , 0xB1 , 0x1A , 0xE1 } ) )
653+ {
654+ return true ;
655+ }
656+
657+ // If not OLE, try to open as package and check for encrypted part
658+ try
659+ {
660+ using ( var package = System . IO . Packaging . Package . Open ( inputStream , FileMode . Open , FileAccess . Read ) )
661+ {
662+ foreach ( var part in package . GetParts ( ) )
663+ {
664+ if ( part . ContentType . Equals ( "application/vnd.openxmlformats-officedocument.encrypted-package" , StringComparison . OrdinalIgnoreCase ) )
665+ {
666+ return true ;
667+ }
668+ }
669+ }
670+ }
671+ catch
672+ {
673+ return false ;
674+ }
675+
676+ return false ;
677+ }
678+ finally
679+ {
680+ inputStream . Seek ( originalPosition , SeekOrigin . Begin ) ;
681+ }
682+ }
683+
684+ /// <summary>
685+ /// Determines whether the file at the specified path is an encrypted Office Open XML file.
686+ /// </summary>
687+ /// <param name="filePath">The path to the file to check. Must not be null.</param>
688+ /// <returns>
689+ /// <c>true</c> if the file is an encrypted Office file (either OLE Compound File or contains an encrypted package part); otherwise, <c>false</c>.
690+ /// </returns>
691+ /// <exception cref="ArgumentNullException">Thrown if <paramref name="filePath"/> is null.</exception>
692+ /// <remarks>
693+ /// This method opens the file at the specified path and checks its contents using <see cref="IsEncryptedOfficeFile(Stream)"/>.
694+ /// </remarks>
695+ public static bool IsEncryptedOfficeFile ( string filePath )
696+ {
697+ if ( filePath is null )
698+ {
699+ throw new ArgumentNullException ( nameof ( filePath ) ) ;
700+ }
701+
702+ using ( FileStream fileStream = File . Open ( filePath , FileMode . Open , FileAccess . Read , FileShare . Read ) )
703+ {
704+ return IsEncryptedOfficeFile ( fileStream ) ;
705+ }
706+ }
615707 }
616708}
0 commit comments