![]() |
Small library for Alternative Data Streams (ADS)
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 |
AW: Small library for Alternative Data Streams (ADS)
Thanks. Interesting.
Could you please edit your post and replace the code tags with Delphi tags, so we get syntax highlighting?
Code:
vs.
procedure bla;
begin end;
Delphi-Quellcode:
procedure bla;
begin end; |
AW: Small library for Alternative Data Streams (ADS)
Zitat:
|
AW: Small library for Alternative Data Streams (ADS)
Passing a TStringList as Var-Parameter does not make sense in this context. The underlying instance has to be created and destroyed from outside, so you can even pass it as Const-Parameter.
|
AW: Small library for Alternative Data Streams (ADS)
Die BackupAPI, und dafür die nötigen Rechte, ist garnicht nötig. (Administrator oder ein Nutzer mit Backup&Restore-Privilegien aka SE_BACKUP_NAME)
Seit Vista gibt es auch eine neue API dafür. ![]() ![]() ![]() ![]() ![]() Mußt du dir aber selbt im Delphi implementieren, da die API-Imports im Delphi viel zu alt sind. (Winapi.Windows.pas) Aber mit etwas Glück, kannst du diesen Teil vielleicht aus dem WinMD nutzen, auch wenn zu viel Anderes einfach nur Schrott ist. (WinMD selbst ist OK, aber nicht das was Embarcadero daraus gemacht hat) ![]() ![]() PS:
Delphi-Quellcode:
=
Length(StreamArr) - 1
Delphi-Quellcode:
High(StreamArr)
|
AW: Small library for Alternative Data Streams (ADS)
Zitat:
Also: we only can dream of compiler and more advanced Delphi/Pascal language feature to detect and prevent internal changes coming form class internal implementation, that makes const and var more powerful, producing safer memory and logical handling. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 02:58 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz