Issues (847)

Security Analysis    not enabled

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.

inc/JpegMeta.php (1 issue)

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
/**
3
 * JPEG metadata reader/writer
4
 *
5
 * @license    BSD <http://www.opensource.org/licenses/bsd-license.php>
6
 * @link       http://github.com/sd/jpeg-php
7
 * @author     Sebastian Delmont <[email protected]>
8
 * @author     Andreas Gohr <[email protected]>
9
 * @author     Hakan Sandell <[email protected]>
10
 * @todo       Add support for Maker Notes, Extend for GIF and PNG metadata
11
 */
12
13
// Original copyright notice:
14
//
15
// Copyright (c) 2003 Sebastian Delmont <[email protected]>
16
// All rights reserved.
17
//
18
// Redistribution and use in source and binary forms, with or without
19
// modification, are permitted provided that the following conditions
20
// are met:
21
// 1. Redistributions of source code must retain the above copyright
22
//    notice, this list of conditions and the following disclaimer.
23
// 2. Redistributions in binary form must reproduce the above copyright
24
//    notice, this list of conditions and the following disclaimer in the
25
//    documentation and/or other materials provided with the distribution.
26
// 3. Neither the name of the author nor the names of its contributors
27
//    may be used to endorse or promote products derived from this software
28
//    without specific prior written permission.
29
//
30
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
31
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
32
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
33
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
34
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
36
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
37
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
38
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
39
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
40
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
41
42
class JpegMeta {
43
    var $_fileName;
44
    var $_fp = null;
45
    var $_fpout = null;
46
    var $_type = 'unknown';
47
48
    var $_markers;
49
    var $_info;
50
51
52
    /**
53
     * Constructor
54
     *
55
     * @author Sebastian Delmont <[email protected]>
56
     *
57
     * @param $fileName
58
     */
59
    function __construct($fileName) {
60
61
        $this->_fileName = $fileName;
62
63
        $this->_fp = null;
64
        $this->_type = 'unknown';
65
66
        unset($this->_info);
67
        unset($this->_markers);
68
    }
69
70
    /**
71
     * Returns all gathered info as multidim array
72
     *
73
     * @author Sebastian Delmont <[email protected]>
74
     */
75
    function & getRawInfo() {
76
        $this->_parseAll();
77
78
        if ($this->_markers == null) {
79
            return false;
80
        }
81
82
        return $this->_info;
83
    }
84
85
    /**
86
     * Returns basic image info
87
     *
88
     * @author Sebastian Delmont <[email protected]>
89
     */
90
    function & getBasicInfo() {
91
        $this->_parseAll();
92
93
        $info = array();
94
95
        if ($this->_markers == null) {
96
            return false;
97
        }
98
99
        $info['Name'] = $this->_info['file']['Name'];
100
        if (isset($this->_info['file']['Url'])) {
101
            $info['Url'] = $this->_info['file']['Url'];
102
            $info['NiceSize'] = "???KB";
103
        } else {
104
            $info['Size'] = $this->_info['file']['Size'];
105
            $info['NiceSize'] = $this->_info['file']['NiceSize'];
106
        }
107
108
        if (@isset($this->_info['sof']['Format'])) {
109
            $info['Format'] = $this->_info['sof']['Format'] . " JPEG";
110
        } else {
111
            $info['Format'] = $this->_info['sof']['Format'] . " JPEG";
112
        }
113
114
        if (@isset($this->_info['sof']['ColorChannels'])) {
115
            $info['ColorMode'] = ($this->_info['sof']['ColorChannels'] > 1) ? "Color" : "B&W";
116
        }
117
118
        $info['Width'] = $this->getWidth();
119
        $info['Height'] = $this->getHeight();
120
        $info['DimStr'] = $this->getDimStr();
121
122
        $dates = $this->getDates();
123
124
        $info['DateTime'] = $dates['EarliestTime'];
125
        $info['DateTimeStr'] = $dates['EarliestTimeStr'];
126
127
        $info['HasThumbnail'] = $this->hasThumbnail();
128
129
        return $info;
130
    }
131
132
133
    /**
134
     * Convinience function to access nearly all available Data
135
     * through one function
136
     *
137
     * @author Andreas Gohr <[email protected]>
138
     *
139
     * @param array|string $fields field name or array with field names
140
     * @return bool|string
141
     */
142
    function getField($fields) {
143
        if(!is_array($fields)) $fields = array($fields);
144
        $info = false;
145
        foreach($fields as $field){
146
            if(strtolower(substr($field,0,5)) == 'iptc.'){
147
                $info = $this->getIPTCField(substr($field,5));
148
            }elseif(strtolower(substr($field,0,5)) == 'exif.'){
149
                $info = $this->getExifField(substr($field,5));
150
            }elseif(strtolower(substr($field,0,4)) == 'xmp.'){
151
                $info = $this->getXmpField(substr($field,4));
152
            }elseif(strtolower(substr($field,0,5)) == 'file.'){
153
                $info = $this->getFileField(substr($field,5));
154
            }elseif(strtolower(substr($field,0,5)) == 'date.'){
155
                $info = $this->getDateField(substr($field,5));
156
            }elseif(strtolower($field) == 'simple.camera'){
157
                $info = $this->getCamera();
158
            }elseif(strtolower($field) == 'simple.raw'){
159
                return $this->getRawInfo();
160
            }elseif(strtolower($field) == 'simple.title'){
161
                $info = $this->getTitle();
162
            }elseif(strtolower($field) == 'simple.shutterspeed'){
163
                $info = $this->getShutterSpeed();
164
            }else{
165
                $info = $this->getExifField($field);
166
            }
167
            if($info != false) break;
168
        }
169
170
        if($info === false)  $info = '';
171
        if(is_array($info)){
172
            if(isset($info['val'])){
173
                $info = $info['val'];
174
            }else{
175
                $info = join(', ',$info);
176
            }
177
        }
178
        return trim($info);
179
    }
180
181
    /**
182
     * Convinience function to set nearly all available Data
183
     * through one function
184
     *
185
     * @author Andreas Gohr <[email protected]>
186
     *
187
     * @param string $field field name
188
     * @param string $value
189
     * @return bool success or fail
190
     */
191
    function setField($field, $value) {
192
        if(strtolower(substr($field,0,5)) == 'iptc.'){
193
            return $this->setIPTCField(substr($field,5),$value);
194
        }elseif(strtolower(substr($field,0,5)) == 'exif.'){
195
            return $this->setExifField(substr($field,5),$value);
196
        }else{
197
            return $this->setExifField($field,$value);
198
        }
199
    }
200
201
    /**
202
     * Convinience function to delete nearly all available Data
203
     * through one function
204
     *
205
     * @author Andreas Gohr <[email protected]>
206
     *
207
     * @param string $field field name
208
     * @return bool
209
     */
210
    function deleteField($field) {
211
        if(strtolower(substr($field,0,5)) == 'iptc.'){
212
            return $this->deleteIPTCField(substr($field,5));
213
        }elseif(strtolower(substr($field,0,5)) == 'exif.'){
214
            return $this->deleteExifField(substr($field,5));
215
        }else{
216
            return $this->deleteExifField($field);
217
        }
218
    }
219
220
    /**
221
     * Return a date field
222
     *
223
     * @author Andreas Gohr <[email protected]>
224
     *
225
     * @param string $field
226
     * @return false|string
227
     */
228
    function getDateField($field) {
229
        if (!isset($this->_info['dates'])) {
230
            $this->_info['dates'] = $this->getDates();
231
        }
232
233
        if (isset($this->_info['dates'][$field])) {
234
            return $this->_info['dates'][$field];
235
        }
236
237
        return false;
238
    }
239
240
    /**
241
     * Return a file info field
242
     *
243
     * @author Andreas Gohr <[email protected]>
244
     *
245
     * @param string $field field name
246
     * @return false|string
247
     */
248
    function getFileField($field) {
249
        if (!isset($this->_info['file'])) {
250
            $this->_parseFileInfo();
251
        }
252
253
        if (isset($this->_info['file'][$field])) {
254
            return $this->_info['file'][$field];
255
        }
256
257
        return false;
258
    }
259
260
    /**
261
     * Return the camera info (Maker and Model)
262
     *
263
     * @author Andreas Gohr <[email protected]>
264
     * @todo   handle makernotes
265
     *
266
     * @return false|string
267
     */
268
    function getCamera(){
269
        $make  = $this->getField(array('Exif.Make','Exif.TIFFMake'));
270
        $model = $this->getField(array('Exif.Model','Exif.TIFFModel'));
271
        $cam = trim("$make $model");
272
        if(empty($cam)) return false;
273
        return $cam;
274
    }
275
276
    /**
277
     * Return shutter speed as a ratio
278
     *
279
     * @author Joe Lapp <[email protected]>
280
     *
281
     * @return string
282
     */
283
    function getShutterSpeed() {
284
        if (!isset($this->_info['exif'])) {
285
            $this->_parseMarkerExif();
286
        }
287
        if(!isset($this->_info['exif']['ExposureTime'])){
288
            return '';
289
        }
290
291
        $field = $this->_info['exif']['ExposureTime'];
292
        if($field['den'] == 1) return $field['num'];
293
        return $field['num'].'/'.$field['den'];
294
    }
295
296
    /**
297
     * Return an EXIF field
298
     *
299
     * @author Sebastian Delmont <[email protected]>
300
     *
301
     * @param string $field field name
302
     * @return false|string
303
     */
304
    function getExifField($field) {
305
        if (!isset($this->_info['exif'])) {
306
            $this->_parseMarkerExif();
307
        }
308
309
        if ($this->_markers == null) {
310
            return false;
311
        }
312
313
        if (isset($this->_info['exif'][$field])) {
314
            return $this->_info['exif'][$field];
315
        }
316
317
        return false;
318
    }
319
320
    /**
321
     * Return an XMP field
322
     *
323
     * @author Hakan Sandell <[email protected]>
324
     *
325
     * @param string $field field name
326
     * @return false|string
327
     */
328
    function getXmpField($field) {
329
        if (!isset($this->_info['xmp'])) {
330
            $this->_parseMarkerXmp();
331
        }
332
333
        if ($this->_markers == null) {
334
            return false;
335
        }
336
337
        if (isset($this->_info['xmp'][$field])) {
338
            return $this->_info['xmp'][$field];
339
        }
340
341
        return false;
342
    }
343
344
    /**
345
     * Return an Adobe Field
346
     *
347
     * @author Sebastian Delmont <[email protected]>
348
     *
349
     * @param string $field field name
350
     * @return false|string
351
     */
352
    function getAdobeField($field) {
353
        if (!isset($this->_info['adobe'])) {
354
            $this->_parseMarkerAdobe();
355
        }
356
357
        if ($this->_markers == null) {
358
            return false;
359
        }
360
361
        if (isset($this->_info['adobe'][$field])) {
362
            return $this->_info['adobe'][$field];
363
        }
364
365
        return false;
366
    }
367
368
    /**
369
     * Return an IPTC field
370
     *
371
     * @author Sebastian Delmont <[email protected]>
372
     *
373
     * @param string $field field name
374
     * @return false|string
375
     */
376
    function getIPTCField($field) {
377
        if (!isset($this->_info['iptc'])) {
378
            $this->_parseMarkerAdobe();
379
        }
380
381
        if ($this->_markers == null) {
382
            return false;
383
        }
384
385
        if (isset($this->_info['iptc'][$field])) {
386
            return $this->_info['iptc'][$field];
387
        }
388
389
        return false;
390
    }
391
392
    /**
393
     * Set an EXIF field
394
     *
395
     * @author Sebastian Delmont <[email protected]>
396
     * @author Joe Lapp <[email protected]>
397
     *
398
     * @param string $field field name
399
     * @param string $value
400
     * @return bool
401
     */
402
    function setExifField($field, $value) {
403
        if (!isset($this->_info['exif'])) {
404
            $this->_parseMarkerExif();
405
        }
406
407
        if ($this->_markers == null) {
408
            return false;
409
        }
410
411
        if ($this->_info['exif'] == false) {
412
            $this->_info['exif'] = array();
413
        }
414
415
        // make sure datetimes are in correct format
416
        if(strlen($field) >= 8 && strtolower(substr($field, 0, 8)) == 'datetime') {
417
            if(strlen($value) < 8 || $value[4] != ':' || $value[7] != ':') {
418
                $value = date('Y:m:d H:i:s', strtotime($value));
419
            }
420
        }
421
422
        $this->_info['exif'][$field] = $value;
423
424
        return true;
425
    }
426
427
    /**
428
     * Set an Adobe Field
429
     *
430
     * @author Sebastian Delmont <[email protected]>
431
     *
432
     * @param string $field field name
433
     * @param string $value
434
     * @return bool
435
     */
436
    function setAdobeField($field, $value) {
437
        if (!isset($this->_info['adobe'])) {
438
            $this->_parseMarkerAdobe();
439
        }
440
441
        if ($this->_markers == null) {
442
            return false;
443
        }
444
445
        if ($this->_info['adobe'] == false) {
446
            $this->_info['adobe'] = array();
447
        }
448
449
        $this->_info['adobe'][$field] = $value;
450
451
        return true;
452
    }
453
454
    /**
455
     * Calculates the multiplier needed to resize the image to the given
456
     * dimensions
457
     *
458
     * @author Andreas Gohr <[email protected]>
459
     *
460
     * @param int $maxwidth
461
     * @param int $maxheight
462
     * @return float|int
463
     */
464
    function getResizeRatio($maxwidth,$maxheight=0){
465
        if(!$maxheight) $maxheight = $maxwidth;
466
467
        $w = $this->getField('File.Width');
468
        $h = $this->getField('File.Height');
469
470
        $ratio = 1;
471
        if($w >= $h){
472
            if($w >= $maxwidth){
473
                $ratio = $maxwidth/$w;
474
            }elseif($h > $maxheight){
475
                $ratio = $maxheight/$h;
476
            }
477
        }else{
478
            if($h >= $maxheight){
479
                $ratio = $maxheight/$h;
480
            }elseif($w > $maxwidth){
481
                $ratio = $maxwidth/$w;
482
            }
483
        }
484
        return $ratio;
485
    }
486
487
488
    /**
489
     * Set an IPTC field
490
     *
491
     * @author Sebastian Delmont <[email protected]>
492
     *
493
     * @param string $field field name
494
     * @param string $value
495
     * @return bool
496
     */
497
    function setIPTCField($field, $value) {
498
        if (!isset($this->_info['iptc'])) {
499
            $this->_parseMarkerAdobe();
500
        }
501
502
        if ($this->_markers == null) {
503
            return false;
504
        }
505
506
        if ($this->_info['iptc'] == false) {
507
            $this->_info['iptc'] = array();
508
        }
509
510
        $this->_info['iptc'][$field] = $value;
511
512
        return true;
513
    }
514
515
    /**
516
     * Delete an EXIF field
517
     *
518
     * @author Sebastian Delmont <[email protected]>
519
     *
520
     * @param string $field field name
521
     * @return bool
522
     */
523
    function deleteExifField($field) {
524
        if (!isset($this->_info['exif'])) {
525
            $this->_parseMarkerAdobe();
526
        }
527
528
        if ($this->_markers == null) {
529
            return false;
530
        }
531
532
        if ($this->_info['exif'] != false) {
533
            unset($this->_info['exif'][$field]);
534
        }
535
536
        return true;
537
    }
538
539
    /**
540
     * Delete an Adobe field
541
     *
542
     * @author Sebastian Delmont <[email protected]>
543
     *
544
     * @param string $field field name
545
     * @return bool
546
     */
547
    function deleteAdobeField($field) {
548
        if (!isset($this->_info['adobe'])) {
549
            $this->_parseMarkerAdobe();
550
        }
551
552
        if ($this->_markers == null) {
553
            return false;
554
        }
555
556
        if ($this->_info['adobe'] != false) {
557
            unset($this->_info['adobe'][$field]);
558
        }
559
560
        return true;
561
    }
562
563
    /**
564
     * Delete an IPTC field
565
     *
566
     * @author Sebastian Delmont <[email protected]>
567
     *
568
     * @param string $field field name
569
     * @return bool
570
     */
571
    function deleteIPTCField($field) {
572
        if (!isset($this->_info['iptc'])) {
573
            $this->_parseMarkerAdobe();
574
        }
575
576
        if ($this->_markers == null) {
577
            return false;
578
        }
579
580
        if ($this->_info['iptc'] != false) {
581
            unset($this->_info['iptc'][$field]);
582
        }
583
584
        return true;
585
    }
586
587
    /**
588
     * Get the image's title, tries various fields
589
     *
590
     * @param int $max maximum number chars (keeps words)
591
     * @return false|string
592
     *
593
     * @author Andreas Gohr <[email protected]>
594
     */
595
    function getTitle($max=80){
596
        // try various fields
597
        $cap = $this->getField(array('Iptc.Headline',
598
                    'Iptc.Caption',
599
                    'Xmp.dc:title',
600
                    'Exif.UserComment',
601
                    'Exif.TIFFUserComment',
602
                    'Exif.TIFFImageDescription',
603
                    'File.Name'));
604
        if (empty($cap)) return false;
605
606
        if(!$max) return $cap;
607
        // Shorten to 80 chars (keeping words)
608
        $new = preg_replace('/\n.+$/','',wordwrap($cap, $max));
609
        if($new != $cap) $new .= '...';
610
611
        return $new;
612
    }
613
614
    /**
615
     * Gather various date fields
616
     *
617
     * @author Sebastian Delmont <[email protected]>
618
     *
619
     * @return array|bool
620
     */
621
    function getDates() {
622
        $this->_parseAll();
623
        if ($this->_markers == null) {
624
            if (@isset($this->_info['file']['UnixTime'])) {
625
                $dates = array();
626
                $dates['FileModified'] = $this->_info['file']['UnixTime'];
627
                $dates['Time'] = $this->_info['file']['UnixTime'];
628
                $dates['TimeSource'] = 'FileModified';
629
                $dates['TimeStr'] = date("Y-m-d H:i:s", $this->_info['file']['UnixTime']);
630
                $dates['EarliestTime'] = $this->_info['file']['UnixTime'];
631
                $dates['EarliestTimeSource'] = 'FileModified';
632
                $dates['EarliestTimeStr'] = date("Y-m-d H:i:s", $this->_info['file']['UnixTime']);
633
                $dates['LatestTime'] = $this->_info['file']['UnixTime'];
634
                $dates['LatestTimeSource'] = 'FileModified';
635
                $dates['LatestTimeStr'] = date("Y-m-d H:i:s", $this->_info['file']['UnixTime']);
636
                return $dates;
637
            }
638
            return false;
639
        }
640
641
        $dates = array();
642
643
        $latestTime = 0;
644
        $latestTimeSource = "";
645
        $earliestTime = time();
646
        $earliestTimeSource = "";
647
648
        if (@isset($this->_info['exif']['DateTime'])) {
649
            $dates['ExifDateTime'] = $this->_info['exif']['DateTime'];
650
651
            $aux = $this->_info['exif']['DateTime'];
652
            $aux[4] = "-";
653
            $aux[7] = "-";
654
            $t = strtotime($aux);
655
656
            if ($t && $t > $latestTime) {
657
                $latestTime = $t;
658
                $latestTimeSource = "ExifDateTime";
659
            }
660
661
            if ($t && $t < $earliestTime) {
662
                $earliestTime = $t;
663
                $earliestTimeSource = "ExifDateTime";
664
            }
665
        }
666
667
        if (@isset($this->_info['exif']['DateTimeOriginal'])) {
668
            $dates['ExifDateTimeOriginal'] = $this->_info['exif']['DateTime'];
669
670
            $aux = $this->_info['exif']['DateTimeOriginal'];
671
            $aux[4] = "-";
672
            $aux[7] = "-";
673
            $t = strtotime($aux);
674
675
            if ($t && $t > $latestTime) {
676
                $latestTime = $t;
677
                $latestTimeSource = "ExifDateTimeOriginal";
678
            }
679
680
            if ($t && $t < $earliestTime) {
681
                $earliestTime = $t;
682
                $earliestTimeSource = "ExifDateTimeOriginal";
683
            }
684
        }
685
686
        if (@isset($this->_info['exif']['DateTimeDigitized'])) {
687
            $dates['ExifDateTimeDigitized'] = $this->_info['exif']['DateTime'];
688
689
            $aux = $this->_info['exif']['DateTimeDigitized'];
690
            $aux[4] = "-";
691
            $aux[7] = "-";
692
            $t = strtotime($aux);
693
694
            if ($t && $t > $latestTime) {
695
                $latestTime = $t;
696
                $latestTimeSource = "ExifDateTimeDigitized";
697
            }
698
699
            if ($t && $t < $earliestTime) {
700
                $earliestTime = $t;
701
                $earliestTimeSource = "ExifDateTimeDigitized";
702
            }
703
        }
704
705
        if (@isset($this->_info['iptc']['DateCreated'])) {
706
            $dates['IPTCDateCreated'] = $this->_info['iptc']['DateCreated'];
707
708
            $aux = $this->_info['iptc']['DateCreated'];
709
            $aux = substr($aux, 0, 4) . "-" . substr($aux, 4, 2) . "-" . substr($aux, 6, 2);
710
            $t = strtotime($aux);
711
712
            if ($t && $t > $latestTime) {
713
                $latestTime = $t;
714
                $latestTimeSource = "IPTCDateCreated";
715
            }
716
717
            if ($t && $t < $earliestTime) {
718
                $earliestTime = $t;
719
                $earliestTimeSource = "IPTCDateCreated";
720
            }
721
        }
722
723
        if (@isset($this->_info['file']['UnixTime'])) {
724
            $dates['FileModified'] = $this->_info['file']['UnixTime'];
725
726
            $t = $this->_info['file']['UnixTime'];
727
728
            if ($t && $t > $latestTime) {
729
                $latestTime = $t;
730
                $latestTimeSource = "FileModified";
731
            }
732
733
            if ($t && $t < $earliestTime) {
734
                $earliestTime = $t;
735
                $earliestTimeSource = "FileModified";
736
            }
737
        }
738
739
        $dates['Time'] = $earliestTime;
740
        $dates['TimeSource'] = $earliestTimeSource;
741
        $dates['TimeStr'] = date("Y-m-d H:i:s", $earliestTime);
742
        $dates['EarliestTime'] = $earliestTime;
743
        $dates['EarliestTimeSource'] = $earliestTimeSource;
744
        $dates['EarliestTimeStr'] = date("Y-m-d H:i:s", $earliestTime);
745
        $dates['LatestTime'] = $latestTime;
746
        $dates['LatestTimeSource'] = $latestTimeSource;
747
        $dates['LatestTimeStr'] = date("Y-m-d H:i:s", $latestTime);
748
749
        return $dates;
750
    }
751
752
    /**
753
     * Get the image width, tries various fields
754
     *
755
     * @author Sebastian Delmont <[email protected]>
756
     *
757
     * @return false|string
758
     */
759
    function getWidth() {
760
        if (!isset($this->_info['sof'])) {
761
            $this->_parseMarkerSOF();
762
        }
763
764
        if ($this->_markers == null) {
765
            return false;
766
        }
767
768
        if (isset($this->_info['sof']['ImageWidth'])) {
769
            return $this->_info['sof']['ImageWidth'];
770
        }
771
772
        if (!isset($this->_info['exif'])) {
773
            $this->_parseMarkerExif();
774
        }
775
776
        if (isset($this->_info['exif']['PixelXDimension'])) {
777
            return $this->_info['exif']['PixelXDimension'];
778
        }
779
780
        return false;
781
    }
782
783
    /**
784
     * Get the image height, tries various fields
785
     *
786
     * @author Sebastian Delmont <[email protected]>
787
     *
788
     * @return false|string
789
     */
790
    function getHeight() {
791
        if (!isset($this->_info['sof'])) {
792
            $this->_parseMarkerSOF();
793
        }
794
795
        if ($this->_markers == null) {
796
            return false;
797
        }
798
799
        if (isset($this->_info['sof']['ImageHeight'])) {
800
            return $this->_info['sof']['ImageHeight'];
801
        }
802
803
        if (!isset($this->_info['exif'])) {
804
            $this->_parseMarkerExif();
805
        }
806
807
        if (isset($this->_info['exif']['PixelYDimension'])) {
808
            return $this->_info['exif']['PixelYDimension'];
809
        }
810
811
        return false;
812
    }
813
814
    /**
815
     * Get an dimension string for use in img tag
816
     *
817
     * @author Sebastian Delmont <[email protected]>
818
     *
819
     * @return false|string
820
     */
821
    function getDimStr() {
822
        if ($this->_markers == null) {
823
            return false;
824
        }
825
826
        $w = $this->getWidth();
827
        $h = $this->getHeight();
828
829
        return "width='" . $w . "' height='" . $h . "'";
830
    }
831
832
    /**
833
     * Checks for an embedded thumbnail
834
     *
835
     * @author Sebastian Delmont <[email protected]>
836
     *
837
     * @param string $which possible values: 'any', 'exif' or 'adobe'
838
     * @return false|string
839
     */
840
    function hasThumbnail($which = 'any') {
841
        if (($which == 'any') || ($which == 'exif')) {
842
            if (!isset($this->_info['exif'])) {
843
                $this->_parseMarkerExif();
844
            }
845
846
            if ($this->_markers == null) {
847
                return false;
848
            }
849
850
            if (isset($this->_info['exif']) && is_array($this->_info['exif'])) {
851
                if (isset($this->_info['exif']['JFIFThumbnail'])) {
852
                    return 'exif';
853
                }
854
            }
855
        }
856
857
        if ($which == 'adobe') {
858
            if (!isset($this->_info['adobe'])) {
859
                $this->_parseMarkerAdobe();
860
            }
861
862
            if ($this->_markers == null) {
863
                return false;
864
            }
865
866
            if (isset($this->_info['adobe']) && is_array($this->_info['adobe'])) {
867
                if (isset($this->_info['adobe']['ThumbnailData'])) {
868
                    return 'exif';
869
                }
870
            }
871
        }
872
873
        return false;
874
    }
875
876
    /**
877
     * Send embedded thumbnail to browser
878
     *
879
     * @author Sebastian Delmont <[email protected]>
880
     *
881
     * @param string $which possible values: 'any', 'exif' or 'adobe'
882
     * @return bool
883
     */
884
    function sendThumbnail($which = 'any') {
885
        $data = null;
886
887
        if (($which == 'any') || ($which == 'exif')) {
888
            if (!isset($this->_info['exif'])) {
889
                $this->_parseMarkerExif();
890
            }
891
892
            if ($this->_markers == null) {
893
                return false;
894
            }
895
896
            if (isset($this->_info['exif']) && is_array($this->_info['exif'])) {
897
                if (isset($this->_info['exif']['JFIFThumbnail'])) {
898
                    $data =& $this->_info['exif']['JFIFThumbnail'];
899
                }
900
            }
901
        }
902
903
        if (($which == 'adobe') || ($data == null)){
904
            if (!isset($this->_info['adobe'])) {
905
                $this->_parseMarkerAdobe();
906
            }
907
908
            if ($this->_markers == null) {
909
                return false;
910
            }
911
912
            if (isset($this->_info['adobe']) && is_array($this->_info['adobe'])) {
913
                if (isset($this->_info['adobe']['ThumbnailData'])) {
914
                    $data =& $this->_info['adobe']['ThumbnailData'];
915
                }
916
            }
917
        }
918
919
        if ($data != null) {
920
            header("Content-type: image/jpeg");
921
            echo $data;
922
            return true;
923
        }
924
925
        return false;
926
    }
927
928
    /**
929
     * Save changed Metadata
930
     *
931
     * @author Sebastian Delmont <[email protected]>
932
     * @author Andreas Gohr <[email protected]>
933
     *
934
     * @param string $fileName file name or empty string for a random name
935
     * @return bool
936
     */
937
    function save($fileName = "") {
938
        if ($fileName == "") {
939
            $tmpName = tempnam(dirname($this->_fileName),'_metatemp_');
940
            $this->_writeJPEG($tmpName);
941
            if (file_exists($tmpName)) {
942
                return io_rename($tmpName, $this->_fileName);
943
            }
944
        } else {
945
            return $this->_writeJPEG($fileName);
946
        }
947
        return false;
948
    }
949
950
    /*************************************************************/
951
    /* PRIVATE FUNCTIONS (Internal Use Only!)                    */
952
    /*************************************************************/
953
954
    /*************************************************************/
955
    function _dispose($fileName = "") {
956
        $this->_fileName = $fileName;
957
958
        $this->_fp = null;
959
        $this->_type = 'unknown';
960
961
        unset($this->_markers);
962
        unset($this->_info);
963
    }
964
965
    /*************************************************************/
966
    function _readJPEG() {
967
        unset($this->_markers);
968
        //unset($this->_info);
969
        $this->_markers = array();
970
        //$this->_info = array();
971
972
        $this->_fp = @fopen($this->_fileName, 'rb');
973
        if ($this->_fp) {
974
            if (file_exists($this->_fileName)) {
975
                $this->_type = 'file';
976
            }
977
            else {
978
                $this->_type = 'url';
979
            }
980
        } else {
981
            $this->_fp = null;
982
            return false;  // ERROR: Can't open file
983
        }
984
985
        // Check for the JPEG signature
986
        $c1 = ord(fgetc($this->_fp));
987
        $c2 = ord(fgetc($this->_fp));
988
989
        if ($c1 != 0xFF || $c2 != 0xD8) {   // (0xFF + SOI)
990
            $this->_markers = null;
991
            return false;  // ERROR: File is not a JPEG
992
        }
993
994
        $count = 0;
995
996
        $done = false;
997
        $ok = true;
998
999
        while (!$done) {
1000
            $capture = false;
1001
1002
            // First, skip any non 0xFF bytes
1003
            $discarded = 0;
1004
            $c = ord(fgetc($this->_fp));
1005
            while (!feof($this->_fp) && ($c != 0xFF)) {
1006
                $discarded++;
1007
                $c = ord(fgetc($this->_fp));
1008
            }
1009
            // Then skip all 0xFF until the marker byte
1010
            do {
1011
                $marker = ord(fgetc($this->_fp));
1012
            } while (!feof($this->_fp) && ($marker == 0xFF));
1013
1014
            if (feof($this->_fp)) {
1015
                return false; // ERROR: Unexpected EOF
1016
            }
1017
            if ($discarded != 0) {
1018
                return false; // ERROR: Extraneous data
1019
            }
1020
1021
            $length = ord(fgetc($this->_fp)) * 256 + ord(fgetc($this->_fp));
1022
            if (feof($this->_fp)) {
1023
                return false; // ERROR: Unexpected EOF
1024
            }
1025
            if ($length < 2) {
1026
                return false; // ERROR: Extraneous data
1027
            }
1028
            $length = $length - 2; // The length we got counts itself
1029
1030
            switch ($marker) {
1031
                case 0xC0:    // SOF0
1032
                case 0xC1:    // SOF1
1033
                case 0xC2:    // SOF2
1034
                case 0xC9:    // SOF9
1035
                case 0xE0:    // APP0: JFIF data
1036
                case 0xE1:    // APP1: EXIF or XMP data
1037
                case 0xED:    // APP13: IPTC / Photoshop data
1038
                    $capture = true;
1039
                    break;
1040
                case 0xDA:    // SOS: Start of scan... the image itself and the last block on the file
1041
                    $capture = false;
1042
                    $length = -1;  // This field has no length... it includes all data until EOF
1043
                    $done = true;
1044
                    break;
1045
                default:
1046
                    $capture = true;//false;
1047
                    break;
1048
            }
1049
1050
            $this->_markers[$count] = array();
1051
            $this->_markers[$count]['marker'] = $marker;
1052
            $this->_markers[$count]['length'] = $length;
1053
1054
            if ($capture) {
1055
                if ($length)
1056
                    $this->_markers[$count]['data'] = fread($this->_fp, $length);
1057
                else
1058
                    $this->_markers[$count]['data'] = "";
1059
            }
1060
            elseif (!$done) {
1061
                $result = @fseek($this->_fp, $length, SEEK_CUR);
1062
                // fseek doesn't seem to like HTTP 'files', but fgetc has no problem
1063
                if (!($result === 0)) {
1064
                    for ($i = 0; $i < $length; $i++) {
1065
                        fgetc($this->_fp);
1066
                    }
1067
                }
1068
            }
1069
            $count++;
1070
        }
1071
1072
        if ($this->_fp) {
1073
            fclose($this->_fp);
1074
            $this->_fp = null;
1075
        }
1076
1077
        return $ok;
1078
    }
1079
1080
    /*************************************************************/
1081
    function _parseAll() {
1082
        if (!isset($this->_info['file'])) {
1083
            $this->_parseFileInfo();
1084
        }
1085
        if (!isset($this->_markers)) {
1086
            $this->_readJPEG();
1087
        }
1088
1089
        if ($this->_markers == null) {
1090
            return false;
1091
        }
1092
1093
        if (!isset($this->_info['jfif'])) {
1094
            $this->_parseMarkerJFIF();
1095
        }
1096
        if (!isset($this->_info['jpeg'])) {
1097
            $this->_parseMarkerSOF();
1098
        }
1099
        if (!isset($this->_info['exif'])) {
1100
            $this->_parseMarkerExif();
1101
        }
1102
        if (!isset($this->_info['xmp'])) {
1103
            $this->_parseMarkerXmp();
1104
        }
1105
        if (!isset($this->_info['adobe'])) {
1106
            $this->_parseMarkerAdobe();
1107
        }
1108
    }
1109
1110
    /*************************************************************/
1111
1112
    /**
1113
     * @param string $outputName
1114
     *
1115
     * @return bool
1116
     */
1117
    function _writeJPEG($outputName) {
1118
        $this->_parseAll();
1119
1120
        $wroteEXIF = false;
1121
        $wroteAdobe = false;
1122
1123
        $this->_fp = @fopen($this->_fileName, 'r');
1124
        if ($this->_fp) {
1125
            if (file_exists($this->_fileName)) {
1126
                $this->_type = 'file';
1127
            }
1128
            else {
1129
                $this->_type = 'url';
1130
            }
1131
        } else {
1132
            $this->_fp = null;
1133
            return false;  // ERROR: Can't open file
1134
        }
1135
1136
        $this->_fpout = fopen($outputName, 'wb');
1137
        if (!$this->_fpout) {
1138
            $this->_fpout = null;
1139
            fclose($this->_fp);
1140
            $this->_fp = null;
1141
            return false;  // ERROR: Can't open output file
1142
        }
1143
1144
        // Check for the JPEG signature
1145
        $c1 = ord(fgetc($this->_fp));
1146
        $c2 = ord(fgetc($this->_fp));
1147
1148
        if ($c1 != 0xFF || $c2 != 0xD8) {   // (0xFF + SOI)
1149
            return false;  // ERROR: File is not a JPEG
1150
        }
1151
1152
        fputs($this->_fpout, chr(0xFF), 1);
1153
        fputs($this->_fpout, chr(0xD8), 1); // (0xFF + SOI)
1154
1155
        $count = 0;
1156
1157
        $done = false;
1158
        $ok = true;
1159
1160
        while (!$done) {
1161
            // First, skip any non 0xFF bytes
1162
            $discarded = 0;
1163
            $c = ord(fgetc($this->_fp));
1164
            while (!feof($this->_fp) && ($c != 0xFF)) {
1165
                $discarded++;
1166
                $c = ord(fgetc($this->_fp));
1167
            }
1168
            // Then skip all 0xFF until the marker byte
1169
            do {
1170
                $marker = ord(fgetc($this->_fp));
1171
            } while (!feof($this->_fp) && ($marker == 0xFF));
1172
1173
            if (feof($this->_fp)) {
1174
                $ok = false;
1175
                break; // ERROR: Unexpected EOF
1176
            }
1177
            if ($discarded != 0) {
1178
                $ok = false;
1179
                break; // ERROR: Extraneous data
1180
            }
1181
1182
            $length = ord(fgetc($this->_fp)) * 256 + ord(fgetc($this->_fp));
1183
            if (feof($this->_fp)) {
1184
                $ok = false;
1185
                break; // ERROR: Unexpected EOF
1186
            }
1187
            if ($length < 2) {
1188
                $ok = false;
1189
                break; // ERROR: Extraneous data
1190
            }
1191
            $length = $length - 2; // The length we got counts itself
1192
1193
            unset($data);
1194
            if ($marker == 0xE1) { // APP1: EXIF data
1195
                $data =& $this->_createMarkerEXIF();
1196
                $wroteEXIF = true;
1197
            }
1198
            elseif ($marker == 0xED) { // APP13: IPTC / Photoshop data
1199
                $data =& $this->_createMarkerAdobe();
1200
                $wroteAdobe = true;
1201
            }
1202
            elseif ($marker == 0xDA) { // SOS: Start of scan... the image itself and the last block on the file
1203
                $done = true;
1204
            }
1205
1206
            if (!$wroteEXIF && (($marker < 0xE0) || ($marker > 0xEF))) {
1207
                if (isset($this->_info['exif']) && is_array($this->_info['exif'])) {
1208
                    $exif =& $this->_createMarkerEXIF();
1209
                    $this->_writeJPEGMarker(0xE1, strlen($exif), $exif, 0);
1210
                    unset($exif);
1211
                }
1212
                $wroteEXIF = true;
1213
            }
1214
1215
            if (!$wroteAdobe && (($marker < 0xE0) || ($marker > 0xEF))) {
1216
                if ((isset($this->_info['adobe']) && is_array($this->_info['adobe']))
1217
                        || (isset($this->_info['iptc']) && is_array($this->_info['iptc']))) {
1218
                    $adobe =& $this->_createMarkerAdobe();
1219
                    $this->_writeJPEGMarker(0xED, strlen($adobe), $adobe, 0);
1220
                    unset($adobe);
1221
                }
1222
                $wroteAdobe = true;
1223
            }
1224
1225
            $origLength = $length;
1226
            if (isset($data)) {
1227
                $length = strlen($data);
1228
            }
1229
1230
            if ($marker != -1) {
1231
                $this->_writeJPEGMarker($marker, $length, $data, $origLength);
1232
            }
1233
        }
1234
1235
        if ($this->_fp) {
1236
            fclose($this->_fp);
1237
            $this->_fp = null;
1238
        }
1239
1240
        if ($this->_fpout) {
1241
            fclose($this->_fpout);
1242
            $this->_fpout = null;
1243
        }
1244
1245
        return $ok;
1246
    }
1247
1248
    /*************************************************************/
1249
1250
    /**
1251
     * @param integer $marker
1252
     * @param integer $length
1253
     * @param string $data
1254
     * @param integer $origLength
1255
     *
1256
     * @return bool
1257
     */
1258
    function _writeJPEGMarker($marker, $length, &$data, $origLength) {
1259
        if ($length <= 0) {
1260
            return false;
1261
        }
1262
1263
        fputs($this->_fpout, chr(0xFF), 1);
1264
        fputs($this->_fpout, chr($marker), 1);
1265
        fputs($this->_fpout, chr((($length + 2) & 0x0000FF00) >> 8), 1);
1266
        fputs($this->_fpout, chr((($length + 2) & 0x000000FF) >> 0), 1);
1267
1268
        if (isset($data)) {
1269
            // Copy the generated data
1270
            fputs($this->_fpout, $data, $length);
1271
1272
            if ($origLength > 0) {   // Skip the original data
1273
                $result = @fseek($this->_fp, $origLength, SEEK_CUR);
1274
                // fseek doesn't seem to like HTTP 'files', but fgetc has no problem
1275
                if ($result != 0) {
1276
                    for ($i = 0; $i < $origLength; $i++) {
1277
                        fgetc($this->_fp);
1278
                    }
1279
                }
1280
            }
1281
        } else {
1282
            if ($marker == 0xDA) {  // Copy until EOF
1283
                while (!feof($this->_fp)) {
1284
                    $data = fread($this->_fp, 1024 * 16);
1285
                    fputs($this->_fpout, $data, strlen($data));
1286
                }
1287
            } else { // Copy only $length bytes
1288
                $data = @fread($this->_fp, $length);
1289
                fputs($this->_fpout, $data, $length);
1290
            }
1291
        }
1292
1293
        return true;
1294
    }
1295
1296
    /**
1297
     * Gets basic info from the file - should work with non-JPEGs
1298
     *
1299
     * @author  Sebastian Delmont <[email protected]>
1300
     * @author  Andreas Gohr <[email protected]>
1301
     */
1302
    function _parseFileInfo() {
1303
        if (file_exists($this->_fileName) && is_file($this->_fileName)) {
1304
            $this->_info['file'] = array();
1305
            $this->_info['file']['Name'] = utf8_decodeFN(\dokuwiki\Utf8\PhpString::basename($this->_fileName));
1306
            $this->_info['file']['Path'] = fullpath($this->_fileName);
1307
            $this->_info['file']['Size'] = filesize($this->_fileName);
1308
            if ($this->_info['file']['Size'] < 1024) {
1309
                $this->_info['file']['NiceSize'] = $this->_info['file']['Size'] . 'B';
1310
            } elseif ($this->_info['file']['Size'] < (1024 * 1024)) {
1311
                $this->_info['file']['NiceSize'] = round($this->_info['file']['Size'] / 1024) . 'KB';
1312
            } elseif ($this->_info['file']['Size'] < (1024 * 1024 * 1024)) {
1313
                $this->_info['file']['NiceSize'] = round($this->_info['file']['Size'] / (1024*1024)) . 'MB';
1314
            } else {
1315
                $this->_info['file']['NiceSize'] = $this->_info['file']['Size'] . 'B';
1316
            }
1317
            $this->_info['file']['UnixTime'] = filemtime($this->_fileName);
1318
1319
            // get image size directly from file
1320
            if ($size = getimagesize($this->_fileName)) {
1321
                $this->_info['file']['Width'] = $size[0];
1322
                $this->_info['file']['Height'] = $size[1];
1323
1324
                // set mime types and formats
1325
                // http://php.net/manual/en/function.getimagesize.php
1326
                // http://php.net/manual/en/function.image-type-to-mime-type.php
1327
                switch ($size[2]) {
1328
                    case 1:
1329
                        $this->_info['file']['Mime'] = 'image/gif';
1330
                        $this->_info['file']['Format'] = 'GIF';
1331
                        break;
1332
                    case 2:
1333
                        $this->_info['file']['Mime'] = 'image/jpeg';
1334
                        $this->_info['file']['Format'] = 'JPEG';
1335
                        break;
1336
                    case 3:
1337
                        $this->_info['file']['Mime'] = 'image/png';
1338
                        $this->_info['file']['Format'] = 'PNG';
1339
                        break;
1340
                    case 4:
1341
                        $this->_info['file']['Mime'] = 'application/x-shockwave-flash';
1342
                        $this->_info['file']['Format'] = 'SWF';
1343
                        break;
1344
                    case 5:
1345
                        $this->_info['file']['Mime'] = 'image/psd';
1346
                        $this->_info['file']['Format'] = 'PSD';
1347
                        break;
1348
                    case 6:
1349
                        $this->_info['file']['Mime'] = 'image/bmp';
1350
                        $this->_info['file']['Format'] = 'BMP';
1351
                        break;
1352
                    case 7:
1353
                        $this->_info['file']['Mime'] = 'image/tiff';
1354
                        $this->_info['file']['Format'] = 'TIFF (Intel)';
1355
                        break;
1356
                    case 8:
1357
                        $this->_info['file']['Mime'] = 'image/tiff';
1358
                        $this->_info['file']['Format'] = 'TIFF (Motorola)';
1359
                        break;
1360
                    case 9:
1361
                        $this->_info['file']['Mime'] = 'application/octet-stream';
1362
                        $this->_info['file']['Format'] = 'JPC';
1363
                        break;
1364
                    case 10:
1365
                        $this->_info['file']['Mime'] = 'image/jp2';
1366
                        $this->_info['file']['Format'] = 'JP2';
1367
                        break;
1368
                    case 11:
1369
                        $this->_info['file']['Mime'] = 'application/octet-stream';
1370
                        $this->_info['file']['Format'] = 'JPX';
1371
                        break;
1372
                    case 12:
1373
                        $this->_info['file']['Mime'] = 'application/octet-stream';
1374
                        $this->_info['file']['Format'] = 'JB2';
1375
                        break;
1376
                    case 13:
1377
                        $this->_info['file']['Mime'] = 'application/x-shockwave-flash';
1378
                        $this->_info['file']['Format'] = 'SWC';
1379
                        break;
1380
                    case 14:
1381
                        $this->_info['file']['Mime'] = 'image/iff';
1382
                        $this->_info['file']['Format'] = 'IFF';
1383
                        break;
1384
                    case 15:
1385
                        $this->_info['file']['Mime'] = 'image/vnd.wap.wbmp';
1386
                        $this->_info['file']['Format'] = 'WBMP';
1387
                        break;
1388
                    case 16:
1389
                        $this->_info['file']['Mime'] = 'image/xbm';
1390
                        $this->_info['file']['Format'] = 'XBM';
1391
                        break;
1392
                    default:
1393
                        $this->_info['file']['Mime'] = 'image/unknown';
1394
                }
1395
            }
1396
        } else {
1397
            $this->_info['file'] = array();
1398
            $this->_info['file']['Name'] = \dokuwiki\Utf8\PhpString::basename($this->_fileName);
1399
            $this->_info['file']['Url'] = $this->_fileName;
1400
        }
1401
1402
        return true;
1403
    }
1404
1405
    /*************************************************************/
1406
    function _parseMarkerJFIF() {
1407
        if (!isset($this->_markers)) {
1408
            $this->_readJPEG();
1409
        }
1410
1411
        if ($this->_markers == null) {
1412
            return false;
1413
        }
1414
1415
        $data = null;
1416
        $count = count($this->_markers);
1417
        for ($i = 0; $i < $count; $i++) {
1418
            if ($this->_markers[$i]['marker'] == 0xE0) {
1419
                $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 4);
1420
                if ($signature == 'JFIF') {
1421
                    $data =& $this->_markers[$i]['data'];
1422
                    break;
1423
                }
1424
            }
1425
        }
1426
1427
        if ($data == null) {
1428
            $this->_info['jfif'] = false;
1429
            return false;
1430
        }
1431
1432
        $this->_info['jfif'] = array();
1433
1434
        $vmaj = $this->_getByte($data, 5);
1435
        $vmin = $this->_getByte($data, 6);
1436
1437
        $this->_info['jfif']['Version'] = sprintf('%d.%02d', $vmaj, $vmin);
1438
1439
        $units = $this->_getByte($data, 7);
1440
        switch ($units) {
1441
            case 0:
1442
                $this->_info['jfif']['Units'] = 'pixels';
1443
                break;
1444
            case 1:
1445
                $this->_info['jfif']['Units'] = 'dpi';
1446
                break;
1447
            case 2:
1448
                $this->_info['jfif']['Units'] = 'dpcm';
1449
                break;
1450
            default:
1451
                $this->_info['jfif']['Units'] = 'unknown';
1452
                break;
1453
        }
1454
1455
        $xdens = $this->_getShort($data, 8);
1456
        $ydens = $this->_getShort($data, 10);
1457
1458
        $this->_info['jfif']['XDensity'] = $xdens;
1459
        $this->_info['jfif']['YDensity'] = $ydens;
1460
1461
        $thumbx = $this->_getByte($data, 12);
1462
        $thumby = $this->_getByte($data, 13);
1463
1464
        $this->_info['jfif']['ThumbnailWidth'] = $thumbx;
1465
        $this->_info['jfif']['ThumbnailHeight'] = $thumby;
1466
1467
        return true;
1468
    }
