{*******************************************************}
{                                                       }
{       Tocsg.AIP                                       }
{                                                       }
{       Copyright (C) 2024 kku                          }
{                                                       }
{*******************************************************}

unit Tocsg.AIP;

interface

uses
  System.SysUtils, System.Classes, Winapi.ActiveX;

const
  SIGN_AIP_DRM: array [0..23] of Byte = ($2E, $70, $66, $69, $6C, $65, $03, $00,
                                           $00, $00, $00, $00, $00, $00, $8C, $01,
                                           $00, $00, $0D, $0A, $0D, $0A, $0D, $0A); // .pfile 로 시작

function IsAipEncrytedPDF(sPath: String): Boolean;
function GetAipLabelNameFromPDF(sPath: String): String;

function IsAipEncrytedOldOfficeDoc(sPath, sTempDir: String; nMode: Integer = STGM_READ or STGM_SHARE_EXCLUSIVE): Boolean;
function GetAipLabelNameFromOldOfficeDoc(sPath: String): String;

function IsAipEncrytedOfficeDoc(sPath, sTempDir: String;  nMode: Integer = STGM_READ or STGM_SHARE_EXCLUSIVE): Boolean;
function GetAipLabelNameFromOfficeDoc(sPath: String): String;

function CheckMsPfileExt(sPath: String): Boolean;
function CheckAipEncSign(sPath: String): Boolean;
function IsAipEncryted(sPath: String; sTempDir: String = ''; nMode: Integer = STGM_READ or STGM_SHARE_EXCLUSIVE): Boolean;
function ConvAipEncExt(sPath: String): String;

implementation

uses
  Tocsg.Exception, Tocsg.Hex, Winapi.Windows, Tocsg.Strings, Tocsg.Safe,
  AbUnzper, System.Zip, Tocsg.Path, Tocsg.OLE.Stg, Tocsg.Files;

function IsAipEncrytedPDF(sPath: String): Boolean;
var
  fs: TFileStream;
  pBuf: TBytes;
  sDelPath: String;
begin
  Result := false;
  try
    sDelPath := 'C:\ProgramData\HE\AEN\';
    if sPath.ToUpper.StartsWith(sDelPath.ToUpper) then
      exit;
//    WriteLnFileEndUTF8('C:\ProgramData\HE\ov_log.txt', Format('IsAipEncrytedPDF() .. Path=%s, TempDir=%s', [sPath, sDelPath]));

    if ForceDirectories(sDelPath) then
    begin
      sDelPath := sDelPath + '$' + ExtractFileName(sPath);
      if CopyFile(PChar(sPath), PChar(sDelPath), false) then
      begin
        try
          Guard(fs, TFileStream.Create(sDelPath, fmOpenRead or fmShareDenyNone));
          SetLength(pBuf, 2048);
          if fs.Read(pBuf[0], 2048) <> 2048 then
            exit;
          Result := PosBin(TEncoding.UTF8.GetBytes('MicrosoftIRMServices Protected PDF'), pBuf) <> -1;
        finally
          if sDelPath <> '' then
            DeleteFileForce(sDelPath);
        end;
      end;
    end;
  except
    on E: Exception do
      ETgException.TraceException(E, 'Fail .. IsAipEncryted()');
  end;
end;

function GetAipLabelNameFromPDF(sPath: String): String;
//const
//  p: array [0..13] of Byte = ($FE, $FF, $C7, $7C, $BC, $18, $00, $20, $B8, $08, $C7, $74, $BE, $14); // "일반 레이블"
var
  fs: TFileStream;
  pBuf, pLabel: TBytes;
  nBufLen, nPos, nEnd, nLabelLen: Integer;
begin
  Result := 'Unknown';
  try
    if not FileExists(sPath) then
      exit;

    nBufLen := 1024;
    SetLength(pBuf, nBufLen);

    fs := TFileStream.Create(sPath, fmOpenRead or fmShareDenyNone);
  // 암호화 레이블로 적용되었는지 확인
    if fs.Read(pBuf[0], nBufLen) <> nBufLen then
      exit;

    if PosBin(TEncoding.UTF8.GetBytes('MicrosoftIRMServices Protected PDF'), pBuf) <> -1 then
    begin
    // 암호화된 레이블 정보 위치
      fs.Seek(27440, soBeginning);
      fs.Read(pBuf[0], nBufLen);
    end else begin
    // 일반 레이블 정보 위치
      fs.Seek(-1024, soEnd);
      fs.Read(pBuf[0], nBufLen);
    end;

    nPos := PosBin(TEncoding.UTF8.GetBytes('/MSIP_Label_'), pBuf);
    if nPos > -1 then
    begin
      nPos := PosBin(TEncoding.UTF8.GetBytes('_Name ('), pBuf, nPos);
      if nPos > -1 then
      begin
        Inc(nPos, 7);
        nEnd := PosBin(TEncoding.UTF8.GetBytes(')'), pBuf, nPos);
        nLabelLen := nEnd - nPos;
        if (nEnd > -1) and (nLabelLen > 0) then
        begin
          SetLength(pLabel, nLabelLen);
          CopyMemory(pLabel, @pBuf[nPos], nLabelLen);
          if pLabel[0] = $FE then
            Result := TEncoding.BigEndianUnicode.GetString(pLabel)
          else
            Result := TEncoding.ANSI.GetString(pLabel);
        end;
      end;
    end;
  except
    on E: Exception do
      ETgException.TraceException(E, 'Fail .. GetAipLabelNameFromPDF()');
  end;
