diff --git a/ClosedXML/Excel/XLWorkbook.cs b/ClosedXML/Excel/XLWorkbook.cs
index e946369..9e86a67 100644
--- a/ClosedXML/Excel/XLWorkbook.cs
+++ b/ClosedXML/Excel/XLWorkbook.cs
@@ -1,14 +1,14 @@
+using ClosedXML.Excel.CalcEngine;
+using ClosedXML.Extensions;
+using DocumentFormat.OpenXml;
using System;
using System.Collections.Generic;
+using System.Data;
using System.IO;
-using System.Security.AccessControl;
-using ClosedXML.Excel.CalcEngine;
-using DocumentFormat.OpenXml;
+using System.Linq;
namespace ClosedXML.Excel
{
- using System.Linq;
- using System.Data;
public enum XLEventTracking { Enabled, Disabled }
public enum XLCalculateMode
@@ -864,12 +864,49 @@
public XLWorkbook SetLockStructure(Boolean value) { LockStructure = value; return this; }
public Boolean LockWindows { get; set; }
public XLWorkbook SetLockWindows(Boolean value) { LockWindows = value; return this; }
+ internal HexBinaryValue LockPassword { get; set; }
+ public Boolean IsPasswordProtected { get { return LockPassword != null; } }
+
+ public void Protect(Boolean lockStructure, Boolean lockWindows, String workbookPassword)
+ {
+ if (IsPasswordProtected && workbookPassword == null)
+ throw new InvalidOperationException("The workbook is password protected");
+ var hashPassword = workbookPassword.HashPassword();
+ if (IsPasswordProtected && LockPassword != hashPassword)
+ throw new ArgumentException("Invalid password");
+
+ if (IsPasswordProtected && (lockStructure || lockWindows))
+ throw new InvalidOperationException("The workbook is already protected");
+
+ if (IsPasswordProtected && hashPassword != null && !lockStructure && !lockWindows)
+ {
+ // Workbook currently protected, but we're unsetting the 2 flags
+ // Hence unprotect workbook using password.
+ LockPassword = null;
+ }
+
+
+ if (!IsPasswordProtected && hashPassword != null && (lockStructure || lockWindows))
+ {
+ //Protect workbook using password.
+ LockPassword = hashPassword;
+ }
+
+ LockStructure = lockStructure;
+ LockWindows = lockWindows;
+ }
+
public void Protect()
{
Protect(true);
}
+ public void Protect(string workbookPassword)
+ {
+ Protect(true, false, workbookPassword);
+ }
+
public void Protect(Boolean lockStructure)
{
Protect(lockStructure, false);
@@ -877,8 +914,17 @@
public void Protect(Boolean lockStructure, Boolean lockWindows)
{
- LockStructure = lockStructure;
- LockWindows = LockWindows;
+ Protect(lockStructure, lockWindows, null);
+ }
+
+ public void Unprotect()
+ {
+ Protect(false, false);
+ }
+
+ public void Unprotect(string workbookPassword)
+ {
+ Protect(false, false, workbookPassword);
}
}
-}
+}
\ No newline at end of file
diff --git a/ClosedXML/Excel/XLWorkbook_Load.cs b/ClosedXML/Excel/XLWorkbook_Load.cs
index cf84f81..a23ab4e 100644
--- a/ClosedXML/Excel/XLWorkbook_Load.cs
+++ b/ClosedXML/Excel/XLWorkbook_Load.cs
@@ -97,6 +97,8 @@
LockStructure = wbProtection.LockStructure.Value;
if (wbProtection.LockWindows != null)
LockWindows = wbProtection.LockWindows.Value;
+ if (wbProtection.WorkbookPassword != null)
+ LockPassword = wbProtection.WorkbookPassword.Value;
}
var calculationProperties = dSpreadsheet.WorkbookPart.Workbook.CalculationProperties;
@@ -2397,4 +2399,4 @@
return false;
}
}
-}
+}
\ No newline at end of file
diff --git a/ClosedXML/Excel/XLWorkbook_Save.cs b/ClosedXML/Excel/XLWorkbook_Save.cs
index 20a9088..19ba801 100644
--- a/ClosedXML/Excel/XLWorkbook_Save.cs
+++ b/ClosedXML/Excel/XLWorkbook_Save.cs
@@ -572,6 +572,8 @@
#endregion WorkbookProperties
+ #region WorkbookProtection
+
if (LockStructure || LockWindows)
{
if (workbook.WorkbookProtection == null)
@@ -579,11 +581,16 @@
workbook.WorkbookProtection.LockStructure = LockStructure;
workbook.WorkbookProtection.LockWindows = LockWindows;
+
+ if (LockPassword != null)
+ workbook.WorkbookProtection.WorkbookPassword = LockPassword;
}
else
{
workbook.WorkbookProtection = null;
}
+
+ #endregion
if (workbook.BookViews == null)
workbook.BookViews = new BookViews();
@@ -5038,4 +5045,4 @@
#endregion GenerateWorksheetPartContent
}
-}
+}
\ No newline at end of file
diff --git a/ClosedXML/Extensions/StringExtensions.cs b/ClosedXML/Extensions/StringExtensions.cs
index a3b49ec..facf0a5 100644
--- a/ClosedXML/Extensions/StringExtensions.cs
+++ b/ClosedXML/Extensions/StringExtensions.cs
@@ -8,12 +8,31 @@
{
internal static class StringExtensions
{
- internal static string WrapSheetNameInQuotesIfRequired(this string sheetName)
+ internal static String WrapSheetNameInQuotesIfRequired(this String sheetName)
{
if (sheetName.Contains(' '))
return "'" + sheetName + "'";
else
return sheetName;
}
+
+ internal static String HashPassword(this String password)
+ {
+ if (password == null) return null;
+
+ Int32 pLength = password.Length;
+ Int32 hash = 0;
+ if (pLength == 0) return String.Empty;
+
+ for (Int32 i = pLength - 1; i >= 0; i--)
+ {
+ hash ^= password[i];
+ hash = hash >> 14 & 0x01 | hash << 1 & 0x7fff;
+ }
+ hash ^= 0x8000 | 'N' << 8 | 'K';
+ hash ^= pLength;
+ return hash.ToString("X");
+ }
+
}
}
diff --git a/ClosedXML_Examples/ClosedXML_Examples.csproj b/ClosedXML_Examples/ClosedXML_Examples.csproj
index aee3bcf..9f4871b 100644
--- a/ClosedXML_Examples/ClosedXML_Examples.csproj
+++ b/ClosedXML_Examples/ClosedXML_Examples.csproj
@@ -88,6 +88,7 @@
+
diff --git a/ClosedXML_Examples/Misc/WorkbookProtection.cs b/ClosedXML_Examples/Misc/WorkbookProtection.cs
new file mode 100644
index 0000000..0bd0122
--- /dev/null
+++ b/ClosedXML_Examples/Misc/WorkbookProtection.cs
@@ -0,0 +1,23 @@
+using System;
+using ClosedXML.Excel;
+
+namespace ClosedXML_Examples.Misc
+{
+ public class WorkbookProtection : IXLExample
+ {
+ #region Methods
+
+ // Public
+ public void Create(String filePath)
+ {
+ using (var wb = new XLWorkbook())
+ {
+ var ws = wb.Worksheets.Add("Workbook Protection");
+ wb.Protect(true, false, "Abc@123");
+ wb.SaveAs(filePath);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/ClosedXML_Tests/ClosedXML_Tests.csproj b/ClosedXML_Tests/ClosedXML_Tests.csproj
index 3f12c66..487ea3a 100644
--- a/ClosedXML_Tests/ClosedXML_Tests.csproj
+++ b/ClosedXML_Tests/ClosedXML_Tests.csproj
@@ -281,6 +281,7 @@
+
diff --git a/ClosedXML_Tests/Examples/MiscTests.cs b/ClosedXML_Tests/Examples/MiscTests.cs
index c1f97bf..4f14453 100644
--- a/ClosedXML_Tests/Examples/MiscTests.cs
+++ b/ClosedXML_Tests/Examples/MiscTests.cs
@@ -204,5 +204,11 @@
{
TestHelper.RunTestExample(@"Misc\WorkbookProperties.xlsx");
}
+
+ [Test]
+ public void WorkbookProtection()
+ {
+ TestHelper.RunTestExample(@"Misc\WorkbookProtection.xlsx");
+ }
}
-}
+}
\ No newline at end of file
diff --git a/ClosedXML_Tests/Excel/Misc/XLWorkbookTests.cs b/ClosedXML_Tests/Excel/Misc/XLWorkbookTests.cs
index 48f5225..5a1e4a7 100644
--- a/ClosedXML_Tests/Excel/Misc/XLWorkbookTests.cs
+++ b/ClosedXML_Tests/Excel/Misc/XLWorkbookTests.cs
@@ -255,5 +255,81 @@
Assert.AreEqual("$A$1:$A$1", wsRanges.First().RangeAddress.ToStringFixed());
Assert.AreEqual("$A$3:$A$3", wsRanges.Last().RangeAddress.ToStringFixed());
}
+
+ [Test]
+ public void WbProtect1()
+ {
+ using (var wb = new XLWorkbook())
+ {
+ var ws = wb.Worksheets.Add("Sheet1");
+ wb.Protect();
+ Assert.IsTrue(wb.LockStructure);
+ Assert.IsFalse(wb.LockWindows);
+ Assert.IsFalse(wb.IsPasswordProtected);
+ }
+ }
+
+ [Test]
+ public void WbProtect2()
+ {
+ using (var wb = new XLWorkbook())
+ {
+ var ws = wb.Worksheets.Add("Sheet1");
+ wb.Protect(true, false);
+ Assert.IsTrue(wb.LockStructure);
+ Assert.IsFalse(wb.LockWindows);
+ Assert.IsFalse(wb.IsPasswordProtected);
+ }
+ }
+
+ [Test]
+ public void WbProtect3()
+ {
+ using (var wb = new XLWorkbook())
+ {
+ var ws = wb.Worksheets.Add("Sheet1");
+ wb.Protect("Abc@123");
+ Assert.IsTrue(wb.LockStructure);
+ Assert.IsFalse(wb.LockWindows);
+ Assert.IsTrue(wb.IsPasswordProtected);
+ Assert.Throws(typeof(InvalidOperationException), delegate { wb.Protect(); });
+ Assert.Throws(typeof(InvalidOperationException), delegate { wb.Unprotect(); });
+ Assert.Throws(typeof(ArgumentException), delegate { wb.Unprotect("Cde@345"); });
+ }
+ }
+
+ [Test]
+ public void WbProtect4()
+ {
+ using (var wb = new XLWorkbook())
+ {
+ var ws = wb.Worksheets.Add("Sheet1");
+ wb.Protect();
+ Assert.IsTrue(wb.LockStructure);
+ Assert.IsFalse(wb.LockWindows);
+ Assert.IsFalse(wb.IsPasswordProtected);
+ wb.Protect("Abc@123");
+ Assert.IsTrue(wb.LockStructure);
+ Assert.IsFalse(wb.LockWindows);
+ Assert.IsTrue(wb.IsPasswordProtected);
+ }
+ }
+
+ [Test]
+ public void WbProtect5()
+ {
+ using (var wb = new XLWorkbook())
+ {
+ var ws = wb.Worksheets.Add("Sheet1");
+ wb.Protect(true, false, "Abc@123");
+ Assert.IsTrue(wb.LockStructure);
+ Assert.IsFalse(wb.LockWindows);
+ Assert.IsTrue(wb.IsPasswordProtected);
+ wb.Unprotect("Abc@123");
+ Assert.IsFalse(wb.LockStructure);
+ Assert.IsFalse(wb.LockWindows);
+ Assert.IsFalse(wb.IsPasswordProtected);
+ }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/ClosedXML_Tests/Resource/Examples/Misc/WorkbookProtection.xlsx b/ClosedXML_Tests/Resource/Examples/Misc/WorkbookProtection.xlsx
new file mode 100644
index 0000000..2e7c839
--- /dev/null
+++ b/ClosedXML_Tests/Resource/Examples/Misc/WorkbookProtection.xlsx
Binary files differ