1469
1470
    /*************************************************************/
1471
    function _parseMarkerSOF() {
1472
        if (!isset($this->_markers)) {
1473
            $this->_readJPEG();
1474
        }
1475
1476
        if ($this->_markers == null) {
1477
            return false;
1478
        }
1479
1480
        $data = null;
1481
        $count = count($this->_markers);
1482
        for ($i = 0; $i < $count; $i++) {
1483
            switch ($this->_markers[$i]['marker']) {
1484
                case 0xC0: // SOF0
1485
                case 0xC1: // SOF1
1486
                case 0xC2: // SOF2
1487
                case 0xC9: // SOF9
1488
                    $data =& $this->_markers[$i]['data'];
1489
                    $marker = $this->_markers[$i]['marker'];
1490
                    break;
1491
            }
1492
        }
1493
1494
        if ($data == null) {
1495
            $this->_info['sof'] = false;
1496
            return false;
1497
        }
1498
1499
        $pos = 0;
1500
        $this->_info['sof'] = array();
1501
1502
        switch ($marker) {
1503
            case 0xC0: // SOF0
1504
                $format = 'Baseline';
1505
                break;
1506
            case 0xC1: // SOF1
1507
                $format = 'Progessive';
1508
                break;
1509
            case 0xC2: // SOF2
1510
                $format = 'Non-baseline';
1511
                break;
1512
            case 0xC9: // SOF9
1513
                $format = 'Arithmetic';
1514
                break;
1515
            default:
1516
                return false;
1517
        }
1518
1519
        $this->_info['sof']['Format']          = $format;
1520
        $this->_info['sof']['SamplePrecision'] = $this->_getByte($data, $pos + 0);
1521
        $this->_info['sof']['ImageHeight']     = $this->_getShort($data, $pos + 1);
1522
        $this->_info['sof']['ImageWidth']      = $this->_getShort($data, $pos + 3);
1523
        $this->_info['sof']['ColorChannels']   = $this->_getByte($data, $pos + 5);
1524
1525
        return true;
1526
    }
