* Make sure UTF8 is supported for all filenames when unzipping (bug ID 25982)

git-svn-id: trunk@33360 -
This commit is contained in:
michael 2016-03-28 09:39:53 +00:00
parent 7cda2d237f
commit b000a31abd

View File

@ -403,7 +403,7 @@ Type
FEntries : TZipFileEntries; FEntries : TZipFileEntries;
FZipping : Boolean; FZipping : Boolean;
FBufSize : LongWord; FBufSize : LongWord;
FFileName : String; { Name of resulting Zip file } FFileName : RawByteString; { Name of resulting Zip file }
FFileComment : String; FFileComment : String;
FFiles : TStrings; FFiles : TStrings;
FInMemSize : Int64; FInMemSize : Int64;
@ -431,7 +431,7 @@ Type
Function OpenInput(Item : TZipFileEntry) : Boolean; Function OpenInput(Item : TZipFileEntry) : Boolean;
Procedure GetFileInfo; Procedure GetFileInfo;
Procedure SetBufSize(Value : LongWord); Procedure SetBufSize(Value : LongWord);
Procedure SetFileName(Value : String); Procedure SetFileName(Value : RawByteString);
Function CreateCompressor(Item : TZipFileEntry; AinFile,AZipStream : TStream) : TCompressor; virtual; Function CreateCompressor(Item : TZipFileEntry; AinFile,AZipStream : TStream) : TCompressor; virtual;
Property NeedsZip64 : boolean Read FZipFileNeedsZip64 Write FZipFileNeedsZip64; Property NeedsZip64 : boolean Read FZipFileNeedsZip64 Write FZipFileNeedsZip64;
Public Public
@ -439,14 +439,14 @@ Type
Destructor Destroy;override; Destructor Destroy;override;
Procedure ZipAllFiles; virtual; Procedure ZipAllFiles; virtual;
// Saves zip to file and changes FileName // Saves zip to file and changes FileName
Procedure SaveToFile(AFileName: string); Procedure SaveToFile(AFileName: RawByteString);
// Saves zip to stream // Saves zip to stream
Procedure SaveToStream(AStream: TStream); Procedure SaveToStream(AStream: TStream);
// Zips specified files into a zip with name AFileName // Zips specified files into a zip with name AFileName
Procedure ZipFiles(AFileName : String; FileList : TStrings); Procedure ZipFiles(AFileName : RawByteString; FileList : TStrings);
Procedure ZipFiles(FileList : TStrings); Procedure ZipFiles(FileList : TStrings);
// Zips specified entries into a zip with name AFileName // Zips specified entries into a zip with name AFileName
Procedure ZipFiles(AFileName : String; Entries : TZipFileEntries); Procedure ZipFiles(AFileName : RawByteString; Entries : TZipFileEntries);
Procedure ZipFiles(Entries : TZipFileEntries); Procedure ZipFiles(Entries : TZipFileEntries);
Procedure Clear; Procedure Clear;
Public Public
@ -455,7 +455,7 @@ Type
Property OnProgress : TProgressEvent Read FOnProgress Write FOnProgress; Property OnProgress : TProgressEvent Read FOnProgress Write FOnProgress;
Property OnStartFile : TOnStartFileEvent Read FOnStartFile Write FOnStartFile; Property OnStartFile : TOnStartFileEvent Read FOnStartFile Write FOnStartFile;
Property OnEndFile : TOnEndOfFileEvent Read FOnEndOfFile Write FOnEndOfFile; Property OnEndFile : TOnEndOfFileEvent Read FOnEndOfFile Write FOnEndOfFile;
Property FileName : String Read FFileName Write SetFileName; Property FileName : RawByteString Read FFileName Write SetFileName;
Property FileComment: String Read FFileComment Write FFileComment; Property FileComment: String Read FFileComment Write FFileComment;
// Deprecated. Use Entries.AddFileEntry(FileName) or Entries.AddFileEntries(List) instead. // Deprecated. Use Entries.AddFileEntry(FileName) or Entries.AddFileEntries(List) instead.
Property Files : TStrings Read FFiles; deprecated; Property Files : TStrings Read FFiles; deprecated;
@ -501,8 +501,8 @@ Type
FOnOpenInputStream: TCustomInputStreamEvent; FOnOpenInputStream: TCustomInputStreamEvent;
FUnZipping : Boolean; FUnZipping : Boolean;
FBufSize : LongWord; FBufSize : LongWord;
FFileName : String; { Name of resulting Zip file } FFileName : RawByteString; { Name of resulting Zip file }
FOutputPath : String; FOutputPath : RawByteString;
FFileComment: String; FFileComment: String;
FEntries : TFullZipFileEntries; FEntries : TFullZipFileEntries;
FFiles : TStrings; FFiles : TStrings;
@ -529,18 +529,18 @@ Type
Procedure ReadZipHeader(Item : TFullZipFileEntry; out AMethod : Word); Procedure ReadZipHeader(Item : TFullZipFileEntry; out AMethod : Word);
Procedure DoEndOfFile; Procedure DoEndOfFile;
Procedure UnZipOneFile(Item : TFullZipFileEntry); virtual; Procedure UnZipOneFile(Item : TFullZipFileEntry); virtual;
Function OpenOutput(OutFileName : RawByteString; var OutStream: TStream; Item : TFullZipFileEntry) : Boolean; Function OpenOutput(OutFileName : RawByteString; Out OutStream: TStream; Item : TFullZipFileEntry) : Boolean;
Procedure SetBufSize(Value : LongWord); Procedure SetBufSize(Value : LongWord);
Procedure SetFileName(Value : String); Procedure SetFileName(Value : RawByteString);
Procedure SetOutputPath(Value:String); Procedure SetOutputPath(Value: RawByteString);
Function CreateDeCompressor(Item : TZipFileEntry; AMethod : Word;AZipFile,AOutFile : TStream) : TDeCompressor; virtual; Function CreateDeCompressor(Item : TZipFileEntry; AMethod : Word;AZipFile,AOutFile : TStream) : TDeCompressor; virtual;
Public Public
Constructor Create; Constructor Create;
Destructor Destroy;override; Destructor Destroy;override;
Procedure UnZipAllFiles; virtual; Procedure UnZipAllFiles; virtual;
Procedure UnZipFiles(AFileName : String; FileList : TStrings); Procedure UnZipFiles(AFileName : RawByteString; FileList : TStrings);
Procedure UnZipFiles(FileList : TStrings); Procedure UnZipFiles(FileList : TStrings);
Procedure UnZipAllFiles(AFileName : String); Procedure UnZipAllFiles(AFileName : RawByteString);
Procedure Clear; Procedure Clear;
Procedure Examine; Procedure Examine;
Public Public
@ -553,8 +553,8 @@ Type
Property OnProgress : TProgressEvent Read FOnProgress Write FOnProgress; Property OnProgress : TProgressEvent Read FOnProgress Write FOnProgress;
Property OnStartFile : TOnStartFileEvent Read FOnStartFile Write FOnStartFile; Property OnStartFile : TOnStartFileEvent Read FOnStartFile Write FOnStartFile;
Property OnEndFile : TOnEndOfFileEvent Read FOnEndOfFile Write FOnEndOfFile; Property OnEndFile : TOnEndOfFileEvent Read FOnEndOfFile Write FOnEndOfFile;
Property FileName : String Read FFileName Write SetFileName; Property FileName : RawByteString Read FFileName Write SetFileName;
Property OutputPath : String Read FOutputPath Write SetOutputPath; Property OutputPath : RawByteString Read FOutputPath Write SetOutputPath;
Property FileComment: String Read FFileComment; Property FileComment: String Read FFileComment;
Property Files : TStrings Read FFiles; Property Files : TStrings Read FFiles;
Property Entries : TFullZipFileEntries Read FEntries; Property Entries : TFullZipFileEntries Read FEntries;
@ -565,6 +565,8 @@ Type
Implementation Implementation
uses rtlconsts;
ResourceString ResourceString
SErrBufsizeChange = 'Changing buffer size is not allowed while (un)zipping.'; SErrBufsizeChange = 'Changing buffer size is not allowed while (un)zipping.';
SErrFileChange = 'Changing output file name is not allowed while (un)zipping.'; SErrFileChange = 'Changing output file name is not allowed while (un)zipping.';
@ -576,7 +578,6 @@ ResourceString
SErrMissingFileName = 'Missing filename in entry %d.'; SErrMissingFileName = 'Missing filename in entry %d.';
SErrMissingArchiveName = 'Missing archive filename in streamed entry %d.'; SErrMissingArchiveName = 'Missing archive filename in streamed entry %d.';
SErrFileDoesNotExist = 'File "%s" does not exist.'; SErrFileDoesNotExist = 'File "%s" does not exist.';
SErrFileTooLarge = 'File size %d is larger than maximum supported size %d.';
SErrPosTooLarge = 'Position/offset %d is larger than maximum supported %d.'; SErrPosTooLarge = 'Position/offset %d is larger than maximum supported %d.';
SErrNoFileName = 'No archive filename for examine operation.'; SErrNoFileName = 'No archive filename for examine operation.';
SErrNoStream = 'No stream is opened.'; SErrNoStream = 'No stream is opened.';
@ -586,6 +587,50 @@ ResourceString
{ --------------------------------------------------------------------- { ---------------------------------------------------------------------
Auxiliary Auxiliary
---------------------------------------------------------------------} ---------------------------------------------------------------------}
Type
// A local version of TFileStream which uses rawbytestring. It
TFileStream = class(THandleStream)
Private
FFileName : RawBytestring;
public
constructor Create(const AFileName: RawBytestring; Mode: Word);
constructor Create(const AFileName: RawBytestring; Mode: Word; Rights: Cardinal);
destructor Destroy; override;
property FileName : RawBytestring Read FFilename;
end;
constructor TFileStream.Create(const AFileName: rawbytestring; Mode: Word);
begin
Create(AFileName,Mode,438);
end;
constructor TFileStream.Create(const AFileName: rawbytestring; Mode: Word; Rights: Cardinal);
Var
H : Thandle;
begin
FFileName:=AFileName;
If (Mode and fmCreate) > 0 then
H:=FileCreate(AFileName,Mode,Rights)
else
H:=FileOpen(AFileName,Mode);
If (THandle(H)=feInvalidHandle) then
If Mode=fmcreate then
raise EFCreateError.createfmt(SFCreateError,[AFileName])
else
raise EFOpenError.Createfmt(SFOpenError,[AFilename]);
Inherited Create(H);
end;
destructor TFileStream.Destroy;
begin
FileClose(Handle);
end;
{$IFDEF FPC_BIG_ENDIAN} {$IFDEF FPC_BIG_ENDIAN}
function SwapLFH(const Values: Local_File_Header_Type): Local_File_Header_Type; function SwapLFH(const Values: Local_File_Header_Type): Local_File_Header_Type;
@ -1815,7 +1860,7 @@ begin
SaveToFile(FileName); SaveToFile(FileName);
end; end;
procedure TZipper.SaveToFile(AFileName: string); procedure TZipper.SaveToFile(AFileName: RawByteString);
var var
lStream: TFileStream; lStream: TFileStream;
begin begin
@ -1860,7 +1905,7 @@ begin
FBufSize:=Value; FBufSize:=Value;
end; end;
Procedure TZipper.SetFileName(Value : String); Procedure TZipper.SetFileName(Value : RawByteString);
begin begin
If FZipping then If FZipping then
@ -1868,7 +1913,7 @@ begin
FFileName:=Value; FFileName:=Value;
end; end;
Procedure TZipper.ZipFiles(AFileName : String; FileList : TStrings); Procedure TZipper.ZipFiles(AFileName : RawByteString; FileList : TStrings);
begin begin
FFileName:=AFileName; FFileName:=AFileName;
@ -1881,7 +1926,7 @@ begin
ZipAllFiles; ZipAllFiles;
end; end;
procedure TZipper.ZipFiles(AFileName: String; Entries: TZipFileEntries); procedure TZipper.ZipFiles(AFileName: RawByteString; Entries: TZipFileEntries);
begin begin
FFileName:=AFileName; FFileName:=AFileName;
ZipFiles(Entries); ZipFiles(Entries);
@ -1969,7 +2014,6 @@ begin
Inherited; Inherited;
end; end;
{ --------------------------------------------------------------------- { ---------------------------------------------------------------------
TUnZipper TUnZipper
---------------------------------------------------------------------} ---------------------------------------------------------------------}
@ -1985,10 +2029,11 @@ End;
function TUnZipper.OpenOutput(OutFileName: RawByteString; function TUnZipper.OpenOutput(OutFileName: RawByteString;
var OutStream: TStream; Item: TFullZipFileEntry): Boolean; out OutStream: TStream; Item: TFullZipFileEntry): Boolean;
Var Var
Path: String; Path: RawByteString;
OldDirectorySeparators: set of char; OldDirectorySeparators: set of char;
Begin Begin
{ the default RTL behavior is broken on Unix platforms { the default RTL behavior is broken on Unix platforms
for Windows compatibility: it allows both '/' and '\' for Windows compatibility: it allows both '/' and '\'
@ -2023,6 +2068,7 @@ Begin
ForceDirectories(Path); ForceDirectories(Path);
AllowDirectorySeparators:=OldDirectorySeparators; AllowDirectorySeparators:=OldDirectorySeparators;
OutStream:=TFileStream.Create(OutFileName,fmCreate); OutStream:=TFileStream.Create(OutFileName,fmCreate);
end; end;
AllowDirectorySeparators:=OldDirectorySeparators; AllowDirectorySeparators:=OldDirectorySeparators;
@ -2398,7 +2444,7 @@ Begin
if CRC32Str(S)=Infozip_unicode_path_crc32 then if CRC32Str(S)=Infozip_unicode_path_crc32 then
begin begin
SetLength(U,ExtraFieldHeader.Data_Size-5); SetLength(U,ExtraFieldHeader.Data_Size-5);
FZipStream.ReadBuffer(U[1],Length(U)); FZipStream.ReadBuffer(U[1],Length(U));
NewNode.UTF8ArchiveFileName:=U; NewNode.UTF8ArchiveFileName:=U;
end end
else else
@ -2434,42 +2480,99 @@ end;
procedure TUnZipper.UnZipOneFile(Item: TFullZipFileEntry); procedure TUnZipper.UnZipOneFile(Item: TFullZipFileEntry);
Var Var
Count: int64;
Attrs: Longint;
ZMethod : Word; ZMethod : Word;
{$ifdef unix}
LinkTargetStream: TStringStream; LinkTargetStream: TStringStream;
{$endif}
OutputFileName: RawByteString; OutputFileName: RawByteString;
FOutStream: TStream; FOutStream: TStream;
IsLink: Boolean; IsLink: Boolean;
IsCustomStream: Boolean; IsCustomStream: Boolean;
U : UnicodeString;
procedure DoUnzip(const Dest: TStream); Procedure SetAttributes;
Var
Attrs : Longint;
begin begin
if ZMethod=0 then // set attributes
begin FileSetDate(OutputFileName, DateTimeToFileDate(Item.DateTime));
if (LocalHdr.Compressed_Size<>0) then if (Item.Attributes <> 0) then
begin
Attrs := 0;
{$IFDEF UNIX}
if (Item.OS in [OS_UNIX,OS_OSX]) then Attrs := Item.Attributes;
if (Item.OS in [OS_FAT,OS_NTFS,OS_OS2,OS_VFAT]) then
Attrs := ZipFatAttrsToUnixAttrs(Item.Attributes);
{$ELSE}
if (Item.OS in [OS_FAT,OS_NTFS,OS_OS2,OS_VFAT]) then Attrs := Item.Attributes;
if (Item.OS in [OS_UNIX,OS_OSX]) then
Attrs := ZipUnixAttrsToFatAttrs(ExtractFileName(Item.ArchiveFileName), Item.Attributes);
{$ENDIF}
if Attrs <> 0 then
begin begin
if LocalZip64Fld.Compressed_Size>0 then {$IFDEF UNIX}
Count:=Dest.CopyFrom(FZipStream,LocalZip64Fld.Compressed_Size) FpChmod(OutputFileName, Attrs);
else {$ELSE}
Count:=Dest.CopyFrom(FZipStream,LocalHdr.Compressed_Size); FileSetAttr(OutputFileName, Attrs);
{$warning TODO: Implement CRC Check} {$ENDIF}
end end;
else
Count:=0;
end
else
With CreateDecompressor(Item, ZMethod, FZipStream, Dest) do
Try
OnProgress:=Self.OnProgress;
OnPercent:=Self.OnPercent;
DeCompress;
if Item.CRC32 <> Crc32Val then
raise EZipError.CreateFmt(SErrInvalidCRC,[Item.ArchiveFileName]);
Finally
Free;
end; end;
end; end;
procedure DoUnzip(const Dest: TStream);
begin
if ZMethod=0 then
begin
if (LocalHdr.Compressed_Size<>0) then
begin
if LocalZip64Fld.Compressed_Size>0 then
Dest.CopyFrom(FZipStream,LocalZip64Fld.Compressed_Size)
else
Dest.CopyFrom(FZipStream,LocalHdr.Compressed_Size);
{$warning TODO: Implement CRC Check}
end;
end
else
With CreateDecompressor(Item, ZMethod, FZipStream, Dest) do
Try
OnProgress:=Self.OnProgress;
OnPercent:=Self.OnPercent;
DeCompress;
if Item.CRC32 <> Crc32Val then
raise EZipError.CreateFmt(SErrInvalidCRC,[Item.ArchiveFileName]);
Finally
Free;
end;
end;
Procedure GetOutputFileName;
Var
I : Integer;
begin
if Not UseUTF8 then
OutputFileName:=StringReplace(Item.DiskFileName,'/',DirectorySeparator,[rfReplaceAll])
else
begin
// Sets codepage.
OutputFileName:=Item.UTF8DiskFileName;
U:=UTF8Decode(OutputFileName);
// Do not use stringreplace, it will mess up the codepage.
if '/'<>DirectorySeparator then
For I:=1 to Length(U) do
if U[i]='/' then
U[i]:=DirectorySeparator;
OutputFileName:=UTF8Encode(U);
end;
if (Not IsCustomStream) and (FOutputPath<>'') then
begin
// Do not use IncludeTrailingPathdelimiter
OutputFileName:=FOutputPath+OutputFileName;
end;
end;
Begin Begin
ReadZipHeader(Item, ZMethod); ReadZipHeader(Item, ZMethod);
if (Item.BitFlags and 1)<>0 then if (Item.BitFlags and 1)<>0 then
@ -2478,30 +2581,19 @@ Begin
Raise EZipError.CreateFmt(SErrPatchSetNotSupported,[Item.ArchiveFileName]); Raise EZipError.CreateFmt(SErrPatchSetNotSupported,[Item.ArchiveFileName]);
// Normalize output filename to conventions of target platform. // Normalize output filename to conventions of target platform.
// Zip file always has / path separators // Zip file always has / path separators
if UseUTF8 then
OutputFileName:=StringReplace(Item.UTF8DiskFileName,'/',DirectorySeparator,[rfReplaceAll])
else
OutputFileName:=StringReplace(Item.DiskFileName,'/',DirectorySeparator,[rfReplaceAll]);
IsCustomStream := Assigned(FOnCreateStream); IsCustomStream := Assigned(FOnCreateStream);
GetOutputFileName;
if (IsCustomStream = False) and (FOutputPath<>'') then
OutputFileName:=IncludeTrailingPathDelimiter(FOutputPath)+OutputFileName;
IsLink := Item.IsLink; IsLink := Item.IsLink;
{$IFNDEF UNIX} {$IFNDEF UNIX}
if IsLink and Not IsCustomStream then if IsLink and Not IsCustomStream then
begin begin
{$warning TODO: Implement symbolic link creation for non-unix, e.g. {$warning TODO: Implement symbolic link creation for non-unix, e.g.
Windows NTFS} Windows NTFS}
IsLink := False; IsLink := False;
end; end;
{$ENDIF} {$ENDIF}
if IsCustomStream then if IsCustomStream then
begin begin
try try
OpenOutput(OutputFileName, FOutStream, Item); OpenOutput(OutputFileName, FOutStream, Item);
if (IsLink = False) and (Item.IsDirectory = False) then if (IsLink = False) and (Item.IsDirectory = False) then
@ -2509,65 +2601,34 @@ Begin
Finally Finally
CloseOutput(Item, FOutStream); CloseOutput(Item, FOutStream);
end; end;
end
else
begin
if IsLink then
begin
{$IFDEF UNIX}
LinkTargetStream := TStringStream.Create('');
try
DoUnzip(LinkTargetStream);
fpSymlink(PChar(LinkTargetStream.DataString), PChar(OutputFileName));
finally
LinkTargetStream.Free;
end;
{$ENDIF}
end end
else else
begin begin
if Item.IsDirectory then if IsLink then
CreateDir(OutputFileName)
else
begin begin
{$IFDEF UNIX}
LinkTargetStream := TStringStream.Create('');
try try
OpenOutput(OutputFileName, FOutStream, Item); DoUnzip(LinkTargetStream);
DoUnzip(FOutStream); fpSymlink(PChar(LinkTargetStream.DataString), PChar(OutputFileName));
Finally finally
CloseOutput(Item, FOutStream); LinkTargetStream.Free;
end; end;
end; {$ENDIF}
end; end
end; else if Item.IsDirectory then
CreateDir(OutputFileName)
if Not IsCustomStream then else
begin
// set attributes
FileSetDate(OutputFileName, DateTimeToFileDate(Item.DateTime));
if (Item.Attributes <> 0) then
begin
Attrs := 0;
{$IFDEF UNIX}
if (Item.OS in [OS_UNIX,OS_OSX]) then Attrs := Item.Attributes;
if (Item.OS in [OS_FAT,OS_NTFS,OS_OS2,OS_VFAT]) then
Attrs := ZipFatAttrsToUnixAttrs(Item.Attributes);
{$ELSE}
if (Item.OS in [OS_FAT,OS_NTFS,OS_OS2,OS_VFAT]) then Attrs := Item.Attributes;
if (Item.OS in [OS_UNIX,OS_OSX]) then
Attrs := ZipUnixAttrsToFatAttrs(ExtractFileName(Item.ArchiveFileName), Item.Attributes);
{$ENDIF}
if Attrs <> 0 then
begin begin
{$IFDEF UNIX} try
FpChmod(OutputFileName, Attrs); OpenOutput(OutputFileName, FOutStream, Item);
{$ELSE} DoUnzip(FOutStream);
FileSetAttr(OutputFileName, Attrs); Finally
{$ENDIF} CloseOutput(Item, FOutStream);
end; end;
end;
SetAttributes;
end; end;
end;
end; end;
@ -2618,7 +2679,7 @@ begin
FBufSize:=Value; FBufSize:=Value;
end; end;
procedure TUnZipper.SetFileName(Value: String); procedure TUnZipper.SetFileName(Value: RawByteString);
begin begin
If FUnZipping then If FUnZipping then
@ -2626,14 +2687,25 @@ begin
FFileName:=Value; FFileName:=Value;
end; end;
procedure TUnZipper.SetOutputPath(Value: String); procedure TUnZipper.SetOutputPath(Value: RawByteString);
Var
DS : RawByteString;
begin begin
If FUnZipping then If FUnZipping then
Raise EZipError.Create(SErrFileChange); Raise EZipError.Create(SErrFileChange);
FOutputPath:=Value; FOutputPath:=Value;
If (FOutputPath<>'') and (FoutputPath[Length(FoutputPath)]<>DirectorySeparator) then
begin
// Preserve codepage of outputpath
DS:=DirectorySeparator;
SetCodePage(DS,StringCodePage(FoutputPath),False);
FOutputPath:=FoutputPath+DS;
end;
end; end;
procedure TUnZipper.UnZipFiles(AFileName: String; FileList: TStrings); procedure TUnZipper.UnZipFiles(AFileName: RawByteString; FileList: TStrings);
begin begin
FFileName:=AFileName; FFileName:=AFileName;
@ -2646,7 +2718,7 @@ begin
UnZipAllFiles; UnZipAllFiles;
end; end;
procedure TUnZipper.UnZipAllFiles(AFileName: String); procedure TUnZipper.UnZipAllFiles(AFileName: RawByteString);
begin begin
FFileName:=AFileName; FFileName:=AFileName;