diff --git a/.gitattributes b/.gitattributes index d9b307e47d..9cc5b35868 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3253,6 +3253,7 @@ components/onlinepackagemanager/opkman_createjsonforupdates.pas svneol=native#te components/onlinepackagemanager/opkman_createrepositorypackage.lfm svneol=native#text/plain components/onlinepackagemanager/opkman_createrepositorypackage.pas svneol=native#text/pascal components/onlinepackagemanager/opkman_downloader.pas svneol=native#text/pascal +components/onlinepackagemanager/opkman_fpcdef.inc svneol=native#text/pascal components/onlinepackagemanager/opkman_installer.pas svneol=native#text/pascal components/onlinepackagemanager/opkman_mainfrm.lfm svneol=native#text/plain components/onlinepackagemanager/opkman_mainfrm.pas svneol=native#text/pascal diff --git a/components/onlinepackagemanager/fpcmod/opkman_httpclient.pas b/components/onlinepackagemanager/fpcmod/opkman_httpclient.pas index 687040c78c..02c663a62c 100644 --- a/components/onlinepackagemanager/fpcmod/opkman_httpclient.pas +++ b/components/onlinepackagemanager/fpcmod/opkman_httpclient.pas @@ -18,21 +18,10 @@ unit opkman_httpclient; Todo: * Proxy support ? ---------------------------------------------------------------------} -{ - TFPHTTPClient does not implement a timeout/aborting mechanism(2016.10.01), which - is useful when downloading a large file for example. opkman_httpclient and opkman_downloader - fix this issue. -} {$mode objfpc}{$H+} -{$IF FPC_VERSION = 3} - {$IF FPC_RELEASE > 0} - {$IF FPC_PATCH > 0} - {$DEFINE FPC311} - {$ENDIF} - {$ENDIF} -{$ENDIF} +{$INCLUDE opkman_fpcdef.inc} interface @@ -83,6 +72,7 @@ Type FDataRead : Int64; FContentLength : Int64; FAllowRedirect: Boolean; + FKeepConnection: Boolean; FMaxRedirects: Byte; FOnDataReceived: TDataEvent; FOnHeaders: TNotifyEvent; @@ -101,9 +91,9 @@ Type FServerHTTPVersion: String; FSocket : TInetSocket; FBuffer : Ansistring; + FTerminated: Boolean; FUserName: String; FOnGetSocketHandler : TGetSocketHandlerEvent; - FNeedToBreak: Boolean; FProxy : TProxyData; function CheckContentLength: Int64; function CheckTransferEncoding: string; @@ -111,11 +101,26 @@ Type function GetProxy: TProxyData; Procedure ResetResponse; Procedure SetCookies(const AValue: TStrings); + procedure SetHTTPVersion(const AValue: String); + procedure SetKeepConnection(AValue: Boolean); procedure SetProxy(AValue: TProxyData); Procedure SetRequestHeaders(const AValue: TStrings); procedure SetIOTimeout(AValue: Integer); + Procedure ExtractHostPort(AURI: TURI; Out AHost: String; Out APort: Word); + Procedure CheckConnectionCloseHeader; protected + Function NoContentAllowed(ACode : Integer) : Boolean; + // Peform a request, close connection. + Procedure DoNormalRequest(const AURI: TURI; const AMethod: string; + AStream: TStream; const AAllowedResponseCodes: array of Integer; + AHeadersOnly, AIsHttps: Boolean); virtual; + // Peform a request, try to keep connection. + Procedure DoKeepConnectionRequest(const AURI: TURI; const AMethod: string; + AStream: TStream; const AAllowedResponseCodes: array of Integer; + AHeadersOnly, AIsHttps: Boolean); virtual; + // Return True if FSocket is assigned + Function IsConnected: Boolean; virtual; // True if we need to use a proxy: ProxyData Assigned and Hostname Set Function ProxyActive : Boolean; // Override this if you want to create a custom instance of proxy. @@ -127,19 +132,23 @@ Type // Construct server URL for use in request line. function GetServerURL(URI: TURI): String; // Read 1 line of response. Fills FBuffer - function ReadString: String; + function ReadString(out S: String): Boolean; // Check if response code is in AllowedResponseCodes. if not, an exception is raised. // If AllowRedirect is true, and the result is a Redirect status code, the result is also true // If the OnPassword event is set, then a 401 will also result in True. function CheckResponseCode(ACode: Integer; const AllowedResponseCodes: array of Integer): Boolean; virtual; // Read response from server, and write any document to Stream. - Procedure ReadResponse(Stream: TStream; const AllowedResponseCodes: array of Integer; HeadersOnly: Boolean = False); virtual; + Function ReadResponse(Stream: TStream; const AllowedResponseCodes: array of Integer; HeadersOnly: Boolean = False): Boolean; virtual; // Read server response line and headers. Returns status code. Function ReadResponseHeaders : integer; virtual; // Allow header in request ? (currently checks only if non-empty and contains : token) function AllowHeader(var AHeader: String): Boolean; virtual; + // Return True if the "connection: close" header is present + Function HasConnectionClose: Boolean; virtual; // Connect to the server. Must initialize FSocket. Procedure ConnectToServer(const AHost: String; APort: Integer; UseSSL : Boolean=False); virtual; + // Re-connect to the server. Must reinitialize FSocket. + Procedure ReconnectToServer(const AHost: String; APort: Integer; UseSSL : Boolean=False); virtual; // Disconnect from server. Must free FSocket. Procedure DisconnectFromServer; virtual; // Run method AMethod, using request URL AURL. Write Response to Stream, and headers in ResponseHeaders. @@ -160,13 +169,16 @@ Type Class Function IndexOfHeader(HTTPHeaders : TStrings; Const AHeader : String) : Integer; // Return value of header AHeader from httpheaders. Returns empty if it doesn't exist yet. Class Function GetHeader(HTTPHeaders : TStrings; Const AHeader : String) : String; + { Terminate the current request. + It will stop the client from trying to send and/or receive data after the current chunk is sent/received. } + Procedure Terminate; // Request Header management // Return index of header, -1 if not present. Function IndexOfHeader(Const AHeader : String) : Integer; // Add header, replacing an existing one if it exists. Procedure AddHeader(Const AHeader,AValue : String); // Return header value, empty if not present. - Function GetHeader(Const AHeader : String) : String; + Function GetHeader(Const AHeader : String) : String; // General-purpose call. Handles redirect and authorization retry (OnPassword). Procedure HTTPMethod(Const AMethod,AURL : String; Stream : TStream; Const AllowedResponseCodes : Array of Integer); virtual; // Execute GET on server, store result in Stream, File, StringList or string @@ -239,7 +251,7 @@ Type Procedure FormPost(const URL : string; FormData: TStrings; const Response: TStrings); function FormPost(const URL, FormData: string): String; function FormPost(const URL: string; FormData : TStrings): String; - // Simple form + // Simple form Class Procedure SimpleFormPost(const URL, FormData: string; const Response: TStream); Class Procedure SimpleFormPost(const URL : string; FormData: TStrings; const Response: TStream); Class Procedure SimpleFormPost(const URL, FormData: string; const Response: TStrings); @@ -256,6 +268,8 @@ Type Procedure StreamFormPost(const AURL: string; FormData: TStrings; const AFieldName, AFileName: string; const AStream: TStream; const Response: TStream); // Simple form of Posting a file Class Procedure SimpleFileFormPost(const AURL, AFieldName, AFileName: string; const Response: TStream); + // Has Terminate been called ? + Property Terminated : Boolean Read FTerminated; Protected // Timeouts Property IOTimeout : Integer read FIOTimeout write SetIOTimeout; @@ -268,7 +282,8 @@ Type // Optional body to send (mainly in POST request) Property RequestBody : TStream read FRequestBody Write FRequestBody; // used HTTP version when constructing the request. - Property HTTPversion : String Read FHTTPVersion Write FHTTPVersion; + // Setting this to any other value than 1.1 will set KeepConnection to False. + Property HTTPversion : String Read FHTTPVersion Write SetHTTPVersion; // After request properties. // After request, this contains the headers sent by server. Property ResponseHeaders : TStrings Read FResponseHeaders; @@ -292,6 +307,10 @@ Type // They also override any Authenticate: header in Requestheaders. Property UserName : String Read FUserName Write FUserName; Property Password : String Read FPassword Write FPassword; + // Is client connected? + Property Connected: Boolean read IsConnected; + // Keep-Alive support. Setting to true will set HTTPVersion to 1.1 + Property KeepConnection: Boolean Read FKeepConnection Write SetKeepConnection; // If a request returns a 401, then the OnPassword event is fired. // It can modify the username/password and set RepeatRequest to true; Property OnPassword : TPasswordEvent Read FOnPassword Write FOnPassword; @@ -301,12 +320,14 @@ Type Property OnHeaders : TNotifyEvent Read FOnHeaders Write FOnHeaders; // Called to create socket handler. If not set, or Nil is returned, a standard socket handler is created. Property OnGetSocketHandler : TGetSocketHandlerEvent Read FOnGetSocketHandler Write FOnGetSocketHandler; - Property NeedToBreak: Boolean read FNeedToBreak write FNeedToBreak; + end; TFPHTTPClient = Class(TFPCustomHTTPClient) Published + Property KeepConnection; + Property Connected; Property IOTimeout; Property RequestHeaders; Property RequestBody; @@ -326,7 +347,6 @@ Type Property OnHeaders; Property OnGetSocketHandler; Property Proxy; - Property NeedToBreak; end; EHTTPClient = Class(EHTTP); @@ -335,19 +355,19 @@ Function EncodeURLElement(S : String) : String; Function DecodeURLElement(Const S : String) : String; implementation -{$IFnDEF HASAMIGA} +{$if not defined(hasamiga)} uses sslsockets; -{$ENDIF} +{$endif} resourcestring - SErrInvalidProtocol = 'Invalid protocol: "%s"'; + SErrInvalidProtocol = 'Invalid protocol : "%s"'; SErrReadingSocket = 'Error reading data from socket'; SErrInvalidProtocolVersion = 'Invalid protocol version in response: "%s"'; SErrInvalidStatusCode = 'Invalid response status code: %s'; SErrUnexpectedResponse = 'Unexpected response status code: %d'; SErrChunkTooBig = 'Chunk too big'; SErrChunkLineEndMissing = 'Chunk line end missing'; - SErrMaxRedirectsReached = 'Maximum allowed redirects reached: %d'; + SErrMaxRedirectsReached = 'Maximum allowed redirects reached : %d'; //SErrRedirectAborted = 'Redirect aborted.'; Const @@ -475,6 +495,11 @@ begin {$ENDIF} end; +function TFPCustomHTTPClient.IsConnected: Boolean; +begin + Result := Assigned(FSocket); +end; + function TFPCustomHTTPClient.NoContentAllowed(ACode: Integer): Boolean; begin Result:=((ACode div 100)=1) or ((ACode=204) or (ACode=304)) @@ -545,11 +570,11 @@ begin if Assigned(FonGetSocketHandler) then FOnGetSocketHandler(Self,UseSSL,Result); if (Result=Nil) then - {$IFnDEF HASAMIGA} + {$if not defined(HASAMIGA)} If UseSSL then Result:=TSSLSocketHandler.Create else - {$ENDIF} + {$endif} Result:=TSocketHandler.Create; end; @@ -561,17 +586,19 @@ Var begin + If IsConnected Then + DisconnectFromServer; // avoid memory leaks if (Aport=0) then if UseSSL then Aport:=443 else Aport:=80; - G:=GetSocketHandler(UseSSL); + G:=GetSocketHandler(UseSSL); FSocket:=TInetSocket.Create(AHost,APort,G); try {$IFDEF FPC311} - if FIOTimeout <> 0 then - FSocket.IOTimeout := FIOTimeout; + if FIOTimeout<>0 then + FSocket.IOTimeout:=FIOTimeout; {$ENDIF} FSocket.Connect; except @@ -580,6 +607,13 @@ begin end; end; +Procedure TFPCustomHTTPClient.ReconnectToServer(const AHost: String; + APort: Integer; UseSSL: Boolean); +begin + DisconnectFromServer; + ConnectToServer(AHost, APort, UseSSL); +end; + procedure TFPCustomHTTPClient.DisconnectFromServer; begin @@ -592,6 +626,11 @@ begin Result:=(AHeader<>'') and (Pos(':',AHeader)<>0); end; +Function TFPCustomHTTPClient.HasConnectionClose: Boolean; +begin + Result := CompareText(GetHeader('Connection'), 'close') = 0; +end; + procedure TFPCustomHTTPClient.SendRequest(const AMethod: String; URI: TURI); Var @@ -626,6 +665,7 @@ begin S:=S+CRLF; If Assigned(RequestBody) and (IndexOfHeader('Content-Length')=-1) then AddHeader('Content-Length',IntToStr(RequestBody.Size)); + CheckConnectionCloseHeader; For I:=0 to FRequestHeaders.Count-1 do begin l:=FRequestHeaders[i]; @@ -648,55 +688,60 @@ begin FSentCookies:=FCookies; FCookies:=Nil; S:=S+CRLF; - FSocket.WriteBuffer(S[1],Length(S)); - If Assigned(FRequestBody) then + if not Terminated then + FSocket.WriteBuffer(S[1],Length(S)); + If Assigned(FRequestBody) and not Terminated then FSocket.CopyFrom(FRequestBody,FRequestBody.Size); end; -function TFPCustomHTTPClient.ReadString : String; +function TFPCustomHTTPClient.ReadString(out S: String): Boolean; - Procedure FillBuffer; + Function FillBuffer: Boolean; Var R : Integer; begin + if Terminated then + Exit(False); SetLength(FBuffer,ReadBufLen); r:=FSocket.Read(FBuffer[1],ReadBufLen); - If r<0 then + If (r=0) or Terminated Then + Exit(False); + If (r<0) then Raise EHTTPClient.Create(SErrReadingSocket); if (r0; end; Var - CheckLF,Done : Boolean; + CheckLF: Boolean; P,L : integer; begin - Result:=''; - Done:=False; + S:=''; + Result:=False; CheckLF:=False; Repeat - if NeedToBreak then - Break; if Length(FBuffer)=0 then - FillBuffer; + if not FillBuffer then + Break; if Length(FBuffer)=0 then - Done:=True + Result:=True else if CheckLF then begin If (FBuffer[1]<>#10) then - Result:=Result+#13 + S:=S+#13 else begin System.Delete(FBuffer,1,1); - Done:=True; + Result:=True; end; end; - if not Done then + if not Result then begin P:=Pos(#13#10,FBuffer); If P=0 then @@ -704,20 +749,21 @@ begin L:=Length(FBuffer); CheckLF:=FBuffer[L]=#13; if CheckLF then - Result:=Result+Copy(FBuffer,1,L-1) + S:=S+Copy(FBuffer,1,L-1) else - Result:=Result+FBuffer; + S:=S+FBuffer; FBuffer:=''; end else begin - Result:=Result+Copy(FBuffer,1,P-1); + S:=S+Copy(FBuffer,1,P-1); System.Delete(FBuffer,1,P+1); - Done:=True; + Result:=True; end; end; - until Done; + until Result or Terminated; end; + Function GetNextWord(Var S : String) : string; Const @@ -770,15 +816,13 @@ function TFPCustomHTTPClient.ReadResponseHeaders: integer; P:=Pos(':',S); System.Delete(S,1,P); Repeat - if NeedToBreak then - Break; P:=Pos(';',S); If (P=0) then P:=Length(S)+1; C:=Trim(Copy(S,1,P-1)); Cookies.Add(C); System.Delete(S,1,P); - Until (S=''); + Until (S='') or Terminated; end; Const @@ -788,20 +832,18 @@ Var StatusLine,S : String; begin - StatusLine:=ReadString; + if not ReadString(StatusLine) then + Exit(0); Result:=ParseStatusLine(StatusLine); Repeat - if NeedToBreak then - Break; - S:=ReadString; - if (S<>'') then + if ReadString(S) and (S<>'') then begin ResponseHeaders.Add(S); If (LowerCase(Copy(S,1,Length(SetCookie)))=SetCookie) then DoCookies(S); end - Until (S=''); - If Assigned(FOnHeaders) then + Until (S='') or Terminated; + If Assigned(FOnHeaders) and not Terminated then FOnHeaders(Self); end; @@ -902,14 +944,33 @@ begin GetCookies.Assign(AValue); end; +procedure TFPCustomHTTPClient.SetHTTPVersion(const AValue: String); +begin + if FHTTPVersion = AValue then Exit; + FHTTPVersion := AValue; + if (AValue<>'1.1') then + KeepConnection:=False; +end; + +procedure TFPCustomHTTPClient.SetKeepConnection(AValue: Boolean); +begin + if FKeepConnection=AValue then Exit; + FKeepConnection:=AValue; + if AValue then + HTTPVersion:='1.1' + else if IsConnected then + DisconnectFromServer; + CheckConnectionCloseHeader; +end; + procedure TFPCustomHTTPClient.SetProxy(AValue: TProxyData); begin if (AValue=FProxy) then exit; Proxy.Assign(AValue); end; -procedure TFPCustomHTTPClient.ReadResponse(Stream: TStream; - const AllowedResponseCodes: array of Integer; HeadersOnly: Boolean); +Function TFPCustomHTTPClient.ReadResponse(Stream: TStream; + const AllowedResponseCodes: array of Integer; HeadersOnly: Boolean): Boolean; Function Transfer(LB : Integer) : Integer; @@ -944,6 +1005,9 @@ procedure TFPCustomHTTPClient.ReadResponse(Stream: TStream; function FetchData(out Cnt: integer): boolean; begin + Result:=False; + If Terminated then + exit; SetLength(FBuffer,ReadBuflen); Cnt:=FSocket.Read(FBuffer[1],length(FBuffer)); If Cnt<0 then @@ -984,31 +1048,28 @@ procedure TFPCustomHTTPClient.ReadResponse(Stream: TStream; begin BufPos:=1; repeat - if NeedToBreak then - Break; // read ChunkSize ChunkSize:=0; repeat - if NeedToBreak then - Break; if ReadData(@c,1)<1 then exit; case c of '0'..'9': ChunkSize:=ChunkSize*16+ord(c)-ord('0'); 'a'..'f': ChunkSize:=ChunkSize*16+ord(c)-ord('a')+10; 'A'..'F': ChunkSize:=ChunkSize*16+ord(c)-ord('A')+10; - else break; + else + break; end; if ChunkSize>1000000 then Raise EHTTPClient.Create(SErrChunkTooBig); - until false; + until Terminated; // read till line end - while (c<>#10) do + while (c<>#10) and not Terminated do if ReadData(@c,1)<1 then exit; if ChunkSize=0 then exit; // read data repeat - if NeedToBreak then - Break; + if Terminated then + exit; l:=length(FBuffer)-BufPos+1; if l=0 then if not FetchData(l) then @@ -1024,14 +1085,18 @@ procedure TFPCustomHTTPClient.ReadResponse(Stream: TStream; end; until ChunkSize=0; // read #13#10 - if ReadData(@c,1)<1 then exit; - if c<>#13 then - Raise EHTTPClient.Create(SErrChunkLineEndMissing); - if ReadData(@c,1)<1 then exit; - if c<>#10 then - Raise EHTTPClient.Create(SErrChunkLineEndMissing); - // next chunk - until false; + if ReadData(@c,1)<1 then + exit; + if Not Terminated then + begin + if c<>#13 then + Raise EHTTPClient.Create(SErrChunkLineEndMissing); + if ReadData(@c,1)<1 then exit; + if c<>#10 then + Raise EHTTPClient.Create(SErrChunkLineEndMissing); + // next chunk + end; + until Terminated; end; Var @@ -1043,6 +1108,9 @@ begin FContentLength:=0; SetLength(FBuffer,0); FResponseStatusCode:=ReadResponseHeaders; + Result := FResponseStatusCode > 0; + if not Result then + Exit; if not CheckResponseCode(FResponseStatusCode,AllowedResponseCodes) then Raise EHTTPClient.CreateFmt(SErrUnexpectedResponse,[ResponseStatusCode]); if HeadersOnly Or (AllowRedirect and IsRedirect(FResponseStatusCode)) then @@ -1064,34 +1132,122 @@ begin // We cannot use copyfrom, it uses ReadBuffer, and this is dangerous with sockets L:=L-LB; Repeat - if NeedToBreak then - Break; LB:=ReadBufLen; If (LB>L) then LB:=L; R:=Transfer(LB); L:=L-R; - until (L=0) or (R=0); + until (L=0) or (R=0) or Terminated; end else if (L<0) and (Not NoContentAllowed(ResponseStatusCode)) then begin // No content-length, so we read till no more data available. Repeat - if NeedToBreak then - Break; R:=Transfer(ReadBufLen); - until (R=0); + until (R=0) or Terminated; end; end; end; -procedure TFPCustomHTTPClient.DoMethod(const AMethod, AURL: String; - Stream: TStream; const AllowedResponseCodes: array of Integer); +Procedure TFPCustomHTTPClient.ExtractHostPort(AURI: TURI; Out AHost: String; + Out APort: Word); +Begin + if ProxyActive then + begin + AHost:=Proxy.Host; + APort:=Proxy.Port; + end + else + begin + AHost:=AURI.Host; + APort:=AURI.Port; + end; +End; + +procedure TFPCustomHTTPClient.CheckConnectionCloseHeader; Var - URI : TURI; - P,CHost : String; - CPort : Word; + I : integer; + N,V : String; + +begin + V:=GetHeader('Connection'); + If FKeepConnection Then + begin + I:=IndexOfHeader(FRequestHeaders,'Connection'); + If i>-1 Then + begin + // It can be keep-alive, check value + FRequestHeaders.GetNameValue(I,N,V); + If CompareText(V,'close')=0 then + FRequestHeaders.Delete(i); + end + end + Else + AddHeader('Connection', 'close'); +end; + +Procedure TFPCustomHTTPClient.DoNormalRequest(const AURI: TURI; + const AMethod: string; AStream: TStream; + const AAllowedResponseCodes: array of Integer; + AHeadersOnly, AIsHttps: Boolean); + +Var + CHost: string; + CPort: Word; + +begin + ExtractHostPort(AURI, CHost, CPort); + ConnectToServer(CHost,CPort,AIsHttps); + Try + SendRequest(AMethod,AURI); + if not Terminated then + ReadResponse(AStream,AAllowedResponseCodes,AHeadersOnly); + Finally + DisconnectFromServer; + End; +end; + +Procedure TFPCustomHTTPClient.DoKeepConnectionRequest(const AURI: TURI; + const AMethod: string; AStream: TStream; + const AAllowedResponseCodes: array of Integer; + AHeadersOnly, AIsHttps: Boolean); + +Var + T: Boolean; + CHost: string; + CPort: Word; + +begin + ExtractHostPort(AURI, CHost, CPort); + T := False; + Repeat + If Not IsConnected Then + ConnectToServer(CHost,CPort,AIsHttps); + Try + if not Terminated then + SendRequest(AMethod,AURI); + if not Terminated then + begin + T := ReadResponse(AStream,AAllowedResponseCodes,AHeadersOnly); + If Not T Then + ReconnectToServer(CHost,CPort,AIsHttps); + end; + Finally + // On terminate, we close the request + If HasConnectionClose or Terminated Then + DisconnectFromServer; + End; + Until T or Terminated; +end; + +Procedure TFPCustomHTTPClient.DoMethod(Const AMethod, AURL: String; + Stream: TStream; Const AllowedResponseCodes: Array of Integer); + +Var + URI: TURI; + P: String; + IsHttps, HeadersOnly: Boolean; begin ResetResponse; @@ -1099,23 +1255,12 @@ begin p:=LowerCase(URI.Protocol); If Not ((P='http') or (P='https')) then Raise EHTTPClient.CreateFmt(SErrInvalidProtocol,[URI.Protocol]); - if ProxyActive then - begin - CHost:=Proxy.Host; - CPort:=Proxy.Port; - end + IsHttps:=P='https'; + HeadersOnly:=CompareText(AMethod,'HEAD')=0; + if FKeepConnection then + DoKeepConnectionRequest(URI,AMethod,Stream,AllowedResponseCodes,HeadersOnly,IsHttps) else - begin - CHost:=URI.Host; - CPort:=URI.Port; - end; - ConnectToServer(CHost,CPort,P='https'); - try - SendRequest(AMethod,URI); - ReadResponse(Stream,AllowedResponseCodes,CompareText(AMethod,'HEAD')=0); - finally - DisconnectFromServer; - end; + DoNormalRequest(URI,AMethod,Stream,AllowedResponseCodes,HeadersOnly,IsHttps); end; constructor TFPCustomHTTPClient.Create(AOwner: TComponent); @@ -1124,13 +1269,17 @@ begin // Infinite timeout on most platforms FIOTimeout:=0; FRequestHeaders:=TStringList.Create; + FRequestHeaders.NameValueSeparator:=':'; FResponseHeaders:=TStringList.Create; - FHTTPVersion:='1.1'; + FResponseHeaders.NameValueSeparator:=':'; + HTTPVersion:='1.1'; FMaxRedirects:=DefMaxRedirects; end; destructor TFPCustomHTTPClient.Destroy; begin + if IsConnected then + DisconnectFromServer; FreeAndNil(FProxy); FreeAndNil(FCookies); FreeAndNil(FSentCookies); @@ -1184,6 +1333,11 @@ begin end; end; +procedure TFPCustomHTTPClient.Terminate; +begin + FTerminated:=True; +end; + procedure TFPCustomHTTPClient.ResetResponse; begin @@ -1204,19 +1358,19 @@ Var RR : Boolean; // Repeat request ? begin + // Reset Terminated + FTerminated:=False; L:=AURL; RC:=0; RR:=False; M:=AMethod; Repeat - if FNeedToBreak then - Break; if Not AllowRedirect then DoMethod(M,L,Stream,AllowedResponseCodes) else begin DoMethod(M,L,Stream,AllowedResponseCodes); - if IsRedirect(FResponseStatusCode) then + if IsRedirect(FResponseStatusCode) and not Terminated then begin Inc(RC); if (RC>MaxRedirects) then @@ -1242,8 +1396,8 @@ begin FOnPassword(Self,RR); end else - RR:=AllowRedirect and IsRedirect(FResponseStatusCode) and (L<>'') - until not RR; + RR:=AllowRedirect and IsRedirect(FResponseStatusCode) and (L<>''); + until Terminated or not RR ; end; procedure TFPCustomHTTPClient.Get(const AURL: String; Stream: TStream); @@ -1310,7 +1464,7 @@ class procedure TFPCustomHTTPClient.SimpleGet(const AURL: String; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Get(AURL,Stream); finally Free; @@ -1324,7 +1478,7 @@ class procedure TFPCustomHTTPClient.SimpleGet(const AURL: String; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Get(AURL,LocalFileName); finally Free; @@ -1338,7 +1492,7 @@ class procedure TFPCustomHTTPClient.SimpleGet(const AURL: String; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Get(AURL,Response); finally Free; @@ -1347,7 +1501,7 @@ end; class function TFPCustomHTTPClient.SimpleGet(const AURL: String): String; - + begin With Self.Create(nil) do try @@ -1406,7 +1560,7 @@ class procedure TFPCustomHTTPClient.SimplePost(const URL: string; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Post(URL,Response); finally Free; @@ -1420,7 +1574,7 @@ class procedure TFPCustomHTTPClient.SimplePost(const URL: string; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Post(URL,Response); finally Free; @@ -1434,7 +1588,7 @@ class procedure TFPCustomHTTPClient.SimplePost(const URL: string; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Post(URL,LocalFileName); finally Free; @@ -1447,7 +1601,7 @@ class function TFPCustomHTTPClient.SimplePost(const URL: string): String; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Result:=Post(URL); finally Free; @@ -1498,7 +1652,7 @@ class procedure TFPCustomHTTPClient.SimplePut(const URL: string; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Put(URL,Response); finally Free; @@ -1511,7 +1665,7 @@ class procedure TFPCustomHTTPClient.SimplePut(const URL: string; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Put(URL,Response); finally Free; @@ -1524,7 +1678,7 @@ class procedure TFPCustomHTTPClient.SimplePut(const URL: string; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Put(URL,LocalFileName); finally Free; @@ -1536,7 +1690,7 @@ class function TFPCustomHTTPClient.SimplePut(const URL: string): String; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Result:=Put(URL); finally Free; @@ -1588,7 +1742,7 @@ class procedure TFPCustomHTTPClient.SimpleDelete(const URL: string; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Delete(URL,Response); finally Free; @@ -1601,7 +1755,7 @@ class procedure TFPCustomHTTPClient.SimpleDelete(const URL: string; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Delete(URL,Response); finally Free; @@ -1614,7 +1768,7 @@ class procedure TFPCustomHTTPClient.SimpleDelete(const URL: string; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Delete(URL,LocalFileName); finally Free; @@ -1626,7 +1780,7 @@ class function TFPCustomHTTPClient.SimpleDelete(const URL: string): String; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Result:=Delete(URL); finally Free; @@ -1678,7 +1832,7 @@ class procedure TFPCustomHTTPClient.SimpleOptions(const URL: string; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Options(URL,Response); finally Free; @@ -1691,7 +1845,7 @@ class procedure TFPCustomHTTPClient.SimpleOptions(const URL: string; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Options(URL,Response); finally Free; @@ -1704,7 +1858,7 @@ class procedure TFPCustomHTTPClient.SimpleOptions(const URL: string; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Options(URL,LocalFileName); finally Free; @@ -1716,7 +1870,7 @@ class function TFPCustomHTTPClient.SimpleOptions(const URL: string): String; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Result:=Options(URL); finally Free; @@ -1727,7 +1881,7 @@ class procedure TFPCustomHTTPClient.Head(AURL: String; Headers: TStrings); begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; HTTPMethod('HEAD', AURL, Nil, [200]); Headers.Assign(ResponseHeaders); Finally @@ -1812,7 +1966,7 @@ class procedure TFPCustomHTTPClient.SimpleFormPost(const URL, FormData: string; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; FormPost(URL,FormData,Response); Finally Free; @@ -1826,7 +1980,7 @@ class procedure TFPCustomHTTPClient.SimpleFormPost(const URL: string; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; FormPost(URL,FormData,Response); Finally Free; @@ -1840,7 +1994,7 @@ class procedure TFPCustomHTTPClient.SimpleFormPost(const URL, FormData: string; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; FormPost(URL,FormData,Response); Finally Free; @@ -1853,7 +2007,7 @@ class procedure TFPCustomHTTPClient.SimpleFormPost(const URL: string; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; FormPost(URL,FormData,Response); Finally Free; @@ -1866,7 +2020,7 @@ class function TFPCustomHTTPClient.SimpleFormPost(const URL, FormData: string begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Result:=FormPost(URL,FormData); Finally Free; @@ -1879,7 +2033,7 @@ class function TFPCustomHTTPClient.SimpleFormPost(const URL: string; begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; Result:=FormPost(URL,FormData); Finally Free; @@ -1958,7 +2112,7 @@ class procedure TFPCustomHTTPClient.SimpleFileFormPost(const AURL, AFieldName, begin With Self.Create(nil) do try - RequestHeaders.Add('Connection: Close'); + KeepConnection := False; FileFormPost(AURL,AFieldName,AFileName,Response); Finally Free; diff --git a/components/onlinepackagemanager/fpcmod/opkman_zip.pas b/components/onlinepackagemanager/fpcmod/opkman_zip.pas index 9feef08fa6..391c6031ea 100644 --- a/components/onlinepackagemanager/fpcmod/opkman_zip.pas +++ b/components/onlinepackagemanager/fpcmod/opkman_zip.pas @@ -21,8 +21,7 @@ Uses {$IFDEF UNIX} BaseUnix, {$ENDIF} - SysUtils,Classes,zstream, - dialogs; + SysUtils,Classes,zstream; Const @@ -33,6 +32,8 @@ Const LOCAL_FILE_HEADER_SIGNATURE = $04034B50; CENTRAL_FILE_HEADER_SIGNATURE = $02014B50; ZIP64_HEADER_ID = $0001; + // infozip unicode path + INFOZIP_UNICODE_PATH_ID = $7075; const OS_FAT = 0; //MS-DOS and OS/2 (FAT/VFAT/FAT32) @@ -70,6 +71,7 @@ Type Local_File_Header_Type = Packed Record //1 per zipped file Signature : LongInt; //4 bytes Extract_Version_Reqd : Word; //if zip64: >= 45 + {$warning TODO implement EFS/language enooding using UTF-8} Bit_Flag : Word; //"General purpose bit flag in PKZip appnote Compress_Method : Word; Last_Mod_Time : Word; @@ -204,13 +206,15 @@ Type { TCompressor } TCompressor = Class(TObject) + private + FTerminated: Boolean; Protected - FInFile : TStream; { I/O file variables } - FOutFile : TStream; - FCrc32Val : LongWord; { CRC calculation variable } - FBufferSize : LongWord; - FOnPercent : Integer; - FOnProgress : TProgressEvent; + FInFile : TStream; { I/O file variables } + FOutFile : TStream; + FCrc32Val : LongWord; { CRC calculation variable } + FBufferSize : LongWord; + FOnPercent : Integer; + FOnProgress : TProgressEvent; Procedure UpdC32(Octet: Byte); Public Constructor Create(AInFile, AOutFile : TStream; ABufSize : LongWord); virtual; @@ -218,35 +222,39 @@ Type Class Function ZipID : Word; virtual; Abstract; Class Function ZipVersionReqd: Word; virtual; Abstract; Function ZipBitFlag: Word; virtual; Abstract; + Procedure Terminate; Property BufferSize : LongWord read FBufferSize; Property OnPercent : Integer Read FOnPercent Write FOnPercent; Property OnProgress : TProgressEvent Read FOnProgress Write FOnProgress; Property Crc32Val : LongWord Read FCrc32Val Write FCrc32Val; + Property Terminated : Boolean Read FTerminated; end; { TDeCompressor } TDeCompressor = Class(TObject) Protected - FInFile : TStream; { I/O file variables } - FOutFile : TStream; - FCrc32Val : LongWord; { CRC calculation variable } - FBufferSize : LongWord; - FOnPercent : Integer; - FOnProgress : TProgressEvent; + FInFile : TStream; { I/O file variables } + FOutFile : TStream; + FCrc32Val : LongWord; { CRC calculation variable } + FBufferSize : LongWord; + FOnPercent : Integer; + FOnProgress : TProgressEvent; FOnProgressEx: TProgressEventEx; FTotPos : Int64; FTotSize : Int64; - FNeedToBreak : Boolean; + FTerminated : Boolean; Procedure UpdC32(Octet: Byte); Public Constructor Create(AInFile, AOutFile : TStream; ABufSize : LongWord); virtual; Procedure DeCompress; Virtual; Abstract; + Procedure Terminate; Class Function ZipID : Word; virtual; Abstract; Property BufferSize : LongWord read FBufferSize; Property OnPercent : Integer Read FOnPercent Write FOnPercent; Property OnProgress : TProgressEvent Read FOnProgress Write FOnProgress; Property OnProgressEx : TProgressEventEx Read FOnProgressEx Write FOnProgressEx; Property Crc32Val : LongWord Read FCrc32Val Write FCrc32Val; + Property Terminated : Boolean Read FTerminated; end; { TShrinker } @@ -345,6 +353,8 @@ Type TZipFileEntry = Class(TCollectionItem) private FArchiveFileName: String; //Name of the file as it appears in the zip file list + FUTF8FileName : UTF8String; + FUTF8DiskFileName : UTF8String; FAttributes: LongWord; FDateTime: TDateTime; FDiskFileName: String; {Name of the file on disk (i.e. uncompressed. Can be empty if based on a stream.); @@ -356,8 +366,12 @@ Type FStream: TStream; FCompressionLevel: TCompressionlevel; function GetArchiveFileName: String; + function GetUTF8ArchiveFileName: UTF8String; + function GetUTF8DiskFileName: UTF8String; procedure SetArchiveFileName(Const AValue: String); procedure SetDiskFileName(Const AValue: String); + procedure SetUTF8ArchiveFileName(AValue: UTF8String); + procedure SetUTF8DiskFileName(AValue: UTF8String); Protected // For multi-disk support, a disk number property could be added here. Property HdrPos : int64 Read FHeaderPos Write FheaderPos; @@ -370,7 +384,9 @@ Type Property Stream : TStream Read FStream Write FStream; Published Property ArchiveFileName : String Read GetArchiveFileName Write SetArchiveFileName; + Property UTF8ArchiveFileName : UTF8String Read GetUTF8ArchiveFileName Write SetUTF8ArchiveFileName; Property DiskFileName : String Read FDiskFileName Write SetDiskFileName; + Property UTF8DiskFileName : UTF8String Read GetUTF8DiskFileName Write SetUTF8DiskFileName; Property Size : Int64 Read FSize Write FSize; Property DateTime : TDateTime Read FDateTime Write FDateTime; property OS: Byte read FOS write FOS; @@ -397,9 +413,10 @@ Type TZipper = Class(TObject) Private FEntries : TZipFileEntries; + FTerminated: Boolean; FZipping : Boolean; FBufSize : LongWord; - FFileName : String; { Name of resulting Zip file } + FFileName : RawByteString; { Name of resulting Zip file } FFileComment : String; FFiles : TStrings; FInMemSize : Int64; @@ -411,12 +428,11 @@ Type LocalZip64Fld : Zip64_Extended_Info_Field_Type; //header is in LocalZip64ExtHdr CentralHdr : Central_File_Header_Type; EndHdr : End_of_Central_Dir_Type; - FNeedToBreak : Boolean; FOnPercent : LongInt; FOnProgress : TProgressEvent; - FOnProgressEx : TProgressEventEx; FOnEndOfFile : TOnEndOfFileEvent; FOnStartFile : TOnStartFileEvent; + FCurrentCompressor : TCompressor; function CheckEntries: Integer; procedure SetEntries(const AValue: TZipFileEntries); Protected @@ -429,7 +445,7 @@ Type Function OpenInput(Item : TZipFileEntry) : Boolean; Procedure GetFileInfo; Procedure SetBufSize(Value : LongWord); - Procedure SetFileName(Value : String); + Procedure SetFileName(Value : RawByteString); Function CreateCompressor(Item : TZipFileEntry; AinFile,AZipStream : TStream) : TCompressor; virtual; Property NeedsZip64 : boolean Read FZipFileNeedsZip64 Write FZipFileNeedsZip64; Public @@ -437,40 +453,42 @@ Type Destructor Destroy;override; Procedure ZipAllFiles; virtual; // Saves zip to file and changes FileName - Procedure SaveToFile(AFileName: string); + Procedure SaveToFile(AFileName: RawByteString); // Saves zip to stream Procedure SaveToStream(AStream: TStream); // 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); // 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 Clear; + Procedure Terminate; Public Property BufferSize : LongWord Read FBufSize Write SetBufSize; Property OnPercent : Integer Read FOnPercent Write FOnPercent; Property OnProgress : TProgressEvent Read FOnProgress Write FOnProgress; - Property OnProgressEx : TProgressEventEx Read FOnProgressEx Write FOnProgressEx; Property OnStartFile : TOnStartFileEvent Read FOnStartFile Write FOnStartFile; 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; // Deprecated. Use Entries.AddFileEntry(FileName) or Entries.AddFileEntries(List) instead. Property Files : TStrings Read FFiles; deprecated; Property InMemSize : Int64 Read FInMemSize Write FInMemSize; Property Entries : TZipFileEntries Read FEntries Write SetEntries; - Property NeedToBreak : Boolean Read FNeedToBreak Write FNeedToBreak; + Property Terminated : Boolean Read FTerminated; end; { TFullZipFileEntry } TFullZipFileEntry = Class(TZipFileEntry) private + FBitFlags: Word; FCompressedSize: QWord; FCompressMethod: Word; FCRC32: LongWord; Public + Property BitFlags : Word Read FBitFlags; Property CompressMethod : Word Read FCompressMethod; Property CompressedSize : QWord Read FCompressedSize; property CRC32: LongWord read FCRC32 write FCRC32; @@ -499,23 +517,27 @@ Type FOnOpenInputStream: TCustomInputStreamEvent; FUnZipping : Boolean; FBufSize : LongWord; - FFileName : String; { Name of resulting Zip file } - FOutputPath : String; + FFileName : RawByteString; { Name of resulting Zip file } + FOutputPath : RawByteString; FFileComment: String; FEntries : TFullZipFileEntries; FFiles : TStrings; + FUseUTF8: Boolean; FZipStream : TStream; { I/O file variables } LocalHdr : Local_File_Header_Type; //Local header, before compressed file data LocalZip64Fld : Zip64_Extended_Info_Field_Type; //header is in LocalZip64ExtHdr CentralHdr : Central_File_Header_Type; FTotPos : Int64; FTotSize : Int64; - FNeedToBreak: Boolean; + FTerminated: Boolean; FOnPercent : LongInt; FOnProgress : TProgressEvent; FOnProgressEx : TProgressEventEx; FOnEndOfFile : TOnEndOfFileEvent; FOnStartFile : TOnStartFileEvent; + FCurrentDecompressor: TDecompressor; + function CalcTotalSize(AllFiles: Boolean): Int64; + function IsMatch(I: TFullZipFileEntry): Boolean; Protected Procedure OpenInput; Procedure CloseOutput(Item : TFullZipFileEntry; var OutStream: TStream); @@ -529,21 +551,21 @@ Type Procedure ReadZipHeader(Item : TFullZipFileEntry; out AMethod : Word); Procedure DoEndOfFile; Procedure UnZipOneFile(Item : TFullZipFileEntry); virtual; - Function OpenOutput(OutFileName : String; var OutStream: TStream; Item : TFullZipFileEntry) : Boolean; + Function OpenOutput(OutFileName : RawByteString; Out OutStream: TStream; Item : TFullZipFileEntry) : Boolean; Procedure SetBufSize(Value : LongWord); - Procedure SetFileName(Value : String); - Procedure SetOutputPath(Value:String); - Function CreateDeCompressor({%H-}Item : TZipFileEntry; AMethod : Word;AZipFile,AOutFile : TStream) : TDeCompressor; virtual; + Procedure SetFileName(Value : RawByteString); + Procedure SetOutputPath(Value: RawByteString); + Function CreateDeCompressor(Item : TZipFileEntry; AMethod : Word;AZipFile,AOutFile : TStream) : TDeCompressor; virtual; Public Constructor Create; Destructor Destroy;override; Procedure UnZipAllFiles; virtual; - Procedure UnZipFiles(AFileName : String; FileList : TStrings); + Procedure UnZipFiles(AFileName : RawByteString; FileList : TStrings); Procedure UnZipFiles(FileList : TStrings); - Procedure UnZipAllFiles(AFileName : String); + Procedure UnZipAllFiles(AFileName : RawByteString); Procedure Clear; Procedure Examine; - Function GetZipSize(var IsDirZipped: Boolean; var ABaseDir: String): Int64; + Procedure Terminate; Public Property BufferSize : LongWord Read FBufSize Write SetBufSize; Property OnOpenInputStream: TCustomInputStreamEvent read FOnOpenInputStream write FOnOpenInputStream; @@ -555,24 +577,27 @@ Type Property OnProgressEx : TProgressEventEx Read FOnProgressEx Write FOnProgressEx; Property OnStartFile : TOnStartFileEvent Read FOnStartFile Write FOnStartFile; Property OnEndFile : TOnEndOfFileEvent Read FOnEndOfFile Write FOnEndOfFile; - Property FileName : String Read FFileName Write SetFileName; - Property OutputPath : String Read FOutputPath Write SetOutputPath; + Property FileName : RawByteString Read FFileName Write SetFileName; + Property OutputPath : RawByteString Read FOutputPath Write SetOutputPath; Property FileComment: String Read FFileComment; Property Files : TStrings Read FFiles; Property Entries : TFullZipFileEntries Read FEntries; - Property NeedToBreak: Boolean Read FNeedToBreak Write FNeedToBreak; + Property UseUTF8 : Boolean Read FUseUTF8 Write FUseUTF8; + Property Terminated : Boolean Read FTerminated; end; EZipError = Class(Exception); Implementation +uses rtlconsts; + ResourceString SErrBufsizeChange = 'Changing buffer size is not allowed while (un)zipping.'; SErrFileChange = 'Changing output file name is not allowed while (un)zipping.'; SErrInvalidCRC = 'Invalid CRC checksum while unzipping %s.'; SErrCorruptZIP = 'Corrupt ZIP file %s.'; - SErrUnsupportedCompressionFormat = 'Unsupported compression format %d.'; + SErrUnsupportedCompressionFormat = 'Unsupported compression format %d'; SErrUnsupportedMultipleDisksCD = 'A central directory split over multiple disks is unsupported.'; SErrMaxEntries = 'Encountered %d file entries; maximum supported is %d.'; SErrMissingFileName = 'Missing filename in entry %d.'; @@ -581,10 +606,56 @@ ResourceString SErrPosTooLarge = 'Position/offset %d is larger than maximum supported %d.'; SErrNoFileName = 'No archive filename for examine operation.'; SErrNoStream = 'No stream is opened.'; + SErrEncryptionNotSupported = 'Cannot unzip item "%s" : encryption is not supported.'; + SErrPatchSetNotSupported = 'Cannot unzip item "%s" : Patch sets are not supported.'; { --------------------------------------------------------------------- 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} function SwapLFH(const Values: Local_File_Header_Type): Local_File_Header_Type; @@ -773,6 +844,17 @@ begin Result := Result or UNIX_FILE; end; +function CRC32Str(const s:string):DWord; +var + i:Integer; +begin + Result:=$FFFFFFFF; + if Length(S)>0 then + for i:=1 to Length(s) do + Result:=Crc_32_Tab[Byte(Result XOR LongInt(s[i]))] XOR ((Result SHR 8) AND $00FFFFFF); + Result:=not Result; +end; + { --------------------------------------------------------------------- TDeCompressor ---------------------------------------------------------------------} @@ -781,7 +863,7 @@ end; Procedure TDeCompressor.UpdC32(Octet: Byte); Begin - FCrc32Val := Crc_32_Tab[Byte(FCrc32Val {%H-}XOR LongInt(Octet))] XOR ((FCrc32Val SHR 8) AND $00FFFFFF); + FCrc32Val := Crc_32_Tab[Byte(FCrc32Val XOR LongInt(Octet))] XOR ((FCrc32Val SHR 8) AND $00FFFFFF); end; constructor TDeCompressor.Create(AInFile, AOutFile: TStream; ABufSize: LongWord); @@ -792,6 +874,11 @@ begin CRC32Val:=$FFFFFFFF; end; +procedure TDeCompressor.Terminate; +begin + FTerminated:=True; +end; + { --------------------------------------------------------------------- TCompressor @@ -801,7 +888,7 @@ end; Procedure TCompressor.UpdC32(Octet: Byte); Begin - FCrc32Val := Crc_32_Tab[Byte(FCrc32Val {%H-}XOR LongInt(Octet))] XOR ((FCrc32Val SHR 8) AND $00FFFFFF); + FCrc32Val := Crc_32_Tab[Byte(FCrc32Val XOR LongInt(Octet))] XOR ((FCrc32Val SHR 8) AND $00FFFFFF); end; constructor TCompressor.Create(AInFile, AOutFile: TStream; ABufSize: LongWord); @@ -812,6 +899,11 @@ begin CRC32Val:=$FFFFFFFF; end; +procedure TCompressor.Terminate; +begin + FTerminated:=True; +end; + { --------------------------------------------------------------------- TDeflater @@ -861,7 +953,7 @@ begin FOnProgress(self,100 * ( BytesNow / FSize)); inc(NextMark,OnBytes); end; - Until (Count=0); + Until (Count=0) or Terminated; Finally C.Free; end; @@ -915,6 +1007,7 @@ Var NextMark : Integer; OnBytes : Integer; FSize : Integer; + begin CRC32Val:=$FFFFFFFF; if FOnPercent = 0 then @@ -925,12 +1018,11 @@ begin If Assigned(FOnProgress) then fOnProgress(self,0); + Buf:=GetMem(FBufferSize); Try C:=TDeCompressionStream.Create(FInFile,True); Try - if assigned(FOnProgress) then - fOnProgress(self,0); Repeat Count:=C.Read(Buf^,FBufferSize); For I:=0 to Count-1 do @@ -945,9 +1037,7 @@ begin FOnProgressEx(Self, FTotPos + BytesNow, FTotSize); inc(NextMark,OnBytes); end; - if FNeedToBreak then - Break; - Until (Count=0); + Until (Count=0) or Terminated; FTotPos := FTotPos + FOutFile.Size; Finally C.Free; @@ -1001,7 +1091,7 @@ begin inherited Destroy; end; -procedure TShrinker.Compress; +Procedure TShrinker.Compress; Var OneString : String; @@ -1049,13 +1139,16 @@ begin Result:=0; end; -procedure TShrinker.DoOnProgress(const Pct: Double); + +Procedure TShrinker.DoOnProgress(Const Pct: Double); + begin If Assigned(FOnProgress) then FOnProgress(Self,Pct); end; -procedure TShrinker.FillInputBuffer; + +Procedure TShrinker.FillInputBuffer; Begin MaxInbufIDx:=FInfile.Read(InBuf[0], FBufSize); @@ -1067,14 +1160,14 @@ Begin end; -procedure TShrinker.WriteOutputBuffer; +Procedure TShrinker.WriteOutputBuffer; Begin FOutFile.WriteBuffer(OutBuf[0], OutBufIdx); OutBufIdx := 0; end; -procedure TShrinker.PutChar(B: Byte); +Procedure TShrinker.PutChar(B : Byte); Begin OutBuf[OutBufIdx] := B; @@ -1084,7 +1177,7 @@ Begin Inc(BytesOut); end; -procedure TShrinker.FlushOutput; +Procedure TShrinker.FlushOutput; Begin If OutBufIdx>0 then WriteOutputBuffer; @@ -1120,7 +1213,7 @@ begin end; -procedure TShrinker.InitializeCodeTable; +Procedure TShrinker.InitializeCodeTable; Var I : Word; @@ -1142,7 +1235,7 @@ Begin end; -procedure TShrinker.Prune(Parent: Word); +Procedure TShrinker.Prune(Parent : Word); Var CurrChild : Smallint; @@ -1183,7 +1276,7 @@ Begin end; -procedure TShrinker.Clear_Table; +Procedure TShrinker.Clear_Table; Var Node : Word; Begin @@ -1204,7 +1297,7 @@ Begin end; -procedure TShrinker.Table_Add(Prefix: Word; Suffix: Byte); +Procedure TShrinker.Table_Add(Prefix : Word; Suffix : Byte); Var FreeNode : Word; Begin @@ -1229,8 +1322,9 @@ Begin TableFull := TRUE; end; -function TShrinker.Table_Lookup(TargetPrefix: Smallint; TargetSuffix: Byte; out - FoundAt: Smallint): Boolean; +function TShrinker.Table_Lookup( TargetPrefix : Smallint; + TargetSuffix : Byte; + Out FoundAt : Smallint ) : Boolean; var TempPrefix : Smallint; @@ -1257,7 +1351,7 @@ begin FoundAt := -1; end; -procedure TShrinker.Shrink(Suffix: Smallint); +Procedure TShrinker.Shrink(Suffix : Smallint); Const LastCode : Smallint = 0; @@ -1318,7 +1412,7 @@ Begin end; end; -procedure TShrinker.ProcessLine(const Source: String); +Procedure TShrinker.ProcessLine(Const Source : String); Var I : Word; @@ -1434,7 +1528,7 @@ Begin With LocalHdr do begin Signature := LOCAL_FILE_HEADER_SIGNATURE; - Extract_Version_Reqd := 10; //default value, v1.0 + Extract_Version_Reqd := 20; //default value, v2.0 Bit_Flag := 0; Compress_Method := 1; DateTimeToZipDateTime(Item.DateTime,Last_Mod_Date,Last_Mod_Time); @@ -1524,12 +1618,13 @@ Begin LocalHdr.Extra_Field_Length:=SizeOf(LocalZip64ExtHdr)+SizeOf(LocalZip64Fld); FOutStream.WriteBuffer({$IFDEF ENDIAN_BIG}SwapLFH{$ENDIF}(LocalHdr),SizeOf(LocalHdr)); // Append extensible field header+zip64 extensible field if needed: + FOutStream.WriteBuffer(ZFileName[1],Length(ZFileName)); if IsZip64 then begin + LocalZip64ExtHdr.Header_ID:=ZIP64_HEADER_ID; FOutStream.WriteBuffer({$IFDEF ENDIAN_BIG}SwapEDFH{$ENDIF}(LocalZip64ExtHdr),SizeOf(LocalZip64ExtHdr)); FOutStream.WriteBuffer({$IFDEF ENDIAN_BIG}SwapZ64EIF{$ENDIF}(LocalZip64Fld),SizeOf(LocalZip64Fld)); end; - FOutStream.WriteBuffer(ZFileName[1],Length(ZFileName)); End; @@ -1563,12 +1658,11 @@ Begin FillChar(LocalZip64Fld,SizeOf(LocalZip64Fld),0); // easier to check compressed length if LocalHdr.Extra_Field_Length>0 then begin - SavePos := FOutStream.Position; if (IsZip64 and (LocalHdr.Extra_Field_Length>=SizeOf(LocalZip64ExtHdr)+SizeOf(LocalZip64Fld))) then while FOutStream.Position0 then + Inc(I); + end; + if (FEntries.Count>0) and not Terminated then BuildZipDirectory; finally FZipping:=False; @@ -1854,7 +1952,7 @@ begin FBufSize:=Value; end; -Procedure TZipper.SetFileName(Value : String); +Procedure TZipper.SetFileName(Value : RawByteString); begin If FZipping then @@ -1862,7 +1960,7 @@ begin FFileName:=Value; end; -Procedure TZipper.ZipFiles(AFileName : String; FileList : TStrings); +Procedure TZipper.ZipFiles(AFileName : RawByteString; FileList : TStrings); begin FFileName:=AFileName; @@ -1875,7 +1973,7 @@ begin ZipAllFiles; end; -procedure TZipper.ZipFiles(AFileName: String; Entries: TZipFileEntries); +procedure TZipper.ZipFiles(AFileName: RawByteString; Entries: TZipFileEntries); begin FFileName:=AFileName; ZipFiles(Entries); @@ -1954,6 +2052,13 @@ begin FFiles.Clear; end; +procedure TZipper.Terminate; +begin + FTerminated:=True; + if Assigned(FCurrentCompressor) then + FCurrentCompressor.Terminate; +end; + Destructor TZipper.Destroy; begin @@ -1963,7 +2068,6 @@ begin Inherited; end; - { --------------------------------------------------------------------- TUnZipper ---------------------------------------------------------------------} @@ -1978,11 +2082,12 @@ Begin End; -function TUnZipper.OpenOutput(OutFileName: String; var OutStream: TStream; - Item: TFullZipFileEntry): Boolean; +function TUnZipper.OpenOutput(OutFileName: RawByteString; + out OutStream: TStream; Item: TFullZipFileEntry): Boolean; Var - Path: String; + Path: RawByteString; OldDirectorySeparators: set of char; + Begin { the default RTL behavior is broken on Unix platforms for Windows compatibility: it allows both '/' and '\' @@ -2017,6 +2122,7 @@ Begin ForceDirectories(Path); AllowDirectorySeparators:=OldDirectorySeparators; OutStream:=TFileStream.Create(OutFileName,fmCreate); + end; AllowDirectorySeparators:=OldDirectorySeparators; @@ -2053,9 +2159,13 @@ end; procedure TUnZipper.ReadZipHeader(Item: TFullZipFileEntry; out AMethod: Word); Var S : String; + U : UTF8String; D : TDateTime; ExtraFieldHdr: Extensible_Data_Field_Header_Type; SavePos: int64; //could be qword but limited by stream + // Infozip unicode path + Infozip_Unicode_Path_Ver:Byte; + Infozip_Unicode_Path_CRC32:DWord; Begin FZipStream.Seek(Item.HdrPos,soBeginning); FZipStream.ReadBuffer(LocalHdr,SizeOf(LocalHdr)); @@ -2065,6 +2175,7 @@ Begin FillChar(LocalZip64Fld,SizeOf(LocalZip64Fld),0); //ensure no erroneous info With LocalHdr do begin + Item.FBitFlags:=Bit_Flag; SetLength(S,Filename_Length); FZipStream.ReadBuffer(S[1],Filename_Length); Item.ArchiveFileName:=S; @@ -2073,10 +2184,10 @@ Begin if Extra_Field_Length>0 then begin SavePos := FZipStream.Position; - if (LocalHdr.Extra_Field_Length>=SizeOf(ExtraFieldHdr)+SizeOf(LocalZip64Fld)) then + if (LocalHdr.Extra_Field_Length>=SizeOf(ExtraFieldHdr)) then while FZipStream.Position0) then + // 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 - if LocalZip64Fld.Compressed_Size>0 then - Count:=Dest.CopyFrom(FZipStream,LocalZip64Fld.Compressed_Size) - else - Count:=Dest.CopyFrom(FZipStream,LocalHdr.Compressed_Size); - end - else - Count:=0; - end - else - With CreateDecompressor(Item, ZMethod, FZipStream, Dest) do - Try - FTotPos := Self.FTotPos; - FTotSize := Self.FTotSize; - OnProgress:=Self.OnProgress; - OnProgressEx := Self.OnProgressEx; - OnPercent:=Self.OnPercent; - DeCompress; - Self.FTotPos := FTotPos; - if Item.CRC32 <> Crc32Val then - raise EZipError.CreateFmt(SErrInvalidCRC,[Item.ArchiveFileName]); - Finally - Free; + {$IFDEF UNIX} + FpChmod(OutputFileName, Attrs); + {$ELSE} + FileSetAttr(OutputFileName, Attrs); + {$ENDIF} + 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 + FTotPos := Self.FTotPos; + FTotSize := Self.FTotSize; + OnProgress:=Self.OnProgress; + OnProgressEx := Self.OnProgressEx; + OnPercent:=Self.OnPercent; + OnProgress:=Self.OnProgress; + OnPercent:=Self.OnPercent; + DeCompress; + Self.FTotPos := FTotPos; + if Item.CRC32 <> Crc32Val then + raise EZipError.CreateFmt(SErrInvalidCRC,[Item.ArchiveFileName]); + Finally + FCurrentDecompressor:=Nil; + 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 ReadZipHeader(Item, ZMethod); + if (Item.BitFlags and 1)<>0 then + Raise EZipError.CreateFmt(SErrEncryptionNotSupported,[Item.ArchiveFileName]); + if (Item.BitFlags and (1 shl 5))<>0 then + Raise EZipError.CreateFmt(SErrPatchSetNotSupported,[Item.ArchiveFileName]); // Normalize output filename to conventions of target platform. // Zip file always has / path separators - OutputFileName:=StringReplace(Item.DiskFileName,'/',DirectorySeparator,[rfReplaceAll]); - IsCustomStream := Assigned(FOnCreateStream); - - if (IsCustomStream = False) and (FOutputPath<>'') then - OutputFileName:=IncludeTrailingPathDelimiter(FOutputPath)+OutputFileName; - + GetOutputFileName; IsLink := Item.IsLink; - {$IFNDEF UNIX} if IsLink and Not IsCustomStream then - begin + begin + {$warning TODO: Implement symbolic link creation for non-unix, e.g. + Windows NTFS} IsLink := False; - end; + end; {$ENDIF} - if IsCustomStream then - begin + begin try - OpenOutput(OutputFileName, {%H-}FOutStream, Item); + OpenOutput(OutputFileName, FOutStream, Item); if (IsLink = False) and (Item.IsDirectory = False) then DoUnzip(FOutStream); Finally CloseOutput(Item, FOutStream); 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 - else + else begin - if Item.IsDirectory then - CreateDir(OutputFileName) - else + if IsLink then begin + {$IFDEF UNIX} + LinkTargetStream := TStringStream.Create(''); try - OpenOutput(OutputFileName, FOutStream, Item); - DoUnzip(FOutStream); - Finally - CloseOutput(Item, FOutStream); + DoUnzip(LinkTargetStream); + fpSymlink(PChar(LinkTargetStream.DataString), PChar(OutputFileName)); + finally + LinkTargetStream.Free; end; - end; - end; - end; - - if Not IsCustomStream then - 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 + {$ENDIF} + end + else if Item.IsDirectory then + CreateDir(OutputFileName) + else begin - {$IFDEF UNIX} - FpChmod(OutputFileName, Attrs); - {$ELSE} - FileSetAttr(OutputFileName, Attrs); - {$ENDIF} + try + OpenOutput(OutputFileName, FOutStream, Item); + DoUnzip(FOutStream); + Finally + CloseOutput(Item, FOutStream); end; + end; + SetAttributes; end; - end; end; +Function TUnZipper.IsMatch(I : TFullZipFileEntry) : Boolean; + +begin + if UseUTF8 then + Result:=(FFiles.IndexOf(I.UTF8ArchiveFileName)<>-1) + else + Result:=(FFiles.IndexOf(I.ArchiveFileName)<>-1) +end; + +Function TUnZipper.CalcTotalSize(AllFiles : Boolean) : Int64; + +Var + I : Integer; + Item : TFullZipFileEntry; + +begin + Result:=0; + for i:=0 to FEntries.Count-1 do + begin + Item := FEntries[i]; + if AllFiles or IsMatch(Item) then + Result := Result + TZipFileEntry(Item).Size; + end; +end; procedure TUnZipper.UnZipAllFiles; + + Var Item : TFullZipFileEntry; I : integer; //Really QWord but limited to FEntries.Count AllFiles : Boolean; Begin + FTerminated:=False; FUnZipping:=True; Try AllFiles:=(FFiles.Count=0); @@ -2515,23 +2735,17 @@ Begin Try ReadZipDirectory; FTotPos := 0; - FTotSize := 0; - for i:=0 to FEntries.Count-1 do - begin - Item := FEntries[i]; - if AllFiles or (FFiles.IndexOf(Item.ArchiveFileName)<>-1) then - FTotSize := FTotSize + TZipFileEntry(Item).Size; - end; - for i:=0 to FEntries.Count-1 do - begin + FTotSize := CalcTotalSize(AllFiles); + i:=0; + While (I-1) then + if AllFiles or IsMatch(Item) then UnZipOneFile(Item); - if FNeedToBreak then - Break; - end; - if Assigned(FOnProgressEx) then - FOnProgressEx(Self, FTotSize, FTotSize); + inc(I); + end; + if Assigned(FOnProgressEx) and not Terminated then + FOnProgressEx(Self, FTotPos, FTotSize); Finally CloseInput; end; @@ -2540,6 +2754,7 @@ Begin end; end; + procedure TUnZipper.SetBufSize(Value: LongWord); begin @@ -2549,7 +2764,7 @@ begin FBufSize:=Value; end; -procedure TUnZipper.SetFileName(Value: String); +procedure TUnZipper.SetFileName(Value: RawByteString); begin If FUnZipping then @@ -2557,14 +2772,25 @@ begin FFileName:=Value; end; -procedure TUnZipper.SetOutputPath(Value: String); +procedure TUnZipper.SetOutputPath(Value: RawByteString); + +Var + DS : RawByteString; + begin If FUnZipping then Raise EZipError.Create(SErrFileChange); 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; -procedure TUnZipper.UnZipFiles(AFileName: String; FileList: TStrings); +procedure TUnZipper.UnZipFiles(AFileName: RawByteString; FileList: TStrings); begin FFileName:=AFileName; @@ -2577,7 +2803,7 @@ begin UnZipAllFiles; end; -procedure TUnZipper.UnZipAllFiles(AFileName: String); +procedure TUnZipper.UnZipAllFiles(AFileName: RawByteString); begin FFileName:=AFileName; @@ -2643,44 +2869,11 @@ begin end; end; -function TUnZipper.GetZipSize(var IsDirZipped: Boolean; var ABaseDir: String): Int64; -var - I: Integer; - Item: TFullZipFileEntry; - AllFiles: Boolean; - P: Integer; +procedure TUnZipper.Terminate; begin - AllFiles := (FFiles.Count = 0); - OpenInput; - try - ReadZipDirectory; - Result := 0; - if FEntries.Count > 0 then - begin - P := Pos('/', TZipFileEntry(FEntries.Items[0]).ArchiveFileName); - if P = 0 then - P := Pos('\', TZipFileEntry(FEntries.Items[0]).ArchiveFileName); - if P <> 0 then - ABaseDir := Copy(TZipFileEntry(FEntries.Items[0]).ArchiveFileName, 1, P); - end; - for i:=0 to FEntries.Count-1 do - begin - Item := FEntries[i]; - if AllFiles or (FFiles.IndexOf(Item.ArchiveFileName)<>-1) then - begin - Result := Result + TZipFileEntry(Item).Size; - if IsDirZipped then - if Pos(ABaseDir, Item.ArchiveFileName) = 0 then - IsDirZipped := False; - end; - end; - if not IsDirZipped then - ABaseDir := '' - else - ABaseDir := Copy(ABaseDir, 1, Length(ABaseDir) - 1); - finally - CloseInput; - end; + FTerminated:=True; + if Assigned(FCurrentDecompressor) then + FCurrentDecompressor.Terminate; end; destructor TUnZipper.Destroy; @@ -2701,6 +2894,20 @@ begin Result:=FDiskFileName; end; +function TZipFileEntry.GetUTF8ArchiveFileName: UTF8String; +begin + Result:=FUTF8FileName; + If Result='' then + Result:=ArchiveFileName; +end; + +function TZipFileEntry.GetUTF8DiskFileName: UTF8String; +begin + Result:=FUTF8DiskFileName; + If Result='' then + Result:=DiskFileName; +end; + constructor TZipFileEntry.Create(ACollection: TCollection); begin @@ -2742,11 +2949,12 @@ begin end; procedure TZipFileEntry.SetArchiveFileName(const AValue: String); + begin if FArchiveFileName=AValue then Exit; // Zip standard: filenames inside the zip archive have / path separator if DirectorySeparator='/' then - {%H-}FArchiveFileName:=AValue + FArchiveFileName:=AValue else FArchiveFileName:=StringReplace(AValue, DirectorySeparator, '/', [rfReplaceAll]); end; @@ -2757,11 +2965,31 @@ begin // Zip file uses / as directory separator on all platforms // so convert to separator used on current OS if DirectorySeparator='/' then - {%H-}FDiskFileName:=AValue + FDiskFileName:=AValue else FDiskFileName:=StringReplace(AValue,'/',DirectorySeparator,[rfReplaceAll]); end; +procedure TZipFileEntry.SetUTF8ArchiveFileName(AValue: UTF8String); +begin + FUTF8FileName:=AValue; + If ArchiveFileName='' then + if DefaultSystemCodePage<>CP_UTF8 then + ArchiveFileName:=Utf8ToAnsi(AValue) + else + ArchiveFileName:=AValue; +end; + +procedure TZipFileEntry.SetUTF8DiskFileName(AValue: UTF8String); +begin + FUTF8DiskFileName:=AValue; + If DiskFileName='' then + if DefaultRTLFileSystemCodePage<>CP_UTF8 then + DiskFileName:=Utf8ToAnsi(AValue) + else + DiskFileName:=AValue; +end; + procedure TZipFileEntry.Assign(Source: TPersistent); @@ -2818,8 +3046,10 @@ begin end; Procedure TZipFileEntries.AddFileEntries(Const List : TStrings); + Var I : integer; + begin For I:=0 to List.Count-1 do AddFileEntry(List[i]); diff --git a/components/onlinepackagemanager/opkman_downloader.pas b/components/onlinepackagemanager/opkman_downloader.pas index 7022ee57b8..14e44c7008 100644 --- a/components/onlinepackagemanager/opkman_downloader.pas +++ b/components/onlinepackagemanager/opkman_downloader.pas @@ -26,11 +26,14 @@ unit opkman_downloader; {$mode objfpc}{$H+} +{$INCLUDE opkman_fpcdef.inc} + interface uses - Classes, SysUtils, fpjson, opkman_httpclient, opkman_timer, opkman_common, - opkman_serializablepackages; + Classes, SysUtils, fpjson, opkman_timer, opkman_common, opkman_serializablepackages, + {$IFDEF FPC311}fphttpclient{$ELSE}opkman_httpclient{$ENDIF}; + type TDownloadType = (dtJSON, dtPackage, dtUpdate); @@ -270,7 +273,7 @@ procedure TThreadDownload.DoOnTimer(Sender: TObject); begin if FDownloadType = dtJSON then begin - FHTTPClient.NeedToBreak := True; + FHTTPClient.Terminate; FErrMsg := rsMainFrm_rsMessageError2; FErrTyp := etTimeOut; FTimer.StopTimer; @@ -679,7 +682,7 @@ procedure TPackageDownloader.CancelDownloadPackages; begin if Assigned(FDownload) then begin - FDownload.FHTTPClient.NeedToBreak := True; + FDownload.FHTTPClient.Terminate; FDownload.FTimer.StopTimer; FDownload.NeedToBreak := True; end; @@ -700,7 +703,7 @@ procedure TPackageDownloader.CancelUpdatePackages; begin if Assigned(FDownload) then begin - FDownload.FHTTPClient.NeedToBreak := True; + FDownload.FHTTPClient.Terminate; FDownload.FTimer.StopTimer; FDownload.NeedToBreak := True; end; diff --git a/components/onlinepackagemanager/opkman_fpcdef.inc b/components/onlinepackagemanager/opkman_fpcdef.inc new file mode 100644 index 0000000000..b044d443d7 --- /dev/null +++ b/components/onlinepackagemanager/opkman_fpcdef.inc @@ -0,0 +1,7 @@ +{$IF FPC_VERSION = 3} + {$IF FPC_RELEASE > 0} + {$IF FPC_PATCH > 0} + {$DEFINE FPC311} + {$ENDIF} + {$ENDIF} +{$ENDIF} \ No newline at end of file diff --git a/components/onlinepackagemanager/opkman_updates.pas b/components/onlinepackagemanager/opkman_updates.pas index 8199d453d0..ef549f543f 100644 --- a/components/onlinepackagemanager/opkman_updates.pas +++ b/components/onlinepackagemanager/opkman_updates.pas @@ -25,11 +25,14 @@ unit opkman_updates; {$mode objfpc}{$H+} +{$INCLUDE opkman_fpcdef.inc} + interface uses Classes, SysUtils, LazIDEIntf, Laz2_XMLCfg, LazFileUtils, fpjson, fpjsonrtti, - opkman_httpclient, opkman_timer, opkman_serializablepackages, dateutils; + opkman_timer, opkman_serializablepackages, dateutils, + {$IFDEF FPC311}fphttpclient{$ELSE}opkman_httpclient{$ENDIF}; const OpkVersion = 1; @@ -128,7 +131,8 @@ var implementation -uses opkman_options, opkman_common, opkman_const, opkman_zip; +uses opkman_options, opkman_common, opkman_const, + {$IFDEF FPC311}zipper{$ELSE}opkman_zip{$ENDIF}; { TUpdatePackage } @@ -527,7 +531,7 @@ begin ResetPackageData(SerializablePackages.Items[I]); end else - FHTTPClient.NeedToBreak := True; + FHTTPClient.Terminate; end; if Assigned(FOnUpdate) and (not FNeedToBreak) and (not FPaused) then Synchronize(@DoOnUpdate); @@ -561,7 +565,7 @@ begin Save; FTimer.StopTimer; FStarted := False; - FHTTPClient.NeedToBreak := True; + FHTTPClient.Terminate; end; procedure TUpdates.PauseUpdate; diff --git a/components/onlinepackagemanager/opkman_uploader.pas b/components/onlinepackagemanager/opkman_uploader.pas index 40142d6333..a2d1e348af 100644 --- a/components/onlinepackagemanager/opkman_uploader.pas +++ b/components/onlinepackagemanager/opkman_uploader.pas @@ -26,12 +26,15 @@ unit opkman_uploader; {$mode objfpc}{$H+} +{$INCLUDE opkman_fpcdef.inc} + interface uses - Classes, SysUtils, fpjson, base64, LazFileUtils, opkman_httpclient, + Classes, SysUtils, fpjson, base64, LazFileUtils, + {$IFDEF FPC311}fphttpclient{$ELSE}opkman_httpclient{$ENDIF}; + - dialogs; type TOnUploadProgress = procedure(Sender: TObject; AFileName: String) of object; @@ -210,7 +213,7 @@ end; procedure TUploader.StopUpload; begin if Assigned(FHTTPClient) then - FHTTPClient.NeedToBreak := True; + FHTTPClient.Terminate; FNeedToBreak := True; end; diff --git a/components/onlinepackagemanager/opkman_zipper.pas b/components/onlinepackagemanager/opkman_zipper.pas index 000a1e6c0b..3e2f43cc6b 100644 --- a/components/onlinepackagemanager/opkman_zipper.pas +++ b/components/onlinepackagemanager/opkman_zipper.pas @@ -26,10 +26,13 @@ unit opkman_zipper; {$mode objfpc}{$H+} +{$INCLUDE opkman_fpcdef.inc} + interface uses - Classes, SysUtils, FileUtil, LazFileUtils, strutils, opkman_timer, opkman_zip; + Classes, SysUtils, FileUtil, LazFileUtils, strutils, opkman_timer, + {$IFDEF FPC311}zipper{$ELSE}opkman_zip{$ENDIF}; type TOnProgressEx = procedure(Sender : TObject; const ATotPos, ATotSize: Int64); @@ -69,6 +72,7 @@ type procedure DoOnZipProgress; procedure DoOnZipError; procedure DoOnZipCompleted; + function GetZipSize(var AIsDirZipped: Boolean; var ABaseDir: String): Int64; protected procedure Execute; override; public @@ -226,6 +230,42 @@ begin Sleep(5); end; +function TPackageUnzipper.GetZipSize(var AIsDirZipped: Boolean; var ABaseDir: String): Int64; +var + I: Integer; + Item: TFullZipFileEntry; + AllFiles: Boolean; + P: Integer; +begin + FUnZipper.Examine; + AllFiles := (FUnZipper.Files.Count = 0); + Result := 0; + if FUnZipper.Entries.Count > 0 then + begin + P := Pos('/', TZipFileEntry(FUnZipper.Entries.Items[0]).ArchiveFileName); + if P = 0 then + P := Pos('\', TZipFileEntry(FUnZipper.Entries.Items[0]).ArchiveFileName); + if P <> 0 then + ABaseDir := Copy(TZipFileEntry(FUnZipper.Entries.Items[0]).ArchiveFileName, 1, P); + end; + for I := 0 to FUnZipper.Entries.Count-1 do + begin + Item := FUnZipper.Entries[i]; + if AllFiles or (FUnZipper.Files.IndexOf(Item.ArchiveFileName)<>-1) then + begin + Result := Result + TZipFileEntry(Item).Size; + if AIsDirZipped then + if Pos(ABaseDir, Item.ArchiveFileName) = 0 then + AIsDirZipped := False; + end; + end; + if not AIsDirZipped then + ABaseDir := '' + else + ABaseDir := Copy(ABaseDir, 1, Length(ABaseDir) - 1); +end; + + procedure TPackageUnzipper.StartUnZip(const ASrcDir, ADstDir: String; const AIsUpdate: Boolean); var @@ -250,7 +290,7 @@ begin FUnZipper.Examine; IsDirZipped := True; BaseDir := ''; - FTotSize := FTotSize + FUnZipper.GetZipSize(IsDirZipped, BaseDir); + FTotSize := FTotSize + GetZipSize(IsDirZipped, BaseDir); SerializablePackages.Items[I].IsDirZipped := IsDirZipped; if BaseDir <> '' then BaseDir := AppendPathDelim(BaseDir); @@ -276,7 +316,7 @@ end; procedure TPackageUnzipper.StopUnZip; begin if Assigned(FUnZipper) then - FUnZipper.NeedToBreak := True; + FUnZipper.Terminate; if Assigned(FTimer) then FTimer.StopTimer; FNeedToBreak := True; @@ -373,7 +413,7 @@ end; procedure TPackageZipper.StopZip; begin if Assigned(FZipper) then - FZipper.NeedToBreak := True; + FZipper.Terminate; FNeedToBreak := True; FStarted := False; end;