1527
1528
    /**
1529
     * Parses the XMP data
1530
     *
1531
     * @author  Hakan Sandell <[email protected]>
1532
     */
1533
    function _parseMarkerXmp() {
1534
        if (!isset($this->_markers)) {
1535
            $this->_readJPEG();
1536
        }
1537
1538
        if ($this->_markers == null) {
1539
            return false;
1540
        }
1541
1542
        $data = null;
1543
        $count = count($this->_markers);
1544
        for ($i = 0; $i < $count; $i++) {
1545
            if ($this->_markers[$i]['marker'] == 0xE1) {
1546
                $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 29);
1547
                if ($signature == "http://ns.adobe.com/xap/1.0/\0") {
1548
                    $data = substr($this->_markers[$i]['data'], 29);
1549
                    break;
1550
                }
1551
            }
1552
        }
1553
1554
        if ($data == null) {
1555
            $this->_info['xmp'] = false;
1556
            return false;
1557
        }
1558
1559
        $parser = xml_parser_create();
1560
        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
1561
        xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
1562
        $result = xml_parse_into_struct($parser, $data, $values, $tags);
1563
        xml_parser_free($parser);
1564
1565
        if ($result == 0) {
1566
            $this->_info['xmp'] = false;
1567
            return false;
1568
        }
