{*******************************************************} { } { KvCttSchClient } { } { Copyright (C) 2023 kku } { } {*******************************************************} unit KvCttSchClient; interface uses Tocsg.ClientBase, System.SysUtils, System.Classes, Tocsg.Thread, Winapi.Windows, System.Generics.Collections, Tocsg.Packet, ManagerPattern; type PExtrEnt = ^TExtrEnt; TExtrEnt = record sPath, sOldName, sSchPtrn: String; nRcvCmd, nReturnCmd, nEvent, nLimitHit: Integer; bDoEnc: Boolean; end; TKvCttSchClient = class; TThdExtrText = class(TTgThread) protected Client_: TKvCttSchClient; MgPtn_: TManagerPattern; qEnts_: TQueue; procedure OnNotifyEnt(Sender: TObject; const Item: PExtrEnt; Action: TCollectionNotification); procedure SetPatternList(sPatternOpt: String; var aList: TPatternEntList); procedure SetRuleToPtrnList(sKwdPtrn: String; var aList: TPatternEntList); procedure OnUnzipProgress(Sender : TObject; Progress : Byte; var Abort : Boolean); procedure OnPwdEvent(Sender : TObject; var NewPassword : String); procedure Execute; override; public Constructor Create(aClient: TKvCttSchClient); Destructor Destroy; override; procedure PushExtrEnt(pEnt: PExtrEnt); end; TKvCttSchClient = class(TTgClientBase) private ThdExtr_: TThdExtrText; protected procedure ConnectedEvent; override; procedure DisconnectedEvent; override; procedure ProcessRcvPacket(aRcv: IRcvPacket); override; public Constructor Create; Destructor Destroy; override; end; implementation uses Tocsg.Exception, CttSchDefine, Tocsg.KvFilter, Tocsg.Safe, Define, Tocsg.Path, Tocsg.DRM.Encrypt, Tocsg.Encrypt, Tocsg.KvFilter.types, System.Zip, Tocsg.Files, superobject, Tocsg.Strings, Tocsg.PCRE, Tocsg.KvFilter.adinfo, Tocsg.Json, ProcessDecompress, Condition, Tocsg.Fasoo, Winapi.ActiveX, Tocsg.Hash, Tocsg.Valid; { TThdExtrText } Constructor TThdExtrText.Create(aClient: TKvCttSchClient); procedure InitDefCttPattern; begin end; begin Inherited Create; Client_ := aClient; qEnts_ := TQueue.Create; MgPtn_ := TManagerPattern.Create; MgPtn_.LangId := 1; // 컨텐츠 필터 사용을 위함 end; Destructor TThdExtrText.Destroy; begin Inherited; FreeAndNil(MgPtn_); qEnts_.OnNotify := OnNotifyEnt; FreeAndNil(qEnts_); end; procedure TThdExtrText.PushExtrEnt(pEnt: PExtrEnt); begin qEnts_.Enqueue(pEnt); end; procedure TThdExtrText.OnNotifyEnt(Sender: TObject; const Item: PExtrEnt; Action: TCollectionNotification); begin if Action = cnRemoved then Dispose(Item); end; // ManagerService.pas에서 가져옴 23_0420 16:47:05 kku procedure TThdExtrText.SetPatternList(sPatternOpt: String; var aList: TPatternEntList); var O: ISuperObject; PtrnEnt, NewEnt: TPatternEnt; StrList: TStringList; i, nPos: Integer; iter: TSuperObjectIter; sName: String; begin try aList.Clear; if sPatternOpt = '' then exit; sPatternOpt := StringReplace(sPatternOpt, ';', '|', [rfReplaceAll]); sPatternOpt := StringReplace(sPatternOpt, #13#10, '|', [rfReplaceAll]); O := SO(sPatternOpt); if O <> nil then begin if ObjectFindFirst(O, iter) then begin Repeat if iter.key = 'scanoption' then begin Guard(StrList, TStringList.Create); SplitString(O.S['scanoption'], '|', StrList); for i := 0 to StrList.Count - 1 do begin PtrnEnt := MgPtn_.GetPatternEntByName(StrList[i]); if PtrnEnt <> nil then begin NewEnt := TPatternEnt.Create(MgPtn_, nil); NewEnt.AddName(MgPtn_.LangId, CttCodeToStr(StrList[i])); NewEnt.PatternList.Add(PtrnEnt.GetSearchText); aList.Add(NewEnt); end; end; end else begin sName := iter.key; if sName.StartsWith('custom__') then begin Delete(sName, 1, 8); // todo : 키워드, 패턴에 맞는 대응 필요? nPos := Pos('__keyword', sName); if nPos = 0 then nPos := Pos('__pattern', sName); if nPos > 0 then begin sName := Copy(sName, 1, nPos - 1); PtrnEnt := aList.GetPtrnEntByName(sName); if PtrnEnt = nil then begin PtrnEnt := TPatternEnt.Create(MgPtn_, nil); PtrnEnt.AddName(MgPtn_.LangId, sName); aList.Add(PtrnEnt); end; PtrnEnt.PatternList.Add(iter.val.AsString); end; end; end; Until not ObjectFindNext(iter); ObjectFindClose(iter); end; end; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. SetPatternList()'); end; end; // ManagerService.pas에서 가져옴 23_0919 15:26:45 kku procedure TThdExtrText.SetRuleToPtrnList(sKwdPtrn: String; var aList: TPatternEntList); var i: Integer; PtrnEnt: TPatternEnt; begin aList.Clear; if sKwdPtrn <> '' then begin var CusList: TStringList; Guard(CusList, TStringList.Create); SplitString(sKwdPtrn, '**', CusList); var InfoList: TStringList; Guard(InfoList, TStringList.Create); for i := 0 to CusList.Count - 1 do begin SplitString(CusList[i], '::', InfoList); if InfoList.Count > 4 then begin PtrnEnt := TPatternEnt.Create(MgPtn_, nil, nil, StrToIntDef(InfoList[2], 1)); PtrnEnt.RType := ManagerPattern.TRuleType(StrToIntDef(InfoList[3], 0)); PtrnEnt.IsAnd := InfoList[4] = 'T'; PtrnEnt.AddName(MgPtn_.LangId, InfoList[0]); PtrnEnt.PatternList.Add(InfoList[1]); aList.Add(PtrnEnt); end; end; // var nPos: Integer := 0; // var nCnt: Integer := 0; // var sPtrnName: String := ''; // var sPtrnEnt: String := ''; // var sPtrnVal: String := ''; // for i := 0 to CusList.Count - 1 do // begin // sPtrnEnt := CusList[i]; // nPos := Pos('::', sPtrnEnt); // if nPos > 0 then // begin // sPtrnName := Copy(sPtrnEnt, 1, nPos - 1); // Delete(sPtrnEnt, 1, nPos + 1); // nPos := Pos('|*|', sPtrnEnt); // if nPos > 0 then // begin // sPtrnVal := Copy(sPtrnEnt, 1, nPos - 1); // Delete(sPtrnEnt, 1, nPos + 2); // nCnt := StrToIntDef(sPtrnEnt, 1); // end else begin // sPtrnVal := sPtrnEnt; // nCnt := 1; // end; // // PtrnEnt := TPatternEnt.Create(MgPtn_, nil, nil, nCnt); // PtrnEnt.AddName(MgPtn_.LangId, sPtrnName); // PtrnEnt.PatternList.Add(sPtrnVal); // aList.Add(PtrnEnt); // end; // end; end; end; procedure TThdExtrText.OnUnzipProgress(Sender : TObject; Progress : Byte; var Abort : Boolean); begin end; procedure TThdExtrText.OnPwdEvent(Sender : TObject; var NewPassword : String); begin NewPassword := ''; end; procedure TThdExtrText.Execute; var KvFilter: TKvFilter; pEnt: PExtrEnt; sWorkDir, sFName, sTemp, sPass, sExt, sZipExtrDir: String; bIncDrm, bIncZip, bMakeDrm: Boolean; i: Integer; WorkList: TStringList; PatternEntList: TPatternEntList; nDecompDepth: Integer; SchExtList: TStringList; fas: TTgFasoo; function EncFile(sPath: String): Boolean; var enc: TTgDrmEnc; sEncPath: String; begin Result := false; try sEncPath := sWorkDir + Format('%d-dc_%s', [GetTickCount, ExtractFileName(sPath)]); case CUSTOMER_TYPE of CUSTOMER_WELFND, CUSTOMER_WELFNI : begin if (fas <> nil) and (fas.GetFileType(sPath) = 29) then begin if not MoveFile_wait(sPath, sEncPath, 3) then exit; var nResult: Integer := -1; if fas.DoPackagingFsn2(sEncPath, sPath, @nResult) then begin DeleteFile(PChar(sEncPath)); Result := true; end else begin _Trace('Fail .. FASOO DRM .. Code=%d', [nResult]); MoveFile_wait(sEncPath, sPath, 3); end; end; end; end; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. EncFile()'); end; end; procedure ExtractText(sSrcPath: String; sParentPath: String = ''; sOrgPPath: String = ''; bDoEnc: Boolean = false); var sOrgPath, sDecPath, sDestPath, sText: String; Send: ISendPacket; begin try sOrgPath := ''; sDecPath := ''; sDestPath := sWorkDir + Format('%d-%s.$kv', [GetTickCount, ExtractFileName(sSrcPath)]); DeleteFile(PChar(sDestPath)); try if bIncDrm then begin if (gParam.CustomerType = CUSTOMER_WELFNI) or (gParam.CustomerType = CUSTOMER_WELFND) then begin if fas <> nil then begin var nEncType: Integer := fas.GetFileType(sSrcPath); if nEncType = 103 then begin // 파수 암/복호화 시 확장자 필수 25_0414 14:58:58 kku sDecPath := sWorkDir + Format('%d-dc_%d.%s', [GetTickCount, ConvStrToHash(sSrcPath), GetFileExt(sSrcPath)]); if fas.DoExtract(sSrcPath, sDecPath, true) then begin sOrgPath := sSrcPath; sSrcPath := sDecPath; end; end; end; end else if TTgEncrypt.CheckSign(sSrcPath, SIG_DRM) then begin if sPass = '' then exit; var dec: TTgDrmDec := TTgDrmDec.Create(sSrcPath); try dec.CheckSig(SIG_DRM); // 헤더 오프셋 설정을 위해 추가 if not dec.ExtrHead(PASS_DRM_HEAD) then exit; sDecPath := sWorkDir + Format('%d-dc_%s', [GetTickCount, ExtractFileName(sSrcPath)]); if not dec.DecryptToFile(sPass, sDecPath) then exit; sOrgPath := sSrcPath; sSrcPath := sDecPath; finally dec.Free; end; end; end; except // .. end; try if KvFilter.FilterFile(sSrcPath, sDestPath) = KVERR_Success then begin if not FileExists(sDestPath) then exit; if sOrgPath <> '' then sSrcPath := sOrgPath; if sParentPath <> '' then sFName := sParentPath + ' > ' + ExtractFileName(sSrcPath) else sFName := ExtractFileName(sSrcPath); if sOrgPPath <> '' then sSrcPath := sOrgPPath; sText := Trim(ExtractTextSafe(sDestPath)); DeleteFile(PChar(sDestPath)); sText := StringReplace(sText, #0, ' ', [rfReplaceAll]); WorkList.Clear; if sText <> '' then begin case pEnt.nRcvCmd of KVC_REQUEST_EXTRACT_TEXT : begin Send := TTgPacket.Create(KVC_RESPONSE_EXTRACT_TEXT); Send.S['FName'] := sFName; Send.S['Path'] := sSrcPath; Send.S['Text'] := sText; Client_.SendPacket(Send); end; KVC_REQUEST_KEYWORD_SEARCH : begin if pEnt.sSchPtrn.Contains('scanoption') then SetPatternList(pEnt.sSchPtrn, PatternEntList) else SetRuleToPtrnList(pEnt.sSchPtrn, PatternEntList); if PatternEntList.Count = 0 then exit; var nHits, nTotalHits: Integer; var sFound: String := ''; var sResult: String := ''; var sSchName: String := ''; var sFoundSum: String := ''; var sSchTxt: String := ''; var i: Integer; var nOrCnt: Integer := 0; var nAndCnt: Integer := 0; var nHighCnt: Integer := 0; nTotalHits := 0; for i := 0 to PatternEntList.Count - 1 do begin sSchTxt := PatternEntList[i].GetSearchText; nHits := TTgPcre.GetMatchValues(sText, sSchTxt, sFound); if (nHits > 0) and (nHits >= PatternEntList[i].IfCount) then begin if sSchTxt.StartsWith('(?= pEnt.nLimitHit then begin var bFoundOk: Boolean := true; if (nHighCnt = 0) and (PatternEntList.AndCount > 0) then begin // AND 갯수가 다르다면 X if nAndCnt <> PatternEntList.AndCount then bFoundOk := false; // OR가 조건으로 있는데 검출된 OR가 없다면 X if (PatternEntList.AndCount <> PatternEntList.Count) and (nOrCnt = 0) then bFoundOk := false; end; if bFoundOk then begin bMakeDrm := false; if bDoEnc then begin // todo : 앞으로 사용되지 않을 예정. 나중에 지우자 25_0623 15:50:11 kku // 메인에서 암호화 지연등 처리 if (CUSTOMER_TYPE = CUSTOMER_WELFNI) or (CUSTOMER_TYPE = CUSTOMER_WELFND) then begin if (sDecPath <> '') and FileExists(sDecPath) then begin // 파수DRM 적용된 파일이었다면, // 개인보안등급인지 확인해서 아니면 적용하기 25_0408 13:34:53 kku try sTemp := fas.GetFileHeader(sSrcPath); if sTemp <> '' then begin SplitString(sTemp, ';', WorkList, true); if WorkList.Count > 11 then begin // 보안코드 12번째 있음 // 개인보안 등급이 아니면 다시 암호화 25_0408 10:21:32 kku if WorkList[11] <> _sSecurityLevel then begin bMakeDrm := EncFile(sDecPath); if bMakeDrm and FileExists(sDecPath) then begin if DeleteFile(PChar(sSrcPath)) then MoveFile_wait(sDecPath, sSrcPath); end; end; end; end; except // .. end; end else bMakeDrm := EncFile(sSrcPath); end; end; var DocInfo: TAdDocInfo := KvFilter.GetDocInfoFile(sSrcPath); Send := TTgPacket.Create(KVC_RESPONSE_KEYWORD_SEARCH); Send.S['Path'] := sSrcPath; Send.S['Text'] := sText; Send.S['Founds'] := sResult; // 구버전 Send.S['SchName'] := sSchName; // 신버전 Send.S['FoundSum'] := sFoundSum; // 신버전 Send.I['TotalHits'] := nTotalHits; Send.I['Action'] := pEnt.nReturnCmd; Send.I['Event'] := pEnt.nEvent; Send.S['OldName'] := pEnt.sOldName; Send.O['Info'] := TTgJson.ValueToJsonObject(DocInfo); if bMakeDrm then Send.B['MakeDrm'] := bMakeDrm; Client_.SendPacket(Send); end; end; end; end; end; end; finally if FileExists(sDecPath) then DeleteFile(PChar(sDecPath)); end; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. ExtractText()'); end; end; procedure ProcessDecompFile(sPath, sExportDir: String; sOrgCompPath: String; sParentFile: String = ''); var i: Integer; sExt: String; begin try // 압축파일 처리 22_1201 14:54:18 kku DeleteDir(sExportDir, true, true); if ForceDirectories(sExportDir) then begin Inc(nDecompDepth); try var FList: TStringList; Guard(FList, TStringList.Create); try DecompressFile(sPath, sExportDir, OnUnzipProgress, OnPwdEvent); ExtrFilesPathFromDir(sExportDir, FList, true); for i := 0 to FList.Count - 1 do begin sExt := GetFileExt(FList[i]).ToUpper; if SchExtList.IndexOf(sExt) <> -1 then ExtractText(FList[i], sParentFile + ExtractFileName(sPath), sOrgCompPath) else if Pos(sExt, COMPRESS_EXTS) > 0 then ProcessDecompFile(FList[i], Format('%s%d\', [sExportDir, nDecompDepth]), sOrgCompPath, sParentFile + ExtractFileName(sPath) + ' > ' + ExtractFileName(FList[i]) + ' > '); end; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. Unzip'); end; finally DeleteDir(sExportDir, true, true); Dec(nDecompDepth); end; end; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. ProcessDecompFile()'); end; end; begin if gParam.CttSchOpt.sMK <> '' then sPass := DecText(gParam.CttSchOpt.sMK) else sPass := ''; bIncDrm := gParam.CttSchOpt.bIncDrm; bIncZip := gParam.CttSchOpt.bIncZip; sWorkDir := gParam.CttSchOpt.sTaskDir; if sWorkDir = '' then sWorkDir := GetRunExePathDir + 'ExtrTemp\' else sWorkDir := IncludeTrailingBackslash(sWorkDir); fas := nil; if UseFasooDecrypt then begin var bLoadFas: Boolean := true; case CUSTOMER_TYPE of CUSTOMER_WELFNI : SetDSD_CODE(DSD_CODE_WFNI); CUSTOMER_WELFND : SetDSD_CODE(DSD_CODE_WFND); else bLoadFas := false; end; if bLoadFas then begin var sFsDir: String := GetRunExePathDir + 'fsdinit'; if not DirectoryExists(sFsDir) then sFsDir := GetRunExePathDir + 'conf\fsdinit'; if DirectoryExists(sFsDir) then begin CoInitializeEx(nil, COINIT_APARTMENTTHREADED); fas := TTgFasoo.Create(sFsDir); end; end; end; Guard(SchExtList, TStringList.Create); SplitString(UpperCase(DOC_EXTS), '|', SchExtList); Guard(PatternEntList, TPatternEntList.Create); Guard(WorkList, TStringList.Create); Guard(KvFilter, TKvFilter.Create(gParam.CttSchOpt.sKvMdPath)); while not Terminated and not GetWorkStop do begin try if qEnts_.Count > 0 then pEnt := qEnts_.Dequeue else pEnt := nil; if pEnt = nil then begin Sleep(100); continue; end; try if GetFileAttributes(PChar(pEnt.sPath)) = INVALID_FILE_ATTRIBUTES then begin // Sleep(10); continue; end; if ForceDirectories(sWorkDir) then begin sExt := UpperCase(GetFileExt(pEnt.sPath)); if bIncZip and (Pos(sExt, COMPRESS_EXTS) > 0) then begin nDecompDepth := 0; ProcessDecompFile(pEnt.sPath, sWorkDir + '@etr\', pEnt.sPath); end else ExtractText(pEnt.sPath, '', '', pEnt.bDoEnc); end; finally Dispose(pEnt); end; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. Execute()'); end; end; end; { TKvCttSchClient } Constructor TKvCttSchClient.Create; begin Inherited Create('', 0); ThdExtr_ := TThdExtrText.Create(Self); ThdExtr_.StartThread; end; Destructor TKvCttSchClient.Destroy; begin FreeAndNil(ThdExtr_); Inherited; end; procedure TKvCttSchClient.ConnectedEvent; begin try Inherited; SetSendPauseState(false); _Trace('Connected.'); except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. ConnectedEvent()'); end; end; procedure TKvCttSchClient.DisconnectedEvent; begin try Inherited; QSendPacket_.Clear; _Trace('Disconnected'); except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. DisconnectedEvent()'); end; end; procedure TKvCttSchClient.ProcessRcvPacket(aRcv: IRcvPacket); var Send: ISendPacket; procedure process_KVC_REQUEST_EXTRACT_TEXT; var pEnt: PExtrEnt; begin New(pEnt); ZeroMemory(pEnt, SizeOf(TExtrEnt)); pEnt.sPath := aRcv.S['Path']; pEnt.nRcvCmd := KVC_REQUEST_EXTRACT_TEXT; ThdExtr_.PushExtrEnt(pEnt); end; procedure process_KVC_REQUEST_KEYWORD_SEARCH; var pEnt: PExtrEnt; begin New(pEnt); ZeroMemory(pEnt, SizeOf(TExtrEnt)); pEnt.sPath := aRcv.S['Path']; pEnt.sSchPtrn := aRcv.S['Ptrn']; pEnt.nReturnCmd := aRcv.I['Action']; pEnt.nLimitHit := aRcv.I['LimitHit']; pEnt.nEvent := aRcv.I['Event']; pEnt.sOldName := aRcv.S['OldName']; pEnt.bDoEnc := aRcv.B['DoEnc']; // 현재는 웰컴에프엔디에서만 사용 (파수DRM) 25_0408 13:20:48 kku pEnt.nRcvCmd := KVC_REQUEST_KEYWORD_SEARCH; ThdExtr_.PushExtrEnt(pEnt); end; begin try case aRcv.Command of KVC_REQUEST_EXTRACT_TEXT : process_KVC_REQUEST_EXTRACT_TEXT; KVC_REQUEST_KEYWORD_SEARCH : process_KVC_REQUEST_KEYWORD_SEARCH; end; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. ProcessRcvPacket()'); end; end; end.