end;

function IsAipEncrytedOldOfficeDoc(sPath, sTempDir: String; nMode: Integer = STGM_READ or STGM_SHARE_EXCLUSIVE): Boolean;
var
//  fs: TFileStream;
//  pBuf: TBytes;
//  nBufLen: Integer;
  pvStg: IStorage;
  sData: WideString;
  enum: IEnumStatStg;
  elt: TStatStg;
  sDelPath: String;
begin
  Result := false;
  try
    if sPath.ToUpper.StartsWith(sTempDir.ToUpper) then
      exit;
//    WriteLnFileEndUTF8('C:\ProgramData\HE\ov_log.txt', Format('IsAipEncrytedOldOfficeDoc() .. Path=%s, TempDir=%s', [sPath, sTempDir]));

    pvStg := nil;
    sDelPath := '';
    try
    // 쉘 익스텐션 GetIconLocation() 에선 STGM_SHARE_EXCLUSIVE 모드 사용 시 스톱되서 아래처럼 처리 25_0122 14:03:25 kku
      if not StgStorageFileOpen(sPath, pvStg, nMode) then
      begin
        if (sTempDir <> '') and ForceDirectories(sTempDir) then
        begin
        // MS 파일은 사용중이면 안열려서 이렇게 처리 24_1114 09:24:47 kku
          sDelPath := sTempDir + '$' + ExtractFileName(sPath);
          if CopyFile(PChar(sPath), PChar(sDelPath), false) then
          begin
            if not StgStorageFileOpen(sDelPath, pvStg) then
              exit;
          end else exit;
        end else exit;
      end;

      if pvStg = nil then
        exit;

      enum := nil;
      if StgGetEnumElements(pvStg, enum) then
        Result := StgGetStatStgLike(enum, 'DRMContent', elt);
    finally
      enum := nil;
      pvStg := nil;
      if sDelPath <> '' then
        DeleteFileForce(sDelPath);
    end;
  except
    on E: Exception do
      ETgException.TraceException(E, 'Fail .. IsAipEncrytedOldOfficeDoc()');
  end;
end;

function GetAipLabelNameFromOldOfficeDoc(sPath: String): String;
begin
  Result := 'Unknown';
end;

function IsAipEncrytedOfficeDoc(sPath, sTempDir: String; nMode: Integer = STGM_READ or STGM_SHARE_EXCLUSIVE): Boolean;
var
  pvStg: IStorage;
  sData: WideString;
  sDelPath: String;
begin
  Result := false;
  try
    if sPath.ToUpper.StartsWith(sTempDir.ToUpper) then
      exit;
//    WriteLnFileEndUTF8('C:\ProgramData\HE\ov_log.txt', Format('IsAipEncrytedOfficeDoc() .. Path=%s, TempDir=%s', [sPath, sTempDir]));

    pvStg := nil;
    sDelPath := '';
    try
    // 쉘 익스텐션 GetIconLocation() 에선 STGM_SHARE_EXCLUSIVE 모드 사용 시 스톱되서 아래처럼 처리 25_0122 14:03:25 kku
      if not StgStorageFileOpen(sPath, pvStg, nMode) then
      begin
      // MS 파일은 사용중이면 안열려서 이렇게 처리 24_1114 09:24:47 kku
        if (sTempDir <> '') and ForceDirectories(sTempDir) then
        begin
        // MS 파일은 사용중이면 안열려서 이렇게 처리 24_1114 09:24:47 kku
          sDelPath := sTempDir + '$' + ExtractFileName(sPath);
          if CopyFile(PChar(sPath), PChar(sDelPath), false) then
          begin
            if not StgStorageFileOpen(sDelPath, pvStg) then
              exit;
          end else exit;
        end else exit;
      end;

      if pvStg = nil then
        exit;

      Result := StgGetStreamToText(pvStg, 'EncryptedPackage', sData);
    finally
      pvStg := nil;
      if sDelPath <> '' then
        DeleteFileForce(sDelPath);
    end;
  except
    on E: Exception do
      ETgException.TraceException(E, 'Fail .. IsAipEncrytedOldOfficeDoc()');
  end;