1569
1570
        $this->_info['xmp'] = array();
1571
        $count = count($values);
1572
        for ($i = 0; $i < $count; $i++) {
1573
            if ($values[$i]['tag'] == 'rdf:Description' && $values[$i]['type'] == 'open') {
1574
1575
                while ((++$i < $count) && ($values[$i]['tag'] != 'rdf:Description')) {
1576
                    $this->_parseXmpNode($values, $i, $this->_info['xmp'][$values[$i]['tag']], $count);
1577
                }
1578
            }
1579
        }
1580
        return true;
1581
    }
1582
1583
    /**
1584
     * Parses XMP nodes by recursion
1585
     *
1586
     * @author  Hakan Sandell <[email protected]>
1587
     *
1588
     * @param array $values
1589
     * @param int $i
1590
     * @param mixed $meta
1591
     * @param integer $count
1592
     */
1593
    function _parseXmpNode($values, &$i, &$meta, $count) {
1594
        if ($values[$i]['type'] == 'close') return;
1595
1596
        if ($values[$i]['type'] == 'complete') {
1597
            // Simple Type property
1598
            $meta = $values[$i]['value'];
1599
            return;
1600
        }
1601
1602
        $i++;
1603
        if ($i >= $count) return;
1604
1605
        if ($values[$i]['tag'] == 'rdf:Bag' || $values[$i]['tag'] == 'rdf:Seq') {
1606
            // Array property
1607
            $meta = array();
1608
            while ($values[++$i]['tag'] == 'rdf:li') {
1609
                $this->_parseXmpNode($values, $i, $meta[], $count);
1610
            }
1611
            $i++; // skip closing Bag/Seq tag
1612
1613
        } elseif ($values[$i]['tag'] == 'rdf:Alt') {
1614
            // Language Alternative property, only the first (default) value is used
1615
            if ($values[$i]['type'] == 'open') {
1616
                $i++;
1617
                $this->_parseXmpNode($values, $i, $meta, $count);
1618
                while ((++$i < $count) && ($values[$i]['tag'] != 'rdf:Alt'));
1619
                $i++; // skip closing Alt tag
1620
            }
1621
1622
        } else {
1623
            // Structure property
1624
            $meta = array();
1625
            $startTag = $values[$i-1]['tag'];
1626
            do {
1627
                $this->_parseXmpNode($values, $i, $meta[$values[$i]['tag']], $count);
1628
            } while ((++$i < $count) && ($values[$i]['tag'] != $startTag));
1629
        }
1630
    }
1631
1632
    /*************************************************************/
1633
    function _parseMarkerExif() {
1634
        if (!isset($this->_markers)) {
1635
            $this->_readJPEG();
1636
        }
1637
1638
        if ($this->_markers == null) {
1639
            return false;
1640
        }
1641
1642
        $data = null;
1643
        $count = count($this->_markers);
1644
        for ($i = 0; $i < $count; $i++) {
1645
            if ($this->_markers[$i]['marker'] == 0xE1) {
1646
                $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 6);
1647
                if ($signature == "Exif\0\0") {
1648
                    $data =& $this->_markers[$i]['data'];
1649
                    break;
1650
                }
1651
            }
1652
        }
1653
1654
        if ($data == null) {
1655
            $this->_info['exif'] = false;
1656
            return false;
1657
        }
1658
        $pos = 6;
1659
        $this->_info['exif'] = array();
1660
1661
        // We don't increment $pos after this because Exif uses offsets relative to this point
1662
1663
        $byteAlign = $this->_getShort($data, $pos + 0);
1664
1665
        if ($byteAlign == 0x4949) { // "II"
1666
            $isBigEndian = false;
1667
        } elseif ($byteAlign == 0x4D4D) { // "MM"
1668
            $isBigEndian = true;
1669
        } else {
1670
            return false; // Unexpected data
1671
        }
1672
1673
        $alignCheck = $this->_getShort($data, $pos + 2, $isBigEndian);
1674
        if ($alignCheck != 0x002A) // That's the expected value
1675
            return false; // Unexpected data
1676
1677
        if ($isBigEndian) {
1678
            $this->_info['exif']['ByteAlign'] = "Big Endian";
1679
        } else {
1680
            $this->_info['exif']['ByteAlign'] = "Little Endian";
1681
        }
1682
1683
        $offsetIFD0 = $this->_getLong($data, $pos + 4, $isBigEndian);
1684
        if ($offsetIFD0 < 8)
1685
            return false; // Unexpected data
1686
1687
        $offsetIFD1 = $this->_readIFD($data, $pos, $offsetIFD0, $isBigEndian, 'ifd0');
1688
        if ($offsetIFD1 != 0)
1689
            $this->_readIFD($data, $pos, $offsetIFD1, $isBigEndian, 'ifd1');
0 ignored issues
show
It seems like $offsetIFD1 defined by $this->_readIFD($data, $..., $isBigEndian, 'ifd0') on line 1687 can also be of type false; however, JpegMeta::_readIFD() does only seem to accept integer, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1690
1691
        return true;
1692
    }
1693
1694
    /*************************************************************/
1695
1696
    /**
1697
     * @param mixed $data
1698
     * @param integer $base
1699
     * @param integer $offset
1700
     * @param boolean $isBigEndian
1701
     * @param string $mode
1702
     *
1703
     * @return int
1704
     */
1705
    function _readIFD($data, $base, $offset, $isBigEndian, $mode) {
1706
        $EXIFTags = $this->_exifTagNames($mode);
1707
1708
        $numEntries = $this->_getShort($data, $base + $offset, $isBigEndian);
1709
        $offset += 2;
1710
1711
        $exifTIFFOffset = 0;
1712
        $exifTIFFLength = 0;
1713
        $exifThumbnailOffset = 0;
1714
        $exifThumbnailLength = 0;
1715
1716
        for ($i = 0; $i < $numEntries; $i++) {
1717
            $tag = $this->_getShort($data, $base + $offset, $isBigEndian);
1718
            $offset += 2;
1719
            $type = $this->_getShort($data, $base + $offset, $isBigEndian);
1720
            $offset += 2;
1721
            $count = $this->_getLong($data, $base + $offset, $isBigEndian);
1722
            $offset += 4;
1723
1724
            if (($type < 1) || ($type > 12))
1725
                return false; // Unexpected Type
1726
1727
            $typeLengths = array( -1, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 );
1728
1729
            $dataLength = $typeLengths[$type] * $count;
1730
            if ($dataLength > 4) {
1731
                $dataOffset = $this->_getLong($data, $base + $offset, $isBigEndian);
1732
                $rawValue = $this->_getFixedString($data, $base + $dataOffset, $dataLength);
1733
            } else {
1734
                $rawValue = $this->_getFixedString($data, $base + $offset, $dataLength);
1735
            }
1736
            $offset += 4;
1737
1738
            switch ($type) {
1739
                case 1:    // UBYTE
1740
                    if ($count == 1) {
1741
                        $value = $this->_getByte($rawValue, 0);
1742
                    } else {
1743
                        $value = array();
1744
                        for ($j = 0; $j < $count; $j++)
1745
                            $value[$j] = $this->_getByte($rawValue, $j);
1746
                    }
1747
                    break;
1748
                case 2:    // ASCII
1749
                    $value = $rawValue;
1750
                    break;
1751
                case 3:    // USHORT
1752
                    if ($count == 1) {
1753
                        $value = $this->_getShort($rawValue, 0, $isBigEndian);
1754
                    } else {
1755
                        $value = array();
1756
                        for ($j = 0; $j < $count; $j++)
1757
                            $value[$j] = $this->_getShort($rawValue, $j * 2, $isBigEndian);
1758
                    }
1759
                    break;
1760
                case 4:    // ULONG
1761
                    if ($count == 1) {
1762
                        $value = $this->_getLong($rawValue, 0, $isBigEndian);
1763
                    } else {
1764
                        $value = array();
1765
                        for ($j = 0; $j < $count; $j++)
1766
                            $value[$j] = $this->_getLong($rawValue, $j * 4, $isBigEndian);
1767
                    }
1768
                    break;
1769
                case 5:    // URATIONAL
1770
                    if ($count == 1) {
1771
                        $a = $this->_getLong($rawValue, 0, $isBigEndian);
1772
                        $b = $this->_getLong($rawValue, 4, $isBigEndian);
1773
                        $value = array();
1774
                        $value['val'] = 0;
1775
                        $value['num'] = $a;
1776
                        $value['den'] = $b;
1777
                        if (($a != 0) && ($b != 0)) {
1778
                            $value['val'] = $a / $b;
1779
                        }
1780
                    } else {
1781
                        $value = array();
1782
                        for ($j = 0; $j < $count; $j++) {
1783
                            $a = $this->_getLong($rawValue, $j * 8, $isBigEndian);
1784
                            $b = $this->_getLong($rawValue, ($j * 8) + 4, $isBigEndian);
1785
                            $value = array();
1786
                            $value[$j]['val'] = 0;
1787
                            $value[$j]['num'] = $a;
1788
                            $value[$j]['den'] = $b;
1789
                            if (($a != 0) && ($b != 0))
1790
                                $value[$j]['val'] = $a / $b;
1791
                        }
1792
                    }
1793
                    break;
1794
                case 6:    // SBYTE
1795
                    if ($count == 1) {
1796
                        $value = $this->_getByte($rawValue, 0);
1797
                    } else {
1798
                        $value = array();
1799
                        for ($j = 0; $j < $count; $j++)
1800
                            $value[$j] = $this->_getByte($rawValue, $j);
1801
                    }
1802
                    break;
1803
                case 7:    // UNDEFINED
1804
                    $value = $rawValue;
1805
                    break;
1806
                case 8:    // SSHORT
1807
                    if ($count == 1) {
1808
                        $value = $this->_getShort($rawValue, 0, $isBigEndian);
1809
                    } else {
1810
                        $value = array();
1811
                        for ($j = 0; $j < $count; $j++)
1812
                            $value[$j] = $this->_getShort($rawValue, $j * 2, $isBigEndian);
1813
                    }
1814
                    break;
1815
                case 9:    // SLONG
1816
                    if ($count == 1) {
1817
                        $value = $this->_getLong($rawValue, 0, $isBigEndian);
1818
                    } else {
1819
                        $value = array();
1820
                        for ($j = 0; $j < $count; $j++)
1821
                            $value[$j] = $this->_getLong($rawValue, $j * 4, $isBigEndian);
1822
                    }
1823
                    break;
1824
                case 10:   // SRATIONAL
1825
                    if ($count == 1) {
1826
                        $a = $this->_getLong($rawValue, 0, $isBigEndian);
1827
                        $b = $this->_getLong($rawValue, 4, $isBigEndian);
1828
                        $value = array();
1829
                        $value['val'] = 0;
1830
                        $value['num'] = $a;
1831
                        $value['den'] = $b;
1832
                        if (($a != 0) && ($b != 0))
1833
                            $value['val'] = $a / $b;
1834
                    } else {
1835
                        $value = array();
1836
                        for ($j = 0; $j < $count; $j++) {
1837
                            $a = $this->_getLong($rawValue, $j * 8, $isBigEndian);
1838
                            $b = $this->_getLong($rawValue, ($j * 8) + 4, $isBigEndian);
1839
                            $value = array();
1840
                            $value[$j]['val'] = 0;
1841
                            $value[$j]['num'] = $a;
1842
                            $value[$j]['den'] = $b;
1843
                            if (($a != 0) && ($b != 0))
1844
                                $value[$j]['val'] = $a / $b;
1845
                        }
1846
                    }
1847
                    break;
1848
                case 11:   // FLOAT
1849
                    $value = $rawValue;
1850
                    break;
1851
1852
                case 12:   // DFLOAT
1853
                    $value = $rawValue;
1854
                    break;
1855
                default:
1856
                    return false; // Unexpected Type
1857
            }
1858
1859
            $tagName = '';
1860
            if (($mode == 'ifd0') && ($tag == 0x8769)) {  // ExifIFDOffset
1861
                $this->_readIFD($data, $base, $value, $isBigEndian, 'exif');
1862
            } elseif (($mode == 'ifd0') && ($tag == 0x8825)) {  // GPSIFDOffset
1863
                $this->_readIFD($data, $base, $value, $isBigEndian, 'gps');
1864
            } elseif (($mode == 'ifd1') && ($tag == 0x0111)) {  // TIFFStripOffsets
1865
                $exifTIFFOffset = $value;
1866
            } elseif (($mode == 'ifd1') && ($tag == 0x0117)) {  // TIFFStripByteCounts
1867
                $exifTIFFLength = $value;
1868
            } elseif (($mode == 'ifd1') && ($tag == 0x0201)) {  // TIFFJFIFOffset
1869
                $exifThumbnailOffset = $value;
1870
            } elseif (($mode == 'ifd1') && ($tag == 0x0202)) {  // TIFFJFIFLength
1871
                $exifThumbnailLength = $value;
1872
            } elseif (($mode == 'exif') && ($tag == 0xA005)) {  // InteropIFDOffset
1873
                $this->_readIFD($data, $base, $value, $isBigEndian, 'interop');
1874
            }
1875
            // elseif (($mode == 'exif') && ($tag == 0x927C)) {  // MakerNote
1876
            // }
1877
            else {
1878
                if (isset($EXIFTags[$tag])) {
1879
                    $tagName = $EXIFTags[$tag];
1880
                    if (isset($this->_info['exif'][$tagName])) {
1881
                        if (!is_array($this->_info['exif'][$tagName])) {
1882
                            $aux = array();
1883
                            $aux[0] = $this->_info['exif'][$tagName];
1884
                            $this->_info['exif'][$tagName] = $aux;
1885
                        }
1886
1887
                        $this->_info['exif'][$tagName][count($this->_info['exif'][$tagName])] = $value;
1888
                    } else {
1889
                        $this->_info['exif'][$tagName] = $value;
1890
                    }
1891
                }
1892
                /*
1893
                 else {
1894
                    echo sprintf("<h1>Unknown tag %02x (t: %d l: %d) %s in %s</h1>", $tag, $type, $count, $mode, $this->_fileName);
1895
                    // Unknown Tags will be ignored!!!
1896
                    // That's because the tag might be a pointer (like the Exif tag)
1897
                    // and saving it without saving the data it points to might
1898
                    // create an invalid file.
1899
                }
1900
                */
1901
            }
1902
        }
1903
1904
        if (($exifThumbnailOffset > 0) && ($exifThumbnailLength > 0)) {
1905
            $this->_info['exif']['JFIFThumbnail'] = $this->_getFixedString($data, $base + $exifThumbnailOffset, $exifThumbnailLength);
1906
        }
1907
1908
        if (($exifTIFFOffset > 0) && ($exifTIFFLength > 0)) {
1909
            $this->_info['exif']['TIFFStrips'] = $this->_getFixedString($data, $base + $exifTIFFOffset, $exifTIFFLength);
1910
        }
1911
1912
        $nextOffset = $this->_getLong($data, $base + $offset, $isBigEndian);
1913
        return $nextOffset;
1914
    }
