123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601 |
- //#define Trace
- // ZipEntry.Write.cs
- // ------------------------------------------------------------------
- //
- // Copyright (c) 2009-2011 Dino Chiesa
- // All rights reserved.
- //
- // This code module is part of DotNetZip, a zipfile class library.
- //
- // ------------------------------------------------------------------
- //
- // This code is licensed under the Microsoft Public License.
- // See the file License.txt for the license details.
- // More info on: http://dotnetzip.codeplex.com
- //
- // ------------------------------------------------------------------
- //
- // Last Saved: <2011-July-30 14:55:47>
- //
- // ------------------------------------------------------------------
- //
- // This module defines logic for writing (saving) the ZipEntry into a
- // zip file.
- //
- // ------------------------------------------------------------------
- using System;
- using System.IO;
- using RE = System.Text.RegularExpressions;
- namespace Ionic.Zip
- {
- public partial class ZipEntry
- {
- internal void WriteCentralDirectoryEntry(Stream s)
- {
- //CDE header size: 46 + extra field length + filename length + comment length
- byte[] bytes = new byte[8192];
- int i = 0;
- // signature
- bytes[i++] = (byte)(ZipConstants.ZipDirEntrySignature & 0x000000FF);
- bytes[i++] = (byte)((ZipConstants.ZipDirEntrySignature & 0x0000FF00) >> 8);
- bytes[i++] = (byte)((ZipConstants.ZipDirEntrySignature & 0x00FF0000) >> 16);
- bytes[i++] = (byte)((ZipConstants.ZipDirEntrySignature & 0xFF000000) >> 24);
- // Version Made By
- // workitem 7071
- // We must not overwrite the VersionMadeBy field when writing out a zip
- // archive. The VersionMadeBy tells the zip reader the meaning of the
- // File attributes. Overwriting the VersionMadeBy will result in
- // inconsistent metadata. Consider the scenario where the application
- // opens and reads a zip file that had been created on Linux. Then the
- // app adds one file to the Zip archive, and saves it. The file
- // attributes for all the entries added on Linux will be significant for
- // Linux. Therefore the VersionMadeBy for those entries must not be
- // changed. Only the entries that are actually created on Windows NTFS
- // should get the VersionMadeBy indicating Windows/NTFS.
- bytes[i++] = (byte)(_VersionMadeBy & 0x00FF);
- bytes[i++] = (byte)((_VersionMadeBy & 0xFF00) >> 8);
- // Apparently we want to duplicate the extra field here; we cannot
- // simply zero it out and assume tools and apps will use the right one.
- ////Int16 extraFieldLengthSave = (short)(_EntryHeader[28] + _EntryHeader[29] * 256);
- ////_EntryHeader[28] = 0;
- ////_EntryHeader[29] = 0;
- // Version Needed, Bitfield, compression method, lastmod,
- // crc, compressed and uncompressed sizes, filename length and extra field length.
- // These are all present in the local file header, but they may be zero values there.
- // So we cannot just copy them.
- // workitem 11969: Version Needed To Extract in central directory must be
- // the same as the local entry or MS .NET System.IO.Zip fails read.
- Int16 vNeeded = (Int16)(VersionNeeded != 0 ? VersionNeeded : 20);
- // workitem 12964
- if (_OutputUsesZip64==null)
- {
- // a zipentry in a zipoutputstream, with zero bytes written
- _OutputUsesZip64 = new Nullable<bool>(_container.Zip64 == Zip64Option.Always);
- }
- Int16 versionNeededToExtract = (Int16)(_OutputUsesZip64.Value ? 45 : vNeeded);
- #if BZIP
- if (this.CompressionMethod == Ionic.Zip.CompressionMethod.BZip2)
- versionNeededToExtract = 46;
- #endif
- bytes[i++] = (byte)(versionNeededToExtract & 0x00FF);
- bytes[i++] = (byte)((versionNeededToExtract & 0xFF00) >> 8);
- bytes[i++] = (byte)(_BitField & 0x00FF);
- bytes[i++] = (byte)((_BitField & 0xFF00) >> 8);
- bytes[i++] = (byte)(_CompressionMethod & 0x00FF);
- bytes[i++] = (byte)((_CompressionMethod & 0xFF00) >> 8);
- #if AESCRYPTO
- if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
- Encryption == EncryptionAlgorithm.WinZipAes256)
- {
- i -= 2;
- bytes[i++] = 0x63;
- bytes[i++] = 0;
- }
- #endif
- bytes[i++] = (byte)(_TimeBlob & 0x000000FF);
- bytes[i++] = (byte)((_TimeBlob & 0x0000FF00) >> 8);
- bytes[i++] = (byte)((_TimeBlob & 0x00FF0000) >> 16);
- bytes[i++] = (byte)((_TimeBlob & 0xFF000000) >> 24);
- bytes[i++] = (byte)(_Crc32 & 0x000000FF);
- bytes[i++] = (byte)((_Crc32 & 0x0000FF00) >> 8);
- bytes[i++] = (byte)((_Crc32 & 0x00FF0000) >> 16);
- bytes[i++] = (byte)((_Crc32 & 0xFF000000) >> 24);
- int j = 0;
- if (_OutputUsesZip64.Value)
- {
- // CompressedSize (Int32) and UncompressedSize - all 0xFF
- for (j = 0; j < 8; j++)
- bytes[i++] = 0xFF;
- }
- else
- {
- bytes[i++] = (byte)(_CompressedSize & 0x000000FF);
- bytes[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
- bytes[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
- bytes[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);
- bytes[i++] = (byte)(_UncompressedSize & 0x000000FF);
- bytes[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
- bytes[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
- bytes[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);
- }
- byte[] fileNameBytes = GetEncodedFileNameBytes();
- Int16 filenameLength = (Int16)fileNameBytes.Length;
- bytes[i++] = (byte)(filenameLength & 0x00FF);
- bytes[i++] = (byte)((filenameLength & 0xFF00) >> 8);
- // do this again because now we have real data
- _presumeZip64 = _OutputUsesZip64.Value;
- // workitem 11131
- //
- // cannot generate the extra field again, here's why: In the case of a
- // zero-byte entry, which uses encryption, DotNetZip will "remove" the
- // encryption from the entry. It does this in PostProcessOutput; it
- // modifies the entry header, and rewrites it, resetting the Bitfield
- // (one bit indicates encryption), and potentially resetting the
- // compression method - for AES the Compression method is 0x63, and it
- // would get reset to zero (no compression). It then calls SetLength()
- // to truncate the stream to remove the encryption header (12 bytes for
- // AES256). But, it leaves the previously-generated "Extra Field"
- // metadata (11 bytes) for AES in the entry header. This extra field
- // data is now "orphaned" - it refers to AES encryption when in fact no
- // AES encryption is used. But no problem, the PKWARE spec says that
- // unrecognized extra fields can just be ignored. ok. After "removal"
- // of AES encryption, the length of the Extra Field can remains the
- // same; it's just that there will be 11 bytes in there that previously
- // pertained to AES which are now unused. Even the field code is still
- // there, but it will be unused by readers, as the encryption bit is not
- // set.
- //
- // Re-calculating the Extra field now would produce a block that is 11
- // bytes shorter, and that mismatch - between the extra field in the
- // local header and the extra field in the Central Directory - would
- // cause problems. (where? why? what problems?) So we can't do
- // that. It's all good though, because though the content may have
- // changed, the length definitely has not. Also, the _EntryHeader
- // contains the "updated" extra field (after PostProcessOutput) at
- // offset (30 + filenameLength).
- _Extra = ConstructExtraField(true);
- Int16 extraFieldLength = (Int16)((_Extra == null) ? 0 : _Extra.Length);
- bytes[i++] = (byte)(extraFieldLength & 0x00FF);
- bytes[i++] = (byte)((extraFieldLength & 0xFF00) >> 8);
- // File (entry) Comment Length
- // the _CommentBytes private field was set during WriteHeader()
- int commentLength = (_CommentBytes == null) ? 0 : _CommentBytes.Length;
- // skip comment length because we set it at the end
- i += 2;
- // Disk number start
- bool segmented = (this._container.ZipFile != null) &&
- (this._container.ZipFile.MaxOutputSegmentSize != 0);
- if (segmented) // workitem 13915
- {
- // Emit nonzero disknumber only if saving segmented archive.
- bytes[i++] = (byte)(_diskNumber & 0x00FF);
- bytes[i++] = (byte)((_diskNumber & 0xFF00) >> 8);
- }
- else
- {
- // If reading a segmneted archive and saving to a regular archive,
- // ZipEntry._diskNumber will be non-zero but it should be saved as
- // zero.
- bytes[i++] = 0;
- bytes[i++] = 0;
- }
- // internal file attrs
- // workitem 7801
- bytes[i++] = (byte)((_IsText) ? 1 : 0); // lo bit: filetype hint. 0=bin, 1=txt.
- bytes[i++] = 0;
- // external file attrs
- // workitem 7071
- bytes[i++] = (byte)(_ExternalFileAttrs & 0x000000FF);
- bytes[i++] = (byte)((_ExternalFileAttrs & 0x0000FF00) >> 8);
- bytes[i++] = (byte)((_ExternalFileAttrs & 0x00FF0000) >> 16);
- bytes[i++] = (byte)((_ExternalFileAttrs & 0xFF000000) >> 24);
- // workitem 11131
- // relative offset of local header.
- //
- // If necessary to go to 64-bit value, then emit 0xFFFFFFFF,
- // else write out the value.
- //
- // Even if zip64 is required for other reasons - number of the entry
- // > 65534, or uncompressed size of the entry > MAX_INT32, the ROLH
- // need not be stored in a 64-bit field .
- if (_RelativeOffsetOfLocalHeader > 0xFFFFFFFFL) // _OutputUsesZip64.Value
- {
- bytes[i++] = 0xFF;
- bytes[i++] = 0xFF;
- bytes[i++] = 0xFF;
- bytes[i++] = 0xFF;
- }
- else
- {
- bytes[i++] = (byte)(_RelativeOffsetOfLocalHeader & 0x000000FF);
- bytes[i++] = (byte)((_RelativeOffsetOfLocalHeader & 0x0000FF00) >> 8);
- bytes[i++] = (byte)((_RelativeOffsetOfLocalHeader & 0x00FF0000) >> 16);
- bytes[i++] = (byte)((_RelativeOffsetOfLocalHeader & 0xFF000000) >> 24);
- }
- // actual filename
- Buffer.BlockCopy(fileNameBytes, 0, bytes, i, filenameLength);
- i += filenameLength;
- // "Extra field"
- if (_Extra != null)
- {
- // workitem 11131
- //
- // copy from EntryHeader if available - it may have been updated.
- // if not, copy from Extra. This would be unnecessary if I just
- // updated the Extra field when updating EntryHeader, in
- // PostProcessOutput.
- //?? I don't understand why I wouldn't want to just use
- // the recalculated Extra field. ??
- // byte[] h = _EntryHeader ?? _Extra;
- // int offx = (h == _EntryHeader) ? 30 + filenameLength : 0;
- // Buffer.BlockCopy(h, offx, bytes, i, extraFieldLength);
- // i += extraFieldLength;
- byte[] h = _Extra;
- int offx = 0;
- Buffer.BlockCopy(h, offx, bytes, i, extraFieldLength);
- i += extraFieldLength;
- }
- // file (entry) comment
- if (commentLength != 0)
- {
- // the size of our buffer defines the max length of the comment we can write
- if (commentLength + i > bytes.Length)
- commentLength = bytes.Length - i;
- // now actually write the comment itself into the byte buffer
- Buffer.BlockCopy(_CommentBytes, 0, bytes, i, commentLength);
- // for (j = 0; (j < commentLength) && (i + j < bytes.Length); j++)
- // bytes[i + j] = _CommentBytes[j];
- i += commentLength;
- }
- bytes[32] = (byte)(commentLength & 0x00FF);
- bytes[33] = (byte)((commentLength & 0xFF00) >> 8);
- s.Write(bytes, 0, i);
- }
- #if INFOZIP_UTF8
- static private bool FileNameIsUtf8(char[] FileNameChars)
- {
- bool isUTF8 = false;
- bool isUnicode = false;
- for (int j = 0; j < FileNameChars.Length; j++)
- {
- byte[] b = System.BitConverter.GetBytes(FileNameChars[j]);
- isUnicode |= (b.Length != 2);
- isUnicode |= (b[1] != 0);
- isUTF8 |= ((b[0] & 0x80) != 0);
- }
- return isUTF8;
- }
- #endif
- private byte[] ConstructExtraField(bool forCentralDirectory)
- {
- var listOfBlocks = new System.Collections.Generic.List<byte[]>();
- byte[] block;
- // Conditionally emit an extra field with Zip64 information. If the
- // Zip64 option is Always, we emit the field, before knowing that it's
- // necessary. Later, if it turns out this entry does not need zip64,
- // we'll set the header ID to rubbish and the data will be ignored.
- // This results in additional overhead metadata in the zip file, but
- // it will be small in comparison to the entry data.
- //
- // On the other hand if the Zip64 option is AsNecessary and it's NOT
- // for the central directory, then we do the same thing. Or, if the
- // Zip64 option is AsNecessary and it IS for the central directory,
- // and the entry requires zip64, then emit the header.
- if (_container.Zip64 == Zip64Option.Always ||
- (_container.Zip64 == Zip64Option.AsNecessary &&
- (!forCentralDirectory || _entryRequiresZip64.Value)))
- {
- // add extra field for zip64 here
- // workitem 7924
- int sz = 4 + (forCentralDirectory ? 28 : 16);
- block = new byte[sz];
- int i = 0;
- if (_presumeZip64 || forCentralDirectory)
- {
- // HeaderId = always use zip64 extensions.
- block[i++] = 0x01;
- block[i++] = 0x00;
- }
- else
- {
- // HeaderId = dummy data now, maybe set to 0x0001 (ZIP64) later.
- block[i++] = 0x99;
- block[i++] = 0x99;
- }
- // DataSize
- block[i++] = (byte)(sz - 4); // decimal 28 or 16 (workitem 7924)
- block[i++] = 0x00;
- // The actual metadata - we may or may not have real values yet...
- // uncompressed size
- Array.Copy(BitConverter.GetBytes(_UncompressedSize), 0, block, i, 8);
- i += 8;
- // compressed size
- Array.Copy(BitConverter.GetBytes(_CompressedSize), 0, block, i, 8);
- i += 8;
- // workitem 7924 - only include this if the "extra" field is for
- // use in the central directory. It is unnecessary and not useful
- // for local header; makes WinZip choke.
- if (forCentralDirectory)
- {
- // relative offset
- Array.Copy(BitConverter.GetBytes(_RelativeOffsetOfLocalHeader), 0, block, i, 8);
- i += 8;
- // starting disk number
- Array.Copy(BitConverter.GetBytes(0), 0, block, i, 4);
- }
- listOfBlocks.Add(block);
- }
- #if AESCRYPTO
- if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
- Encryption == EncryptionAlgorithm.WinZipAes256)
- {
- block = new byte[4 + 7];
- int i = 0;
- // extra field for WinZip AES
- // header id
- block[i++] = 0x01;
- block[i++] = 0x99;
- // data size
- block[i++] = 0x07;
- block[i++] = 0x00;
- // vendor number
- block[i++] = 0x01; // AE-1 - means "Verify CRC"
- block[i++] = 0x00;
- // vendor id "AE"
- block[i++] = 0x41;
- block[i++] = 0x45;
- // key strength
- int keystrength = GetKeyStrengthInBits(Encryption);
- if (keystrength == 128)
- block[i] = 1;
- else if (keystrength == 256)
- block[i] = 3;
- else
- block[i] = 0xFF;
- i++;
- // actual compression method
- block[i++] = (byte)(_CompressionMethod & 0x00FF);
- block[i++] = (byte)(_CompressionMethod & 0xFF00);
- listOfBlocks.Add(block);
- }
- #endif
- if (_ntfsTimesAreSet && _emitNtfsTimes)
- {
- block = new byte[32 + 4];
- // HeaderId 2 bytes 0x000a == NTFS times
- // Datasize 2 bytes 32
- // reserved 4 bytes ?? don't care
- // timetag 2 bytes 0x0001 == NTFS time
- // size 2 bytes 24 == 8 bytes each for ctime, mtime, atime
- // mtime 8 bytes win32 ticks since win32epoch
- // atime 8 bytes win32 ticks since win32epoch
- // ctime 8 bytes win32 ticks since win32epoch
- int i = 0;
- // extra field for NTFS times
- // header id
- block[i++] = 0x0a;
- block[i++] = 0x00;
- // data size
- block[i++] = 32;
- block[i++] = 0;
- i += 4; // reserved
- // time tag
- block[i++] = 0x01;
- block[i++] = 0x00;
- // data size (again)
- block[i++] = 24;
- block[i++] = 0;
- Int64 z = _Mtime.ToFileTime();
- Array.Copy(BitConverter.GetBytes(z), 0, block, i, 8);
- i += 8;
- z = _Atime.ToFileTime();
- Array.Copy(BitConverter.GetBytes(z), 0, block, i, 8);
- i += 8;
- z = _Ctime.ToFileTime();
- Array.Copy(BitConverter.GetBytes(z), 0, block, i, 8);
- i += 8;
- listOfBlocks.Add(block);
- }
- if (_ntfsTimesAreSet && _emitUnixTimes)
- {
- int len = 5 + 4;
- if (!forCentralDirectory) len += 8;
- block = new byte[len];
- // local form:
- // --------------
- // HeaderId 2 bytes 0x5455 == unix timestamp
- // Datasize 2 bytes 13
- // flags 1 byte 7 (low three bits all set)
- // mtime 4 bytes seconds since unix epoch
- // atime 4 bytes seconds since unix epoch
- // ctime 4 bytes seconds since unix epoch
- //
- // central directory form:
- //---------------------------------
- // HeaderId 2 bytes 0x5455 == unix timestamp
- // Datasize 2 bytes 5
- // flags 1 byte 7 (low three bits all set)
- // mtime 4 bytes seconds since unix epoch
- //
- int i = 0;
- // extra field for "unix" times
- // header id
- block[i++] = 0x55;
- block[i++] = 0x54;
- // data size
- block[i++] = unchecked((byte)(len - 4));
- block[i++] = 0;
- // flags
- block[i++] = 0x07;
- Int32 z = unchecked((int)((_Mtime - _unixEpoch).TotalSeconds));
- Array.Copy(BitConverter.GetBytes(z), 0, block, i, 4);
- i += 4;
- if (!forCentralDirectory)
- {
- z = unchecked((int)((_Atime - _unixEpoch).TotalSeconds));
- Array.Copy(BitConverter.GetBytes(z), 0, block, i, 4);
- i += 4;
- z = unchecked((int)((_Ctime - _unixEpoch).TotalSeconds));
- Array.Copy(BitConverter.GetBytes(z), 0, block, i, 4);
- i += 4;
- }
- listOfBlocks.Add(block);
- }
- // inject other blocks here...
- // concatenate any blocks we've got:
- byte[] aggregateBlock = null;
- if (listOfBlocks.Count > 0)
- {
- int totalLength = 0;
- int i, current = 0;
- for (i = 0; i < listOfBlocks.Count; i++)
- totalLength += listOfBlocks[i].Length;
- aggregateBlock = new byte[totalLength];
- for (i = 0; i < listOfBlocks.Count; i++)
- {
- System.Array.Copy(listOfBlocks[i], 0, aggregateBlock, current, listOfBlocks[i].Length);
- current += listOfBlocks[i].Length;
- }
- }
- return aggregateBlock;
- }
- // private System.Text.Encoding GenerateCommentBytes()
- // {
- // var getEncoding = new Func<System.Text.Encoding>({
- // switch (AlternateEncodingUsage)
- // {
- // case ZipOption.Always:
- // return AlternateEncoding;
- // case ZipOption.Never:
- // return ibm437;
- // }
- // var cb = ibm437.GetBytes(_Comment);
- // // need to use this form of GetString() for .NET CF
- // string s1 = ibm437.GetString(cb, 0, cb.Length);
- // if (s1 == _Comment)
- // return ibm437;
- // return AlternateEncoding;
- // });
- //
- // var encoding = getEncoding();
- // _CommentBytes = encoding.GetBytes(_Comment);
- // return encoding;
- // }
- private string NormalizeFileName()
- {
- // here, we need to flip the backslashes to forward-slashes,
- // also, we need to trim the \\server\share syntax from any UNC path.
- // and finally, we need to remove any leading .\
- string SlashFixed = FileName.Replace("\\", "/");
- string s1 = null;
- if ((_TrimVolumeFromFullyQualifiedPaths) && (FileName.Length >= 3)
- && (FileName[1] == ':') && (SlashFixed[2] == '/'))
- {
- // trim off volume letter, colon, and slash
- s1 = SlashFixed.Substring(3);
- }
- else if ((FileName.Length >= 4)
- && ((SlashFixed[0] == '/') && (SlashFixed[1] == '/')))
- {
- int n = SlashFixed.IndexOf('/', 2);
- if (n == -1)
- throw new ArgumentException("The path for that entry appears to be badly formatted");
- s1 = SlashFixed.Substring(n + 1);
- }
- else if ((FileName.Length >= 3)
- && ((SlashFixed[0] == '.') && (SlashFixed[1] == '/')))
- {
- // trim off dot and slash
- s1 = SlashFixed.Substring(2);
- }
- else
- {
- s1 = SlashFixed;
- }
- return s1;
- }
- /// <summary>
- /// generate and return a byte array that encodes the filename
- /// for the entry.
- /// </summary>
- /// <remarks>
- /// <para>
- /// side effects: generate and store into _CommentBytes the
- /// byte array for any comment attached to the entry. Also
- /// sets _actualEncoding to indicate the actual encoding
- /// used. The same encoding is used for both filename and
- /// comment.
- /// </para>
- /// </remarks>
- private byte[] GetEncodedFileNameBytes()
- {
- // workitem 6513
- var s1 = NormalizeFileName();
- switch(AlternateEncodingUsage)
- {
- case ZipOption.Always:
- if (!(_Comment == null || _Comment.Length == 0))
- _CommentBytes = AlternateEncoding.GetBytes(_Comment);
- _actualEncoding = AlternateEncoding;
- return AlternateEncoding.GetBytes(s1);
- case ZipOption.Never:
- if (!(_Comment == null || _Comment.Length == 0))
- _CommentBytes = ibm437.GetBytes(_Comment);
- _actualEncoding = ibm437;
- return ibm437.GetBytes(s1);
- }
- // arriving here means AlternateEncodingUsage is "AsNecessary"
- // case ZipOption.AsNecessary:
- // workitem 6513: when writing, use the alternative encoding
- // only when _actualEncoding is not yet set (it can be set
- // during Read), and when ibm437 will not do.
- byte[] result = ibm437.GetBytes(s1);
- // need to use this form of GetString() for .NET CF
- string s2 = ibm437.GetString(result, 0, result.Length);
- _CommentBytes = null;
- if (s2 != s1)
- {
- // Encoding the filename with ibm437 does not allow round-trips.
- // Therefore, use the alternate encoding. Assume it will work,
- // no checking of round trips here.
- result = AlternateEncoding.GetBytes(s1);
- if (_Comment != null && _Comment.Length != 0)
- _CommentBytes = AlternateEncoding.GetBytes(_Comment);
- _actualEncoding = AlternateEncoding;
- return result;
- }
- _actualEncoding = ibm437;
- // Using ibm437, FileName can be encoded without information
- // loss; now try the Comment.
- // if there is no comment, use ibm437.
- if (_Comment == null || _Comment.Length == 0)
- return result;
- // there is a comment. Get the encoded form.
- byte[] cbytes = ibm437.GetBytes(_Comment);
- string c2 = ibm437.GetString(cbytes,0,cbytes.Length);
- // Check for round-trip.
- if (c2 != Comment)
- {
- // Comment cannot correctly be encoded with ibm437. Use
- // the alternate encoding.
- result = AlternateEncoding.GetBytes(s1);
- _CommentBytes = AlternateEncoding.GetBytes(_Comment);
- _actualEncoding = AlternateEncoding;
- return result;
- }
- // use IBM437
- _CommentBytes = cbytes;
- return result;
- }
- private bool WantReadAgain()
- {
- if (_UncompressedSize < 0x10) return false;
- if (_CompressionMethod == 0x00) return false;
- if (CompressionLevel == Ionic.Zlib.CompressionLevel.None) return false;
- if (_CompressedSize < _UncompressedSize) return false;
- if (this._Source == ZipEntrySource.Stream && !this._sourceStream.CanSeek) return false;
- #if AESCRYPTO
- if (_aesCrypto_forWrite != null && (CompressedSize - _aesCrypto_forWrite.SizeOfEncryptionMetadata) <= UncompressedSize + 0x10) return false;
- #endif
- if (_zipCrypto_forWrite != null && (CompressedSize - 12) <= UncompressedSize) return false;
- return true;
- }
- private void MaybeUnsetCompressionMethodForWriting(int cycle)
- {
- // if we've already tried with compression... turn it off this time
- if (cycle > 1)
- {
- _CompressionMethod = 0x0;
- return;
- }
- // compression for directories = 0x00 (No Compression)
- if (IsDirectory)
- {
- _CompressionMethod = 0x0;
- return;
- }
- if (this._Source == ZipEntrySource.ZipFile)
- {
- return; // do nothing
- }
- // If __FileDataPosition is zero, then that means we will get the data
- // from a file or stream.
- // It is never possible to compress a zero-length file, so we check for
- // this condition.
- if (this._Source == ZipEntrySource.Stream)
- {
- // workitem 7742
- if (_sourceStream != null && _sourceStream.CanSeek)
- {
- // Length prop will throw if CanSeek is false
- long fileLength = _sourceStream.Length;
- if (fileLength == 0)
- {
- _CompressionMethod = 0x00;
- return;
- }
- }
- }
- else if ((this._Source == ZipEntrySource.FileSystem) && (SharedUtilities.GetFileLength(LocalFileName) == 0L))
- {
- _CompressionMethod = 0x00;
- return;
- }
- // Ok, we're getting the data to be compressed from a
- // non-zero-length file or stream, or a file or stream of
- // unknown length, and we presume that it is non-zero. In
- // that case we check the callback to see if the app wants
- // to tell us whether to compress or not.
- if (SetCompression != null)
- CompressionLevel = SetCompression(LocalFileName, _FileNameInArchive);
- // finally, set CompressionMethod to None if CompressionLevel is None
- if (CompressionLevel == (short)Ionic.Zlib.CompressionLevel.None &&
- CompressionMethod == Ionic.Zip.CompressionMethod.Deflate)
- _CompressionMethod = 0x00;
- return;
- }
- // write the header info for an entry
- internal void WriteHeader(Stream s, int cycle)
- {
- // Must remember the offset, within the output stream, of this particular
- // entry header.
- //
- // This is for 2 reasons:
- //
- // 1. so we can determine the RelativeOffsetOfLocalHeader (ROLH) for
- // use in the central directory.
- // 2. so we can seek backward in case there is an error opening or reading
- // the file, and the application decides to skip the file. In this case,
- // we need to seek backward in the output stream to allow the next entry
- // to be added to the zipfile output stream.
- //
- // Normally you would just store the offset before writing to the output
- // stream and be done with it. But the possibility to use split archives
- // makes this approach ineffective. In split archives, each file or segment
- // is bound to a max size limit, and each local file header must not span a
- // segment boundary; it must be written contiguously. If it will fit in the
- // current segment, then the ROLH is just the current Position in the output
- // stream. If it won't fit, then we need a new file (segment) and the ROLH
- // is zero.
- //
- // But we only can know if it is possible to write a header contiguously
- // after we know the size of the local header, a size that varies with
- // things like filename length, comments, and extra fields. We have to
- // compute the header fully before knowing whether it will fit.
- //
- // That takes care of item #1 above. Now, regarding #2. If an error occurs
- // while computing the local header, we want to just seek backward. The
- // exception handling logic (in the caller of WriteHeader) uses ROLH to
- // scroll back.
- //
- // All this means we have to preserve the starting offset before computing
- // the header, and also we have to compute the offset later, to handle the
- // case of split archives.
- var counter = s as CountingStream;
- // workitem 8098: ok (output)
- // This may change later, for split archives
- // Don't set _RelativeOffsetOfLocalHeader. Instead, set a temp variable.
- // This allows for re-streaming, where a zip entry might be read from a
- // zip archive (and maybe decrypted, and maybe decompressed) and then
- // written to another zip archive, with different settings for
- // compression method, compression level, or encryption algorithm.
- _future_ROLH = (counter != null)
- ? counter.ComputedPosition
- : s.Position;
- int j = 0, i = 0;
- byte[] block = new byte[30];
- // signature
- block[i++] = (byte)(ZipConstants.ZipEntrySignature & 0x000000FF);
- block[i++] = (byte)((ZipConstants.ZipEntrySignature & 0x0000FF00) >> 8);
- block[i++] = (byte)((ZipConstants.ZipEntrySignature & 0x00FF0000) >> 16);
- block[i++] = (byte)((ZipConstants.ZipEntrySignature & 0xFF000000) >> 24);
- // Design notes for ZIP64:
- //
- // The specification says that the header must include the Compressed
- // and Uncompressed sizes, as well as the CRC32 value. When creating
- // a zip via streamed processing, these quantities are not known until
- // after the compression is done. Thus, a typical way to do it is to
- // insert zeroes for these quantities, then do the compression, then
- // seek back to insert the appropriate values, then seek forward to
- // the end of the file data.
- //
- // There is also the option of using bit 3 in the GP bitfield - to
- // specify that there is a data descriptor after the file data
- // containing these three quantities.
- //
- // This works when the size of the quantities is known, either 32-bits
- // or 64 bits as with the ZIP64 extensions.
- //
- // With Zip64, the 4-byte fields are set to 0xffffffff, and there is a
- // corresponding data block in the "extra field" that contains the
- // actual Compressed, uncompressed sizes. (As well as an additional
- // field, the "Relative Offset of Local Header")
- //
- // The problem is when the app desires to use ZIP64 extensions
- // optionally, only when necessary. Suppose the library assumes no
- // zip64 extensions when writing the header, then after compression
- // finds that the size of the data requires zip64. At this point, the
- // header, already written to the file, won't have the necessary data
- // block in the "extra field". The size of the entry header is fixed,
- // so it is not possible to just "add on" the zip64 data block after
- // compressing the file. On the other hand, always using zip64 will
- // break interoperability with many other systems and apps.
- //
- // The approach we take is to insert a 32-byte dummy data block in the
- // extra field, whenever zip64 is to be used "as necessary". This data
- // block will get the actual zip64 HeaderId and zip64 metadata if
- // necessary. If not necessary, the data block will get a meaningless
- // HeaderId (0x1111), and will be filled with zeroes.
- //
- // When zip64 is actually in use, we also need to set the
- // VersionNeededToExtract field to 45.
- //
- // There is one additional wrinkle: using zip64 as necessary conflicts
- // with output to non-seekable devices. The header is emitted and
- // must indicate whether zip64 is in use, before we know if zip64 is
- // necessary. Because there is no seeking, the header can never be
- // changed. Therefore, on non-seekable devices,
- // Zip64Option.AsNecessary is the same as Zip64Option.Always.
- //
- // version needed- see AppNote.txt.
- //
- // need v5.1 for PKZIP strong encryption, or v2.0 for no encryption or
- // for PK encryption, 4.5 for zip64. We may reset this later, as
- // necessary or zip64.
- _presumeZip64 = (_container.Zip64 == Zip64Option.Always ||
- (_container.Zip64 == Zip64Option.AsNecessary && !s.CanSeek));
- Int16 VersionNeededToExtract = (Int16)(_presumeZip64 ? 45 : 20);
- #if BZIP
- if (this.CompressionMethod == Ionic.Zip.CompressionMethod.BZip2)
- VersionNeededToExtract = 46;
- #endif
- // (i==4)
- block[i++] = (byte)(VersionNeededToExtract & 0x00FF);
- block[i++] = (byte)((VersionNeededToExtract & 0xFF00) >> 8);
- // Get byte array. Side effect: sets ActualEncoding.
- // Must determine encoding before setting the bitfield.
- // workitem 6513
- byte[] fileNameBytes = GetEncodedFileNameBytes();
- Int16 filenameLength = (Int16)fileNameBytes.Length;
- // general purpose bitfield
- // In the current implementation, this library uses only these bits
- // in the GP bitfield:
- // bit 0 = if set, indicates the entry is encrypted
- // bit 3 = if set, indicates the CRC, C and UC sizes follow the file data.
- // bit 6 = strong encryption - for pkware's meaning of strong encryption
- // bit 11 = UTF-8 encoding is used in the comment and filename
- // Here we set or unset the encryption bit.
- // _BitField may already be set, as with a ZipEntry added into ZipOutputStream, which
- // has bit 3 always set. We only want to set one bit
- if (_Encryption == EncryptionAlgorithm.None)
- _BitField &= ~1; // encryption bit OFF
- else
- _BitField |= 1; // encryption bit ON
- // workitem 7941: WinZip does not the "strong encryption" bit when using AES.
- // This "Strong Encryption" is a PKWare Strong encryption thing.
- // _BitField |= 0x0020;
- // set the UTF8 bit if necessary
- #if SILVERLIGHT
- if (_actualEncoding.WebName == "utf-8")
- #else
- if (_actualEncoding.CodePage == System.Text.Encoding.UTF8.CodePage)
- #endif
- _BitField |= 0x0800;
- // The PKZIP spec says that if bit 3 is set (0x0008) in the General
- // Purpose BitField, then the CRC, Compressed size, and uncompressed
- // size are written directly after the file data.
- //
- // These 3 quantities are normally present in the regular zip entry
- // header. But, they are not knowable until after the compression is
- // done. So, in the normal case, we
- //
- // - write the header, using zeros for these quantities
- // - compress the data, and incidentally compute these quantities.
- // - seek back and write the correct values them into the header.
- //
- // This is nice because, while it is more complicated to write the zip
- // file, it is simpler and less error prone to read the zip file, and
- // as a result more applications can read zip files produced this way,
- // with those 3 quantities in the header.
- //
- // But if seeking in the output stream is not possible, then we need
- // to set the appropriate bitfield and emit these quantities after the
- // compressed file data in the output.
- //
- // workitem 7216 - having trouble formatting a zip64 file that is
- // readable by WinZip. not sure why! What I found is that setting
- // bit 3 and following all the implications, the zip64 file is
- // readable by WinZip 12. and Perl's IO::Compress::Zip . Perl takes
- // an interesting approach - it always sets bit 3 if ZIP64 in use.
- // DotNetZip now does the same; this gives better compatibility with
- // WinZip 12.
- if (IsDirectory || cycle == 99)
- {
- // (cycle == 99) indicates a zero-length entry written by ZipOutputStream
- _BitField &= ~0x0008; // unset bit 3 - no "data descriptor" - ever
- _BitField &= ~0x0001; // unset bit 1 - no encryption - ever
- Encryption = EncryptionAlgorithm.None;
- Password = null;
- }
- else if (!s.CanSeek)
- _BitField |= 0x0008;
- #if DONT_GO_THERE
- else if (this.Encryption == EncryptionAlgorithm.PkzipWeak &&
- this._Source != ZipEntrySource.ZipFile)
- {
- // Set bit 3 to avoid the double-read perf issue.
- //
- // When PKZIP encryption is used, byte 11 of the encryption header is
- // used as a consistency check. It is normally set to the MSByte of the
- // CRC. But this means the cRC must be known ebfore compression and
- // encryption, which means the entire stream has to be read twice. To
- // avoid that, the high-byte of the time blob (when in DOS format) can
- // be used for the consistency check (byte 11 in the encryption header).
- // But this means the entry must have bit 3 set.
- //
- // Previously I used a more complex arrangement - using the methods like
- // FigureCrc32(), PrepOutputStream() and others, in order to manage the
- // seek-back in the source stream. Why? Because bit 3 is not always
- // friendly with third-party zip tools, like those on the Mac.
- //
- // This is why this code is still ifdef'd out.
- //
- // Might consider making this yet another programmable option -
- // AlwaysUseBit3ForPkzip. But that's for another day.
- //
- _BitField |= 0x0008;
- }
- #endif
- // (i==6)
- block[i++] = (byte)(_BitField & 0x00FF);
- block[i++] = (byte)((_BitField & 0xFF00) >> 8);
- // Here, we want to set values for Compressed Size, Uncompressed Size,
- // and CRC. If we have __FileDataPosition as not -1 (zero is a valid
- // FDP), then that means we are reading this zip entry from a zip
- // file, and we have good values for those quantities.
- //
- // If _FileDataPosition is -1, then we are constructing this Entry
- // from nothing. We zero those quantities now, and we will compute
- // actual values for the three quantities later, when we do the
- // compression, and then seek back to write them into the appropriate
- // place in the header.
- if (this.__FileDataPosition == -1)
- {
- //_UncompressedSize = 0; // do not unset - may need this value for restream
- // _Crc32 = 0; // ditto
- _CompressedSize = 0;
- _crcCalculated = false;
- }
- // set compression method here
- MaybeUnsetCompressionMethodForWriting(cycle);
- // (i==8) compression method
- block[i++] = (byte)(_CompressionMethod & 0x00FF);
- block[i++] = (byte)((_CompressionMethod & 0xFF00) >> 8);
- if (cycle == 99)
- {
- // (cycle == 99) indicates a zero-length entry written by ZipOutputStream
- SetZip64Flags();
- }
- #if AESCRYPTO
- else if (Encryption == EncryptionAlgorithm.WinZipAes128 || Encryption == EncryptionAlgorithm.WinZipAes256)
- {
- i -= 2;
- block[i++] = 0x63;
- block[i++] = 0;
- }
- #endif
- // LastMod
- if (_dontEmitLastModified)
- {
- _TimeBlob = 0;
- }
- else
- {
- _TimeBlob = Ionic.Zip.SharedUtilities.DateTimeToPacked(LastModified);
- }
-
- // (i==10) time blob
- block[i++] = (byte)(_TimeBlob & 0x000000FF);
- block[i++] = (byte)((_TimeBlob & 0x0000FF00) >> 8);
- block[i++] = (byte)((_TimeBlob & 0x00FF0000) >> 16);
- block[i++] = (byte)((_TimeBlob & 0xFF000000) >> 24);
- // (i==14) CRC - if source==filesystem, this is zero now, actual value
- // will be calculated later. if source==archive, this is a bonafide
- // value.
- block[i++] = (byte)(_Crc32 & 0x000000FF);
- block[i++] = (byte)((_Crc32 & 0x0000FF00) >> 8);
- block[i++] = (byte)((_Crc32 & 0x00FF0000) >> 16);
- block[i++] = (byte)((_Crc32 & 0xFF000000) >> 24);
- if (_presumeZip64)
- {
- // (i==18) CompressedSize (Int32) and UncompressedSize - all 0xFF for now
- for (j = 0; j < 8; j++)
- block[i++] = 0xFF;
- }
- else
- {
- // (i==18) CompressedSize (Int32) - this value may or may not be
- // bonafide. if source == filesystem, then it is zero, and we'll
- // learn it after we compress. if source == archive, then it is
- // bonafide data.
- block[i++] = (byte)(_CompressedSize & 0x000000FF);
- block[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
- block[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
- block[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);
- // (i==22) UncompressedSize (Int32) - this value may or may not be
- // bonafide.
- block[i++] = (byte)(_UncompressedSize & 0x000000FF);
- block[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
- block[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
- block[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);
- }
- // (i==26) filename length (Int16)
- block[i++] = (byte)(filenameLength & 0x00FF);
- block[i++] = (byte)((filenameLength & 0xFF00) >> 8);
- _Extra = ConstructExtraField(false);
- // (i==28) extra field length (short)
- Int16 extraFieldLength = (Int16)((_Extra == null) ? 0 : _Extra.Length);
- block[i++] = (byte)(extraFieldLength & 0x00FF);
- block[i++] = (byte)((extraFieldLength & 0xFF00) >> 8);
- // workitem 13542
- byte[] bytes = new byte[i + filenameLength + extraFieldLength];
- // get the fixed portion
- Buffer.BlockCopy(block, 0, bytes, 0, i);
- //for (j = 0; j < i; j++) bytes[j] = block[j];
- // The filename written to the archive.
- Buffer.BlockCopy(fileNameBytes, 0, bytes, i, fileNameBytes.Length);
- // for (j = 0; j < fileNameBytes.Length; j++)
- // bytes[i + j] = fileNameBytes[j];
- i += fileNameBytes.Length;
- // "Extra field"
- if (_Extra != null)
- {
- Buffer.BlockCopy(_Extra, 0, bytes, i, _Extra.Length);
- // for (j = 0; j < _Extra.Length; j++)
- // bytes[i + j] = _Extra[j];
- i += _Extra.Length;
- }
- _LengthOfHeader = i;
- // handle split archives
- var zss = s as ZipSegmentedStream;
- if (zss != null)
- {
- zss.ContiguousWrite = true;
- UInt32 requiredSegment = zss.ComputeSegment(i);
- if (requiredSegment != zss.CurrentSegment)
- _future_ROLH = 0; // rollover!
- else
- _future_ROLH = zss.Position;
- _diskNumber = requiredSegment;
- }
- // validate the ZIP64 usage
- if (_container.Zip64 == Zip64Option.Never && (uint)_RelativeOffsetOfLocalHeader >= 0xFFFFFFFF)
- throw new ZipException("Offset within the zip archive exceeds 0xFFFFFFFF. Consider setting the UseZip64WhenSaving property on the ZipFile instance.");
- // finally, write the header to the stream
- s.Write(bytes, 0, i);
- // now that the header is written, we can turn off the contiguous write restriction.
- if (zss != null)
- zss.ContiguousWrite = false;
- // Preserve this header data, we'll use it again later.
- // ..when seeking backward, to write again, after we have the Crc, compressed
- // and uncompressed sizes.
- // ..and when writing the central directory structure.
- _EntryHeader = bytes;
- }
- Int32 FigureCrc32()
- {
- if (_crcCalculated == false)
- {
- Stream input = null;
- // get the original stream:
- if (this._Source == ZipEntrySource.WriteDelegate)
- {
- var output = new Ionic.Crc.CrcCalculatorStream(Stream.Null);
- // allow the application to write the data
- this._WriteDelegate(this.FileName, output);
- _Crc32 = output.Crc;
- }
- else if (this._Source == ZipEntrySource.ZipFile)
- {
- // nothing to do - the CRC is already set
- }
- else
- {
- if (this._Source == ZipEntrySource.Stream)
- {
- PrepSourceStream();
- input = this._sourceStream;
- }
- else if (this._Source == ZipEntrySource.JitStream)
- {
- // allow the application to open the stream
- if (this._sourceStream == null)
- _sourceStream = this._OpenDelegate(this.FileName);
- PrepSourceStream();
- input = this._sourceStream;
- }
- else if (this._Source == ZipEntrySource.ZipOutputStream)
- {
- }
- else
- {
- //input = File.OpenRead(LocalFileName);
- input = File.Open(LocalFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
- }
- var crc32 = new Ionic.Crc.CRC32();
- _Crc32 = crc32.GetCrc32(input);
- if (_sourceStream == null)
- {
- #if NETCF
- input.Close();
- #else
- input.Dispose();
- #endif
- }
- }
- _crcCalculated = true;
- }
- return _Crc32;
- }
- /// <summary>
- /// Stores the position of the entry source stream, or, if the position is
- /// already stored, seeks to that position.
- /// </summary>
- ///
- /// <remarks>
- /// <para>
- /// This method is called in prep for reading the source stream. If PKZIP
- /// encryption is used, then we need to calc the CRC32 before doing the
- /// encryption, because the CRC is used in the 12th byte of the PKZIP
- /// encryption header. So, we need to be able to seek backward in the source
- /// when saving the ZipEntry. This method is called from the place that
- /// calculates the CRC, and also from the method that does the encryption of
- /// the file data.
- /// </para>
- ///
- /// <para>
- /// The first time through, this method sets the _sourceStreamOriginalPosition
- /// field. Subsequent calls to this method seek to that position.
- /// </para>
- /// </remarks>
- private void PrepSourceStream()
- {
- if (_sourceStream == null)
- throw new ZipException(String.Format("The input stream is null for entry '{0}'.", FileName));
- if (this._sourceStreamOriginalPosition != null)
- {
- // this will happen the 2nd cycle through, if the stream is seekable
- this._sourceStream.Position = this._sourceStreamOriginalPosition.Value;
- }
- else if (this._sourceStream.CanSeek)
- {
- // this will happen the first cycle through, if seekable
- this._sourceStreamOriginalPosition = new Nullable<Int64>(this._sourceStream.Position);
- }
- else if (this.Encryption == EncryptionAlgorithm.PkzipWeak)
- {
- // In general, using PKZIP encryption on a a zip entry whose input
- // comes from a non-seekable stream, is tricky. Here's why:
- //
- // Byte 11 of the PKZIP encryption header is used for password
- // validation and consistency checknig.
- //
- // Normally, the highest byte of the CRC is used as the 11th (last) byte
- // in the PKZIP encryption header. This means the CRC must be known
- // before encryption is performed. Normally that means we read the full
- // data stream, compute the CRC, then seek back and read it again for
- // the compression+encryption phase. Obviously this is bad for
- // performance with a large input file.
- //
- // There's a twist in the ZIP spec (actually documented only in infozip
- // code, not in the spec itself) that allows the high-order byte of the
- // last modified time for the entry, when the lastmod time is in packed
- // (DOS) format, to be used for Byte 11 in the encryption header. In
- // this case, the bit 3 "data descriptor" must be used.
- //
- // An intelligent implementation would therefore force the use of the
- // bit 3 data descriptor when PKZIP encryption is in use, regardless.
- // This avoids the double-read of the stream to be encrypted. So far,
- // DotNetZip doesn't do that; it just punts when the input stream is
- // non-seekable, and the output does not use Bit 3.
- //
- // The other option is to use the CRC when it is already available, eg,
- // when the source for the data is a ZipEntry (when the zip file is
- // being updated). In this case we already know the CRC and can just use
- // what we know.
- if (this._Source != ZipEntrySource.ZipFile && ((this._BitField & 0x0008) != 0x0008))
- throw new ZipException("It is not possible to use PKZIP encryption on a non-seekable input stream");
- }
- }
- /// <summary>
- /// Copy metadata that may have been changed by the app. We do this when
- /// resetting the zipFile instance. If the app calls Save() on a ZipFile, then
- /// tries to party on that file some more, we may need to Reset() it , which
- /// means re-reading the entries and then copying the metadata. I think.
- /// </summary>
- internal void CopyMetaData(ZipEntry source)
- {
- this.__FileDataPosition = source.__FileDataPosition;
- this.CompressionMethod = source.CompressionMethod;
- this._CompressionMethod_FromZipFile = source._CompressionMethod_FromZipFile;
- this._CompressedFileDataSize = source._CompressedFileDataSize;
- this._UncompressedSize = source._UncompressedSize;
- this._BitField = source._BitField;
- this._Source = source._Source;
- this._LastModified = source._LastModified;
- this._Mtime = source._Mtime;
- this._Atime = source._Atime;
- this._Ctime = source._Ctime;
- this._ntfsTimesAreSet = source._ntfsTimesAreSet;
- this._emitUnixTimes = source._emitUnixTimes;
- this._emitNtfsTimes = source._emitNtfsTimes;
- }
- private void OnWriteBlock(Int64 bytesXferred, Int64 totalBytesToXfer)
- {
- if (_container.ZipFile != null)
- _ioOperationCanceled = _container.ZipFile.OnSaveBlock(this, bytesXferred, totalBytesToXfer);
- }
- private void _WriteEntryData(Stream s)
- {
- // Read in the data from the input stream (often a file in the filesystem),
- // and write it to the output stream, calculating a CRC on it as we go.
- // We will also compress and encrypt as necessary.
- Stream input = null;
- long fdp = -1L;
- try
- {
- // Want to record the position in the zip file of the zip entry
- // data (as opposed to the metadata). s.Position may fail on some
- // write-only streams, eg stdout or System.Web.HttpResponseStream.
- // We swallow that exception, because we don't care, in that case.
- // But, don't set __FileDataPosition directly. It may be needed
- // to READ the zip entry from the zip file, if this is a
- // "re-stream" situation. In other words if the zip entry has
- // changed compression level, or compression method, or (maybe?)
- // encryption algorithm. In that case if the original entry is
- // encrypted, we need __FileDataPosition to be the value for the
- // input zip file. This s.Position is for the output zipfile. So
- // we copy fdp to __FileDataPosition after this entry has been
- // (maybe) restreamed.
- fdp = s.Position;
- }
- catch (Exception) { }
- try
- {
- // Use fileLength for progress updates, and to decide whether we can
- // skip encryption and compression altogether (in case of length==zero)
- long fileLength = SetInputAndFigureFileLength(ref input);
- // Wrap a counting stream around the raw output stream:
- // This is the last thing that happens before the bits go to the
- // application-provided stream.
- //
- // Sometimes s is a CountingStream. Doesn't matter. Wrap it with a
- // counter anyway. We need to count at both levels.
- CountingStream entryCounter = new CountingStream(s);
- Stream encryptor;
- Stream compressor;
- if (fileLength != 0L)
- {
- // Maybe wrap an encrypting stream around the counter: This will
- // happen BEFORE output counting, and AFTER compression, if encryption
- // is used.
- encryptor = MaybeApplyEncryption(entryCounter);
- // Maybe wrap a compressing Stream around that.
- // This will happen BEFORE encryption (if any) as we write data out.
- compressor = MaybeApplyCompression(encryptor, fileLength);
- }
- else
- {
- encryptor = compressor = entryCounter;
- }
- // Wrap a CrcCalculatorStream around that.
- // This will happen BEFORE compression (if any) as we write data out.
- var output = new Ionic.Crc.CrcCalculatorStream(compressor, true);
- // output.Write() causes this flow:
- // calc-crc -> compress -> encrypt -> count -> actually write
- if (this._Source == ZipEntrySource.WriteDelegate)
- {
- // allow the application to write the data
- this._WriteDelegate(this.FileName, output);
- }
- else
- {
- // synchronously copy the input stream to the output stream-chain
- byte[] buffer = new byte[BufferSize];
- int n;
- while ((n = SharedUtilities.ReadWithRetry(input, buffer, 0, buffer.Length, FileName)) != 0)
- {
- output.Write(buffer, 0, n);
- OnWriteBlock(output.TotalBytesSlurped, fileLength);
- if (_ioOperationCanceled)
- break;
- }
- }
- FinishOutputStream(s, entryCounter, encryptor, compressor, output);
- }
- finally
- {
- if (this._Source == ZipEntrySource.JitStream)
- {
- // allow the application to close the stream
- if (this._CloseDelegate != null)
- this._CloseDelegate(this.FileName, input);
- this._sourceStream = null;
- }
- else if ((input as FileStream) != null)
- {
- #if NETCF
- input.Close();
- #else
- input.Dispose();
- #endif
- }
- }
- if (_ioOperationCanceled)
- return;
- // set FDP now, to allow for re-streaming
- this.__FileDataPosition = fdp;
- PostProcessOutput(s);
- }
- /// <summary>
- /// Set the input stream and get its length, if possible. The length is
- /// used for progress updates, AND, to allow an optimization in case of
- /// a stream/file of zero length. In that case we skip the Encrypt and
- /// compression Stream. (like DeflateStream or BZip2OutputStream)
- /// </summary>
- private long SetInputAndFigureFileLength(ref Stream input)
- {
- long fileLength = -1L;
- // get the original stream:
- if (this._Source == ZipEntrySource.Stream)
- {
- PrepSourceStream();
- input = this._sourceStream;
- // Try to get the length, no big deal if not available.
- try { fileLength = this._sourceStream.Length; }
- catch (NotSupportedException) { }
- }
- else if (this._Source == ZipEntrySource.ZipFile)
- {
- // we are "re-streaming" the zip entry.
- string pwd = (_Encryption_FromZipFile == EncryptionAlgorithm.None) ? null : (this._Password ?? this._container.Password);
- this._sourceStream = InternalOpenReader(pwd);
- PrepSourceStream();
- input = this._sourceStream;
- fileLength = this._sourceStream.Length;
- }
- else if (this._Source == ZipEntrySource.JitStream)
- {
- // allow the application to open the stream
- if (this._sourceStream == null) _sourceStream = this._OpenDelegate(this.FileName);
- PrepSourceStream();
- input = this._sourceStream;
- try { fileLength = this._sourceStream.Length; }
- catch (NotSupportedException) { }
- }
- else if (this._Source == ZipEntrySource.FileSystem)
- {
- // workitem 7145
- FileShare fs = FileShare.ReadWrite;
- #if !NETCF
- // FileShare.Delete is not defined for the Compact Framework
- fs |= FileShare.Delete;
- #endif
- // workitem 8423
- input = File.Open(LocalFileName, FileMode.Open, FileAccess.Read, fs);
- fileLength = input.Length;
- }
- return fileLength;
- }
- internal void FinishOutputStream(Stream s,
- CountingStream entryCounter,
- Stream encryptor,
- Stream compressor,
- Ionic.Crc.CrcCalculatorStream output)
- {
- if (output == null) return;
- output.Close();
- // by calling Close() on the deflate stream, we write the footer bytes, as necessary.
- if ((compressor as Ionic.Zlib.DeflateStream) != null)
- compressor.Close();
- #if BZIP
- else if ((compressor as Ionic.BZip2.BZip2OutputStream) != null)
- compressor.Close();
- #if !NETCF
- else if ((compressor as Ionic.BZip2.ParallelBZip2OutputStream) != null)
- compressor.Close();
- #endif
- #endif
- #if !NETCF
- else if ((compressor as Ionic.Zlib.ParallelDeflateOutputStream) != null)
- compressor.Close();
- #endif
- encryptor.Flush();
- encryptor.Close();
- _LengthOfTrailer = 0;
- _UncompressedSize = output.TotalBytesSlurped;
- #if AESCRYPTO
- WinZipAesCipherStream wzacs = encryptor as WinZipAesCipherStream;
- if (wzacs != null && _UncompressedSize > 0)
- {
- s.Write(wzacs.FinalAuthentication, 0, 10);
- _LengthOfTrailer += 10;
- }
- #endif
- _CompressedFileDataSize = entryCounter.BytesWritten;
- _CompressedSize = _CompressedFileDataSize; // may be adjusted
- _Crc32 = output.Crc;
- // Set _RelativeOffsetOfLocalHeader now, to allow for re-streaming
- StoreRelativeOffset();
- }
- internal void PostProcessOutput(Stream s)
- {
- var s1 = s as CountingStream;
- // workitem 8931 - for WriteDelegate.
- // The WriteDelegate changes things because there can be a zero-byte stream
- // written. In all other cases DotNetZip knows the length of the stream
- // before compressing and encrypting. In this case we have to circle back,
- // and omit all the crypto stuff - the GP bitfield, and the crypto header.
- if (_UncompressedSize == 0 && _CompressedSize == 0)
- {
- if (this._Source == ZipEntrySource.ZipOutputStream) return; // nothing to do...
- if (_Password != null)
- {
- int headerBytesToRetract = 0;
- if (Encryption == EncryptionAlgorithm.PkzipWeak)
- headerBytesToRetract = 12;
- #if AESCRYPTO
- else if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
- Encryption == EncryptionAlgorithm.WinZipAes256)
- {
- headerBytesToRetract = _aesCrypto_forWrite._Salt.Length + _aesCrypto_forWrite.GeneratedPV.Length;
- }
- #endif
- if (this._Source == ZipEntrySource.ZipOutputStream && !s.CanSeek)
- throw new ZipException("Zero bytes written, encryption in use, and non-seekable output.");
- if (Encryption != EncryptionAlgorithm.None)
- {
- // seek back in the stream to un-output the security metadata
- s.Seek(-1 * headerBytesToRetract, SeekOrigin.Current);
- s.SetLength(s.Position);
- // workitem 10178
- Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(s);
- // workitem 11131
- // adjust the count on the CountingStream as necessary
- if (s1 != null) s1.Adjust(headerBytesToRetract);
- // subtract the size of the security header from the _LengthOfHeader
- _LengthOfHeader -= headerBytesToRetract;
- __FileDataPosition -= headerBytesToRetract;
- }
- _Password = null;
- // turn off the encryption bit
- _BitField &= ~(0x0001);
- // copy the updated bitfield value into the header
- int j = 6;
- _EntryHeader[j++] = (byte)(_BitField & 0x00FF);
- _EntryHeader[j++] = (byte)((_BitField & 0xFF00) >> 8);
- #if AESCRYPTO
- if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
- Encryption == EncryptionAlgorithm.WinZipAes256)
- {
- // Fix the extra field - overwrite the 0x9901 headerId
- // with dummy data. (arbitrarily, 0x9999)
- Int16 fnLength = (short)(_EntryHeader[26] + _EntryHeader[27] * 256);
- int offx = 30 + fnLength;
- int aesIndex = FindExtraFieldSegment(_EntryHeader, offx, 0x9901);
- if (aesIndex >= 0)
- {
- _EntryHeader[aesIndex++] = 0x99;
- _EntryHeader[aesIndex++] = 0x99;
- }
- }
- #endif
- }
- CompressionMethod = 0;
- Encryption = EncryptionAlgorithm.None;
- }
- else if (_zipCrypto_forWrite != null
- #if AESCRYPTO
- || _aesCrypto_forWrite != null
- #endif
- )
- {
- if (Encryption == EncryptionAlgorithm.PkzipWeak)
- {
- _CompressedSize += 12; // 12 extra bytes for the encryption header
- }
- #if AESCRYPTO
- else if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
- Encryption == EncryptionAlgorithm.WinZipAes256)
- {
- // adjust the compressed size to include the variable (salt+pv)
- // security header and 10-byte trailer. According to the winzip AES
- // spec, that metadata is included in the "Compressed Size" figure
- // when encoding the zip archive.
- _CompressedSize += _aesCrypto_forWrite.SizeOfEncryptionMetadata;
- }
- #endif
- }
- int i = 8;
- _EntryHeader[i++] = (byte)(_CompressionMethod & 0x00FF);
- _EntryHeader[i++] = (byte)((_CompressionMethod & 0xFF00) >> 8);
- i = 14;
- // CRC - the correct value now
- _EntryHeader[i++] = (byte)(_Crc32 & 0x000000FF);
- _EntryHeader[i++] = (byte)((_Crc32 & 0x0000FF00) >> 8);
- _EntryHeader[i++] = (byte)((_Crc32 & 0x00FF0000) >> 16);
- _EntryHeader[i++] = (byte)((_Crc32 & 0xFF000000) >> 24);
- SetZip64Flags();
- // (i==26) filename length (Int16)
- Int16 filenameLength = (short)(_EntryHeader[26] + _EntryHeader[27] * 256);
- Int16 extraFieldLength = (short)(_EntryHeader[28] + _EntryHeader[29] * 256);
- if (_OutputUsesZip64.Value)
- {
- // VersionNeededToExtract - set to 45 to indicate zip64
- _EntryHeader[4] = (byte)(45 & 0x00FF);
- _EntryHeader[5] = 0x00;
- // workitem 7924 - don't need bit 3
- // // workitem 7917
- // // set bit 3 for ZIP64 compatibility with WinZip12
- // _BitField |= 0x0008;
- // _EntryHeader[6] = (byte)(_BitField & 0x00FF);
- // CompressedSize and UncompressedSize - 0xFF
- for (int j = 0; j < 8; j++)
- _EntryHeader[i++] = 0xff;
- // At this point we need to find the "Extra field" that follows the
- // filename. We had already emitted it, but the data (uncomp, comp,
- // ROLH) was not available at the time we did so. Here, we emit it
- // again, with final values.
- i = 30 + filenameLength;
- _EntryHeader[i++] = 0x01; // zip64
- _EntryHeader[i++] = 0x00;
- i += 2; // skip over data size, which is 16+4
- Array.Copy(BitConverter.GetBytes(_UncompressedSize), 0, _EntryHeader, i, 8);
- i += 8;
- Array.Copy(BitConverter.GetBytes(_CompressedSize), 0, _EntryHeader, i, 8);
- }
- else
- {
- // VersionNeededToExtract - reset to 20 since no zip64
- _EntryHeader[4] = (byte)(20 & 0x00FF);
- _EntryHeader[5] = 0x00;
- // CompressedSize - the correct value now
- i = 18;
- _EntryHeader[i++] = (byte)(_CompressedSize & 0x000000FF);
- _EntryHeader[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
- _EntryHeader[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
- _EntryHeader[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);
- // UncompressedSize - the correct value now
- _EntryHeader[i++] = (byte)(_UncompressedSize & 0x000000FF);
- _EntryHeader[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
- _EntryHeader[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
- _EntryHeader[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);
- // The HeaderId in the extra field header, is already dummied out.
- if (extraFieldLength != 0)
- {
- i = 30 + filenameLength;
- // For zip archives written by this library, if the zip64
- // header exists, it is the first header. Because of the logic
- // used when first writing the _EntryHeader bytes, the
- // HeaderId is not guaranteed to be any particular value. So
- // we determine if the first header is a putative zip64 header
- // by examining the datasize. UInt16 HeaderId =
- // (UInt16)(_EntryHeader[i] + _EntryHeader[i + 1] * 256);
- Int16 DataSize = (short)(_EntryHeader[i + 2] + _EntryHeader[i + 3] * 256);
- if (DataSize == 16)
- {
- // reset to Header Id to dummy value, effectively dummy-ing out the zip64 metadata
- _EntryHeader[i++] = 0x99;
- _EntryHeader[i++] = 0x99;
- }
- }
- }
- #if AESCRYPTO
- if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
- Encryption == EncryptionAlgorithm.WinZipAes256)
- {
- // Must set compressionmethod to 0x0063 (decimal 99)
- //
- // and then set the compression method bytes inside the extra
- // field to the actual compression method value.
- i = 8;
- _EntryHeader[i++] = 0x63;
- _EntryHeader[i++] = 0;
- i = 30 + filenameLength;
- do
- {
- UInt16 HeaderId = (UInt16)(_EntryHeader[i] + _EntryHeader[i + 1] * 256);
- Int16 DataSize = (short)(_EntryHeader[i + 2] + _EntryHeader[i + 3] * 256);
- if (HeaderId != 0x9901)
- {
- // skip this header
- i += DataSize + 4;
- }
- else
- {
- i += 9;
- // actual compression method
- _EntryHeader[i++] = (byte)(_CompressionMethod & 0x00FF);
- _EntryHeader[i++] = (byte)(_CompressionMethod & 0xFF00);
- }
- } while (i < (extraFieldLength - 30 - filenameLength));
- }
- #endif
- // finally, write the data.
- // workitem 7216 - sometimes we don't seek even if we CAN. ASP.NET
- // Response.OutputStream, or stdout are non-seekable. But we may also want
- // to NOT seek in other cases, eg zip64. For all cases, we just check bit 3
- // to see if we want to seek. There's one exception - if using a
- // ZipOutputStream, and PKZip encryption is in use, then we set bit 3 even
- // if the out is seekable. This is so the check on the last byte of the
- // PKZip Encryption Header can be done on the current time, as opposed to
- // the CRC, to prevent streaming the file twice. So, test for
- // ZipOutputStream and seekable, and if so, seek back, even if bit 3 is set.
- if ((_BitField & 0x0008) != 0x0008 ||
- (this._Source == ZipEntrySource.ZipOutputStream && s.CanSeek))
- {
- // seek back and rewrite the entry header
- var zss = s as ZipSegmentedStream;
- if (zss != null && _diskNumber != zss.CurrentSegment)
- {
- // In this case the entry header is in a different file,
- // which has already been closed. Need to re-open it.
- using (Stream hseg = ZipSegmentedStream.ForUpdate(this._container.ZipFile.Name, _diskNumber))
- {
- hseg.Seek(this._RelativeOffsetOfLocalHeader, SeekOrigin.Begin);
- hseg.Write(_EntryHeader, 0, _EntryHeader.Length);
- }
- }
- else
- {
- // seek in the raw output stream, to the beginning of the header for
- // this entry.
- // workitem 8098: ok (output)
- s.Seek(this._RelativeOffsetOfLocalHeader, SeekOrigin.Begin);
- // write the updated header to the output stream
- s.Write(_EntryHeader, 0, _EntryHeader.Length);
- // adjust the count on the CountingStream as necessary
- if (s1 != null) s1.Adjust(_EntryHeader.Length);
- // seek in the raw output stream, to the end of the file data
- // for this entry
- s.Seek(_CompressedSize, SeekOrigin.Current);
- }
- }
- // emit the descriptor - only if not a directory.
- if (((_BitField & 0x0008) == 0x0008) && !IsDirectory)
- {
- byte[] Descriptor = new byte[16 + (_OutputUsesZip64.Value ? 8 : 0)];
- i = 0;
- // signature
- Array.Copy(BitConverter.GetBytes(ZipConstants.ZipEntryDataDescriptorSignature), 0, Descriptor, i, 4);
- i += 4;
- // CRC - the correct value now
- Array.Copy(BitConverter.GetBytes(_Crc32), 0, Descriptor, i, 4);
- i += 4;
- // workitem 7917
- if (_OutputUsesZip64.Value)
- {
- // CompressedSize - the correct value now
- Array.Copy(BitConverter.GetBytes(_CompressedSize), 0, Descriptor, i, 8);
- i += 8;
- // UncompressedSize - the correct value now
- Array.Copy(BitConverter.GetBytes(_UncompressedSize), 0, Descriptor, i, 8);
- i += 8;
- }
- else
- {
- // CompressedSize - (lower 32 bits) the correct value now
- Descriptor[i++] = (byte)(_CompressedSize & 0x000000FF);
- Descriptor[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
- Descriptor[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
- Descriptor[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);
- // UncompressedSize - (lower 32 bits) the correct value now
- Descriptor[i++] = (byte)(_UncompressedSize & 0x000000FF);
- Descriptor[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
- Descriptor[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
- Descriptor[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);
- }
- // finally, write the trailing descriptor to the output stream
- s.Write(Descriptor, 0, Descriptor.Length);
- _LengthOfTrailer += Descriptor.Length;
- }
- }
- private void SetZip64Flags()
- {
- // zip64 housekeeping
- _entryRequiresZip64 = new Nullable<bool>
- (_CompressedSize >= 0xFFFFFFFF || _UncompressedSize >= 0xFFFFFFFF || _RelativeOffsetOfLocalHeader >= 0xFFFFFFFF);
- // validate the ZIP64 usage
- if (_container.Zip64 == Zip64Option.Never && _entryRequiresZip64.Value)
- throw new ZipException("Compressed or Uncompressed size, or offset exceeds the maximum value. Consider setting the UseZip64WhenSaving property on the ZipFile instance.");
- _OutputUsesZip64 = new Nullable<bool>(_container.Zip64 == Zip64Option.Always || _entryRequiresZip64.Value);
- }
- /// <summary>
- /// Prepare the given stream for output - wrap it in a CountingStream, and
- /// then in a CRC stream, and an encryptor and deflator as appropriate.
- /// </summary>
- /// <remarks>
- /// <para>
- /// Previously this was used in ZipEntry.Write(), but in an effort to
- /// introduce some efficiencies in that method I've refactored to put the
- /// code inline. This method still gets called by ZipOutputStream.
- /// </para>
- /// </remarks>
- internal void PrepOutputStream(Stream s,
- long streamLength,
- out CountingStream outputCounter,
- out Stream encryptor,
- out Stream compressor,
- out Ionic.Crc.CrcCalculatorStream output)
- {
- TraceWriteLine("PrepOutputStream: e({0}) comp({1}) crypto({2}) zf({3})",
- FileName,
- CompressionLevel,
- Encryption,
- _container.Name);
- // Wrap a counting stream around the raw output stream:
- // This is the last thing that happens before the bits go to the
- // application-provided stream.
- outputCounter = new CountingStream(s);
- // Sometimes the incoming "raw" output stream is already a CountingStream.
- // Doesn't matter. Wrap it with a counter anyway. We need to count at both
- // levels.
- if (streamLength != 0L)
- {
- // Maybe wrap an encrypting stream around that:
- // This will happen BEFORE output counting, and AFTER deflation, if encryption
- // is used.
- encryptor = MaybeApplyEncryption(outputCounter);
- // Maybe wrap a compressing Stream around that.
- // This will happen BEFORE encryption (if any) as we write data out.
- compressor = MaybeApplyCompression(encryptor, streamLength);
- }
- else
- {
- encryptor = compressor = outputCounter;
- }
- // Wrap a CrcCalculatorStream around that.
- // This will happen BEFORE compression (if any) as we write data out.
- output = new Ionic.Crc.CrcCalculatorStream(compressor, true);
- }
- private Stream MaybeApplyCompression(Stream s, long streamLength)
- {
- if (_CompressionMethod == 0x08 && CompressionLevel != Ionic.Zlib.CompressionLevel.None)
- {
- #if !NETCF
- // ParallelDeflateThreshold == 0 means ALWAYS use parallel deflate
- // ParallelDeflateThreshold == -1L means NEVER use parallel deflate
- // Other values specify the actual threshold.
- if (_container.ParallelDeflateThreshold == 0L ||
- (streamLength > _container.ParallelDeflateThreshold &&
- _container.ParallelDeflateThreshold > 0L))
- {
- // This is sort of hacky.
- //
- // It's expensive to create a ParallelDeflateOutputStream, because
- // of the large memory buffers. But the class is unlike most Stream
- // classes in that it can be re-used, so the caller can compress
- // multiple files with it, one file at a time. The key is to call
- // Reset() on it, in between uses.
- //
- // The ParallelDeflateOutputStream is attached to the container
- // itself - there is just one for the entire ZipFile or
- // ZipOutputStream. So it gets created once, per save, and then
- // re-used many times.
- //
- // This approach will break when we go to a "parallel save"
- // approach, where multiple entries within the zip file are being
- // compressed and saved at the same time. But for now it's ok.
- //
- // instantiate the ParallelDeflateOutputStream
- if (_container.ParallelDeflater == null)
- {
- _container.ParallelDeflater =
- new Ionic.Zlib.ParallelDeflateOutputStream(s,
- CompressionLevel,
- _container.Strategy,
- true);
- // can set the codec buffer size only before the first call to Write().
- if (_container.CodecBufferSize > 0)
- _container.ParallelDeflater.BufferSize = _container.CodecBufferSize;
- if (_container.ParallelDeflateMaxBufferPairs > 0)
- _container.ParallelDeflater.MaxBufferPairs =
- _container.ParallelDeflateMaxBufferPairs;
- }
- // reset it with the new stream
- Ionic.Zlib.ParallelDeflateOutputStream o1 = _container.ParallelDeflater;
- o1.Reset(s);
- return o1;
- }
- #endif
- var o = new Ionic.Zlib.DeflateStream(s, Ionic.Zlib.CompressionMode.Compress,
- CompressionLevel,
- true);
- if (_container.CodecBufferSize > 0)
- o.BufferSize = _container.CodecBufferSize;
- o.Strategy = _container.Strategy;
- return o;
- }
- #if BZIP
- if (_CompressionMethod == 0x0c)
- {
- #if !NETCF
- if (_container.ParallelDeflateThreshold == 0L ||
- (streamLength > _container.ParallelDeflateThreshold &&
- _container.ParallelDeflateThreshold > 0L))
- {
- var o1 = new Ionic.BZip2.ParallelBZip2OutputStream(s, true);
- return o1;
- }
- #endif
- var o = new Ionic.BZip2.BZip2OutputStream(s, true);
- return o;
- }
- #endif
- return s;
- }
- private Stream MaybeApplyEncryption(Stream s)
- {
- if (Encryption == EncryptionAlgorithm.PkzipWeak)
- {
- TraceWriteLine("MaybeApplyEncryption: e({0}) PKZIP", FileName);
- return new ZipCipherStream(s, _zipCrypto_forWrite, CryptoMode.Encrypt);
- }
- #if AESCRYPTO
- if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
- Encryption == EncryptionAlgorithm.WinZipAes256)
- {
- TraceWriteLine("MaybeApplyEncryption: e({0}) AES", FileName);
- return new WinZipAesCipherStream(s, _aesCrypto_forWrite, CryptoMode.Encrypt);
- }
- #endif
- TraceWriteLine("MaybeApplyEncryption: e({0}) None", FileName);
- return s;
- }
- private void OnZipErrorWhileSaving(Exception e)
- {
- if (_container.ZipFile != null)
- _ioOperationCanceled = _container.ZipFile.OnZipErrorSaving(this, e);
- }
- internal void Write(Stream s)
- {
- var cs1 = s as CountingStream;
- var zss1 = s as ZipSegmentedStream;
- bool done = false;
- do
- {
- try
- {
- // When the app is updating a zip file, it may be possible to
- // just copy data for a ZipEntry from the source zipfile to the
- // destination, as a block, without decompressing and
- // recompressing, etc. But, in some cases the app modifies the
- // properties on a ZipEntry prior to calling Save(). A change to
- // any of the metadata - the FileName, CompressioLeve and so on,
- // means DotNetZip cannot simply copy through the existing
- // ZipEntry data unchanged.
- //
- // There are two cases:
- //
- // 1. Changes to only metadata, which means the header and
- // central directory must be changed.
- //
- // 2. Changes to the properties that affect the compressed
- // stream, such as CompressionMethod, CompressionLevel, or
- // EncryptionAlgorithm. In this case, DotNetZip must
- // "re-stream" the data: the old entry data must be maybe
- // decrypted, maybe decompressed, then maybe re-compressed
- // and maybe re-encrypted.
- //
- // This test checks if the source for the entry data is a zip file, and
- // if a restream is necessary. If NOT, then it just copies through
- // one entry, potentially changing the metadata.
- if (_Source == ZipEntrySource.ZipFile && !_restreamRequiredOnSave)
- {
- CopyThroughOneEntry(s);
- return;
- }
- // Is the entry a directory? If so, the write is relatively simple.
- if (IsDirectory)
- {
- WriteHeader(s, 1);
- StoreRelativeOffset();
- _entryRequiresZip64 = new Nullable<bool>(_RelativeOffsetOfLocalHeader >= 0xFFFFFFFF);
- _OutputUsesZip64 = new Nullable<bool>(_container.Zip64 == Zip64Option.Always || _entryRequiresZip64.Value);
- // handle case for split archives
- if (zss1 != null)
- _diskNumber = zss1.CurrentSegment;
- return;
- }
- // At this point, the source for this entry is not a directory, and
- // not a previously created zip file, or the source for the entry IS
- // a previously created zip but the settings whave changed in
- // important ways and therefore we will need to process the
- // bytestream (compute crc, maybe compress, maybe encrypt) in order
- // to write the content into the new zip.
- //
- // We do this in potentially 2 passes: The first time we do it as
- // requested, maybe with compression and maybe encryption. If that
- // causes the bytestream to inflate in size, and if compression was
- // on, then we turn off compression and do it again.
- bool readAgain = true;
- int nCycles = 0;
- do
- {
- nCycles++;
- WriteHeader(s, nCycles);
- // write the encrypted header
- WriteSecurityMetadata(s);
- // write the (potentially compressed, potentially encrypted) file data
- _WriteEntryData(s);
- // track total entry size (including the trailing descriptor and MAC)
- _TotalEntrySize = _LengthOfHeader + _CompressedFileDataSize + _LengthOfTrailer;
- // The file data has now been written to the stream, and
- // the file pointer is positioned directly after file data.
- if (nCycles > 1) readAgain = false;
- else if (!s.CanSeek) readAgain = false;
- else readAgain = WantReadAgain();
- if (readAgain)
- {
- // Seek back in the raw output stream, to the beginning of the file
- // data for this entry.
- // handle case for split archives
- if (zss1 != null)
- {
- // Console.WriteLine("***_diskNumber/first: {0}", _diskNumber);
- // Console.WriteLine("***_diskNumber/current: {0}", zss.CurrentSegment);
- zss1.TruncateBackward(_diskNumber, _RelativeOffsetOfLocalHeader);
- }
- else
- // workitem 8098: ok (output).
- s.Seek(_RelativeOffsetOfLocalHeader, SeekOrigin.Begin);
- // If the last entry expands, we read again; but here, we must
- // truncate the stream to prevent garbage data after the
- // end-of-central-directory.
- // workitem 8098: ok (output).
- s.SetLength(s.Position);
- // Adjust the count on the CountingStream as necessary.
- if (cs1 != null) cs1.Adjust(_TotalEntrySize);
- }
- }
- while (readAgain);
- _skippedDuringSave = false;
- done = true;
- }
- catch (System.Exception exc1)
- {
- ZipErrorAction orig = this.ZipErrorAction;
- int loop = 0;
- do
- {
- if (ZipErrorAction == ZipErrorAction.Throw)
- throw;
- if (ZipErrorAction == ZipErrorAction.Skip ||
- ZipErrorAction == ZipErrorAction.Retry)
- {
- // must reset file pointer here.
- // workitem 13903 - seek back only when necessary
- long p1 = (cs1 != null)
- ? cs1.ComputedPosition
- : s.Position;
- long delta = p1 - _future_ROLH;
- if (delta > 0)
- {
- s.Seek(delta, SeekOrigin.Current); // may throw
- long p2 = s.Position;
- s.SetLength(s.Position); // to prevent garbage if this is the last entry
- if (cs1 != null) cs1.Adjust(p1 - p2);
- }
- if (ZipErrorAction == ZipErrorAction.Skip)
- {
- WriteStatus("Skipping file {0} (exception: {1})", LocalFileName, exc1.ToString());
- _skippedDuringSave = true;
- done = true;
- }
- else
- this.ZipErrorAction = orig;
- break;
- }
- if (loop > 0) throw;
- if (ZipErrorAction == ZipErrorAction.InvokeErrorEvent)
- {
- OnZipErrorWhileSaving(exc1);
- if (_ioOperationCanceled)
- {
- done = true;
- break;
- }
- }
- loop++;
- }
- while (true);
- }
- }
- while (!done);
- }
- internal void StoreRelativeOffset()
- {
- _RelativeOffsetOfLocalHeader = _future_ROLH;
- }
- internal void NotifySaveComplete()
- {
- // When updating a zip file, there are two contexts for properties
- // like Encryption or CompressionMethod - the values read from the
- // original zip file, and the values used in the updated zip file.
- // The _FromZipFile versions are the originals. At the end of a save,
- // these values are the same. So we need to update them. This takes
- // care of the boundary case where a single zipfile instance can be
- // saved multiple times, with distinct changes to the properties on
- // the entries, in between each Save().
- _Encryption_FromZipFile = _Encryption;
- _CompressionMethod_FromZipFile = _CompressionMethod;
- _restreamRequiredOnSave = false;
- _metadataChanged = false;
- //_Source = ZipEntrySource.None;
- _Source = ZipEntrySource.ZipFile; // workitem 10694
- }
- internal void WriteSecurityMetadata(Stream outstream)
- {
- if (Encryption == EncryptionAlgorithm.None)
- return;
- string pwd = this._Password;
- // special handling for source == ZipFile.
- // Want to support the case where we re-stream an encrypted entry. This will involve,
- // at runtime, reading, decrypting, and decompressing from the original zip file, then
- // compressing, encrypting, and writing to the output zip file.
- // If that's what we're doing, and the password hasn't been set on the entry,
- // we use the container (ZipFile/ZipOutputStream) password to decrypt.
- // This test here says to use the container password to re-encrypt, as well,
- // with that password, if the entry password is null.
- if (this._Source == ZipEntrySource.ZipFile && pwd == null)
- pwd = this._container.Password;
- if (pwd == null)
- {
- _zipCrypto_forWrite = null;
- #if AESCRYPTO
- _aesCrypto_forWrite = null;
- #endif
- return;
- }
- TraceWriteLine("WriteSecurityMetadata: e({0}) crypto({1}) pw({2})",
- FileName, Encryption.ToString(), pwd);
- if (Encryption == EncryptionAlgorithm.PkzipWeak)
- {
- // If PKZip (weak) encryption is in use, then the encrypted entry data
- // is preceded by 12-byte "encryption header" for the entry.
- _zipCrypto_forWrite = ZipCrypto.ForWrite(pwd);
- // generate the random 12-byte header:
- var rnd = new System.Random();
- byte[] encryptionHeader = new byte[12];
- rnd.NextBytes(encryptionHeader);
- // workitem 8271
- if ((this._BitField & 0x0008) == 0x0008)
- {
- // In the case that bit 3 of the general purpose bit flag is set to
- // indicate the presence of a 'data descriptor' (signature
- // 0x08074b50), the last byte of the decrypted header is sometimes
- // compared with the high-order byte of the lastmodified time,
- // rather than the high-order byte of the CRC, to verify the
- // password.
- //
- // This is not documented in the PKWare Appnote.txt.
- // This was discovered this by analysis of the Crypt.c source file in the
- // InfoZip library
- // http://www.info-zip.org/pub/infozip/
- // Also, winzip insists on this!
- if (_dontEmitLastModified)
- {
- _TimeBlob = 0;
- }
- else
- {
- _TimeBlob = Ionic.Zip.SharedUtilities.DateTimeToPacked(LastModified);
- }
-
- encryptionHeader[11] = (byte)((this._TimeBlob >> 8) & 0xff);
- }
- else
- {
- // When bit 3 is not set, the CRC value is required before
- // encryption of the file data begins. In this case there is no way
- // around it: must read the stream in its entirety to compute the
- // actual CRC before proceeding.
- FigureCrc32();
- encryptionHeader[11] = (byte)((this._Crc32 >> 24) & 0xff);
- }
- // Encrypt the random header, INCLUDING the final byte which is either
- // the high-order byte of the CRC32, or the high-order byte of the
- // _TimeBlob. Must do this BEFORE encrypting the file data. This
- // step changes the state of the cipher, or in the words of the PKZIP
- // spec, it "further initializes" the cipher keys.
- byte[] cipherText = _zipCrypto_forWrite.EncryptMessage(encryptionHeader, encryptionHeader.Length);
- // Write the ciphered bonafide encryption header.
- outstream.Write(cipherText, 0, cipherText.Length);
- _LengthOfHeader += cipherText.Length; // 12 bytes
- }
- #if AESCRYPTO
- else if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
- Encryption == EncryptionAlgorithm.WinZipAes256)
- {
- // If WinZip AES encryption is in use, then the encrypted entry data is
- // preceded by a variable-sized Salt and a 2-byte "password
- // verification" value for the entry.
- int keystrength = GetKeyStrengthInBits(Encryption);
- _aesCrypto_forWrite = WinZipAesCrypto.Generate(pwd, keystrength);
- outstream.Write(_aesCrypto_forWrite.Salt, 0, _aesCrypto_forWrite._Salt.Length);
- outstream.Write(_aesCrypto_forWrite.GeneratedPV, 0, _aesCrypto_forWrite.GeneratedPV.Length);
- _LengthOfHeader += _aesCrypto_forWrite._Salt.Length + _aesCrypto_forWrite.GeneratedPV.Length;
- TraceWriteLine("WriteSecurityMetadata: AES e({0}) keybits({1}) _LOH({2})",
- FileName, keystrength, _LengthOfHeader);
- }
- #endif
- }
- private void CopyThroughOneEntry(Stream outStream)
- {
- // Just read the entry from the existing input zipfile and write to the output.
- // But, if metadata has changed (like file times or attributes), or if the ZIP64
- // option has changed, we can re-stream the entry data but must recompute the
- // metadata.
- if (this.LengthOfHeader == 0)
- throw new BadStateException("Bad header length.");
- // is it necessary to re-constitute new metadata for this entry?
- bool needRecompute = _metadataChanged ||
- (this.ArchiveStream is ZipSegmentedStream) ||
- (outStream is ZipSegmentedStream) ||
- (_InputUsesZip64 && _container.UseZip64WhenSaving == Zip64Option.Never) ||
- (!_InputUsesZip64 && _container.UseZip64WhenSaving == Zip64Option.Always);
- if (needRecompute)
- CopyThroughWithRecompute(outStream);
- else
- CopyThroughWithNoChange(outStream);
- // zip64 housekeeping
- _entryRequiresZip64 = new Nullable<bool>
- (_CompressedSize >= 0xFFFFFFFF || _UncompressedSize >= 0xFFFFFFFF ||
- _RelativeOffsetOfLocalHeader >= 0xFFFFFFFF
- );
- _OutputUsesZip64 = new Nullable<bool>(_container.Zip64 == Zip64Option.Always || _entryRequiresZip64.Value);
- }
- private void CopyThroughWithRecompute(Stream outstream)
- {
- int n;
- byte[] bytes = new byte[BufferSize];
- var input = new CountingStream(this.ArchiveStream);
- long origRelativeOffsetOfHeader = _RelativeOffsetOfLocalHeader;
- // The header length may change due to rename of file, add a comment, etc.
- // We need to retain the original.
- int origLengthOfHeader = LengthOfHeader; // including crypto bytes!
- // WriteHeader() has the side effect of changing _RelativeOffsetOfLocalHeader
- // and setting _LengthOfHeader. While ReadHeader() reads the crypto header if
- // present, WriteHeader() does not write the crypto header.
- WriteHeader(outstream, 0);
- StoreRelativeOffset();
- if (!this.FileName.EndsWith("/"))
- {
- // Not a directory; there is file data.
- // Seek to the beginning of the entry data in the input stream.
- long pos = origRelativeOffsetOfHeader + origLengthOfHeader;
- int len = GetLengthOfCryptoHeaderBytes(_Encryption_FromZipFile);
- pos -= len; // want to keep the crypto header
- _LengthOfHeader += len;
- input.Seek(pos, SeekOrigin.Begin);
- // copy through everything after the header to the output stream
- long remaining = this._CompressedSize;
- while (remaining > 0)
- {
- len = (remaining > bytes.Length) ? bytes.Length : (int)remaining;
- // read
- n = input.Read(bytes, 0, len);
- _CheckRead(n);
- // write
- outstream.Write(bytes, 0, n);
- remaining -= n;
- OnWriteBlock(input.BytesRead, this._CompressedSize);
- if (_ioOperationCanceled)
- break;
- }
- // bit 3 descriptor
- if ((this._BitField & 0x0008) == 0x0008)
- {
- int size = 16;
- if (_InputUsesZip64) size += 8;
- byte[] Descriptor = new byte[size];
- input.Read(Descriptor, 0, size);
- if (_InputUsesZip64 && _container.UseZip64WhenSaving == Zip64Option.Never)
- {
- // original descriptor was 24 bytes, now we need 16.
- // Must check for underflow here.
- // signature + CRC.
- outstream.Write(Descriptor, 0, 8);
- // Compressed
- if (_CompressedSize > 0xFFFFFFFF)
- throw new InvalidOperationException("ZIP64 is required");
- outstream.Write(Descriptor, 8, 4);
- // UnCompressed
- if (_UncompressedSize > 0xFFFFFFFF)
- throw new InvalidOperationException("ZIP64 is required");
- outstream.Write(Descriptor, 16, 4);
- _LengthOfTrailer -= 8;
- }
- else if (!_InputUsesZip64 && _container.UseZip64WhenSaving == Zip64Option.Always)
- {
- // original descriptor was 16 bytes, now we need 24
- // signature + CRC
- byte[] pad = new byte[4];
- outstream.Write(Descriptor, 0, 8);
- // Compressed
- outstream.Write(Descriptor, 8, 4);
- outstream.Write(pad, 0, 4);
- // UnCompressed
- outstream.Write(Descriptor, 12, 4);
- outstream.Write(pad, 0, 4);
- _LengthOfTrailer += 8;
- }
- else
- {
- // same descriptor on input and output. Copy it through.
- outstream.Write(Descriptor, 0, size);
- //_LengthOfTrailer += size;
- }
- }
- }
- _TotalEntrySize = _LengthOfHeader + _CompressedFileDataSize + _LengthOfTrailer;
- }
- private void CopyThroughWithNoChange(Stream outstream)
- {
- int n;
- byte[] bytes = new byte[BufferSize];
- var input = new CountingStream(this.ArchiveStream);
- // seek to the beginning of the entry data in the input stream
- input.Seek(this._RelativeOffsetOfLocalHeader, SeekOrigin.Begin);
- if (this._TotalEntrySize == 0)
- {
- // We've never set the length of the entry.
- // Set it here.
- this._TotalEntrySize = this._LengthOfHeader + this._CompressedFileDataSize + _LengthOfTrailer;
- // The CompressedSize includes all the leading metadata associated
- // to encryption, if any, as well as the compressed data, or
- // compressed-then-encrypted data, and the trailer in case of AES.
- // The CompressedFileData size is the same, less the encryption
- // framing data (12 bytes header for PKZip; 10/18 bytes header and
- // 10 byte trailer for AES).
- // The _LengthOfHeader includes all the zip entry header plus the
- // crypto header, if any. The _LengthOfTrailer includes the
- // 10-byte MAC for AES, where appropriate, and the bit-3
- // Descriptor, where applicable.
- }
- // workitem 5616
- // remember the offset, within the output stream, of this particular entry header.
- // This may have changed if any of the other entries changed (eg, if a different
- // entry was removed or added.)
- var counter = outstream as CountingStream;
- _RelativeOffsetOfLocalHeader = (counter != null)
- ? counter.ComputedPosition
- : outstream.Position; // BytesWritten
- // copy through the header, filedata, trailer, everything...
- long remaining = this._TotalEntrySize;
- while (remaining > 0)
- {
- int len = (remaining > bytes.Length) ? bytes.Length : (int)remaining;
- // read
- n = input.Read(bytes, 0, len);
- _CheckRead(n);
- // write
- outstream.Write(bytes, 0, n);
- remaining -= n;
- OnWriteBlock(input.BytesRead, this._TotalEntrySize);
- if (_ioOperationCanceled)
- break;
- }
- }
- [System.Diagnostics.ConditionalAttribute("Trace")]
- private void TraceWriteLine(string format, params object[] varParams)
- {
- lock (_outputLock)
- {
- int tid = System.Threading.Thread.CurrentThread.GetHashCode();
- #if ! (NETCF || SILVERLIGHT || IOS || ANDROID)
- Console.ForegroundColor = (ConsoleColor)(tid % 8 + 8);
- #endif
- Console.Write("{0:000} ZipEntry.Write ", tid);
- Console.WriteLine(format, varParams);
- #if ! (NETCF || SILVERLIGHT || IOS || ANDROID)
- Console.ResetColor();
- #endif
- }
- }
- private object _outputLock = new Object();
- }
- }
|