#483. Output escaping for RTF.

This commit is contained in:
Roman Syroeshko 2016-07-08 22:56:59 +04:00
parent 649da97a42
commit 3f1e0ac4a7
11 changed files with 172 additions and 31 deletions

View File

@ -13,6 +13,7 @@ Place announcement text here.
- Introduced writer for the "Paragraph Alignment" element (see `\PhpOffice\PhpWord\Writer\Word2007\Element\ParagraphAlignment`). - @RomanSyroeshko - Introduced writer for the "Paragraph Alignment" element (see `\PhpOffice\PhpWord\Writer\Word2007\Element\ParagraphAlignment`). - @RomanSyroeshko
- Introduced writer for the "Table Alignment" element (see `\PhpOffice\PhpWord\Writer\Word2007\Element\TableAlignment`). - @RomanSyroeshko - Introduced writer for the "Table Alignment" element (see `\PhpOffice\PhpWord\Writer\Word2007\Element\TableAlignment`). - @RomanSyroeshko
- Supported indexed arrays in arguments of `TemplateProcessor::setValue()`. - @RomanSyroeshko #618 - Supported indexed arrays in arguments of `TemplateProcessor::setValue()`. - @RomanSyroeshko #618
- Introduced automatic output escaping for OOXML, ODF, HTML, and RTF. To turn the feature on use `phpword.ini` or `\PhpOffice\PhpWord\Settings`. - @RomanSyroeshko #483
### Changed ### Changed
- Improved error message for the case when `autoload.php` is not found. - @RomanSyroeshko #371 - Improved error message for the case when `autoload.php` is not found. - @RomanSyroeshko #371

View File

@ -25,22 +25,22 @@ namespace PhpOffice\PhpWord\Escaper;
abstract class AbstractEscaper implements EscaperInterface abstract class AbstractEscaper implements EscaperInterface
{ {
/** /**
* @param string $subject * @param string $input
* *
* @return string * @return string
*/ */
abstract protected function escapeSingleValue($subject); abstract protected function escapeSingleValue($input);
public function escape($subject) public function escape($input)
{ {
if (is_array($subject)) { if (is_array($input)) {
foreach ($subject as &$item) { foreach ($input as &$item) {
$item = $this->escapeSingleValue($item); $item = $this->escapeSingleValue($item);
} }
} else { } else {
$subject = $this->escapeSingleValue($subject); $input = $this->escapeSingleValue($input);
} }
return $subject; return $input;
} }
} }

View File

@ -25,9 +25,9 @@ namespace PhpOffice\PhpWord\Escaper;
interface EscaperInterface interface EscaperInterface
{ {
/** /**
* @param mixed $subject * @param mixed $input
* *
* @return mixed * @return mixed
*/ */
public function escape($subject); public function escape($input);
} }

View File

@ -26,8 +26,8 @@ class RegExp extends AbstractEscaper
{ {
const REG_EXP_DELIMITER = '/'; const REG_EXP_DELIMITER = '/';
protected function escapeSingleValue($subject) protected function escapeSingleValue($input)
{ {
return self::REG_EXP_DELIMITER . preg_quote($subject, self::REG_EXP_DELIMITER) . self::REG_EXP_DELIMITER . 'u'; return self::REG_EXP_DELIMITER . preg_quote($input, self::REG_EXP_DELIMITER) . self::REG_EXP_DELIMITER . 'u';
} }
} }

View File