1915
1916
    /*************************************************************/
1917
    function & _createMarkerExif() {
1918
        $data = null;
1919
        $count = count($this->_markers);
1920
        for ($i = 0; $i < $count; $i++) {
1921
            if ($this->_markers[$i]['marker'] == 0xE1) {
1922
                $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 6);
1923
                if ($signature == "Exif\0\0") {
1924
                    $data =& $this->_markers[$i]['data'];
1925
                    break;
1926
                }
1927
            }
1928
        }
1929
1930
        if (!isset($this->_info['exif'])) {
1931
            return false;
1932
        }
1933
1934
        $data = "Exif\0\0";
1935
        $pos = 6;
1936
        $offsetBase = 6;
1937
1938
        if (isset($this->_info['exif']['ByteAlign']) && ($this->_info['exif']['ByteAlign'] == "Big Endian")) {
1939
            $isBigEndian = true;
1940
            $aux = "MM";
1941
            $pos = $this->_putString($data, $pos, $aux);
1942
        } else {
1943
            $isBigEndian = false;
1944
            $aux = "II";
1945
            $pos = $this->_putString($data, $pos, $aux);
1946
        }
1947
        $pos = $this->_putShort($data, $pos, 0x002A, $isBigEndian);
1948
        $pos = $this->_putLong($data, $pos, 0x00000008, $isBigEndian); // IFD0 Offset is always 8
1949
1950
        $ifd0 =& $this->_getIFDEntries($isBigEndian, 'ifd0');
1951
        $ifd1 =& $this->_getIFDEntries($isBigEndian, 'ifd1');
1952
1953
        $pos = $this->_writeIFD($data, $pos, $offsetBase, $ifd0, $isBigEndian, true);
1954
        $pos = $this->_writeIFD($data, $pos, $offsetBase, $ifd1, $isBigEndian, false);
1955
1956
        return $data;
1957
    }
1958
1959
    /*************************************************************/
1960
1961
    /**
1962
     * @param mixed $data
1963
     * @param integer $pos
1964
     * @param integer $offsetBase
1965
     * @param array $entries
1966
     * @param boolean $isBigEndian
1967
     * @param boolean $hasNext
1968
     *
1969
     * @return mixed
1970
     */
1971
    function _writeIFD(&$data, $pos, $offsetBase, &$entries, $isBigEndian, $hasNext) {
1972
        $tiffData = null;
1973
        $tiffDataOffsetPos = -1;
1974
1975
        $entryCount = count($entries);
1976
1977
        $dataPos = $pos + 2 + ($entryCount * 12) + 4;
1978
        $pos = $this->_putShort($data, $pos, $entryCount, $isBigEndian);
1979
1980
        for ($i = 0; $i < $entryCount; $i++) {
1981
            $tag = $entries[$i]['tag'];
1982
            $type = $entries[$i]['type'];
1983
1984
            if ($type == -99) { // SubIFD
1985
                $pos = $this->_putShort($data, $pos, $tag, $isBigEndian);
1986
                $pos = $this->_putShort($data, $pos, 0x04, $isBigEndian); // LONG
1987
                $pos = $this->_putLong($data, $pos, 0x01, $isBigEndian); // Count = 1
1988
                $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian);
1989
1990
                $dataPos = $this->_writeIFD($data, $dataPos, $offsetBase, $entries[$i]['value'], $isBigEndian, false);
1991
            } elseif ($type == -98) { // TIFF Data
1992
                $pos = $this->_putShort($data, $pos, $tag, $isBigEndian);
1993
                $pos = $this->_putShort($data, $pos, 0x04, $isBigEndian); // LONG
1994
                $pos = $this->_putLong($data, $pos, 0x01, $isBigEndian); // Count = 1
1995
                $tiffDataOffsetPos = $pos;
1996
                $pos = $this->_putLong($data, $pos, 0x00, $isBigEndian); // For Now
1997
                $tiffData =& $entries[$i]['value'] ;
1998
            } else { // Regular Entry
1999
                $pos = $this->_putShort($data, $pos, $tag, $isBigEndian);
2000
                $pos = $this->_putShort($data, $pos, $type, $isBigEndian);
2001
                $pos = $this->_putLong($data, $pos, $entries[$i]['count'], $isBigEndian);
2002
                if (strlen($entries[$i]['value']) > 4) {
2003
                    $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian);
2004
                    $dataPos = $this->_putString($data, $dataPos, $entries[$i]['value']);
2005
                } else {
2006
                    $val = str_pad($entries[$i]['value'], 4, "\0");
2007
                    $pos = $this->_putString($data, $pos, $val);
2008
                }
2009
            }
2010
        }
2011
2012
        if ($tiffData != null) {
2013
            $this->_putLong($data, $tiffDataOffsetPos, $dataPos - $offsetBase, $isBigEndian);
2014
            $dataPos = $this->_putString($data, $dataPos, $tiffData);
2015
        }
2016
2017
        if ($hasNext) {
2018
            $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian);
2019
        } else {
2020
            $pos = $this->_putLong($data, $pos, 0, $isBigEndian);
2021
        }
2022
2023
        return $dataPos;
2024
    }
2025
2026
    /*************************************************************/
2027
2028
    /**
2029
     * @param boolean $isBigEndian
2030
     * @param string $mode
2031
     *
2032
     * @return array
2033
     */
2034
    function & _getIFDEntries($isBigEndian, $mode) {
2035
        $EXIFNames = $this->_exifTagNames($mode);
2036
        $EXIFTags = $this->_exifNameTags($mode);
2037
        $EXIFTypeInfo = $this->_exifTagTypes($mode);
2038
2039
        $ifdEntries = array();
2040
        $entryCount = 0;
2041
2042
        foreach($EXIFNames as $tag => $name) {
2043
            $type = $EXIFTypeInfo[$tag][0];
2044
            $count = $EXIFTypeInfo[$tag][1];
2045
            $value = null;
2046
2047
            if (($mode == 'ifd0') && ($tag == 0x8769)) {  // ExifIFDOffset
2048
                if (isset($this->_info['exif']['EXIFVersion'])) {
2049
                    $value =& $this->_getIFDEntries($isBigEndian, "exif");
2050
                    $type = -99;
2051
                }
2052
                else {
2053
                    $value = null;
2054
                }
2055
            } elseif (($mode == 'ifd0') && ($tag == 0x8825)) {  // GPSIFDOffset
2056
                if (isset($this->_info['exif']['GPSVersionID'])) {
2057
                    $value =& $this->_getIFDEntries($isBigEndian, "gps");
2058
                    $type = -99;
2059
                } else {
2060
                    $value = null;
2061
                }
2062
            } elseif (($mode == 'ifd1') && ($tag == 0x0111)) {  // TIFFStripOffsets
2063
                if (isset($this->_info['exif']['TIFFStrips'])) {
2064
                    $value =& $this->_info['exif']['TIFFStrips'];
2065
                    $type = -98;
2066
                } else {
2067
                    $value = null;
2068
                }
2069
            } elseif (($mode == 'ifd1') && ($tag == 0x0117)) {  // TIFFStripByteCounts
2070
                if (isset($this->_info['exif']['TIFFStrips'])) {
2071
                    $value = strlen($this->_info['exif']['TIFFStrips']);
2072
                } else {
2073
                    $value = null;
2074
                }
2075
            } elseif (($mode == 'ifd1') && ($tag == 0x0201)) {  // TIFFJFIFOffset
2076
                if (isset($this->_info['exif']['JFIFThumbnail'])) {
2077
                    $value =& $this->_info['exif']['JFIFThumbnail'];
2078
                    $type = -98;
2079
                } else {
2080
                    $value = null;
2081
                }
2082
            } elseif (($mode == 'ifd1') && ($tag == 0x0202)) {  // TIFFJFIFLength
2083
                if (isset($this->_info['exif']['JFIFThumbnail'])) {
2084
                    $value = strlen($this->_info['exif']['JFIFThumbnail']);
2085
                } else {
2086
                    $value = null;
2087
                }
2088
            } elseif (($mode == 'exif') && ($tag == 0xA005)) {  // InteropIFDOffset
2089
                if (isset($this->_info['exif']['InteroperabilityIndex'])) {
2090
                    $value =& $this->_getIFDEntries($isBigEndian, "interop");
2091
                    $type = -99;
2092
                } else {
2093
                    $value = null;
2094
                }
2095
            } elseif (isset($this->_info['exif'][$name])) {
2096
                $origValue =& $this->_info['exif'][$name];
2097
2098
                // This makes it easier to process variable size elements
2099
                if (!is_array($origValue) || isset($origValue['val'])) {
2100
                    unset($origValue); // Break the reference
2101
                    $origValue = array($this->_info['exif'][$name]);
2102
                }
2103
                $origCount = count($origValue);
2104
2105
                if ($origCount == 0 ) {
2106
                    $type = -1;  // To ignore this field
2107
                }
2108
2109
                $value = " ";
2110
2111
                switch ($type) {
2112
                    case 1:    // UBYTE
2113
                        if ($count == 0) {
2114
                            $count = $origCount;
2115
                        }
2116
2117
                        $j = 0;
2118
                        while (($j < $count) && ($j < $origCount)) {
2119
2120
                            $this->_putByte($value, $j, $origValue[$j]);
2121
                            $j++;
2122
                        }
2123
2124
                        while ($j < $count) {
2125
                            $this->_putByte($value, $j, 0);
2126
                            $j++;
2127
                        }
2128
                        break;
2129
                    case 2:    // ASCII
2130
                        $v = strval($origValue[0]);
2131
                        if (($count != 0) && (strlen($v) > $count)) {
2132
                            $v = substr($v, 0, $count);
2133
                        }
2134
                        elseif (($count > 0) && (strlen($v) < $count)) {
2135
                            $v = str_pad($v, $count, "\0");
2136
                        }
2137
2138
                        $count = strlen($v);
2139
2140
                        $this->_putString($value, 0, $v);
2141
                        break;
2142
                    case 3:    // USHORT
2143
                        if ($count == 0) {
2144
                            $count = $origCount;
2145
                        }
2146
2147
                        $j = 0;
2148
                        while (($j < $count) && ($j < $origCount)) {
2149
                            $this->_putShort($value, $j * 2, $origValue[$j], $isBigEndian);
2150
                            $j++;
2151
                        }
2152
2153
                        while ($j < $count) {
2154
                            $this->_putShort($value, $j * 2, 0, $isBigEndian);
2155
                            $j++;
2156
                        }
2157
                        break;
2158
                    case 4:    // ULONG
2159
                        if ($count == 0) {
2160
                            $count = $origCount;
2161
                        }
2162
2163
                        $j = 0;
2164
                        while (($j < $count) && ($j < $origCount)) {
2165
                            $this->_putLong($value, $j * 4, $origValue[$j], $isBigEndian);
2166
                            $j++;
2167
                        }
2168
2169
                        while ($j < $count) {
2170
                            $this->_putLong($value, $j * 4, 0, $isBigEndian);
2171
                            $j++;
2172
                        }
2173
                        break;
2174
                    case 5:    // URATIONAL
2175
                        if ($count == 0) {
2176
                            $count = $origCount;
2177
                        }
2178
2179
                        $j = 0;
2180
                        while (($j < $count) && ($j < $origCount)) {
2181
                            $v = $origValue[$j];
2182
                            if (is_array($v)) {
2183
                                $a = $v['num'];
2184
                                $b = $v['den'];
2185
                            }
2186
                            else {
2187
                                $a = 0;
2188
                                $b = 0;
2189
                                // TODO: Allow other types and convert them
2190
                            }
2191
                            $this->_putLong($value, $j * 8, $a, $isBigEndian);
2192
                            $this->_putLong($value, ($j * 8) + 4, $b, $isBigEndian);
2193
                            $j++;
2194
                        }
2195
2196
                        while ($j < $count) {
2197
                            $this->_putLong($value, $j * 8, 0, $isBigEndian);
2198
                            $this->_putLong($value, ($j * 8) + 4, 0, $isBigEndian);
2199
                            $j++;
2200
                        }
2201
                        break;
2202
                    case 6:    // SBYTE
2203
                        if ($count == 0) {
2204
                            $count = $origCount;
2205
                        }
2206
2207
                        $j = 0;
2208
                        while (($j < $count) && ($j < $origCount)) {
2209
                            $this->_putByte($value, $j, $origValue[$j]);
2210
                            $j++;
2211
                        }
2212
2213
                        while ($j < $count) {
2214
                            $this->_putByte($value, $j, 0);
2215
                            $j++;
2216
                        }
2217
                        break;
2218
                    case 7:    // UNDEFINED
2219
                        $v = strval($origValue[0]);
2220
                        if (($count != 0) && (strlen($v) > $count)) {
2221
                            $v = substr($v, 0, $count);
2222
                        }
2223
                        elseif (($count > 0) && (strlen($v) < $count)) {
2224
                            $v = str_pad($v, $count, "\0");
2225
                        }
2226
2227
                        $count = strlen($v);
2228
2229
                        $this->_putString($value, 0, $v);
2230
                        break;
2231
                    case 8:    // SSHORT
2232
                        if ($count == 0) {
2233
                            $count = $origCount;
2234
                        }
2235
2236
                        $j = 0;
2237
                        while (($j < $count) && ($j < $origCount)) {
2238
                            $this->_putShort($value, $j * 2, $origValue[$j], $isBigEndian);
2239
                            $j++;
2240
                        }
2241
2242
                        while ($j < $count) {
2243
                            $this->_putShort($value, $j * 2, 0, $isBigEndian);
2244
                            $j++;
2245
                        }
2246
                        break;
2247
                    case 9:    // SLONG
2248
                        if ($count == 0) {
2249
                            $count = $origCount;
2250
                        }
2251
2252
                        $j = 0;
2253
                        while (($j < $count) && ($j < $origCount)) {
2254
                            $this->_putLong($value, $j * 4, $origValue[$j], $isBigEndian);
2255
                            $j++;
2256
                        }
2257
2258
                        while ($j < $count) {
2259
                            $this->_putLong($value, $j * 4, 0, $isBigEndian);
2260
                            $j++;
2261
                        }
2262
                        break;
2263
                    case 10:   // SRATIONAL
2264
                        if ($count == 0) {
2265
                            $count = $origCount;
2266
                        }
2267
2268
                        $j = 0;
2269
                        while (($j < $count) && ($j < $origCount)) {
2270
                            $v = $origValue[$j];
2271
                            if (is_array($v)) {
2272
                                $a = $v['num'];
2273
                                $b = $v['den'];
2274
                            }
2275
                            else {
2276
                                $a = 0;
2277
                                $b = 0;
2278
                                // TODO: Allow other types and convert them
2279
                            }
2280
2281
                            $this->_putLong($value, $j * 8, $a, $isBigEndian);
2282
                            $this->_putLong($value, ($j * 8) + 4, $b, $isBigEndian);
2283
                            $j++;
2284
                        }
2285
2286
                        while ($j < $count) {
2287
                            $this->_putLong($value, $j * 8, 0, $isBigEndian);
2288
                            $this->_putLong($value, ($j * 8) + 4, 0, $isBigEndian);
2289
                            $j++;
2290
                        }
2291
                        break;
2292
                    case 11:   // FLOAT
2293
                        if ($count == 0) {
2294
                            $count = $origCount;
2295
                        }
2296
2297
                        $j = 0;
2298
                        while (($j < $count) && ($j < $origCount)) {
2299
                            $v = strval($origValue[$j]);
2300
                            if (strlen($v) > 4) {
2301
                                $v = substr($v, 0, 4);
2302
                            }
2303
                            elseif (strlen($v) < 4) {
2304
                                $v = str_pad($v, 4, "\0");
2305
                            }
2306
                            $this->_putString($value, $j * 4, $v);
2307
                            $j++;
2308
                        }
2309
2310
                        while ($j < $count) {
2311
                            $v = "\0\0\0\0";
2312
                            $this->_putString($value, $j * 4, $v);
2313
                            $j++;
2314
                        }
2315
                        break;
2316
                    case 12:   // DFLOAT
2317
                        if ($count == 0) {
2318
                            $count = $origCount;
2319
                        }
2320
2321
                        $j = 0;
2322
                        while (($j < $count) && ($j < $origCount)) {
2323
                            $v = strval($origValue[$j]);
2324
                            if (strlen($v) > 8) {
2325
                                $v = substr($v, 0, 8);
2326
                            }
2327
                            elseif (strlen($v) < 8) {
2328
                                $v = str_pad($v, 8, "\0");
2329
                            }
2330
                            $this->_putString($value, $j * 8, $v);
2331
                            $j++;
2332
                        }
2333
2334
                        while ($j < $count) {
2335
                            $v = "\0\0\0\0\0\0\0\0";
2336
                            $this->_putString($value, $j * 8, $v);
2337
                            $j++;
2338
                        }
2339
                        break;
2340
                    default:
2341
                        $value = null;
2342
                        break;
2343
                }
2344
            }
2345
2346
            if ($value != null) {
2347
                $ifdEntries[$entryCount] = array();
2348
                $ifdEntries[$entryCount]['tag'] = $tag;
2349
                $ifdEntries[$entryCount]['type'] = $type;
2350
                $ifdEntries[$entryCount]['count'] = $count;
2351
                $ifdEntries[$entryCount]['value'] = $value;
2352
2353
                $entryCount++;
2354
            }
2355
        }
2356
2357
        return $ifdEntries;
2358
    }
