BSOne.SFC/Tocsg.Module/Bs1Flt/bs1dc_Delphi/bs1PolicyUnit.pas

1029 lines
29 KiB
Plaintext

unit bs1PolicyUnit;
interface
uses
System.SysUtils, System.Classes, System.Generics.Collections, System.SyncObjs,
System.JSON, System.IOUtils, Winapi.Windows, Winapi.Messages, Winapi.ActiveX,
Bs1FltCtrl;
type
// [1] MTP/Bluetooth 설정 구조체
TMtpBtConfig = record
Policy: Integer;
FileSize: Integer;
end;
// [2] 프로세스 훅 설정 클래스
THookProcessItem = class
public
ProcessName: string; // Key (예: FSQUIRT)
DeviceType: TStringList;
Mode: Integer;
ExceptionPath: TStringList;
Extension: TStringList;
ExceptionExt: TStringList; // JSON 키: exceptionExtenstion (오타 주의)
constructor Create(AName: string);
destructor Destroy; override;
function Clone: THookProcessItem;
end;
// [3] DataFlow 전체 설정 클래스
TDataFlowConfig = class
public
Seq: DWORD; // 기본값 "1"
GlobalExceptionPath: TStringList; // 공통 예외 경로
// DeviceControl (MTP, BT)
MtpConfig: TMtpBtConfig;
BtConfig: TMtpBtConfig;
CDROMConfig: TMtpBtConfig;
UsbToUsbConfig: TMtpBtConfig;
// HookProcess
HookProcesses: TObjectList<THookProcessItem>;
constructor Create;
destructor Destroy; override;
procedure Assign(Source: TDataFlowConfig); // 데이터 복사 (UI 취소 기능 지원용)
end;
TMatchCriteria = class
public
PropType: DWORD;
MatchData: string;
Enumerator: string;
constructor Create(APropType: DWORD; AMatchData, AEnum: string);
function Clone: TMatchCriteria;
end;
TPolicyItem = class
public
name: string;
flag_: DWORD; // KCD_xxx 값
state_: TDeviceState; // 허용/차단/로그
isLog_: Boolean;
targetGUID_: string; // pGUID
matchPropType_: DWORD; // 예: SPDRP_SERVICE, SPDRP_CLASSGUID
// 특수 장치 플래그
isBluetooth_: Boolean;
Matches_: TObjectList<TMatchCriteria>;
constructor Create(const AName: string; AFlag: DWORD; AGUIDStr: string; IsBT: Boolean = False);
destructor Destroy;
procedure AddMatch(PropType: DWORD; MatchData, Enumerator: string);
function Clone: TPolicyItem;
end;
TBs1Policy = class
private
FPolicyFilePath: string;
FLock: TCriticalSection;
FPolicies: TObjectList<TPolicyItem>;
//FExceptionList: TStringList;
FDataFlowSeq: string;
FDataFlowConfig: TDataFlowConfig; // DataFlow 설정 객체
FUsbExceptList_: TStringList;
FUsbExceptListLock_: TCriticalSection;
public
constructor Create;
destructor Destroy; override;
// 개별 정책 상태 변경 (UI 연동용)
//procedure UpdatePolicyStateInList(const GuidStr: string; NewState: TPolicyState);
function CreatePolicy(const Name: string; Flag: TDeviceType; AGUID: string = ''; IsBT: Boolean = False): TPolicyItem;
// procedure AddPolicy(Flag: DWORD; PropType: DWORD; const MatchData: string; pGUID: string; EnumStr: string; IsBT: Boolean = False);
procedure AddPolicyObject(APolicy: TPolicyItem);
procedure UpdatePolicyState(Flag: DWORD; NewState: TDeviceState; NewLogState: Boolean);
procedure CopyPoliciesTo(ATargetList: TObjectList<TPolicyItem>);
function GetPolicyJsonArray: TJSONArray;
// 파일 입출력 (JSON)
procedure SavePolicyToFile;
procedure LoadDataControlConfig;
procedure LoadDataFlowConfig;
procedure SaveDataFlowConfig; // DataFlow 설정 전용 저장 함수
property Policies: TObjectList<TPolicyItem> read FPolicies;
property DataFlowConfig: TDataFlowConfig read FDataFlowConfig;
//property ExceptionList: TStringList read FExceptionList;
procedure SaveDeviceControlExceptProcessConfig(ExceptionPath: TStrings);
procedure LoadDeviceControlExceptProcessConfig(ExceptionPath: TStrings);
function ParseStringArray(AJSONValue: TJSONValue): TArray<string>;
function IsUsbPortExcept(const DeviceInstancePath: string): Boolean;
function RemoveUsbPortExcept(const Vendor, Product, Serial: string): Boolean;
function AddUsbPortExcept(const Vendor, Product, Serial: string): Boolean;
end;
var
gBs1Policy: TBs1Policy = nil;
implementation
{ THookProcessItem }
constructor THookProcessItem.Create(AName: string);
begin
ProcessName := AName;
ExceptionPath := TStringList.Create;
Extension := TStringList.Create;
ExceptionExt := TStringList.Create;
DeviceType:= TStringList.Create;
Mode := 0;
end;
destructor THookProcessItem.Destroy;
begin
ExceptionPath.Free;
Extension.Free;
ExceptionExt.Free;
DeviceType.Free;
inherited;
end;
function THookProcessItem.Clone: THookProcessItem;
begin
Result := THookProcessItem.Create(Self.ProcessName);
Result.DeviceType.Assign(Self.DeviceType);
Result.Mode := Self.Mode;
Result.ExceptionPath.Assign(Self.ExceptionPath);
Result.Extension.Assign(Self.Extension);
Result.ExceptionExt.Assign(Self.ExceptionExt);
end;
{ TDataFlowConfig }
constructor TDataFlowConfig.Create;
begin
Seq := 1;
GlobalExceptionPath := TStringList.Create;
HookProcesses := TObjectList<THookProcessItem>.Create;
// 기본값
MtpConfig.Policy := 1; MtpConfig.FileSize := 20;
BtConfig.Policy := 0; BtConfig.FileSize := 20;
end;
destructor TDataFlowConfig.Destroy;
begin
GlobalExceptionPath.Free;
HookProcesses.Free;
inherited;
end;
procedure TDataFlowConfig.Assign(Source: TDataFlowConfig);
begin
Self.Seq := Source.Seq;
Self.GlobalExceptionPath.Assign(Source.GlobalExceptionPath);
Self.MtpConfig := Source.MtpConfig;
Self.BtConfig := Source.BtConfig;
Self.CDROMConfig := Source.CDROMConfig;
Self.UsbToUsbConfig := Source.UsbToUsbConfig;
Self.HookProcesses.Clear;
for var Item in Source.HookProcesses do
Self.HookProcesses.Add(Item.Clone);
end;
{ TMatchCriteria }
constructor TMatchCriteria.Create(APropType: DWORD; AMatchData, AEnum: string);
begin
PropType := APropType;
MatchData := AMatchData;
Enumerator := AEnum;
end;
function TMatchCriteria.Clone: TMatchCriteria;
begin
Result := TMatchCriteria.Create(self.PropType, self.MatchData, self.Enumerator);
end;
{ TPolicyItem }
constructor TPolicyItem.Create(const AName: string; AFlag: DWORD; AGUIDStr: string; IsBT: Boolean);
begin
Name := AName;
flag_ := AFlag;
targetGUID_ := AGUIDStr;
isBluetooth_ := IsBT;
state_ := dsEnable; // 기본값
isLog_:= False;
matches_ := TObjectList<TMatchCriteria>.Create;
end;
destructor TPolicyItem.Destroy;
begin
Matches_.Free;
inherited;
end;
procedure TPolicyItem.AddMatch(PropType: DWORD; MatchData, Enumerator: string);
begin
Matches_.Add(TMatchCriteria.Create(PropType, MatchData, Enumerator));
end;
function TPolicyItem.Clone: TPolicyItem;
var
Match: TMatchCriteria;
begin
// 기본 필드 복사
Result := TPolicyItem.Create(self.name, self.flag_, self.targetGUID_, self.isBluetooth_);
Result.state_ := self.state_;
Result.isLog_ := self.isLog_;
Result.matchPropType_ := self.matchPropType_;
// Match 리스트 내부 아이템들도 깊은 복사 수행
for Match in self.Matches_ do
begin
Result.Matches_.Add(Match.Clone);
end;
end;
{
procedure TDeviceGuardEngine.AddDefaultPolicy(const Name, GuidStr: string; IsBT: Boolean);
begin
FLock.Enter;
try
// 중복 확인 후 없으면 추가
for var Item in FPolicies do
if IsEqualGUID(Item.GUID, StringToGUID(GuidStr)) then Exit;
FPolicies.Add(TPolicyItem.Create(Name, GuidStr, psLog, IsBT));
finally
FLock.Leave;
end;
end;
procedure TDeviceGuardEngine.UpdatePolicyStateInList(const GuidStr: string; NewState: TPolicyState);
var
TargetGUID: TGUID;
begin
TargetGUID := StringToGUID(GuidStr);
FLock.Enter;
try
for var Item in FPolicies do
begin
if IsEqualGUID(Item.GUID, TargetGUID) then
begin
Item.State := NewState;
Break;
end;
end;
finally
FLock.Leave;
end;
end; }
{
procedure TDeviceGuardEngine.AddPolicy(Flag: DWORD; PropType: DWORD; const MatchData: string; pGUID: string; EnumStr: string; IsBT: Boolean);
begin
FLock.Enter;
try
// Flag 중복 체크
for var Item in FPolicies do
if Item.flag_ = Flag then Exit;
FPolicies.Add(TPolicyItem.Create(Flag, PropType, MatchData, pGUID, EnumStr, IsBT));
finally
FLock.Leave;
end;
end; }
////////////////////////////////////////////////////////////////
// 헬퍼: JSON 배열 -> 스트링리스트
procedure JsonArrayToStrings(JArr: TJSONArray; List: TStrings);
begin
List.Clear;
if JArr = nil then Exit;
for var I := 0 to JArr.Count - 1 do
List.Add(JArr.Items[I].Value);
end;
// 헬퍼: 스트링리스트 -> JSON 배열
function StringsToJsonArray(List: TStrings): TJSONArray;
begin
Result := TJSONArray.Create;
for var S in List do
Result.Add(S);
end;
{ TBs1Policy }
constructor TBs1Policy.Create;
begin
gBs1Policy:= self;
FPolicies := TObjectList<TPolicyItem>.Create;
FLock := TCriticalSection.Create;
FDataFlowConfig := TDataFlowConfig.Create;
FPolicyFilePath := ChangeFileExt(ParamStr(0), '.json');
//
//FExceptionList := TStringList.Create;
FDataFlowSeq := '1'; // 기본값
FUsbExceptList_ := TStringList.Create;
FUsbExceptListLock_ := TCriticalSection.Create;
end;
destructor TBs1Policy.Destroy;
begin
FUsbExceptList_.Free;
FUsbExceptListLock_.Free;
FDataFlowConfig.Free;
// FExceptionList.Free;
FPolicies.Free;
FLock.Free;
inherited;
end;
function TBs1Policy.AddUsbPortExcept(const Vendor, Product, Serial: string): Boolean;
var
Key: string;
begin
Key := Format('USB\VID_%s&PID_%s\%s', [Vendor, Product, Serial]);
Key := Trim(Key);
//OutputDebugStringW(PChar(Format('bs1 (key : %s)', [Key])));
FUsbExceptListLock_.Enter;
try
// Sorted=True, Duplicates=dupIgnore 설정 덕분에 Add만 호출해도 중복체크 됨
FUsbExceptList_.Add(Key);
Result := True;
finally
FUsbExceptListLock_.Leave;
end;
end;
function TBs1Policy.RemoveUsbPortExcept(const Vendor, Product, Serial: string): Boolean;
var
Key: string;
Idx: Integer;
begin
Result:= False;
Key := Format('USB\VID_%s&PID_%s\%s', [Vendor, Product, Serial]);
Key := Trim(Key);
//OutputDebugStringW(PChar(Format('bs1 (key : %s)', [Key])));
FUsbExceptListLock_.Enter;
try
Idx := FUsbExceptList_.IndexOf(Key);
if Idx >= 0 then
begin
FUsbExceptList_.Delete(Idx);
Result := True;
end;
finally
FUsbExceptListLock_.Leave;
end;
end;
function TBs1Policy.IsUsbPortExcept(const DeviceInstancePath: string): Boolean;
begin
FUsbExceptListLock_.Enter;
try
// IndexOf는 대소문자 무시(CaseSensitive=False)하여 찾음
Result := FUsbExceptList_.IndexOf(DeviceInstancePath) >= 0;
finally
FUsbExceptListLock_.Leave;
end;
end;
procedure TBs1Policy.CopyPoliciesTo(ATargetList: TObjectList<TPolicyItem>);
begin
if ATargetList = nil then Exit;
FLock.Enter;
try
ATargetList.Clear; // 대상 리스트 초기화 (필요에 따라 주석 처리 가능)
for var Item in FPolicies do
begin
// 각 아이템을 복제(Clone)하여 대상 리스트에 추가
// TObjectList가 OwnsObjects=True라면 나중에 ATargetList가 해제될 때 복제된 객체들도 자동 해제됨
ATargetList.Add(Item.Clone);
end;
finally
FLock.Leave;
end;
end;
function TBs1Policy.ParseStringArray(AJSONValue: TJSONValue): TArray<string>;
var
LArray: TJSONArray;
LString: TJSONString;
I: Integer;
begin
Result := [];
if AJSONValue = nil then Exit;
if AJSONValue is TJSONArray then
begin
LArray := AJSONValue as TJSONArray;
SetLength(Result, LArray.Count);
for I := 0 to LArray.Count - 1 do
Result[I] := LArray.Items[I].Value;
end
else if (AJSONValue is TJSONString) then
begin
LString := AJSONValue as TJSONString;
if not LString.Value.IsEmpty then
Result := LString.Value.Split(['|']);
end;
end;
function TBs1Policy.CreatePolicy(const Name: string; Flag: TDeviceType; AGUID: string; IsBT: Boolean): TPolicyItem;
begin
Result := TPolicyItem.Create(Name, DWORD(Flag), AGUID, IsBT);
end;
procedure TBs1Policy.AddPolicyObject(APolicy: TPolicyItem);
begin
FLock.Enter;
try
// 중복 체크 (Flag 기준)
for var Item in FPolicies do
begin
if Item.flag_ = APolicy.flag_ then
begin
APolicy.Free; // 이미 있으면 해제하고 무시
Exit;
end;
end;
FPolicies.Add(APolicy);
finally
FLock.Leave;
end;
end;
function TBs1Policy.GetPolicyJsonArray: TJSONArray;
var
JObjPolicy, JObjMatch: TJSONObject;
JArrMatch: TJSONArray;
begin
Result := TJSONArray.Create;
try
for var Item in FPolicies do
begin
JObjPolicy := TJSONObject.Create;
JObjPolicy.AddPair('name', Item.Name);
JObjPolicy.AddPair('flag', TJSONNumber.Create(Item.flag_));
JObjPolicy.AddPair('state', TJSONNumber.Create(Ord(Item.state_)));
// boolean을 int로 저장 (기존 로직 유지)
if Item.isLog_ then
JObjPolicy.AddPair('log', TJSONNumber.Create(1))
else
JObjPolicy.AddPair('log', TJSONNumber.Create(0));
if Item.targetGUID_ <> '' then
JObjPolicy.AddPair('guid', Item.targetGUID_);
// Match 배열 생성
if Item.matches_.Count > 0 then
begin
JArrMatch := TJSONArray.Create;
for var Match in Item.matches_ do
begin
JObjMatch := TJSONObject.Create;
JObjMatch.AddPair('propType', TJSONNumber.Create(Match.PropType));
JObjMatch.AddPair('matchData', Match.MatchData);
JObjMatch.AddPair('enumerator', Match.Enumerator);
JArrMatch.AddElement(JObjMatch);
end;
JObjPolicy.AddPair('match', JArrMatch);
end;
Result.AddElement(JObjPolicy);
end;
except
Result.Free;
raise;
end;
end;
procedure TBs1Policy.UpdatePolicyState(Flag: DWORD; NewState: TDeviceState; NewLogState: Boolean);
begin
FLock.Enter;
try
for var Item in FPolicies do
begin
if Item.flag_ = Flag then
begin
Item.state_ := NewState;
Item.isLog_ := NewLogState;
Break;
end;
end;
finally
FLock.Leave;
end;
end;
// ---------------------------------------------------------------------------
// [Save] bs1dc.json 파일을 읽어서 deviceControl 부분만 업데이트 후 저장
// ---------------------------------------------------------------------------
procedure TBs1Policy.SavePolicyToFile;
var
JsonText: string;
JRootVal: TJSONValue;
JObjRoot, JObjDataFlow: TJSONObject;
JArrPolicies: TJSONArray;
IsNewFile: Boolean;
begin
FLock.Enter;
try
IsNewFile := not FileExists(FPolicyFilePath);
JObjRoot := nil;
// 1. 기존 파일이 있으면 읽어서 파싱, 없으면 새로 생성
if not IsNewFile then
begin
JsonText := TFile.ReadAllText(FPolicyFilePath);
JRootVal := TJSONObject.ParseJSONValue(JsonText);
if JRootVal is TJSONObject then
JObjRoot := JRootVal as TJSONObject
else
JRootVal.Free; // 잘못된 포맷이면 폐기
end;
if JObjRoot = nil then
begin
JObjRoot := TJSONObject.Create;
JObjRoot.AddPair('dataFlowSeq', TJSONNumber.Create(1));
end;
try
// // 2. 'dataFlow' 객체 찾기 또는 생성
// if not JObjRoot.TryGetValue<TJSONObject>('dataFlow', JObjDataFlow) then
// begin
// JObjDataFlow := TJSONObject.Create;
// JObjRoot.AddPair('dataFlow', JObjDataFlow);
// end;
// 3. 기존의 'deviceControl' 키가 있다면 제거 (새로 덮어쓰기 위해)
if JObjRoot.GetValue('deviceControl') <> nil then
begin
JObjRoot.RemovePair('deviceControl').Free;
end;
// 4. 현재 정책들을 JSON Array로 변환하여 추가
JArrPolicies := GetPolicyJsonArray;
JObjRoot.AddPair('deviceControl', JArrPolicies);
// 5. 파일 저장
// (Format을 하면 보기 좋지만, 용량이 커질 수 있습니다. 필요 시 TJsonFormatType.Indented 사용)
TFile.WriteAllText(FPolicyFilePath, JObjRoot.ToString);
finally
JObjRoot.Free; // Root를 해제하면 하위 객체들도 모두 해제됨
end;
finally
FLock.Leave;
end;
end;
// ---------------------------------------------------------------------------
// [Load 1] bs1dc.json의 dataFlow -> deviceControl (기존 bs1dc 역할) 로드
// ---------------------------------------------------------------------------
procedure TBs1Policy.LoadDataControlConfig;
var
JsonText: string;
JRootVal: TJSONValue;
JArrPolicies, JArrMatch: TJSONArray;
JObjRoot, JObjPolicy, JObjMatch: TJSONObject;
FlagVal, StateVal, LogVal, PropVal: Integer;
NameStr, GuidStr, MatchDataStr, EnumStr: string;
NewPolicy: TPolicyItem;
begin
if not FileExists(FPolicyFilePath) then Exit;
FLock.Enter;
try
FPolicies.Clear; // 기존 정책 초기화
JsonText := TFile.ReadAllText(FPolicyFilePath);
JRootVal := TJSONObject.ParseJSONValue(JsonText);
try
// 루트 확인
if (JRootVal is TJSONObject) then
begin
JObjRoot := JRootVal as TJSONObject;
// [수정] 최상위 경로에서 'deviceControl' 찾기
// 구조: { "dataFlow": {...}, "deviceControl": [...] }
if JObjRoot.TryGetValue<TJSONArray>('deviceControl', JArrPolicies) then
begin
for var I := 0 to JArrPolicies.Count - 1 do
begin
JObjPolicy := JArrPolicies.Items[I] as TJSONObject;
if JObjPolicy.TryGetValue<string>('name', NameStr) and
JObjPolicy.TryGetValue<Integer>('flag', FlagVal) and
JObjPolicy.TryGetValue<Integer>('state', StateVal) then
begin
GuidStr := '';
JObjPolicy.TryGetValue<string>('guid', GuidStr);
LogVal := 0;
JObjPolicy.TryGetValue<Integer>('log', LogVal);
// 정책 객체 생성
var IsBT := (DWORD(FlagVal) = DWORD(BDC_BLUETOOTH));
NewPolicy := TPolicyItem.Create(NameStr, DWORD(FlagVal), GuidStr, IsBT);
NewPolicy.state_ := TDeviceState(StateVal);
NewPolicy.isLog_ := (LogVal <> 0);
// match 배열 로드
if JObjPolicy.TryGetValue<TJSONArray>('match', JArrMatch) then
begin
for var K := 0 to JArrMatch.Count - 1 do
begin
JObjMatch := JArrMatch.Items[K] as TJSONObject;
if JObjMatch.TryGetValue<Integer>('propType', PropVal) and
JObjMatch.TryGetValue<string>('matchData', MatchDataStr) and
JObjMatch.TryGetValue<string>('enumerator', EnumStr) then
begin
NewPolicy.AddMatch(DWORD(PropVal), MatchDataStr, EnumStr);
end;
end;
end;
FPolicies.Add(NewPolicy);
end;
end;
end;
end;
finally
JRootVal.Free;
end;
finally
FLock.Leave;
end;
end;
procedure TBs1Policy.LoadDataFlowConfig;
var
JsonText: string;
JRoot, JDataFlow, JRule, JDevCtrl, JHookProc, JItem: TJSONObject;
JArr: TJSONArray;
JVal: TJSONValue;
SeqKey: string;
HookItem: THookProcessItem;
SeqInt: Integer;
JArrDevice: TJSONArray;
StrDevice: string;
begin
if not FileExists(FPolicyFilePath) then Exit;
FLock.Enter;
try
JsonText := TFile.ReadAllText(FPolicyFilePath);
// ParseJSONValue는 실패 시 nil 반환하므로 체크 필수
JRoot := TJSONObject.ParseJSONValue(JsonText) as TJSONObject;
try
if JRoot = nil then Exit;
// 0. dataFlowSeq 값 읽기
if JRoot.TryGetValue<Integer>('dataFlowSeq', SeqInt) then
FDataFlowConfig.Seq := SeqInt;
// 1. dataFlow 객체 찾기
if not JRoot.TryGetValue<TJSONObject>('dataFlow', JDataFlow) then Exit;
// 2. Seq 키 (예: "6") 찾기
SeqKey := FDataFlowConfig.Seq.ToString;
if not JDataFlow.TryGetValue<TJSONObject>(SeqKey, JRule) then Exit;
// 3. Exception Path (Global)
if JRule.TryGetValue<TJSONArray>('exceptionPath', JArr) then
JsonArrayToStrings(JArr, FDataFlowConfig.GlobalExceptionPath);
// 4. deviceControl 로드 (기존 코드 유지)
if JRule.TryGetValue<TJSONObject>('deviceControl', JDevCtrl) then
begin
if JDevCtrl.TryGetValue<TJSONObject>('mtp', JItem) then
begin
JItem.TryGetValue<Integer>('policy', FDataFlowConfig.MtpConfig.Policy);
JItem.TryGetValue<Integer>('fileSize', FDataFlowConfig.MtpConfig.FileSize);
end;
if JDevCtrl.TryGetValue<TJSONObject>('bluetooth', JItem) then
begin
JItem.TryGetValue<Integer>('policy', FDataFlowConfig.BtConfig.Policy);
JItem.TryGetValue<Integer>('fileSize', FDataFlowConfig.BtConfig.FileSize);
end;
if JDevCtrl.TryGetValue<TJSONObject>('cdrom', JItem) then
begin
JItem.TryGetValue<Integer>('policy', FDataFlowConfig.CDROMConfig.Policy);
JItem.TryGetValue<Integer>('fileSize', FDataFlowConfig.CDROMConfig.FileSize);
end;
if JDevCtrl.TryGetValue<TJSONObject>('usbTousb', JItem) then
begin
JItem.TryGetValue<Integer>('policy', FDataFlowConfig.UsbToUsbConfig.Policy);
JItem.TryGetValue<Integer>('fileSize', FDataFlowConfig.UsbToUsbConfig.FileSize);
end;
end;
// 5. hookProcess 로드
FDataFlowConfig.HookProcesses.Clear;
if JRule.TryGetValue<TJSONObject>('hookProcess', JHookProc) then
begin
for var Pair in JHookProc do
begin
JItem := Pair.JsonValue as TJSONObject;
HookItem := THookProcessItem.Create(Pair.JsonString.Value); // Key = ProcessName
// [검토] deviceType 로드 로직
HookItem.DeviceType.Clear;
JVal := JItem.GetValue('deviceType');
if JVal <> nil then
begin
if JVal is TJSONArray then
begin
// 배열인 경우 (["mtp", "cdrom"])
JsonArrayToStrings(JVal as TJSONArray, HookItem.DeviceType);
end
else if JVal is TJSONString then
begin
// 문자열인 경우 ("bluetooth")
if (JVal as TJSONString).Value.Trim <> '' then
HookItem.DeviceType.Add((JVal as TJSONString).Value);
end;
end;
JItem.TryGetValue<Integer>('mode', HookItem.Mode);
if JItem.TryGetValue<TJSONArray>('exceptionPath', JArr) then
JsonArrayToStrings(JArr, HookItem.ExceptionPath);
if JItem.TryGetValue<TJSONArray>('extension', JArr) then
JsonArrayToStrings(JArr, HookItem.Extension);
if JItem.TryGetValue<TJSONArray>('exceptionExtenstion', JArr) then
JsonArrayToStrings(JArr, HookItem.ExceptionExt);
FDataFlowConfig.HookProcesses.Add(HookItem);
end;
end;
finally
JRoot.Free;
end;
finally
FLock.Leave;
end;
end;
procedure TBs1Policy.SaveDataFlowConfig;
var
JsonText: string;
JRoot, JDataFlow, JRule, JDevCtrl, JHookProc, JHookItem, JSubItem: TJSONObject;
JArrDeviceType: TJSONArray;
JSeqValue: TJSONValue;
CurrentSeq, NewSeq: Integer;
OldSeqStr, NewSeqStr: string;
begin
FLock.Enter;
try
// 1. 기존 파일 로드 (주석 보존 및 구조 유지 위해)
JRoot := nil;
if FileExists(FPolicyFilePath) then
begin
try
JsonText := TFile.ReadAllText(FPolicyFilePath);
JRoot := TJSONObject.ParseJSONValue(JsonText) as TJSONObject;
except
JRoot := nil;
end;
end;
if JRoot = nil then
JRoot := TJSONObject.Create;
try
// 2. Seq 관리 (증가 로직)
CurrentSeq := 0;
if JRoot.TryGetValue<Integer>('dataFlowSeq', CurrentSeq) then
begin
// 성공적으로 읽음
end;
// [주의] 만약 단순히 현재 설정을 저장하는 것이라면 Seq를 증가시키지 않아도 될 수 있습니다.
// 여기서는 요청하신 로직대로 +1 증가시킵니다.
NewSeq := CurrentSeq + 1;
FDataFlowConfig.Seq := NewSeq;
OldSeqStr := CurrentSeq.ToString;
NewSeqStr := NewSeq.ToString;
// Root의 Seq 업데이트
if JRoot.GetValue('dataFlowSeq') <> nil then
JRoot.RemovePair('dataFlowSeq').Free;
JRoot.AddPair('dataFlowSeq', TJSONNumber.Create(NewSeq));
// 3. dataFlow 객체 확보
JDataFlow := nil;
if JRoot.GetValue('dataFlow') <> nil then
JDataFlow := JRoot.GetValue('dataFlow') as TJSONObject;
if JDataFlow = nil then
begin
JDataFlow := TJSONObject.Create;
JRoot.AddPair('dataFlow', JDataFlow);
end;
// 기존 Seq 데이터 삭제 (롤링 업데이트)
if JDataFlow.GetValue(OldSeqStr) <> nil then
JDataFlow.RemovePair(OldSeqStr).Free;
// 혹시 모를 충돌 방지
if JDataFlow.GetValue(NewSeqStr) <> nil then
JDataFlow.RemovePair(NewSeqStr).Free;
// 4. 새 규칙 생성 (NewSeq)
JRule := TJSONObject.Create;
JDataFlow.AddPair(NewSeqStr, JRule);
// ExceptionPath
JRule.AddPair('exceptionPath', StringsToJsonArray(FDataFlowConfig.GlobalExceptionPath));
// DeviceControl
JDevCtrl := TJSONObject.Create;
// MTP
JSubItem := TJSONObject.Create;
JSubItem.AddPair('policy', TJSONNumber.Create(FDataFlowConfig.MtpConfig.Policy));
JSubItem.AddPair('fileSize', TJSONNumber.Create(FDataFlowConfig.MtpConfig.FileSize));
JDevCtrl.AddPair('mtp', JSubItem);
// Bluetooth
JSubItem := TJSONObject.Create;
JSubItem.AddPair('policy', TJSONNumber.Create(FDataFlowConfig.BtConfig.Policy));
JSubItem.AddPair('fileSize', TJSONNumber.Create(FDataFlowConfig.BtConfig.FileSize));
JDevCtrl.AddPair('bluetooth', JSubItem);
// CDROM
JSubItem := TJSONObject.Create;
JSubItem.AddPair('policy', TJSONNumber.Create(FDataFlowConfig.CDROMConfig.Policy));
JSubItem.AddPair('fileSize', TJSONNumber.Create(FDataFlowConfig.CDROMConfig.FileSize));
JDevCtrl.AddPair('cdrom', JSubItem);
// UsbToUsb
JSubItem := TJSONObject.Create;
JSubItem.AddPair('policy', TJSONNumber.Create(FDataFlowConfig.UsbToUsbConfig.Policy));
JSubItem.AddPair('fileSize', TJSONNumber.Create(FDataFlowConfig.UsbToUsbConfig.FileSize));
JDevCtrl.AddPair('usbTousb', JSubItem);
JRule.AddPair('deviceControl', JDevCtrl);
// 5. HookProcess 저장 (여기가 핵심)
JHookProc := TJSONObject.Create;
for var Item in FDataFlowConfig.HookProcesses do
begin
JHookItem := TJSONObject.Create;
// [핵심 수정] deviceType 저장 로직 개선
// 1개면 String, 그 외엔 Array로 저장하여 JSON 포맷 유지
if Item.DeviceType.Count = 1 then
begin
// 문자열로 저장: "deviceType": "bluetooth"
JHookItem.AddPair('deviceType', Item.DeviceType[0]);
end
else
begin
// 배열로 저장: "deviceType": ["mtp", "cdrom"]
JArrDeviceType := TJSONArray.Create;
for var S in Item.DeviceType do
begin
JArrDeviceType.Add(S);
end;
JHookItem.AddPair('deviceType', JArrDeviceType);
end;
JHookItem.AddPair('mode', TJSONNumber.Create(Item.Mode));
JHookItem.AddPair('exceptionPath', StringsToJsonArray(Item.ExceptionPath));
JHookItem.AddPair('extension', StringsToJsonArray(Item.Extension));
JHookItem.AddPair('exceptionExtenstion', StringsToJsonArray(Item.ExceptionExt));
JHookProc.AddPair(Item.ProcessName, JHookItem);
end;
JRule.AddPair('hookProcess', JHookProc);
// 파일 저장
TFile.WriteAllText(FPolicyFilePath, JRoot.ToString);
finally
JRoot.Free;
end;
finally
FLock.Leave;
end;
end;
procedure TBs1Policy.SaveDeviceControlExceptProcessConfig(ExceptionPath: TStrings);
var
JsonText: string;
JRoot, JDataFlow, JConfigObj, JHookItem: TJSONObject;
begin
FLock.Enter;
try
if FileExists(FPolicyFilePath) then
begin
JsonText := TFile.ReadAllText(FPolicyFilePath);
JRoot := TJSONObject.ParseJSONValue(JsonText) as TJSONObject;
end
else
JRoot := TJSONObject.Create;
if JRoot = nil then JRoot := TJSONObject.Create;
try
if JRoot.GetValue('deviceControlConfig') <> nil then
begin
JRoot.RemovePair('deviceControlConfig').Free;
end;
JConfigObj := TJSONObject.Create;
JConfigObj.AddPair('exceptionPath', StringsToJsonArray(ExceptionPath));
JRoot.AddPair('deviceControlConfig', JConfigObj);
TFile.WriteAllText(FPolicyFilePath, JRoot.ToString);
finally
JRoot.Free;
end;
finally
FLock.Leave;
end;
end;
procedure TBs1Policy.LoadDeviceControlExceptProcessConfig(ExceptionPath: TStrings);
var
JsonText: string;
JRootVal: TJSONValue;
JRootObj, JConfigObj: TJSONObject;
JArrExceptions: TJSONArray;
begin
// 1. 파일 존재 확인
if not FileExists(FPolicyFilePath) then Exit;
FLock.Enter;
try
ExceptionPath.Clear; // 기존 데이터 초기화
// 2. 파일 읽기 및 파싱
JsonText := TFile.ReadAllText(FPolicyFilePath);
JRootVal := TJSONObject.ParseJSONValue(JsonText);
try
// 루트가 JSON 객체인지 확인
if (JRootVal <> nil) and (JRootVal is TJSONObject) then
begin
JRootObj := JRootVal as TJSONObject;
// 3. 'deviceControlConfig' 객체 찾기
if JRootObj.TryGetValue<TJSONObject>('deviceControlConfig', JConfigObj) then
begin
// 4. 그 안에서 'exceptionPath' 배열 찾기
if JConfigObj.TryGetValue<TJSONArray>('exceptionPath', JArrExceptions) then
begin
// 5. 배열 내용을 TStrings(ExceptionPath)에 추가
for var I := 0 to JArrExceptions.Count - 1 do
begin
ExceptionPath.Add(JArrExceptions.Items[I].Value);
end;
end;
end;
end;
finally
JRootVal.Free; // 파싱된 JSON 객체 해제
end;
finally
FLock.Leave;
end;
end;
end.