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.
RnQ/for.RnQ/RnQNet.pas

1321 lines
41 KiB
Plaintext

unit RnQNet;
{$I forRnQConfig.inc}
{ $I RnQConfig.inc }
interface
uses
System.Generics.Collections, System.Types, System.SysUtils, System.NetConsts, System.SyncObjs, Vcl.ExtCtrls, Classes, Forms,
JSON, NetEncoding, RDGlobal, RnQGlobal, System.Net.HttpClient, System.Net.URLClient;
{$I NoRTTI.inc}
type
TProxyProto = (PP_NONE = 0, PP_HTTPS = 1);
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');
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; 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(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; 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 .. 26] 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',
'fake/lottie'
);
ImageExtensions: array [0 .. 26] 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',
'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; ErrorText: String = ''; const URL: String = '');
function HandleStatus(Code: Integer): Boolean;
function HandleStatusAndError(Code: Integer; 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, Base64, StrUtils,
RDUtils, RnQPrefsLib, RnQZip, ZSTD, ZSTDLib,
{$IFDEF UNICODE}
AnsiStrings,
{$ENDIF UNICODE}
{$IFDEF RNQ}
RnQLangs, RnQDialogs, RQUtil, utilLib, ICQConsts, ICQContacts, ICQSession, globalLib,
{$ENDIF RNQ}
{$IFDEF RNQ_PLUGIN}
RDPlugins,
{$ENDIF RNQ_PLUGIN}
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
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(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; 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 := 'http://' + 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; 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('store_id=') then
Exit;
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; 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 ' + Account.AccProto.MyAccNum + ' ' + ICQ_DEV_ID + ' 0.11.9999.' + IntToStr(RnQBuild) + '(' + IntToStr(RnQBuildCustom) + ') ' + TOSVersion.Name.Replace(' ', '_') + ' PC',
'ICQ Desktop ' + Account.AccProto.MyAccNum + ' ' + ICQ_DEV_ID + ' 10.0.0(' + 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_HTTPS then
if auth then
begin
UseDefaultCredentials := NTLM;
ProxySettings := TProxySettings.Create(addr.host, addr.port, user, pwd);
end
else
begin
UseDefaultCredentials := True;
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; 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; 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; 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; 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; 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;
Buf := TMemoryStream.Create;
LoadFromURLAsStream(FileIdStr, Buf, False);
SetLength(ImgStr, Buf.Size);
Buf.ReadBuffer(ImgStr[1], Buf.Size);
Buf.Free;
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;
Buf := TMemoryStream.Create;
LoadFromURLAsStream(FileIdStr, Buf, False);
SetLength(ImgStr, Buf.Size);
Buf.ReadBuffer(ImgStr[1], Buf.Size);
Buf.Free;
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 = 200) 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);
Result.avstatus := IfThen(JSONObject2.GetValue('antivirus_check') = nil, 'unknown', JSONObject2.GetValue('antivirus_check').Value);
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
Result.preview := JSONObject3.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 then
res := CacheLottie(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.