2359
2360
    /*************************************************************/
2361
    function _parseMarkerAdobe() {
2362
        if (!isset($this->_markers)) {
2363
            $this->_readJPEG();
2364
        }
2365
2366
        if ($this->_markers == null) {
2367
            return false;
2368
        }
2369
2370
        $data = null;
2371
        $count = count($this->_markers);
2372
        for ($i = 0; $i < $count; $i++) {
2373
            if ($this->_markers[$i]['marker'] == 0xED) {
2374
                $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 14);
2375
                if ($signature == "Photoshop 3.0\0") {
2376
                    $data =& $this->_markers[$i]['data'];
2377
                    break;
2378
                }
2379
            }
2380
        }
2381
2382
        if ($data == null) {
2383
            $this->_info['adobe'] = false;
2384
            $this->_info['iptc'] = false;
2385
            return false;
2386
        }
2387
        $pos = 14;
2388
        $this->_info['adobe'] = array();
2389
        $this->_info['adobe']['raw'] = array();
2390
        $this->_info['iptc'] = array();
2391
2392
        $datasize = strlen($data);
2393
2394
        while ($pos < $datasize) {
2395
            $signature = $this->_getFixedString($data, $pos, 4);
2396
            if ($signature != '8BIM')
2397
                return false;
2398
            $pos += 4;
2399
2400
            $type = $this->_getShort($data, $pos);
2401
            $pos += 2;
2402
2403
            $strlen = $this->_getByte($data, $pos);
2404
            $pos += 1;
2405
            $header = '';
2406
            for ($i = 0; $i < $strlen; $i++) {
2407
                $header .= $data[$pos + $i];
2408
            }
2409
            $pos += $strlen + 1 - ($strlen % 2);  // The string is padded to even length, counting the length byte itself
2410
2411
            $length = $this->_getLong($data, $pos);
2412
            $pos += 4;
2413
2414
            $basePos = $pos;
2415
2416
            switch ($type) {
2417
                case 0x0404: // Caption (IPTC Data)
2418
                    $pos = $this->_readIPTC($data, $pos);
2419
                    if ($pos == false)
2420
                        return false;
2421
                    break;
2422
                case 0x040A: // CopyrightFlag
2423
                    $this->_info['adobe']['CopyrightFlag'] = $this->_getByte($data, $pos);
2424
                    $pos += $length;
2425
                    break;
2426
                case 0x040B: // ImageURL
2427
                    $this->_info['adobe']['ImageURL'] = $this->_getFixedString($data, $pos, $length);
2428
                    $pos += $length;
2429
                    break;
2430
                case 0x040C: // Thumbnail
2431
                    $aux = $this->_getLong($data, $pos);
2432
                    $pos += 4;
2433
                    if ($aux == 1) {
2434
                        $this->_info['adobe']['ThumbnailWidth'] = $this->_getLong($data, $pos);
2435
                        $pos += 4;
2436
                        $this->_info['adobe']['ThumbnailHeight'] = $this->_getLong($data, $pos);
2437
                        $pos += 4;
2438
2439
                        $pos += 16; // Skip some data
2440
2441
                        $this->_info['adobe']['ThumbnailData'] = $this->_getFixedString($data, $pos, $length - 28);
2442
                        $pos += $length - 28;
2443
                    }
2444
                    break;
2445
                default:
2446
                    break;
2447
            }
2448
2449
            // We save all blocks, even those we recognized
2450
            $label = sprintf('8BIM_0x%04x', $type);
2451
            $this->_info['adobe']['raw'][$label] = array();
2452
            $this->_info['adobe']['raw'][$label]['type'] = $type;
2453
            $this->_info['adobe']['raw'][$label]['header'] = $header;
2454
            $this->_info['adobe']['raw'][$label]['data'] =& $this->_getFixedString($data, $basePos, $length);
2455
2456
            $pos = $basePos + $length + ($length % 2); // Even padding
2457
        }
2458
2459
    }
2460
2461
    /*************************************************************/
2462
    function _readIPTC(&$data, $pos = 0) {
2463
        $totalLength = strlen($data);
2464
2465
        $IPTCTags = $this->_iptcTagNames();
2466
2467
        while ($pos < ($totalLength - 5)) {
2468
            $signature = $this->_getShort($data, $pos);
2469
            if ($signature != 0x1C02)
2470
                return $pos;
2471
            $pos += 2;
2472
2473
            $type = $this->_getByte($data, $pos);
2474
            $pos += 1;
2475
            $length = $this->_getShort($data, $pos);
2476
            $pos += 2;
2477
2478
            $basePos = $pos;
2479
            $label = '';
2480
2481
            if (isset($IPTCTags[$type])) {
2482
                $label = $IPTCTags[$type];
2483
            } else {
2484
                $label = sprintf('IPTC_0x%02x', $type);
2485
            }
2486
2487
            if ($label != '') {
2488
                if (isset($this->_info['iptc'][$label])) {
2489
                    if (!is_array($this->_info['iptc'][$label])) {
2490
                        $aux = array();
2491
                        $aux[0] = $this->_info['iptc'][$label];
2492
                        $this->_info['iptc'][$label] = $aux;
2493
                    }
2494
                    $this->_info['iptc'][$label][ count($this->_info['iptc'][$label]) ] = $this->_getFixedString($data, $pos, $length);
2495
                } else {
2496
                    $this->_info['iptc'][$label] = $this->_getFixedString($data, $pos, $length);
2497
                }
2498
            }
2499
2500
            $pos = $basePos + $length; // No padding
2501
        }
2502
        return $pos;
2503
    }
2504
2505
    /*************************************************************/
2506
    function & _createMarkerAdobe() {
2507
        if (isset($this->_info['iptc'])) {
2508
            if (!isset($this->_info['adobe'])) {
2509
                $this->_info['adobe'] = array();
2510
            }
2511
            if (!isset($this->_info['adobe']['raw'])) {
2512
                $this->_info['adobe']['raw'] = array();
2513
            }
2514
            if (!isset($this->_info['adobe']['raw']['8BIM_0x0404'])) {
2515
                $this->_info['adobe']['raw']['8BIM_0x0404'] = array();
2516
            }
2517
            $this->_info['adobe']['raw']['8BIM_0x0404']['type'] = 0x0404;
2518
            $this->_info['adobe']['raw']['8BIM_0x0404']['header'] = "Caption";
2519
            $this->_info['adobe']['raw']['8BIM_0x0404']['data'] =& $this->_writeIPTC();
2520
        }
2521
2522
        if (isset($this->_info['adobe']['raw']) && (count($this->_info['adobe']['raw']) > 0)) {
2523
            $data = "Photoshop 3.0\0";
2524
            $pos = 14;
2525
2526
            reset($this->_info['adobe']['raw']);
2527
            foreach ($this->_info['adobe']['raw'] as $value){
2528
                $pos = $this->_write8BIM(
2529
                        $data,
2530
                        $pos,
2531
                        $value['type'],
2532
                        $value['header'],
2533
                        $value['data'] );
2534
            }
2535
        }
2536
2537
        return $data;
2538
    }
2539
2540
    /*************************************************************/
2541
2542
    /**
2543
     * @param mixed $data
2544
     * @param integer $pos
2545
     *
2546
     * @param string $type
2547
     * @param string $header
2548
     * @param mixed $value
2549
     *
2550
     * @return int|mixed
2551
     */
2552
    function _write8BIM(&$data, $pos, $type, $header, &$value) {
2553
        $signature = "8BIM";
2554
2555
        $pos = $this->_putString($data, $pos, $signature);
2556
        $pos = $this->_putShort($data, $pos, $type);
2557
2558
        $len = strlen($header);
2559
2560
        $pos = $this->_putByte($data, $pos, $len);
2561
        $pos = $this->_putString($data, $pos, $header);
2562
        if (($len % 2) == 0) {  // Even padding, including the length byte
2563
            $pos = $this->_putByte($data, $pos, 0);
2564
        }
2565
2566
        $len = strlen($value);
2567
        $pos = $this->_putLong($data, $pos, $len);
2568
        $pos = $this->_putString($data, $pos, $value);
2569
        if (($len % 2) != 0) {  // Even padding
2570
            $pos = $this->_putByte($data, $pos, 0);
2571
        }
2572
        return $pos;
2573
    }
2574
2575
    /*************************************************************/
2576
    function & _writeIPTC() {
2577
        $data = " ";
2578
        $pos = 0;
2579
2580
        $IPTCNames =& $this->_iptcNameTags();
2581
2582
        foreach($this->_info['iptc'] as $label => $value) {
2583
            $value =& $this->_info['iptc'][$label];
2584
            $type = -1;
2585
2586
            if (isset($IPTCNames[$label])) {
2587
                $type = $IPTCNames[$label];
2588
            }
2589
            elseif (substr($label, 0, 7) == "IPTC_0x") {
2590
                $type = hexdec(substr($label, 7, 2));
2591
            }
2592
2593
            if ($type != -1) {
2594
                if (is_array($value)) {
2595
                    $vcnt = count($value);
2596
                    for ($i = 0; $i < $vcnt; $i++) {
2597
                        $pos = $this->_writeIPTCEntry($data, $pos, $type, $value[$i]);
2598
                    }
2599
                }
2600
                else {
2601
                    $pos = $this->_writeIPTCEntry($data, $pos, $type, $value);
2602
                }
2603
            }
2604
        }
2605
2606
        return $data;
2607
    }
2608
2609
    /*************************************************************/
2610
2611
    /**
2612
     * @param mixed $data
2613
     * @param integer $pos
2614
     *
2615
     * @param string $type
2616
     * @param mixed $value
2617
     *
2618
     * @return int|mixed
2619
     */
2620
    function _writeIPTCEntry(&$data, $pos, $type, &$value) {
2621
        $pos = $this->_putShort($data, $pos, 0x1C02);
2622
        $pos = $this->_putByte($data, $pos, $type);
2623
        $pos = $this->_putShort($data, $pos, strlen($value));
2624
        $pos = $this->_putString($data, $pos, $value);
2625
2626
        return $pos;
2627
    }
2628
2629
    /*************************************************************/