end;

function GetAipLabelNameFromOfficeDoc(sPath: String): String;
begin
  Result := 'Unknown';
end;

// MS문서 중 .pfile로 변경 가능성이 있는 확장자인지 체크 25_1222 17:52:57 kku
function CheckMsPfileExt(sPath: String): Boolean;
var
  sExt: String;
begin
  sExt := GetFileExt(sPath).ToUpper;
  Result := (sExt = 'DOC') or (sExt = 'XLS') or (sExt = 'PPT') or
            (sExt = 'DOT') or (sExt = 'PPS') or (sExt = 'POT') or
            (sExt = 'XLT') or (sExt = 'XLTM') or (sExt = 'XLTX') or
            (sExt = 'XPS') or (sExt = 'DOTM') or (sExt = 'PPSM') or
            (sExt = 'PPSX') or (sExt = 'PPTM') or (sExt = 'XPS');
end;

function CheckAipEncSign(sPath: String): Boolean;
begin
  Result := false;
  try
    if not FileExists(sPath) then
      exit;

    Result := CheckSign(sPath, @SIGN_AIP_DRM[0], 24);
  except
    on E: Exception do
      ETgException.TraceException(E, 'Fail .. CheckAipEncSign()');
  end;
end;

function IsAipEncryted(sPath: String; sTempDir: String = ''; nMode: Integer = STGM_READ or STGM_SHARE_EXCLUSIVE): Boolean;
var
  sExt: String;
begin
  Result := false;
  try
    if not FileExists(sPath) then
      exit;

    if sPath.ToUpper.StartsWith(sTempDir.ToUpper) then
      exit;
//    WriteLnFileEndUTF8('C:\ProgramData\HE\ov_log.txt', Format('IsAipEncryted() .. Path=%s, TempDir=%s', [sPath, sTempDir]));

    Result := CheckAipEncSign(sPath);
    if not Result then
    begin
      sExt := GetFileExt(sPath).ToUpper;
      if sExt = 'PDF' then
      begin
        Result := IsAipEncrytedPDF(sPath);
      end else
      if CheckMsPfileExt(sPath) then
      begin
        Result := IsAipEncrytedOldOfficeDoc(sPath, sTempDir, nMode);
        if not Result then
          Result := IsAipEncrytedOfficeDoc(sPath, sTempDir, nMode);
      end else
      if (sExt = 'DOCX') or (sExt = 'XLSX') or (sExt = 'PPTX') or
         (sExt = 'DOCM') or (sExt = 'DOTX') or (sExt = 'XLSM') or
         (sExt = 'XLSB') then
      begin
        Result := IsAipEncrytedOfficeDoc(sPath, sTempDir, nMode);
      end;
    end;
  except
    on E: Exception do
      ETgException.TraceException(E, 'Fail .. IsAipEncryted()');
  end;
end;

function ConvAipEncExt(sPath: String): String;
var
  sExt, sPathNoExt: String;
begin
  Result := sPath;
  if sPath = '' then
    exit;

  sExt := GetFileExt(sPath).ToUpper;
  sPathNoExt := CutFileExt(sPath);

  if sExt = 'TXT' then
    Result := sPathNoExt + '.ptxt'
  else if sExt = 'XML' then
    Result := sPathNoExt + '.pxml'
  else if sExt = 'PNG' then
    Result := sPathNoExt + '.ppng'
  else if sExt = 'BMP' then
    Result := sPathNoExt + '.pbmp'
  else if sExt = 'JPG' then
    Result := sPathNoExt + '.pjpg'
  else if sExt = 'JPEG' then
    Result := sPathNoExt + '.pjpeg'
  else if (sExt <> 'DOCX') and (sExt <> 'XLSX') and
          (sExt <> 'PPTX') and (sExt <> 'PDF') and
          (sExt <> 'XLSB') and (sExt <> 'XLSM') and
          (sExt <> 'DOTX') and (sExt <> 'DOCM') and
          (sExt <> 'DOC') and (sExt <> 'XLS') and
          (sExt <> 'PPT') and (sExt <> 'DOT') and
          (sExt <> 'PPS') and (sExt <> 'POT') and
          (sExt <> 'XLT') and (sExt <> 'XLTM') and (sExt <> 'XLTX') and
          (sExt <> 'XPS') and (sExt <> 'DOTM') and (sExt <> 'PPSM') and
          (sExt <> 'PPSX') and (sExt <> 'PPTM') and (sExt <> 'XPS') then
  begin
    Result := sPath + '.pfile';
  end;
end;

end.
