diff --git a/.github/workflows/Notifications.yml b/.github/workflows/Notifications.yml
new file mode 100644
index 00000000..bd385304
--- /dev/null
+++ b/.github/workflows/Notifications.yml
@@ -0,0 +1,70 @@
+name: TaurusTLS GitHub Notifications
+
+on:
+ pull_request:
+ types: [opened, reopened, closed, review_requested]
+ branches:
+ - main
+ issues:
+ types: [opened, reopened, closed]
+ issue_comment:
+ types: [created]
+
+jobs:
+ notify-telegram:
+ runs-on: ubuntu-latest
+ # Skip job if it's a comment on a PR (GitHub treats PRs as issues for comments)
+ if: ${{ github.event_name != 'issue_comment' || !github.event.issue.pull_request }}
+
+ steps:
+ - name: Set Event Details
+ id: event-info
+ run: |
+ if [ "${{ github.event_name }}" == "pull_request" ]; then
+ echo "TITLE=Pull Request ${{ github.event.action }}" >> $GITHUB_OUTPUT
+ echo "SUBJECT=${{ github.event.pull_request.title }}" >> $GITHUB_OUTPUT
+ echo "URL=${{ github.event.pull_request.html_url }}" >> $GITHUB_OUTPUT
+ echo "AUTHOR=${{ github.event.pull_request.user.login }}" >> $GITHUB_OUTPUT
+
+ elif [ "${{ github.event_name }}" == "issues" ]; then
+ echo "TITLE=Issue ${{ github.event.action }}" >> $GITHUB_OUTPUT
+ echo "SUBJECT=${{ github.event.issue.title }}" >> $GITHUB_OUTPUT
+ echo "URL=${{ github.event.issue.html_url }}" >> $GITHUB_OUTPUT
+ echo "AUTHOR=${{ github.event.issue.user.login }}" >> $GITHUB_OUTPUT
+
+ elif [ "${{ github.event_name }}" == "issue_comment" ]; then
+ echo "TITLE=New Comment on Issue #${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
+ echo "SUBJECT=${{ github.event.issue.title }}" >> $GITHUB_OUTPUT
+ echo "URL=${{ github.event.comment.html_url }}" >> $GITHUB_OUTPUT
+ echo "AUTHOR=${{ github.event.comment.user.login }}" >> $GITHUB_OUTPUT
+
+ elif [ "${{ github.event_name }}" == "workflow_run" ]; then
+ echo "TITLE=CI Build Failed" >> $GITHUB_OUTPUT
+ echo "SUBJECT=${{ github.repository }}" >> $GITHUB_OUTPUT
+ echo "URL=${{ github.event.workflow_run.html_url }}" >> $GITHUB_OUTPUT
+ echo "AUTHOR=GitHub Actions" >> $GITHUB_OUTPUT
+
+ else
+ echo "TITLE=Event Triggered: ${{ github.event_name }}" >> $GITHUB_OUTPUT
+ echo "SUBJECT=${{ github.repository }}" >> $GITHUB_OUTPUT
+ echo "URL=https://github.com/${{ github.repository }}" >> $GITHUB_OUTPUT
+ echo "AUTHOR=${{ github.actor }}" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Send Telegram Notification
+ uses: appleboy/telegram-action@master
+ with:
+ to: ${{ secrets.TLG_CHAT_ID_TAURUSTLS_ALERTS }}
+ token: ${{ secrets.TLG_BOT_TKN_TAURUSTLSDEV_BOT }}
+ message_thread_id: ${{ vars.TLG_THRD_ID_TAURUSTLS_ALERTS }}
+ disable_web_page_preview: true
+ format: markdown
+ message: |
+ 🛠️ *TaurusTLS GitHub Notification*
+
+ *Event:* ${{ steps.event-info.outputs.TITLE }}
+ *Subject:* ${{ steps.event-info.outputs.SUBJECT }}
+ *Repository:* `TaurusTLS/${{ github.event.repository.name }}`
+ *Triggered by:* @${{ steps.event-info.outputs.AUTHOR }}
+
+ [🔗 View on GitHub](${{ steps.event-info.outputs.URL }})
diff --git a/Source/TaurusTLS_Utils.pas b/Source/TaurusTLS_Utils.pas
index df42c1b2..503f7279 100644
--- a/Source/TaurusTLS_Utils.pas
+++ b/Source/TaurusTLS_Utils.pas
@@ -145,12 +145,6 @@ function MDAsString(const AMD: TTaurusTLSLEVP_MD): String; {$IFDEF USE_INLINE}
///
/// Returns the second of the minute.
///
-///
-/// Returns the Time Zone offset hour
-///
-///
-/// Returns the Time Zone offset minute.
-///
///
/// True if parsing was successful or False if failed.
///
@@ -260,6 +254,7 @@ implementation
{$IFDEF WINDOWS}
IdIDN,
{$ENDIF}
+ DateUtils,
TaurusTLS_ResourceStrings, TaurusTLSHeaders_bio, TaurusTLSHeaders_objects,
TaurusTLSHeaders_x509, TaurusTLSHeaders_x509_vfy, SysUtils;
@@ -555,106 +550,190 @@ function ASN1_OBJECT_ToStr(a: PASN1_OBJECT): String;
end;
end;
+// Inline helper function for `ASN1_Time_Decode`
+function CharArrayToWord(AChars: PIdAnsiChar; AStart, AEnd: TIdC_INT;
+ out AResult: Word): Boolean; {$IFDEF USE_INLINE}inline; {$ENDIF}
+var
+ i: TIdC_INT;
+ lChar: AnsiChar;
+ lResult: Word;
+begin
+ Result := Assigned(AChars) and (AStart >= 0) and (AEnd >= AStart);
+ if Result then
+ begin
+ lResult := 0;
+ for i := AStart to AEnd do
+ begin
+ lChar := AChars[i];
+ if lChar in ['0'..'9'] then
+ lResult := (lResult * 10) + (Byte(lChar) - Byte('0'))
+ else
+ Exit(False);
+ end;
+ AResult := lResult;
+ end;
+end;
+
+// Note: ASN1TimeToDateTime was previously the sole consumer of this function.
+// While ASN1TimeToDateTime has been refactored to use native OpenSSL routines
+// and ASN1_Time_Decode is no longer used internally within TaurusTLS, this function
+// remains part of the public API contract and must be preserved for compatibility.
+//
+// The legacy implementation relied on multiple temporary string allocations and
+// lacked adequate input validation for malformed time strings, making it inefficient
+// and prone to unexpected failures.
+//
+// The refactored version below provides a highly optimized, low-footprint parser
+// that extracts raw ASN.1 time components into local time values safely.
function ASN1_Time_Decode(const a: PASN1_TIME; out year, month, day, hour, min,
sec: Word; out tz_hour, tz_min: Integer): Boolean;
var
- i, tz_dir: Integer;
-{$IFDEF FPC}
- time_str: AnsiString;
-{$ELSE}
- time_str: string;
-{$ENDIF}
-{$IFNDEF USE_MARSHALLED_PTRS}
-{$IFNDEF STRING_IS_ANSI}
- LTemp: AnsiString;
-{$ENDIF}
-{$ENDIF}
+ lStr: PASN1_STRING;
+ lTime: PIdAnsiChar;
+ lLen: TIdC_INT;
+ lIdx: TIdC_INT;
+ lIsGeneralized: Boolean;
+ lRawYear: Word;
+ lSignIdx: TIdC_INT;
+ lPositiveTZ: Boolean;
+ lTZH, lTZM: Word;
+
begin
- year := 0;
- month := 1;
- day := 1;
- hour := 0;
- min := 0;
- sec := 0;
- tz_hour := 0;
- tz_min := 0;
- Result := False; { default is to return with an error indication }
- if ASN1_STRING_length(PASN1_STRING(a)) < 12 then //PALOFF - Possible bad typecast
+ year := 0; month := 0; day := 0;
+ hour := 0; min := 0; sec := 0;
+ tz_hour := 0; tz_min := 0;
+
+ lStr := PASN1_STRING(a);
+ if not Assigned(lStr) then
+ Exit(False);
+
+ lLen := ASN1_STRING_length(lStr);
+ lTime := PIdAnsiChar(ASN1_STRING_get0_data(lStr));
+
+ if not Assigned(lTime) or (lLen < 11) then
+ Exit(False);
+
+ // Determine if UTCTime (2-digit year) or GeneralizedTime (4-digit year)
+ // ASN1_STRING_type(lStr) can be checked, or infer from string length/format.
+ lIsGeneralized := (ASN1_STRING_type(lStr) = V_ASN1_GENERALIZEDTIME) or
+ ((lLen >= 15) and (lTime[14] in ['Z', 'z', '+', '-', '.', ',']));
+
+ lIdx := 0;
+
+ // 1. Parse Year
+ if lIsGeneralized then
begin
- Exit;
+ if not CharArrayToWord(lTime, lIdx, lIdx + 3, year) then Exit(False);
+ Inc(lIdx, 4);
+ end
+ else
+ begin
+ if not CharArrayToWord(lTime, lIdx, lIdx + 1, lRawYear) then Exit(False);
+ // UTCTime sliding window rule (RFC 5280): YY >= 50 -> 19YY, YY < 50 -> 20YY
+ if lRawYear >= 50 then
+ year := 1900 + lRawYear
+ else
+ year := 2000 + lRawYear;
+ Inc(lIdx, 2);
end;
-{$IFDEF USE_MARSHALLED_PTRS}
- time_str := TMarshal.ReadStringAsAnsi(TPtrWrapper.Create(ASN1_STRING_get0_data(PASN1_STRING(a))),
- ASN1_STRING_length(PASN1_STRING(a)));
-{$ELSE}
-{$IFDEF STRING_IS_ANSI}
- SetString(time_str, PIdAnsiChar(ASN1_STRING_get0_data(PASN1_STRING(a))), ASN1_STRING_length(PASN1_STRING(a)));
-{$ELSE}
- SetString(LTemp, PIdAnsiChar(ASN1_STRING_get0_data(PASN1_STRING(a))), ASN1_STRING_length(PASN1_STRING(a))); //PALOFF - Possible bad pointer usage [data : PByte cast to PAnsiChar]
- { Note: UTCtime is a type defined by OpenSSL and hence is ansistring and not UCS-2 }
- // TODO: do we need to use SetCodePage() here?
- time_str := String(LTemp); // explicit convert to Unicode
-{$ENDIF}
-{$ENDIF}
- // Check if first 12 chars are numbers
- if not IsNumeric(time_str, 12) then
+
+ // 2. Parse Month, Day, Hour, Minute
+ if not CharArrayToWord(lTime, lIdx, lIdx + 1, month) then Exit(False);
+ if not CharArrayToWord(lTime, lIdx + 2, lIdx + 3, day) then Exit(False);
+ if not CharArrayToWord(lTime, lIdx + 4, lIdx + 5, hour) then Exit(False);
+ if not CharArrayToWord(lTime, lIdx + 6, lIdx + 7, min) then Exit(False);
+ Inc(lIdx, 8);
+
+ // 3. Parse Seconds (optional in some BER structures, standard in DER)
+ if (lIdx + 1 < lLen) and (lTime[lIdx] in ['0'..'9']) then
begin
- Exit;
+ if not CharArrayToWord(lTime, lIdx, lIdx + 1, sec) then Exit(False);
+ Inc(lIdx, 2);
+ end
+ else
+ sec := 0;
+
+ // 4. Skip Fractional Seconds (.sss or ,sss if present in GeneralizedTime)
+ if (lIdx < lLen) and (lTime[lIdx] in ['.', ',']) then
+ begin
+ Inc(lIdx);
+ while (lIdx < lLen) and (lTime[lIdx] in ['0'..'9']) do
+ Inc(lIdx);
end;
- // Convert time from string to number
- year := IndyStrToInt(Copy(time_str, 1, 2)) + 1900;
- month := IndyStrToInt(Copy(time_str, 3, 2));
- day := IndyStrToInt(Copy(time_str, 5, 2));
- hour := IndyStrToInt(Copy(time_str, 7, 2));
- min := IndyStrToInt(Copy(time_str, 9, 2));
- sec := IndyStrToInt(Copy(time_str, 11, 2));
- // Fix year. This function is Y2k but isn't compatible with Y2k5 :-( {Do not Localize}
- if year < 1950 then
+
+ // 5. Parse Timezone Portion
+ if (lIdx >= lLen) or (lTime[lIdx] in ['Z', 'z']) then
begin
- Inc(year, 100);
+ // UTC / Zulu time
+ tz_hour := 0;
+ tz_min := 0;
+ Exit(True);
end;
- // Check TZ
- tz_hour := 0;
- tz_min := 0;
- if CharIsInSet(time_str, 13, '-+') then
- begin { Do not Localize }
- tz_dir := iif(CharEquals(time_str, 13, '-'), -1, 1); { Do not Localize }
- for i := 14 to 18 do
- begin // Check if numbers are numbers
- if i = 16 then
- begin
- Continue;
- end;
- if not IsNumeric(time_str[i]) then
- begin
- Exit;
- end;
- end;
- tz_hour := IndyStrToInt(Copy(time_str, 14, 15)) * tz_dir;
- tz_min := IndyStrToInt(Copy(time_str, 17, 18)) * tz_dir;
+
+ // Scan for offset sign '+' or '-'
+ lSignIdx := lIdx;
+ while (lSignIdx < lLen) and not (lTime[lSignIdx] in ['+', '-']) do
+ Inc(lSignIdx);
+
+ if lSignIdx >= lLen then
+ Exit(True); // Implicit local time or no explicit offset provided
+
+ lPositiveTZ := (lTime[lSignIdx] = '+');
+
+ // Parse TZ Hours (2 digits)
+ if not CharArrayToWord(lTime, lSignIdx + 1, lSignIdx + 2, lTZH) then
+ Exit(False);
+
+ // Parse TZ Minutes (2 digits, if available)
+ if (lSignIdx + 4 < lLen) and (lTime[lSignIdx + 3] in ['0'..'9']) then
+ begin
+ if not CharArrayToWord(lTime, lSignIdx + 3, lSignIdx + 4, lTZM) then
+ Exit(False);
+ end
+ else
+ lTZM := 0;
+
+ if lPositiveTZ then
+ begin
+ tz_hour := lTZH;
+ tz_min := lTZM;
+ end
+ else
+ begin
+ tz_hour := -Integer(lTZH);
+ tz_min := -Integer(lTZM);
end;
- Result := True; { everthing OK }
+
+ Result := True;
end;
function ASN1TimeToDateTime(a: PASN1_TIME): TDateTime;
var
- year: Word;
- month: Word;
- day: Word;
- hour: Word;
- min: Word;
- sec: Word;
- tz_h: Integer;
- tz_m: Integer;
+ lTm: TIdC_TM;
+ lUtcDT: TDateTime;
+ lSec: Word;
+
begin
Result := 0;
- if ASN1_Time_Decode(a, year, month, day, hour, min, sec, tz_h, tz_m) then
+ if not Assigned(a) then
+ Exit;
+
+ {$IFDEF DCC}{$WARN UNSAFE_CODE OFF}{$ENDIF}
+ if ASN1_TIME_to_tm(a, @lTm) = 1 then
begin
- Result := EncodeDate(year, month, day) + EncodeTime(hour, min, sec, 0);
- Result := Result + (tz_m / (60 * 24));
- Result := Result + (tz_h / 24.0);
- Result := UTCTimeToLocalTime(Result);
+ // Handle leap seconds safely (clamp sec = 60 to 59)
+ lSec := lTm.tm_sec;
+ if lSec > 59 then
+ lSec := 59;
+
+ // Safely encode UTC DateTime without throwing exceptions on edge cases
+ if TryEncodeDateTime(lTm.tm_year + 1900, lTm.tm_mon + 1, lTm.tm_mday,
+ lTm.tm_hour, lTm.tm_min, lSec, 0, lUtcDT) then
+ begin
+ Result := UTCTimeToLocalTime(lUtcDT);
+ end;
end;
+ {$IFDEF DCC}{$WARN UNSAFE_CODE DEFAULT}{$ENDIF}
end;
function DirName(const ADirName: PX509_NAME): String;
diff --git a/Tests/TaurusTLS.UT.Headers.Bio.pas b/Tests/TaurusTLS.UT.Headers.Bio.pas
index 26262547..df7cf0a2 100644
--- a/Tests/TaurusTLS.UT.Headers.Bio.pas
+++ b/Tests/TaurusTLS.UT.Headers.Bio.pas
@@ -178,21 +178,11 @@ procedure TBioReadWriteFixture.Test_BIO_new_mem_buf(const AValue: RawByteString)
end;
end;
-type
- // https://github.com/openssl/openssl/blob/bde55d421b1f49e31248c240efe50ff1f0d24141/include/openssl/buffer.h#L42
- PBUF_MEMM = ^TBUF_MEMM;
- TBUF_MEMM = record
- length: TIdC_SIZET;
- data: PIdAnsiChar;
- max: TIdC_SIZET;
- flags: TIdC_UINT;
- end;
-
procedure TBioReadWriteFixture.Test_BIO_new_mem_ref(const AValue: RawByteString);
var
lBio: PBIO;
- lLen: TIdC_INT;
- lRef: PBUF_MEM;
+ lLen, lBioLen: TIdC_INT;
+ lBioData: pointer;
begin
lBio:=nil;
@@ -201,11 +191,11 @@ procedure TBioReadWriteFixture.Test_BIO_new_mem_ref(const AValue: RawByteString)
try
lBio:=BIO_new_mem_buf(AValue[1], lLen);
Assert.IsNotNull(lBio, 'lBio is ''nil''');
- Assert.AreEqual(1, BIO_get_mem_ptr(lBio, lRef), 'BIO_get_mem_ptr');
- Assert.AreEqual(lLen, PBUF_MEMM(lRef)^.length,
- '@AValue[1] <> PBUF_MEMM(lRef)^.length');
- Assert.AreEqual(@AValue[1], PBUF_MEMM(lRef)^.data,
- '@AValue[1] <> PBUF_MEMM(lRef)^.data');
+ lBioLen:=BIO_get_mem_data(lBio, lBioData);
+ Assert.AreEqual(lLen, lBioLen,
+ 'BIO Lengths and Original Data Lengths are not equal.');
+ Assert.AreEqual(@AValue[1], lBioData,
+ 'BIO does not refer Origonal data');
finally
BIO_free(lBio);
end;
diff --git a/Tests/TaurusTLS.UT.Utils.pas b/Tests/TaurusTLS.UT.Utils.pas
index 8e0556a5..e288d06e 100644
--- a/Tests/TaurusTLS.UT.Utils.pas
+++ b/Tests/TaurusTLS.UT.Utils.pas
@@ -27,22 +27,35 @@ interface
/// TestFixtures derived from
TOsslLoader = class
+ {$IFNDEF OPENSSL_STATIC_LINK_MODEL}
public const
- cEnvVarName = 'OPENSSL_PATH';
- cShortOptName = 'osp';
- cLongOptName = 'opensslpath';
+ cEnvVarPathName = 'OPENSSL_PATH';
+ cShortOptPathName = 'osp';
+ cLongOptPathName = 'opensslpath';
+
+ cEnvVarModulesPathName = 'OPENSSL_MODULES';
+ cShortOptModulesPathName = 'osmp';
+ cLongOptModulesPathName = 'opensslmodulespath';
private class var
FLoader: IOpenSSLLoader;
FLoadCount: FixedUInt;
+ FLock: TObject;
private
+ class procedure Lock; {$IFDEF USE_INLINE}inline; {$ENDIF}
+ class procedure Unlock; {$IFDEF USE_INLINE}inline; {$ENDIF}
class procedure RegisterOptions; static;
class function GetFromEnvVar: string; static;
class procedure SetPath(const Value: string); static;
class function GetPath: string; static;
+ class function GetProvidersPath: string; static;
+ class procedure SetProvidersPath(const Value: string); static;
+ {$ENDIF}
class function GetLoaded: boolean; static;
public
+ {$IFNDEF OPENSSL_STATIC_LINK_MODEL}
class constructor Create;
class destructor Destroy;
+ {$ENDIF}
///
/// Tries to load Openssl library. Can be called multiple times.
///
@@ -68,6 +81,7 @@ TOsslLoader = class
///
class property Loaded: boolean read GetLoaded;
+ {$IFNDEF OPENSSL_STATIC_LINK_MODEL}
///
/// Allows to change path to the Openssl library folder
///
@@ -75,6 +89,15 @@ TOsslLoader = class
///
///
class property Path: string read GetPath write SetPath;
+ ///
+ /// Allows to change path to the Openssl module provider libraries folder
+ ///
+ /// Path can be changed if Openssl library unloaded, otherwise exception is rised.
+ ///
+ ///
+ class property ProvidersPath: string read GetProvidersPath
+ write SetProvidersPath;
+ {$ENDIF}
end;
{$IFDEF USE_FASTMM5}
@@ -110,17 +133,18 @@ TFastMMDebugLog = class
{$ENDIF}
resourcestring
- rcOssLoaderHelp = 'Specify path to OpenSSL library folder';
+ rcOssLoaderPathHelp = 'Specify path to OpenSSL library folder';
+ rcOssLoaderModulesPathHelp = 'Specify path to OpenSSL module provider libraries'' folder';
rcFastMMDebugEnableHelp = 'Enable or disable detailed memory leak';
rcFastMMLogHelp = 'Memory leak report file name.';
-{$IFDEF USE_FASTMM5}
- {$IFDEF WIN32}
+ {$IFDEF USE_FASTMM5}
+ {$IFDEF WIN32}
rcFastMMDllNamelHelp = 'Path to FastMM_FullDebugMode.dll';
- {$ENDIF}
- {$IFDEF WIN64}
+ {$ENDIF}
+ {$IFDEF WIN64}
rcFastMMDllNamelHelp = 'Path to FastMM_FullDebugMode64.dll';
+ {$ENDIF}
{$ENDIF}
-{$ENDIF}
implementation
@@ -132,8 +156,10 @@ implementation
{ TOsslLoader }
+{$IFNDEF OPENSSL_STATIC_LINK_MODEL}
class constructor TOsslLoader.Create;
begin
+ FLock:=TObject.Create;
FLoader:=GetOpenSSLLoader;
Path:=GetFromEnvVar;
RegisterOptions;
@@ -142,60 +168,41 @@ implementation
class destructor TOsslLoader.Destroy;
begin
Unload;
+ FreeAndNil(FLock);
end;
-class function TOsslLoader.GetFromEnvVar: string;
-begin
- Result:=GetEnvironmentVariable(cEnvVarName);
-end;
-
-class function TOsslLoader.GetLoaded: boolean;
+class procedure TOsslLoader.Lock;
begin
- Result:=FLoadCount > 0;
+ TMonitor.Enter(FLock);
end;
-class function TOsslLoader.Load: boolean;
+class procedure TOsslLoader.Unlock;
begin
- Result:=Loaded;
- if Result then
- begin
- TInterlocked.Increment(FLoadCount);
- Exit(True);
- end;
- FLoader:=GetOpenSSLLoader;
- Result:=FLoader.Load;
- if Result then
- TInterlocked.Increment(FLoadCount);
+ TMonitor.Exit(FLock);
end;
-class procedure TOsslLoader.Unload;
-var
- lLoadCount: cardinal;
- lSpinW: TSpinWait;
-
+class function TOsslLoader.GetFromEnvVar: string;
begin
- lSpinW.Reset;
- repeat
- lLoadCount:=FLoadCount;
- if lLoadCount = 0 then
- break;
- if TInterlocked.CompareExchange(FLoadCount, lLoadCount-1, lLoadCount) = lLoadCount then
- break;
- lSpinW.SpinCycle;
- until False;
- if lLoadCount > 0 then
- FLoader.Unload;
+ Result:=GetEnvironmentVariable(cEnvVarPathName);
end;
class procedure TOsslLoader.RegisterOptions;
begin
- TOptionsRegistry.RegisterOption(cLongOptName, cShortOptName,
- rcOssLoaderHelp,
+ TOptionsRegistry.RegisterOption(cLongOptPathName, cShortOptPathName,
+ rcOssLoaderPathHelp,
procedure(Value: string)
begin
Path:=Value;
end
);
+
+ TOptionsRegistry.RegisterOption(cLongOptModulesPathName,
+ cShortOptModulesPathName, rcOssLoaderModulesPathHelp,
+ procedure(Value: string)
+ begin
+ ProvidersPath:=Value;
+ end
+ );
end;
class function TOsslLoader.GetPath: string;
@@ -203,11 +210,79 @@ class function TOsslLoader.GetPath: string;
Result:=FLoader.OpenSSLPath;
end;
+class function TOsslLoader.GetProvidersPath: string;
+begin
+ Result:=FLoader.ProvidersPath;
+end;
+
class procedure TOsslLoader.SetPath(const Value: string);
begin
FLoader.OpenSSLPath:=Value;
end;
+class procedure TOsslLoader.SetProvidersPath(const Value: string);
+begin
+ FLoader.ProvidersPath:=Value;
+end;
+{$ENDIF}
+
+class function TOsslLoader.GetLoaded: boolean;
+begin
+ {$IFDEF OPENSSL_STATIC_LINK_MODEL}
+ Result:=True;
+ {$ELSE}
+ Result:=FLoadCount > 0;
+ {$ENDIF}
+end;
+
+class function TOsslLoader.Load: boolean;
+begin
+ {$IFDEF OPENSSL_STATIC_LINK_MODEL}
+ Result:=True;
+ {$ELSE}
+ Lock;
+ try
+ if FLoadCount = 0 then
+ begin
+ if FLoader = nil then
+ FLoader:=GetOpenSSLLoader;
+
+ Result := FLoader.Load;
+ if not Result then
+ Exit(False);
+ end
+ else
+ Result := True;
+
+ Inc(FLoadCount);
+ finally
+ Unlock;
+ end;
+ {$ENDIF}
+end;
+
+class procedure TOsslLoader.Unload;
+begin
+ {$IFNDEF OPENSSL_STATIC_LINK_MODEL}
+ Lock;
+ try
+ if FLoadCount > 0 then
+ begin
+ Dec(FLoadCount);
+ if FLoadCount = 0 then
+ begin
+ if Assigned(FLoader) then
+ begin
+ FLoader.Unload;
+ end;
+ end;
+ end;
+ finally
+ Unlock;
+ end;
+ {$ENDIF}
+end;
+
{$IFDEF USE_FASTMM5}
{ TFastMMDebugLoader }