2630
    function _exifTagNames($mode) {
2631
        $tags = array();
2632
2633
        if ($mode == 'ifd0') {
2634
            $tags[0x010E] = 'ImageDescription';
2635
            $tags[0x010F] = 'Make';
2636
            $tags[0x0110] = 'Model';
2637
            $tags[0x0112] = 'Orientation';
2638
            $tags[0x011A] = 'XResolution';
2639
            $tags[0x011B] = 'YResolution';
2640
            $tags[0x0128] = 'ResolutionUnit';
2641
            $tags[0x0131] = 'Software';
2642
            $tags[0x0132] = 'DateTime';
2643
            $tags[0x013B] = 'Artist';
2644
            $tags[0x013E] = 'WhitePoint';
2645
            $tags[0x013F] = 'PrimaryChromaticities';
2646
            $tags[0x0211] = 'YCbCrCoefficients';
2647
            $tags[0x0212] = 'YCbCrSubSampling';
2648
            $tags[0x0213] = 'YCbCrPositioning';
2649
            $tags[0x0214] = 'ReferenceBlackWhite';
2650
            $tags[0x8298] = 'Copyright';
2651
            $tags[0x8769] = 'ExifIFDOffset';
2652
            $tags[0x8825] = 'GPSIFDOffset';
2653
        }
2654
        if ($mode == 'ifd1') {
2655
            $tags[0x00FE] = 'TIFFNewSubfileType';
2656
            $tags[0x00FF] = 'TIFFSubfileType';
2657
            $tags[0x0100] = 'TIFFImageWidth';
2658
            $tags[0x0101] = 'TIFFImageHeight';
2659
            $tags[0x0102] = 'TIFFBitsPerSample';
2660
            $tags[0x0103] = 'TIFFCompression';
2661
            $tags[0x0106] = 'TIFFPhotometricInterpretation';
2662
            $tags[0x0107] = 'TIFFThreshholding';
2663
            $tags[0x0108] = 'TIFFCellWidth';
2664
            $tags[0x0109] = 'TIFFCellLength';
2665
            $tags[0x010A] = 'TIFFFillOrder';
2666
            $tags[0x010E] = 'TIFFImageDescription';
2667
            $tags[0x010F] = 'TIFFMake';
2668
            $tags[0x0110] = 'TIFFModel';
2669
            $tags[0x0111] = 'TIFFStripOffsets';
2670
            $tags[0x0112] = 'TIFFOrientation';
2671
            $tags[0x0115] = 'TIFFSamplesPerPixel';
2672
            $tags[0x0116] = 'TIFFRowsPerStrip';
2673
            $tags[0x0117] = 'TIFFStripByteCounts';
2674
            $tags[0x0118] = 'TIFFMinSampleValue';
2675
            $tags[0x0119] = 'TIFFMaxSampleValue';
2676
            $tags[0x011A] = 'TIFFXResolution';
2677
            $tags[0x011B] = 'TIFFYResolution';
2678
            $tags[0x011C] = 'TIFFPlanarConfiguration';
2679
            $tags[0x0122] = 'TIFFGrayResponseUnit';
2680
            $tags[0x0123] = 'TIFFGrayResponseCurve';
2681
            $tags[0x0128] = 'TIFFResolutionUnit';
2682
            $tags[0x0131] = 'TIFFSoftware';
2683
            $tags[0x0132] = 'TIFFDateTime';
2684
            $tags[0x013B] = 'TIFFArtist';
2685
            $tags[0x013C] = 'TIFFHostComputer';
2686
            $tags[0x0140] = 'TIFFColorMap';
2687
            $tags[0x0152] = 'TIFFExtraSamples';
2688
            $tags[0x0201] = 'TIFFJFIFOffset';
2689
            $tags[0x0202] = 'TIFFJFIFLength';
2690
            $tags[0x0211] = 'TIFFYCbCrCoefficients';
2691
            $tags[0x0212] = 'TIFFYCbCrSubSampling';
2692
            $tags[0x0213] = 'TIFFYCbCrPositioning';
2693
            $tags[0x0214] = 'TIFFReferenceBlackWhite';
2694
            $tags[0x8298] = 'TIFFCopyright';
2695
            $tags[0x9286] = 'TIFFUserComment';
2696
        } elseif ($mode == 'exif') {
2697
            $tags[0x829A] = 'ExposureTime';
2698
            $tags[0x829D] = 'FNumber';
2699
            $tags[0x8822] = 'ExposureProgram';
2700
            $tags[0x8824] = 'SpectralSensitivity';
2701
            $tags[0x8827] = 'ISOSpeedRatings';
2702
            $tags[0x8828] = 'OECF';
2703
            $tags[0x9000] = 'EXIFVersion';
2704
            $tags[0x9003] = 'DateTimeOriginal';
2705
            $tags[0x9004] = 'DateTimeDigitized';
2706
            $tags[0x9101] = 'ComponentsConfiguration';
2707
            $tags[0x9102] = 'CompressedBitsPerPixel';
2708
            $tags[0x9201] = 'ShutterSpeedValue';
2709
            $tags[0x9202] = 'ApertureValue';
2710
            $tags[0x9203] = 'BrightnessValue';
2711
            $tags[0x9204] = 'ExposureBiasValue';
2712
            $tags[0x9205] = 'MaxApertureValue';
2713
            $tags[0x9206] = 'SubjectDistance';
2714
            $tags[0x9207] = 'MeteringMode';
2715
            $tags[0x9208] = 'LightSource';
2716
            $tags[0x9209] = 'Flash';
2717
            $tags[0x920A] = 'FocalLength';
2718
            $tags[0x927C] = 'MakerNote';
2719
            $tags[0x9286] = 'UserComment';
2720
            $tags[0x9290] = 'SubSecTime';
2721
            $tags[0x9291] = 'SubSecTimeOriginal';
2722
            $tags[0x9292] = 'SubSecTimeDigitized';
2723
            $tags[0xA000] = 'FlashPixVersion';
2724
            $tags[0xA001] = 'ColorSpace';
2725
            $tags[0xA002] = 'PixelXDimension';
2726
            $tags[0xA003] = 'PixelYDimension';
2727
            $tags[0xA004] = 'RelatedSoundFile';
2728
            $tags[0xA005] = 'InteropIFDOffset';
2729
            $tags[0xA20B] = 'FlashEnergy';
2730
            $tags[0xA20C] = 'SpatialFrequencyResponse';
2731
            $tags[0xA20E] = 'FocalPlaneXResolution';
2732
            $tags[0xA20F] = 'FocalPlaneYResolution';
2733
            $tags[0xA210] = 'FocalPlaneResolutionUnit';
2734
            $tags[0xA214] = 'SubjectLocation';
2735
            $tags[0xA215] = 'ExposureIndex';
2736
            $tags[0xA217] = 'SensingMethod';
2737
            $tags[0xA300] = 'FileSource';
2738
            $tags[0xA301] = 'SceneType';
2739
            $tags[0xA302] = 'CFAPattern';
2740
        } elseif ($mode == 'interop') {
2741
            $tags[0x0001] = 'InteroperabilityIndex';
2742
            $tags[0x0002] = 'InteroperabilityVersion';
2743
            $tags[0x1000] = 'RelatedImageFileFormat';
2744
            $tags[0x1001] = 'RelatedImageWidth';
2745
            $tags[0x1002] = 'RelatedImageLength';
2746
        } elseif ($mode == 'gps') {
2747
            $tags[0x0000] = 'GPSVersionID';
2748
            $tags[0x0001] = 'GPSLatitudeRef';
2749
            $tags[0x0002] = 'GPSLatitude';
2750
            $tags[0x0003] = 'GPSLongitudeRef';
2751
            $tags[0x0004] = 'GPSLongitude';
2752
            $tags[0x0005] = 'GPSAltitudeRef';
2753
            $tags[0x0006] = 'GPSAltitude';
2754
            $tags[0x0007] = 'GPSTimeStamp';
2755
            $tags[0x0008] = 'GPSSatellites';
2756
            $tags[0x0009] = 'GPSStatus';
2757
            $tags[0x000A] = 'GPSMeasureMode';
2758
            $tags[0x000B] = 'GPSDOP';
2759
            $tags[0x000C] = 'GPSSpeedRef';
2760
            $tags[0x000D] = 'GPSSpeed';
2761
            $tags[0x000E] = 'GPSTrackRef';
2762
            $tags[0x000F] = 'GPSTrack';
2763
            $tags[0x0010] = 'GPSImgDirectionRef';
2764
            $tags[0x0011] = 'GPSImgDirection';
2765
            $tags[0x0012] = 'GPSMapDatum';
2766
            $tags[0x0013] = 'GPSDestLatitudeRef';
2767
            $tags[0x0014] = 'GPSDestLatitude';
2768
            $tags[0x0015] = 'GPSDestLongitudeRef';
2769
            $tags[0x0016] = 'GPSDestLongitude';
2770
            $tags[0x0017] = 'GPSDestBearingRef';
2771
            $tags[0x0018] = 'GPSDestBearing';
2772
            $tags[0x0019] = 'GPSDestDistanceRef';
2773
            $tags[0x001A] = 'GPSDestDistance';
2774
        }
2775
2776
        return $tags;
2777
    }
2778
2779
    /*************************************************************/
2780
    function _exifTagTypes($mode) {
2781
        $tags = array();
2782
2783
        if ($mode == 'ifd0') {
2784
            $tags[0x010E] = array(2, 0); // ImageDescription -> ASCII, Any
2785
            $tags[0x010F] = array(2, 0); // Make -> ASCII, Any
2786
            $tags[0x0110] = array(2, 0); // Model -> ASCII, Any
2787
            $tags[0x0112] = array(3, 1); // Orientation -> SHORT, 1
2788
            $tags[0x011A] = array(5, 1); // XResolution -> RATIONAL, 1
2789
            $tags[0x011B] = array(5, 1); // YResolution -> RATIONAL, 1
2790
            $tags[0x0128] = array(3, 1); // ResolutionUnit -> SHORT
2791
            $tags[0x0131] = array(2, 0); // Software -> ASCII, Any
2792
            $tags[0x0132] = array(2, 20); // DateTime -> ASCII, 20
2793
            $tags[0x013B] = array(2, 0); // Artist -> ASCII, Any
2794
            $tags[0x013E] = array(5, 2); // WhitePoint -> RATIONAL, 2
2795
            $tags[0x013F] = array(5, 6); // PrimaryChromaticities -> RATIONAL, 6
2796
            $tags[0x0211] = array(5, 3); // YCbCrCoefficients -> RATIONAL, 3
2797
            $tags[0x0212] = array(3, 2); // YCbCrSubSampling -> SHORT, 2
2798
            $tags[0x0213] = array(3, 1); // YCbCrPositioning -> SHORT, 1
2799
            $tags[0x0214] = array(5, 6); // ReferenceBlackWhite -> RATIONAL, 6
2800
            $tags[0x8298] = array(2, 0); // Copyright -> ASCII, Any
2801
            $tags[0x8769] = array(4, 1); // ExifIFDOffset -> LONG, 1
2802
            $tags[0x8825] = array(4, 1); // GPSIFDOffset -> LONG, 1
2803
        }
2804
        if ($mode == 'ifd1') {
2805
            $tags[0x00FE] = array(4, 1); // TIFFNewSubfileType -> LONG, 1
2806
            $tags[0x00FF] = array(3, 1); // TIFFSubfileType -> SHORT, 1
2807
            $tags[0x0100] = array(4, 1); // TIFFImageWidth -> LONG (or SHORT), 1
2808
            $tags[0x0101] = array(4, 1); // TIFFImageHeight -> LONG (or SHORT), 1
2809
            $tags[0x0102] = array(3, 3); // TIFFBitsPerSample -> SHORT, 3
2810
            $tags[0x0103] = array(3, 1); // TIFFCompression -> SHORT, 1
2811
            $tags[0x0106] = array(3, 1); // TIFFPhotometricInterpretation -> SHORT, 1
2812
            $tags[0x0107] = array(3, 1); // TIFFThreshholding -> SHORT, 1
2813
            $tags[0x0108] = array(3, 1); // TIFFCellWidth -> SHORT, 1
2814
            $tags[0x0109] = array(3, 1); // TIFFCellLength -> SHORT, 1
2815
            $tags[0x010A] = array(3, 1); // TIFFFillOrder -> SHORT, 1
2816
            $tags[0x010E] = array(2, 0); // TIFFImageDescription -> ASCII, Any
2817
            $tags[0x010F] = array(2, 0); // TIFFMake -> ASCII, Any
2818
            $tags[0x0110] = array(2, 0); // TIFFModel -> ASCII, Any
2819
            $tags[0x0111] = array(4, 0); // TIFFStripOffsets -> LONG (or SHORT), Any (one per strip)
2820
            $tags[0x0112] = array(3, 1); // TIFFOrientation -> SHORT, 1
2821
            $tags[0x0115] = array(3, 1); // TIFFSamplesPerPixel -> SHORT, 1
2822
            $tags[0x0116] = array(4, 1); // TIFFRowsPerStrip -> LONG (or SHORT), 1
2823
            $tags[0x0117] = array(4, 0); // TIFFStripByteCounts -> LONG (or SHORT), Any (one per strip)
2824
            $tags[0x0118] = array(3, 0); // TIFFMinSampleValue -> SHORT, Any (SamplesPerPixel)
2825
            $tags[0x0119] = array(3, 0); // TIFFMaxSampleValue -> SHORT, Any (SamplesPerPixel)
2826
            $tags[0x011A] = array(5, 1); // TIFFXResolution -> RATIONAL, 1
2827
            $tags[0x011B] = array(5, 1); // TIFFYResolution -> RATIONAL, 1
2828
            $tags[0x011C] = array(3, 1); // TIFFPlanarConfiguration -> SHORT, 1
2829
            $tags[0x0122] = array(3, 1); // TIFFGrayResponseUnit -> SHORT, 1
2830
            $tags[0x0123] = array(3, 0); // TIFFGrayResponseCurve -> SHORT, Any (2^BitsPerSample)
2831
            $tags[0x0128] = array(3, 1); // TIFFResolutionUnit -> SHORT, 1
2832
            $tags[0x0131] = array(2, 0); // TIFFSoftware -> ASCII, Any
2833
            $tags[0x0132] = array(2, 20); // TIFFDateTime -> ASCII, 20
2834
            $tags[0x013B] = array(2, 0); // TIFFArtist -> ASCII, Any
2835
            $tags[0x013C] = array(2, 0); // TIFFHostComputer -> ASCII, Any
2836
            $tags[0x0140] = array(3, 0); // TIFFColorMap -> SHORT, Any (3 * 2^BitsPerSample)
2837
            $tags[0x0152] = array(3, 0); // TIFFExtraSamples -> SHORT, Any (SamplesPerPixel - 3)
2838
            $tags[0x0201] = array(4, 1); // TIFFJFIFOffset -> LONG, 1
2839
            $tags[0x0202] = array(4, 1); // TIFFJFIFLength -> LONG, 1
2840
            $tags[0x0211] = array(5, 3); // TIFFYCbCrCoefficients -> RATIONAL, 3
2841
            $tags[0x0212] = array(3, 2); // TIFFYCbCrSubSampling -> SHORT, 2
2842
            $tags[0x0213] = array(3, 1); // TIFFYCbCrPositioning -> SHORT, 1
2843
            $tags[0x0214] = array(5, 6); // TIFFReferenceBlackWhite -> RATIONAL, 6
2844
            $tags[0x8298] = array(2, 0); // TIFFCopyright -> ASCII, Any
2845
            $tags[0x9286] = array(2, 0); // TIFFUserComment -> ASCII, Any
2846
        } elseif ($mode == 'exif') {
2847
            $tags[0x829A] = array(5, 1); // ExposureTime -> RATIONAL, 1
2848
            $tags[0x829D] = array(5, 1); // FNumber -> RATIONAL, 1
2849
            $tags[0x8822] = array(3, 1); // ExposureProgram -> SHORT, 1
2850
            $tags[0x8824] = array(2, 0); // SpectralSensitivity -> ASCII, Any
2851
            $tags[0x8827] = array(3, 0); // ISOSpeedRatings -> SHORT, Any
2852
            $tags[0x8828] = array(7, 0); // OECF -> UNDEFINED, Any
2853
            $tags[0x9000] = array(7, 4); // EXIFVersion -> UNDEFINED, 4
2854
            $tags[0x9003] = array(2, 20); // DateTimeOriginal -> ASCII, 20
2855
            $tags[0x9004] = array(2, 20); // DateTimeDigitized -> ASCII, 20
2856
            $tags[0x9101] = array(7, 4); // ComponentsConfiguration -> UNDEFINED, 4
2857
            $tags[0x9102] = array(5, 1); // CompressedBitsPerPixel -> RATIONAL, 1
2858
            $tags[0x9201] = array(10, 1); // ShutterSpeedValue -> SRATIONAL, 1
2859
            $tags[0x9202] = array(5, 1); // ApertureValue -> RATIONAL, 1
2860
            $tags[0x9203] = array(10, 1); // BrightnessValue -> SRATIONAL, 1
2861
            $tags[0x9204] = array(10, 1); // ExposureBiasValue -> SRATIONAL, 1
2862
            $tags[0x9205] = array(5, 1); // MaxApertureValue -> RATIONAL, 1
2863
            $tags[0x9206] = array(5, 1); // SubjectDistance -> RATIONAL, 1
2864
            $tags[0x9207] = array(3, 1); // MeteringMode -> SHORT, 1
2865
            $tags[0x9208] = array(3, 1); // LightSource -> SHORT, 1
2866
            $tags[0x9209] = array(3, 1); // Flash -> SHORT, 1
2867
            $tags[0x920A] = array(5, 1); // FocalLength -> RATIONAL, 1
2868
            $tags[0x927C] = array(7, 0); // MakerNote -> UNDEFINED, Any
2869
            $tags[0x9286] = array(7, 0); // UserComment -> UNDEFINED, Any
2870
            $tags[0x9290] = array(2, 0); // SubSecTime -> ASCII, Any
2871
            $tags[0x9291] = array(2, 0); // SubSecTimeOriginal -> ASCII, Any
2872
            $tags[0x9292] = array(2, 0); // SubSecTimeDigitized -> ASCII, Any
2873
            $tags[0xA000] = array(7, 4); // FlashPixVersion -> UNDEFINED, 4
2874
            $tags[0xA001] = array(3, 1); // ColorSpace -> SHORT, 1
2875
            $tags[0xA002] = array(4, 1); // PixelXDimension -> LONG (or SHORT), 1
2876
            $tags[0xA003] = array(4, 1); // PixelYDimension -> LONG (or SHORT), 1
2877
            $tags[0xA004] = array(2, 13); // RelatedSoundFile -> ASCII, 13
2878
            $tags[0xA005] = array(4, 1); // InteropIFDOffset -> LONG, 1
2879
            $tags[0xA20B] = array(5, 1); // FlashEnergy -> RATIONAL, 1
2880
            $tags[0xA20C] = array(7, 0); // SpatialFrequencyResponse -> UNDEFINED, Any
2881
            $tags[0xA20E] = array(5, 1); // FocalPlaneXResolution -> RATIONAL, 1
2882
            $tags[0xA20F] = array(5, 1); // FocalPlaneYResolution -> RATIONAL, 1
2883
            $tags[0xA210] = array(3, 1); // FocalPlaneResolutionUnit -> SHORT, 1
2884
            $tags[0xA214] = array(3, 2); // SubjectLocation -> SHORT, 2
2885
            $tags[0xA215] = array(5, 1); // ExposureIndex -> RATIONAL, 1
2886
            $tags[0xA217] = array(3, 1); // SensingMethod -> SHORT, 1
2887
            $tags[0xA300] = array(7, 1); // FileSource -> UNDEFINED, 1
2888
            $tags[0xA301] = array(7, 1); // SceneType -> UNDEFINED, 1
2889
            $tags[0xA302] = array(7, 0); // CFAPattern -> UNDEFINED, Any
2890
        } elseif ($mode == 'interop') {
2891
            $tags[0x0001] = array(2, 0); // InteroperabilityIndex -> ASCII, Any
2892
            $tags[0x0002] = array(7, 4); // InteroperabilityVersion -> UNKNOWN, 4
2893
            $tags[0x1000] = array(2, 0); // RelatedImageFileFormat -> ASCII, Any
2894
            $tags[0x1001] = array(4, 1); // RelatedImageWidth -> LONG (or SHORT), 1
2895
            $tags[0x1002] = array(4, 1); // RelatedImageLength -> LONG (or SHORT), 1
2896
        } elseif ($mode == 'gps') {
2897
            $tags[0x0000] = array(1, 4); // GPSVersionID -> BYTE, 4
2898
            $tags[0x0001] = array(2, 2); // GPSLatitudeRef -> ASCII, 2
2899
            $tags[0x0002] = array(5, 3); // GPSLatitude -> RATIONAL, 3
2900
            $tags[0x0003] = array(2, 2); // GPSLongitudeRef -> ASCII, 2
2901
            $tags[0x0004] = array(5, 3); // GPSLongitude -> RATIONAL, 3
2902
            $tags[0x0005] = array(2, 2); // GPSAltitudeRef -> ASCII, 2
2903
            $tags[0x0006] = array(5, 1); // GPSAltitude -> RATIONAL, 1
2904
            $tags[0x0007] = array(5, 3); // GPSTimeStamp -> RATIONAL, 3
2905
            $tags[0x0008] = array(2, 0); // GPSSatellites -> ASCII, Any
2906
            $tags[0x0009] = array(2, 2); // GPSStatus -> ASCII, 2
2907
            $tags[0x000A] = array(2, 2); // GPSMeasureMode -> ASCII, 2
2908
            $tags[0x000B] = array(5, 1); // GPSDOP -> RATIONAL, 1
2909
            $tags[0x000C] = array(2, 2); // GPSSpeedRef -> ASCII, 2
2910
            $tags[0x000D] = array(5, 1); // GPSSpeed -> RATIONAL, 1
2911
            $tags[0x000E] = array(2, 2); // GPSTrackRef -> ASCII, 2
2912
            $tags[0x000F] = array(5, 1); // GPSTrack -> RATIONAL, 1
2913
            $tags[0x0010] = array(2, 2); // GPSImgDirectionRef -> ASCII, 2
2914
            $tags[0x0011] = array(5, 1); // GPSImgDirection -> RATIONAL, 1
2915
            $tags[0x0012] = array(2, 0); // GPSMapDatum -> ASCII, Any
2916
            $tags[0x0013] = array(2, 2); // GPSDestLatitudeRef -> ASCII, 2
2917
            $tags[0x0014] = array(5, 3); // GPSDestLatitude -> RATIONAL, 3
2918
            $tags[0x0015] = array(2, 2); // GPSDestLongitudeRef -> ASCII, 2
2919
            $tags[0x0016] = array(5, 3); // GPSDestLongitude -> RATIONAL, 3
2920
            $tags[0x0017] = array(2, 2); // GPSDestBearingRef -> ASCII, 2
2921
            $tags[0x0018] = array(5, 1); // GPSDestBearing -> RATIONAL, 1
2922
            $tags[0x0019] = array(2, 2); // GPSDestDistanceRef -> ASCII, 2
2923
            $tags[0x001A] = array(5, 1); // GPSDestDistance -> RATIONAL, 1
2924
        }
2925
2926
        return $tags;
2927
    }
