Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reading Invalid FileNames from a folder

Tags:

file-io

delphi

I have an interesting problem. A client of ours recorded voice conversations from phone calls, but the filename that was given to the recording was invalid. Here is an example of the filename 123:123.wmv

Believe it, Windows Media encoder created the file, and all the information is in the file, however Windows obviously don't recognise the filename, and only display it in the folder as 123 and the file is 0KB

Well from here it is edited: Thanks to Keith Miller that pointed me in the right direction I could write a function that will extract the stream names from the file and use it.

I've included a working copy of how to create two streams of data into a file, read the stream names and read the data from each stream. This is totally awesome, so I hope other people can use this as well. My code ignores the main stream. If you write data into the main stream, it is best if you do not ignore it.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, uGeneralStuff;

type
  _FILE_STREAM_INFORMATION = record
    NextEntryOffset: cardinal;
    StreamNameLength: cardinal;
    StreamSize: int64;
    StreamAllocationSize: int64;
    StreamName: array[0..MAX_PATH] of WideChar;
  end;

  PFILE_STREAM_INFORMATION = ^_FILE_STREAM_INFORMATION;

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    InfoBlock: _FILE_STREAM_INFORMATION;
    StatusBlock : record
      Status: Cardinal;
      Information: PDWORD;
    end;

    procedure CreateFile(FileName, Info: String);
    function ReadFile(FileName: String): String;
    function ReadStreams(filename: String): TStringList;
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

function NtQueryInformationFile(FileHandle : Cardinal;
                                  IoStatusBlock : Pointer;
                                  FileInformation : Pointer;
                                  FileInformationLength : Cardinal;
                                  FileInformationClass : Cardinal
                                  ): Cardinal; stdcall; external 'ntdll.dll';
implementation

uses Math, StrUtils;
{$R *.dfm}

function TForm1.ReadStreams(filename: String): TStringList;
var
  iFH1: Integer;
  aFileName: array[0..MAX_PATH] of WideChar;
  aStreamName: String;
begin
Result := TStringList.Create;
iFH1 := FileOpen(filename, GENERIC_READ);
NtQueryInformationFile(iFH1, @StatusBlock, @InfoBlock, SizeOf(InfoBlock), 22);  // 22 Means FileStreamInformation
FileClose(iFH1);
while (1=1) do
  begin
  if InfoBlock.StreamNameLength = 0 then
    break;
  CopyMemory(@aFileName, @InfoBlock.StreamName, InfoBlock.StreamNameLength);
  aStreamName := Copy(aFileName, 1, PosEx(':', aFileName, 2) - 1);
  if aStreamName <> ':' then   //Ignore main stream, because I know I didn't write data in there
    Result.Add(aStreamName);
  if (InfoBlock.NextEntryOffset = 0) then
    break;
  InfoBlock := PFILE_STREAM_INFORMATION(PByte(@InfoBlock) + InfoBlock.NextEntryOffset)^;
  end;
end;


procedure TForm1.Button2Click(Sender: TObject);
var
  aStreams: TStringList;
  I: Integer;
begin
aStreams := ReadStreams('C:\Temp\123');
for I := 0 to aStreams.Count - 1 do
  begin
  ShowMessage(ReadFile('C:\Temp\123' + aStreams[I]));
  end;
end;

procedure TForm1.CreateFile(FileName, Info: String);
var
  iFH1: Integer;
  Buffer: PAnsiString;
begin
  iFH1 := FileCreate(FileName);
  Buffer := PAnsiString(AnsiString(Info) + #0);
  FileWrite(iFH1, Buffer^, Length(Info));
  FileClose(iFH1);
end;

function TForm1.ReadFile(FileName: String): String;
var
  iFH1: Integer;
  Buffer: PAnsiChar;
  iFL: Integer;
  iBR, iCurPos, iReadSize: Integer;
begin
  iFH1 := FileOpen(FileName, GENERIC_READ);
  iFL := FileSeek(iFH1, 0, 2);
  FileSeek(iFH1, 0, 0);
  iReadSize := Min(iFL, 1024);
  Buffer := AllocMem(iReadSize + 1);
  iCurPos := 0;
  Result := '';
  while iCurPos < iFL do
    begin
    iBR := FileRead(iFH1, Buffer^, iReadSize);
    if iBR = -1 then
      break;
    Result := Result + Buffer;
    Inc(iCurPos, iBR);
    end;
  FileClose(iFH1);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  CreateFile('C:\Temp\123:123.txt', 'This is TestFile 1');
  CreateFile('C:\Temp\123:345.txt', 'This is TestFile 2');
  ShowMessage(ReadFile('C:\Temp\123:123.txt'));
  ShowMessage(ReadFile('C:\Temp\123:345.txt'));
end;

end.
like image 775
Jaques Avatar asked May 10 '12 14:05

Jaques


2 Answers

Using a : in a file name creates an alternate data stream in the file. See the article at http://support.microsoft.com/kb/105763

In your example the file is called 123 and the stream is called 123.wmv. You could write a program to extract the stream from the file and rewrite it with a conventional file name.

The article at http://www.flexhex.com/docs/articles/alternate-streams.phtml should help.

like image 104
Keith Miller Avatar answered Oct 12 '22 12:10

Keith Miller


FindFirst uses a TSearchRec record to return the file properties. There you have a FindData element (TWin32FindData), which contains some extra properties like the alternate name of the file. Perhaps you can use that.

Edit: I found a page that contains a unit with a function named ADSFindFirst (which, incidently, contains NtQueryInformationFile neatly wrapped internally.) I don't have Delphi here but looks promising: http://www.tek-tips.com/faqs.cfm?fid=7167

like image 41
Leonardo Herrera Avatar answered Oct 12 '22 11:10

Leonardo Herrera