Skip to content

Commit ed21854

Browse files
rdarcy1PowerKiKi
authored andcommitted
Throw exception for invalid range to prevent infinite loop
Fixes #519 Closes #521
1 parent 4bc3ee3 commit ed21854

File tree

3 files changed

+119
-40
lines changed

3 files changed

+119
-40
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2121
- Xlsx loaded an extra empty comment for each real comment - [#375](https://github.com/PHPOffice/PhpSpreadsheet/issues/375)
2222
- Xlsx reader do not read rows and columns filtered out in readFilter at all - [#370](https://github.com/PHPOffice/PhpSpreadsheet/issues/370)
2323
- Make newer Excel versions properly recalculate formulas on document open - [#456](https://github.com/PHPOffice/PhpSpreadsheet/issues/456)
24+
- `Coordinate::extractAllCellReferencesInRange()` throws an exception for an invalid range – [#519](https://github.com/PHPOffice/PhpSpreadsheet/issues/519)
2425

2526
## [1.2.1] - 2018-04-10
2627

src/PhpSpreadsheet/Cell/Coordinate.php

Lines changed: 100 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -327,57 +327,20 @@ public static function stringFromColumnIndex($columnIndex)
327327
}
328328

329329
/**
330-
* Extract all cell references in range.
330+
* Extract all cell references in range, which may be comprised of multiple cell ranges.
331331
*
332332
* @param string $pRange Range (e.g. A1 or A1:C10 or A1:E10 A20:E25)
333333
*
334334
* @return array Array containing single cell references
335335
*/
336336
public static function extractAllCellReferencesInRange($pRange)
337337
{
338-
// Returnvalue
339338
$returnValue = [];
340339

341340
// Explode spaces
342-
$cellBlocks = explode(' ', str_replace('$', '', strtoupper($pRange)));
341+
$cellBlocks = self::getCellBlocksFromRangeString($pRange);
343342
foreach ($cellBlocks as $cellBlock) {
344-
// Single cell?
345-
if (!self::coordinateIsRange($cellBlock)) {
346-
$returnValue[] = $cellBlock;
347-
348-
continue;
349-
}
350-
351-
// Range...
352-
$ranges = self::splitRange($cellBlock);
353-
foreach ($ranges as $range) {
354-
// Single cell?
355-
if (!isset($range[1])) {
356-
$returnValue[] = $range[0];
357-
358-
continue;
359-
}
360-
361-
// Range...
362-
list($rangeStart, $rangeEnd) = $range;
363-
sscanf($rangeStart, '%[A-Z]%d', $startCol, $startRow);
364-
sscanf($rangeEnd, '%[A-Z]%d', $endCol, $endRow);
365-
++$endCol;
366-
367-
// Current data
368-
$currentCol = $startCol;
369-
$currentRow = $startRow;
370-
371-
// Loop cells
372-
while ($currentCol != $endCol) {
373-
while ($currentRow <= $endRow) {
374-
$returnValue[] = $currentCol . $currentRow;
375-
++$currentRow;
376-
}
377-
++$currentCol;
378-
$currentRow = $startRow;
379-
}
380-
}
343+
$returnValue = array_merge($returnValue, static::getReferencesForCellBlock($cellBlock));
381344
}
382345

383346
// Sort the result by column and row
@@ -392,6 +355,72 @@ public static function extractAllCellReferencesInRange($pRange)
392355
return array_values($sortKeys);
393356
}
394357

358+
/**
359+
* Get all cell references for an individual cell block.
360+
*
361+
* @param string $cellBlock A cell range e.g. A4:B5
362+
*
363+
* @throws Exception
364+
*
365+
* @return array All individual cells in that range
366+
*/
367+
private static function getReferencesForCellBlock($cellBlock)
368+
{
369+
$returnValue = [];
370+
371+
// Single cell?
372+
if (!self::coordinateIsRange($cellBlock)) {
373+
return (array) $cellBlock;
374+
}
375+
376+
// Range...
377+
$ranges = self::splitRange($cellBlock);
378+
foreach ($ranges as $range) {
379+
// Single cell?
380+
if (!isset($range[1])) {
381+
$returnValue[] = $range[0];
382+
383+
continue;
384+
}
385+
386+
// Range...
387+
list($rangeStart, $rangeEnd) = $range;
388+
list($startCol, $startRow) = static::extractColumnAndRow($rangeStart);
389+
list($endCol, $endRow) = static::extractColumnAndRow($rangeEnd);
390+
++$endCol;
391+
392+
// Current data
393+
$currentCol = $startCol;
394+
$currentRow = $startRow;
395+
396+
static::validateRange($cellBlock, $startCol, $endCol, $currentRow, $endRow);
397+
398+
// Loop cells
399+
while ($currentCol < $endCol) {
400+
while ($currentRow <= $endRow) {
401+
$returnValue[] = $currentCol . $currentRow;
402+
++$currentRow;
403+
}
404+
++$currentCol;
405+
$currentRow = $startRow;
406+
}
407+
}
408+
409+
return $returnValue;
410+
}
411+
412+
/**
413+
* Extract the column and row from a cell reference in the format [$column, $row].
414+
*
415+
* @param string $cell
416+
*
417+
* @return array
418+
*/
419+
private static function extractColumnAndRow($cell)
420+
{
421+
return sscanf($cell, '%[A-Z]%d');
422+
}
423+
395424
/**
396425
* Convert an associative array of single cell coordinates to values to an associative array
397426
* of cell ranges to values. Only adjacent cell coordinates with the same
@@ -477,4 +506,35 @@ public static function mergeRangesInCollection(array $pCoordCollection)
477506

478507
return $mergedCoordCollection;
479508
}
509+
510+
/**
511+
* Get the individual cell blocks from a range string, splitting by space and removing any $ characters.
512+
*
513+
* @param string $pRange
514+
*
515+
* @return string[]
516+
*/
517+
private static function getCellBlocksFromRangeString($pRange)
518+
{
519+
return explode(' ', str_replace('$', '', strtoupper($pRange)));
520+
}
521+
522+
/**
523+
* Check that the given range is valid, i.e. that the start column and row are not greater than the end column and
524+
* row.
525+
*
526+
* @param string $cellBlock The original range, for displaying a meaningful error message
527+
* @param string $startCol
528+
* @param string $endCol
529+
* @param int $currentRow
530+
* @param int $endRow
531+
*
532+
* @throws Exception
533+
*/
534+
private static function validateRange($cellBlock, $startCol, $endCol, $currentRow, $endRow)
535+
{
536+
if ($startCol >= $endCol || $currentRow > $endRow) {
537+
throw new Exception('Invalid range: "' . $cellBlock . '"');
538+
}
539+
}
480540
}

tests/PhpSpreadsheetTests/Cell/CoordinateTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,24 @@ public function providerExtractAllCellReferencesInRange()
315315
return require 'data/CellExtractAllCellReferencesInRange.php';
316316
}
317317

318+
/**
319+
* @dataProvider providerInvalidRange
320+
*
321+
* @param string $range
322+
*/
323+
public function testExtractAllCellReferencesInRangeInvalidRange($range)
324+
{
325+
$this->expectException(Exception::class);
326+
$this->expectExceptionMessage('Invalid range: "' . $range . '"');
327+
328+
Coordinate::extractAllCellReferencesInRange($range);
329+
}
330+
331+
public function providerInvalidRange()
332+
{
333+
return [['Z1:A1'], ['A4:A1'], ['B1:A1'], ['AA1:Z1']];
334+
}
335+
318336
/**
319337
* @dataProvider providerMergeRangesInCollection
320338
*

0 commit comments

Comments
 (0)