/*+verifyPackage+*/ /*** HELP START ***/ %macro verifyPackage( packageName /* name of a package, e.g. myPackage, required and not null */ , path = %sysfunc(pathname(packages)) /* location of a package, by default it looks for location of "packages" fileref */ , 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 20260216. Run %verifyPackage() for help info.' ; %if (%superq(packageName) = ) OR (%qupcase(&packageName.) = HELP) %then %do; %local options_tmp ; %let options_tmp = ls=%sysfunc(getoption(ls)) ps=%sysfunc(getoption(ps)) %sysfunc(getoption(notes)) %sysfunc(getoption(source)) msglevel=%sysfunc(getoption(msglevel)) ; options NOnotes NOsource ls=MAX ps=MAX msglevel=N; %put ; %put #################################################################################; %put ### This is short help information for the `verifyPackage` macro #; %put #-------------------------------------------------------------------------------#; %put # #; %put # Macro to verify SAS package with it hash digest, version `20260216` #; %put # #; %put # A SAS package is a zip file containing a group #; %put # of SAS codes (macros, functions, data steps generating #; %put # data, etc.) wrapped up together and embedded inside the zip. #; %put # #; %put # The `%nrstr(%%verifyPackage())` macro generate package SHA256 hash #; %put # and compares it with the one provided by the user. #; %put # #; %put # #; %put # *Minimum SAS version required for the process is 9.4M6.* #; %put # #; %put #### Parameters: #; %put # #; %put # 1. `packageName` Name of a package, e.g. myPackage, #; %put # Required and not null, default use case: #; %put # `%nrstr(%%loadPackage(myPackage))`. #; %put # If empty displays this help information. #; %put # #; %put # - `hash=` A value of the package `SHA256` hash. #; %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. #; %put # `%nrstr(%%sysfunc(pathname(packages)))` #; %put # #; %put #-------------------------------------------------------------------------------#; %put # #; %put # Visit: `https://github.com/yabwon/SAS_PACKAGES/tree/main/SPF/Documentation` #; %put # to learn more. #; %put # Tutorials available at: `https://github.com/yabwon/HoW-SASPackages` #; %put # #; %put #### Example ####################################################################; %put # #; %put # Enabling the SAS Package Framework #; %put # from the local directory and installing & loading #; %put # the SQLinDS package from the Internet. #; %put # #; %put # Assume that the `SPFinit.sas` file #; %put # is located in the "C:/SAS_PACKAGES/" folder. #; %put # #; %put # Run the following code in your SAS session: #; %put ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas; %put %nrstr( filename packages "C:/SAS_PACKAGES"; %%* set-up a directory for packages; ); %put %nrstr( %%include packages(SPFinit.sas); %%* enable the framework; ); %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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; %put #################################################################################; %put ; options &options_tmp.; %GOTO ENDofverifyPackage; %end; %local ls_tmp ps_tmp notes_tmp source_tmp stimer_tmp fullstimer_tmp msglevel_tmp mautocomploc_tmp; %let ls_tmp = %sysfunc(getoption(ls)); %let ps_tmp = %sysfunc(getoption(ps)); %let notes_tmp = %sysfunc(getoption(notes)); %let source_tmp = %sysfunc(getoption(source)); %let stimer_tmp = %sysfunc(getoption(stimer)); %let fullstimer_tmp = %sysfunc(getoption(fullstimer)); %let msglevel_tmp = %sysfunc(getoption(msglevel)); %let mautocomploc_tmp = %sysfunc(getoption(mautocomploc)); options NOnotes NOsource ls=MAX ps=MAX NOfullstimer NOstimer msglevel=N NOmautocomploc; %local _PackageFileref_ checkExist; data _null_; length packageName $ 140; 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_;*/ /* <- 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, cats(packageName,".zip"))); /* check on zip files only! */ if exists then leave; end; if exists then call symputx("path", p, "L"); else call symputx("checkExist", '0 AND', "L"); run; filename &_PackageFileref_. /* put location of package myPackageFile.zip here */ "&path./%sysfunc(lowcase(&packageName.)).zip" ; %if &checkExist. %sysfunc(fexist(&_PackageFileref_.)) %then %do; /* create hash SHA256 id *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ %local HASHING_FILE_exist; %let HASHING_FILE_exist = 0; %if %sysfunc(exist(sashelp.vfunc, VIEW)) %then %do; data _null_; set sashelp.vfunc(keep=fncname); where fncname = "HASHING_FILE"; call symputX('HASHING_FILE_exist', 1, "L"); stop; run; %end; %if &HASHING_FILE_exist. = 1 %then %do; options notes; filename &_PackageFileref_. list; data _null_; length providedHash $ 128 packageName $ 140; providedHash = strip(symget("hash")); 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 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; %else %put WARNING: Verification impossible! Minimum SAS version required for the process is 9.4M6. ; /*-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-*/ %end; %else %put ERROR:[&sysmacroname] File "&path./&packageName..zip" does not exist!; filename &_PackageFileref_. clear; options ls = &ls_tmp. ps = &ps_tmp. ¬es_tmp. &source_tmp. &stimer_tmp. &fullstimer_tmp. msglevel=&msglevel_tmp. &mautocomploc_tmp.; %ENDofverifyPackage: %mend verifyPackage; /**/