@ -0,0 +1,88 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @link https://github.com/PHPOffice/PHPWord
* @copyright 2010-2016 PHPWord contributors
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Escaper;
/**
* @since 0.13.0
*
* @codeCoverageIgnore
*/
class Rtf extends AbstractEscaper
{
protected function escapeAsciiCharacter($code) {
if (20 > $code || $code >= 80) {
return '{\u' . $code . '}';
} else {
return chr($code);
}
}
protected function escapeMultibyteCharacter($code) {
return '\uc0{\u' . $code . '}';
}
/**
* @see http://www.randomchaos.com/documents/?source=php_and_unicode
*/
protected function escapeSingleValue($input)
{
$escapedValue = '';
$numberOfBytes = 1;
$bytes = array();
for ($i = 0; $i < strlen($input); ++$i) {
$character = $input[$i];
$asciiCode = ord($character);
if ($asciiCode < 128) {
$escapedValue .= $this->escapeAsciiCharacter($asciiCode);
} else {
if (0 == count($bytes)) {
if ($asciiCode < 224) {
$numberOfBytes = 2;
} else if ($asciiCode < 240) {
$numberOfBytes = 3;
} else if ($asciiCode < 248) {
$numberOfBytes = 4;
}
}
$bytes[] = $asciiCode;
if ($numberOfBytes == count($bytes)) {
if (4 == $numberOfBytes) {
$multibyteCode = ($bytes[0] % 8) * 262144 + ($bytes[1] % 64) * 4096 + ($bytes[2] % 64) * 64 + ($bytes[3] % 64);
} elseif (3 == $numberOfBytes) {
$multibyteCode = ($bytes[0] % 16) * 4096 + ($bytes[1] % 64) * 64 + ($bytes[2] % 64);
} else {
$multibyteCode = ($bytes[0] % 32) * 64 + ($bytes[1] % 64);
}
if (65279 != $multibyteCode) {
$escapedValue .= $multibyteCode < 128 ? $this->escapeAsciiCharacter($multibyteCode) : $this->escapeMultibyteCharacter($multibyteCode);
}
$numberOfBytes = 1;
$bytes = array();
}
}
}
return $escapedValue;
}
}

View File

@ -24,9 +24,9 @@ namespace PhpOffice\PhpWord\Escaper;
*/ */
class Xml extends AbstractEscaper class Xml extends AbstractEscaper
{ {
protected function escapeSingleValue($subject) protected function escapeSingleValue($input)
{ {
// todo: omit encoding parameter after migration onto PHP 5.4 // todo: omit encoding parameter after migration onto PHP 5.4
return htmlspecialchars($subject, ENT_QUOTES, 'UTF-8'); return htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
} }
} }

View File