2928
2929
    /*************************************************************/
2930
    function _exifNameTags($mode) {
2931
        $tags = $this->_exifTagNames($mode);
2932
        return $this->_names2Tags($tags);
2933
    }
2934
2935
    /*************************************************************/
2936
    function _iptcTagNames() {
2937
        $tags = array();
2938
        $tags[0x14] = 'SuplementalCategories';
2939
        $tags[0x19] = 'Keywords';
2940
        $tags[0x78] = 'Caption';
2941
        $tags[0x7A] = 'CaptionWriter';
2942
        $tags[0x69] = 'Headline';
2943
        $tags[0x28] = 'SpecialInstructions';
2944
        $tags[0x0F] = 'Category';
2945
        $tags[0x50] = 'Byline';
2946
        $tags[0x55] = 'BylineTitle';
2947
        $tags[0x6E] = 'Credit';
2948
        $tags[0x73] = 'Source';
2949
        $tags[0x74] = 'CopyrightNotice';
2950
        $tags[0x05] = 'ObjectName';
2951
        $tags[0x5A] = 'City';
2952
        $tags[0x5C] = 'Sublocation';
2953
        $tags[0x5F] = 'ProvinceState';
2954
        $tags[0x65] = 'CountryName';
2955
        $tags[0x67] = 'OriginalTransmissionReference';
2956
        $tags[0x37] = 'DateCreated';
2957
        $tags[0x0A] = 'CopyrightFlag';
2958
2959
        return $tags;
2960
    }
2961
2962
    /*************************************************************/
2963
    function & _iptcNameTags() {
2964
        $tags = $this->_iptcTagNames();
2965
        return $this->_names2Tags($tags);
2966
    }
2967
2968
    /*************************************************************/
2969
    function _names2Tags($tags2Names) {
2970
        $names2Tags = array();
2971
2972
        foreach($tags2Names as $tag => $name) {
2973
            $names2Tags[$name] = $tag;
2974
        }
2975
2976
        return $names2Tags;
2977
    }
2978
2979
    /*************************************************************/
2980
2981
    /**
2982
     * @param $data
2983
     * @param integer $pos
2984
     *
2985
     * @return int
2986
     */
2987
    function _getByte(&$data, $pos) {
2988
        return ord($data[$pos]);
2989
    }
2990
2991
    /*************************************************************/
2992
2993
    /**
2994
     * @param mixed $data
2995
     * @param integer $pos
2996
     *
2997
     * @param mixed $val
2998
     *
2999
     * @return int
3000
     */
3001
    function _putByte(&$data, $pos, $val) {
3002
        $val = intval($val);
3003
3004
        $data[$pos] = chr($val);
3005
3006
        return $pos + 1;
3007
    }
3008
3009
    /*************************************************************/
3010
    function _getShort(&$data, $pos, $bigEndian = true) {
3011
        if ($bigEndian) {
3012
            return (ord($data[$pos]) << 8)
3013
                + ord($data[$pos + 1]);
3014
        } else {
3015
            return ord($data[$pos])
3016
                + (ord($data[$pos + 1]) << 8);
3017
        }
3018
    }
3019
3020
    /*************************************************************/
3021
    function _putShort(&$data, $pos = 0, $val = 0, $bigEndian = true) {
3022
        $val = intval($val);
3023
3024
        if ($bigEndian) {
3025
            $data[$pos + 0] = chr(($val & 0x0000FF00) >> 8);
3026
            $data[$pos + 1] = chr(($val & 0x000000FF) >> 0);
3027
        } else {
3028
            $data[$pos + 0] = chr(($val & 0x00FF) >> 0);
3029
            $data[$pos + 1] = chr(($val & 0xFF00) >> 8);
3030
        }
3031
3032
        return $pos + 2;
3033
    }
3034
3035
    /*************************************************************/
3036
3037
    /**
3038
     * @param mixed $data
3039
     * @param integer $pos
3040
     *
3041
     * @param bool $bigEndian
3042
     *
3043
     * @return int
3044
     */
3045
    function _getLong(&$data, $pos, $bigEndian = true) {
3046
        if ($bigEndian) {
3047
            return (ord($data[$pos]) << 24)
3048
                + (ord($data[$pos + 1]) << 16)
3049
                + (ord($data[$pos + 2]) << 8)
3050
                + ord($data[$pos + 3]);
3051
        } else {
3052
            return ord($data[$pos])
3053
                + (ord($data[$pos + 1]) << 8)
3054
                + (ord($data[$pos + 2]) << 16)
3055
                + (ord($data[$pos + 3]) << 24);
3056
        }
3057
    }
3058
3059
    /*************************************************************/
3060
3061
    /**
3062
     * @param mixed $data
3063
     * @param integer $pos
3064
     *
3065
     * @param mixed $val
3066
     * @param bool $bigEndian
3067
     *
3068
     * @return int
3069
     */
3070
    function _putLong(&$data, $pos, $val, $bigEndian = true) {
3071
        $val = intval($val);
3072
3073
        if ($bigEndian) {
3074
            $data[$pos + 0] = chr(($val & 0xFF000000) >> 24);
3075
            $data[$pos + 1] = chr(($val & 0x00FF0000) >> 16);
3076
            $data[$pos + 2] = chr(($val & 0x0000FF00) >> 8);
3077
            $data[$pos + 3] = chr(($val & 0x000000FF) >> 0);
3078
        } else {
3079
            $data[$pos + 0] = chr(($val & 0x000000FF) >> 0);
3080
            $data[$pos + 1] = chr(($val & 0x0000FF00) >> 8);
3081
            $data[$pos + 2] = chr(($val & 0x00FF0000) >> 16);
3082
            $data[$pos + 3] = chr(($val & 0xFF000000) >> 24);
3083
        }
3084
3085
        return $pos + 4;
3086
    }
3087
3088
    /*************************************************************/
3089
    function & _getNullString(&$data, $pos) {
3090
        $str = '';
3091
        $max = strlen($data);
3092
3093
        while ($pos < $max) {
3094
            if (ord($data[$pos]) == 0) {
3095
                return $str;
3096
            } else {
3097
                $str .= $data[$pos];
3098
            }
3099
            $pos++;
3100
        }
3101
3102
        return $str;
3103
    }
3104
3105
    /*************************************************************/
3106
    function & _getFixedString(&$data, $pos, $length = -1) {
3107
        if ($length == -1) {
3108
            $length = strlen($data) - $pos;
3109
        }
3110
3111
        $rv = substr($data, $pos, $length);
3112
        return $rv;
3113
    }
3114
3115
    /*************************************************************/
3116
    function _putString(&$data, $pos, &$str) {
3117
        $len = strlen($str);
3118
        for ($i = 0; $i < $len; $i++) {
3119
            $data[$pos + $i] = $str[$i];
3120
        }
3121
3122
        return $pos + $len;
3123
    }
3124
3125
    /*************************************************************/
3126
    function _hexDump(&$data, $start = 0, $length = -1) {
3127
        if (($length == -1) || (($length + $start) > strlen($data))) {
3128
            $end = strlen($data);
3129
        } else {
3130
            $end = $start + $length;
3131
        }
3132
3133
        $ascii = '';
3134
        $count = 0;
3135
3136
        echo "<tt>\n";
3137
3138
        while ($start < $end) {
3139
            if (($count % 16) == 0) {
3140
                echo sprintf('%04d', $count) . ': ';
3141
            }
3142
3143
            $c = ord($data[$start]);
3144
            $count++;
3145
            $start++;
3146
3147
            $aux = dechex($c);
3148
            if (strlen($aux) == 1)
3149
                echo '0';
3150
            echo $aux . ' ';
3151
3152
            if ($c == 60)
3153
                $ascii .= '&lt;';
3154
            elseif ($c == 62)
3155
                $ascii .= '&gt;';
3156
            elseif ($c == 32)
3157
                $ascii .= '&#160;';
3158
            elseif ($c > 32)
3159
                $ascii .= chr($c);
3160
            else
3161
                $ascii .= '.';
3162
3163
            if (($count % 4) == 0) {
3164
                echo ' - ';
3165
            }
3166
3167
            if (($count % 16) == 0) {
3168
                echo ': ' . $ascii . "<br>\n";
3169
                $ascii = '';
3170
            }
3171
        }
3172
3173
        if ($ascii != '') {
3174
            while (($count % 16) != 0) {
3175
                echo '-- ';
3176
                $count++;
3177
                if (($count % 4) == 0) {
3178
                    echo ' - ';
3179
                }
3180
            }
3181
            echo ': ' . $ascii . "<br>\n";
3182
        }
3183
3184
        echo "</tt>\n";
3185
    }
3186
3187
    /*****************************************************************/
3188
}
3189
3190
/* vim: set expandtab tabstop=4 shiftwidth=4: */
3191