SAS Packages Framework, version 20260202

SAS Packages Framework, version 20260202

Changes:

-If the `hash=` parameter is not provided the %verifyPackage() macro prints SHA256, SHA1, and MD5 hashes to the log now.
- Small fix in the %unbundlePackage() macro.
- Bunch general code simplifications.
- Documentation updated.
This commit is contained in:
Bart Jablonski
2026-02-02 13:06:45 +01:00
parent dcb012883f
commit 3fbc55c01c
24 changed files with 345 additions and 245 deletions
+64 -31
View File
@@ -8,12 +8,12 @@
, path = %sysfunc(pathname(packages)) /* location of a package,
by default it looks for
location of "packages" fileref */
, hash = /* The SHA256 hash digest for
, hash = F* /* The SHA256 hash digest for
the package generated by
hashing_file() function, SAS 9.4M6 */
)/secure
/*** HELP END ***/
des = 'Macro to verify SAS package with the hash digest, version 20260126. Run %verifyPackage() for help info.'
des = 'Macro to verify SAS package with the hash digest, version 20260202. Run %verifyPackage() for help info.'
;
%if (%superq(packageName) = ) OR (%qupcase(&packageName.) = HELP) %then
%do;
@@ -28,7 +28,7 @@ des = 'Macro to verify SAS package with the hash digest, version 20260126. Run %
%put ### This is short help information for the `verifyPackage` macro #;
%put #-------------------------------------------------------------------------------#;
%put # #;
%put # Macro to verify SAS package with it hash digest, version `20260126` #;
%put # Macro to verify SAS package with it hash digest, version `20260202` #;
%put # #;
%put # A SAS package is a zip file containing a group #;
%put # of SAS codes (macros, functions, data steps generating #;
@@ -48,7 +48,9 @@ des = 'Macro to verify SAS package with the hash digest, version 20260126. Run %
%put # If empty displays this help information. #;
%put # #;
%put # - `hash=` A value of the package `SHA256` hash. #;
%put # Provided by the user. #;
%put # Provided by the user. When the value is not provided #;
%put # then macro calculates `SHA256`, `SHA1`, and `MD5` #;
%put # digests and display then in the log. #;
%put # #;
%put # - `path=` Location of a package. By default it looks for #;
%put # location of the "packages" fileref, i.e. #;
@@ -76,7 +78,7 @@ des = 'Macro to verify SAS package with the hash digest, version 20260126. Run %
%put ;
%put %nrstr( %%installPackage(SQLinDS) %%* install the package from the Internet; );
%put %nrstr( %%verifyPackage%(SQLinDS, %%* verify the package with provided hash; );
%put %nrstr( hash=HDA478ANJ3HKHRY327FGE88HF89VH89HFFFV73GCV98RF390VB4%) );
%put %nrstr( hash=HDA478ANJ3HKHRY327FGE88HF89VH89HFFFV73GCV98RF390VB4%) );
%put ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~;
%put #################################################################################;
%put ;
@@ -97,19 +99,21 @@ des = 'Macro to verify SAS package with the hash digest, version 20260126. Run %
options NOnotes NOsource ls=MAX ps=MAX NOfullstimer NOstimer msglevel=N NOmautocomploc;
%local _PackageFileref_ checkExist;
data _null_;
call symputX("_PackageFileref_", "P" !! put(MD5(lowcase("&packageName.")), hex7. -L), "L");
run;
data _null_;
length packageName $ 32;
packageName = lowcase(symget("packageName"));
call symputX("_PackageFileref_", "P" !! put(MD5(strip(packageName)), hex7. -L), "L");
/*run;*/ /* <- comment out, because it can be 1 data step, not 2 */
/* when the packages reference is multi-directory search for the first one containing the package */
data _null_;
/*data _null_;*/ /* <- comment out, because it can be 1 data step, not 2 */
exists = 0;
length packages $ 32767 p $ 4096;
packages = resolve(symget("path"));
if char(packages,1) ^= "(" then packages = quote(strip(packages)); /* for paths with spaces */
do i = 1 to kcountw(packages, "()", "QS");
p = dequote(kscanx(packages, i, "()", "QS"));
exists + fileexist(catx("/", p, lowcase("&packageName.") !! ".zip")); /* check on zip files only! */
exists + fileexist(catx("/", p, cats(packageName,".zip"))); /* check on zip files only! */
if exists then leave;
end;
if exists then call symputx("path", p, "L");
@@ -142,31 +146,60 @@ des = 'Macro to verify SAS package with the hash digest, version 20260126. Run %
filename &_PackageFileref_. list;
data _null_;
length providedHash $ 128;
length providedHash $ 128 packageName $ 32;
providedHash = strip(symget("hash"));
select;
when ( 'F*' = upcase(substr(providedHash,1,2)) ) /* F = file digest */
SHA256 = 'F*' !! HASHING_FILE("SHA256", pathname("&_PackageFileref_.",'F'), 0);
when ( 'C*' = upcase(substr(providedHash,1,2)) ) /* C = content digest */
SHA256 = 'C*' !! HASHING_FILE("SHA256", "&_PackageFileref_.", 4);
otherwise /* legacy approach, without C or F, digest value equivalent to C */
SHA256 = HASHING_FILE("SHA256", "&_PackageFileref_.", 4);
end;
put "Provided Hash: " providedHash;
put "SHA256 digest: " SHA256;
put " ";
if upcase(SHA256) = upcase(providedHash) then
do;
put "NOTE: Package verification SUCCESSFUL.";
put "NOTE- Generated hash is EQUAL to the provided one.";
packageName = strip(symget("packageName"));
emptyHash = (providedHash = " " OR providedHash in ("F*" "f*" "C*" "c*"));
put 82*"-" / @2 packageName / 82*"-" /;
if NOT emptyHash then put "Provided Hash: " providedHash;
length method $ 8 digest $ 128;
/* calculate SHA256 */
method="SHA256";
LINK CalcualteHashDigest; /* go to Link 1 */
if NOT emptyHash then
do; /* step for veryfication */
if upcase(digest) = upcase(providedHash) then
do;
put "NOTE: Verification SUCCESSFUL."
/ "NOTE- Generated hash is EQUAL to the provided one." / ;
end;
else
do;
pos = 0;
do i = 1 to max(lengthn(digest),lengthn(providedHash)) while(pos=0);
if char(digest,i) NE char(providedHash,i) then pos = i;
end;
put "ERROR- " @(pos+15)"^"/"ERROR- " @(pos+15)"| diff @" pos/"ERROR- ";
put "ERROR: Verification FAILED!!"
/ "ERROR- Generated hash is DIFFERENT than the provided one."
/ "ERROR- Check if the ZIP is genuine." / ;
end;
end;
else
do;
put "ERROR: Package verification FAILED!!";
put "ERROR- Generated hash is DIFFERENT than the provided one.";
put "ERROR- Confirm if the package is genuine.";
do method = "SHA1", "MD5"; /* step for digest display, calcualte also SHA1 and MD5 */
LINK CalcualteHashDigest; /* go to Link 1 */
end;
put 82*"-" /;
stop;
return;
CalcualteHashDigest: /* Link 1 */
select;
when ( 'F*' = upcase(substr(providedHash,1,2)) ) /* F = file digest */
digest = 'F*' !! HASHING_FILE(method, pathname("&_PackageFileref_.",'F'), 0);
when ( 'C*' = upcase(substr(providedHash,1,2)) ) /* C = content digest */
digest = 'C*' !! HASHING_FILE(method, "&_PackageFileref_.", 4);
otherwise /* legacy approach, without C or F, digest value equivalent to C */
digest = HASHING_FILE(method, "&_PackageFileref_.", 4);
end;
put method "digest: " digest /;
return;
run;
%let HASHING_FILE_exist = 0;
%end;