You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1337 lines
42 KiB
Plaintext
1337 lines
42 KiB
Plaintext
unit RnQNet;
|
|
{$I forRnQConfig.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
System.Generics.Collections, System.Types, System.SysUtils, System.NetConsts, System.SyncObjs, Vcl.ExtCtrls, Classes, Forms,
|
|
JSON, NetEncoding, RDGlobal, System.Net.HttpClient, System.Net.URLClient;
|
|
|
|
{$I NoRTTI.inc}
|
|
|
|
type
|
|
TProxyProto = (PP_NONE = 0, PP_HTTPS = 1, PP_SYSTEM = 2);
|
|
|
|
THostPort = record
|
|
host: String;
|
|
port: Integer;
|
|
end;
|
|
|
|
PProxy = ^TProxy;
|
|
TProxy = record
|
|
name: String;
|
|
user: String;
|
|
pwd: String;
|
|
auth: Boolean;
|
|
NTLM: Boolean;
|
|
proto: TProxyProto;
|
|
addr: THostPort;
|
|
end;
|
|
|
|
TLinkInfo = record
|
|
code: Integer;
|
|
size: Int64;
|
|
mime: String;
|
|
redirects: Integer;
|
|
end;
|
|
|
|
TICQFileInfo = record
|
|
jsonlink: String;
|
|
dlink: String;
|
|
mime: String;
|
|
filename: String;
|
|
is_previewable: Boolean;
|
|
recognized: Boolean;
|
|
preview: String;
|
|
avstatus: String;
|
|
filesize: Int64;
|
|
end;
|
|
|
|
TProxies = TArray |
|
|
|
const
|
|
ProxyProto2Str: array [TProxyProto] of String = ('NONE', 'HTTPS', 'SYSTEM');
|
|
|
|
procedure CopyProxy(var pTo: Tproxy; const pFrom: Tproxy);
|
|
procedure ClearProxy(var p1: Tproxy);
|
|
procedure CopyProxyArr(var pATo: TProxies; const pAFrom: TProxies);
|
|
procedure ClearProxyArr(var pa: TProxies);
|
|
|
|
type
|
|
TDataReceived = procedure(Sender: TObject; ErrCode: Word; pkt: RawByteString) of object;
|
|
TProxyLogData = procedure(Sender: TObject; isReceive: Boolean; Data: RawByteString) of object;
|
|
TRequestDone = reference to procedure(Sender: TObject; const Response: IHTTPResponse; const Error: String = '');
|
|
|
|
THttpRequestType = (HTRT_TEXT, HTRT_FILE, HTRT_STREAM);
|
|
|
|
THttpRequestParams = class
|
|
public
|
|
RequestType: THttpRequestType;
|
|
URL: String;
|
|
Threshold: LongInt;
|
|
FileName: String;
|
|
Mime: String;
|
|
ResponseText: String;
|
|
ResponseStream: TMemoryStream;
|
|
POSTData: RawByteString;
|
|
ExtByContent: Boolean;
|
|
ShowErrors: Boolean;
|
|
|
|
constructor Create(ReqType: THttpRequestType);
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
THttpAsync = class
|
|
private
|
|
FClient: THttpClient;
|
|
FHandle: THandle;
|
|
FTimeout: TTimer;
|
|
FQuiet: Boolean;
|
|
FOnRequestDone: TRequestDone;
|
|
FParentThread: TThread;
|
|
FAsyncCallback: TAsyncCallback;
|
|
FHTTPRequest: IHTTPRequest;
|
|
FSendStream: TMemoryStream;
|
|
public
|
|
URL: String;
|
|
|
|
property Callback: TRequestDone read FOnRequestDone write FOnRequestDone;
|
|
property Client: THttpClient read FClient;
|
|
property HTTPRequest: IHTTPRequest read FHTTPRequest;
|
|
|
|
constructor Create(Timeout: Integer = 30);
|
|
destructor Destroy; override;
|
|
function HeadAsync: IAsyncResult;
|
|
function GetAsync: IAsyncResult;
|
|
function PostAsync(const Data: RawByteString = ''): IAsyncResult; overload;
|
|
function PostAsync(Data: TStream): IAsyncResult; overload;
|
|
procedure Setup(const Link: String; Redirect: Boolean = True; RedirectCount: Integer = 5; const POSTData: RawByteString = '');
|
|
procedure SetQuiet;
|
|
procedure SetLongPoll;
|
|
procedure SetNativeTimeout(Timeout: Integer);
|
|
procedure SetTimeout(Timeout: Integer);
|
|
procedure StartTimeout;
|
|
procedure StopTimeout;
|
|
procedure TimedOut(Sender: TObject);
|
|
procedure Abort;
|
|
procedure OnRequestDoneTimed(const Response: IHTTPResponse; const Error: String = '');
|
|
end;
|
|
|
|
TCallbacks = class
|
|
private
|
|
FOnSendData: TSendDataEvent;
|
|
FOnSendDataCallback: TSendDataCallback;
|
|
public
|
|
Data: Pointer;
|
|
property OnSendData: TSendDataEvent read FOnSendData write FOnSendData;
|
|
property OnSendDataCallback: TSendDataCallback read FOnSendDataCallback write FOnSendDataCallback;
|
|
end;
|
|
|
|
const
|
|
ConnectionError = 'Connection error\n%s\n[%d] %s';
|
|
SSLError = 'SSL certificate error\n%s\n[%d] %s';
|
|
MaxRedirectError = 'Request is redirecting too many times\n%s\n[%d] %s';
|
|
FileTooBig = 'File is too big, max size %s MB';
|
|
AuthFailed = 'File hosting authentication failed';
|
|
InitFailed = 'Failed to get upload link from ICQ server';
|
|
UploadError = 'Failed to upload file! Server response';
|
|
|
|
ImageContentTypes: array [0 .. 27] of string = (
|
|
'image/bmp', 'image/x-bmp', 'image/x-bitmap', 'image/x-xbitmap', 'image/x-win-bitmap', 'image/x-windows-bmp', 'image/ms-bmp', 'image/x-ms-bmp', 'application/bmp', 'application/x-bmp', 'application/x-win-bitmap',
|
|
'image/jpeg', 'image/jpg', 'application/jpg', 'application/x-jpg',
|
|
'image/gif',
|
|
'image/png', 'application/png', 'application/x-png',
|
|
'image/ico', 'image/x-icon', 'application/ico', 'application/x-ico',
|
|
'image/tiff', 'image/x-tiff',
|
|
'image/webp',
|
|
'image/svg+xml',
|
|
'fake/lottie'
|
|
);
|
|
ImageExtensions: array [0 .. 27] of string = (
|
|
'bmp', 'bmp', 'bmp', 'bmp', 'bmp', 'bmp', 'bmp', 'bmp', 'bmp', 'bmp', 'bmp',
|
|
'jpg', 'jpg', 'jpg', 'jpg',
|
|
'gif',
|
|
'png', 'png', 'png',
|
|
'ico', 'ico', 'ico', 'ico',
|
|
'tiff', 'tiff',
|
|
'webp',
|
|
'svg',
|
|
'json'
|
|
);
|
|
function ContentTypeFromURL(const URL: String): String;
|
|
function InfoFromURL(const URL: String): TLinkInfo;
|
|
function UploadFileICQ(FileStream: TStream; const FileName: String; UploadCallbacks: TCallbacks): String;
|
|
function UploadFileMikanoshi(FileStream: TStream; const FileName: String; UploadCallbacks: TCallbacks): String;
|
|
function UploadFileRnQ(FileStream: TStream; const FileName: String; UploadCallbacks: TCallbacks): String;
|
|
function CreateZip(str: TStringList): TMemoryStream;
|
|
function GetICQFileLinkInfoPublic(const Link: String): TICQFileInfo;
|
|
function GetICQFileLinkInfoPrivate(const Link: String): TICQFileInfo;
|
|
function CheckType(const lnk: String): Boolean; overload;
|
|
function CheckType(const lnk: String; var sA: RawByteString; var ext: String): Boolean; overload;
|
|
function DownloadAndCache(const lnk: String): Boolean;
|
|
|
|
procedure SetupHttpClient(var HttpCli: THttpClient; Redirect: Boolean = True; RedirectCount: Integer = 5; const POSTData: RawByteString = '');
|
|
procedure HandleHTTPError(E: Exception; ErrorCode: Integer = 0; const ErrorText: String = ''; const URL: String = '');
|
|
function HandleStatus(Code: Integer): Boolean;
|
|
function HandleStatusAndError(Code: Integer; const Status: String; const URL: String; const ErrText: String = ''; Quiet: Boolean = True): Boolean;
|
|
|
|
function ValidateURL(const URL: String): String;
|
|
function DecodeURL(const url: String): String;
|
|
function LoadFromURL(const Params: THttpRequestParams): Boolean; overload;
|
|
function LoadFromURLAsString(const URL: String; var Response: String; const POSTData: RawByteString = ''; ShowErrors: Boolean = True): Boolean;
|
|
function LoadFromURLAsFile(const URL: String; var FileName: String; ExtByContent: Boolean = False): Boolean;
|
|
function LoadFromURLAsStream(const URL: String; var Response: TMemoryStream; ShowErrors: Boolean = True): Boolean;
|
|
procedure LoadFromURLAsync(const URL: String; Callback: TRequestDone; const POSTData: RawByteString = '');
|
|
|
|
var
|
|
MainProxy: TProxy;
|
|
AllProxies: TProxies;
|
|
IsUploading: Boolean;
|
|
UploadSize: Int64;
|
|
UploadedSize: Int64;
|
|
|
|
EnableVideoLinks: Boolean;
|
|
|
|
implementation
|
|
|
|
uses
|
|
Windows, StrUtils,
|
|
RDUtils, RnQZip, ZSTD, ZSTDLib,
|
|
{$IFDEF UNICODE}
|
|
AnsiStrings,
|
|
{$ENDIF UNICODE}
|
|
{$IFDEF RNQ}
|
|
RnQLangs, RQUtil, RQlog, utilLib, Protocol_ICQ, ICQCommon, ICQConsts, ICQSession, globalLib,
|
|
{$ENDIF RNQ}
|
|
RnQGraphics32;
|
|
|
|
const
|
|
Browser = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0';
|
|
|
|
Procedure CopyProxy(var pTo: Tproxy; const pFrom: Tproxy);
|
|
begin
|
|
// p1.enabled:= p2.enabled;
|
|
pTo.name := pFrom.name;
|
|
pTo.proto := pFrom.proto;
|
|
{ for pp:=low(pp) to high(pp) do
|
|
begin
|
|
proxy.addr[pp].host:=proxyes[lastProxy].addr[pp].host;
|
|
proxy.addr[pp].port:=proxyes[lastProxy].addr[pp].port;
|
|
end; }
|
|
pTo.addr.host := pFrom.addr.host;
|
|
pTo.addr.port := pFrom.addr.port;
|
|
pTo.user := pFrom.user;
|
|
pTo.pwd := pFrom.pwd;
|
|
pTo.auth := pFrom.auth;
|
|
pTo.NTLM := pFrom.NTLM;
|
|
end;
|
|
|
|
Procedure ClearProxy(var p1: Tproxy);
|
|
begin
|
|
p1.name := '';
|
|
p1.proto := PP_NONE;
|
|
{ for pp:=low(pp) to high(pp) do
|
|
begin
|
|
proxy.addr[pp].host:=proxyes[lastProxy].addr[pp].host;
|
|
proxy.addr[pp].port:=proxyes[lastProxy].addr[pp].port;
|
|
end; }
|
|
p1.addr.host := '';
|
|
p1.user := '';
|
|
p1.pwd := '';
|
|
end;
|
|
|
|
Procedure CopyProxyArr(var pATo: TProxies; const pAFrom: TProxies);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
ClearProxyArr(pATo);
|
|
SetLength(pATo, Length(pAFrom));
|
|
if Length(pAFrom) > 0 then
|
|
for I := Low(pAFrom) to High(pAFrom) do
|
|
// ClearProxy(pa[i]);
|
|
CopyProxy(pATo[I], pAFrom[I]);
|
|
// SetLength(pa, 0);
|
|
end;
|
|
|
|
procedure ClearProxyArr(var pa: TProxies);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
if Length(pa) > 0 then
|
|
begin
|
|
for I := Low(pa) to High(pa) do
|
|
ClearProxy(pa[I]);
|
|
SetLength(pa, 0);
|
|
end;
|
|
end;
|
|
|
|
function LocalizedErrorMessage(ErrorCode: Cardinal; AModuleHandle: THandle): String;
|
|
var
|
|
Buffer: PChar;
|
|
Lang, Len: Integer;
|
|
Flags: DWORD;
|
|
begin
|
|
Flags := FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_IGNORE_INSERTS or FORMAT_MESSAGE_ARGUMENT_ARRAY or FORMAT_MESSAGE_ALLOCATE_BUFFER;
|
|
if AModuleHandle <> 0 then
|
|
Flags := Flags or FORMAT_MESSAGE_FROM_HMODULE;
|
|
|
|
Lang := System.SysUtils.TLanguages.GetLocaleIDFromLocaleName(IfThen(IsRuLang, 'ru-RU', 'en-US'));
|
|
Len := FormatMessage(Flags, Pointer(AModuleHandle), ErrorCode, Lang, @Buffer, 0, nil);
|
|
|
|
try
|
|
while (Len > 0) and ((Buffer[Len - 1] <= #32) or (Buffer[Len - 1] = '.')) do
|
|
Dec(Len);
|
|
SetString(Result, Buffer, Len);
|
|
finally
|
|
LocalFree(HLOCAL(Buffer));
|
|
end;
|
|
end;
|
|
|
|
constructor THttpRequestParams.Create(ReqType: THttpRequestType);
|
|
begin
|
|
RequestType := ReqType;
|
|
URL := '';
|
|
Threshold := 0;
|
|
FileName := '';
|
|
ResponseText := '';
|
|
ResponseStream := nil;
|
|
ExtByContent := False;
|
|
POSTData := '';
|
|
ShowErrors := True;
|
|
end;
|
|
|
|
destructor THttpRequestParams.Destroy;
|
|
begin
|
|
inherited;
|
|
end;
|
|
|
|
constructor THttpAsync.Create(Timeout: Integer = 30);
|
|
begin
|
|
FClient := THttpClient.Create;
|
|
FHandle := GetModuleHandle('winhttp.dll');
|
|
FHTTPRequest := nil;
|
|
FSendStream := nil;
|
|
FOnRequestDone := nil;
|
|
FQuiet := False;
|
|
FParentThread := TThread.Current;
|
|
FAsyncCallback := procedure(const ASyncResult: IAsyncResult)
|
|
var
|
|
Response: IHTTPResponse;
|
|
ErrorCode: Integer;
|
|
ErrorText: String;
|
|
begin
|
|
Response := nil;
|
|
ErrorText := '';
|
|
try
|
|
Response := FClient.EndAsyncHTTP(ASyncResult);
|
|
except
|
|
on E: Exception do
|
|
begin
|
|
ErrorCode := GetLastError;
|
|
TThread.Synchronize(FParentThread, procedure()
|
|
begin
|
|
Response := nil;
|
|
ErrorText := LocalizedErrorMessage(ErrorCode, FHandle);
|
|
// Long poll callback will handle these errors
|
|
if Assigned(FClient) and (FClient.ConnectionTimeout = -1) then
|
|
begin
|
|
// Long poll timed out by itself, don't show error
|
|
if ErrorCode = 12002 then
|
|
ErrorText := ''
|
|
else
|
|
ErrorText := '[' + IntToStr(ErrorCode) + '] ' + ErrorText;
|
|
end else if not FQuiet then
|
|
HandleHTTPError(E, ErrorCode, ErrorText, URL);
|
|
end);
|
|
end;
|
|
end;
|
|
|
|
TThread.Synchronize(FParentThread, procedure()
|
|
begin
|
|
StopTimeout;
|
|
FreeAndNil(FSendStream);
|
|
OnRequestDoneTimed(Response, ErrorText);
|
|
end);
|
|
end;
|
|
|
|
if Timeout > 0 then
|
|
begin
|
|
FTimeout := TTimer.Create(nil);
|
|
FTimeout.Interval := Timeout * 1000;
|
|
FTimeout.OnTimer := TimedOut;
|
|
end else
|
|
FTimeout := nil;
|
|
end;
|
|
|
|
destructor THttpAsync.Destroy;
|
|
begin
|
|
StopTimeout;
|
|
FAsyncCallback := nil;
|
|
FHTTPRequest := nil;
|
|
FreeAndNil(FTimeout);
|
|
FreeAndNil(FClient);
|
|
inherited;
|
|
end;
|
|
|
|
function THttpAsync.HeadAsync: IAsyncResult;
|
|
begin
|
|
FHTTPRequest := FClient.GetRequest(sHTTPMethodHead, URL);
|
|
Result := FClient.BeginExecute(FAsyncCallback, FHTTPRequest);
|
|
end;
|
|
|
|
function THttpAsync.GetAsync: IAsyncResult;
|
|
begin
|
|
FHTTPRequest := FClient.GetRequest(sHTTPMethodGet, URL);
|
|
Result := FClient.BeginExecute(FAsyncCallback, FHTTPRequest);
|
|
end;
|
|
|
|
function THttpAsync.PostAsync(const Data: RawByteString = ''): IAsyncResult;
|
|
begin
|
|
FSendStream := TMemoryStream.Create;
|
|
FSendStream.Write(Data[1], Length(Data));
|
|
FSendStream.Seek(0, soFromBeginning);
|
|
FHTTPRequest := FClient.GetRequest(sHTTPMethodPost, URL);
|
|
FHTTPRequest.SourceStream := FSendStream;
|
|
Result := FClient.BeginExecute(FAsyncCallback, FHTTPRequest);
|
|
end;
|
|
|
|
function THttpAsync.PostAsync(Data: TStream): IAsyncResult;
|
|
begin
|
|
FSendStream := TMemoryStream.Create;
|
|
FSendStream.CopyFrom(Data);
|
|
FSendStream.Seek(0, soFromBeginning);
|
|
FHTTPRequest := FClient.GetRequest(sHTTPMethodPost, URL);
|
|
FHTTPRequest.SourceStream := FSendStream;
|
|
Result := FClient.BeginExecute(FAsyncCallback, FHTTPRequest);
|
|
end;
|
|
|
|
procedure THttpAsync.Setup(const Link: String; Redirect: Boolean = True; RedirectCount: Integer = 5; const POSTData: RawByteString = '');
|
|
begin
|
|
URL := Link;
|
|
SetupHttpClient(FClient, Redirect, RedirectCount, POSTData);
|
|
end;
|
|
|
|
procedure THttpAsync.SetQuiet;
|
|
begin
|
|
FQuiet := True;
|
|
end;
|
|
|
|
procedure THttpAsync.SetLongPoll;
|
|
begin
|
|
FClient.ConnectionTimeout := -1;
|
|
FClient.SendTimeout := -1;
|
|
FClient.ResponseTimeout := -1;
|
|
end;
|
|
|
|
procedure THttpAsync.SetNativeTimeout(Timeout: Integer);
|
|
begin
|
|
FClient.ConnectionTimeout := Timeout * 1000;
|
|
FClient.SendTimeout := Timeout * 1000;
|
|
FClient.ResponseTimeout := Timeout * 1000;
|
|
end;
|
|
|
|
procedure THttpAsync.SetTimeout(Timeout: Integer);
|
|
begin
|
|
if Assigned(FTimeout) then
|
|
FTimeout.Interval := Timeout * 1000;
|
|
end;
|
|
|
|
procedure THttpAsync.StartTimeout;
|
|
begin
|
|
if Assigned(FTimeout) then
|
|
FTimeout.Enabled := True;
|
|
end;
|
|
|
|
procedure THttpAsync.StopTimeout;
|
|
begin
|
|
if Assigned(FTimeout) then
|
|
FTimeout.Enabled := False;
|
|
end;
|
|
|
|
procedure THttpAsync.TimedOut(Sender: TObject);
|
|
begin
|
|
StopTimeout;
|
|
Abort;
|
|
end;
|
|
|
|
procedure THttpAsync.Abort;
|
|
begin
|
|
if Assigned(FHTTPRequest) and not FHTTPRequest.IsCancelled then
|
|
FHTTPRequest.Cancel;
|
|
end;
|
|
|
|
procedure THttpAsync.OnRequestDoneTimed(const Response: IHTTPResponse; const Error: String = '');
|
|
begin
|
|
StopTimeout;
|
|
if Assigned(FOnRequestDone) then
|
|
FOnRequestDone(Self, Response, Error);
|
|
end;
|
|
|
|
function ValidateURL(const URL: String): String;
|
|
begin
|
|
if URL.Contains('#') then
|
|
Result := URL.Substring(0, URL.IndexOf('#'))
|
|
else
|
|
Result := URL;
|
|
if not StartsText('http://', Result) and not StartsText('https://', Result) and not ContainsText('://', Result) then
|
|
Result := 'https://' + Result;
|
|
end;
|
|
|
|
function DecodeURL(const url: String): String;
|
|
begin
|
|
Result := TEncoding.UTF8.GetString(TNetEncoding.URL.DecodeStringToBytes(url));
|
|
end;
|
|
|
|
function HandleStatus(Code: Integer): Boolean;
|
|
begin
|
|
Result := (Code < 400) or (Code = 401) or (Code = 400) or (Code = 407);
|
|
end;
|
|
|
|
function HandleStatusAndError(Code: Integer; const Status: String; const URL: String; const ErrText: String = ''; Quiet: Boolean = True): Boolean;
|
|
begin
|
|
Result := HandleStatus(Code);
|
|
if not Result then
|
|
if (Code <> 404) or not Quiet then
|
|
begin
|
|
if (Code = 404) and
|
|
(URL.Contains('openstore') and (URL.Contains('store_id=') or URL.Contains('file_id=')) or
|
|
URL.Contains('eventSubscribe')) then
|
|
begin
|
|
LogEvent(GetTranslation(ConnectionError, [URL, Code, GetTranslation(Status)]), PIC_EXCLAMATION);
|
|
Exit;
|
|
end;
|
|
if not (ErrText = '') then
|
|
MsgDlg(GetTranslation(UploadError) + ': ' + #13#10 + ErrText, False, mtError)
|
|
else
|
|
MsgDlg(GetTranslation(ConnectionError, [URL, Code, GetTranslation(Status)]), False, mtError);
|
|
end;
|
|
end;
|
|
|
|
procedure HandleHTTPError(E: Exception; ErrorCode: Integer = 0; const ErrorText: String = ''; const URL: String = '');
|
|
var
|
|
Args: array [0..2] of TVarRec;
|
|
begin
|
|
Args[0].VType := vtUnicodeString;
|
|
Args[0].VUnicodeString := Pointer(URL);
|
|
Args[1].VType := vtInteger;
|
|
Args[1].VInteger := ErrorCode;
|
|
Args[2].VType := vtUnicodeString;
|
|
if ErrorText = '' then
|
|
Args[2].VUnicodeString := Pointer(E.Message)
|
|
else
|
|
Args[2].VUnicodeString := Pointer(ErrorText);
|
|
if E is ENetHTTPRequestException then
|
|
MsgDlg(GetTranslation(MaxRedirectError, Args), False, mtError)
|
|
else if E is ENetHTTPCertificateException then
|
|
MsgDlg(GetTranslation(SSLError, Args), False, mtError)
|
|
else if E is Exception then
|
|
MsgDlg(GetTranslation(ConnectionError, Args), False, mtError);
|
|
end;
|
|
|
|
procedure SimpleCertCheck(const Sender: TObject; const ARequest: TURLRequest; const Certificate: TCertificate; var Accepted: Boolean);
|
|
begin
|
|
Accepted := not Certificate.IsEmpty and (Now >= Certificate.Start) and (Now <= Certificate.Expiry);
|
|
end;
|
|
|
|
procedure SetupHttpClient(var HttpCli: THttpClient; Redirect: Boolean = True; RedirectCount: Integer = 5; const POSTData: RawByteString = '');
|
|
begin
|
|
with HttpCli do
|
|
begin
|
|
HandleRedirects := Redirect;
|
|
MaxRedirects := RedirectCount;
|
|
ConnectionTimeout := 30000;
|
|
SendTimeout := 30000;
|
|
ResponseTimeout := 30000;
|
|
Accept := 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8';
|
|
AcceptLanguage := 'en,en-US;q=0.7,ru;q=0.3';
|
|
if IsEightOne then
|
|
begin
|
|
AcceptEncoding := 'gzip, deflate';
|
|
AutomaticDecompression := [THTTPCompressionMethod.Any];
|
|
end;
|
|
if not IsEight then
|
|
SecureProtocols := [THTTPSecureProtocol.TLS1, THTTPSecureProtocol.TLS11, THTTPSecureProtocol.TLS12, THTTPSecureProtocol.TLS13];
|
|
ValidateServerCertificateCallback := SimpleCertCheck;
|
|
UserAgent := IfThen(
|
|
Account.AccProto.ShowClientID,
|
|
'R&Q Desktop ' + ICQ_DEV_ID + ' 0.11.9999.' + IntToStr(RnQBuild) + '(' + IntToStr(RnQBuildCustom) + ') ',
|
|
'ICQ Desktop ' + ICQ_DEV_ID + ' ' + ICQ_FAKE_VERSION + '(' + ICQ_FAKE_BUILD + ') '
|
|
) + TOSVersion.Name.Replace(' ', '_') + ' PC';
|
|
if not (POSTData = '') then
|
|
ContentType := IfThen(POSTData[1] = '{', 'application/json', 'application/x-www-form-urlencoded');
|
|
|
|
with MainProxy do
|
|
if proto = PP_NONE then
|
|
begin
|
|
UseDefaultCredentials := False;
|
|
ProxySettings := TProxySettings.Create('http://direct');
|
|
end else if proto = PP_HTTPS then
|
|
if auth then
|
|
begin
|
|
UseDefaultCredentials := NTLM;
|
|
ProxySettings := TProxySettings.Create(addr.host, addr.port, user, pwd);
|
|
end
|
|
else
|
|
begin
|
|
UseDefaultCredentials := False;
|
|
ProxySettings := TProxySettings.Create(addr.host, addr.port);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function LoadFromURLAsString(const URL: String; var Response: String; const POSTData: RawByteString = ''; ShowErrors: Boolean = True): Boolean;
|
|
var
|
|
HttpParams: THttpRequestParams;
|
|
begin
|
|
HttpParams := THttpRequestParams.Create(HTRT_TEXT);
|
|
HttpParams.URL := URL;
|
|
HttpParams.POSTData := POSTData;
|
|
HttpParams.ShowErrors := ShowErrors;
|
|
Result := LoadFromURL(HttpParams);
|
|
Response := HttpParams.ResponseText;
|
|
HttpParams.Free;
|
|
end;
|
|
|
|
function LoadFromURLAsFile(const URL: String; var FileName: String; ExtByContent: Boolean = False): Boolean;
|
|
var
|
|
HttpParams: THttpRequestParams;
|
|
begin
|
|
HttpParams := THttpRequestParams.Create(HTRT_FILE);
|
|
HttpParams.URL := URL;
|
|
HttpParams.FileName := FileName;
|
|
HttpParams.ExtByContent := ExtByContent;
|
|
Result := LoadFromURL(HttpParams);
|
|
FileName := HttpParams.FileName;
|
|
HttpParams.Free;
|
|
end;
|
|
|
|
function LoadFromURLAsStream(const URL: String; var Response: TMemoryStream; ShowErrors: Boolean = True): Boolean;
|
|
var
|
|
HttpParams: THttpRequestParams;
|
|
begin
|
|
HttpParams := THttpRequestParams.Create(HTRT_STREAM);
|
|
HttpParams.URL := URL;
|
|
HttpParams.ResponseStream := Response;
|
|
HttpParams.ShowErrors := ShowErrors;
|
|
Result := LoadFromURL(HttpParams);
|
|
HttpParams.Free;
|
|
end;
|
|
|
|
procedure WaitForResponse(Event: TEvent);
|
|
begin
|
|
// ODS('Req on main? ' + booltostr(GetCurrentThreadID = MainThreadID, True));
|
|
if not (GetCurrentThreadID = MainThreadID) then
|
|
Event.WaitFor // Block thread until response is ready
|
|
else
|
|
while Event.WaitFor(0) = wrTimeout do
|
|
if MsgWaitForMultipleObjects(0, Pointer(nil)^, False, 1000, QS_ALLINPUT) = WAIT_OBJECT_0 then
|
|
Application.ProcessMessages; // Do not block if it's a main thead
|
|
end;
|
|
|
|
function LoadFromURL(const Params: THttpRequestParams): Boolean;
|
|
var
|
|
HttpCli: THttpClient;
|
|
HttpAsync: THttpAsync;
|
|
DataFormat: TPAFormat;
|
|
FinishedEvent: TEvent;
|
|
HTTPResponse: IHTTPResponse;
|
|
ValidURL: String;
|
|
begin
|
|
Result := False;
|
|
ValidURL := ValidateURL(Params.URL);
|
|
|
|
try
|
|
TURI.Create(ValidURL);
|
|
except
|
|
Exit;
|
|
end;
|
|
|
|
FinishedEvent := TEvent.Create(nil, True, False, '');
|
|
HttpCli := nil;
|
|
if Params.Threshold > 0 then
|
|
try
|
|
HttpCli := THttpClient.Create;
|
|
SetupHttpClient(HttpCli);
|
|
try
|
|
HTTPResponse := HttpCli.Head(ValidURL);
|
|
if HTTPResponse.ContentLength > Params.Threshold then
|
|
begin
|
|
HTTPResponse := nil;
|
|
Exit;
|
|
end;
|
|
except end;
|
|
finally
|
|
FreeAndNil(HttpCli);
|
|
end;
|
|
|
|
HTTPResponse := nil;
|
|
HttpAsync := THttpAsync.Create(0);
|
|
HttpAsync.Setup(ValidURL, True, 5, Params.POSTData);
|
|
if not Params.ShowErrors then
|
|
HttpAsync.SetQuiet;
|
|
HttpAsync.Callback := procedure(Sender: TObject; const Response: IHTTPResponse; const Error: String = '')
|
|
// var
|
|
// Header: TNameValuePair;
|
|
begin
|
|
// ODS('- - - - - - - - - -');
|
|
// for Header in HttpAsync.HTTPRequest.Headers do
|
|
// ODS(header.Name + ': ' + Header.Value);
|
|
// ODS('- - - - - - - - - -');
|
|
// if Assigned(Response) then
|
|
// for Header in Response.Headers do
|
|
// ODS(header.Name + ': ' + Header.Value);
|
|
if Assigned(Response) then
|
|
if HandleStatusAndError(Response.StatusCode, Response.StatusText, ValidURL, '', not Params.ShowErrors) then
|
|
HTTPResponse := Response
|
|
else
|
|
HTTPResponse := nil;
|
|
FinishedEvent.SetEvent;
|
|
end;
|
|
|
|
if Params.POSTData = '' then
|
|
HttpAsync.GetAsync
|
|
else
|
|
HttpAsync.PostAsync(Params.POSTData);
|
|
|
|
WaitForResponse(FinishedEvent);
|
|
FreeAndNil(FinishedEvent);
|
|
|
|
try
|
|
if Assigned(HTTPResponse) and Assigned(HTTPResponse.ContentStream) then
|
|
begin
|
|
Result := True;
|
|
Params.Mime := HTTPResponse.MimeType;
|
|
HTTPResponse.ContentStream.Seek(0, soFromBeginning);
|
|
if Params.RequestType = HTRT_TEXT then
|
|
Params.ResponseText := HTTPResponse.ContentAsString(TEncoding.UTF8)
|
|
else if (Params.RequestType = HTRT_FILE) and not (Params.FileName = '') then
|
|
begin
|
|
if Params.ExtByContent then
|
|
begin
|
|
DataFormat := DetectFileFormatStream(HTTPResponse.ContentStream);
|
|
if DataFormat <> PA_FORMAT_UNK then
|
|
Params.FileName := ChangeFileExt(Params.FileName, PAFormat[DataFormat]);
|
|
end;
|
|
TMemoryStream(HTTPResponse.ContentStream).SaveToFile(Params.FileName);
|
|
end else if (Params.RequestType = HTRT_STREAM) and Assigned(Params.ResponseStream) then
|
|
begin
|
|
TMemoryStream(HTTPResponse.ContentStream).SaveToStream(Params.ResponseStream);
|
|
Params.ResponseStream.Seek(0, soFromBeginning);
|
|
end;
|
|
end;
|
|
finally
|
|
HTTPResponse := nil;
|
|
FreeAndNil(HttpAsync);
|
|
end;
|
|
end;
|
|
|
|
procedure LoadFromURLAsync(const URL: String; Callback: TRequestDone; const POSTData: RawByteString = '');
|
|
var
|
|
HttpCli: THttpAsync;
|
|
ValidURL: String;
|
|
begin
|
|
ValidURL := ValidateURL(URL);
|
|
|
|
try
|
|
TURI.Create(URL);
|
|
except
|
|
Exit;
|
|
end;
|
|
|
|
HttpCli := THttpAsync.Create;
|
|
HttpCli.Setup(ValidURL, True, 5, POSTData);
|
|
HttpCli.Callback := Callback;
|
|
if POSTData = '' then
|
|
HttpCli.GetAsync
|
|
else
|
|
HttpCli.PostAsync(POSTData);
|
|
end;
|
|
|
|
function ContentTypeFromURL(const URL: String): String;
|
|
var
|
|
HttpAsync: THttpAsync;
|
|
FinishedEvent: TEvent;
|
|
HTTPResponse: IHTTPResponse;
|
|
ValidURL: String;
|
|
begin
|
|
Result := '';
|
|
if URL = '' then
|
|
Exit;
|
|
|
|
ValidURL := ValidateURL(URL);
|
|
|
|
try
|
|
TURI.Create(ValidURL);
|
|
except
|
|
Exit;
|
|
end;
|
|
|
|
FinishedEvent := TEvent.Create(nil, True, False, '');
|
|
|
|
HTTPResponse := nil;
|
|
HttpAsync := THttpAsync.Create(0);
|
|
HttpAsync.Setup(ValidURL);
|
|
HttpAsync.SetNativeTimeout(2);
|
|
HttpAsync.SetQuiet;
|
|
HttpAsync.Client.UserAgent := Browser;
|
|
HttpAsync.Callback := procedure(Sender: TObject; const Response: IHTTPResponse; const Error: String = '')
|
|
begin
|
|
HTTPResponse := Response;
|
|
FinishedEvent.SetEvent;
|
|
end;
|
|
|
|
HttpAsync.HeadAsync;
|
|
WaitForResponse(FinishedEvent);
|
|
FreeAndNil(FinishedEvent);
|
|
|
|
try
|
|
if Assigned(HTTPResponse) then
|
|
Result := HTTPResponse.MimeType;
|
|
finally
|
|
HTTPResponse := nil;
|
|
FreeAndNil(HttpAsync);
|
|
end;
|
|
end;
|
|
|
|
function InfoFromURL(const URL: String): TLinkInfo;
|
|
var
|
|
HttpAsync: THttpAsync;
|
|
FinishedEvent: TEvent;
|
|
HTTPResponse: IHTTPResponse;
|
|
ValidURL: String;
|
|
begin
|
|
Result := Default(TLinkInfo);
|
|
if URL = '' then
|
|
Exit;
|
|
|
|
ValidURL := ValidateURL(URL);
|
|
|
|
try
|
|
TURI.Create(ValidURL);
|
|
except
|
|
Exit;
|
|
end;
|
|
|
|
FinishedEvent := TEvent.Create(nil, True, False, '');
|
|
|
|
HTTPResponse := nil;
|
|
HttpAsync := THttpAsync.Create(0);
|
|
HttpAsync.Setup(ValidURL, True, 10);
|
|
HttpAsync.SetNativeTimeout(5);
|
|
HttpAsync.SetQuiet;
|
|
HttpAsync.Client.UserAgent := Browser;
|
|
HttpAsync.Client.CustomHeaders['Pragma'] := 'no-cache';
|
|
HttpAsync.Client.CustomHeaders['Cache-Control'] := 'no-cache';
|
|
HttpAsync.Client.ReceiveDataCallBack := procedure(const Sender: TObject; AContentLength: Int64; AReadCount: Int64; var AAbort: Boolean)
|
|
begin
|
|
AAbort := True;
|
|
end;
|
|
HttpAsync.Callback := procedure(Sender: TObject; const Response: IHTTPResponse; const Error: String = '')
|
|
// var
|
|
// Header: TNameValuePair;
|
|
begin
|
|
// for Header in HttpAsync.HTTPRequest.Headers do
|
|
// ODS(header.Name + ': ' + Header.Value);
|
|
// ODS('- - - - - - - - - -');
|
|
// for Header in Response.Headers do
|
|
// ODS(header.Name + ': ' + Header.Value);
|
|
HTTPResponse := Response;
|
|
FinishedEvent.SetEvent;
|
|
end;
|
|
|
|
HttpAsync.GetAsync;
|
|
WaitForResponse(FinishedEvent);
|
|
FreeAndNil(FinishedEvent);
|
|
|
|
try
|
|
if Assigned(HTTPResponse) then
|
|
begin
|
|
Result.code := HTTPResponse.StatusCode;
|
|
Result.size := HTTPResponse.ContentLength;
|
|
Result.mime := HTTPResponse.MimeType;
|
|
//Result.redirects := HTTPResponse.LocationChangeCurCount;
|
|
end;
|
|
finally
|
|
HTTPResponse := nil;
|
|
FreeAndNil(HttpAsync);
|
|
end;
|
|
end;
|
|
|
|
function FileSize(const aFilename: String): Int64;
|
|
var
|
|
info: TWin32FileAttributeData;
|
|
begin
|
|
result := -1;
|
|
|
|
if not GetFileAttributesEx(PWideChar(aFileName), GetFileExInfoStandard, @info) then
|
|
Exit;
|
|
|
|
result := Int64(info.nFileSizeLow) or Int64(info.nFileSizeHigh shl 32);
|
|
end;
|
|
|
|
function InputText(const Boundry, Name, Value: RawByteString): RawByteString;
|
|
begin
|
|
Result := '--' + boundry + CRLF + 'Content-Disposition: form-data; name="' + name + '"' + CRLF + CRLF + value + CRLF;
|
|
end;
|
|
|
|
function UploadFileICQ(FileStream: TStream; const FileName: String; UploadCallbacks: TCallbacks): String;
|
|
var
|
|
HttpAsync: THttpAsync;
|
|
HTTPResponse: IHTTPResponse;
|
|
FinishedEvent: TEvent;
|
|
Response: RawByteString;
|
|
JSON, Res: TJSONObject;
|
|
ULimit, Code: Integer;
|
|
UploadLink: String;
|
|
begin
|
|
Result := '';
|
|
|
|
ULimit := 4096;
|
|
if FileSize(FileName) > ULimit * 1024 * 1024 then
|
|
begin
|
|
MsgDlg(GetTranslation(FileTooBig, [IntToStr(ULimit)]), False, mtError);
|
|
Exit;
|
|
end;
|
|
|
|
UploadLink := Account.AccProto.FilesInit(FileName, FileStream.Size);
|
|
if UploadLink = '' then
|
|
begin
|
|
MsgDlg(AuthFailed, True, mtError);
|
|
Exit;
|
|
end;
|
|
|
|
FinishedEvent := TEvent.Create(nil, True, False, '');
|
|
|
|
HTTPResponse := nil;
|
|
HttpAsync := THttpAsync.Create;
|
|
HttpAsync.Setup(UploadLink);
|
|
HttpAsync.Client.ContentType := 'application/octet-stream';
|
|
HttpAsync.Client.CustomHeaders['Content-Disposition'] := 'attachment; filename="' + FileName + '"';
|
|
HttpAsync.Client.CustomHeaders['Content-Range'] := 'bytes 0-' + IntToStr(FileStream.Size - 1) + '/' + IntToStr(FileStream.Size);
|
|
HttpAsync.Client.CustomHeaders['Pragma'] := 'no-cache';
|
|
HttpAsync.Client.CustomHeaders['Cache-Control'] := 'no-cache';
|
|
HttpAsync.Client.SendDataCallBack := UploadCallbacks.OnSendDataCallback;
|
|
HttpAsync.Callback := procedure(Sender: TObject; const Response: IHTTPResponse; const Error: String = '')
|
|
begin
|
|
if Assigned(Response) then
|
|
if HandleStatusAndError(Response.StatusCode, Response.StatusText, UploadLink, '', False) then
|
|
HTTPResponse := Response
|
|
else
|
|
HTTPResponse := nil;
|
|
FinishedEvent.SetEvent;
|
|
end;
|
|
|
|
IsUploading := True;
|
|
HttpAsync.PostAsync(FileStream);
|
|
WaitForResponse(FinishedEvent);
|
|
FreeAndNil(FinishedEvent);
|
|
IsUploading := False;
|
|
|
|
if Assigned(HTTPResponse) and Assigned(HTTPResponse.ContentStream) then
|
|
try
|
|
Response := HTTPResponse.ContentAsString(TEncoding.UTF8);
|
|
JSON := TJSONObject.ParseJSONValue(Response) as TJSONObject;
|
|
if Assigned(JSON) then
|
|
try
|
|
TJSONObject(JSON.GetValue('status')).GetValue('code').TryGetValue(Code);
|
|
if not (Code = 200) or (JSON.GetValue('result') = nil) then
|
|
begin
|
|
MsgDlg(GetTranslation(UploadError) + ': ' + IntToStr(Code), False, mtError);
|
|
Exit;
|
|
end;
|
|
Res := TJSONObject(JSON.GetValue('result'));
|
|
Result := Res.GetValue('static_url').Value;
|
|
finally
|
|
FreeAndNil(JSON);
|
|
end;
|
|
except
|
|
MsgDlg(GetTranslation(UploadError) + ': ' + IntToStr(HTTPResponse.StatusCode) + ' ' + HTTPResponse.StatusText, False, mtError);
|
|
end;
|
|
HTTPResponse := nil;
|
|
FreeAndNil(HttpAsync);
|
|
end;
|
|
|
|
function UploadFile2MyServer(FileStream: TStream; const FileName: String; const Boundry: RawByteString; const URL: String; UploadCallbacks: TCallbacks): String;
|
|
var
|
|
HttpAsync: THttpAsync;
|
|
HTTPResponse: IHTTPResponse;
|
|
FinishedEvent: TEvent;
|
|
SendStream: TMemoryStream;
|
|
Buf: RawByteString;
|
|
begin
|
|
Result := '';
|
|
|
|
if FileStream.Size > 100 * 1024 * 1024 then
|
|
begin
|
|
MsgDlg(GetTranslation(FileTooBig, [IntToStr(100)]), False, mtError);
|
|
Exit;
|
|
end;
|
|
|
|
FinishedEvent := TEvent.Create(nil, True, False, '');
|
|
|
|
HTTPResponse := nil;
|
|
HttpAsync := THttpAsync.Create;
|
|
HttpAsync.Setup(URL, False);
|
|
HttpAsync.Client.ContentType := 'multipart/form-data; boundary=' + Boundry;
|
|
HttpAsync.Client.CustomHeaders['Pragma'] := 'no-cache';
|
|
HttpAsync.Client.CustomHeaders['Cache-Control'] := 'no-cache';
|
|
HttpAsync.Client.SendDataCallBack := UploadCallbacks.OnSendDataCallback;
|
|
HttpAsync.Callback := procedure(Sender: TObject; const Response: IHTTPResponse; const Error: String = '')
|
|
begin
|
|
if Assigned(Response) then
|
|
if HandleStatusAndError(Response.StatusCode, Response.StatusText, URL, '', False) then
|
|
HTTPResponse := Response
|
|
else
|
|
HTTPResponse := nil;
|
|
FinishedEvent.SetEvent;
|
|
end;
|
|
|
|
SendStream := TMemoryStream.Create;
|
|
Buf := InputText(Boundry, 'fname', UTF(FileName)) +
|
|
'--' + Boundry + CRLF + 'Content-Disposition: form-data; name="file"; filename="' + UTF(FileName) + '"' + CRLF +
|
|
'Content-Transfer-Encoding: binary' + CRLF + CRLF;
|
|
SendStream.Write(Buf[1], Length(Buf));
|
|
SendStream.CopyFrom(FileStream);
|
|
Buf := CRLF + '--' + Boundry + '--' + CRLF;
|
|
SendStream.Write(Buf[1], Length(Buf));
|
|
SendStream.Seek(0, soFromBeginning);
|
|
|
|
IsUploading := True;
|
|
HttpAsync.PostAsync(SendStream);
|
|
WaitForResponse(FinishedEvent);
|
|
FreeAndNil(FinishedEvent);
|
|
FreeAndNil(SendStream);
|
|
IsUploading := False;
|
|
|
|
if Assigned(HTTPResponse) and Assigned(HTTPResponse.ContentStream) then
|
|
try
|
|
Result := LowerCase(HTTPResponse.ContentAsString(TEncoding.UTF8));
|
|
except
|
|
MsgDlg(GetTranslation(UploadError) + ': ' + IntToStr(HTTPResponse.StatusCode) + ' ' + HTTPResponse.StatusText, False, mtError);
|
|
end;
|
|
HTTPResponse := nil;
|
|
FreeAndNil(HttpAsync);
|
|
end;
|
|
|
|
function UploadFileRnQ(FileStream: TStream; const FileName: String; UploadCallbacks: TCallbacks): String;
|
|
begin
|
|
Result := UploadFile2MyServer(FileStream, FileName, '---------------RnQPortalServerUpload', 'https://rnq.ru/file_upload.php', UploadCallbacks);
|
|
end;
|
|
|
|
function UploadFileMikanoshi(FileStream: TStream; const FileName: String; UploadCallbacks: TCallbacks): String;
|
|
begin
|
|
Result := UploadFile2MyServer(FileStream, FileName, '---------------MikanoshiServerUpload', 'https://code.highspec.ru/upload.php', UploadCallbacks);
|
|
end;
|
|
|
|
function GetICQFileLinkInfoPublic(const Link: String): TICQFileInfo;
|
|
var
|
|
JSONObject, JSONObject2: TJSONObject;
|
|
Status: Integer;
|
|
FileIdStr, ImgStr: RawByteString;
|
|
Tmp: String;
|
|
Buf: TMemoryStream;
|
|
begin
|
|
Result := Default(TICQFileInfo);
|
|
|
|
FileIdStr := ReplaceText(Trim(Link), 'files.icq.net/get/', 'files.icq.com/getinfo?file_id=');
|
|
FileIdStr := ReplaceText(FileIdStr, 'files.icq.net/files/get?fileId=', 'files.icq.com/getinfo?file_id=');
|
|
FileIdStr := ReplaceText(FileIdStr, 'http://', 'https://');
|
|
Result.jsonlink := FileIdStr;
|
|
|
|
LogICQPacket(WL_sent_text, 'GET', 'Get ICQ file link info', FileIdStr);
|
|
Buf := TMemoryStream.Create;
|
|
LoadFromURLAsStream(FileIdStr, Buf, False);
|
|
SetLength(ImgStr, Buf.Size);
|
|
Buf.ReadBuffer(ImgStr[1], Buf.Size);
|
|
Buf.Free;
|
|
LogICQPacket(WL_rcvd_text, 'GET', 'Get ICQ file link info', ImgStr);
|
|
|
|
if Length(ImgStr) = 0 then
|
|
Exit;
|
|
|
|
JSONObject := TJSONObject.ParseJSONValue(ImgStr) as TJSONObject;
|
|
if Assigned(JSONObject) then
|
|
try
|
|
JSONObject.GetValueSafe('status', Status);
|
|
if not (Status = 200) then
|
|
Exit;
|
|
|
|
JSONObject2 := TJSONArray(JSONObject.GetValue('file_list')).Items[0] as TJSONObject;
|
|
try
|
|
Result.dlink := JSONObject2.GetValue('dlink').Value;
|
|
Result.mime := JSONObject2.GetValue('mime').Value;
|
|
Result.filename := JSONObject2.GetValue('filename').Value;
|
|
Result.filesize := StrToInt64(JSONObject2.GetValue('filesize').Value);
|
|
Result.avstatus := IfThen(JSONObject2.GetValue('avstatus') = nil, 'unknown', JSONObject2.GetValue('avstatus').Value);
|
|
Result.is_previewable := StrToInt(JSONObject2.GetValue('is_previewable').Value) = 1;
|
|
Tmp := '';
|
|
JSONObject2.TryGetValue('recognize', Tmp);
|
|
Result.recognized := Tmp = 'true';
|
|
if Result.is_previewable then
|
|
begin
|
|
if PreviewResolution = 0 then
|
|
Result.preview := JSONObject2.GetValue('mdpi').Value
|
|
else if PreviewResolution = 1 then
|
|
Result.preview := JSONObject2.GetValue('hdpi').Value
|
|
else if PreviewResolution = 2 then
|
|
Result.preview := JSONObject2.GetValue('xhdpi').Value
|
|
else if PreviewResolution = 3 then
|
|
Result.preview := JSONObject2.GetValue('xxhdpi').Value
|
|
else if PreviewResolution = 4 then
|
|
Result.preview := JSONObject2.GetValue('large').Value
|
|
end else if not (JSONObject2.GetValue('xlarge') = nil) then
|
|
Result.preview := JSONObject2.GetValue('xlarge').Value;
|
|
finally
|
|
FreeAndNil(JSONObject);
|
|
end;
|
|
except
|
|
FreeAndNil(JSONObject);
|
|
end;
|
|
end;
|
|
|
|
function GetICQFileLinkInfoPrivate(const Link: String): TICQFileInfo;
|
|
var
|
|
JSONObject, JSONObject2, JSONObject3: TJSONObject;
|
|
FileIdStr, ImgStr: RawByteString;
|
|
Buf: TMemoryStream;
|
|
Code: Integer;
|
|
begin
|
|
Result := Default(TICQFileInfo);
|
|
|
|
FileIdStr := ReplaceText(Trim(Link), 'files.icq.net/get/', 'u.icq.net/api/' + API_VERSION + '/files/info/');
|
|
FileIdStr := ReplaceText(FileIdStr, 'files.icq.net/files/get?fileId=', 'u.icq.net/api/' + API_VERSION + '/files/info/');
|
|
FileIdStr := FileIdStr + '?aimsid=' + Account.AccProto.AimSid + '&previews=mdpi,hdpi,xhdpi,xxhdpi,small,medium,large,xlarge';
|
|
FileIdStr := ReplaceText(FileIdStr, 'http://', 'https://');
|
|
Result.jsonlink := FileIdStr;
|
|
|
|
LogICQPacket(WL_sent_text, 'GET', 'Get ICQ file link info', FileIdStr);
|
|
Buf := TMemoryStream.Create;
|
|
LoadFromURLAsStream(FileIdStr, Buf, False);
|
|
SetLength(ImgStr, Buf.Size);
|
|
Buf.ReadBuffer(ImgStr[1], Buf.Size);
|
|
Buf.Free;
|
|
LogICQPacket(WL_rcvd_text, 'GET', 'Get ICQ file link info', ImgStr);
|
|
|
|
if Length(ImgStr) = 0 then
|
|
Exit;
|
|
|
|
JSONObject := TJSONObject.ParseJSONValue(ImgStr) as TJSONObject;
|
|
if Assigned(JSONObject) then
|
|
try
|
|
TJSONObject(JSONObject.GetValue('status')).GetValue('code').TryGetValue(Code);
|
|
if (not (Code = Ord(ERAPI_OK)) and not (Code = Ord(EAC_OK))) or (JSONObject.GetValue('result') = nil) then
|
|
Exit;
|
|
JSONObject2 := TJSONObject(JSONObject.GetValue('result')).GetValue('info') as TJSONObject;
|
|
try
|
|
Result.dlink := JSONObject2.GetValue('dlink').Value;
|
|
Result.mime := JSONObject2.GetValue('mime').Value;
|
|
Result.filename := JSONObject2.GetValue('file_name').Value;
|
|
Result.filesize := StrToInt64(JSONObject2.GetValue('file_size').Value);
|
|
JSONObject3 := TJSONObject(JSONObject.GetValue('antivirus')) as TJSONObject;
|
|
if not (JSONObject3 = nil) then
|
|
Result.avstatus := IfThen(JSONObject3.GetValue('check_result') = nil, 'unknown', JSONObject2.GetValue('check_result').Value)
|
|
else
|
|
Result.avstatus := 'unknown';
|
|
if not JSONObject2.TryGetValue('has_previews', Result.is_previewable) then
|
|
Result.is_previewable := False;
|
|
Result.recognized := False;
|
|
if Result.is_previewable then
|
|
begin
|
|
JSONObject3 := TJSONObject(JSONObject.GetValue('result')).GetValue('previews') as TJSONObject;
|
|
if PreviewResolution = 0 then
|
|
Result.preview := JSONObject3.GetValue('mdpi').Value
|
|
else if PreviewResolution = 1 then
|
|
Result.preview := JSONObject3.GetValue('hdpi').Value
|
|
else if PreviewResolution = 2 then
|
|
Result.preview := JSONObject3.GetValue('xhdpi').Value
|
|
else if PreviewResolution = 3 then
|
|
Result.preview := JSONObject3.GetValue('xxhdpi').Value
|
|
else if PreviewResolution = 4 then
|
|
Result.preview := JSONObject3.GetValue('large').Value
|
|
end else if not (JSONObject2.GetValue('xlarge') = nil) then
|
|
Result.preview := JSONObject2.GetValue('xlarge').Value;
|
|
finally
|
|
FreeAndNil(JSONObject);
|
|
end;
|
|
except
|
|
FreeAndNil(JSONObject);
|
|
end;
|
|
end;
|
|
|
|
function CheckType(const lnk: String): Boolean;
|
|
var
|
|
ext: String;
|
|
sA: RawByteString;
|
|
begin
|
|
Result := CheckType(lnk, sA, ext);
|
|
end;
|
|
|
|
function CheckType(const lnk: String; var sA: RawByteString; var ext: String): Boolean;
|
|
var
|
|
Res, HasMime, IsLottie: Boolean;
|
|
sALocal: RawByteString;
|
|
extLocal, anchor: String;
|
|
info: TICQFileInfo;
|
|
FinishedEvent: TEvent;
|
|
begin
|
|
Res := False;
|
|
HasMime := False;
|
|
IsLottie := False;
|
|
FinishedEvent := TEvent.Create(nil, True, False, '');
|
|
|
|
TThread.CreateAnonymousThread(procedure()
|
|
var
|
|
buf: TMemoryStream;
|
|
idx: Integer;
|
|
ctype: String;
|
|
imgStr: RawByteString;
|
|
begin
|
|
|
|
if EnableVideoLinks and (ContainsText(lnk, 'youtube.com/') or ContainsText(lnk, 'youtu.be/') or ContainsText(lnk, 'vimeo.com/')) then
|
|
begin
|
|
buf := TMemoryStream.Create;
|
|
LoadFromURLAsStream(lnk, buf, False);
|
|
SetLength(imgStr, buf.Size);
|
|
buf.ReadBuffer(imgStr[1], buf.Size);
|
|
buf.Free;
|
|
|
|
anchor := 'property="og:image" content="';
|
|
sALocal := copy(imgStr, pos(anchor, imgStr) + length(anchor));
|
|
sALocal := copy(sALocal, 1, pos('"', sALocal) - 1);
|
|
sALocal := DecodeURL(UnUTF(sALocal));
|
|
end else if ContainsText(lnk, 'files.icq.net/') then
|
|
begin
|
|
info := GetICQFileLinkInfoPublic(lnk);
|
|
IsLottie := IsLottieFile(info.filename);
|
|
if info.mime.StartsWith('image/') or IsLottie then
|
|
begin
|
|
if info.preview = '' then
|
|
sALocal := info.dlink
|
|
else if info.is_previewable then
|
|
sALocal := info.preview;
|
|
if IsLottie then
|
|
ctype := 'fake/lottie'
|
|
else
|
|
ctype := info.mime;
|
|
HasMime := True;
|
|
end else if info.is_previewable then
|
|
begin
|
|
sALocal := info.preview;
|
|
ctype := info.mime;
|
|
HasMime := True;
|
|
end else
|
|
begin
|
|
sALocal := Trim(lnk);
|
|
ctype := info.mime;
|
|
HasMime := not (ctype = '');
|
|
end;
|
|
end else
|
|
sALocal := Trim(lnk);
|
|
|
|
if not HasMime then
|
|
ctype := ContentTypeFromURL(sALocal);
|
|
|
|
if not (ctype = '') and (pos(';', ctype) > 0) then
|
|
ctype := copy(ctype, 1, pos(';', ctype) - 1);
|
|
|
|
TThread.Synchronize(nil, procedure begin
|
|
CacheType(lnk, ctype);
|
|
end);
|
|
|
|
if IsLottie then
|
|
begin
|
|
Res := True;
|
|
extLocal := 'json';
|
|
end else if MatchText(ctype, ImageContentTypes) then
|
|
begin
|
|
Res := True;
|
|
idx := IndexText(ctype, ImageContentTypes);
|
|
if idx >= 0 then
|
|
extLocal := ImageExtensions[idx]
|
|
else
|
|
extLocal := 'jpg';
|
|
end;
|
|
|
|
FinishedEvent.SetEvent;
|
|
end).Start;
|
|
|
|
//WaitForResponse(FinishedEvent);
|
|
FinishedEvent.WaitFor(10000);
|
|
|
|
sA := sALocal;
|
|
ext := extLocal;
|
|
Result := Res;
|
|
end;
|
|
|
|
function DownloadAndCache(const lnk: String): Boolean;
|
|
var
|
|
ext: String;
|
|
sA: RawByteString;
|
|
res, lottie, zstd: Boolean;
|
|
buf, json: TMemoryStream;
|
|
begin
|
|
Result := False;
|
|
if not CheckType(lnk, sA, ext) then
|
|
Exit;
|
|
|
|
buf := TMemoryStream.Create;
|
|
lottie := IsLottieMime(lnk);
|
|
zstd := FileExists(ZSTDDllName);
|
|
if lottie and not zstd then
|
|
LoadFromURLAsStream(lnk.Replace('https://files.icq.net/get/', LOTTIE_ORIGINAL_HOST), buf, False)
|
|
else
|
|
begin
|
|
LoadFromURLAsStream(sA, buf, False);
|
|
if lottie then
|
|
begin
|
|
buf.Seek(0, 0);
|
|
json := TMemoryStream.Create;
|
|
try
|
|
ZSTDDecompressStream(buf, json);
|
|
buf.Clear;
|
|
buf.LoadFromStream(json);
|
|
finally
|
|
json.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
TThread.Synchronize(nil, procedure begin
|
|
if buf.Size = 0 then
|
|
CacheType(lnk, 'text/html')
|
|
else if lottie or IsSVGMime(lnk) then
|
|
res := CacheTextImage(buf, lnk, ext)
|
|
else
|
|
res := CacheImage(buf, lnk, ext);
|
|
end);
|
|
|
|
buf.Free;
|
|
Result := res;
|
|
end;
|
|
|
|
function CreateZip(str: TStringList): TMemoryStream;
|
|
var
|
|
Zip: TZipFile;
|
|
i: Integer;
|
|
fs: TFileStream;
|
|
pData: RawByteString;
|
|
begin
|
|
Result := TMemoryStream.Create;
|
|
Zip := TZipFile.Create;
|
|
try
|
|
for i := 0 to str.Count - 1 do
|
|
if FileExists(str.Strings[i]) then
|
|
begin
|
|
fs := TFileStream.Create(str.Strings[i], fmOpenRead);
|
|
SetLength(pData, fs.Size);
|
|
fs.ReadBuffer(pData[1], fs.Size);
|
|
FreeAndNil(fs);
|
|
Zip.AddFile(ExtractFileName(str.Strings[i]), 0, '', pData);
|
|
end;
|
|
Zip.SaveToStream(Result);
|
|
except end;
|
|
FreeAndNil(Zip);
|
|
end;
|
|
|
|
end.
|