Issues (29)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Workbook.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace Xls;
3
4
use Xls\OLE\PpsFile;
5
use Xls\OLE\PpsRoot;
6
7
/**
8
 * Class for generating Excel Spreadsheets
9
*/
10
11
class Workbook extends BIFFwriter
12
{
13
    const COUNTRY_NONE = -1;
14
    const COUNTRY_USA = 1;
15
16
    const BOF_TYPE = 0x0005;
17
18
    /**
19
     * Formula parser
20
     * @var FormulaParser
21
     */
22
    protected $formulaParser;
23
24
    /**
25
     * The active worksheet of the workbook (0 indexed)
26
     * @var integer
27
     */
28
    protected $activeSheetIndex = 0;
29
30
    /**
31
     * 1st displayed worksheet in the workbook (0 indexed)
32
     * @var integer
33
     */
34
    protected $firstSheetIndex = 0;
35
36
    /**
37
     * Index for creating adding new formats to the workbook
38
     * 15 style XF's and 1 cell XF
39
     * @var integer
40
     */
41
    protected $xfIndex = 16;
42
43
    /**
44
     * Flag for preventing close from being called twice.
45
     * @var boolean
46
     * @see close()
47
     */
48
    protected $saved = false;
49
50
    /**
51
     * The default XF format.
52
     * @var Format
53
     */
54
    protected $tmpFormat;
55
56
    /**
57
     * Array containing references to all of this workbook's worksheets
58
     * @var Worksheet[]
59
     */
60
    protected $worksheets = array();
61
62
    /**
63
     * Array of sheetnames for creating the EXTERNSHEET records
64
     * @var array
65
     */
66
    protected $sheetNames = array();
67
68
    /**
69
     * Array containing references to all of this workbook's formats
70
     * @var Format[]
71
     */
72
    protected $formats = array();
73
74
    /**
75
     * Array containing the colour palette
76
     * @var array
77
     */
78
    protected $palette = array();
79
80
    /**
81
     * The default format for URLs.
82
     * @var Format
83
     */
84
    protected $urlFormat;
85
86
    /**
87
     * The country code used for localization
88
     * @var integer
89
     */
90
    protected $countryCode = self::COUNTRY_NONE;
91
92
    /**
93
     * @var int
94
     */
95
    protected $creationTimestamp;
96
97
    /**
98
     * @var SharedStringsTable
99
     */
100
    protected $sst;
101
102
    /**
103
     *
104
     */
105
    public function __construct()
106
    {
107
        $this->formulaParser = new FormulaParser();
108
109
        $this->palette = Palette::getXl97Palette();
110
111
        $this->addDefaultFormats();
112
113
        $this->sst = new SharedStringsTable();
114
115
        $this->setCreationTimestamp(time());
116
    }
117
118
    /**
119
     *
120
     */
121
    protected function addDefaultFormats()
122
    {
123
        $this->tmpFormat = new Format();
124
125
        // Add the default format for hyperlinks
126
        $this->urlFormat = $this->addFormat(
127
            array(
128
                'font.color' => 'blue',
129
                'font.underline' => Font::UNDERLINE_ONCE
130
            )
131
        );
132
    }
133
134
    /**
135
     * @param int $creationTime
136
     */
137
    public function setCreationTimestamp($creationTime)
138
    {
139
        $this->creationTimestamp = $creationTime;
140
    }
141
142
    /**
143
     * Assemble worksheets into a workbook and send the BIFF data to an OLE
144
     * storage.
145
     * This method should always be the last one to be called on every workbook
146
     *
147
     * @param string $filePath File path to save
148
     *
149
     * @throws \Exception
150
     */
151
    public function save($filePath)
152
    {
153
        if ($this->saved) {
154
            throw new \Exception('Workbook was already saved!');
155
        }
156
157
        if (count($this->worksheets) == 0) {
158
            throw new \Exception('Cannot save workbook with no sheets');
159
        }
160
161
        $this->appendRecord('Bof', array(static::BOF_TYPE));
162
        $this->appendRecord('Codepage', array(Biff8::CODEPAGE));
163
        $this->storeWindow1();
164
        $this->storeDatemode();
165
166
        $this->storeAllFonts();
167
        $this->storeAllNumFormats();
168
        $this->storeAllXfs();
169
        $this->storeAllStyles();
170
        $this->appendRecord('Palette', array($this->palette));
171
172
        $this->startBufferedWrite();
173
        $this->storeCountry();
174
        $this->appendRecord('RecalcId');
175
        $this->storeSupbookInternal();
176
        $this->storeExternsheet();
177
        $this->storeDefinedNames();
178
        $this->storeDrawings();
179
        $this->storeSharedStringsTable();
180
        $this->appendRecord('Eof');
181
        $this->endBufferedWrite();
182
183
        $this->storeSheets();
184
185
        $this->appendRaw($this->getBuffer());
186
187
        $this->saveOleFile($filePath);
188
189
        $this->saved = true;
190
    }
191
192
    protected function storeDrawings()
193
    {
194
        $totalDrawings = 0;
195
        foreach ($this->getWorksheets() as $sheet) {
196
            $totalDrawings += count($sheet->getDrawings());
197
        }
198
199
        if ($totalDrawings == 0) {
200
            return;
201
        }
202
203
        $data = '0F 00 00 F0 52 00 00 00 00 00 06 F0 18 00 00 00';
204
        $data .= '01 08';
205
        $data .= '00 00 02 00 00 00 03 00 00 00 01 00 00 00 01 00 00 00 03 00 00 00 33 00 0B F0 12 00 00 00 BF 00 08';
206
        $data .= ' 00 08 00 81 01 41 00 00 08 C0 01 40 00 00 08 40 00 1E F1 10 00 00 00 0D 00 00 08 0C 00 00 08 17 00';
207
        $data .= ' 00 08 F7 00 00 10';
208
        $this->appendRecord('MsoDrawingGroup', array($data));
209
    }
210
211
    /**
212
     * Write Internal SUPBOOK record
213
     */
214
    protected function storeSupbookInternal()
215
    {
216
        $this->appendRecord('ExternalBook', array($this->getSheetsCount()));
217
    }
218
219
    /**
220
     * Calculate the number of selected worksheet tabs and call the finalization
221
     * methods for each worksheet
222
     */
223
    protected function closeSheets()
224
    {
225
        foreach ($this->getWorksheets() as $sheet) {
226
            $sheet->close();
227
        }
228
    }
229
230
    /**
231
     *
232
     */
233
    protected function storeSheets()
234
    {
235
        $this->closeSheets();
236
237
        $offset = $this->getDataSize();
238
        $offset += $this->calcSheetRecordsTotalSize();
239
        $offset += $this->getBufferSize();
240
241
        foreach ($this->getWorksheets() as $sheet) {
242
            $this->appendRecord('Sheet', array($sheet->getName(), $offset));
243
            $offset += $sheet->getDataSize();
244
        }
245
    }
246
247
    /**
248
     * @return int
249
     */
250
    protected function calcSheetRecordsTotalSize()
251
    {
252
        $size = 0;
253
        foreach ($this->worksheets as $sheet) {
254
            $recordData = $this->getRecord('Sheet', array($sheet->getName()));
255
            $size += strlen($recordData);
256
        }
257
258
        return $size;
259
    }
260
261
    /**
262
     * Returns an array of the worksheet objects in a workbook
263
     *
264
     * @return Worksheet[]
265
     */
266
    public function getWorksheets()
267
    {
268
        return $this->worksheets;
269
    }
270
271
    /**
272
     * @return int
273
     */
274
    public function getSheetsCount()
275
    {
276
        return count($this->worksheets);
277
    }
278
279
    /**
280
     * Set the country identifier for the workbook
281
     *
282
     * @param integer $code Is the international calling country code for the
283
     *                      chosen country.
284
     */
285
    public function setCountry($code)
286
    {
287
        $this->countryCode = $code;
288
    }
289
290
    /**
291
     * Add a new worksheet to the Excel workbook.
292
     * If no name is given the name of the worksheet will be Sheeti$i, with
293
     * $i in [1..].
294
     *
295
     * @param string $name the optional name of the worksheet
296
     * @throws \Exception
297
     * @return Worksheet
298
     */
299
    public function addWorksheet($name = '')
300
    {
301
        $index = count($this->worksheets);
302
303
        if ($name == '') {
304
            $name = 'Sheet' . ($index + 1);
305
        }
306
307
        $this->checkSheetName($name);
308
309
        if ($this->hasSheet($name)) {
310
            throw new \Exception("Worksheet '$name' already exists");
311
        }
312
313
        $worksheet = new Worksheet(
314
            $name,
315
            $index,
316
            $this,
317
            $this->sst,
318
            $this->urlFormat,
319
            $this->formulaParser
320
        );
321
322
        $this->worksheets[$index] = $worksheet;
323
        $this->sheetNames[$index] = $name;
324
325
        if (count($this->worksheets) == 1) {
326
            $this->setActiveSheetIndex(0);
327
        }
328
329
        // Register worksheet name with parser
330
        $this->formulaParser->addSheet($name, $index);
331
        $this->formulaParser->addRef($index, $index);
332
333
        return $worksheet;
334
    }
335
336
    /**
337
     * @param string $name
338
     *
339
     * @return string
340
     * @throws \Exception
341
     */
342
    protected function checkSheetName($name)
343
    {
344
        $maxLen = Biff8::MAX_SHEET_NAME_LENGTH;
345
        if (strlen($name) > $maxLen) {
346
            throw new \Exception(
347
                "Sheet name must be shorter than $maxLen chars"
348
            );
349
        }
350
    }
351
352
    /**
353
     * @param int $sheetIndex
354
     */
355
    public function setActiveSheetIndex($sheetIndex)
356
    {
357
        $this->activeSheetIndex = $sheetIndex;
358
        foreach ($this->worksheets as $idx => $sheet) {
359
            if ($idx == $sheetIndex) {
360
                $sheet->select();
361
            } else {
362
                $sheet->unselect();
363
            }
364
        }
365
    }
366
367
    /**
368
     * @return int
369
     */
370
    public function getActiveSheetIndex()
371
    {
372
        return $this->activeSheetIndex;
373
    }
374
375
    /**
376
     * @return int
377
     */
378
    public function getFirstSheetIndex()
379
    {
380
        return $this->firstSheetIndex;
381
    }
382
383
    /**
384
     * @param int $firstSheetIndex
385
     */
386
    public function setFirstSheetIndex($firstSheetIndex)
387
    {
388
        $this->firstSheetIndex = $firstSheetIndex;
389
    }
390
391
    /**
392
     * @param string $name
393
     *
394
     * @return bool
395
     */
396
    public function hasSheet($name)
397
    {
398
        return in_array($name, $this->sheetNames, true);
399
    }
400
401
    /**
402
     * Add a new format to the Excel workbook.
403
     * Also, pass any properties to the Format constructor.
404
     *
405
     * @param array $properties array with properties for initializing the format.
406
     * @return Format reference to an Excel Format
407
     */
408
    public function addFormat($properties = array())
409
    {
410
        $format = new Format($this->xfIndex, $properties);
411
        $this->xfIndex++;
412
        $this->formats[] = $format;
413
414
        return $format;
415
    }
416
417
    /**
418
     * Create new validator.
419
     *
420
     * @return Validator reference to a Validator
421
     */
422
    public function addValidator()
423
    {
424
        return new Validator($this->formulaParser);
425
    }
426
427
    /**
428
     * Change the RGB components of the elements in the colour palette.
429
     *
430
     * @param integer $index colour index
431
     * @param integer $red   red RGB value [0-255]
432
     * @param integer $green green RGB value [0-255]
433
     * @param integer $blue  blue RGB value [0-255]
434
     * @throws \Exception
435
     *
436
     * @return integer The palette index for the custom color
437
     */
438
    public function setCustomColor($index, $red, $green, $blue)
439
    {
440
        Palette::validateColor($index, $red, $green, $blue);
441
442
        // Set the RGB value, adjust colour index (wingless dragonfly)
443
        $this->palette[$index - 8] = array($red, $green, $blue, 0);
444
445
        return $index;
446
    }
447
448
    /**
449
     * Store the workbook in an OLE container
450
     * @param string $filePath
451
     */
452
    protected function saveOleFile($filePath)
453
    {
454
        $ole = new PpsFile(Biff8::WORKBOOK_NAME);
455
        $ole->append($this->data);
456
457
        foreach ($this->worksheets as $sheet) {
458
            $ole->append($sheet->getData());
459
        }
460
461
        $root = new PpsRoot(
462
            $this->creationTimestamp,
463
            array($ole)
464
        );
465
466
        $root->save($filePath);
467
    }
468
469
    /**
470
     * Store the Excel FONT records.
471
     */
472
    protected function storeAllFonts()
473
    {
474
        foreach ($this->getFonts() as $font) {
475
            $this->appendRecord('Font', array($font));
476
        }
477
    }
478
479
    /**
480
     * @return Font[]
481
     */
482
    protected function getFonts()
483
    {
484
        $fontsMap = array();
485
486
        $defaultFont = $this->tmpFormat->getFont();
487
        $defaultFont->setIndex(0);
488
489
        $key = $defaultFont->getKey();
490
        $fontsMap[$key] = 1;
491
492
        //add default font for 5 times
493
        $fonts = array_fill(0, 5, $defaultFont);
494
495
        // Iterate through the XF objects and write a FONT record if it isn't the
496
        // same as the default FONT and if it hasn't already been used.
497
        $index = 6; // The first user defined FONT
498
        foreach ($this->formats as $format) {
499
            $font = $format->getFont();
500
            $key = $font->getKey();
501
502
            if (!isset($fontsMap[$key])) {
503
                // Add a new FONT record
504
                $fontsMap[$key] = 1;
505
                $font->setIndex($index);
506
                $fonts[] = $font;
507
                $index++;
508
            }
509
        }
510
511
        return $fonts;
512
    }
513
514
    /**
515
     * Store user defined numerical formats i.e. FORMAT records
516
     */
517
    protected function storeAllNumFormats()
518
    {
519
        $map = array();
520
        $index = 164;
521
522
        // Iterate through the XF objects and write a FORMAT record if it isn't a
523
        // built-in format type and if the FORMAT string hasn't already been used.
524
        foreach ($this->formats as $format) {
525
            $numFormat = $format->getNumFormat();
526
527
            if (NumberFormat::isBuiltIn($numFormat)) {
528
                $format->setNumFormatIndex($numFormat);
529
                continue;
530
            }
531
532
            if (!isset($map[$numFormat])) {
533
                // Add a new FORMAT
534
                $map[$numFormat] = 1;
535
                $format->setNumFormatIndex($index);
536
                $this->appendRecord('Format', array($numFormat, $index));
537
                $index++;
538
            }
539
        }
540
    }
541
542
    /**
543
     * Write all XF records.
544
     */
545
    protected function storeAllXfs()
546
    {
547
        // tmpFormat is added by the constructor. We use this to write the default XF's
548
        // The default font index is 0
549
        for ($i = 0; $i <= 14; $i++) {
550
            $xfRecord = $this->tmpFormat->getXf('style');
551
            $this->append($xfRecord);
552
        }
553
554
        $xfRecord = $this->tmpFormat->getXf('cell');
555
        $this->append($xfRecord);
556
557
        // User defined XFs
558
        foreach ($this->formats as $format) {
559
            $xfRecord = $format->getXf('cell');
560
            $this->append($xfRecord);
561
        }
562
    }
563
564
    /**
565
     * Write all STYLE records.
566
     */
567
    protected function storeAllStyles()
568
    {
569
        $this->appendRecord('Style');
570
    }
571
572
    /**
573
     *
574
     */
575
    protected function storeCountry()
576
    {
577
        if ($this->countryCode != self::COUNTRY_NONE) {
578
            $this->appendRecord('Country', array($this->countryCode));
579
        }
580
    }
581
582
    /**
583
     * Write the NAME record to define the print area and the repeat rows and cols.
584
     */
585
    protected function storeDefinedNames()
586
    {
587
        $this->storePrintAreaNames();
588
        $this->storePrintTitleNames();
589
    }
590
591
    /**
592
     * Create the print area NAME records
593
     */
594
    protected function storePrintAreaNames()
595
    {
596
        foreach ($this->worksheets as $sheet) {
597
            $printSetup = $sheet->getPrintSetup();
598
            if ($printSetup->isPrintAreaSet()) {
599
                $area = $printSetup->getPrintArea();
600
601
                $data = $this->getRangeCommonHeader($sheet);
602
                $data .= \Xls\Subrecord\Range::getData(array($area), false);
0 ignored issues
show
array($area) is of type array<integer,null|objec...l|object<Xls\\Range>"}>, but the function expects a array<integer,object<Xls\Range>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
603
604
                $this->appendRecord('DefinedName', array(
605
                    Record\DefinedName::BUILTIN_PRINT_AREA,
606
                    $sheet->getIndex() + 1,
607
                    $data
608
                ));
609
            }
610
        }
611
    }
612
613
    protected function getRangeCommonHeader(Worksheet $sheet)
614
    {
615
        return pack('Cv', 0x3B, $sheet->getIndex());
616
    }
617
618
    /**
619
     * Create the print title NAME records
620
     */
621
    protected function storePrintTitleNames()
622
    {
623
        foreach ($this->worksheets as $sheet) {
624
            $this->storePrintTitleName($sheet);
625
        }
626
    }
627
628
    /**
629
     * @param Worksheet $sheet
630
     */
631
    protected function storePrintTitleName(Worksheet $sheet)
632
    {
633
        $printRepeat = $sheet->getPrintSetup()->getPrintRepeat();
634
        if ($printRepeat->isEmpty()) {
635
            return;
636
        }
637
638
        $this->appendRecord('DefinedName', array(
639
            Record\DefinedName::BUILTIN_PRINT_TITLES,
640
            $sheet->getIndex() + 1,
641
            $this->getPrintTitleData($sheet)
642
        ));
643
    }
644
645
    /**
646
     * @param Worksheet $sheet
647
     *
648
     * @return string
649
     */
650
    protected function getPrintTitleData(Worksheet $sheet)
651
    {
652
        $printRepeat = $sheet->getPrintSetup()->getPrintRepeat();
653
654
        $rowmin = $printRepeat->getRowFrom();
655
        $rowmax = $printRepeat->getRowTo();
656
        $colmin = $printRepeat->getColFrom();
657
        $colmax = $printRepeat->getColTo();
658
659
        $rangeHeader = $this->getRangeCommonHeader($sheet);
660
661
        if ($rowmax !== Biff8::MAX_ROW_IDX && $colmax !== Biff8::MAX_COL_IDX) {
662
            $data = pack('Cv', 0x29, 0x17); // tMemFunc
663
            $data .= $rangeHeader;
664
            $data .= pack('v4', 0, Biff8::MAX_ROW_IDX, $colmin, $colmax); // tArea3d
665
            $data .= $rangeHeader;
666
            $data .= pack('v4', $rowmin, $rowmax, 0, Biff8::MAX_COL_IDX); // tArea3d
667
            $data .= pack('C', 0x10); // tList
668
        } else {
669
            $data = $rangeHeader;
670
            $data .= pack('v4', $rowmin, $rowmax, $colmin, $colmax);
671
        }
672
673
        return $data;
674
    }
675
676
    /**
677
     * Write Excel BIFF WINDOW1 record.
678
     */
679
    protected function storeWindow1()
680
    {
681
        $selectedSheetsCount = 1;
682
        $this->appendRecord(
683
            'Window1',
684
            array(
685
                $selectedSheetsCount,
686
                $this->firstSheetIndex,
687
                $this->activeSheetIndex
688
            )
689
        );
690
    }
691
692
    /**
693
     * Writes the Excel BIFF EXTERNSHEET record. These references are used by
694
     * formulas.
695
     */
696
    protected function storeExternsheet()
697
    {
698
        $this->appendRecord('Externsheet', array($this->formulaParser->getReferences()));
699
    }
700
701
    /**
702
     * Write DATEMODE record to indicate the date system in use (1904 or 1900)
703
     * Flag for 1904 date system (0 => base date is 1900, 1 => base date is 1904)
704
     */
705
    protected function storeDatemode()
706
    {
707
        $this->appendRecord('Datemode', array(0));
708
    }
709
710
    /**
711
     * Write all of the workbooks strings into an indexed array.
712
     *
713
     * The Excel documentation says that the SST record should be followed by an
714
     * EXTSST record. The EXTSST record is a hash table that is used to optimise
715
     * access to SST. However, despite the documentation it doesn't seem to be
716
     * required so we will ignore it.
717
     */
718
    protected function storeSharedStringsTable()
719
    {
720
        $this->appendRecord('SharedStringsTable', array($this->sst));
721
722
        foreach ($this->sst->getDataForWrite() as $item) {
723
            $this->append($item);
724
        }
725
    }
726
}
727