package Ġnet . cord a . node . services . persistence Ċ Ċ import Ġcom . co da h ale . metrics . Metric Registry Ċ import Ġcom . google . common . annotations . Visible ForTesting Ċ import Ġcom . google . common . hash . Hash ing Ċ import Ġcom . google . common . hash . Hash ing InputStream Ċ import Ġcom . google . common . io . Count ing InputStream Ċ import Ġnet . cord a . core .* Ċ import Ġnet . cord a . core . contracts . Attachment Ċ import Ġnet . cord a . core . crypto . Secure Hash Ċ import Ġnet . cord a . core . node . services . Attachment Storage Ċ import Ġnet . cord a . core . utilities . logger For Ċ import Ġnet . cord a . node . services . api . Accept s FileUpload Ċ import Ġjava . io . Filter InputStream Ċ import Ġjava . io . InputStream Ċ import Ġjava . nio . file .* Ċ import Ġjava . util .* Ċ import Ġjava . util . jar . Jar InputStream Ċ import Ġjavax . annotation . concurrent . Thread Safe Ċ Ċ /** Ċ Ġ* ĠStores Ġattachments Ġin Ġthe Ġspecified Ġlocal Ġdirectory , Ġwhich Ġmust Ġexist . ĠDoes n 't Ġallow Ġnew Ġattachments Ġto Ġbe Ġuploaded . Ċ Ġ*/ Ċ @ Thread Safe Ċ class ĠNode Attachment Service ( val Ġstore Path : ĠPath , Ġmetrics : ĠMetric Registry ) Ġ: ĠAttachment Storage , ĠAccept s FileUpload Ġ{ ĊĠĠĠ Ġprivate Ġval Ġlog Ġ= Ġlogger For < Node Attachment Service >() ĊĊĠĠĠ Ġ@ Visible ForTesting ĊĠĠĠ Ġvar Ġcheck Attachments OnLoad Ġ= Ġtrue ĊĊĠĠĠ Ġprivate Ġval Ġattachment Count Ġ= Ġmetrics . counter (" Attachments ") ĊĊĠĠĠ Ġinit Ġ{ ĊĠĠĠĠĠĠĠ Ġattachment Count . inc ( count Attachments ()) ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġ// ĠJust Ġcount Ġall Ġnon - directories Ġin Ġthe Ġattachment Ġstore , Ġand Ġassume Ġthe Ġadmin Ġhasn 't Ġdumped Ġany Ġj unk Ġthere . ĊĠĠĠ Ġprivate Ġfun Ġcount Attachments () Ġ= Ġstore Path . list Ġ{ Ġit . filter Ġ{ Ġit . is Regular File () Ġ}. count () Ġ} ĊĊĠĠĠ Ġ/** ĊĠĠĠĠ Ġ* ĠIf Ġtrue , Ġnewly Ġinserted Ġattachments Ġwill Ġbe Ġun z ipped Ġto Ġa Ġsubdirectory Ġof Ġthe Ġ[ store Path ]. ĠThis Ġis Ġintended Ġfor ĊĠĠĠĠ Ġ* Ġhuman Ġb rowsing Ġconvenience : Ġthe Ġattachment Ġitself Ġwill Ġstill Ġbe Ġthe Ġfile Ġ( that Ġis , Ġedits Ġto Ġthe Ġextracted Ġdirectory ĊĠĠĠĠ Ġ* Ġwill Ġnot Ġhave Ġany Ġeffect ). ĊĠĠĠĠ Ġ*/ ĊĠĠĠ Ġ@ Volatile Ġvar Ġautomatically Extract Attachments Ġ= Ġfalse ĊĊĠĠĠ Ġinit Ġ{ ĊĠĠĠĠĠĠĠ Ġrequire ( store Path . isDirectory ()) Ġ{ Ġ"$ store Path Ġmust Ġbe Ġa Ġdirectory " Ġ} ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġclass ĠOn Disk Hash Mismatch ( val Ġfile : ĠPath , Ġval Ġactual : ĠSecure Hash ) Ġ: ĠException () Ġ{ ĊĠĠĠĠĠĠĠ Ġoverride Ġfun ĠtoString () Ġ= Ġ" File Ġ$ file Ġhashed Ġto Ġ$ actual : Ġcor ruption Ġin Ġattachment Ġstore ?" ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġ/** ĊĠĠĠĠ Ġ* ĠWrap s Ġa Ġstream Ġand Ġhashes Ġdata Ġas Ġit Ġis Ġread : Ġif Ġthe Ġentire Ġstream Ġis Ġconsumed , Ġthen Ġat Ġthe Ġend Ġthe Ġhash Ġof ĊĠĠĠĠ Ġ* Ġthe Ġread Ġdata Ġis Ġcompared Ġto Ġthe Ġ[ expected ] Ġhash Ġand Ġ[ On Disk Hash Mismatch ] Ġis Ġthrown Ġby Ġ[ close ] Ġif Ġthey Ġdidn 't ĊĠĠĠĠ Ġ* Ġmatch . ĠThe Ġgoal Ġof Ġthis Ġis Ġto Ġdetect Ġcases Ġwhere Ġattachments Ġin Ġthe Ġstore Ġhave Ġbeen Ġt amper ed Ġwith Ġor Ġcorrupted ĊĠĠĠĠ Ġ* Ġand Ġno Ġlonger Ġmatch Ġtheir Ġfile Ġname . ĠIt Ġwon 't Ġalways Ġwork : Ġif Ġwe Ġread Ġa Ġzip Ġfor Ġour Ġown Ġuses Ġand Ġskip Ġaround ĊĠĠĠĠ Ġ* Ġinside Ġit , Ġwe Ġhaven 't Ġread Ġthe Ġwhole Ġfile , Ġso Ġwe Ġcan 't Ġcheck Ġthe Ġhash . ĠBut Ġwhen Ġcopying Ġit Ġover Ġthe Ġnetwork ĊĠĠĠĠ Ġ* Ġthis Ġwill Ġprovide Ġan Ġadditional Ġsafety Ġcheck Ġagainst Ġuser Ġerror . ĊĠĠĠĠ Ġ*/ ĊĠĠĠ Ġprivate Ġclass ĠHash Checking Stream ( val Ġexpected : ĠSecure Hash . SHA 2 5 6 , ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġval ĠfilePath : ĠPath , ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġinput : ĠInputStream , ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġprivate Ġval Ġcounter : ĠCount ing InputStream Ġ= ĠCount ing InputStream ( input ), ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġprivate Ġval Ġstream : ĠHash ing InputStream Ġ= ĠHash ing InputStream ( Hash ing . sha 2 5 6 (), Ġcounter )) Ġ: ĠFilter InputStream ( stream ) Ġ{ ĊĊĠĠĠĠĠĠĠ Ġprivate Ġval Ġexpected Size Ġ= ĠfilePath . size ĊĊĠĠĠĠĠĠĠ Ġoverride Ġfun Ġclose () Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠ Ġsuper . close () ĊĠĠĠĠĠĠĠĠĠĠĠ Ġif Ġ( counter . count Ġ!= Ġexpected Size ) Ġreturn ĊĠĠĠĠĠĠĠĠĠĠĠ Ġval Ġactual Ġ= ĠSecure Hash . SHA 2 5 6 ( stream . hash (). as Bytes ()) ĊĠĠĠĠĠĠĠĠĠĠĠ Ġif Ġ( actual Ġ!= Ġexpected ) ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġthrow ĠOn Disk Hash Mismatch ( filePath , Ġactual ) ĊĠĠĠĠĠĠĠ Ġ} ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġ// ĠDel iber ately Ġnot Ġan Ġinner Ġclass Ġto Ġavoid Ġholding Ġa Ġreference Ġto Ġthe Ġattachments Ġservice . ĊĠĠĠ Ġprivate Ġclass ĠAttachment Impl ( override Ġval Ġid : ĠSecure Hash , ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġprivate Ġval Ġpath : ĠPath , ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġprivate Ġval Ġcheck OnLoad : ĠBoolean ) Ġ: ĠAttachment Ġ{ ĊĠĠĠĠĠĠĠ Ġoverride Ġfun Ġopen (): ĠInputStream Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠ Ġvar Ġstream Ġ= ĠFiles . new InputStream ( path ) ĊĠĠĠĠĠĠĠĠĠĠĠ Ġ// ĠThis Ġis Ġjust Ġan Ġoptional Ġsafety Ġcheck . ĠIf Ġit Ġslow s Ġthings Ġdown Ġtoo Ġmuch Ġit Ġcan Ġbe Ġdisabled . ĊĠĠĠĠĠĠĠĠĠĠĠ Ġif Ġ( id Ġis ĠSecure Hash . SHA 2 5 6 Ġ&& Ġcheck OnLoad ) ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġstream Ġ= ĠHash Checking Stream ( id , Ġpath , Ġstream ) ĊĠĠĠĠĠĠĠĠĠĠĠ Ġreturn Ġstream ĊĠĠĠĠĠĠĠ Ġ} ĊĊĠĠĠĠĠĠĠ Ġoverride Ġfun Ġequals ( other : ĠAny ?) Ġ= Ġother Ġis ĠAttachment Ġ&& Ġother . id Ġ== Ġid ĊĠĠĠĠĠĠĠ Ġoverride Ġfun ĠhashCode (): ĠInt Ġ= Ġid . hashCode () ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġoverride Ġfun Ġopen Attachment ( id : ĠSecure Hash ): ĠAttachment ? Ġ{ ĊĠĠĠĠĠĠĠ Ġval Ġpath Ġ= Ġstore Path Ġ/ Ġid . toString () ĊĠĠĠĠĠĠĠ Ġif Ġ(! path . exists ()) Ġreturn Ġnull ĊĠĠĠĠĠĠĠ Ġreturn ĠAttachment Impl ( id , Ġpath , Ġcheck Attachments OnLoad ) ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġ// ĠTODO : ĠP LT - 1 4 7 : ĠThe Ġattachment Ġshould Ġbe Ġrandom ised Ġto Ġprevent Ġbr ute Ġforce Ġguess ing Ġand Ġthus Ġprivacy Ġle aks . ĊĠĠĠ Ġoverride Ġfun Ġimport Attachment ( jar : ĠInputStream ): ĠSecure Hash Ġ{ ĊĠĠĠĠĠĠĠ Ġrequire ( jar Ġ! is ĠJar InputStream ) ĊĠĠĠĠĠĠĠ Ġval Ġhs Ġ= ĠHash ing InputStream ( Hash ing . sha 2 5 6 (), Ġjar ) ĊĠĠĠĠĠĠĠ Ġval Ġtmp Ġ= Ġstore Path Ġ/ Ġ" tmp .${ UUID . randomUUID () }" ĊĠĠĠĠĠĠĠ Ġhs . copy To ( tmp ) ĊĠĠĠĠĠĠĠ Ġcheck Is A Valid JAR ( tmp ) ĊĠĠĠĠĠĠĠ Ġval Ġid Ġ= ĠSecure Hash . SHA 2 5 6 ( hs . hash (). as Bytes ()) ĊĠĠĠĠĠĠĠ Ġval Ġfinal Path Ġ= Ġstore Path Ġ/ Ġid . toString () ĊĠĠĠĠĠĠĠ Ġtry Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠ Ġ// ĠMove Ġinto Ġplace Ġatom ically Ġor Ġfail Ġif Ġthat Ġisn 't Ġpossible . ĠWe Ġdon 't Ġwant Ġa Ġhalf Ġmoved Ġattachment Ġto ĊĠĠĠĠĠĠĠĠĠĠĠ Ġ// Ġbe Ġexposed Ġto Ġparallel Ġthreads . ĠThis Ġgives Ġus Ġthread Ġsafety . ĊĠĠĠĠĠĠĠĠĠĠĠ Ġif Ġ(! final Path . exists ()) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġlog . info (" Stored Ġnew Ġattachment Ġ$ id ") ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġattachment Count . inc () ĊĠĠĠĠĠĠĠĠĠĠĠ Ġ} Ġelse Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġlog . info (" Repl acing Ġattachment Ġ$ id Ġ- Ġonly Ġbother Ġdoing Ġthis Ġif Ġyou 're Ġtrying Ġto Ġrepair Ġfile Ġcor ruption ") ĊĠĠĠĠĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠĠĠĠĠ Ġtmp . moveTo ( final Path , ĠStandard Copy Option . ATOMIC _ MOVE ) ĊĠĠĠĠĠĠĠ Ġ} Ġfinally Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠ Ġtmp . delete IfExists () ĊĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠ Ġif Ġ( autom atically Extract Attachments ) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠ Ġval Ġextract To Ġ= Ġstore Path Ġ/ Ġ"$ id . jar " ĊĠĠĠĠĠĠĠĠĠĠĠ Ġtry Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġextract To . create Directory () ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġextract Zip File ( final Path , Ġextract To ) ĊĠĠĠĠĠĠĠĠĠĠĠ Ġ} Ġcatch ( e : ĠFile AlreadyExists Exception ) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġlog . trace (" Did Ġnot Ġextract Ġattachment Ġjar Ġto Ġdirectory Ġbecause Ġit Ġalready Ġexists ") ĊĠĠĠĠĠĠĠĠĠĠĠ Ġ} Ġcatch ( e : ĠException ) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġlog . error (" Failed Ġto Ġextract Ġattachment Ġjar Ġ$ id , Ġ", Ġe ) ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ// ĠTODO : ĠDelete Ġthe Ġextract To Ġdirectory Ġhere . ĊĠĠĠĠĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠ Ġreturn Ġid ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġprivate Ġfun Ġcheck Is A Valid JAR ( path : ĠPath ) Ġ{ ĊĠĠĠĠĠĠĠ Ġ// ĠJust Ġiterate Ġover Ġthe Ġentries Ġwith Ġverification Ġenabled : Ġshould Ġbe Ġgood Ġenough Ġto Ġcatch Ġmist akes . ĊĠĠĠĠĠĠĠ Ġpath . read Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠ Ġval Ġjar Ġ= ĠJar InputStream ( it ) ĊĠĠĠĠĠĠĠĠĠĠĠ Ġwhile Ġ( true ) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġval Ġcursor Ġ= Ġjar . next Jar Entry Ġ?: Ġbreak ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġval Ġentry Path Ġ= ĠPaths . get ( cursor . name ) ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ// ĠSecurity Ġcheck Ġto Ġstop Ġzip s Ġtrying Ġto Ġescape Ġtheir Ġright ful Ġplace . ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġif Ġ( entry Path . is Absolute Ġ|| Ġentry Path . normalize () Ġ!= Ġentry Path Ġ|| Ġ'\\ ' Ġin Ġcursor . name ) ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġthrow ĠIllegalArgumentException (" Path Ġis Ġeither Ġabsolute Ġor Ġnon - normal ised : Ġ$ entry Path ") ĊĠĠĠĠĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠ Ġ} ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġ// ĠImplement ations Ġfor ĠAccept s FileUpload ĊĠĠĠ Ġoverride Ġval ĠdataType Prefix Ġ= Ġ" attachment " ĊĠĠĠ Ġoverride Ġval Ġacceptable File Extensions Ġ= ĠlistOf (". jar ", Ġ". zip ") ĊĠĠĠ Ġoverride Ġfun Ġupload ( data : ĠInputStream ) Ġ= Ġimport Attachment ( data ). toString () Ċ } Ċ