@ -22,15 +22,11 @@ use PhpOffice\PhpWord\Writer\AbstractWriter;
use Zend\Escaper\Escaper; use Zend\Escaper\Escaper;
/** /**
* Abstract HTML part writer
*
* @since 0.11.0 * @since 0.11.0
*/ */
abstract class AbstractPart abstract class AbstractPart
{ {
/** /**
* Parent writer
*
* @var \PhpOffice\PhpWord\Writer\AbstractWriter * @var \PhpOffice\PhpWord\Writer\AbstractWriter
*/ */
private $parentWriter; private $parentWriter;
@ -46,16 +42,13 @@ abstract class AbstractPart
} }
/** /**
* Write part
*
* @return string * @return string
*/ */
abstract public function write(); abstract public function write();
/** /**
* Set parent writer.
*
* @param \PhpOffice\PhpWord\Writer\AbstractWriter $writer * @param \PhpOffice\PhpWord\Writer\AbstractWriter $writer
*
* @return void * @return void
*/ */
public function setParentWriter(AbstractWriter $writer = null) public function setParentWriter(AbstractWriter $writer = null)
@ -64,8 +57,6 @@ abstract class AbstractPart
} }
/** /**
* Get parent writer
*
* @return \PhpOffice\PhpWord\Writer\AbstractWriter * @return \PhpOffice\PhpWord\Writer\AbstractWriter
* *
* @throws \PhpOffice\PhpWord\Exception\Exception * @throws \PhpOffice\PhpWord\Exception\Exception

View File

@ -18,9 +18,13 @@
namespace PhpOffice\PhpWord\Writer\RTF\Element; namespace PhpOffice\PhpWord\Writer\RTF\Element;
use PhpOffice\Common\Text as CommonText; use PhpOffice\Common\Text as CommonText;
use PhpOffice\PhpWord\Element\AbstractElement as Element;
use PhpOffice\PhpWord\Escaper\Rtf;
use PhpOffice\PhpWord\Settings;
use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style;
use PhpOffice\PhpWord\Style\Font as FontStyle; use PhpOffice\PhpWord\Style\Font as FontStyle;
use PhpOffice\PhpWord\Style\Paragraph as ParagraphStyle; use PhpOffice\PhpWord\Style\Paragraph as ParagraphStyle;
use PhpOffice\PhpWord\Writer\AbstractWriter;
use PhpOffice\PhpWord\Writer\HTML\Element\AbstractElement as HTMLAbstractElement; use PhpOffice\PhpWord\Writer\HTML\Element\AbstractElement as HTMLAbstractElement;
use PhpOffice\PhpWord\Writer\RTF\Style\Font as FontStyleWriter; use PhpOffice\PhpWord\Writer\RTF\Style\Font as FontStyleWriter;
use PhpOffice\PhpWord\Writer\RTF\Style\Paragraph as ParagraphStyleWriter; use PhpOffice\PhpWord\Writer\RTF\Style\Paragraph as ParagraphStyleWriter;
@ -46,6 +50,13 @@ abstract class AbstractElement extends HTMLAbstractElement
*/ */
private $paragraphStyle; private $paragraphStyle;
public function __construct(AbstractWriter $parentWriter, Element $element, $withoutP)
{
parent::__construct($parentWriter, $element, $withoutP);
$this->escaper = new Rtf();
}
/** /**
* Get font and paragraph styles. * Get font and paragraph styles.
* *
@ -112,8 +123,12 @@ abstract class AbstractElement extends HTMLAbstractElement
*/ */
protected function writeText($text) protected function writeText($text)
{ {
if (Settings::isOutputEscapingEnabled()) {
return $this->escaper->escape($text);
} else {
return CommonText::toUnicode($text); return CommonText::toUnicode($text);
} }
}
/** /**
* Write closing * Write closing

View File

@ -17,13 +17,56 @@
namespace PhpOffice\PhpWord\Writer\RTF\Part; namespace PhpOffice\PhpWord\Writer\RTF\Part;
use PhpOffice\PhpWord\Writer\HTML\Part\AbstractPart as HTMLAbstractPart; use PhpOffice\PhpWord\Escaper\Rtf;
use PhpOffice\PhpWord\Exception\Exception;
use PhpOffice\PhpWord\Writer\AbstractWriter;
/** /**
* Abstract RTF part writer
*
* @since 0.11.0 * @since 0.11.0
*/ */
abstract class AbstractPart extends HTMLAbstractPart abstract class AbstractPart
{ {
/**
* @var \PhpOffice\PhpWord\Writer\AbstractWriter
*/
private $parentWriter;
/**
* @var \PhpOffice\PhpWord\Escaper\EscaperInterface
*/
protected $escaper;
public function __construct()
{
$this->escaper = new Rtf();
}
/**
* @return string
*/
abstract public function write();
/**
* @param \PhpOffice\PhpWord\Writer\AbstractWriter $writer
*
* @return void
*/
public function setParentWriter(AbstractWriter $writer = null)
{
$this->parentWriter = $writer;
}
/**
* @return \PhpOffice\PhpWord\Writer\AbstractWriter
*
* @throws \PhpOffice\PhpWord\Exception\Exception
*/
public function getParentWriter()
{
if ($this->parentWriter !== null) {
return $this->parentWriter;
} else {
throw new Exception('No parent WriterInterface assigned.');
}
}
} }

View File

@ -65,7 +65,11 @@ class Document extends AbstractPart
$content .= '\info'; $content .= '\info';
foreach ($properties as $property) { foreach ($properties as $property) {
$method = 'get' . (isset($mapping[$property]) ? $mapping[$property] : $property); $method = 'get' . (isset($mapping[$property]) ? $mapping[$property] : $property);
if (!in_array($property, $dateFields) && Settings::isOutputEscapingEnabled()) {
$value = $this->escaper->escape($docProps->$method());
} else {
$value = $docProps->$method(); $value = $docProps->$method();
}
$value = in_array($property, $dateFields) ? $this->getDateValue($value) : $value; $value = in_array($property, $dateFields) ? $this->getDateValue($value) : $value;
$content .= "{\\{$property} {$value}}"; $content .= "{\\{$property} {$value}}";
} }
@ -105,7 +109,6 @@ class Document extends AbstractPart
*/ */
private function writeSections() private function writeSections()
{ {
$content = ''; $content = '';
$sections = $this->getParentWriter()->getPhpWord()->getSections(); $sections = $this->getParentWriter()->getPhpWord()->getSections();

View File

@ -173,7 +173,7 @@ class Header extends AbstractPart
{ {
$content = ''; $content = '';
$content .= '{\*\generator PhpWord;}'; // Set the generator $content .= '{\*\generator PHPWord;}'; // Set the generator
$content .= PHP_EOL; $content .= PHP_EOL;
return $content; return $content;