Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
'method_chaining_indentation' => true,
'modernize_strpos' => true,
'modernize_types_casting' => true,
'modifier_keywords' => ['elements' => ['property', 'method']], // not const
'multiline_comment_opening_closing' => true,
'multiline_whitespace_before_semicolons' => true,
'native_constant_invocation' => false, // Micro optimization that look messy
Expand Down Expand Up @@ -236,7 +237,6 @@
'types_spaces' => true,
'unary_operator_spaces' => true,
'use_arrow_functions' => true,
'visibility_required' => ['elements' => ['property', 'method']], // not const
'void_return' => true,
'whitespace_after_comma_in_array' => true,
'yoda_style' => false,
Expand Down
141 changes: 129 additions & 12 deletions src/PhpSpreadsheet/Document/Security.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,21 @@ class Security
*/
private string $workbookPassword = '';

/**
* Create a new Document Security instance.
*/
public function __construct()
{
}
private string $workbookAlgorithmName = '';

private string $workbookHashValue = '';

private string $workbookSaltValue = '';

private int $workbookSpinCount = 0;

private string $revisionsAlgorithmName = '';

private string $revisionsHashValue = '';

private string $revisionsSaltValue = '';

private int $revisionsSpinCount = 0;

/**
* Is some sort of document security enabled?
Expand Down Expand Up @@ -105,10 +114,18 @@ public function getRevisionsPassword(): string
public function setRevisionsPassword(?string $password, bool $alreadyHashed = false): static
{
if ($password !== null) {
if (!$alreadyHashed) {
$password = PasswordHasher::hashPassword($password);
if ($this->advancedRevisionsPassword()) {
if (!$alreadyHashed) {
$password = PasswordHasher::hashPassword($password, $this->revisionsAlgorithmName, $this->revisionsSaltValue, $this->revisionsSpinCount);
}
$this->revisionsHashValue = $password;
$this->revisionsPassword = '';
} else {
if (!$alreadyHashed) {
$password = PasswordHasher::hashPassword($password);
}
$this->revisionsPassword = $password;
}
$this->revisionsPassword = $password;
}

return $this;
Expand All @@ -129,12 +146,112 @@ public function getWorkbookPassword(): string
public function setWorkbookPassword(?string $password, bool $alreadyHashed = false): static
{
if ($password !== null) {
if (!$alreadyHashed) {
$password = PasswordHasher::hashPassword($password);
if ($this->advancedPassword()) {
if (!$alreadyHashed) {
$password = PasswordHasher::hashPassword($password, $this->workbookAlgorithmName, $this->workbookSaltValue, $this->workbookSpinCount);
}
$this->workbookHashValue = $password;
$this->workbookPassword = '';
} else {
if (!$alreadyHashed) {
$password = PasswordHasher::hashPassword($password);
}
$this->workbookPassword = $password;
}
$this->workbookPassword = $password;
}

return $this;
}

public function getWorkbookHashValue(): string
{
return $this->advancedPassword() ? $this->workbookHashValue : '';
}

public function advancedPassword(): bool
{
return $this->workbookAlgorithmName !== '' && $this->workbookSaltValue !== '' && $this->workbookSpinCount > 0;
}

public function getWorkbookAlgorithmName(): string
{
return $this->workbookAlgorithmName;
}

public function setWorkbookAlgorithmName(string $workbookAlgorithmName): static
{
$this->workbookAlgorithmName = $workbookAlgorithmName;

return $this;
}

public function getWorkbookSpinCount(): int
{
return $this->workbookSpinCount;
}

public function setWorkbookSpinCount(int $workbookSpinCount): static
{
$this->workbookSpinCount = $workbookSpinCount;

return $this;
}

public function getWorkbookSaltValue(): string
{
return $this->workbookSaltValue;
}

public function setWorkbookSaltValue(string $workbookSaltValue, bool $base64Required): static
{
$this->workbookSaltValue = $base64Required ? base64_encode($workbookSaltValue) : $workbookSaltValue;

return $this;
}

public function getRevisionsHashValue(): string
{
return $this->advancedRevisionsPassword() ? $this->revisionsHashValue : '';
}

public function advancedRevisionsPassword(): bool
{
return $this->revisionsAlgorithmName !== '' && $this->revisionsSaltValue !== '' && $this->revisionsSpinCount > 0;
}

public function getRevisionsAlgorithmName(): string
{
return $this->revisionsAlgorithmName;
}

public function setRevisionsAlgorithmName(string $revisionsAlgorithmName): static
{
$this->revisionsAlgorithmName = $revisionsAlgorithmName;

return $this;
}

public function getRevisionsSpinCount(): int
{
return $this->revisionsSpinCount;
}

public function setRevisionsSpinCount(int $revisionsSpinCount): static
{
$this->revisionsSpinCount = $revisionsSpinCount;

return $this;
}

public function getRevisionsSaltValue(): string
{
return $this->revisionsSaltValue;
}

public function setRevisionsSaltValue(string $revisionsSaltValue, bool $base64Required): static
{
$this->revisionsSaltValue = $base64Required ? base64_encode($revisionsSaltValue) : $revisionsSaltValue;

return $this;
}
}
66 changes: 61 additions & 5 deletions src/PhpSpreadsheet/Reader/Xlsx.php
Original file line number Diff line number Diff line change
Expand Up @@ -2201,23 +2201,79 @@ private function readProtection(Spreadsheet $excel, SimpleXMLElement $xmlWorkboo
return;
}

$excel->getSecurity()->setLockRevision(self::getLockValue($xmlWorkbook->workbookProtection, 'lockRevision'));
$excel->getSecurity()->setLockStructure(self::getLockValue($xmlWorkbook->workbookProtection, 'lockStructure'));
$excel->getSecurity()->setLockWindows(self::getLockValue($xmlWorkbook->workbookProtection, 'lockWindows'));
$security = $excel->getSecurity();
$security->setLockRevision(
self::getLockValue($xmlWorkbook->workbookProtection, 'lockRevision')
);
$security->setLockStructure(
self::getLockValue($xmlWorkbook->workbookProtection, 'lockStructure')
);
$security->setLockWindows(
self::getLockValue($xmlWorkbook->workbookProtection, 'lockWindows')
);

if ($xmlWorkbook->workbookProtection['revisionsPassword']) {
$excel->getSecurity()->setRevisionsPassword(
$security->setRevisionsPassword(
(string) $xmlWorkbook->workbookProtection['revisionsPassword'],
true
);
}
if ($xmlWorkbook->workbookProtection['revisionsAlgorithmName']) {
$security->setRevisionsAlgorithmName(
(string) $xmlWorkbook->workbookProtection['revisionsAlgorithmName']
);
}
if ($xmlWorkbook->workbookProtection['revisionsSaltValue']) {
$security->setRevisionsSaltValue(
(string) $xmlWorkbook->workbookProtection['revisionsSaltValue'],
false
);
}
if ($xmlWorkbook->workbookProtection['revisionsSpinCount']) {
$security->setRevisionsSpinCount(
(int) $xmlWorkbook->workbookProtection['revisionsSpinCount']
);
}
if ($xmlWorkbook->workbookProtection['revisionsHashValue']) {
if ($security->advancedRevisionsPassword()) {
$security->setRevisionsPassword(
(string) $xmlWorkbook->workbookProtection['revisionsHashValue'],
true
);
}
}

if ($xmlWorkbook->workbookProtection['workbookPassword']) {
$excel->getSecurity()->setWorkbookPassword(
$security->setWorkbookPassword(
(string) $xmlWorkbook->workbookProtection['workbookPassword'],
true
);
}

if ($xmlWorkbook->workbookProtection['workbookAlgorithmName']) {
$security->setWorkbookAlgorithmName(
(string) $xmlWorkbook->workbookProtection['workbookAlgorithmName']
);
}
if ($xmlWorkbook->workbookProtection['workbookSaltValue']) {
$security->setWorkbookSaltValue(
(string) $xmlWorkbook->workbookProtection['workbookSaltValue'],
false
);
}
if ($xmlWorkbook->workbookProtection['workbookSpinCount']) {
$security->setWorkbookSpinCount(
(int) $xmlWorkbook->workbookProtection['workbookSpinCount']
);
}
if ($xmlWorkbook->workbookProtection['workbookHashValue']) {
if ($security->advancedPassword()) {
$security->setWorkbookPassword(
(string) $xmlWorkbook->workbookProtection['workbookHashValue'],
true
);
}
}
}

private static function getLockValue(SimpleXMLElement $protection, string $key): ?bool
Expand Down
2 changes: 1 addition & 1 deletion src/PhpSpreadsheet/Shared/PasswordHasher.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ private static function defaultHashPassword(string $password): string
*
* @param string $password Password to hash
* @param string $algorithm Hash algorithm used to compute the password hash value
* @param string $salt Pseudorandom string
* @param string $salt Pseudorandom base64-encoded string
* @param int $spinCount Number of times to iterate on a hash of a password
*
* @return string Hashed password
Expand Down
35 changes: 26 additions & 9 deletions src/PhpSpreadsheet/Writer/Xlsx/Workbook.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,18 +125,35 @@ private function writeBookViews(XMLWriter $objWriter, Spreadsheet $spreadsheet):
*/
private function writeWorkbookProtection(XMLWriter $objWriter, Spreadsheet $spreadsheet): void
{
if ($spreadsheet->getSecurity()->isSecurityEnabled()) {
$security = $spreadsheet->getSecurity();
if ($security->isSecurityEnabled()) {
$objWriter->startElement('workbookProtection');
$objWriter->writeAttribute('lockRevision', ($spreadsheet->getSecurity()->getLockRevision() ? 'true' : 'false'));
$objWriter->writeAttribute('lockStructure', ($spreadsheet->getSecurity()->getLockStructure() ? 'true' : 'false'));
$objWriter->writeAttribute('lockWindows', ($spreadsheet->getSecurity()->getLockWindows() ? 'true' : 'false'));

if ($spreadsheet->getSecurity()->getRevisionsPassword() != '') {
$objWriter->writeAttribute('revisionsPassword', $spreadsheet->getSecurity()->getRevisionsPassword());
$objWriter->writeAttribute('lockRevision', ($security->getLockRevision() ? 'true' : 'false'));
$objWriter->writeAttribute('lockStructure', ($security->getLockStructure() ? 'true' : 'false'));
$objWriter->writeAttribute('lockWindows', ($security->getLockWindows() ? 'true' : 'false'));

if ($security->getRevisionsPassword() !== '') {
$objWriter->writeAttribute('revisionsPassword', $security->getRevisionsPassword());
} else {
$hashValue = $security->getRevisionsHashValue();
if ($hashValue !== '') {
$objWriter->writeAttribute('revisionsAlgorithmName', $security->getRevisionsAlgorithmName());
$objWriter->writeAttribute('revisionsHashValue', $hashValue);
$objWriter->writeAttribute('revisionsSaltValue', $security->getRevisionsSaltValue());
$objWriter->writeAttribute('revisionsSpinCount', (string) $security->getRevisionsSpinCount());
}
}

if ($spreadsheet->getSecurity()->getWorkbookPassword() != '') {
$objWriter->writeAttribute('workbookPassword', $spreadsheet->getSecurity()->getWorkbookPassword());
if ($security->getWorkbookPassword() !== '') {
$objWriter->writeAttribute('workbookPassword', $security->getWorkbookPassword());
} else {
$hashValue = $security->getWorkbookHashValue();
if ($hashValue !== '') {
$objWriter->writeAttribute('workbookAlgorithmName', $security->getWorkbookAlgorithmName());
$objWriter->writeAttribute('workbookHashValue', $hashValue);
$objWriter->writeAttribute('workbookSaltValue', $security->getWorkbookSaltValue());
$objWriter->writeAttribute('workbookSpinCount', (string) $security->getWorkbookSpinCount());
}
}

$objWriter->endElement();
Expand Down
Loading