ZipFile.cs 133 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915
  1. using ICSharpCode.SharpZipLib.Checksum;
  2. using ICSharpCode.SharpZipLib.Core;
  3. using ICSharpCode.SharpZipLib.Encryption;
  4. using ICSharpCode.SharpZipLib.Zip.Compression;
  5. using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
  6. using System;
  7. using System.Collections;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Security.Cryptography;
  11. using System.Text;
  12. namespace ICSharpCode.SharpZipLib.Zip
  13. {
  14. #region Keys Required Event Args
  15. /// <summary>
  16. /// Arguments used with KeysRequiredEvent
  17. /// </summary>
  18. public class KeysRequiredEventArgs : EventArgs
  19. {
  20. #region Constructors
  21. /// <summary>
  22. /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see>
  23. /// </summary>
  24. /// <param name="name">The name of the file for which keys are required.</param>
  25. public KeysRequiredEventArgs(string name)
  26. {
  27. fileName = name;
  28. }
  29. /// <summary>
  30. /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see>
  31. /// </summary>
  32. /// <param name="name">The name of the file for which keys are required.</param>
  33. /// <param name="keyValue">The current key value.</param>
  34. public KeysRequiredEventArgs(string name, byte[] keyValue)
  35. {
  36. fileName = name;
  37. key = keyValue;
  38. }
  39. #endregion Constructors
  40. #region Properties
  41. /// <summary>
  42. /// Gets the name of the file for which keys are required.
  43. /// </summary>
  44. public string FileName
  45. {
  46. get { return fileName; }
  47. }
  48. /// <summary>
  49. /// Gets or sets the key value
  50. /// </summary>
  51. public byte[] Key
  52. {
  53. get { return key; }
  54. set { key = value; }
  55. }
  56. #endregion Properties
  57. #region Instance Fields
  58. private readonly string fileName;
  59. private byte[] key;
  60. #endregion Instance Fields
  61. }
  62. #endregion Keys Required Event Args
  63. #region Test Definitions
  64. /// <summary>
  65. /// The strategy to apply to testing.
  66. /// </summary>
  67. public enum TestStrategy
  68. {
  69. /// <summary>
  70. /// Find the first error only.
  71. /// </summary>
  72. FindFirstError,
  73. /// <summary>
  74. /// Find all possible errors.
  75. /// </summary>
  76. FindAllErrors,
  77. }
  78. /// <summary>
  79. /// The operation in progress reported by a <see cref="ZipTestResultHandler"/> during testing.
  80. /// </summary>
  81. /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso>
  82. public enum TestOperation
  83. {
  84. /// <summary>
  85. /// Setting up testing.
  86. /// </summary>
  87. Initialising,
  88. /// <summary>
  89. /// Testing an individual entries header
  90. /// </summary>
  91. EntryHeader,
  92. /// <summary>
  93. /// Testing an individual entries data
  94. /// </summary>
  95. EntryData,
  96. /// <summary>
  97. /// Testing an individual entry has completed.
  98. /// </summary>
  99. EntryComplete,
  100. /// <summary>
  101. /// Running miscellaneous tests
  102. /// </summary>
  103. MiscellaneousTests,
  104. /// <summary>
  105. /// Testing is complete
  106. /// </summary>
  107. Complete,
  108. }
  109. /// <summary>
  110. /// Status returned returned by <see cref="ZipTestResultHandler"/> during testing.
  111. /// </summary>
  112. /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso>
  113. public class TestStatus
  114. {
  115. #region Constructors
  116. /// <summary>
  117. /// Initialise a new instance of <see cref="TestStatus"/>
  118. /// </summary>
  119. /// <param name="file">The <see cref="ZipFile"/> this status applies to.</param>
  120. public TestStatus(ZipFile file)
  121. {
  122. file_ = file;
  123. }
  124. #endregion Constructors
  125. #region Properties
  126. /// <summary>
  127. /// Get the current <see cref="TestOperation"/> in progress.
  128. /// </summary>
  129. public TestOperation Operation
  130. {
  131. get { return operation_; }
  132. }
  133. /// <summary>
  134. /// Get the <see cref="ZipFile"/> this status is applicable to.
  135. /// </summary>
  136. public ZipFile File
  137. {
  138. get { return file_; }
  139. }
  140. /// <summary>
  141. /// Get the current/last entry tested.
  142. /// </summary>
  143. public ZipEntry Entry
  144. {
  145. get { return entry_; }
  146. }
  147. /// <summary>
  148. /// Get the number of errors detected so far.
  149. /// </summary>
  150. public int ErrorCount
  151. {
  152. get { return errorCount_; }
  153. }
  154. /// <summary>
  155. /// Get the number of bytes tested so far for the current entry.
  156. /// </summary>
  157. public long BytesTested
  158. {
  159. get { return bytesTested_; }
  160. }
  161. /// <summary>
  162. /// Get a value indicating wether the last entry test was valid.
  163. /// </summary>
  164. public bool EntryValid
  165. {
  166. get { return entryValid_; }
  167. }
  168. #endregion Properties
  169. #region Internal API
  170. internal void AddError()
  171. {
  172. errorCount_++;
  173. entryValid_ = false;
  174. }
  175. internal void SetOperation(TestOperation operation)
  176. {
  177. operation_ = operation;
  178. }
  179. internal void SetEntry(ZipEntry entry)
  180. {
  181. entry_ = entry;
  182. entryValid_ = true;
  183. bytesTested_ = 0;
  184. }
  185. internal void SetBytesTested(long value)
  186. {
  187. bytesTested_ = value;
  188. }
  189. #endregion Internal API
  190. #region Instance Fields
  191. private readonly ZipFile file_;
  192. private ZipEntry entry_;
  193. private bool entryValid_;
  194. private int errorCount_;
  195. private long bytesTested_;
  196. private TestOperation operation_;
  197. #endregion Instance Fields
  198. }
  199. /// <summary>
  200. /// Delegate invoked during <see cref="ZipFile.TestArchive(bool, TestStrategy, ZipTestResultHandler)">testing</see> if supplied indicating current progress and status.
  201. /// </summary>
  202. /// <remarks>If the message is non-null an error has occured. If the message is null
  203. /// the operation as found in <see cref="TestStatus">status</see> has started.</remarks>
  204. public delegate void ZipTestResultHandler(TestStatus status, string message);
  205. #endregion Test Definitions
  206. #region Update Definitions
  207. /// <summary>
  208. /// The possible ways of <see cref="ZipFile.CommitUpdate()">applying updates</see> to an archive.
  209. /// </summary>
  210. public enum FileUpdateMode
  211. {
  212. /// <summary>
  213. /// Perform all updates on temporary files ensuring that the original file is saved.
  214. /// </summary>
  215. Safe,
  216. /// <summary>
  217. /// Update the archive directly, which is faster but less safe.
  218. /// </summary>
  219. Direct,
  220. }
  221. #endregion Update Definitions
  222. #region ZipFile Class
  223. /// <summary>
  224. /// This class represents a Zip archive. You can ask for the contained
  225. /// entries, or get an input stream for a file entry. The entry is
  226. /// automatically decompressed.
  227. ///
  228. /// You can also update the archive adding or deleting entries.
  229. ///
  230. /// This class is thread safe for input: You can open input streams for arbitrary
  231. /// entries in different threads.
  232. /// <br/>
  233. /// <br/>Author of the original java version : Jochen Hoenicke
  234. /// </summary>
  235. /// <example>
  236. /// <code>
  237. /// using System;
  238. /// using System.Text;
  239. /// using System.Collections;
  240. /// using System.IO;
  241. ///
  242. /// using ICSharpCode.SharpZipLib.Zip;
  243. ///
  244. /// class MainClass
  245. /// {
  246. /// static public void Main(string[] args)
  247. /// {
  248. /// using (ZipFile zFile = new ZipFile(args[0])) {
  249. /// Console.WriteLine("Listing of : " + zFile.Name);
  250. /// Console.WriteLine("");
  251. /// Console.WriteLine("Raw Size Size Date Time Name");
  252. /// Console.WriteLine("-------- -------- -------- ------ ---------");
  253. /// foreach (ZipEntry e in zFile) {
  254. /// if ( e.IsFile ) {
  255. /// DateTime d = e.DateTime;
  256. /// Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}", e.Size, e.CompressedSize,
  257. /// d.ToString("dd-MM-yy"), d.ToString("HH:mm"),
  258. /// e.Name);
  259. /// }
  260. /// }
  261. /// }
  262. /// }
  263. /// }
  264. /// </code>
  265. /// </example>
  266. public class ZipFile : IEnumerable, IDisposable
  267. {
  268. #region KeyHandling
  269. /// <summary>
  270. /// Delegate for handling keys/password setting during compresion/decompression.
  271. /// </summary>
  272. public delegate void KeysRequiredEventHandler(
  273. object sender,
  274. KeysRequiredEventArgs e
  275. );
  276. /// <summary>
  277. /// Event handler for handling encryption keys.
  278. /// </summary>
  279. public KeysRequiredEventHandler KeysRequired;
  280. /// <summary>
  281. /// Handles getting of encryption keys when required.
  282. /// </summary>
  283. /// <param name="fileName">The file for which encryption keys are required.</param>
  284. private void OnKeysRequired(string fileName)
  285. {
  286. if (KeysRequired != null)
  287. {
  288. var krea = new KeysRequiredEventArgs(fileName, key);
  289. KeysRequired(this, krea);
  290. key = krea.Key;
  291. }
  292. }
  293. /// <summary>
  294. /// Get/set the encryption key value.
  295. /// </summary>
  296. private byte[] Key
  297. {
  298. get { return key; }
  299. set { key = value; }
  300. }
  301. /// <summary>
  302. /// Password to be used for encrypting/decrypting files.
  303. /// </summary>
  304. /// <remarks>Set to null if no password is required.</remarks>
  305. public string Password
  306. {
  307. set
  308. {
  309. if (string.IsNullOrEmpty(value))
  310. {
  311. key = null;
  312. }
  313. else
  314. {
  315. rawPassword_ = value;
  316. key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(value));
  317. }
  318. }
  319. }
  320. /// <summary>
  321. /// Get a value indicating wether encryption keys are currently available.
  322. /// </summary>
  323. private bool HaveKeys
  324. {
  325. get { return key != null; }
  326. }
  327. #endregion KeyHandling
  328. #region Constructors
  329. /// <summary>
  330. /// Opens a Zip file with the given name for reading.
  331. /// </summary>
  332. /// <param name="name">The name of the file to open.</param>
  333. /// <exception cref="ArgumentNullException">The argument supplied is null.</exception>
  334. /// <exception cref="IOException">
  335. /// An i/o error occurs
  336. /// </exception>
  337. /// <exception cref="ZipException">
  338. /// The file doesn't contain a valid zip archive.
  339. /// </exception>
  340. public ZipFile(string name)
  341. {
  342. name_ = name ?? throw new ArgumentNullException(nameof(name));
  343. baseStream_ = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read);
  344. isStreamOwner = true;
  345. try
  346. {
  347. ReadEntries();
  348. }
  349. catch
  350. {
  351. DisposeInternal(true);
  352. throw;
  353. }
  354. }
  355. /// <summary>
  356. /// Opens a Zip file reading the given <see cref="FileStream"/>.
  357. /// </summary>
  358. /// <param name="file">The <see cref="FileStream"/> to read archive data from.</param>
  359. /// <exception cref="ArgumentNullException">The supplied argument is null.</exception>
  360. /// <exception cref="IOException">
  361. /// An i/o error occurs.
  362. /// </exception>
  363. /// <exception cref="ZipException">
  364. /// The file doesn't contain a valid zip archive.
  365. /// </exception>
  366. public ZipFile(FileStream file) :
  367. this(file, false)
  368. {
  369. }
  370. /// <summary>
  371. /// Opens a Zip file reading the given <see cref="FileStream"/>.
  372. /// </summary>
  373. /// <param name="file">The <see cref="FileStream"/> to read archive data from.</param>
  374. /// <param name="leaveOpen">true to leave the <see cref="FileStream">file</see> open when the ZipFile is disposed, false to dispose of it</param>
  375. /// <exception cref="ArgumentNullException">The supplied argument is null.</exception>
  376. /// <exception cref="IOException">
  377. /// An i/o error occurs.
  378. /// </exception>
  379. /// <exception cref="ZipException">
  380. /// The file doesn't contain a valid zip archive.
  381. /// </exception>
  382. public ZipFile(FileStream file, bool leaveOpen)
  383. {
  384. if (file == null)
  385. {
  386. throw new ArgumentNullException(nameof(file));
  387. }
  388. if (!file.CanSeek)
  389. {
  390. throw new ArgumentException("Stream is not seekable", nameof(file));
  391. }
  392. baseStream_ = file;
  393. name_ = file.Name;
  394. isStreamOwner = !leaveOpen;
  395. try
  396. {
  397. ReadEntries();
  398. }
  399. catch
  400. {
  401. DisposeInternal(true);
  402. throw;
  403. }
  404. }
  405. /// <summary>
  406. /// Opens a Zip file reading the given <see cref="Stream"/>.
  407. /// </summary>
  408. /// <param name="stream">The <see cref="Stream"/> to read archive data from.</param>
  409. /// <exception cref="IOException">
  410. /// An i/o error occurs
  411. /// </exception>
  412. /// <exception cref="ZipException">
  413. /// The stream doesn't contain a valid zip archive.<br/>
  414. /// </exception>
  415. /// <exception cref="ArgumentException">
  416. /// The <see cref="Stream">stream</see> doesnt support seeking.
  417. /// </exception>
  418. /// <exception cref="ArgumentNullException">
  419. /// The <see cref="Stream">stream</see> argument is null.
  420. /// </exception>
  421. public ZipFile(Stream stream) :
  422. this(stream, false)
  423. {
  424. }
  425. /// <summary>
  426. /// Opens a Zip file reading the given <see cref="Stream"/>.
  427. /// </summary>
  428. /// <param name="stream">The <see cref="Stream"/> to read archive data from.</param>
  429. /// <param name="leaveOpen">true to leave the <see cref="Stream">stream</see> open when the ZipFile is disposed, false to dispose of it</param>
  430. /// <exception cref="IOException">
  431. /// An i/o error occurs
  432. /// </exception>
  433. /// <exception cref="ZipException">
  434. /// The stream doesn't contain a valid zip archive.<br/>
  435. /// </exception>
  436. /// <exception cref="ArgumentException">
  437. /// The <see cref="Stream">stream</see> doesnt support seeking.
  438. /// </exception>
  439. /// <exception cref="ArgumentNullException">
  440. /// The <see cref="Stream">stream</see> argument is null.
  441. /// </exception>
  442. public ZipFile(Stream stream, bool leaveOpen)
  443. {
  444. if (stream == null)
  445. {
  446. throw new ArgumentNullException(nameof(stream));
  447. }
  448. if (!stream.CanSeek)
  449. {
  450. throw new ArgumentException("Stream is not seekable", nameof(stream));
  451. }
  452. baseStream_ = stream;
  453. isStreamOwner = !leaveOpen;
  454. if (baseStream_.Length > 0)
  455. {
  456. try
  457. {
  458. ReadEntries();
  459. }
  460. catch
  461. {
  462. DisposeInternal(true);
  463. throw;
  464. }
  465. }
  466. else
  467. {
  468. entries_ = new ZipEntry[0];
  469. isNewArchive_ = true;
  470. }
  471. }
  472. /// <summary>
  473. /// Initialises a default <see cref="ZipFile"/> instance with no entries and no file storage.
  474. /// </summary>
  475. internal ZipFile()
  476. {
  477. entries_ = new ZipEntry[0];
  478. isNewArchive_ = true;
  479. }
  480. #endregion Constructors
  481. #region Destructors and Closing
  482. /// <summary>
  483. /// Finalize this instance.
  484. /// </summary>
  485. ~ZipFile()
  486. {
  487. Dispose(false);
  488. }
  489. /// <summary>
  490. /// Closes the ZipFile. If the stream is <see cref="IsStreamOwner">owned</see> then this also closes the underlying input stream.
  491. /// Once closed, no further instance methods should be called.
  492. /// </summary>
  493. /// <exception cref="System.IO.IOException">
  494. /// An i/o error occurs.
  495. /// </exception>
  496. public void Close()
  497. {
  498. DisposeInternal(true);
  499. GC.SuppressFinalize(this);
  500. }
  501. #endregion Destructors and Closing
  502. #region Creators
  503. /// <summary>
  504. /// Create a new <see cref="ZipFile"/> whose data will be stored in a file.
  505. /// </summary>
  506. /// <param name="fileName">The name of the archive to create.</param>
  507. /// <returns>Returns the newly created <see cref="ZipFile"/></returns>
  508. /// <exception cref="ArgumentNullException"><paramref name="fileName"></paramref> is null</exception>
  509. public static ZipFile Create(string fileName)
  510. {
  511. if (fileName == null)
  512. {
  513. throw new ArgumentNullException(nameof(fileName));
  514. }
  515. FileStream fs = File.Create(fileName);
  516. return new ZipFile
  517. {
  518. name_ = fileName,
  519. baseStream_ = fs,
  520. isStreamOwner = true
  521. };
  522. }
  523. /// <summary>
  524. /// Create a new <see cref="ZipFile"/> whose data will be stored on a stream.
  525. /// </summary>
  526. /// <param name="outStream">The stream providing data storage.</param>
  527. /// <returns>Returns the newly created <see cref="ZipFile"/></returns>
  528. /// <exception cref="ArgumentNullException"><paramref name="outStream"> is null</paramref></exception>
  529. /// <exception cref="ArgumentException"><paramref name="outStream"> doesnt support writing.</paramref></exception>
  530. public static ZipFile Create(Stream outStream)
  531. {
  532. if (outStream == null)
  533. {
  534. throw new ArgumentNullException(nameof(outStream));
  535. }
  536. if (!outStream.CanWrite)
  537. {
  538. throw new ArgumentException("Stream is not writeable", nameof(outStream));
  539. }
  540. if (!outStream.CanSeek)
  541. {
  542. throw new ArgumentException("Stream is not seekable", nameof(outStream));
  543. }
  544. var result = new ZipFile
  545. {
  546. baseStream_ = outStream
  547. };
  548. return result;
  549. }
  550. #endregion Creators
  551. #region Properties
  552. /// <summary>
  553. /// Get/set a flag indicating if the underlying stream is owned by the ZipFile instance.
  554. /// If the flag is true then the stream will be closed when <see cref="Close">Close</see> is called.
  555. /// </summary>
  556. /// <remarks>
  557. /// The default value is true in all cases.
  558. /// </remarks>
  559. public bool IsStreamOwner
  560. {
  561. get { return isStreamOwner; }
  562. set { isStreamOwner = value; }
  563. }
  564. /// <summary>
  565. /// Get a value indicating wether
  566. /// this archive is embedded in another file or not.
  567. /// </summary>
  568. public bool IsEmbeddedArchive
  569. {
  570. // Not strictly correct in all circumstances currently
  571. get { return offsetOfFirstEntry > 0; }
  572. }
  573. /// <summary>
  574. /// Get a value indicating that this archive is a new one.
  575. /// </summary>
  576. public bool IsNewArchive
  577. {
  578. get { return isNewArchive_; }
  579. }
  580. /// <summary>
  581. /// Gets the comment for the zip file.
  582. /// </summary>
  583. public string ZipFileComment
  584. {
  585. get { return comment_; }
  586. }
  587. /// <summary>
  588. /// Gets the name of this zip file.
  589. /// </summary>
  590. public string Name
  591. {
  592. get { return name_; }
  593. }
  594. /// <summary>
  595. /// Gets the number of entries in this zip file.
  596. /// </summary>
  597. /// <exception cref="InvalidOperationException">
  598. /// The Zip file has been closed.
  599. /// </exception>
  600. [Obsolete("Use the Count property instead")]
  601. public int Size
  602. {
  603. get
  604. {
  605. return entries_.Length;
  606. }
  607. }
  608. /// <summary>
  609. /// Get the number of entries contained in this <see cref="ZipFile"/>.
  610. /// </summary>
  611. public long Count
  612. {
  613. get
  614. {
  615. return entries_.Length;
  616. }
  617. }
  618. /// <summary>
  619. /// Indexer property for ZipEntries
  620. /// </summary>
  621. [System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")]
  622. public ZipEntry this[int index]
  623. {
  624. get
  625. {
  626. return (ZipEntry)entries_[index].Clone();
  627. }
  628. }
  629. #endregion Properties
  630. #region Input Handling
  631. /// <summary>
  632. /// Gets an enumerator for the Zip entries in this Zip file.
  633. /// </summary>
  634. /// <returns>Returns an <see cref="IEnumerator"/> for this archive.</returns>
  635. /// <exception cref="ObjectDisposedException">
  636. /// The Zip file has been closed.
  637. /// </exception>
  638. public IEnumerator GetEnumerator()
  639. {
  640. if (isDisposed_)
  641. {
  642. throw new ObjectDisposedException("ZipFile");
  643. }
  644. return new ZipEntryEnumerator(entries_);
  645. }
  646. /// <summary>
  647. /// Return the index of the entry with a matching name
  648. /// </summary>
  649. /// <param name="name">Entry name to find</param>
  650. /// <param name="ignoreCase">If true the comparison is case insensitive</param>
  651. /// <returns>The index position of the matching entry or -1 if not found</returns>
  652. /// <exception cref="ObjectDisposedException">
  653. /// The Zip file has been closed.
  654. /// </exception>
  655. public int FindEntry(string name, bool ignoreCase)
  656. {
  657. if (isDisposed_)
  658. {
  659. throw new ObjectDisposedException("ZipFile");
  660. }
  661. // TODO: This will be slow as the next ice age for huge archives!
  662. for (int i = 0; i < entries_.Length; i++)
  663. {
  664. if (string.Compare(name, entries_[i].Name, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) == 0)
  665. {
  666. return i;
  667. }
  668. }
  669. return -1;
  670. }
  671. /// <summary>
  672. /// Searches for a zip entry in this archive with the given name.
  673. /// String comparisons are case insensitive
  674. /// </summary>
  675. /// <param name="name">
  676. /// The name to find. May contain directory components separated by slashes ('/').
  677. /// </param>
  678. /// <returns>
  679. /// A clone of the zip entry, or null if no entry with that name exists.
  680. /// </returns>
  681. /// <exception cref="ObjectDisposedException">
  682. /// The Zip file has been closed.
  683. /// </exception>
  684. public ZipEntry GetEntry(string name)
  685. {
  686. if (isDisposed_)
  687. {
  688. throw new ObjectDisposedException("ZipFile");
  689. }
  690. int index = FindEntry(name, true);
  691. return (index >= 0) ? (ZipEntry)entries_[index].Clone() : null;
  692. }
  693. /// <summary>
  694. /// Gets an input stream for reading the given zip entry data in an uncompressed form.
  695. /// Normally the <see cref="ZipEntry"/> should be an entry returned by GetEntry().
  696. /// </summary>
  697. /// <param name="entry">The <see cref="ZipEntry"/> to obtain a data <see cref="Stream"/> for</param>
  698. /// <returns>An input <see cref="Stream"/> containing data for this <see cref="ZipEntry"/></returns>
  699. /// <exception cref="ObjectDisposedException">
  700. /// The ZipFile has already been closed
  701. /// </exception>
  702. /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException">
  703. /// The compression method for the entry is unknown
  704. /// </exception>
  705. /// <exception cref="IndexOutOfRangeException">
  706. /// The entry is not found in the ZipFile
  707. /// </exception>
  708. public Stream GetInputStream(ZipEntry entry)
  709. {
  710. if (entry == null)
  711. {
  712. throw new ArgumentNullException(nameof(entry));
  713. }
  714. if (isDisposed_)
  715. {
  716. throw new ObjectDisposedException("ZipFile");
  717. }
  718. long index = entry.ZipFileIndex;
  719. if ((index < 0) || (index >= entries_.Length) || (entries_[index].Name != entry.Name))
  720. {
  721. index = FindEntry(entry.Name, true);
  722. if (index < 0)
  723. {
  724. throw new ZipException("Entry cannot be found");
  725. }
  726. }
  727. return GetInputStream(index);
  728. }
  729. /// <summary>
  730. /// Creates an input stream reading a zip entry
  731. /// </summary>
  732. /// <param name="entryIndex">The index of the entry to obtain an input stream for.</param>
  733. /// <returns>
  734. /// An input <see cref="Stream"/> containing data for this <paramref name="entryIndex"/>
  735. /// </returns>
  736. /// <exception cref="ObjectDisposedException">
  737. /// The ZipFile has already been closed
  738. /// </exception>
  739. /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException">
  740. /// The compression method for the entry is unknown
  741. /// </exception>
  742. /// <exception cref="IndexOutOfRangeException">
  743. /// The entry is not found in the ZipFile
  744. /// </exception>
  745. public Stream GetInputStream(long entryIndex)
  746. {
  747. if (isDisposed_)
  748. {
  749. throw new ObjectDisposedException("ZipFile");
  750. }
  751. long start = LocateEntry(entries_[entryIndex]);
  752. CompressionMethod method = entries_[entryIndex].CompressionMethod;
  753. Stream result = new PartialInputStream(this, start, entries_[entryIndex].CompressedSize);
  754. if (entries_[entryIndex].IsCrypted == true)
  755. {
  756. result = CreateAndInitDecryptionStream(result, entries_[entryIndex]);
  757. if (result == null)
  758. {
  759. throw new ZipException("Unable to decrypt this entry");
  760. }
  761. }
  762. switch (method)
  763. {
  764. case CompressionMethod.Stored:
  765. // read as is.
  766. break;
  767. case CompressionMethod.Deflated:
  768. // No need to worry about ownership and closing as underlying stream close does nothing.
  769. result = new InflaterInputStream(result, new Inflater(true));
  770. break;
  771. default:
  772. throw new ZipException("Unsupported compression method " + method);
  773. }
  774. return result;
  775. }
  776. #endregion Input Handling
  777. #region Archive Testing
  778. /// <summary>
  779. /// Test an archive for integrity/validity
  780. /// </summary>
  781. /// <param name="testData">Perform low level data Crc check</param>
  782. /// <returns>true if all tests pass, false otherwise</returns>
  783. /// <remarks>Testing will terminate on the first error found.</remarks>
  784. public bool TestArchive(bool testData)
  785. {
  786. return TestArchive(testData, TestStrategy.FindFirstError, null);
  787. }
  788. /// <summary>
  789. /// Test an archive for integrity/validity
  790. /// </summary>
  791. /// <param name="testData">Perform low level data Crc check</param>
  792. /// <param name="strategy">The <see cref="TestStrategy"></see> to apply.</param>
  793. /// <param name="resultHandler">The <see cref="ZipTestResultHandler"></see> handler to call during testing.</param>
  794. /// <returns>true if all tests pass, false otherwise</returns>
  795. /// <exception cref="ObjectDisposedException">The object has already been closed.</exception>
  796. public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandler resultHandler)
  797. {
  798. if (isDisposed_)
  799. {
  800. throw new ObjectDisposedException("ZipFile");
  801. }
  802. var status = new TestStatus(this);
  803. resultHandler?.Invoke(status, null);
  804. HeaderTest test = testData ? (HeaderTest.Header | HeaderTest.Extract) : HeaderTest.Header;
  805. bool testing = true;
  806. try
  807. {
  808. int entryIndex = 0;
  809. while (testing && (entryIndex < Count))
  810. {
  811. if (resultHandler != null)
  812. {
  813. status.SetEntry(this[entryIndex]);
  814. status.SetOperation(TestOperation.EntryHeader);
  815. resultHandler(status, null);
  816. }
  817. try
  818. {
  819. TestLocalHeader(this[entryIndex], test);
  820. }
  821. catch (ZipException ex)
  822. {
  823. status.AddError();
  824. resultHandler?.Invoke(status, $"Exception during test - '{ex.Message}'");
  825. testing &= strategy != TestStrategy.FindFirstError;
  826. }
  827. if (testing && testData && this[entryIndex].IsFile)
  828. {
  829. if (resultHandler != null)
  830. {
  831. status.SetOperation(TestOperation.EntryData);
  832. resultHandler(status, null);
  833. }
  834. var crc = new Crc32();
  835. using (Stream entryStream = this.GetInputStream(this[entryIndex]))
  836. {
  837. byte[] buffer = new byte[4096];
  838. long totalBytes = 0;
  839. int bytesRead;
  840. while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0)
  841. {
  842. crc.Update(new ArraySegment<byte>(buffer, 0, bytesRead));
  843. if (resultHandler != null)
  844. {
  845. totalBytes += bytesRead;
  846. status.SetBytesTested(totalBytes);
  847. resultHandler(status, null);
  848. }
  849. }
  850. }
  851. if (this[entryIndex].Crc != crc.Value)
  852. {
  853. status.AddError();
  854. resultHandler?.Invoke(status, "CRC mismatch");
  855. testing &= strategy != TestStrategy.FindFirstError;
  856. }
  857. if ((this[entryIndex].Flags & (int)GeneralBitFlags.Descriptor) != 0)
  858. {
  859. var helper = new ZipHelperStream(baseStream_);
  860. var data = new DescriptorData();
  861. helper.ReadDataDescriptor(this[entryIndex].LocalHeaderRequiresZip64, data);
  862. if (this[entryIndex].Crc != data.Crc)
  863. {
  864. status.AddError();
  865. }
  866. if (this[entryIndex].CompressedSize != data.CompressedSize)
  867. {
  868. status.AddError();
  869. }
  870. if (this[entryIndex].Size != data.Size)
  871. {
  872. status.AddError();
  873. }
  874. }
  875. }
  876. if (resultHandler != null)
  877. {
  878. status.SetOperation(TestOperation.EntryComplete);
  879. resultHandler(status, null);
  880. }
  881. entryIndex += 1;
  882. }
  883. if (resultHandler != null)
  884. {
  885. status.SetOperation(TestOperation.MiscellaneousTests);
  886. resultHandler(status, null);
  887. }
  888. // TODO: the 'Corrina Johns' test where local headers are missing from
  889. // the central directory. They are therefore invisible to many archivers.
  890. }
  891. catch (Exception ex)
  892. {
  893. status.AddError();
  894. resultHandler?.Invoke(status, $"Exception during test - '{ex.Message}'");
  895. }
  896. if (resultHandler != null)
  897. {
  898. status.SetOperation(TestOperation.Complete);
  899. status.SetEntry(null);
  900. resultHandler(status, null);
  901. }
  902. return (status.ErrorCount == 0);
  903. }
  904. [Flags]
  905. private enum HeaderTest
  906. {
  907. Extract = 0x01, // Check that this header represents an entry whose data can be extracted
  908. Header = 0x02, // Check that this header contents are valid
  909. }
  910. /// <summary>
  911. /// Test a local header against that provided from the central directory
  912. /// </summary>
  913. /// <param name="entry">
  914. /// The entry to test against
  915. /// </param>
  916. /// <param name="tests">The type of <see cref="HeaderTest">tests</see> to carry out.</param>
  917. /// <returns>The offset of the entries data in the file</returns>
  918. private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
  919. {
  920. lock (baseStream_)
  921. {
  922. bool testHeader = (tests & HeaderTest.Header) != 0;
  923. bool testData = (tests & HeaderTest.Extract) != 0;
  924. var entryAbsOffset = offsetOfFirstEntry + entry.Offset;
  925. baseStream_.Seek(entryAbsOffset, SeekOrigin.Begin);
  926. var signature = (int)ReadLEUint();
  927. if (signature != ZipConstants.LocalHeaderSignature)
  928. {
  929. throw new ZipException(string.Format("Wrong local header signature at 0x{0:x}, expected 0x{1:x8}, actual 0x{2:x8}",
  930. entryAbsOffset, ZipConstants.LocalHeaderSignature, signature));
  931. }
  932. var extractVersion = (short)(ReadLEUshort() & 0x00ff);
  933. var localFlags = (short)ReadLEUshort();
  934. var compressionMethod = (short)ReadLEUshort();
  935. var fileTime = (short)ReadLEUshort();
  936. var fileDate = (short)ReadLEUshort();
  937. uint crcValue = ReadLEUint();
  938. long compressedSize = ReadLEUint();
  939. long size = ReadLEUint();
  940. int storedNameLength = ReadLEUshort();
  941. int extraDataLength = ReadLEUshort();
  942. byte[] nameData = new byte[storedNameLength];
  943. StreamUtils.ReadFully(baseStream_, nameData);
  944. byte[] extraData = new byte[extraDataLength];
  945. StreamUtils.ReadFully(baseStream_, extraData);
  946. var localExtraData = new ZipExtraData(extraData);
  947. // Extra data / zip64 checks
  948. if (localExtraData.Find(1))
  949. {
  950. // 2010-03-04 Forum 10512: removed checks for version >= ZipConstants.VersionZip64
  951. // and size or compressedSize = MaxValue, due to rogue creators.
  952. size = localExtraData.ReadLong();
  953. compressedSize = localExtraData.ReadLong();
  954. if ((localFlags & (int)GeneralBitFlags.Descriptor) != 0)
  955. {
  956. // These may be valid if patched later
  957. if ((size != -1) && (size != entry.Size))
  958. {
  959. throw new ZipException("Size invalid for descriptor");
  960. }
  961. if ((compressedSize != -1) && (compressedSize != entry.CompressedSize))
  962. {
  963. throw new ZipException("Compressed size invalid for descriptor");
  964. }
  965. }
  966. }
  967. else
  968. {
  969. // No zip64 extra data but entry requires it.
  970. if ((extractVersion >= ZipConstants.VersionZip64) &&
  971. (((uint)size == uint.MaxValue) || ((uint)compressedSize == uint.MaxValue)))
  972. {
  973. throw new ZipException("Required Zip64 extended information missing");
  974. }
  975. }
  976. if (testData)
  977. {
  978. if (entry.IsFile)
  979. {
  980. if (!entry.IsCompressionMethodSupported())
  981. {
  982. throw new ZipException("Compression method not supported");
  983. }
  984. if ((extractVersion > ZipConstants.VersionMadeBy)
  985. || ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64)))
  986. {
  987. throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extractVersion));
  988. }
  989. if ((localFlags & (int)(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.EnhancedCompress | GeneralBitFlags.HeaderMasked)) != 0)
  990. {
  991. throw new ZipException("The library does not support the zip version required to extract this entry");
  992. }
  993. }
  994. }
  995. if (testHeader)
  996. {
  997. if ((extractVersion <= 63) && // Ignore later versions as we dont know about them..
  998. (extractVersion != 10) &&
  999. (extractVersion != 11) &&
  1000. (extractVersion != 20) &&
  1001. (extractVersion != 21) &&
  1002. (extractVersion != 25) &&
  1003. (extractVersion != 27) &&
  1004. (extractVersion != 45) &&
  1005. (extractVersion != 46) &&
  1006. (extractVersion != 50) &&
  1007. (extractVersion != 51) &&
  1008. (extractVersion != 52) &&
  1009. (extractVersion != 61) &&
  1010. (extractVersion != 62) &&
  1011. (extractVersion != 63)
  1012. )
  1013. {
  1014. throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersion));
  1015. }
  1016. // Local entry flags dont have reserved bit set on.
  1017. if ((localFlags & (int)(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.ReservedPkware15)) != 0)
  1018. {
  1019. throw new ZipException("Reserved bit flags cannot be set.");
  1020. }
  1021. // Encryption requires extract version >= 20
  1022. if (((localFlags & (int)GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20))
  1023. {
  1024. throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion));
  1025. }
  1026. // Strong encryption requires encryption flag to be set and extract version >= 50.
  1027. if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0)
  1028. {
  1029. if ((localFlags & (int)GeneralBitFlags.Encrypted) == 0)
  1030. {
  1031. throw new ZipException("Strong encryption flag set but encryption flag is not set");
  1032. }
  1033. if (extractVersion < 50)
  1034. {
  1035. throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion));
  1036. }
  1037. }
  1038. // Patched entries require extract version >= 27
  1039. if (((localFlags & (int)GeneralBitFlags.Patched) != 0) && (extractVersion < 27))
  1040. {
  1041. throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion));
  1042. }
  1043. // Central header flags match local entry flags.
  1044. if (localFlags != entry.Flags)
  1045. {
  1046. throw new ZipException("Central header/local header flags mismatch");
  1047. }
  1048. // Central header compression method matches local entry
  1049. if (entry.CompressionMethod != (CompressionMethod)compressionMethod)
  1050. {
  1051. throw new ZipException("Central header/local header compression method mismatch");
  1052. }
  1053. if (entry.Version != extractVersion)
  1054. {
  1055. throw new ZipException("Extract version mismatch");
  1056. }
  1057. // Strong encryption and extract version match
  1058. if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0)
  1059. {
  1060. if (extractVersion < 62)
  1061. {
  1062. throw new ZipException("Strong encryption flag set but version not high enough");
  1063. }
  1064. }
  1065. if ((localFlags & (int)GeneralBitFlags.HeaderMasked) != 0)
  1066. {
  1067. if ((fileTime != 0) || (fileDate != 0))
  1068. {
  1069. throw new ZipException("Header masked set but date/time values non-zero");
  1070. }
  1071. }
  1072. if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0)
  1073. {
  1074. if (crcValue != (uint)entry.Crc)
  1075. {
  1076. throw new ZipException("Central header/local header crc mismatch");
  1077. }
  1078. }
  1079. // Crc valid for empty entry.
  1080. // This will also apply to streamed entries where size isnt known and the header cant be patched
  1081. if ((size == 0) && (compressedSize == 0))
  1082. {
  1083. if (crcValue != 0)
  1084. {
  1085. throw new ZipException("Invalid CRC for empty entry");
  1086. }
  1087. }
  1088. // TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS strings
  1089. // Assuming a code page at this point is not valid? Best is to store the name length in the ZipEntry probably
  1090. if (entry.Name.Length > storedNameLength)
  1091. {
  1092. throw new ZipException("File name length mismatch");
  1093. }
  1094. // Name data has already been read convert it and compare.
  1095. string localName = ZipStrings.ConvertToStringExt(localFlags, nameData);
  1096. // Central directory and local entry name match
  1097. if (localName != entry.Name)
  1098. {
  1099. throw new ZipException("Central header and local header file name mismatch");
  1100. }
  1101. // Directories have zero actual size but can have compressed size
  1102. if (entry.IsDirectory)
  1103. {
  1104. if (size > 0)
  1105. {
  1106. throw new ZipException("Directory cannot have size");
  1107. }
  1108. // There may be other cases where the compressed size can be greater than this?
  1109. // If so until details are known we will be strict.
  1110. if (entry.IsCrypted)
  1111. {
  1112. if (compressedSize > ZipConstants.CryptoHeaderSize + 2)
  1113. {
  1114. throw new ZipException("Directory compressed size invalid");
  1115. }
  1116. }
  1117. else if (compressedSize > 2)
  1118. {
  1119. // When not compressed the directory size can validly be 2 bytes
  1120. // if the true size wasnt known when data was originally being written.
  1121. // NOTE: Versions of the library 0.85.4 and earlier always added 2 bytes
  1122. throw new ZipException("Directory compressed size invalid");
  1123. }
  1124. }
  1125. if (!ZipNameTransform.IsValidName(localName, true))
  1126. {
  1127. throw new ZipException("Name is invalid");
  1128. }
  1129. }
  1130. // Tests that apply to both data and header.
  1131. // Size can be verified only if it is known in the local header.
  1132. // it will always be known in the central header.
  1133. if (((localFlags & (int)GeneralBitFlags.Descriptor) == 0) ||
  1134. ((size > 0 || compressedSize > 0) && entry.Size > 0))
  1135. {
  1136. if ((size != 0)
  1137. && (size != entry.Size))
  1138. {
  1139. throw new ZipException(
  1140. string.Format("Size mismatch between central header({0}) and local header({1})",
  1141. entry.Size, size));
  1142. }
  1143. if ((compressedSize != 0)
  1144. && (compressedSize != entry.CompressedSize && compressedSize != 0xFFFFFFFF && compressedSize != -1))
  1145. {
  1146. throw new ZipException(
  1147. string.Format("Compressed size mismatch between central header({0}) and local header({1})",
  1148. entry.CompressedSize, compressedSize));
  1149. }
  1150. }
  1151. int extraLength = storedNameLength + extraDataLength;
  1152. return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength;
  1153. }
  1154. }
  1155. #endregion Archive Testing
  1156. #region Updating
  1157. private const int DefaultBufferSize = 4096;
  1158. /// <summary>
  1159. /// The kind of update to apply.
  1160. /// </summary>
  1161. private enum UpdateCommand
  1162. {
  1163. Copy, // Copy original file contents.
  1164. Modify, // Change encryption, compression, attributes, name, time etc, of an existing file.
  1165. Add, // Add a new file to the archive.
  1166. }
  1167. #region Properties
  1168. /// <summary>
  1169. /// Get / set the <see cref="INameTransform"/> to apply to names when updating.
  1170. /// </summary>
  1171. public INameTransform NameTransform
  1172. {
  1173. get
  1174. {
  1175. return updateEntryFactory_.NameTransform;
  1176. }
  1177. set
  1178. {
  1179. updateEntryFactory_.NameTransform = value;
  1180. }
  1181. }
  1182. /// <summary>
  1183. /// Get/set the <see cref="IEntryFactory"/> used to generate <see cref="ZipEntry"/> values
  1184. /// during updates.
  1185. /// </summary>
  1186. public IEntryFactory EntryFactory
  1187. {
  1188. get
  1189. {
  1190. return updateEntryFactory_;
  1191. }
  1192. set
  1193. {
  1194. if (value == null)
  1195. {
  1196. updateEntryFactory_ = new ZipEntryFactory();
  1197. }
  1198. else
  1199. {
  1200. updateEntryFactory_ = value;
  1201. }
  1202. }
  1203. }
  1204. /// <summary>
  1205. /// Get /set the buffer size to be used when updating this zip file.
  1206. /// </summary>
  1207. public int BufferSize
  1208. {
  1209. get { return bufferSize_; }
  1210. set
  1211. {
  1212. if (value < 1024)
  1213. {
  1214. throw new ArgumentOutOfRangeException(nameof(value), "cannot be below 1024");
  1215. }
  1216. if (bufferSize_ != value)
  1217. {
  1218. bufferSize_ = value;
  1219. copyBuffer_ = null;
  1220. }
  1221. }
  1222. }
  1223. /// <summary>
  1224. /// Get a value indicating an update has <see cref="BeginUpdate()">been started</see>.
  1225. /// </summary>
  1226. public bool IsUpdating
  1227. {
  1228. get { return updates_ != null; }
  1229. }
  1230. /// <summary>
  1231. /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries.
  1232. /// </summary>
  1233. public UseZip64 UseZip64
  1234. {
  1235. get { return useZip64_; }
  1236. set { useZip64_ = value; }
  1237. }
  1238. #endregion Properties
  1239. #region Immediate updating
  1240. // TBD: Direct form of updating
  1241. //
  1242. // public void Update(IEntryMatcher deleteMatcher)
  1243. // {
  1244. // }
  1245. //
  1246. // public void Update(IScanner addScanner)
  1247. // {
  1248. // }
  1249. #endregion Immediate updating
  1250. #region Deferred Updating
  1251. /// <summary>
  1252. /// Begin updating this <see cref="ZipFile"/> archive.
  1253. /// </summary>
  1254. /// <param name="archiveStorage">The <see cref="IArchiveStorage">archive storage</see> for use during the update.</param>
  1255. /// <param name="dataSource">The <see cref="IDynamicDataSource">data source</see> to utilise during updating.</param>
  1256. /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception>
  1257. /// <exception cref="ArgumentNullException">One of the arguments provided is null</exception>
  1258. /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception>
  1259. public void BeginUpdate(IArchiveStorage archiveStorage, IDynamicDataSource dataSource)
  1260. {
  1261. if (isDisposed_)
  1262. {
  1263. throw new ObjectDisposedException("ZipFile");
  1264. }
  1265. if (IsEmbeddedArchive)
  1266. {
  1267. throw new ZipException("Cannot update embedded/SFX archives");
  1268. }
  1269. archiveStorage_ = archiveStorage ?? throw new ArgumentNullException(nameof(archiveStorage));
  1270. updateDataSource_ = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
  1271. // NOTE: the baseStream_ may not currently support writing or seeking.
  1272. updateIndex_ = new Dictionary<string, int>();
  1273. updates_ = new List<ZipUpdate>(entries_.Length);
  1274. foreach (ZipEntry entry in entries_)
  1275. {
  1276. int index = updates_.Count;
  1277. updates_.Add(new ZipUpdate(entry));
  1278. updateIndex_.Add(entry.Name, index);
  1279. }
  1280. // We must sort by offset before using offset's calculated sizes
  1281. updates_.Sort(new UpdateComparer());
  1282. int idx = 0;
  1283. foreach (ZipUpdate update in updates_)
  1284. {
  1285. //If last entry, there is no next entry offset to use
  1286. if (idx == updates_.Count - 1)
  1287. break;
  1288. update.OffsetBasedSize = ((ZipUpdate)updates_[idx + 1]).Entry.Offset - update.Entry.Offset;
  1289. idx++;
  1290. }
  1291. updateCount_ = updates_.Count;
  1292. contentsEdited_ = false;
  1293. commentEdited_ = false;
  1294. newComment_ = null;
  1295. }
  1296. /// <summary>
  1297. /// Begin updating to this <see cref="ZipFile"/> archive.
  1298. /// </summary>
  1299. /// <param name="archiveStorage">The storage to use during the update.</param>
  1300. public void BeginUpdate(IArchiveStorage archiveStorage)
  1301. {
  1302. BeginUpdate(archiveStorage, new DynamicDiskDataSource());
  1303. }
  1304. /// <summary>
  1305. /// Begin updating this <see cref="ZipFile"/> archive.
  1306. /// </summary>
  1307. /// <seealso cref="BeginUpdate(IArchiveStorage)"/>
  1308. /// <seealso cref="CommitUpdate"></seealso>
  1309. /// <seealso cref="AbortUpdate"></seealso>
  1310. public void BeginUpdate()
  1311. {
  1312. if (Name == null)
  1313. {
  1314. BeginUpdate(new MemoryArchiveStorage(), new DynamicDiskDataSource());
  1315. }
  1316. else
  1317. {
  1318. BeginUpdate(new DiskArchiveStorage(this), new DynamicDiskDataSource());
  1319. }
  1320. }
  1321. /// <summary>
  1322. /// Commit current updates, updating this archive.
  1323. /// </summary>
  1324. /// <seealso cref="BeginUpdate()"></seealso>
  1325. /// <seealso cref="AbortUpdate"></seealso>
  1326. /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception>
  1327. public void CommitUpdate()
  1328. {
  1329. if (isDisposed_)
  1330. {
  1331. throw new ObjectDisposedException("ZipFile");
  1332. }
  1333. CheckUpdating();
  1334. try
  1335. {
  1336. updateIndex_.Clear();
  1337. updateIndex_ = null;
  1338. if (contentsEdited_)
  1339. {
  1340. RunUpdates();
  1341. }
  1342. else if (commentEdited_)
  1343. {
  1344. UpdateCommentOnly();
  1345. }
  1346. else
  1347. {
  1348. // Create an empty archive if none existed originally.
  1349. if (entries_.Length == 0)
  1350. {
  1351. byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipStrings.ConvertToArray(comment_);
  1352. using (ZipHelperStream zhs = new ZipHelperStream(baseStream_))
  1353. {
  1354. zhs.WriteEndOfCentralDirectory(0, 0, 0, theComment);
  1355. }
  1356. }
  1357. }
  1358. }
  1359. finally
  1360. {
  1361. PostUpdateCleanup();
  1362. }
  1363. }
  1364. /// <summary>
  1365. /// Abort updating leaving the archive unchanged.
  1366. /// </summary>
  1367. /// <seealso cref="BeginUpdate()"></seealso>
  1368. /// <seealso cref="CommitUpdate"></seealso>
  1369. public void AbortUpdate()
  1370. {
  1371. PostUpdateCleanup();
  1372. }
  1373. /// <summary>
  1374. /// Set the file comment to be recorded when the current update is <see cref="CommitUpdate">commited</see>.
  1375. /// </summary>
  1376. /// <param name="comment">The comment to record.</param>
  1377. /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception>
  1378. public void SetComment(string comment)
  1379. {
  1380. if (isDisposed_)
  1381. {
  1382. throw new ObjectDisposedException("ZipFile");
  1383. }
  1384. CheckUpdating();
  1385. newComment_ = new ZipString(comment);
  1386. if (newComment_.RawLength > 0xffff)
  1387. {
  1388. newComment_ = null;
  1389. throw new ZipException("Comment length exceeds maximum - 65535");
  1390. }
  1391. // We dont take account of the original and current comment appearing to be the same
  1392. // as encoding may be different.
  1393. commentEdited_ = true;
  1394. }
  1395. #endregion Deferred Updating
  1396. #region Adding Entries
  1397. private void AddUpdate(ZipUpdate update)
  1398. {
  1399. contentsEdited_ = true;
  1400. int index = FindExistingUpdate(update.Entry.Name);
  1401. if (index >= 0)
  1402. {
  1403. if (updates_[index] == null)
  1404. {
  1405. updateCount_ += 1;
  1406. }
  1407. // Direct replacement is faster than delete and add.
  1408. updates_[index] = update;
  1409. }
  1410. else
  1411. {
  1412. index = updates_.Count;
  1413. updates_.Add(update);
  1414. updateCount_ += 1;
  1415. updateIndex_.Add(update.Entry.Name, index);
  1416. }
  1417. }
  1418. /// <summary>
  1419. /// Add a new entry to the archive.
  1420. /// </summary>
  1421. /// <param name="fileName">The name of the file to add.</param>
  1422. /// <param name="compressionMethod">The compression method to use.</param>
  1423. /// <param name="useUnicodeText">Ensure Unicode text is used for name and comment for this entry.</param>
  1424. /// <exception cref="ArgumentNullException">Argument supplied is null.</exception>
  1425. /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception>
  1426. /// <exception cref="NotImplementedException">Compression method is not supported for creating entries.</exception>
  1427. public void Add(string fileName, CompressionMethod compressionMethod, bool useUnicodeText)
  1428. {
  1429. if (fileName == null)
  1430. {
  1431. throw new ArgumentNullException(nameof(fileName));
  1432. }
  1433. if (isDisposed_)
  1434. {
  1435. throw new ObjectDisposedException("ZipFile");
  1436. }
  1437. CheckSupportedCompressionMethod(compressionMethod);
  1438. CheckUpdating();
  1439. contentsEdited_ = true;
  1440. ZipEntry entry = EntryFactory.MakeFileEntry(fileName);
  1441. entry.IsUnicodeText = useUnicodeText;
  1442. entry.CompressionMethod = compressionMethod;
  1443. AddUpdate(new ZipUpdate(fileName, entry));
  1444. }
  1445. /// <summary>
  1446. /// Add a new entry to the archive.
  1447. /// </summary>
  1448. /// <param name="fileName">The name of the file to add.</param>
  1449. /// <param name="compressionMethod">The compression method to use.</param>
  1450. /// <exception cref="ArgumentNullException">ZipFile has been closed.</exception>
  1451. /// <exception cref="NotImplementedException">Compression method is not supported for creating entries.</exception>
  1452. public void Add(string fileName, CompressionMethod compressionMethod)
  1453. {
  1454. if (fileName == null)
  1455. {
  1456. throw new ArgumentNullException(nameof(fileName));
  1457. }
  1458. CheckSupportedCompressionMethod(compressionMethod);
  1459. CheckUpdating();
  1460. contentsEdited_ = true;
  1461. ZipEntry entry = EntryFactory.MakeFileEntry(fileName);
  1462. entry.CompressionMethod = compressionMethod;
  1463. AddUpdate(new ZipUpdate(fileName, entry));
  1464. }
  1465. /// <summary>
  1466. /// Add a file to the archive.
  1467. /// </summary>
  1468. /// <param name="fileName">The name of the file to add.</param>
  1469. /// <exception cref="ArgumentNullException">Argument supplied is null.</exception>
  1470. public void Add(string fileName)
  1471. {
  1472. if (fileName == null)
  1473. {
  1474. throw new ArgumentNullException(nameof(fileName));
  1475. }
  1476. CheckUpdating();
  1477. AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName)));
  1478. }
  1479. /// <summary>
  1480. /// Add a file to the archive.
  1481. /// </summary>
  1482. /// <param name="fileName">The name of the file to add.</param>
  1483. /// <param name="entryName">The name to use for the <see cref="ZipEntry"/> on the Zip file created.</param>
  1484. /// <exception cref="ArgumentNullException">Argument supplied is null.</exception>
  1485. public void Add(string fileName, string entryName)
  1486. {
  1487. if (fileName == null)
  1488. {
  1489. throw new ArgumentNullException(nameof(fileName));
  1490. }
  1491. if (entryName == null)
  1492. {
  1493. throw new ArgumentNullException(nameof(entryName));
  1494. }
  1495. CheckUpdating();
  1496. AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName, entryName, true)));
  1497. }
  1498. /// <summary>
  1499. /// Add a file entry with data.
  1500. /// </summary>
  1501. /// <param name="dataSource">The source of the data for this entry.</param>
  1502. /// <param name="entryName">The name to give to the entry.</param>
  1503. public void Add(IStaticDataSource dataSource, string entryName)
  1504. {
  1505. if (dataSource == null)
  1506. {
  1507. throw new ArgumentNullException(nameof(dataSource));
  1508. }
  1509. if (entryName == null)
  1510. {
  1511. throw new ArgumentNullException(nameof(entryName));
  1512. }
  1513. CheckUpdating();
  1514. AddUpdate(new ZipUpdate(dataSource, EntryFactory.MakeFileEntry(entryName, false)));
  1515. }
  1516. /// <summary>
  1517. /// Add a file entry with data.
  1518. /// </summary>
  1519. /// <param name="dataSource">The source of the data for this entry.</param>
  1520. /// <param name="entryName">The name to give to the entry.</param>
  1521. /// <param name="compressionMethod">The compression method to use.</param>
  1522. /// <exception cref="NotImplementedException">Compression method is not supported for creating entries.</exception>
  1523. public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod)
  1524. {
  1525. if (dataSource == null)
  1526. {
  1527. throw new ArgumentNullException(nameof(dataSource));
  1528. }
  1529. if (entryName == null)
  1530. {
  1531. throw new ArgumentNullException(nameof(entryName));
  1532. }
  1533. CheckSupportedCompressionMethod(compressionMethod);
  1534. CheckUpdating();
  1535. ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false);
  1536. entry.CompressionMethod = compressionMethod;
  1537. AddUpdate(new ZipUpdate(dataSource, entry));
  1538. }
  1539. /// <summary>
  1540. /// Add a file entry with data.
  1541. /// </summary>
  1542. /// <param name="dataSource">The source of the data for this entry.</param>
  1543. /// <param name="entryName">The name to give to the entry.</param>
  1544. /// <param name="compressionMethod">The compression method to use.</param>
  1545. /// <param name="useUnicodeText">Ensure Unicode text is used for name and comments for this entry.</param>
  1546. /// <exception cref="NotImplementedException">Compression method is not supported for creating entries.</exception>
  1547. public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod, bool useUnicodeText)
  1548. {
  1549. if (dataSource == null)
  1550. {
  1551. throw new ArgumentNullException(nameof(dataSource));
  1552. }
  1553. if (entryName == null)
  1554. {
  1555. throw new ArgumentNullException(nameof(entryName));
  1556. }
  1557. CheckSupportedCompressionMethod(compressionMethod);
  1558. CheckUpdating();
  1559. ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false);
  1560. entry.IsUnicodeText = useUnicodeText;
  1561. entry.CompressionMethod = compressionMethod;
  1562. AddUpdate(new ZipUpdate(dataSource, entry));
  1563. }
  1564. /// <summary>
  1565. /// Add a <see cref="ZipEntry"/> that contains no data.
  1566. /// </summary>
  1567. /// <param name="entry">The entry to add.</param>
  1568. /// <remarks>This can be used to add directories, volume labels, or empty file entries.</remarks>
  1569. public void Add(ZipEntry entry)
  1570. {
  1571. if (entry == null)
  1572. {
  1573. throw new ArgumentNullException(nameof(entry));
  1574. }
  1575. CheckUpdating();
  1576. if ((entry.Size != 0) || (entry.CompressedSize != 0))
  1577. {
  1578. throw new ZipException("Entry cannot have any data");
  1579. }
  1580. AddUpdate(new ZipUpdate(UpdateCommand.Add, entry));
  1581. }
  1582. /// <summary>
  1583. /// Add a <see cref="ZipEntry"/> with data.
  1584. /// </summary>
  1585. /// <param name="dataSource">The source of the data for this entry.</param>
  1586. /// <param name="entry">The entry to add.</param>
  1587. /// <remarks>This can be used to add file entries with a custom data source.</remarks>
  1588. /// <exception cref="NotSupportedException">
  1589. /// The encryption method specified in <paramref name="entry"/> is unsupported.
  1590. /// </exception>
  1591. /// <exception cref="NotImplementedException">Compression method is not supported for creating entries.</exception>
  1592. public void Add(IStaticDataSource dataSource, ZipEntry entry)
  1593. {
  1594. if (entry == null)
  1595. {
  1596. throw new ArgumentNullException(nameof(entry));
  1597. }
  1598. if (dataSource == null)
  1599. {
  1600. throw new ArgumentNullException(nameof(dataSource));
  1601. }
  1602. // We don't currently support adding entries with AES encryption, so throw
  1603. // up front instead of failing or falling back to ZipCrypto later on
  1604. if (entry.AESKeySize > 0)
  1605. {
  1606. throw new NotSupportedException("Creation of AES encrypted entries is not supported");
  1607. }
  1608. CheckSupportedCompressionMethod(entry.CompressionMethod);
  1609. CheckUpdating();
  1610. AddUpdate(new ZipUpdate(dataSource, entry));
  1611. }
  1612. /// <summary>
  1613. /// Add a directory entry to the archive.
  1614. /// </summary>
  1615. /// <param name="directoryName">The directory to add.</param>
  1616. public void AddDirectory(string directoryName)
  1617. {
  1618. if (directoryName == null)
  1619. {
  1620. throw new ArgumentNullException(nameof(directoryName));
  1621. }
  1622. CheckUpdating();
  1623. ZipEntry dirEntry = EntryFactory.MakeDirectoryEntry(directoryName);
  1624. AddUpdate(new ZipUpdate(UpdateCommand.Add, dirEntry));
  1625. }
  1626. /// <summary>
  1627. /// Check if the specified compression method is supported for adding a new entry.
  1628. /// </summary>
  1629. /// <param name="compressionMethod">The compression method for the new entry.</param>
  1630. private void CheckSupportedCompressionMethod(CompressionMethod compressionMethod)
  1631. {
  1632. if (compressionMethod != CompressionMethod.Deflated && compressionMethod != CompressionMethod.Stored)
  1633. {
  1634. throw new NotImplementedException("Compression method not supported");
  1635. }
  1636. }
  1637. #endregion Adding Entries
  1638. #region Modifying Entries
  1639. /* Modify not yet ready for public consumption.
  1640. Direct modification of an entry should not overwrite original data before its read.
  1641. Safe mode is trivial in this sense.
  1642. public void Modify(ZipEntry original, ZipEntry updated)
  1643. {
  1644. if ( original == null ) {
  1645. throw new ArgumentNullException("original");
  1646. }
  1647. if ( updated == null ) {
  1648. throw new ArgumentNullException("updated");
  1649. }
  1650. CheckUpdating();
  1651. contentsEdited_ = true;
  1652. updates_.Add(new ZipUpdate(original, updated));
  1653. }
  1654. */
  1655. #endregion Modifying Entries
  1656. #region Deleting Entries
  1657. /// <summary>
  1658. /// Delete an entry by name
  1659. /// </summary>
  1660. /// <param name="fileName">The filename to delete</param>
  1661. /// <returns>True if the entry was found and deleted; false otherwise.</returns>
  1662. public bool Delete(string fileName)
  1663. {
  1664. if (fileName == null)
  1665. {
  1666. throw new ArgumentNullException(nameof(fileName));
  1667. }
  1668. CheckUpdating();
  1669. bool result = false;
  1670. int index = FindExistingUpdate(fileName);
  1671. if ((index >= 0) && (updates_[index] != null))
  1672. {
  1673. result = true;
  1674. contentsEdited_ = true;
  1675. updates_[index] = null;
  1676. updateCount_ -= 1;
  1677. }
  1678. else
  1679. {
  1680. throw new ZipException("Cannot find entry to delete");
  1681. }
  1682. return result;
  1683. }
  1684. /// <summary>
  1685. /// Delete a <see cref="ZipEntry"/> from the archive.
  1686. /// </summary>
  1687. /// <param name="entry">The entry to delete.</param>
  1688. public void Delete(ZipEntry entry)
  1689. {
  1690. if (entry == null)
  1691. {
  1692. throw new ArgumentNullException(nameof(entry));
  1693. }
  1694. CheckUpdating();
  1695. int index = FindExistingUpdate(entry);
  1696. if (index >= 0)
  1697. {
  1698. contentsEdited_ = true;
  1699. updates_[index] = null;
  1700. updateCount_ -= 1;
  1701. }
  1702. else
  1703. {
  1704. throw new ZipException("Cannot find entry to delete");
  1705. }
  1706. }
  1707. #endregion Deleting Entries
  1708. #region Update Support
  1709. #region Writing Values/Headers
  1710. private void WriteLEShort(int value)
  1711. {
  1712. baseStream_.WriteByte((byte)(value & 0xff));
  1713. baseStream_.WriteByte((byte)((value >> 8) & 0xff));
  1714. }
  1715. /// <summary>
  1716. /// Write an unsigned short in little endian byte order.
  1717. /// </summary>
  1718. private void WriteLEUshort(ushort value)
  1719. {
  1720. baseStream_.WriteByte((byte)(value & 0xff));
  1721. baseStream_.WriteByte((byte)(value >> 8));
  1722. }
  1723. /// <summary>
  1724. /// Write an int in little endian byte order.
  1725. /// </summary>
  1726. private void WriteLEInt(int value)
  1727. {
  1728. WriteLEShort(value & 0xffff);
  1729. WriteLEShort(value >> 16);
  1730. }
  1731. /// <summary>
  1732. /// Write an unsigned int in little endian byte order.
  1733. /// </summary>
  1734. private void WriteLEUint(uint value)
  1735. {
  1736. WriteLEUshort((ushort)(value & 0xffff));
  1737. WriteLEUshort((ushort)(value >> 16));
  1738. }
  1739. /// <summary>
  1740. /// Write a long in little endian byte order.
  1741. /// </summary>
  1742. private void WriteLeLong(long value)
  1743. {
  1744. WriteLEInt((int)(value & 0xffffffff));
  1745. WriteLEInt((int)(value >> 32));
  1746. }
  1747. private void WriteLEUlong(ulong value)
  1748. {
  1749. WriteLEUint((uint)(value & 0xffffffff));
  1750. WriteLEUint((uint)(value >> 32));
  1751. }
  1752. private void WriteLocalEntryHeader(ZipUpdate update)
  1753. {
  1754. ZipEntry entry = update.OutEntry;
  1755. // TODO: Local offset will require adjusting for multi-disk zip files.
  1756. entry.Offset = baseStream_.Position;
  1757. // TODO: Need to clear any entry flags that dont make sense or throw an exception here.
  1758. if (update.Command != UpdateCommand.Copy)
  1759. {
  1760. if (entry.CompressionMethod == CompressionMethod.Deflated)
  1761. {
  1762. if (entry.Size == 0)
  1763. {
  1764. // No need to compress - no data.
  1765. entry.CompressedSize = entry.Size;
  1766. entry.Crc = 0;
  1767. entry.CompressionMethod = CompressionMethod.Stored;
  1768. }
  1769. }
  1770. else if (entry.CompressionMethod == CompressionMethod.Stored)
  1771. {
  1772. entry.Flags &= ~(int)GeneralBitFlags.Descriptor;
  1773. }
  1774. if (HaveKeys)
  1775. {
  1776. entry.IsCrypted = true;
  1777. if (entry.Crc < 0)
  1778. {
  1779. entry.Flags |= (int)GeneralBitFlags.Descriptor;
  1780. }
  1781. }
  1782. else
  1783. {
  1784. entry.IsCrypted = false;
  1785. }
  1786. switch (useZip64_)
  1787. {
  1788. case UseZip64.Dynamic:
  1789. if (entry.Size < 0)
  1790. {
  1791. entry.ForceZip64();
  1792. }
  1793. break;
  1794. case UseZip64.On:
  1795. entry.ForceZip64();
  1796. break;
  1797. case UseZip64.Off:
  1798. // Do nothing. The entry itself may be using Zip64 independantly.
  1799. break;
  1800. }
  1801. }
  1802. // Write the local file header
  1803. WriteLEInt(ZipConstants.LocalHeaderSignature);
  1804. WriteLEShort(entry.Version);
  1805. WriteLEShort(entry.Flags);
  1806. WriteLEShort((byte)entry.CompressionMethodForHeader);
  1807. WriteLEInt((int)entry.DosTime);
  1808. if (!entry.HasCrc)
  1809. {
  1810. // Note patch address for updating CRC later.
  1811. update.CrcPatchOffset = baseStream_.Position;
  1812. WriteLEInt((int)0);
  1813. }
  1814. else
  1815. {
  1816. WriteLEInt(unchecked((int)entry.Crc));
  1817. }
  1818. if (entry.LocalHeaderRequiresZip64)
  1819. {
  1820. WriteLEInt(-1);
  1821. WriteLEInt(-1);
  1822. }
  1823. else
  1824. {
  1825. if ((entry.CompressedSize < 0) || (entry.Size < 0))
  1826. {
  1827. update.SizePatchOffset = baseStream_.Position;
  1828. }
  1829. WriteLEInt((int)entry.CompressedSize);
  1830. WriteLEInt((int)entry.Size);
  1831. }
  1832. byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name);
  1833. if (name.Length > 0xFFFF)
  1834. {
  1835. throw new ZipException("Entry name too long.");
  1836. }
  1837. var ed = new ZipExtraData(entry.ExtraData);
  1838. if (entry.LocalHeaderRequiresZip64)
  1839. {
  1840. ed.StartNewEntry();
  1841. // Local entry header always includes size and compressed size.
  1842. // NOTE the order of these fields is reversed when compared to the normal headers!
  1843. ed.AddLeLong(entry.Size);
  1844. ed.AddLeLong(entry.CompressedSize);
  1845. ed.AddNewEntry(1);
  1846. }
  1847. else
  1848. {
  1849. ed.Delete(1);
  1850. }
  1851. entry.ExtraData = ed.GetEntryData();
  1852. WriteLEShort(name.Length);
  1853. WriteLEShort(entry.ExtraData.Length);
  1854. if (name.Length > 0)
  1855. {
  1856. baseStream_.Write(name, 0, name.Length);
  1857. }
  1858. if (entry.LocalHeaderRequiresZip64)
  1859. {
  1860. if (!ed.Find(1))
  1861. {
  1862. throw new ZipException("Internal error cannot find extra data");
  1863. }
  1864. update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex;
  1865. }
  1866. if (entry.ExtraData.Length > 0)
  1867. {
  1868. baseStream_.Write(entry.ExtraData, 0, entry.ExtraData.Length);
  1869. }
  1870. }
  1871. private int WriteCentralDirectoryHeader(ZipEntry entry)
  1872. {
  1873. if (entry.CompressedSize < 0)
  1874. {
  1875. throw new ZipException("Attempt to write central directory entry with unknown csize");
  1876. }
  1877. if (entry.Size < 0)
  1878. {
  1879. throw new ZipException("Attempt to write central directory entry with unknown size");
  1880. }
  1881. if (entry.Crc < 0)
  1882. {
  1883. throw new ZipException("Attempt to write central directory entry with unknown crc");
  1884. }
  1885. // Write the central file header
  1886. WriteLEInt(ZipConstants.CentralHeaderSignature);
  1887. // Version made by
  1888. WriteLEShort((entry.HostSystem << 8) | entry.VersionMadeBy);
  1889. // Version required to extract
  1890. WriteLEShort(entry.Version);
  1891. WriteLEShort(entry.Flags);
  1892. unchecked
  1893. {
  1894. WriteLEShort((byte)entry.CompressionMethodForHeader);
  1895. WriteLEInt((int)entry.DosTime);
  1896. WriteLEInt((int)entry.Crc);
  1897. }
  1898. bool useExtraCompressedSize = false; //Do we want to store the compressed size in the extra data?
  1899. if ((entry.IsZip64Forced()) || (entry.CompressedSize >= 0xffffffff))
  1900. {
  1901. useExtraCompressedSize = true;
  1902. WriteLEInt(-1);
  1903. }
  1904. else
  1905. {
  1906. WriteLEInt((int)(entry.CompressedSize & 0xffffffff));
  1907. }
  1908. bool useExtraUncompressedSize = false; //Do we want to store the uncompressed size in the extra data?
  1909. if ((entry.IsZip64Forced()) || (entry.Size >= 0xffffffff))
  1910. {
  1911. useExtraUncompressedSize = true;
  1912. WriteLEInt(-1);
  1913. }
  1914. else
  1915. {
  1916. WriteLEInt((int)entry.Size);
  1917. }
  1918. byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name);
  1919. if (name.Length > 0xFFFF)
  1920. {
  1921. throw new ZipException("Entry name is too long.");
  1922. }
  1923. WriteLEShort(name.Length);
  1924. // Central header extra data is different to local header version so regenerate.
  1925. var ed = new ZipExtraData(entry.ExtraData);
  1926. if (entry.CentralHeaderRequiresZip64)
  1927. {
  1928. ed.StartNewEntry();
  1929. if (useExtraUncompressedSize)
  1930. {
  1931. ed.AddLeLong(entry.Size);
  1932. }
  1933. if (useExtraCompressedSize)
  1934. {
  1935. ed.AddLeLong(entry.CompressedSize);
  1936. }
  1937. if (entry.Offset >= 0xffffffff)
  1938. {
  1939. ed.AddLeLong(entry.Offset);
  1940. }
  1941. // Number of disk on which this file starts isnt supported and is never written here.
  1942. ed.AddNewEntry(1);
  1943. }
  1944. else
  1945. {
  1946. // Should have already be done when local header was added.
  1947. ed.Delete(1);
  1948. }
  1949. byte[] centralExtraData = ed.GetEntryData();
  1950. WriteLEShort(centralExtraData.Length);
  1951. WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0);
  1952. WriteLEShort(0); // disk number
  1953. WriteLEShort(0); // internal file attributes
  1954. // External file attributes...
  1955. if (entry.ExternalFileAttributes != -1)
  1956. {
  1957. WriteLEInt(entry.ExternalFileAttributes);
  1958. }
  1959. else
  1960. {
  1961. if (entry.IsDirectory)
  1962. {
  1963. WriteLEUint(16);
  1964. }
  1965. else
  1966. {
  1967. WriteLEUint(0);
  1968. }
  1969. }
  1970. if (entry.Offset >= 0xffffffff)
  1971. {
  1972. WriteLEUint(0xffffffff);
  1973. }
  1974. else
  1975. {
  1976. WriteLEUint((uint)(int)entry.Offset);
  1977. }
  1978. if (name.Length > 0)
  1979. {
  1980. baseStream_.Write(name, 0, name.Length);
  1981. }
  1982. if (centralExtraData.Length > 0)
  1983. {
  1984. baseStream_.Write(centralExtraData, 0, centralExtraData.Length);
  1985. }
  1986. byte[] rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : new byte[0];
  1987. if (rawComment.Length > 0)
  1988. {
  1989. baseStream_.Write(rawComment, 0, rawComment.Length);
  1990. }
  1991. return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length;
  1992. }
  1993. #endregion Writing Values/Headers
  1994. private void PostUpdateCleanup()
  1995. {
  1996. updateDataSource_ = null;
  1997. updates_ = null;
  1998. updateIndex_ = null;
  1999. if (archiveStorage_ != null)
  2000. {
  2001. archiveStorage_.Dispose();
  2002. archiveStorage_ = null;
  2003. }
  2004. }
  2005. private string GetTransformedFileName(string name)
  2006. {
  2007. INameTransform transform = NameTransform;
  2008. return (transform != null) ?
  2009. transform.TransformFile(name) :
  2010. name;
  2011. }
  2012. private string GetTransformedDirectoryName(string name)
  2013. {
  2014. INameTransform transform = NameTransform;
  2015. return (transform != null) ?
  2016. transform.TransformDirectory(name) :
  2017. name;
  2018. }
  2019. /// <summary>
  2020. /// Get a raw memory buffer.
  2021. /// </summary>
  2022. /// <returns>Returns a raw memory buffer.</returns>
  2023. private byte[] GetBuffer()
  2024. {
  2025. if (copyBuffer_ == null)
  2026. {
  2027. copyBuffer_ = new byte[bufferSize_];
  2028. }
  2029. return copyBuffer_;
  2030. }
  2031. private void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source)
  2032. {
  2033. int bytesToCopy = GetDescriptorSize(update);
  2034. if (bytesToCopy > 0)
  2035. {
  2036. byte[] buffer = GetBuffer();
  2037. while (bytesToCopy > 0)
  2038. {
  2039. int readSize = Math.Min(buffer.Length, bytesToCopy);
  2040. int bytesRead = source.Read(buffer, 0, readSize);
  2041. if (bytesRead > 0)
  2042. {
  2043. dest.Write(buffer, 0, bytesRead);
  2044. bytesToCopy -= bytesRead;
  2045. }
  2046. else
  2047. {
  2048. throw new ZipException("Unxpected end of stream");
  2049. }
  2050. }
  2051. }
  2052. }
  2053. private void CopyBytes(ZipUpdate update, Stream destination, Stream source,
  2054. long bytesToCopy, bool updateCrc)
  2055. {
  2056. if (destination == source)
  2057. {
  2058. throw new InvalidOperationException("Destination and source are the same");
  2059. }
  2060. // NOTE: Compressed size is updated elsewhere.
  2061. var crc = new Crc32();
  2062. byte[] buffer = GetBuffer();
  2063. long targetBytes = bytesToCopy;
  2064. long totalBytesRead = 0;
  2065. int bytesRead;
  2066. do
  2067. {
  2068. int readSize = buffer.Length;
  2069. if (bytesToCopy < readSize)
  2070. {
  2071. readSize = (int)bytesToCopy;
  2072. }
  2073. bytesRead = source.Read(buffer, 0, readSize);
  2074. if (bytesRead > 0)
  2075. {
  2076. if (updateCrc)
  2077. {
  2078. crc.Update(new ArraySegment<byte>(buffer, 0, bytesRead));
  2079. }
  2080. destination.Write(buffer, 0, bytesRead);
  2081. bytesToCopy -= bytesRead;
  2082. totalBytesRead += bytesRead;
  2083. }
  2084. }
  2085. while ((bytesRead > 0) && (bytesToCopy > 0));
  2086. if (totalBytesRead != targetBytes)
  2087. {
  2088. throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead));
  2089. }
  2090. if (updateCrc)
  2091. {
  2092. update.OutEntry.Crc = crc.Value;
  2093. }
  2094. }
  2095. /// <summary>
  2096. /// Get the size of the source descriptor for a <see cref="ZipUpdate"/>.
  2097. /// </summary>
  2098. /// <param name="update">The update to get the size for.</param>
  2099. /// <returns>The descriptor size, zero if there isnt one.</returns>
  2100. private int GetDescriptorSize(ZipUpdate update)
  2101. {
  2102. int result = 0;
  2103. if ((update.Entry.Flags & (int)GeneralBitFlags.Descriptor) != 0)
  2104. {
  2105. result = ZipConstants.DataDescriptorSize - 4;
  2106. if (update.Entry.LocalHeaderRequiresZip64)
  2107. {
  2108. result = ZipConstants.Zip64DataDescriptorSize - 4;
  2109. }
  2110. }
  2111. return result;
  2112. }
  2113. private void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition)
  2114. {
  2115. int bytesToCopy = GetDescriptorSize(update);
  2116. while (bytesToCopy > 0)
  2117. {
  2118. var readSize = (int)bytesToCopy;
  2119. byte[] buffer = GetBuffer();
  2120. stream.Position = sourcePosition;
  2121. int bytesRead = stream.Read(buffer, 0, readSize);
  2122. if (bytesRead > 0)
  2123. {
  2124. stream.Position = destinationPosition;
  2125. stream.Write(buffer, 0, bytesRead);
  2126. bytesToCopy -= bytesRead;
  2127. destinationPosition += bytesRead;
  2128. sourcePosition += bytesRead;
  2129. }
  2130. else
  2131. {
  2132. throw new ZipException("Unxpected end of stream");
  2133. }
  2134. }
  2135. }
  2136. private void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc, ref long destinationPosition, ref long sourcePosition)
  2137. {
  2138. long bytesToCopy = update.Entry.CompressedSize;
  2139. // NOTE: Compressed size is updated elsewhere.
  2140. var crc = new Crc32();
  2141. byte[] buffer = GetBuffer();
  2142. long targetBytes = bytesToCopy;
  2143. long totalBytesRead = 0;
  2144. int bytesRead;
  2145. do
  2146. {
  2147. int readSize = buffer.Length;
  2148. if (bytesToCopy < readSize)
  2149. {
  2150. readSize = (int)bytesToCopy;
  2151. }
  2152. stream.Position = sourcePosition;
  2153. bytesRead = stream.Read(buffer, 0, readSize);
  2154. if (bytesRead > 0)
  2155. {
  2156. if (updateCrc)
  2157. {
  2158. crc.Update(new ArraySegment<byte>(buffer, 0, bytesRead));
  2159. }
  2160. stream.Position = destinationPosition;
  2161. stream.Write(buffer, 0, bytesRead);
  2162. destinationPosition += bytesRead;
  2163. sourcePosition += bytesRead;
  2164. bytesToCopy -= bytesRead;
  2165. totalBytesRead += bytesRead;
  2166. }
  2167. }
  2168. while ((bytesRead > 0) && (bytesToCopy > 0));
  2169. if (totalBytesRead != targetBytes)
  2170. {
  2171. throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead));
  2172. }
  2173. if (updateCrc)
  2174. {
  2175. update.OutEntry.Crc = crc.Value;
  2176. }
  2177. }
  2178. private int FindExistingUpdate(ZipEntry entry)
  2179. {
  2180. int result = -1;
  2181. string convertedName = entry.IsDirectory
  2182. ? GetTransformedDirectoryName(entry.Name)
  2183. : GetTransformedFileName(entry.Name);
  2184. if (updateIndex_.ContainsKey(convertedName))
  2185. {
  2186. result = (int)updateIndex_[convertedName];
  2187. }
  2188. /*
  2189. // This is slow like the coming of the next ice age but takes less storage and may be useful
  2190. // for CF?
  2191. for (int index = 0; index < updates_.Count; ++index)
  2192. {
  2193. ZipUpdate zu = ( ZipUpdate )updates_[index];
  2194. if ( (zu.Entry.ZipFileIndex == entry.ZipFileIndex) &&
  2195. (string.Compare(convertedName, zu.Entry.Name, true, CultureInfo.InvariantCulture) == 0) ) {
  2196. result = index;
  2197. break;
  2198. }
  2199. }
  2200. */
  2201. return result;
  2202. }
  2203. private int FindExistingUpdate(string fileName)
  2204. {
  2205. int result = -1;
  2206. string convertedName = GetTransformedFileName(fileName);
  2207. if (updateIndex_.ContainsKey(convertedName))
  2208. {
  2209. result = (int)updateIndex_[convertedName];
  2210. }
  2211. /*
  2212. // This is slow like the coming of the next ice age but takes less storage and may be useful
  2213. // for CF?
  2214. for ( int index = 0; index < updates_.Count; ++index ) {
  2215. if ( string.Compare(convertedName, (( ZipUpdate )updates_[index]).Entry.Name,
  2216. true, CultureInfo.InvariantCulture) == 0 ) {
  2217. result = index;
  2218. break;
  2219. }
  2220. }
  2221. */
  2222. return result;
  2223. }
  2224. /// <summary>
  2225. /// Get an output stream for the specified <see cref="ZipEntry"/>
  2226. /// </summary>
  2227. /// <param name="entry">The entry to get an output stream for.</param>
  2228. /// <returns>The output stream obtained for the entry.</returns>
  2229. private Stream GetOutputStream(ZipEntry entry)
  2230. {
  2231. Stream result = baseStream_;
  2232. if (entry.IsCrypted == true)
  2233. {
  2234. result = CreateAndInitEncryptionStream(result, entry);
  2235. }
  2236. switch (entry.CompressionMethod)
  2237. {
  2238. case CompressionMethod.Stored:
  2239. result = new UncompressedStream(result);
  2240. break;
  2241. case CompressionMethod.Deflated:
  2242. var dos = new DeflaterOutputStream(result, new Deflater(9, true))
  2243. {
  2244. IsStreamOwner = false
  2245. };
  2246. result = dos;
  2247. break;
  2248. default:
  2249. throw new ZipException("Unknown compression method " + entry.CompressionMethod);
  2250. }
  2251. return result;
  2252. }
  2253. private void AddEntry(ZipFile workFile, ZipUpdate update)
  2254. {
  2255. Stream source = null;
  2256. if (update.Entry.IsFile)
  2257. {
  2258. source = update.GetSource();
  2259. if (source == null)
  2260. {
  2261. source = updateDataSource_.GetSource(update.Entry, update.Filename);
  2262. }
  2263. }
  2264. if (source != null)
  2265. {
  2266. using (source)
  2267. {
  2268. long sourceStreamLength = source.Length;
  2269. if (update.OutEntry.Size < 0)
  2270. {
  2271. update.OutEntry.Size = sourceStreamLength;
  2272. }
  2273. else
  2274. {
  2275. // Check for errant entries.
  2276. if (update.OutEntry.Size != sourceStreamLength)
  2277. {
  2278. throw new ZipException("Entry size/stream size mismatch");
  2279. }
  2280. }
  2281. workFile.WriteLocalEntryHeader(update);
  2282. long dataStart = workFile.baseStream_.Position;
  2283. using (Stream output = workFile.GetOutputStream(update.OutEntry))
  2284. {
  2285. CopyBytes(update, output, source, sourceStreamLength, true);
  2286. }
  2287. long dataEnd = workFile.baseStream_.Position;
  2288. update.OutEntry.CompressedSize = dataEnd - dataStart;
  2289. if ((update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor) == (int)GeneralBitFlags.Descriptor)
  2290. {
  2291. var helper = new ZipHelperStream(workFile.baseStream_);
  2292. helper.WriteDataDescriptor(update.OutEntry);
  2293. }
  2294. }
  2295. }
  2296. else
  2297. {
  2298. workFile.WriteLocalEntryHeader(update);
  2299. update.OutEntry.CompressedSize = 0;
  2300. }
  2301. }
  2302. private void ModifyEntry(ZipFile workFile, ZipUpdate update)
  2303. {
  2304. workFile.WriteLocalEntryHeader(update);
  2305. long dataStart = workFile.baseStream_.Position;
  2306. // TODO: This is slow if the changes don't effect the data!!
  2307. if (update.Entry.IsFile && (update.Filename != null))
  2308. {
  2309. using (Stream output = workFile.GetOutputStream(update.OutEntry))
  2310. {
  2311. using (Stream source = this.GetInputStream(update.Entry))
  2312. {
  2313. CopyBytes(update, output, source, source.Length, true);
  2314. }
  2315. }
  2316. }
  2317. long dataEnd = workFile.baseStream_.Position;
  2318. update.Entry.CompressedSize = dataEnd - dataStart;
  2319. }
  2320. private void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destinationPosition)
  2321. {
  2322. bool skipOver = false || update.Entry.Offset == destinationPosition;
  2323. if (!skipOver)
  2324. {
  2325. baseStream_.Position = destinationPosition;
  2326. workFile.WriteLocalEntryHeader(update);
  2327. destinationPosition = baseStream_.Position;
  2328. }
  2329. long sourcePosition = 0;
  2330. const int NameLengthOffset = 26;
  2331. // TODO: Add base for SFX friendly handling
  2332. long entryDataOffset = update.Entry.Offset + NameLengthOffset;
  2333. baseStream_.Seek(entryDataOffset, SeekOrigin.Begin);
  2334. // Clumsy way of handling retrieving the original name and extra data length for now.
  2335. // TODO: Stop re-reading name and data length in CopyEntryDirect.
  2336. uint nameLength = ReadLEUshort();
  2337. uint extraLength = ReadLEUshort();
  2338. sourcePosition = baseStream_.Position + nameLength + extraLength;
  2339. if (skipOver)
  2340. {
  2341. if (update.OffsetBasedSize != -1)
  2342. destinationPosition += update.OffsetBasedSize;
  2343. else
  2344. // TODO: Find out why this calculation comes up 4 bytes short on some entries in ODT (Office Document Text) archives.
  2345. // WinZip produces a warning on these entries:
  2346. // "caution: value of lrec.csize (compressed size) changed from ..."
  2347. destinationPosition +=
  2348. (sourcePosition - entryDataOffset) + NameLengthOffset + // Header size
  2349. update.Entry.CompressedSize + GetDescriptorSize(update);
  2350. }
  2351. else
  2352. {
  2353. if (update.Entry.CompressedSize > 0)
  2354. {
  2355. CopyEntryDataDirect(update, baseStream_, false, ref destinationPosition, ref sourcePosition);
  2356. }
  2357. CopyDescriptorBytesDirect(update, baseStream_, ref destinationPosition, sourcePosition);
  2358. }
  2359. }
  2360. private void CopyEntry(ZipFile workFile, ZipUpdate update)
  2361. {
  2362. workFile.WriteLocalEntryHeader(update);
  2363. if (update.Entry.CompressedSize > 0)
  2364. {
  2365. const int NameLengthOffset = 26;
  2366. long entryDataOffset = update.Entry.Offset + NameLengthOffset;
  2367. // TODO: This wont work for SFX files!
  2368. baseStream_.Seek(entryDataOffset, SeekOrigin.Begin);
  2369. uint nameLength = ReadLEUshort();
  2370. uint extraLength = ReadLEUshort();
  2371. baseStream_.Seek(nameLength + extraLength, SeekOrigin.Current);
  2372. CopyBytes(update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false);
  2373. }
  2374. CopyDescriptorBytes(update, workFile.baseStream_, baseStream_);
  2375. }
  2376. private void Reopen(Stream source)
  2377. {
  2378. isNewArchive_ = false;
  2379. baseStream_ = source ?? throw new ZipException("Failed to reopen archive - no source");
  2380. ReadEntries();
  2381. }
  2382. private void Reopen()
  2383. {
  2384. if (Name == null)
  2385. {
  2386. throw new InvalidOperationException("Name is not known cannot Reopen");
  2387. }
  2388. Reopen(File.Open(Name, FileMode.Open, FileAccess.Read, FileShare.Read));
  2389. }
  2390. private void UpdateCommentOnly()
  2391. {
  2392. long baseLength = baseStream_.Length;
  2393. ZipHelperStream updateFile = null;
  2394. if (archiveStorage_.UpdateMode == FileUpdateMode.Safe)
  2395. {
  2396. Stream copyStream = archiveStorage_.MakeTemporaryCopy(baseStream_);
  2397. updateFile = new ZipHelperStream(copyStream)
  2398. {
  2399. IsStreamOwner = true
  2400. };
  2401. baseStream_.Dispose();
  2402. baseStream_ = null;
  2403. }
  2404. else
  2405. {
  2406. if (archiveStorage_.UpdateMode == FileUpdateMode.Direct)
  2407. {
  2408. // TODO: archiveStorage wasnt originally intended for this use.
  2409. // Need to revisit this to tidy up handling as archive storage currently doesnt
  2410. // handle the original stream well.
  2411. // The problem is when using an existing zip archive with an in memory archive storage.
  2412. // The open stream wont support writing but the memory storage should open the same file not an in memory one.
  2413. // Need to tidy up the archive storage interface and contract basically.
  2414. baseStream_ = archiveStorage_.OpenForDirectUpdate(baseStream_);
  2415. updateFile = new ZipHelperStream(baseStream_);
  2416. }
  2417. else
  2418. {
  2419. baseStream_.Dispose();
  2420. baseStream_ = null;
  2421. updateFile = new ZipHelperStream(Name);
  2422. }
  2423. }
  2424. using (updateFile)
  2425. {
  2426. long locatedCentralDirOffset =
  2427. updateFile.LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature,
  2428. baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff);
  2429. if (locatedCentralDirOffset < 0)
  2430. {
  2431. throw new ZipException("Cannot find central directory");
  2432. }
  2433. const int CentralHeaderCommentSizeOffset = 16;
  2434. updateFile.Position += CentralHeaderCommentSizeOffset;
  2435. byte[] rawComment = newComment_.RawComment;
  2436. updateFile.WriteLEShort(rawComment.Length);
  2437. updateFile.Write(rawComment, 0, rawComment.Length);
  2438. updateFile.SetLength(updateFile.Position);
  2439. }
  2440. if (archiveStorage_.UpdateMode == FileUpdateMode.Safe)
  2441. {
  2442. Reopen(archiveStorage_.ConvertTemporaryToFinal());
  2443. }
  2444. else
  2445. {
  2446. ReadEntries();
  2447. }
  2448. }
  2449. /// <summary>
  2450. /// Class used to sort updates.
  2451. /// </summary>
  2452. private class UpdateComparer : IComparer<ZipUpdate>
  2453. {
  2454. /// <summary>
  2455. /// Compares two objects and returns a value indicating whether one is
  2456. /// less than, equal to or greater than the other.
  2457. /// </summary>
  2458. /// <param name="x">First object to compare</param>
  2459. /// <param name="y">Second object to compare.</param>
  2460. /// <returns>Compare result.</returns>
  2461. public int Compare(ZipUpdate x, ZipUpdate y)
  2462. {
  2463. int result;
  2464. if (x == null)
  2465. {
  2466. if (y == null)
  2467. {
  2468. result = 0;
  2469. }
  2470. else
  2471. {
  2472. result = -1;
  2473. }
  2474. }
  2475. else if (y == null)
  2476. {
  2477. result = 1;
  2478. }
  2479. else
  2480. {
  2481. int xCmdValue = ((x.Command == UpdateCommand.Copy) || (x.Command == UpdateCommand.Modify)) ? 0 : 1;
  2482. int yCmdValue = ((y.Command == UpdateCommand.Copy) || (y.Command == UpdateCommand.Modify)) ? 0 : 1;
  2483. result = xCmdValue - yCmdValue;
  2484. if (result == 0)
  2485. {
  2486. long offsetDiff = x.Entry.Offset - y.Entry.Offset;
  2487. if (offsetDiff < 0)
  2488. {
  2489. result = -1;
  2490. }
  2491. else if (offsetDiff == 0)
  2492. {
  2493. result = 0;
  2494. }
  2495. else
  2496. {
  2497. result = 1;
  2498. }
  2499. }
  2500. }
  2501. return result;
  2502. }
  2503. }
  2504. private void RunUpdates()
  2505. {
  2506. long sizeEntries = 0;
  2507. long endOfStream = 0;
  2508. bool directUpdate = false;
  2509. long destinationPosition = 0; // NOT SFX friendly
  2510. ZipFile workFile;
  2511. if (IsNewArchive)
  2512. {
  2513. workFile = this;
  2514. workFile.baseStream_.Position = 0;
  2515. directUpdate = true;
  2516. }
  2517. else if (archiveStorage_.UpdateMode == FileUpdateMode.Direct)
  2518. {
  2519. workFile = this;
  2520. workFile.baseStream_.Position = 0;
  2521. directUpdate = true;
  2522. // Sort the updates by offset within copies/modifies, then adds.
  2523. // This ensures that data required by copies will not be overwritten.
  2524. updates_.Sort(new UpdateComparer());
  2525. }
  2526. else
  2527. {
  2528. workFile = ZipFile.Create(archiveStorage_.GetTemporaryOutput());
  2529. workFile.UseZip64 = UseZip64;
  2530. if (key != null)
  2531. {
  2532. workFile.key = (byte[])key.Clone();
  2533. }
  2534. }
  2535. try
  2536. {
  2537. foreach (ZipUpdate update in updates_)
  2538. {
  2539. if (update != null)
  2540. {
  2541. switch (update.Command)
  2542. {
  2543. case UpdateCommand.Copy:
  2544. if (directUpdate)
  2545. {
  2546. CopyEntryDirect(workFile, update, ref destinationPosition);
  2547. }
  2548. else
  2549. {
  2550. CopyEntry(workFile, update);
  2551. }
  2552. break;
  2553. case UpdateCommand.Modify:
  2554. // TODO: Direct modifying of an entry will take some legwork.
  2555. ModifyEntry(workFile, update);
  2556. break;
  2557. case UpdateCommand.Add:
  2558. if (!IsNewArchive && directUpdate)
  2559. {
  2560. workFile.baseStream_.Position = destinationPosition;
  2561. }
  2562. AddEntry(workFile, update);
  2563. if (directUpdate)
  2564. {
  2565. destinationPosition = workFile.baseStream_.Position;
  2566. }
  2567. break;
  2568. }
  2569. }
  2570. }
  2571. if (!IsNewArchive && directUpdate)
  2572. {
  2573. workFile.baseStream_.Position = destinationPosition;
  2574. }
  2575. long centralDirOffset = workFile.baseStream_.Position;
  2576. foreach (ZipUpdate update in updates_)
  2577. {
  2578. if (update != null)
  2579. {
  2580. sizeEntries += workFile.WriteCentralDirectoryHeader(update.OutEntry);
  2581. }
  2582. }
  2583. byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipStrings.ConvertToArray(comment_);
  2584. using (ZipHelperStream zhs = new ZipHelperStream(workFile.baseStream_))
  2585. {
  2586. zhs.WriteEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment);
  2587. }
  2588. endOfStream = workFile.baseStream_.Position;
  2589. // And now patch entries...
  2590. foreach (ZipUpdate update in updates_)
  2591. {
  2592. if (update != null)
  2593. {
  2594. // If the size of the entry is zero leave the crc as 0 as well.
  2595. // The calculated crc will be all bits on...
  2596. if ((update.CrcPatchOffset > 0) && (update.OutEntry.CompressedSize > 0))
  2597. {
  2598. workFile.baseStream_.Position = update.CrcPatchOffset;
  2599. workFile.WriteLEInt((int)update.OutEntry.Crc);
  2600. }
  2601. if (update.SizePatchOffset > 0)
  2602. {
  2603. workFile.baseStream_.Position = update.SizePatchOffset;
  2604. if (update.OutEntry.LocalHeaderRequiresZip64)
  2605. {
  2606. workFile.WriteLeLong(update.OutEntry.Size);
  2607. workFile.WriteLeLong(update.OutEntry.CompressedSize);
  2608. }
  2609. else
  2610. {
  2611. workFile.WriteLEInt((int)update.OutEntry.CompressedSize);
  2612. workFile.WriteLEInt((int)update.OutEntry.Size);
  2613. }
  2614. }
  2615. }
  2616. }
  2617. }
  2618. catch
  2619. {
  2620. workFile.Close();
  2621. if (!directUpdate && (workFile.Name != null))
  2622. {
  2623. File.Delete(workFile.Name);
  2624. }
  2625. throw;
  2626. }
  2627. if (directUpdate)
  2628. {
  2629. workFile.baseStream_.SetLength(endOfStream);
  2630. workFile.baseStream_.Flush();
  2631. isNewArchive_ = false;
  2632. ReadEntries();
  2633. }
  2634. else
  2635. {
  2636. baseStream_.Dispose();
  2637. Reopen(archiveStorage_.ConvertTemporaryToFinal());
  2638. }
  2639. }
  2640. private void CheckUpdating()
  2641. {
  2642. if (updates_ == null)
  2643. {
  2644. throw new InvalidOperationException("BeginUpdate has not been called");
  2645. }
  2646. }
  2647. #endregion Update Support
  2648. #region ZipUpdate class
  2649. /// <summary>
  2650. /// Represents a pending update to a Zip file.
  2651. /// </summary>
  2652. private class ZipUpdate
  2653. {
  2654. #region Constructors
  2655. public ZipUpdate(string fileName, ZipEntry entry)
  2656. {
  2657. command_ = UpdateCommand.Add;
  2658. entry_ = entry;
  2659. filename_ = fileName;
  2660. }
  2661. [Obsolete]
  2662. public ZipUpdate(string fileName, string entryName, CompressionMethod compressionMethod)
  2663. {
  2664. command_ = UpdateCommand.Add;
  2665. entry_ = new ZipEntry(entryName)
  2666. {
  2667. CompressionMethod = compressionMethod
  2668. };
  2669. filename_ = fileName;
  2670. }
  2671. [Obsolete]
  2672. public ZipUpdate(string fileName, string entryName)
  2673. : this(fileName, entryName, CompressionMethod.Deflated)
  2674. {
  2675. // Do nothing.
  2676. }
  2677. [Obsolete]
  2678. public ZipUpdate(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod)
  2679. {
  2680. command_ = UpdateCommand.Add;
  2681. entry_ = new ZipEntry(entryName)
  2682. {
  2683. CompressionMethod = compressionMethod
  2684. };
  2685. dataSource_ = dataSource;
  2686. }
  2687. public ZipUpdate(IStaticDataSource dataSource, ZipEntry entry)
  2688. {
  2689. command_ = UpdateCommand.Add;
  2690. entry_ = entry;
  2691. dataSource_ = dataSource;
  2692. }
  2693. public ZipUpdate(ZipEntry original, ZipEntry updated)
  2694. {
  2695. throw new ZipException("Modify not currently supported");
  2696. /*
  2697. command_ = UpdateCommand.Modify;
  2698. entry_ = ( ZipEntry )original.Clone();
  2699. outEntry_ = ( ZipEntry )updated.Clone();
  2700. */
  2701. }
  2702. public ZipUpdate(UpdateCommand command, ZipEntry entry)
  2703. {
  2704. command_ = command;
  2705. entry_ = (ZipEntry)entry.Clone();
  2706. }
  2707. /// <summary>
  2708. /// Copy an existing entry.
  2709. /// </summary>
  2710. /// <param name="entry">The existing entry to copy.</param>
  2711. public ZipUpdate(ZipEntry entry)
  2712. : this(UpdateCommand.Copy, entry)
  2713. {
  2714. // Do nothing.
  2715. }
  2716. #endregion Constructors
  2717. /// <summary>
  2718. /// Get the <see cref="ZipEntry"/> for this update.
  2719. /// </summary>
  2720. /// <remarks>This is the source or original entry.</remarks>
  2721. public ZipEntry Entry
  2722. {
  2723. get { return entry_; }
  2724. }
  2725. /// <summary>
  2726. /// Get the <see cref="ZipEntry"/> that will be written to the updated/new file.
  2727. /// </summary>
  2728. public ZipEntry OutEntry
  2729. {
  2730. get
  2731. {
  2732. if (outEntry_ == null)
  2733. {
  2734. outEntry_ = (ZipEntry)entry_.Clone();
  2735. }
  2736. return outEntry_;
  2737. }
  2738. }
  2739. /// <summary>
  2740. /// Get the command for this update.
  2741. /// </summary>
  2742. public UpdateCommand Command
  2743. {
  2744. get { return command_; }
  2745. }
  2746. /// <summary>
  2747. /// Get the filename if any for this update. Null if none exists.
  2748. /// </summary>
  2749. public string Filename
  2750. {
  2751. get { return filename_; }
  2752. }
  2753. /// <summary>
  2754. /// Get/set the location of the size patch for this update.
  2755. /// </summary>
  2756. public long SizePatchOffset
  2757. {
  2758. get { return sizePatchOffset_; }
  2759. set { sizePatchOffset_ = value; }
  2760. }
  2761. /// <summary>
  2762. /// Get /set the location of the crc patch for this update.
  2763. /// </summary>
  2764. public long CrcPatchOffset
  2765. {
  2766. get { return crcPatchOffset_; }
  2767. set { crcPatchOffset_ = value; }
  2768. }
  2769. /// <summary>
  2770. /// Get/set the size calculated by offset.
  2771. /// Specifically, the difference between this and next entry's starting offset.
  2772. /// </summary>
  2773. public long OffsetBasedSize
  2774. {
  2775. get { return _offsetBasedSize; }
  2776. set { _offsetBasedSize = value; }
  2777. }
  2778. public Stream GetSource()
  2779. {
  2780. Stream result = null;
  2781. if (dataSource_ != null)
  2782. {
  2783. result = dataSource_.GetSource();
  2784. }
  2785. return result;
  2786. }
  2787. #region Instance Fields
  2788. private ZipEntry entry_;
  2789. private ZipEntry outEntry_;
  2790. private readonly UpdateCommand command_;
  2791. private IStaticDataSource dataSource_;
  2792. private readonly string filename_;
  2793. private long sizePatchOffset_ = -1;
  2794. private long crcPatchOffset_ = -1;
  2795. private long _offsetBasedSize = -1;
  2796. #endregion Instance Fields
  2797. }
  2798. #endregion ZipUpdate class
  2799. #endregion Updating
  2800. #region Disposing
  2801. #region IDisposable Members
  2802. void IDisposable.Dispose()
  2803. {
  2804. Close();
  2805. }
  2806. #endregion IDisposable Members
  2807. private void DisposeInternal(bool disposing)
  2808. {
  2809. if (!isDisposed_)
  2810. {
  2811. isDisposed_ = true;
  2812. entries_ = new ZipEntry[0];
  2813. if (IsStreamOwner && (baseStream_ != null))
  2814. {
  2815. lock (baseStream_)
  2816. {
  2817. baseStream_.Dispose();
  2818. }
  2819. }
  2820. PostUpdateCleanup();
  2821. }
  2822. }
  2823. /// <summary>
  2824. /// Releases the unmanaged resources used by the this instance and optionally releases the managed resources.
  2825. /// </summary>
  2826. /// <param name="disposing">true to release both managed and unmanaged resources;
  2827. /// false to release only unmanaged resources.</param>
  2828. protected virtual void Dispose(bool disposing)
  2829. {
  2830. DisposeInternal(disposing);
  2831. }
  2832. #endregion Disposing
  2833. #region Internal routines
  2834. #region Reading
  2835. /// <summary>
  2836. /// Read an unsigned short in little endian byte order.
  2837. /// </summary>
  2838. /// <returns>Returns the value read.</returns>
  2839. /// <exception cref="EndOfStreamException">
  2840. /// The stream ends prematurely
  2841. /// </exception>
  2842. private ushort ReadLEUshort()
  2843. {
  2844. int data1 = baseStream_.ReadByte();
  2845. if (data1 < 0)
  2846. {
  2847. throw new EndOfStreamException("End of stream");
  2848. }
  2849. int data2 = baseStream_.ReadByte();
  2850. if (data2 < 0)
  2851. {
  2852. throw new EndOfStreamException("End of stream");
  2853. }
  2854. return unchecked((ushort)((ushort)data1 | (ushort)(data2 << 8)));
  2855. }
  2856. /// <summary>
  2857. /// Read a uint in little endian byte order.
  2858. /// </summary>
  2859. /// <returns>Returns the value read.</returns>
  2860. /// <exception cref="IOException">
  2861. /// An i/o error occurs.
  2862. /// </exception>
  2863. /// <exception cref="System.IO.EndOfStreamException">
  2864. /// The file ends prematurely
  2865. /// </exception>
  2866. private uint ReadLEUint()
  2867. {
  2868. return (uint)(ReadLEUshort() | (ReadLEUshort() << 16));
  2869. }
  2870. private ulong ReadLEUlong()
  2871. {
  2872. return ReadLEUint() | ((ulong)ReadLEUint() << 32);
  2873. }
  2874. #endregion Reading
  2875. // NOTE this returns the offset of the first byte after the signature.
  2876. private long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData)
  2877. {
  2878. using (ZipHelperStream les = new ZipHelperStream(baseStream_))
  2879. {
  2880. return les.LocateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData);
  2881. }
  2882. }
  2883. /// <summary>
  2884. /// Search for and read the central directory of a zip file filling the entries array.
  2885. /// </summary>
  2886. /// <exception cref="System.IO.IOException">
  2887. /// An i/o error occurs.
  2888. /// </exception>
  2889. /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException">
  2890. /// The central directory is malformed or cannot be found
  2891. /// </exception>
  2892. private void ReadEntries()
  2893. {
  2894. // Search for the End Of Central Directory. When a zip comment is
  2895. // present the directory will start earlier
  2896. //
  2897. // The search is limited to 64K which is the maximum size of a trailing comment field to aid speed.
  2898. // This should be compatible with both SFX and ZIP files but has only been tested for Zip files
  2899. // If a SFX file has the Zip data attached as a resource and there are other resources occuring later then
  2900. // this could be invalid.
  2901. // Could also speed this up by reading memory in larger blocks.
  2902. if (baseStream_.CanSeek == false)
  2903. {
  2904. throw new ZipException("ZipFile stream must be seekable");
  2905. }
  2906. long locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature,
  2907. baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff);
  2908. if (locatedEndOfCentralDir < 0)
  2909. {
  2910. throw new ZipException("Cannot find central directory");
  2911. }
  2912. // Read end of central directory record
  2913. ushort thisDiskNumber = ReadLEUshort();
  2914. ushort startCentralDirDisk = ReadLEUshort();
  2915. ulong entriesForThisDisk = ReadLEUshort();
  2916. ulong entriesForWholeCentralDir = ReadLEUshort();
  2917. ulong centralDirSize = ReadLEUint();
  2918. long offsetOfCentralDir = ReadLEUint();
  2919. uint commentSize = ReadLEUshort();
  2920. if (commentSize > 0)
  2921. {
  2922. byte[] comment = new byte[commentSize];
  2923. StreamUtils.ReadFully(baseStream_, comment);
  2924. comment_ = ZipStrings.ConvertToString(comment);
  2925. }
  2926. else
  2927. {
  2928. comment_ = string.Empty;
  2929. }
  2930. bool isZip64 = false;
  2931. bool requireZip64 = false;
  2932. // Check if zip64 header information is required.
  2933. if ((thisDiskNumber == 0xffff) ||
  2934. (startCentralDirDisk == 0xffff) ||
  2935. (entriesForThisDisk == 0xffff) ||
  2936. (entriesForWholeCentralDir == 0xffff) ||
  2937. (centralDirSize == 0xffffffff) ||
  2938. (offsetOfCentralDir == 0xffffffff))
  2939. {
  2940. requireZip64 = true;
  2941. }
  2942. // #357 - always check for the existance of the Zip64 central directory.
  2943. // #403 - Take account of the fixed size of the locator when searching.
  2944. // Subtract from locatedEndOfCentralDir so that the endLocation is the location of EndOfCentralDirectorySignature,
  2945. // rather than the data following the signature.
  2946. long locatedZip64EndOfCentralDirLocator = LocateBlockWithSignature(
  2947. ZipConstants.Zip64CentralDirLocatorSignature,
  2948. locatedEndOfCentralDir - 4,
  2949. ZipConstants.Zip64EndOfCentralDirectoryLocatorSize,
  2950. 0);
  2951. if (locatedZip64EndOfCentralDirLocator < 0)
  2952. {
  2953. if (requireZip64)
  2954. {
  2955. // This is only an error in cases where the Zip64 directory is required.
  2956. throw new ZipException("Cannot find Zip64 locator");
  2957. }
  2958. }
  2959. else
  2960. {
  2961. isZip64 = true;
  2962. // number of the disk with the start of the zip64 end of central directory 4 bytes
  2963. // relative offset of the zip64 end of central directory record 8 bytes
  2964. // total number of disks 4 bytes
  2965. ReadLEUint(); // startDisk64 is not currently used
  2966. ulong offset64 = ReadLEUlong();
  2967. uint totalDisks = ReadLEUint();
  2968. baseStream_.Position = (long)offset64;
  2969. long sig64 = ReadLEUint();
  2970. if (sig64 != ZipConstants.Zip64CentralFileHeaderSignature)
  2971. {
  2972. throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64));
  2973. }
  2974. // NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12.
  2975. ulong recordSize = ReadLEUlong();
  2976. int versionMadeBy = ReadLEUshort();
  2977. int versionToExtract = ReadLEUshort();
  2978. uint thisDisk = ReadLEUint();
  2979. uint centralDirDisk = ReadLEUint();
  2980. entriesForThisDisk = ReadLEUlong();
  2981. entriesForWholeCentralDir = ReadLEUlong();
  2982. centralDirSize = ReadLEUlong();
  2983. offsetOfCentralDir = (long)ReadLEUlong();
  2984. // NOTE: zip64 extensible data sector (variable size) is ignored.
  2985. }
  2986. entries_ = new ZipEntry[entriesForThisDisk];
  2987. // SFX/embedded support, find the offset of the first entry vis the start of the stream
  2988. // This applies to Zip files that are appended to the end of an SFX stub.
  2989. // Or are appended as a resource to an executable.
  2990. // Zip files created by some archivers have the offsets altered to reflect the true offsets
  2991. // and so dont require any adjustment here...
  2992. // TODO: Difficulty with Zip64 and SFX offset handling needs resolution - maths?
  2993. if (!isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long)centralDirSize)))
  2994. {
  2995. offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long)centralDirSize + offsetOfCentralDir);
  2996. if (offsetOfFirstEntry <= 0)
  2997. {
  2998. throw new ZipException("Invalid embedded zip archive");
  2999. }
  3000. }
  3001. baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin);
  3002. for (ulong i = 0; i < entriesForThisDisk; i++)
  3003. {
  3004. if (ReadLEUint() != ZipConstants.CentralHeaderSignature)
  3005. {
  3006. throw new ZipException("Wrong Central Directory signature");
  3007. }
  3008. int versionMadeBy = ReadLEUshort();
  3009. int versionToExtract = ReadLEUshort();
  3010. int bitFlags = ReadLEUshort();
  3011. int method = ReadLEUshort();
  3012. uint dostime = ReadLEUint();
  3013. uint crc = ReadLEUint();
  3014. var csize = (long)ReadLEUint();
  3015. var size = (long)ReadLEUint();
  3016. int nameLen = ReadLEUshort();
  3017. int extraLen = ReadLEUshort();
  3018. int commentLen = ReadLEUshort();
  3019. int diskStartNo = ReadLEUshort(); // Not currently used
  3020. int internalAttributes = ReadLEUshort(); // Not currently used
  3021. uint externalAttributes = ReadLEUint();
  3022. long offset = ReadLEUint();
  3023. byte[] buffer = new byte[Math.Max(nameLen, commentLen)];
  3024. StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen);
  3025. string name = ZipStrings.ConvertToStringExt(bitFlags, buffer, nameLen);
  3026. var entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method)
  3027. {
  3028. Crc = crc & 0xffffffffL,
  3029. Size = size & 0xffffffffL,
  3030. CompressedSize = csize & 0xffffffffL,
  3031. Flags = bitFlags,
  3032. DosTime = dostime,
  3033. ZipFileIndex = (long)i,
  3034. Offset = offset,
  3035. ExternalFileAttributes = (int)externalAttributes
  3036. };
  3037. if ((bitFlags & 8) == 0)
  3038. {
  3039. entry.CryptoCheckValue = (byte)(crc >> 24);
  3040. }
  3041. else
  3042. {
  3043. entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff);
  3044. }
  3045. if (extraLen > 0)
  3046. {
  3047. byte[] extra = new byte[extraLen];
  3048. StreamUtils.ReadFully(baseStream_, extra);
  3049. entry.ExtraData = extra;
  3050. }
  3051. entry.ProcessExtraData(false);
  3052. if (commentLen > 0)
  3053. {
  3054. StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen);
  3055. entry.Comment = ZipStrings.ConvertToStringExt(bitFlags, buffer, commentLen);
  3056. }
  3057. entries_[i] = entry;
  3058. }
  3059. }
  3060. /// <summary>
  3061. /// Locate the data for a given entry.
  3062. /// </summary>
  3063. /// <returns>
  3064. /// The start offset of the data.
  3065. /// </returns>
  3066. /// <exception cref="System.IO.EndOfStreamException">
  3067. /// The stream ends prematurely
  3068. /// </exception>
  3069. /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException">
  3070. /// The local header signature is invalid, the entry and central header file name lengths are different
  3071. /// or the local and entry compression methods dont match
  3072. /// </exception>
  3073. private long LocateEntry(ZipEntry entry)
  3074. {
  3075. return TestLocalHeader(entry, HeaderTest.Extract);
  3076. }
  3077. private Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry)
  3078. {
  3079. CryptoStream result = null;
  3080. if (entry.CompressionMethodForHeader == CompressionMethod.WinZipAES)
  3081. {
  3082. if (entry.Version >= ZipConstants.VERSION_AES)
  3083. {
  3084. //
  3085. OnKeysRequired(entry.Name);
  3086. if (HaveKeys == false)
  3087. {
  3088. throw new ZipException("No password available for AES encrypted stream");
  3089. }
  3090. int saltLen = entry.AESSaltLen;
  3091. byte[] saltBytes = new byte[saltLen];
  3092. int saltIn = StreamUtils.ReadRequestedBytes(baseStream, saltBytes, 0, saltLen);
  3093. if (saltIn != saltLen)
  3094. throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn);
  3095. //
  3096. byte[] pwdVerifyRead = new byte[2];
  3097. StreamUtils.ReadFully(baseStream, pwdVerifyRead);
  3098. int blockSize = entry.AESKeySize / 8; // bits to bytes
  3099. var decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, false);
  3100. byte[] pwdVerifyCalc = decryptor.PwdVerifier;
  3101. if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1])
  3102. throw new ZipException("Invalid password for AES");
  3103. result = new ZipAESStream(baseStream, decryptor, CryptoStreamMode.Read);
  3104. }
  3105. else
  3106. {
  3107. throw new ZipException("Decryption method not supported");
  3108. }
  3109. }
  3110. else
  3111. {
  3112. if ((entry.Version < ZipConstants.VersionStrongEncryption)
  3113. || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0)
  3114. {
  3115. var classicManaged = new PkzipClassicManaged();
  3116. OnKeysRequired(entry.Name);
  3117. if (HaveKeys == false)
  3118. {
  3119. throw new ZipException("No password available for encrypted stream");
  3120. }
  3121. result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read);
  3122. CheckClassicPassword(result, entry);
  3123. }
  3124. else
  3125. {
  3126. // We don't support PKWare strong encryption
  3127. throw new ZipException("Decryption method not supported");
  3128. }
  3129. }
  3130. return result;
  3131. }
  3132. private Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry)
  3133. {
  3134. CryptoStream result = null;
  3135. if ((entry.Version < ZipConstants.VersionStrongEncryption)
  3136. || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0)
  3137. {
  3138. var classicManaged = new PkzipClassicManaged();
  3139. OnKeysRequired(entry.Name);
  3140. if (HaveKeys == false)
  3141. {
  3142. throw new ZipException("No password available for encrypted stream");
  3143. }
  3144. // Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream
  3145. // which doesnt do this.
  3146. result = new CryptoStream(new UncompressedStream(baseStream),
  3147. classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write);
  3148. if ((entry.Crc < 0) || (entry.Flags & 8) != 0)
  3149. {
  3150. WriteEncryptionHeader(result, entry.DosTime << 16);
  3151. }
  3152. else
  3153. {
  3154. WriteEncryptionHeader(result, entry.Crc);
  3155. }
  3156. }
  3157. return result;
  3158. }
  3159. private static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry)
  3160. {
  3161. byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
  3162. StreamUtils.ReadFully(classicCryptoStream, cryptbuffer);
  3163. if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue)
  3164. {
  3165. throw new ZipException("Invalid password");
  3166. }
  3167. }
  3168. private static void WriteEncryptionHeader(Stream stream, long crcValue)
  3169. {
  3170. byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize];
  3171. var rnd = new Random();
  3172. rnd.NextBytes(cryptBuffer);
  3173. cryptBuffer[11] = (byte)(crcValue >> 24);
  3174. stream.Write(cryptBuffer, 0, cryptBuffer.Length);
  3175. }
  3176. #endregion Internal routines
  3177. #region Instance Fields
  3178. private bool isDisposed_;
  3179. private string name_;
  3180. private string comment_;
  3181. private string rawPassword_;
  3182. private Stream baseStream_;
  3183. private bool isStreamOwner;
  3184. private long offsetOfFirstEntry;
  3185. private ZipEntry[] entries_;
  3186. private byte[] key;
  3187. private bool isNewArchive_;
  3188. // Default is dynamic which is not backwards compatible and can cause problems
  3189. // with XP's built in compression which cant read Zip64 archives.
  3190. // However it does avoid the situation were a large file is added and cannot be completed correctly.
  3191. // Hint: Set always ZipEntry size before they are added to an archive and this setting isnt needed.
  3192. private UseZip64 useZip64_ = UseZip64.Dynamic;
  3193. #region Zip Update Instance Fields
  3194. private List<ZipUpdate> updates_;
  3195. private long updateCount_; // Count is managed manually as updates_ can contain nulls!
  3196. private Dictionary<string, int> updateIndex_;
  3197. private IArchiveStorage archiveStorage_;
  3198. private IDynamicDataSource updateDataSource_;
  3199. private bool contentsEdited_;
  3200. private int bufferSize_ = DefaultBufferSize;
  3201. private byte[] copyBuffer_;
  3202. private ZipString newComment_;
  3203. private bool commentEdited_;
  3204. private IEntryFactory updateEntryFactory_ = new ZipEntryFactory();
  3205. #endregion Zip Update Instance Fields
  3206. #endregion Instance Fields
  3207. #region Support Classes
  3208. /// <summary>
  3209. /// Represents a string from a <see cref="ZipFile"/> which is stored as an array of bytes.
  3210. /// </summary>
  3211. private class ZipString
  3212. {
  3213. #region Constructors
  3214. /// <summary>
  3215. /// Initialise a <see cref="ZipString"/> with a string.
  3216. /// </summary>
  3217. /// <param name="comment">The textual string form.</param>
  3218. public ZipString(string comment)
  3219. {
  3220. comment_ = comment;
  3221. isSourceString_ = true;
  3222. }
  3223. /// <summary>
  3224. /// Initialise a <see cref="ZipString"/> using a string in its binary 'raw' form.
  3225. /// </summary>
  3226. /// <param name="rawString"></param>
  3227. public ZipString(byte[] rawString)
  3228. {
  3229. rawComment_ = rawString;
  3230. }
  3231. #endregion Constructors
  3232. /// <summary>
  3233. /// Get a value indicating the original source of data for this instance.
  3234. /// True if the source was a string; false if the source was binary data.
  3235. /// </summary>
  3236. public bool IsSourceString
  3237. {
  3238. get { return isSourceString_; }
  3239. }
  3240. /// <summary>
  3241. /// Get the length of the comment when represented as raw bytes.
  3242. /// </summary>
  3243. public int RawLength
  3244. {
  3245. get
  3246. {
  3247. MakeBytesAvailable();
  3248. return rawComment_.Length;
  3249. }
  3250. }
  3251. /// <summary>
  3252. /// Get the comment in its 'raw' form as plain bytes.
  3253. /// </summary>
  3254. public byte[] RawComment
  3255. {
  3256. get
  3257. {
  3258. MakeBytesAvailable();
  3259. return (byte[])rawComment_.Clone();
  3260. }
  3261. }
  3262. /// <summary>
  3263. /// Reset the comment to its initial state.
  3264. /// </summary>
  3265. public void Reset()
  3266. {
  3267. if (isSourceString_)
  3268. {
  3269. rawComment_ = null;
  3270. }
  3271. else
  3272. {
  3273. comment_ = null;
  3274. }
  3275. }
  3276. private void MakeTextAvailable()
  3277. {
  3278. if (comment_ == null)
  3279. {
  3280. comment_ = ZipStrings.ConvertToString(rawComment_);
  3281. }
  3282. }
  3283. private void MakeBytesAvailable()
  3284. {
  3285. if (rawComment_ == null)
  3286. {
  3287. rawComment_ = ZipStrings.ConvertToArray(comment_);
  3288. }
  3289. }
  3290. /// <summary>
  3291. /// Implicit conversion of comment to a string.
  3292. /// </summary>
  3293. /// <param name="zipString">The <see cref="ZipString"/> to convert to a string.</param>
  3294. /// <returns>The textual equivalent for the input value.</returns>
  3295. static public implicit operator string(ZipString zipString)
  3296. {
  3297. zipString.MakeTextAvailable();
  3298. return zipString.comment_;
  3299. }
  3300. #region Instance Fields
  3301. private string comment_;
  3302. private byte[] rawComment_;
  3303. private readonly bool isSourceString_;
  3304. #endregion Instance Fields
  3305. }
  3306. /// <summary>
  3307. /// An <see cref="IEnumerator">enumerator</see> for <see cref="ZipEntry">Zip entries</see>
  3308. /// </summary>
  3309. private class ZipEntryEnumerator : IEnumerator
  3310. {
  3311. #region Constructors
  3312. public ZipEntryEnumerator(ZipEntry[] entries)
  3313. {
  3314. array = entries;
  3315. }
  3316. #endregion Constructors
  3317. #region IEnumerator Members
  3318. public object Current
  3319. {
  3320. get
  3321. {
  3322. return array[index];
  3323. }
  3324. }
  3325. public void Reset()
  3326. {
  3327. index = -1;
  3328. }
  3329. public bool MoveNext()
  3330. {
  3331. return (++index < array.Length);
  3332. }
  3333. #endregion IEnumerator Members
  3334. #region Instance Fields
  3335. private ZipEntry[] array;
  3336. private int index = -1;
  3337. #endregion Instance Fields
  3338. }
  3339. /// <summary>
  3340. /// An <see cref="UncompressedStream"/> is a stream that you can write uncompressed data
  3341. /// to and flush, but cannot read, seek or do anything else to.
  3342. /// </summary>
  3343. private class UncompressedStream : Stream
  3344. {
  3345. #region Constructors
  3346. public UncompressedStream(Stream baseStream)
  3347. {
  3348. baseStream_ = baseStream;
  3349. }
  3350. #endregion Constructors
  3351. /// <summary>
  3352. /// Gets a value indicating whether the current stream supports reading.
  3353. /// </summary>
  3354. public override bool CanRead
  3355. {
  3356. get
  3357. {
  3358. return false;
  3359. }
  3360. }
  3361. /// <summary>
  3362. /// Write any buffered data to underlying storage.
  3363. /// </summary>
  3364. public override void Flush()
  3365. {
  3366. baseStream_.Flush();
  3367. }
  3368. /// <summary>
  3369. /// Gets a value indicating whether the current stream supports writing.
  3370. /// </summary>
  3371. public override bool CanWrite
  3372. {
  3373. get
  3374. {
  3375. return baseStream_.CanWrite;
  3376. }
  3377. }
  3378. /// <summary>
  3379. /// Gets a value indicating whether the current stream supports seeking.
  3380. /// </summary>
  3381. public override bool CanSeek
  3382. {
  3383. get
  3384. {
  3385. return false;
  3386. }
  3387. }
  3388. /// <summary>
  3389. /// Get the length in bytes of the stream.
  3390. /// </summary>
  3391. public override long Length
  3392. {
  3393. get
  3394. {
  3395. return 0;
  3396. }
  3397. }
  3398. /// <summary>
  3399. /// Gets or sets the position within the current stream.
  3400. /// </summary>
  3401. public override long Position
  3402. {
  3403. get
  3404. {
  3405. return baseStream_.Position;
  3406. }
  3407. set
  3408. {
  3409. throw new NotImplementedException();
  3410. }
  3411. }
  3412. /// <summary>
  3413. /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
  3414. /// </summary>
  3415. /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param>
  3416. /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param>
  3417. /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
  3418. /// <returns>
  3419. /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
  3420. /// </returns>
  3421. /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </exception>
  3422. /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
  3423. /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception>
  3424. /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception>
  3425. /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
  3426. /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception>
  3427. public override int Read(byte[] buffer, int offset, int count)
  3428. {
  3429. return 0;
  3430. }
  3431. /// <summary>
  3432. /// Sets the position within the current stream.
  3433. /// </summary>
  3434. /// <param name="offset">A byte offset relative to the origin parameter.</param>
  3435. /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point used to obtain the new position.</param>
  3436. /// <returns>
  3437. /// The new position within the current stream.
  3438. /// </returns>
  3439. /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
  3440. /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is constructed from a pipe or console output. </exception>
  3441. /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
  3442. public override long Seek(long offset, SeekOrigin origin)
  3443. {
  3444. return 0;
  3445. }
  3446. /// <summary>
  3447. /// Sets the length of the current stream.
  3448. /// </summary>
  3449. /// <param name="value">The desired length of the current stream in bytes.</param>
  3450. /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. </exception>
  3451. /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
  3452. /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
  3453. public override void SetLength(long value)
  3454. {
  3455. }
  3456. /// <summary>
  3457. /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
  3458. /// </summary>
  3459. /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</param>
  3460. /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current stream.</param>
  3461. /// <param name="count">The number of bytes to be written to the current stream.</param>
  3462. /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
  3463. /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception>
  3464. /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
  3465. /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception>
  3466. /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </exception>
  3467. /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception>
  3468. public override void Write(byte[] buffer, int offset, int count)
  3469. {
  3470. baseStream_.Write(buffer, offset, count);
  3471. }
  3472. private readonly
  3473. #region Instance Fields
  3474. Stream baseStream_;
  3475. #endregion Instance Fields
  3476. }
  3477. /// <summary>
  3478. /// A <see cref="PartialInputStream"/> is an <see cref="InflaterInputStream"/>
  3479. /// whose data is only a part or subsection of a file.
  3480. /// </summary>
  3481. private class PartialInputStream : Stream
  3482. {
  3483. #region Constructors
  3484. /// <summary>
  3485. /// Initialise a new instance of the <see cref="PartialInputStream"/> class.
  3486. /// </summary>
  3487. /// <param name="zipFile">The <see cref="ZipFile"/> containing the underlying stream to use for IO.</param>
  3488. /// <param name="start">The start of the partial data.</param>
  3489. /// <param name="length">The length of the partial data.</param>
  3490. public PartialInputStream(ZipFile zipFile, long start, long length)
  3491. {
  3492. start_ = start;
  3493. length_ = length;
  3494. // Although this is the only time the zipfile is used
  3495. // keeping a reference here prevents premature closure of
  3496. // this zip file and thus the baseStream_.
  3497. // Code like this will cause apparently random failures depending
  3498. // on the size of the files and when garbage is collected.
  3499. //
  3500. // ZipFile z = new ZipFile (stream);
  3501. // Stream reader = z.GetInputStream(0);
  3502. // uses reader here....
  3503. zipFile_ = zipFile;
  3504. baseStream_ = zipFile_.baseStream_;
  3505. readPos_ = start;
  3506. end_ = start + length;
  3507. }
  3508. #endregion Constructors
  3509. /// <summary>
  3510. /// Read a byte from this stream.
  3511. /// </summary>
  3512. /// <returns>Returns the byte read or -1 on end of stream.</returns>
  3513. public override int ReadByte()
  3514. {
  3515. if (readPos_ >= end_)
  3516. {
  3517. // -1 is the correct value at end of stream.
  3518. return -1;
  3519. }
  3520. lock (baseStream_)
  3521. {
  3522. baseStream_.Seek(readPos_++, SeekOrigin.Begin);
  3523. return baseStream_.ReadByte();
  3524. }
  3525. }
  3526. /// <summary>
  3527. /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
  3528. /// </summary>
  3529. /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param>
  3530. /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param>
  3531. /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
  3532. /// <returns>
  3533. /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
  3534. /// </returns>
  3535. /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </exception>
  3536. /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
  3537. /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception>
  3538. /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception>
  3539. /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
  3540. /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception>
  3541. public override int Read(byte[] buffer, int offset, int count)
  3542. {
  3543. lock (baseStream_)
  3544. {
  3545. if (count > end_ - readPos_)
  3546. {
  3547. count = (int)(end_ - readPos_);
  3548. if (count == 0)
  3549. {
  3550. return 0;
  3551. }
  3552. }
  3553. // Protect against Stream implementations that throw away their buffer on every Seek
  3554. // (for example, Mono FileStream)
  3555. if (baseStream_.Position != readPos_)
  3556. {
  3557. baseStream_.Seek(readPos_, SeekOrigin.Begin);
  3558. }
  3559. int readCount = baseStream_.Read(buffer, offset, count);
  3560. if (readCount > 0)
  3561. {
  3562. readPos_ += readCount;
  3563. }
  3564. return readCount;
  3565. }
  3566. }
  3567. /// <summary>
  3568. /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
  3569. /// </summary>
  3570. /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</param>
  3571. /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current stream.</param>
  3572. /// <param name="count">The number of bytes to be written to the current stream.</param>
  3573. /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
  3574. /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception>
  3575. /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
  3576. /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception>
  3577. /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </exception>
  3578. /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception>
  3579. public override void Write(byte[] buffer, int offset, int count)
  3580. {
  3581. throw new NotSupportedException();
  3582. }
  3583. /// <summary>
  3584. /// When overridden in a derived class, sets the length of the current stream.
  3585. /// </summary>
  3586. /// <param name="value">The desired length of the current stream in bytes.</param>
  3587. /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. </exception>
  3588. /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
  3589. /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
  3590. public override void SetLength(long value)
  3591. {
  3592. throw new NotSupportedException();
  3593. }
  3594. /// <summary>
  3595. /// When overridden in a derived class, sets the position within the current stream.
  3596. /// </summary>
  3597. /// <param name="offset">A byte offset relative to the origin parameter.</param>
  3598. /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point used to obtain the new position.</param>
  3599. /// <returns>
  3600. /// The new position within the current stream.
  3601. /// </returns>
  3602. /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
  3603. /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is constructed from a pipe or console output. </exception>
  3604. /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
  3605. public override long Seek(long offset, SeekOrigin origin)
  3606. {
  3607. long newPos = readPos_;
  3608. switch (origin)
  3609. {
  3610. case SeekOrigin.Begin:
  3611. newPos = start_ + offset;
  3612. break;
  3613. case SeekOrigin.Current:
  3614. newPos = readPos_ + offset;
  3615. break;
  3616. case SeekOrigin.End:
  3617. newPos = end_ + offset;
  3618. break;
  3619. }
  3620. if (newPos < start_)
  3621. {
  3622. throw new ArgumentException("Negative position is invalid");
  3623. }
  3624. if (newPos >= end_)
  3625. {
  3626. throw new IOException("Cannot seek past end");
  3627. }
  3628. readPos_ = newPos;
  3629. return readPos_;
  3630. }
  3631. /// <summary>
  3632. /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device.
  3633. /// </summary>
  3634. /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
  3635. public override void Flush()
  3636. {
  3637. // Nothing to do.
  3638. }
  3639. /// <summary>
  3640. /// Gets or sets the position within the current stream.
  3641. /// </summary>
  3642. /// <value></value>
  3643. /// <returns>The current position within the stream.</returns>
  3644. /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
  3645. /// <exception cref="T:System.NotSupportedException">The stream does not support seeking. </exception>
  3646. /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
  3647. public override long Position
  3648. {
  3649. get { return readPos_ - start_; }
  3650. set
  3651. {
  3652. long newPos = start_ + value;
  3653. if (newPos < start_)
  3654. {
  3655. throw new ArgumentException("Negative position is invalid");
  3656. }
  3657. if (newPos >= end_)
  3658. {
  3659. throw new InvalidOperationException("Cannot seek past end");
  3660. }
  3661. readPos_ = newPos;
  3662. }
  3663. }
  3664. /// <summary>
  3665. /// Gets the length in bytes of the stream.
  3666. /// </summary>
  3667. /// <value></value>
  3668. /// <returns>A long value representing the length of the stream in bytes.</returns>
  3669. /// <exception cref="T:System.NotSupportedException">A class derived from Stream does not support seeking. </exception>
  3670. /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
  3671. public override long Length
  3672. {
  3673. get { return length_; }
  3674. }
  3675. /// <summary>
  3676. /// Gets a value indicating whether the current stream supports writing.
  3677. /// </summary>
  3678. /// <value>false</value>
  3679. /// <returns>true if the stream supports writing; otherwise, false.</returns>
  3680. public override bool CanWrite
  3681. {
  3682. get { return false; }
  3683. }
  3684. /// <summary>
  3685. /// Gets a value indicating whether the current stream supports seeking.
  3686. /// </summary>
  3687. /// <value>true</value>
  3688. /// <returns>true if the stream supports seeking; otherwise, false.</returns>
  3689. public override bool CanSeek
  3690. {
  3691. get { return true; }
  3692. }
  3693. /// <summary>
  3694. /// Gets a value indicating whether the current stream supports reading.
  3695. /// </summary>
  3696. /// <value>true.</value>
  3697. /// <returns>true if the stream supports reading; otherwise, false.</returns>
  3698. public override bool CanRead
  3699. {
  3700. get { return true; }
  3701. }
  3702. /// <summary>
  3703. /// Gets a value that determines whether the current stream can time out.
  3704. /// </summary>
  3705. /// <value></value>
  3706. /// <returns>A value that determines whether the current stream can time out.</returns>
  3707. public override bool CanTimeout
  3708. {
  3709. get { return baseStream_.CanTimeout; }
  3710. }
  3711. #region Instance Fields
  3712. private ZipFile zipFile_;
  3713. private Stream baseStream_;
  3714. private readonly long start_;
  3715. private readonly long length_;
  3716. private long readPos_;
  3717. private readonly long end_;
  3718. #endregion Instance Fields
  3719. }
  3720. #endregion Support Classes
  3721. }
  3722. #endregion ZipFile Class
  3723. #region DataSources
  3724. /// <summary>
  3725. /// Provides a static way to obtain a source of data for an entry.
  3726. /// </summary>
  3727. public interface IStaticDataSource
  3728. {
  3729. /// <summary>
  3730. /// Get a source of data by creating a new stream.
  3731. /// </summary>
  3732. /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns>
  3733. /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks>
  3734. Stream GetSource();
  3735. }
  3736. /// <summary>
  3737. /// Represents a source of data that can dynamically provide
  3738. /// multiple <see cref="Stream">data sources</see> based on the parameters passed.
  3739. /// </summary>
  3740. public interface IDynamicDataSource
  3741. {
  3742. /// <summary>
  3743. /// Get a data source.
  3744. /// </summary>
  3745. /// <param name="entry">The <see cref="ZipEntry"/> to get a source for.</param>
  3746. /// <param name="name">The name for data if known.</param>
  3747. /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns>
  3748. /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks>
  3749. Stream GetSource(ZipEntry entry, string name);
  3750. }
  3751. /// <summary>
  3752. /// Default implementation of a <see cref="IStaticDataSource"/> for use with files stored on disk.
  3753. /// </summary>
  3754. public class StaticDiskDataSource : IStaticDataSource
  3755. {
  3756. /// <summary>
  3757. /// Initialise a new instnace of <see cref="StaticDiskDataSource"/>
  3758. /// </summary>
  3759. /// <param name="fileName">The name of the file to obtain data from.</param>
  3760. public StaticDiskDataSource(string fileName)
  3761. {
  3762. fileName_ = fileName;
  3763. }
  3764. #region IDataSource Members
  3765. /// <summary>
  3766. /// Get a <see cref="Stream"/> providing data.
  3767. /// </summary>
  3768. /// <returns>Returns a <see cref="Stream"/> provising data.</returns>
  3769. public Stream GetSource()
  3770. {
  3771. return File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read);
  3772. }
  3773. private readonly
  3774. #endregion IDataSource Members
  3775. #region Instance Fields
  3776. string fileName_;
  3777. #endregion Instance Fields
  3778. }
  3779. /// <summary>
  3780. /// Default implementation of <see cref="IDynamicDataSource"/> for files stored on disk.
  3781. /// </summary>
  3782. public class DynamicDiskDataSource : IDynamicDataSource
  3783. {
  3784. #region IDataSource Members
  3785. /// <summary>
  3786. /// Get a <see cref="Stream"/> providing data for an entry.
  3787. /// </summary>
  3788. /// <param name="entry">The entry to provide data for.</param>
  3789. /// <param name="name">The file name for data if known.</param>
  3790. /// <returns>Returns a stream providing data; or null if not available</returns>
  3791. public Stream GetSource(ZipEntry entry, string name)
  3792. {
  3793. Stream result = null;
  3794. if (name != null)
  3795. {
  3796. result = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read);
  3797. }
  3798. return result;
  3799. }
  3800. #endregion IDataSource Members
  3801. }
  3802. #endregion DataSources
  3803. #region Archive Storage
  3804. /// <summary>
  3805. /// Defines facilities for data storage when updating Zip Archives.
  3806. /// </summary>
  3807. public interface IArchiveStorage
  3808. {
  3809. /// <summary>
  3810. /// Get the <see cref="FileUpdateMode"/> to apply during updates.
  3811. /// </summary>
  3812. FileUpdateMode UpdateMode { get; }
  3813. /// <summary>
  3814. /// Get an empty <see cref="Stream"/> that can be used for temporary output.
  3815. /// </summary>
  3816. /// <returns>Returns a temporary output <see cref="Stream"/></returns>
  3817. /// <seealso cref="ConvertTemporaryToFinal"></seealso>
  3818. Stream GetTemporaryOutput();
  3819. /// <summary>
  3820. /// Convert a temporary output stream to a final stream.
  3821. /// </summary>
  3822. /// <returns>The resulting final <see cref="Stream"/></returns>
  3823. /// <seealso cref="GetTemporaryOutput"/>
  3824. Stream ConvertTemporaryToFinal();
  3825. /// <summary>
  3826. /// Make a temporary copy of the original stream.
  3827. /// </summary>
  3828. /// <param name="stream">The <see cref="Stream"/> to copy.</param>
  3829. /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns>
  3830. Stream MakeTemporaryCopy(Stream stream);
  3831. /// <summary>
  3832. /// Return a stream suitable for performing direct updates on the original source.
  3833. /// </summary>
  3834. /// <param name="stream">The current stream.</param>
  3835. /// <returns>Returns a stream suitable for direct updating.</returns>
  3836. /// <remarks>This may be the current stream passed.</remarks>
  3837. Stream OpenForDirectUpdate(Stream stream);
  3838. /// <summary>
  3839. /// Dispose of this instance.
  3840. /// </summary>
  3841. void Dispose();
  3842. }
  3843. /// <summary>
  3844. /// An abstract <see cref="IArchiveStorage"/> suitable for extension by inheritance.
  3845. /// </summary>
  3846. abstract public class BaseArchiveStorage : IArchiveStorage
  3847. {
  3848. #region Constructors
  3849. /// <summary>
  3850. /// Initializes a new instance of the <see cref="BaseArchiveStorage"/> class.
  3851. /// </summary>
  3852. /// <param name="updateMode">The update mode.</param>
  3853. protected BaseArchiveStorage(FileUpdateMode updateMode)
  3854. {
  3855. updateMode_ = updateMode;
  3856. }
  3857. #endregion Constructors
  3858. #region IArchiveStorage Members
  3859. /// <summary>
  3860. /// Gets a temporary output <see cref="Stream"/>
  3861. /// </summary>
  3862. /// <returns>Returns the temporary output stream.</returns>
  3863. /// <seealso cref="ConvertTemporaryToFinal"></seealso>
  3864. public abstract Stream GetTemporaryOutput();
  3865. /// <summary>
  3866. /// Converts the temporary <see cref="Stream"/> to its final form.
  3867. /// </summary>
  3868. /// <returns>Returns a <see cref="Stream"/> that can be used to read
  3869. /// the final storage for the archive.</returns>
  3870. /// <seealso cref="GetTemporaryOutput"/>
  3871. public abstract Stream ConvertTemporaryToFinal();
  3872. /// <summary>
  3873. /// Make a temporary copy of a <see cref="Stream"/>.
  3874. /// </summary>
  3875. /// <param name="stream">The <see cref="Stream"/> to make a copy of.</param>
  3876. /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns>
  3877. public abstract Stream MakeTemporaryCopy(Stream stream);
  3878. /// <summary>
  3879. /// Return a stream suitable for performing direct updates on the original source.
  3880. /// </summary>
  3881. /// <param name="stream">The <see cref="Stream"/> to open for direct update.</param>
  3882. /// <returns>Returns a stream suitable for direct updating.</returns>
  3883. public abstract Stream OpenForDirectUpdate(Stream stream);
  3884. /// <summary>
  3885. /// Disposes this instance.
  3886. /// </summary>
  3887. public abstract void Dispose();
  3888. /// <summary>
  3889. /// Gets the update mode applicable.
  3890. /// </summary>
  3891. /// <value>The update mode.</value>
  3892. public FileUpdateMode UpdateMode
  3893. {
  3894. get
  3895. {
  3896. return updateMode_;
  3897. }
  3898. }
  3899. #endregion IArchiveStorage Members
  3900. #region Instance Fields
  3901. private readonly FileUpdateMode updateMode_;
  3902. #endregion Instance Fields
  3903. }
  3904. /// <summary>
  3905. /// An <see cref="IArchiveStorage"/> implementation suitable for hard disks.
  3906. /// </summary>
  3907. public class DiskArchiveStorage : BaseArchiveStorage
  3908. {
  3909. #region Constructors
  3910. /// <summary>
  3911. /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class.
  3912. /// </summary>
  3913. /// <param name="file">The file.</param>
  3914. /// <param name="updateMode">The update mode.</param>
  3915. public DiskArchiveStorage(ZipFile file, FileUpdateMode updateMode)
  3916. : base(updateMode)
  3917. {
  3918. if (file.Name == null)
  3919. {
  3920. throw new ZipException("Cant handle non file archives");
  3921. }
  3922. fileName_ = file.Name;
  3923. }
  3924. /// <summary>
  3925. /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class.
  3926. /// </summary>
  3927. /// <param name="file">The file.</param>
  3928. public DiskArchiveStorage(ZipFile file)
  3929. : this(file, FileUpdateMode.Safe)
  3930. {
  3931. }
  3932. #endregion Constructors
  3933. #region IArchiveStorage Members
  3934. /// <summary>
  3935. /// Gets a temporary output <see cref="Stream"/> for performing updates on.
  3936. /// </summary>
  3937. /// <returns>Returns the temporary output stream.</returns>
  3938. public override Stream GetTemporaryOutput()
  3939. {
  3940. if (temporaryName_ != null)
  3941. {
  3942. temporaryName_ = GetTempFileName(temporaryName_, true);
  3943. temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
  3944. }
  3945. else
  3946. {
  3947. // Determine where to place files based on internal strategy.
  3948. // Currently this is always done in system temp directory.
  3949. temporaryName_ = Path.GetTempFileName();
  3950. temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
  3951. }
  3952. return temporaryStream_;
  3953. }
  3954. /// <summary>
  3955. /// Converts a temporary <see cref="Stream"/> to its final form.
  3956. /// </summary>
  3957. /// <returns>Returns a <see cref="Stream"/> that can be used to read
  3958. /// the final storage for the archive.</returns>
  3959. public override Stream ConvertTemporaryToFinal()
  3960. {
  3961. if (temporaryStream_ == null)
  3962. {
  3963. throw new ZipException("No temporary stream has been created");
  3964. }
  3965. Stream result = null;
  3966. string moveTempName = GetTempFileName(fileName_, false);
  3967. bool newFileCreated = false;
  3968. try
  3969. {
  3970. temporaryStream_.Dispose();
  3971. File.Move(fileName_, moveTempName);
  3972. File.Move(temporaryName_, fileName_);
  3973. newFileCreated = true;
  3974. File.Delete(moveTempName);
  3975. result = File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read);
  3976. }
  3977. catch (Exception)
  3978. {
  3979. result = null;
  3980. // Try to roll back changes...
  3981. if (!newFileCreated)
  3982. {
  3983. File.Move(moveTempName, fileName_);
  3984. File.Delete(temporaryName_);
  3985. }
  3986. throw;
  3987. }
  3988. return result;
  3989. }
  3990. /// <summary>
  3991. /// Make a temporary copy of a stream.
  3992. /// </summary>
  3993. /// <param name="stream">The <see cref="Stream"/> to copy.</param>
  3994. /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns>
  3995. public override Stream MakeTemporaryCopy(Stream stream)
  3996. {
  3997. stream.Dispose();
  3998. temporaryName_ = GetTempFileName(fileName_, true);
  3999. File.Copy(fileName_, temporaryName_, true);
  4000. temporaryStream_ = new FileStream(temporaryName_,
  4001. FileMode.Open,
  4002. FileAccess.ReadWrite);
  4003. return temporaryStream_;
  4004. }
  4005. /// <summary>
  4006. /// Return a stream suitable for performing direct updates on the original source.
  4007. /// </summary>
  4008. /// <param name="stream">The current stream.</param>
  4009. /// <returns>Returns a stream suitable for direct updating.</returns>
  4010. /// <remarks>If the <paramref name="stream"/> is not null this is used as is.</remarks>
  4011. public override Stream OpenForDirectUpdate(Stream stream)
  4012. {
  4013. Stream result;
  4014. if ((stream == null) || !stream.CanWrite)
  4015. {
  4016. if (stream != null)
  4017. {
  4018. stream.Dispose();
  4019. }
  4020. result = new FileStream(fileName_,
  4021. FileMode.Open,
  4022. FileAccess.ReadWrite);
  4023. }
  4024. else
  4025. {
  4026. result = stream;
  4027. }
  4028. return result;
  4029. }
  4030. /// <summary>
  4031. /// Disposes this instance.
  4032. /// </summary>
  4033. public override void Dispose()
  4034. {
  4035. if (temporaryStream_ != null)
  4036. {
  4037. temporaryStream_.Dispose();
  4038. }
  4039. }
  4040. #endregion IArchiveStorage Members
  4041. #region Internal routines
  4042. private static string GetTempFileName(string original, bool makeTempFile)
  4043. {
  4044. string result = null;
  4045. if (original == null)
  4046. {
  4047. result = Path.GetTempFileName();
  4048. }
  4049. else
  4050. {
  4051. int counter = 0;
  4052. int suffixSeed = DateTime.Now.Second;
  4053. while (result == null)
  4054. {
  4055. counter += 1;
  4056. string newName = string.Format("{0}.{1}{2}.tmp", original, suffixSeed, counter);
  4057. if (!File.Exists(newName))
  4058. {
  4059. if (makeTempFile)
  4060. {
  4061. try
  4062. {
  4063. // Try and create the file.
  4064. using (FileStream stream = File.Create(newName))
  4065. {
  4066. }
  4067. result = newName;
  4068. }
  4069. catch
  4070. {
  4071. suffixSeed = DateTime.Now.Second;
  4072. }
  4073. }
  4074. else
  4075. {
  4076. result = newName;
  4077. }
  4078. }
  4079. }
  4080. }
  4081. return result;
  4082. }
  4083. #endregion Internal routines
  4084. #region Instance Fields
  4085. private Stream temporaryStream_;
  4086. private readonly string fileName_;
  4087. private string temporaryName_;
  4088. #endregion Instance Fields
  4089. }
  4090. /// <summary>
  4091. /// An <see cref="IArchiveStorage"/> implementation suitable for in memory streams.
  4092. /// </summary>
  4093. public class MemoryArchiveStorage : BaseArchiveStorage
  4094. {
  4095. #region Constructors
  4096. /// <summary>
  4097. /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class.
  4098. /// </summary>
  4099. public MemoryArchiveStorage()
  4100. : base(FileUpdateMode.Direct)
  4101. {
  4102. }
  4103. /// <summary>
  4104. /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class.
  4105. /// </summary>
  4106. /// <param name="updateMode">The <see cref="FileUpdateMode"/> to use</param>
  4107. /// <remarks>This constructor is for testing as memory streams dont really require safe mode.</remarks>
  4108. public MemoryArchiveStorage(FileUpdateMode updateMode)
  4109. : base(updateMode)
  4110. {
  4111. }
  4112. #endregion Constructors
  4113. #region Properties
  4114. /// <summary>
  4115. /// Get the stream returned by <see cref="ConvertTemporaryToFinal"/> if this was in fact called.
  4116. /// </summary>
  4117. public MemoryStream FinalStream
  4118. {
  4119. get { return finalStream_; }
  4120. }
  4121. #endregion Properties
  4122. #region IArchiveStorage Members
  4123. /// <summary>
  4124. /// Gets the temporary output <see cref="Stream"/>
  4125. /// </summary>
  4126. /// <returns>Returns the temporary output stream.</returns>
  4127. public override Stream GetTemporaryOutput()
  4128. {
  4129. temporaryStream_ = new MemoryStream();
  4130. return temporaryStream_;
  4131. }
  4132. /// <summary>
  4133. /// Converts the temporary <see cref="Stream"/> to its final form.
  4134. /// </summary>
  4135. /// <returns>Returns a <see cref="Stream"/> that can be used to read
  4136. /// the final storage for the archive.</returns>
  4137. public override Stream ConvertTemporaryToFinal()
  4138. {
  4139. if (temporaryStream_ == null)
  4140. {
  4141. throw new ZipException("No temporary stream has been created");
  4142. }
  4143. finalStream_ = new MemoryStream(temporaryStream_.ToArray());
  4144. return finalStream_;
  4145. }
  4146. /// <summary>
  4147. /// Make a temporary copy of the original stream.
  4148. /// </summary>
  4149. /// <param name="stream">The <see cref="Stream"/> to copy.</param>
  4150. /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns>
  4151. public override Stream MakeTemporaryCopy(Stream stream)
  4152. {
  4153. temporaryStream_ = new MemoryStream();
  4154. stream.Position = 0;
  4155. StreamUtils.Copy(stream, temporaryStream_, new byte[4096]);
  4156. return temporaryStream_;
  4157. }
  4158. /// <summary>
  4159. /// Return a stream suitable for performing direct updates on the original source.
  4160. /// </summary>
  4161. /// <param name="stream">The original source stream</param>
  4162. /// <returns>Returns a stream suitable for direct updating.</returns>
  4163. /// <remarks>If the <paramref name="stream"/> passed is not null this is used;
  4164. /// otherwise a new <see cref="MemoryStream"/> is returned.</remarks>
  4165. public override Stream OpenForDirectUpdate(Stream stream)
  4166. {
  4167. Stream result;
  4168. if ((stream == null) || !stream.CanWrite)
  4169. {
  4170. result = new MemoryStream();
  4171. if (stream != null)
  4172. {
  4173. stream.Position = 0;
  4174. StreamUtils.Copy(stream, result, new byte[4096]);
  4175. stream.Dispose();
  4176. }
  4177. }
  4178. else
  4179. {
  4180. result = stream;
  4181. }
  4182. return result;
  4183. }
  4184. /// <summary>
  4185. /// Disposes this instance.
  4186. /// </summary>
  4187. public override void Dispose()
  4188. {
  4189. if (temporaryStream_ != null)
  4190. {
  4191. temporaryStream_.Dispose();
  4192. }
  4193. }
  4194. #endregion IArchiveStorage Members
  4195. #region Instance Fields
  4196. private MemoryStream temporaryStream_;
  4197. private MemoryStream finalStream_;
  4198. #endregion Instance Fields
  4199. }
  4200. #endregion Archive Storage
  4201. }