Skip to content

Commit 19b3630

Browse files
author
Meerow
authored
Merge pull request #7 from icarus-consulting/i6-ZipPassword
I6 zip password
2 parents 2799daa + 9e93d06 commit 19b3630

File tree

10 files changed

+346
-1
lines changed

10 files changed

+346
-1
lines changed

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Object oriented tools for handling ZIP archives. Based on [Yaapii.Atoms](https:/
55

66
It follows all the rules suggested in the two "[Elegant Objects](https://www.amazon.de/Elegant-Objects-Yegor-Bugayenko/dp/1519166915)" books.
77

8+
For password protected ZIPs it uses "[DotNetZip](https://github.com/DinoChiesa/DotNetZip)".
9+
810
## Usage
911

1012
There are different Decorator classes which accept a input. A input might come from a file or a memorystream or any other stream. You can use Atoms ```InputOf``` to get an input which you can use with this library.
@@ -85,4 +87,36 @@ var isZip =
8587
new IsZipArchive(
8688
new InputOf(File.OpenRead("c:/some-zip-archive.zip"))
8789
).Value();
90+
```
91+
92+
Test if a file in a zip is protected with a password:
93+
94+
```csharp
95+
var hasPassword =
96+
new HasPassword(
97+
new InputOf(File.OpenRead("c:/some-zip-archive.zip")),
98+
"someFileInTheZip.txt")
99+
).Value();
100+
```
101+
102+
Extract a file with a password in the zip:
103+
104+
```csharp
105+
var extracted =
106+
new ZipPasswordExtracted(
107+
new InputOf(File.OpenRead("c:/some-zip-archive.zip")),
108+
"root/dir/some-file.txt",
109+
"password"
110+
).Stream();
111+
```
112+
113+
Create a file with a password in a zip:
114+
115+
```csharp
116+
var zipArchiveWithPassword =
117+
new ZipWithPassword(
118+
"small.dat",
119+
new InputOf("I feel so compressed"),
120+
"password"
121+
);
88122
```

build.cake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#tool nuget:?package=xunit.runner.console
44
#tool nuget:?package=Codecov
55
#tool nuget:?package=ReportGenerator
6-
#addin nuget:?package=Cake.Codecov
6+
#addin nuget:?package=Cake.Codecov&version=0.5.0
77

88
var target = Argument("target", "Default");
99
var configuration = Argument<string>("configuration", "Release");

src/Yaapii.Zip/HasPassword.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System;
2+
using Yaapii.Atoms;
3+
using Ionic.Zip;
4+
using Yaapii.Atoms.Error;
5+
using System.IO;
6+
7+
namespace Yaapii.Zip
8+
{
9+
/// <summary>
10+
/// Checks if the file in a zip has a password
11+
/// </summary>
12+
public sealed class HasPassword : IScalar<bool>
13+
{
14+
private readonly IInput zip;
15+
private readonly string virtualPath;
16+
17+
/// <summary>
18+
/// Checks if the file in a zip has a password
19+
/// </summary>
20+
public HasPassword(IInput zip, string virtualPath)
21+
{
22+
this.zip = zip;
23+
this.virtualPath = virtualPath;
24+
}
25+
26+
public bool Value()
27+
{
28+
var stream = zip.Stream();
29+
stream.Seek(0, SeekOrigin.Begin);
30+
new FailWhen(
31+
() => !new IsZipArchive(this.zip).Value(),
32+
new ArgumentException(
33+
"Can not extract zip because no zip was provided."
34+
)
35+
).Go();
36+
stream.Seek(0, SeekOrigin.Begin);
37+
bool result;
38+
using (var zip = ZipFile.Read(this.zip.Stream()))
39+
{
40+
result = zip[virtualPath].UsesEncryption;
41+
}
42+
return result;
43+
}
44+
}
45+
}

src/Yaapii.Zip/Yaapii.Zip.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12+
<PackageReference Include="DotNetZip" Version="1.13.3" />
1213
<PackageReference Include="Yaapii.Atoms" Version="0.26.0" />
1314
<PackageReference Include="Yaapii.Xml" Version="0.16.0" />
1415
</ItemGroup>

src/Yaapii.Zip/ZipExtracted.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.IO.Compression;
44
using Yaapii.Atoms;
55
using Yaapii.Atoms.Enumerable;
6+
using Yaapii.Atoms.Error;
67
using Yaapii.Atoms.Scalar;
78

89
namespace Yaapii.Zip
@@ -32,6 +33,12 @@ public Stream Stream()
3233
{
3334
lock (zip)
3435
{
36+
new FailWhen(
37+
() => new HasPassword(this.zip, filePath).Value(),
38+
new InvalidOperationException(
39+
"Can not read content of the zip because the file is password protected"
40+
)
41+
).Go();
3542
Stream content;
3643
using (var archive = new ZipArchive(this.zip.Stream(), ZipArchiveMode.Read, leaveOpen))
3744
{
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using Ionic.Zip;
2+
using System;
3+
using System.IO;
4+
using Yaapii.Atoms;
5+
using Yaapii.Atoms.Error;
6+
using Yaapii.Atoms.IO;
7+
8+
namespace Yaapii.Zip
9+
{
10+
/// <summary>
11+
/// Extracted file in a zip that ís protected with a password
12+
/// </summary>
13+
public sealed class ZipPasswordExtracted : IInput
14+
{
15+
private readonly IInput zip;
16+
private readonly string virtualPath;
17+
private readonly string password;
18+
19+
/// <summary>
20+
/// Extracted file in a zip that ís protected with a password
21+
/// </summary>
22+
public ZipPasswordExtracted(IInput zip, string virtualPath, string password)
23+
{
24+
this.zip = zip;
25+
this.virtualPath = virtualPath;
26+
this.password = password;
27+
}
28+
29+
public Stream Stream()
30+
{
31+
new FailWhen(
32+
() => !new IsZipArchive(this.zip).Value(),
33+
new ArgumentException(
34+
"Can not extract zip because no zip was provided."
35+
)
36+
).Go();
37+
new FailWhen(
38+
() => !new HasPassword(this.zip, this.virtualPath).Value(),
39+
new InvalidOperationException(
40+
"Can not extract zip because the file is not protected with a password."
41+
)
42+
).Go();
43+
zip.Stream().Seek(0, SeekOrigin.Begin);
44+
IInput result;
45+
using (var stream = new MemoryStream())
46+
using (var zip = ZipFile.Read(this.zip.Stream()))
47+
{
48+
try
49+
{
50+
zip[virtualPath].ExtractWithPassword(stream, this.password);
51+
}
52+
catch (BadPasswordException)
53+
{
54+
throw new ArgumentException("Can not extract zip because the provided password is not correct.");
55+
}
56+
result = new InputOf(stream.ToArray());
57+
}
58+
return result.Stream();
59+
}
60+
}
61+
}

src/Yaapii.Zip/ZipWithPassword.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using Ionic.Zip;
2+
using System.IO;
3+
using Yaapii.Atoms;
4+
using Yaapii.Atoms.IO;
5+
using Yaapii.Atoms.Scalar;
6+
7+
namespace Yaapii.Zip
8+
{
9+
/// <summary>
10+
/// A Zip which input is saved with a password
11+
/// </summary>
12+
public sealed class ZipWithPassword : IInput
13+
{
14+
private readonly IScalar<IInput> zip;
15+
16+
/// <summary>
17+
/// A Zip which input is saved with a password
18+
/// </summary>
19+
public ZipWithPassword(string name, string password, IInput origin) : this(
20+
new Sticky<IInput>(() =>
21+
{
22+
IInput output;
23+
using (var stream = new MemoryStream())
24+
using (var zip = new ZipFile())
25+
{
26+
zip.Password = password;
27+
zip.AddEntry(name, origin.Stream());
28+
zip.Save(stream);
29+
output = new InputOf(stream.ToArray());
30+
}
31+
return output;
32+
}))
33+
{ }
34+
35+
private ZipWithPassword(IScalar<IInput> zip)
36+
{
37+
this.zip = zip;
38+
}
39+
40+
public Stream Stream()
41+
{
42+
return zip.Value().Stream();
43+
}
44+
}
45+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using Xunit;
5+
using Yaapii.Atoms.IO;
6+
7+
namespace Yaapii.Zip.Test
8+
{
9+
public class HasPasswordTests
10+
{
11+
[Fact]
12+
public void ReturnsTrueOnPassword()
13+
{
14+
Assert.True(
15+
new HasPassword(
16+
new ZipWithPassword(
17+
"filename.txt",
18+
"aPassword",
19+
new InputOf("a input")
20+
),
21+
"filename.txt"
22+
).Value()
23+
);
24+
}
25+
26+
[Fact]
27+
public void ReturnsFalseOnNoPassword()
28+
{
29+
Assert.False(
30+
new HasPassword(
31+
new Zipped(
32+
"filename.txt",
33+
new InputOf("a input")
34+
),
35+
"filename.txt"
36+
).Value()
37+
);
38+
}
39+
40+
[Fact]
41+
public void ThrowsOnNoZip()
42+
{
43+
Assert.Throws<ArgumentException>(() =>
44+
new HasPassword(new InputOf("test"), "a path").Value()
45+
);
46+
}
47+
}
48+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System;
2+
using Xunit;
3+
using Yaapii.Atoms.IO;
4+
using Yaapii.Atoms.Text;
5+
6+
namespace Yaapii.Zip.Test
7+
{
8+
public class ZipPasswordExtractedTests
9+
{
10+
[Fact]
11+
public void FailsOnWrongPassword()
12+
{
13+
Assert.Throws<ArgumentException>(() =>
14+
new ZipPasswordExtracted(
15+
new ZipWithPassword(
16+
"text.txt",
17+
"password",
18+
new InputOf("safe")
19+
),
20+
"text.txt",
21+
"noPassword"
22+
).Stream()
23+
);
24+
}
25+
26+
[Fact]
27+
public void ReturnsExtractedInput()
28+
{
29+
Assert.Equal(
30+
"safe",
31+
new TextOf(
32+
new ZipPasswordExtracted(
33+
new ZipWithPassword(
34+
"text.txt",
35+
"password",
36+
new InputOf("safe")
37+
),
38+
"text.txt",
39+
"password"
40+
)
41+
).AsString()
42+
);
43+
}
44+
45+
[Fact]
46+
public void FailsOnNoPassword()
47+
{
48+
Assert.Throws<InvalidOperationException>(() =>
49+
new TextOf(
50+
new ZipPasswordExtracted(
51+
new Zipped(
52+
"text.txt",
53+
new InputOf("safe")
54+
),
55+
"text.txt",
56+
"password"
57+
)
58+
).AsString()
59+
);
60+
}
61+
}
62+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using Xunit;
5+
using Yaapii.Atoms.IO;
6+
using Yaapii.Atoms.Scalar;
7+
using Yaapii.Atoms.Text;
8+
9+
namespace Yaapii.Zip.Test
10+
{
11+
public class ZipWithPasswordTests
12+
{
13+
[Fact]
14+
public void HasFileName()
15+
{
16+
var name = new FirstOf<string>(
17+
new Yaapii.Zip.ZipFiles(
18+
new ZipWithPassword(
19+
"input.txt",
20+
"pass",
21+
new InputOf("mechiko")
22+
)
23+
)
24+
).Value();
25+
Assert.Equal("input.txt", name);
26+
}
27+
28+
[Fact]
29+
public void HasPassword()
30+
{
31+
Assert.Throws<InvalidOperationException>(() =>
32+
{
33+
new TextOf(
34+
new ZipExtracted(
35+
new ZipWithPassword("input.txt", "pass", new InputOf("mechiko")),
36+
"input.txt"
37+
)
38+
).AsString();
39+
});
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)