|
Registriert seit: 3. Sep 2023 386 Beiträge |
#1
Hi,
Having my fun with ADS i wrote this, and hope you find it useful
Delphi-Quellcode:
example to use
unit lcNTFSFileStreams;
interface uses Windows, Classes; const // known StreamID values BACKUP_INVALID = 0; BACKUP_DATA = 1; // the file data/content itself BACKUP_EA_DATA = 2; BACKUP_SECURITY_DATA = 3; // security descriptor of the file BACKUP_ALTERNATE_DATA = 4; // attached/alternative data stream, must be have a name BACKUP_LINK = 5; BACKUP_PROPERTY_DATA = 6; BACKUP_OBJECT_ID = 7; BACKUP_REPARSE_DATA = 8; BACKUP_SPARSE_BLOCK = 9; BACKUP_TXFS_DATA = 10; BACKUP_GHOSTED_FILE_EXTENTS = 11; // known StreamAttributes bits STREAM_NORMAL_ATTRIBUTE = 0; STREAM_MODIFIED_WHEN_READ = 1; STREAM_CONTAINS_SECURITY = 2; STREAM_CONTAINS_PROPERTIES = 4; STREAM_SPARSE_ATTRIBUTE = 8; STREAM_CONTAINS_GHOSTED_FILE_EXTENTS = 16; type TFileStreamRec = record StreamID: Cardinal; StreamAttribute: Cardinal; Size: Int64; StreamName: string; end; TFileStreamArr = array of TFileStreamRec; // will enumerate and append to the list any streams with names ( streams with ID = ALTERNATE_DATA ) // return True on success enumeration in full // return False on failure, but will not remove what already being added before failing function FileStreamsEnum(const FileName: string; var StreamNameList: TStringList): Boolean; // same as EnumFileStreams for the result, but with all stream details, and can return all streams not just named function FileStreamsEnumEx(const FileName: string; var StreamsArr: TFileStreamArr; OnlyNamed: Boolean = True): Boolean; // delete named stream(s) (Id = BACKUP_ALTERNATE_DATA) // when StreamName is empty '' , all named streams will be deleted function FileStreamsDelete(const FileName: string; const StreamName: string = ''): Boolean; // not implemented yet, better to be with streamId with the name to suport all, another version is needed to get TBytes in memory //function FileStreamsExtract(const SourceFile,DestFile,StreamName: string):Integer; implementation type PWIN32_STREAM_ID = ^TWIN32_STREAM_ID; TWIN32_STREAM_ID = packed record // Delphi XE8 doesn't have this record packed, i prefer packed dwStreamId: Cardinal; dwStreamAttributes: Cardinal; Size: TLargeInteger; dwStreamNameSize: Cardinal; cStreamName: array[0..1] of WChar; // we compensate with extra 1 char (2 bytes) for 4 bytes, just for alignment end; function FileStreamsEnumEx(const FileName: string; var StreamsArr: TFileStreamArr; OnlyNamed: Boolean = True): Boolean; var Handle: THandle; StreamRec: TWIN32_STREAM_ID; StreamContext: Pointer; BytesRead: Cardinal; dwLowByteSeeked, dwHighByteSeeked: Cardinal; SeekResult: Boolean; StreamName: string; procedure AddStreamToList; var CurrIndex: Integer; begin CurrIndex := Length(StreamsArr); SetLength(StreamsArr, CurrIndex + 1); StreamsArr[CurrIndex].StreamID := StreamRec.dwStreamId; StreamsArr[CurrIndex].StreamAttribute := StreamRec.dwStreamAttributes; StreamsArr[CurrIndex].Size := StreamRec.Size; end; begin Result := False; if FileName = '' then Exit; Handle := CreateFile(Pchar(FileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); if Handle = INVALID_HANDLE_VALUE then Exit; StreamContext := nil; // must be intialized to nil while BackupRead(Handle, @StreamRec, SizeOf(StreamRec) - SizeOf(StreamRec.cStreamName), BytesRead, False, True, StreamContext) do begin if (BytesRead = 0) or (BytesRead < SizeOf(StreamRec) - SizeOf(StreamRec.cStreamName)) then // reached the end Break; // invalid or unknown StreamId, safer to not handle if (StreamRec.dwStreamId = BACKUP_INVALID) or (StreamRec.dwStreamId > BACKUP_GHOSTED_FILE_EXTENTS) then Break; // per specification defined in [MS-BKUP]-210625 // The value of this field (dwStreamId) MUST be 0 for all dwStreamId values other than // ALTERNATE_DATA. For StreamID ALTERNATE_DATA, the value of this field MUST be in the range // 0–65536, and it MUST be an integral multiple of two. if (StreamRec.dwStreamNameSize > 0) and (StreamRec.dwStreamId <> BACKUP_ALTERNATE_DATA) then // corrupted header or we lost position Break; if not OnlyNamed then AddStreamToList; if (StreamRec.dwStreamNameSize > 0) and (StreamRec.dwStreamNameSize < $10000) and (StreamRec.dwStreamNameSize and 1 = 0) then begin SetLength(StreamName, StreamRec.dwStreamNameSize div SizeOf(Char)); if (not BackupRead(Handle, @StreamName[1], StreamRec.dwStreamNameSize, BytesRead, // False, True, StreamContext)) or (BytesRead <> StreamRec.dwStreamNameSize) then Break; if OnlyNamed then AddStreamToList; StreamsArr[Length(StreamsArr) - 1].StreamName := StreamName; end; if StreamRec.Size > 0 then begin // BackUpSeek will not seek across stream headers, so we can use one of the following two method // 1) we do BackupSeek with maximum range without result check, letting BackupRead fail later //BackupSeek(Handle, Cardinal(-1), Cardinal(-1), dwLowByteSeeked, dwHighByteSeeked, StreamContext); // 2) or we do it with exact size and checking for seek result then break now SeekResult := BackupSeek(Handle, Cardinal(StreamRec.Size), Cardinal(StreamRec.Size shr 32), // dwLowByteSeeked, dwHighByteSeeked, StreamContext); if (not SeekResult) or (dwLowByteSeeked + dwHighByteSeeked shl 32 = 0) then Break; // the difference is : which one we prefer to fail as end of streams for this file, BackupRead or BackupSeek end; end; Result := True; if StreamContext <> nil then // cleanup and release stream context BackupRead(Handle, nil, 0, BytesRead, True, True, StreamContext); // bAbort = True CloseHandle(Handle); end; function FileStreamsEnum(const FileName: string; var StreamNameList: TStringList): Boolean; var Streams: TFileStreamArr; i: Integer; begin Result := FileStreamsEnumEx(FileName, Streams, True); if Length(Streams) > 0 then for i := 0 to Length(Streams) - 1 do StreamNameList.Add(Streams[i].StreamName); end; function FileStreamsDelete(const FileName: string; const StreamName: string = ''): Boolean; var Streams: TFileStreamArr; i: Integer; begin Result := FileStreamsEnumEx(FileName, Streams, True); i := 0; while i < Length(Streams) do begin if (StreamName = '') or (StreamName = Streams[i].StreamName) then Result := DeleteFile(PChar(FileName + string(Streams[i].StreamName)));// just concatenate, DeleteFile support Names Streams if StreamName = Streams[i].StreamName then Break; Inc(i); end; end; end.
Delphi-Quellcode:
Memo result
uses
lcNTFSFileStreams; procedure TForm10.Button1Click(Sender: TObject); const FILE_NAME = 'D:\Projects Delphi\NTFSStreams\1.zip'; var StreamArr: TFileStreamArr; procedure DumpList; var i: Integer; begin if Length(StreamArr) > 0 then begin for i := 0 to Length(StreamArr) - 1 do Memo1.Lines.Append(#9'ID: ' + IntToStr(StreamArr[i].StreamID) + ' Size: ' + IntToStr(StreamArr[i].Size) + ' '#9 + StreamArr[i].StreamName); end else Memo1.Lines.Add(#9'file has no alternative data streams'); end; begin Memo1.Lines.Add('list all'); FileStreamsEnumEx(FILE_NAME, StreamArr, False); DumpList; Memo1.Lines.Add('deleting one'); if FileStreamsDelete(FILE_NAME, ':URL:$DATA') then Memo1.Lines.Add('Stream deleted') else Memo1.Lines.Add('Stream wasn''t deleted'); SetLength(StreamArr, 0); // clear FileStreamsEnumEx(FILE_NAME, StreamArr); DumpList; Memo1.Lines.Add('deleting all'); FileStreamsEnumEx(FILE_NAME, StreamArr); FileStreamsDelete(FILE_NAME, ''); Memo1.Lines.Add('list named'); SetLength(StreamArr, 0); // clear FileStreamsEnumEx(FILE_NAME, StreamArr); DumpList; Memo1.Lines.Add('list all'); FileStreamsEnumEx(FILE_NAME, StreamArr, False); DumpList; end;
Code:
list all
ID: 3 Size: 92 ID: 1 Size: 1681665 ID: 4 Size: 935 :Delphi_File:$DATA ID: 4 Size: 19 :URL:$DATA ID: 4 Size: 167 :Zone.Identifier:$DATA deleting one Stream deleted ID: 4 Size: 935 :Delphi_File:$DATA ID: 4 Size: 167 :Zone.Identifier:$DATA deleting all list named file has no alternative data streams list all ID: 3 Size: 92 ID: 1 Size: 1681665
Kas
Geändert von Kas Ob. (21. Aug 2024 um 12:25 Uhr) |
![]() |
Ansicht |
![]() |
![]() |
![]() |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
![]() |
![]() |