Skip to content

Commit 5c58d32

Browse files
committed
PowerPoint2007 Reader / ODPresentation Reader : Support for protection password
1 parent f16f996 commit 5c58d32

File tree

7 files changed

+544
-43
lines changed

7 files changed

+544
-43
lines changed

docs/changes/1.2.0.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- `phpoffice/phpspreadsheet`: Allow version 3.0 by [@Progi1984](https://github.com/Progi1984) fixing [#836](https://github.com/PHPOffice/PHPPresentation/pull/836) in [#839](https://github.com/PHPOffice/PHPPresentation/pull/839)
1111
- `createAutoShape` : Add method to create geometric shapes by [@mhasanshahid](https://github.com/mhasanshahid) & [@Progi1984](https://github.com/Progi1984) in [#848](https://github.com/PHPOffice/PHPPresentation/pull/848)
1212
- Reader : Option to not load images by [@Progi1984](https://github.com/Progi1984) fixing [#795](https://github.com/PHPOffice/PHPPresentation/pull/795) in [#850](https://github.com/PHPOffice/PHPPresentation/pull/850)
13+
- PowerPoint2007 Reader / ODPresentation Reader : Support for protection password by [@Progi1984](https://github.com/Progi1984) fixing [#119](https://github.com/PHPOffice/PHPPresentation/pull/119) in [#208](https://github.com/PHPOffice/PHPPresentation/pull/208)
1314

1415
## Bug fixes
1516

samples/Sample_23_Password.php

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
include_once 'Sample_Header.php';
4+
5+
use PhpOffice\PhpPresentation\IOFactory;
6+
7+
echo '<h2>ODPresentation</h2>';
8+
$pptReader = IOFactory::createReader('ODPresentation');
9+
$pptReader->setPassword('motdepasse');
10+
$oPHPPresentation = $pptReader->load(__DIR__ . '/resources/SamplePassword.odp');
11+
12+
$oTree = new PhpPptTree($oPHPPresentation);
13+
echo $oTree->display();
14+
15+
echo '<h2>PowerPoint2007</h2>';
16+
$pptReader = IOFactory::createReader('PowerPoint2007');
17+
$pptReader->setPassword('motdepasse');
18+
$oPHPPresentation = $pptReader->load(__DIR__ . '/resources/SamplePassword.pptx');
19+
20+
$oTree = new PhpPptTree($oPHPPresentation);
21+
echo $oTree->display();
22+
23+
if (!CLI) {
24+
include_once 'Sample_Footer.php';
25+
}

samples/Sample_Header.php

+64-33
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,34 @@
1717
use PhpOffice\PhpPresentation\Style\Bullet;
1818
use PhpOffice\PhpPresentation\Style\Color;
1919

20+
function hex_dump($data, $newline = "\n"): void
21+
{
22+
static $from = '';
23+
static $to = '';
24+
25+
static $width = 16; // number of bytes per line
26+
27+
static $pad = '.'; // padding for non-visible characters
28+
29+
if ($from === '') {
30+
for ($i = 0; $i <= 0xFF; ++$i) {
31+
$from .= chr($i);
32+
$to .= ($i >= 0x20 && $i <= 0x7E) ? chr($i) : $pad;
33+
}
34+
}
35+
36+
$hex = str_split(bin2hex($data), $width * 2);
37+
$chars = str_split(strtr($data, $from, $to), $width);
38+
39+
$offset = 0;
40+
echo '<pre>';
41+
foreach ($hex as $i => $line) {
42+
echo sprintf('%6X', $offset) . ' : ' . implode(' ', str_split($line, 2)) . ' [' . $chars[$i] . ']' . $newline;
43+
$offset += $width;
44+
}
45+
echo '</pre>';
46+
}
47+
2048
error_reporting(E_ALL);
2149
define('CLI', (PHP_SAPI == 'cli') ? true : false);
2250
define('EOL', CLI ? PHP_EOL : '<br />');
@@ -195,7 +223,7 @@ function createTemplatedSlide(PhpPresentation $objPHPPresentation): Slide
195223
return $slide;
196224
}
197225

198-
class Sample_Header
226+
class PhpPptTree
199227
{
200228
protected $oPhpPresentation;
201229

@@ -478,42 +506,45 @@ protected function getConstantName($class, $search, $startWith = '')
478506
return $constName;
479507
}
480508
}
509+
481510
?>
482-
<title><?php echo $pageTitle; ?></title>
483-
<meta charset="utf-8">
484-
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
485-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
486-
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css" />
487-
<link rel="stylesheet" href="bootstrap/css/font-awesome.min.css" />
488-
<link rel="stylesheet" href="bootstrap/css/phppresentation.css" />
489-
</head>
490-
<body>
511+
<title><?php echo $pageTitle; ?></title>
512+
<meta charset="utf-8">
513+
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
514+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
515+
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"/>
516+
<link rel="stylesheet" href="bootstrap/css/font-awesome.min.css"/>
517+
<link rel="stylesheet" href="bootstrap/css/phppresentation.css"/>
518+
</head>
519+
<body>
491520
<div class="container">
492-
<div class="navbar navbar-default" role="navigation">
493-
<div class="container-fluid">
494-
<div class="navbar-header">
495-
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
496-
<span class="sr-only">Toggle navigation</span>
497-
<span class="icon-bar"></span>
498-
<span class="icon-bar"></span>
499-
<span class="icon-bar"></span>
500-
</button>
501-
<a class="navbar-brand" href="./">PHPPresentation</a>
502-
</div>
503-
<div class="navbar-collapse collapse">
504-
<ul class="nav navbar-nav">
521+
<div class="navbar navbar-default" role="navigation">
522+
<div class="container-fluid">
523+
<div class="navbar-header">
524+
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
525+
<span class="sr-only">Toggle navigation</span>
526+
<span class="icon-bar"></span>
527+
<span class="icon-bar"></span>
528+
<span class="icon-bar"></span>
529+
</button>
530+
<a class="navbar-brand" href="./">PHPPresentation</a>
531+
</div>
532+
<div class="navbar-collapse collapse">
533+
<ul class="nav navbar-nav">
505534
<?php foreach ($files as $key => $groupfiles) { ?>
506-
<li class="dropdown active">
507-
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-code fa-lg"></i>&nbsp;Samples <?php echo $key; ?>x<strong class="caret"></strong></a>
508-
<ul class="dropdown-menu"><?php echo implode('', $groupfiles); ?></ul>
509-
</li>
535+
<li class="dropdown active">
536+
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-code fa-lg"></i>&nbsp;Samples <?php echo $key?>x<strong
537+
class="caret"></strong></a>
538+
<ul class="dropdown-menu"><?php echo implode('', $groupfiles); ?></ul>
539+
</li>
510540
<?php } ?>
511-
</ul>
512-
<ul class="nav navbar-nav navbar-right">
513-
<li><a href="https://github.com/PHPOffice/PHPPresentation"><i class="fa fa-github fa-lg" title="GitHub"></i>&nbsp;</a></li>
514-
<li><a href="https://phpoffice.github.io/PHPPresentation/"><i class="fa fa-book fa-lg" title="Docs"></i>&nbsp;</a></li>
515-
<li><a href="http://twitter.com/PHPOffice"><i class="fa fa-twitter fa-lg" title="Twitter"></i>&nbsp;</a></li>
516-
</ul>
541+
</ul>
542+
<ul class="nav navbar-nav navbar-right">
543+
<li><a href="https://github.com/PHPOffice/PHPPresentation"><i class="fa fa-github fa-lg" title="GitHub"></i>&nbsp;</a></li>
544+
<li><a href="http://phppresentation.readthedocs.org/en/develop/"><i class="fa fa-book fa-lg" title="Docs"></i>&nbsp;</a></li>
545+
<li><a href="http://twitter.com/PHPOffice"><i class="fa fa-twitter fa-lg" title="Twitter"></i>&nbsp;</a></li>
546+
</ul>
547+
</div>
517548
</div>
518549
</div>
519550
</div>

samples/resources/SamplePassword.odp

13.1 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/**
4+
* This file is part of PHPPresentation - A pure PHP library for reading and writing
5+
* presentations documents.
6+
*
7+
* PHPPresentation is free software distributed under the terms of the GNU Lesser
8+
* General Public License version 3 as published by the Free Software Foundation.
9+
*
10+
* For the full copyright and license information, please read the LICENSE
11+
* file that was distributed with this source code. For the full list of
12+
* contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors.
13+
*
14+
* @see https://github.com/PHPOffice/PHPPresentation
15+
*
16+
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
17+
*/
18+
19+
namespace PhpOffice\PhpPresentation\Reader;
20+
21+
abstract class AbstractReader
22+
{
23+
/**
24+
* @var string
25+
*/
26+
protected $password;
27+
28+
/**
29+
* @return string
30+
*/
31+
public function getPassword()
32+
{
33+
return $this->password;
34+
}
35+
36+
/**
37+
* @param string $password
38+
*
39+
* @return AbstractReader
40+
*/
41+
public function setPassword($password)
42+
{
43+
$this->password = $password;
44+
45+
return $this;
46+
}
47+
}

src/PhpPresentation/Reader/ODPresentation.php

+118-8
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
/**
4747
* Serialized format reader.
4848
*/
49-
class ODPresentation implements ReaderInterface
49+
class ODPresentation extends AbstractReader implements ReaderInterface
5050
{
5151
/**
5252
* Output Object.
@@ -62,6 +62,11 @@ class ODPresentation implements ReaderInterface
6262
*/
6363
protected $oZip;
6464

65+
/**
66+
* @var string
67+
*/
68+
protected $filename;
69+
6570
/**
6671
* @var array<string, array{alignment: null|Alignment, background: null, shadow: null|Shadow, fill: null|Fill, spacingAfter: null|int, spacingBefore: null|int, lineSpacingMode: null, lineSpacing: null, font: null, listStyle: null}>
6772
*/
@@ -77,6 +82,11 @@ class ODPresentation implements ReaderInterface
7782
*/
7883
protected $oXMLReader;
7984

85+
/**
86+
* @var XMLReader
87+
*/
88+
protected $oXMLMetaInfManifest;
89+
8090
/**
8191
* @var int
8292
*/
@@ -142,22 +152,21 @@ public function load(string $pFilename, int $flags = 0): PhpPresentation
142152
*/
143153
protected function loadFile($pFilename)
144154
{
155+
$this->filename = $pFilename;
156+
145157
$this->oPhpPresentation = new PhpPresentation();
146158
$this->oPhpPresentation->removeSlideByIndex();
147159

148160
$this->oZip = new ZipArchive();
149-
$this->oZip->open($pFilename);
161+
$this->oZip->open($this->filename);
150162

151-
$this->oXMLReader = new XMLReader();
152-
if (false !== $this->oXMLReader->getDomFromZip($pFilename, 'meta.xml')) {
163+
if ($this->loadFileFromODP('meta.xml') !== false) {
153164
$this->loadDocumentProperties();
154165
}
155-
$this->oXMLReader = new XMLReader();
156-
if (false !== $this->oXMLReader->getDomFromZip($pFilename, 'styles.xml')) {
166+
if ($this->loadFileFromODP('styles.xml') !== false) {
157167
$this->loadStylesFile();
158168
}
159-
$this->oXMLReader = new XMLReader();
160-
if (false !== $this->oXMLReader->getDomFromZip($pFilename, 'content.xml')) {
169+
if ($this->loadFileFromODP('content.xml') !== false) {
161170
$this->loadSlides();
162171
$this->loadPresentationProperties();
163172
}
@@ -762,6 +771,107 @@ protected function loadStylesFile(): void
762771
}
763772
}
764773

774+
/**
775+
* @param string $filename
776+
*
777+
* @return bool
778+
*/
779+
protected function loadFileFromODP($filename)
780+
{
781+
$bEncrypted = false;
782+
783+
if (!$this->oXMLMetaInfManifest) {
784+
$this->oXMLMetaInfManifest = new XMLReader();
785+
if ($this->oXMLMetaInfManifest->getDomFromZip($this->filename, 'META-INF/manifest.xml') === false) {
786+
return false;
787+
}
788+
}
789+
// Search file in META-INF/manifest.xml
790+
$oElement = $this->oXMLMetaInfManifest->getElement('/manifest:manifest/manifest:file-entry[@manifest:full-path=\'' . $filename . '\']');
791+
if (!$oElement) {
792+
return false;
793+
}
794+
// Has it some manifest:encryption-data ?
795+
$oElementEncryption = $this->oXMLMetaInfManifest->getElement('manifest:encryption-data', $oElement);
796+
if ($oElementEncryption) {
797+
$bEncrypted = true;
798+
}
799+
800+
$fileContent = $this->oZip->getFromName($filename);
801+
if (!$fileContent) {
802+
return false;
803+
}
804+
805+
// No Encrypted file
806+
if (!$bEncrypted) {
807+
$this->oXMLReader = new XMLReader();
808+
$this->oXMLReader->getDomFromString($fileContent);
809+
810+
return true;
811+
}
812+
var_dump($filename);
813+
814+
//return false;
815+
/*
816+
<manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml" manifest:size="2090">
817+
<manifest:encryption-data
818+
manifest:checksum-type="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0#sha256-1k"
819+
manifest:checksum="BfB+taOY0kcVO/9WNi4DfqioRp3LMwVoNbqfAQ37yac=">
820+
<manifest:algorithm
821+
manifest:algorithm-name="http://www.w3.org/2001/04/xmlenc#aes256-cbc"
822+
manifest:initialisation-vector="I7rMXmvuynJFxJtm+EQ5qA=="/>
823+
<manifest:key-derivation
824+
manifest:key-derivation-name="PBKDF2"
825+
manifest:key-size="32"
826+
manifest:iteration-count="1024"
827+
manifest:salt="Mows9XX/YiNKNJ0qll3jgA=="/>
828+
<manifest:start-key-generation
829+
manifest:start-key-generation-name="http://www.w3.org/2000/09/xmldsig#sha256"
830+
manifest:key-size="32"/>
831+
</manifest:encryption-data>
832+
</manifest:file-entry>
833+
*/
834+
// Encrypted file
835+
$checksum = $oElementEncryption->getAttribute('manifest:checksum');
836+
837+
$oEltKeyDerivation = $this->oXMLMetaInfManifest->getElement('manifest:key-derivation', $oElementEncryption);
838+
$salt = $oEltKeyDerivation->getAttribute('manifest:salt');
839+
//$salt = base64_decode($salt);
840+
echo 'manifest:salt : ' . $salt . PHP_EOL;
841+
$iterationCount = (int) $oEltKeyDerivation->getAttribute('manifest:iteration-count');
842+
echo 'manifest:iteration-count : ' . $iterationCount . PHP_EOL;
843+
$keySize = (int) $oEltKeyDerivation->getAttribute('manifest:key-size') ?? 16;
844+
echo 'manifest:key-size : ' . $keySize . PHP_EOL;
845+
846+
$oEltAlgorithm = $this->oXMLMetaInfManifest->getElement('manifest:algorithm', $oElementEncryption);
847+
$iv = $oEltAlgorithm->getAttribute('manifest:initialisation-vector');
848+
$iv = base64_decode($iv);
849+
echo 'manifest:initialisation-vector : ' . $iv . PHP_EOL;
850+
851+
// manifest:start-key-generation-name == sha256 sinon sha1
852+
$pwdHash = hash('sha256', $this->getPassword());
853+
echo 'sha256(' . $this->getPassword() . '): ' . $pwdHash . PHP_EOL;
854+
//$pwdHash = substr($pwdHash, 0 , 32);
855+
//var_dump($pwdHash);
856+
857+
// ifmanifest:key-derivation-name="PBKDF2" THEN PBKDF2WithHmacSHA1 SINON ?
858+
$key = hash_pbkdf2('sha1', $pwdHash, $salt, $iterationCount, $keySize, true);
859+
echo 'hash_pbkdf2 (sha1, hash, salt, iterationCount, $iterationCount) : ' . $key . PHP_EOL;
860+
861+
$data = openssl_decrypt($fileContent, 'AES-256-CBC', $key, 0, $iv);
862+
if (!$data) {
863+
while ($msg = openssl_error_string()) {
864+
var_dump($msg);
865+
}
866+
die();
867+
}
868+
var_dump($data);
869+
$data = gzinflate($data);
870+
var_dump($data);
871+
872+
return false;
873+
}
874+
765875
private function getExpressionUnit(string $expr): string
766876
{
767877
if (substr($expr, -1) == '%') {

0 commit comments

Comments
 (0)