1 // ZipTests.cs
2 //
3 // Author:
4 //	   Joao Matos <joao.matos@xamarin.com>
5 //
6 // Copyright (c) 2013 Xamarin Inc. (http://www.xamarin.com)
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to deal
10 // in the Software without restriction, including without limitation the rights
11 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 // copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
14 //
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
17 //
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 // THE SOFTWARE.
25 
26 using System;
27 using System.IO;
28 using System.IO.Compression;
29 using System.Linq;
30 using System.Security.Cryptography;
31 using NUnit.Framework;
32 
33 namespace MonoTests.System.IO.Compression
34 {
35 	[TestFixture]
36 	public class ZipArchiveTests
37 	{
GetSHA1HashFromFile(Stream stream)38 		static string GetSHA1HashFromFile(Stream stream)
39 		{
40 			using (var sha1 = SHA1.Create())
41 			{
42 				return BitConverter.ToString(sha1.ComputeHash(stream))
43 					.Replace("-", string.Empty);
44 			}
45 		}
46 
47 		[Test]
ZipGetEntryReadMode()48 		public void ZipGetEntryReadMode()
49 		{
50 			var tmpFile = Path.GetTempFileName ();
51 			File.Copy("archive.zip", tmpFile, overwrite: true);
52 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
53 				ZipArchiveMode.Read))
54 			{
55 				var entry = archive.GetEntry("foo.txt");
56 				Assert.IsNotNull(entry);
57 
58 				var nullEntry = archive.GetEntry("nonexisting");
59 				Assert.IsNull(nullEntry);
60 			}
61 
62 			File.Delete (tmpFile);
63 		}
64 
65 		[Test]
ZipGetEntryCreateMode()66 		public void ZipGetEntryCreateMode()
67 		{
68 			var tmpFile = Path.GetTempFileName ();
69 			File.Copy("archive.zip", tmpFile, overwrite: true);
70 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
71 				ZipArchiveMode.Create))
72 			{
73 				try {
74 					archive.GetEntry("foo");
75 				} catch(NotSupportedException ex) {
76 					return;
77 				}
78 
79 				Assert.Fail();
80 			}
81 
82 			File.Delete (tmpFile);
83 		}
84 
85 		[Test]
ZipGetEntryUpdateMode()86 		public void ZipGetEntryUpdateMode()
87 		{
88 			var tmpFile = Path.GetTempFileName ();
89 			File.Copy("archive.zip", tmpFile, overwrite: true);
90 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
91 				ZipArchiveMode.Read))
92 			{
93 				var entry = archive.GetEntry("foo.txt");
94 				Assert.IsNotNull(entry);
95 
96 				var nullEntry = archive.GetEntry("nonexisting");
97 				Assert.IsNull(nullEntry);
98 			}
99 
100 			File.Delete (tmpFile);
101 		}
102 
103 		[Test]
ZipGetEntryOpen()104 		public void ZipGetEntryOpen()
105 		{
106 			var tmpFile = Path.GetTempFileName ();
107 			File.Copy("archive.zip", tmpFile, overwrite: true);
108 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
109 				ZipArchiveMode.Read))
110 			{
111 				var entry = archive.GetEntry("foo.txt");
112 				Assert.IsNotNull(entry);
113 
114 				var foo = entry.Open();
115 			}
116 
117 			File.Delete (tmpFile);
118 		}
119 
120 		[Test]
ZipOpenAndReopenEntry()121 		public void ZipOpenAndReopenEntry()
122 		{
123 			var tmpFile = Path.GetTempFileName ();
124 			try {
125 				File.Copy("archive.zip", tmpFile, overwrite: true);
126 				using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
127 					ZipArchiveMode.Update))
128 				{
129 					var entry = archive.GetEntry("foo.txt");
130 					Assert.IsNotNull(entry);
131 
132 					var stream = entry.Open();
133 
134 					try {
135 						stream = entry.Open();
136 					} catch (global::System.IO.IOException ex) {
137 						return;
138 					}
139 
140 					Assert.Fail();
141 				}
142 			} finally {
143 				File.Delete (tmpFile);
144 			}
145 		}
146 
147 
148 		[Test]
ZipOpenCloseAndReopenEntry()149 		public void ZipOpenCloseAndReopenEntry()
150 		{
151 			var tmpFile = Path.GetTempFileName ();
152 			File.Copy("archive.zip", tmpFile, overwrite: true);
153 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
154 				ZipArchiveMode.Update))
155 			{
156 				var entry = archive.GetEntry("foo.txt");
157 				Assert.IsNotNull(entry);
158 
159 				var stream = entry.Open();
160 				stream.Dispose();
161 				stream = entry.Open();
162 			}
163 
164 			File.Delete (tmpFile);
165 		}
166 
167 		[Test]
ZipGetEntryDeleteReadMode()168 		public void ZipGetEntryDeleteReadMode()
169 		{
170 			var tmpFile = Path.GetTempFileName ();
171 			File.Copy("archive.zip", tmpFile, overwrite: true);
172 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
173 				ZipArchiveMode.Update))
174 			{
175 				var entry = archive.GetEntry("foo.txt");
176 				Assert.IsNotNull(entry);
177 
178 				entry.Delete();
179 			}
180 
181 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
182 				ZipArchiveMode.Read))
183 			{
184 				var entry = archive.GetEntry("foo.txt");
185 				Assert.IsNull(entry);
186 			}
187 
188 			File.Delete (tmpFile);
189 		}
190 
191 		[Test]
ZipDeleteEntryCheckEntries()192 		public void ZipDeleteEntryCheckEntries()
193 		{
194 			var tmpFile = Path.GetTempFileName ();
195 			File.Copy("archive.zip", tmpFile, overwrite: true);
196 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
197 				ZipArchiveMode.Update))
198 			{
199 				var entry = archive.GetEntry("foo.txt");
200 				Assert.IsNotNull(entry);
201 
202 				entry.Delete();
203 
204 				Assert.IsNull(archive.Entries.FirstOrDefault(e => e == entry));
205 			}
206 
207 			File.Delete (tmpFile);
208 		}
209 
210 		[Test]
ZipGetEntryDeleteUpdateMode()211 		public void ZipGetEntryDeleteUpdateMode()
212 		{
213 			var tmpFile = Path.GetTempFileName ();
214 			File.Copy("archive.zip", tmpFile, overwrite: true);
215 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
216 				ZipArchiveMode.Update))
217 			{
218 				var entry = archive.GetEntry("foo.txt");
219 				Assert.IsNotNull(entry);
220 
221 				entry.Delete();
222 			}
223 
224 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
225 				ZipArchiveMode.Read))
226 			{
227 				var entry = archive.GetEntry("foo.txt");
228 				Assert.IsNull(entry);
229 			}
230 
231 			File.Delete (tmpFile);
232 		}
233 
234 		[Test]
ZipCreateArchive()235 		public void ZipCreateArchive()
236 		{
237 			var tmpFile = Path.GetTempFileName ();
238 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Create),
239 				ZipArchiveMode.Create))
240 			{
241 				var dir = archive.CreateEntry("foobar/");
242 
243 				var entry = archive.CreateEntry("foo.txt");
244 				using (var stream = entry.Open())
245 				{
246 					using (var streamWriter = new StreamWriter(stream))
247 						streamWriter.Write("foo");
248 				}
249 			}
250 
251 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
252 				ZipArchiveMode.Read))
253 			{
254 				Assert.IsNotNull(archive.GetEntry("foobar/"));
255 
256 				var entry = archive.GetEntry("foo.txt");
257 				Assert.IsNotNull(entry);
258 
259 				var streamReader = new StreamReader(entry.Open());
260 				var text = streamReader.ReadToEnd();
261 
262 				Assert.AreEqual("foo", text);
263 			}
264 
265 			File.Delete (tmpFile);
266 		}
267 
268 		[Test]
ZipEnumerateEntriesModifiedTime()269 		public void ZipEnumerateEntriesModifiedTime()
270 		{
271 			var tmpFile = Path.GetTempFileName ();
272 			File.Copy("archive.zip", tmpFile, overwrite: true);
273 			var date = DateTimeOffset.Now;
274 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
275 				ZipArchiveMode.Update))
276 			{
277 				var entry = archive.GetEntry("foo.txt");
278 				entry.LastWriteTime = date;
279 			}
280 
281 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
282 				ZipArchiveMode.Read))
283 			{
284 				var entry = archive.GetEntry("foo.txt");
285 				Assert.AreEqual(entry.LastWriteTime.Year, date.Year);
286 				Assert.AreEqual(entry.LastWriteTime.Month, date.Month);
287 				Assert.AreEqual(entry.LastWriteTime.Day, date.Day);
288 
289 			}
290 
291 			File.Delete (tmpFile);
292 		}
293 
294 		[Test]
ZipEnumerateArchiveDefaultLastWriteTime()295 		public void ZipEnumerateArchiveDefaultLastWriteTime()
296 		{
297 			var tmpFile = Path.GetTempFileName ();
298 			File.Copy("test.nupkg", tmpFile, overwrite: true);
299 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
300 				ZipArchiveMode.Read))
301 			{
302 				var entry = archive.GetEntry("_rels/.rels");
303 				Assert.AreEqual(new DateTime(624511296000000000).Ticks, entry.LastWriteTime.Ticks);
304 				Assert.IsNotNull(entry);
305 			}
306 			File.Delete (tmpFile);
307 		}
308 
ZipGetArchiveEntryStreamLengthPosition(ZipArchiveMode mode)309 		public void ZipGetArchiveEntryStreamLengthPosition(ZipArchiveMode mode)
310 		{
311 			var tmpFile = Path.GetTempFileName ();
312 			File.Copy("test.nupkg", tmpFile, overwrite: true);
313 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite), mode))
314 			{
315 				var entry = archive.GetEntry("_rels/.rels");
316 				using (var stream = entry.Open())
317 				{
318 					Assert.AreEqual(0, stream.Position);
319 					Assert.AreEqual(425, stream.Length);
320 				}
321 
322 				var entry2 = archive.GetEntry("modernhttpclient.nuspec");
323 				using (var stream = entry2.Open())
324 				{
325 					// .NET does not support these in Read mode
326 					if (mode == ZipArchiveMode.Update)
327 					{
328 						Assert.AreEqual(857, stream.Length);
329 						Assert.AreEqual(0, stream.Position);
330 					}
331 				}
332 			}
333 			File.Delete (tmpFile);
334 		}
335 
336 		[Test]
ZipGetArchiveEntryStreamLengthPositionReadMode()337 		public void ZipGetArchiveEntryStreamLengthPositionReadMode()
338 		{
339 			ZipGetArchiveEntryStreamLengthPosition(ZipArchiveMode.Read);
340 		}
341 
342 		[Test]
ZipGetArchiveEntryStreamLengthPositionUpdateMode()343 		public void ZipGetArchiveEntryStreamLengthPositionUpdateMode()
344 		{
345 			ZipGetArchiveEntryStreamLengthPosition(ZipArchiveMode.Update);
346 		}
347 
348 		[Test]
ZipEnumerateEntriesReadMode()349 		public void ZipEnumerateEntriesReadMode()
350 		{
351 			var tmpFile = Path.GetTempFileName ();
352 			File.Copy("archive.zip", tmpFile, overwrite: true);
353 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
354 				ZipArchiveMode.Read))
355 			{
356 				var entries = archive.Entries;
357 				Assert.AreEqual(5, entries.Count);
358 
359 				Assert.AreEqual("bar.txt", entries[0].FullName);
360 				Assert.AreEqual("foo.txt", entries[1].FullName);
361 				Assert.AreEqual("foobar/", entries[2].FullName);
362 				Assert.AreEqual("foobar/bar.txt", entries[3].FullName);
363 				Assert.AreEqual("foobar/foo.txt", entries[4].FullName);
364 			}
365 
366 			File.Delete (tmpFile);
367 		}
368 
369 		[Test]
ZipWriteEntriesUpdateMode()370 		public void ZipWriteEntriesUpdateMode()
371 		{
372 			var tmpFile = Path.GetTempFileName ();
373 			File.Copy("archive.zip", tmpFile, overwrite: true);
374 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
375 				ZipArchiveMode.Update))
376 			{
377 				var foo = archive.GetEntry("foo.txt");
378 				using (var stream = foo.Open())
379 				using (var sw = new StreamWriter(stream))
380 				{
381 					sw.Write("TEST");
382 				}
383 			}
384 
385 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
386 				ZipArchiveMode.Read))
387 			{
388 				var foo = archive.GetEntry("foo.txt");
389 				using (var stream = foo.Open())
390 				using (var sr = new StreamReader(stream))
391 				{
392 					var line = sr.ReadLine();
393 					Assert.AreEqual("TEST", line);
394 				}
395 			}
396 
397 			File.Delete (tmpFile);
398 		}
399 
400 		[Test]
ZipWriteEntriesUpdateModeNewEntry()401 		public void ZipWriteEntriesUpdateModeNewEntry()
402 		{
403 			var stream = new MemoryStream();
404 			var zipArchive = new ZipArchive(stream, ZipArchiveMode.Update);
405 
406 			var newEntry = zipArchive.CreateEntry("testEntry");
407 
408 			using (var newStream = newEntry.Open())
409 			{
410 				using (var sw = new StreamWriter(newStream))
411 				{
412 					sw.Write("TEST");
413 				}
414 			}
415 		}
416 
417 		[Test]
ZipCreateDuplicateEntriesUpdateMode()418 		public void ZipCreateDuplicateEntriesUpdateMode()
419 		{
420 			var stream = new MemoryStream();
421 			using (var zipArchive = new ZipArchive(stream, ZipArchiveMode.Update, true))
422 			{
423 				var e2 = zipArchive.CreateEntry("BBB");
424 				var e3 = zipArchive.CreateEntry("BBB");
425 			}
426 
427 			stream.Position = 0;
428 			using (var zipArchive = new ZipArchive(stream, ZipArchiveMode.Read))
429 			{
430 				Assert.AreEqual(2, zipArchive.Entries.Count);
431 			}
432 		}
433 
434 		[Test]
ZipWriteEntriesUpdateModeNonZeroPosition()435 		public void ZipWriteEntriesUpdateModeNonZeroPosition()
436 		{
437 			var tmpFile = Path.GetTempFileName ();
438 			File.Copy("archive.zip", tmpFile, overwrite: true);
439 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
440 				ZipArchiveMode.Update))
441 			{
442 				var foo = archive.GetEntry("foo.txt");
443 				using (var stream = foo.Open())
444 				{
445 					var line = stream.ReadByte();
446 					using (var sw = new StreamWriter(stream))
447 					{
448 						sw.Write("TEST");
449 					}
450 				}
451 			}
452 
453 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
454 				ZipArchiveMode.Read))
455 			{
456 				var entries = archive.Entries;
457 				var foo = archive.GetEntry("foo.txt");
458 				using (var stream = foo.Open())
459 				using (var sr = new StreamReader(stream))
460 				{
461 					var line = sr.ReadLine();
462 					Assert.AreEqual("fTEST", line);
463 				}
464 			}
465 
466 			File.Delete (tmpFile);
467 		}
468 
469 		[Test]
ZipEnumerateEntriesUpdateMode()470 		public void ZipEnumerateEntriesUpdateMode()
471 		{
472 			var tmpFile = Path.GetTempFileName ();
473 			File.Copy("archive.zip", tmpFile, overwrite: true);
474 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open, FileAccess.ReadWrite),
475 				ZipArchiveMode.Read))
476 			{
477 				var entries = archive.Entries;
478 				Assert.AreEqual(5, entries.Count);
479 
480 				Assert.AreEqual("bar.txt", entries[0].FullName);
481 				Assert.AreEqual("foo.txt", entries[1].FullName);
482 				Assert.AreEqual("foobar/", entries[2].FullName);
483 				Assert.AreEqual("foobar/bar.txt", entries[3].FullName);
484 				Assert.AreEqual("foobar/foo.txt", entries[4].FullName);
485 			}
486 
487 			File.Delete (tmpFile);
488 		}
489 
490 		[Test]
ZipEnumerateEntriesCreateMode()491 		public void ZipEnumerateEntriesCreateMode()
492 		{
493 			var tmpFile = Path.GetTempFileName ();
494 			File.Copy("archive.zip", tmpFile, overwrite: true);
495 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open),
496 				ZipArchiveMode.Create))
497 			{
498 				try {
499 					archive.Entries.ToList();
500 				} catch(NotSupportedException ex) {
501 					return;
502 				}
503 
504 				Assert.Fail();
505 			}
506 
507 			File.Delete (tmpFile);
508 		}
509 
510 		[Test]
ZipUpdateEmptyArchive()511 		public void ZipUpdateEmptyArchive()
512 		{
513 			var tmpFile = Path.GetTempFileName ();
514 			File.WriteAllText(tmpFile, string.Empty);
515 			using (var archive = new ZipArchive(File.Open(tmpFile, FileMode.Open),
516 				ZipArchiveMode.Update))
517 			{
518 			}
519 			File.Delete (tmpFile);
520 		}
521 
522 		class MyFakeStream : FileStream
523 		{
MyFakeStream(string path, FileMode mode)524 			public MyFakeStream (string path, FileMode mode) : base(path, mode) {}
525 
526 			/// <summary>
527 			/// Simulate "CanSeek" is false, which is the case when you are retreiving data from web.
528 			/// </summary>
529 			public override bool CanSeek => false;
530 
531 			public override long Position {
532 				get {throw new NotSupportedException();}
533 				set {throw new NotSupportedException();}
534 			}
535 		}
536 
537 		[Test]
ZipReadNonSeekableStream()538 		public void ZipReadNonSeekableStream()
539 		{
540 			var tmpFile = Path.GetTempFileName ();
541 			File.Copy("test.nupkg", tmpFile, overwrite: true);
542 			var stream = new MyFakeStream(tmpFile, FileMode.Open);
543 			using (var archive = new ZipArchive (stream, ZipArchiveMode.Read))
544 			{
545 			}
546 			File.Delete (tmpFile);
547 		}
548 
549 		[Test]
ZipWriteNonSeekableStream()550 		public void ZipWriteNonSeekableStream()
551 		{
552 			var tmpFile = Path.GetTempFileName ();
553 			File.Copy("test.nupkg", tmpFile, overwrite: true);
554 			var stream = new MyFakeStream(tmpFile, FileMode.Open );
555 			using ( var archive = new ZipArchive( stream, ZipArchiveMode.Create ) ) {
556 				var entry = archive.CreateEntry( "foo" );
557 				using ( var es = entry.Open() ) {
558 					es.Write( new byte[] { 4, 2 }, 0, 2 );
559 				}
560 			}
561 			File.Delete (tmpFile);
562 		}
563 	}
564 }
565