ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
MimeDir.php
Go to the documentation of this file.
1<?php
2
4
11
25class MimeDir extends Parser {
26
32 protected $input;
33
39 protected $root;
40
53 protected $charset = 'UTF-8';
54
60 protected static $SUPPORTED_CHARSETS = [
61 'UTF-8',
62 'ISO-8859-1',
63 'Windows-1252',
64 ];
65
77 function parse($input = null, $options = 0) {
78
79 $this->root = null;
80
81 if (!is_null($input)) {
82 $this->setInput($input);
83 }
84
85 if (0 !== $options) {
86 $this->options = $options;
87 }
88
89 $this->parseDocument();
90
91 return $this->root;
92
93 }
94
108
109 if (!in_array($charset, self::$SUPPORTED_CHARSETS)) {
110 throw new \InvalidArgumentException('Unsupported encoding. (Supported encodings: ' . implode(', ', self::$SUPPORTED_CHARSETS) . ')');
111 }
112 $this->charset = $charset;
113
114 }
115
123 function setInput($input) {
124
125 // Resetting the parser
126 $this->lineIndex = 0;
127 $this->startLine = 0;
128
129 if (is_string($input)) {
130 // Convering to a stream.
131 $stream = fopen('php://temp', 'r+');
132 fwrite($stream, $input);
133 rewind($stream);
134 $this->input = $stream;
135 } elseif (is_resource($input)) {
136 $this->input = $input;
137 } else {
138 throw new \InvalidArgumentException('This parser can only read from strings or streams.');
139 }
140
141 }
142
148 protected function parseDocument() {
149
150 $line = $this->readLine();
151
152 // BOM is ZERO WIDTH NO-BREAK SPACE (U+FEFF).
153 // It's 0xEF 0xBB 0xBF in UTF-8 hex.
154 if (3 <= strlen($line)
155 && ord($line[0]) === 0xef
156 && ord($line[1]) === 0xbb
157 && ord($line[2]) === 0xbf) {
158 $line = substr($line, 3);
159 }
160
161 switch (strtoupper($line)) {
162 case 'BEGIN:VCALENDAR' :
163 $class = VCalendar::$componentMap['VCALENDAR'];
164 break;
165 case 'BEGIN:VCARD' :
166 $class = VCard::$componentMap['VCARD'];
167 break;
168 default :
169 throw new ParseException('This parser only supports VCARD and VCALENDAR files');
170 }
171
172 $this->root = new $class([], false);
173
174 while (true) {
175
176 // Reading until we hit END:
177 $line = $this->readLine();
178 if (strtoupper(substr($line, 0, 4)) === 'END:') {
179 break;
180 }
181 $result = $this->parseLine($line);
182 if ($result) {
183 $this->root->add($result);
184 }
185
186 }
187
188 $name = strtoupper(substr($line, 4));
189 if ($name !== $this->root->name) {
190 throw new ParseException('Invalid MimeDir file. expected: "END:' . $this->root->name . '" got: "END:' . $name . '"');
191 }
192
193 }
194
203 protected function parseLine($line) {
204
205 // Start of a new component
206 if (strtoupper(substr($line, 0, 6)) === 'BEGIN:') {
207
208 $component = $this->root->createComponent(substr($line, 6), [], false);
209
210 while (true) {
211
212 // Reading until we hit END:
213 $line = $this->readLine();
214 if (strtoupper(substr($line, 0, 4)) === 'END:') {
215 break;
216 }
217 $result = $this->parseLine($line);
218 if ($result) {
219 $component->add($result);
220 }
221
222 }
223
224 $name = strtoupper(substr($line, 4));
225 if ($name !== $component->name) {
226 throw new ParseException('Invalid MimeDir file. expected: "END:' . $component->name . '" got: "END:' . $name . '"');
227 }
228
229 return $component;
230
231 } else {
232
233 // Property reader
234 $property = $this->readProperty($line);
235 if (!$property) {
236 // Ignored line
237 return false;
238 }
239 return $property;
240
241 }
242
243 }
244
253 protected $lineBuffer;
254
258 protected $lineIndex = 0;
259
266 protected $startLine = 0;
267
273 protected $rawLine;
274
284 protected function readLine() {
285
286 if (!\is_null($this->lineBuffer)) {
288 $this->lineBuffer = null;
289 } else {
290 do {
291 $eof = \feof($this->input);
292
293 $rawLine = \fgets($this->input);
294
295 if ($eof || (\feof($this->input) && $rawLine === false)) {
296 throw new EofException('End of document reached prematurely');
297 }
298 if ($rawLine === false) {
299 throw new ParseException('Error reading from input stream');
300 }
301 $rawLine = \rtrim($rawLine, "\r\n");
302 } while ($rawLine === ''); // Skipping empty lines
303 $this->lineIndex++;
304 }
305 $line = $rawLine;
306
307 $this->startLine = $this->lineIndex;
308
309 // Looking ahead for folded lines.
310 while (true) {
311
312 $nextLine = \rtrim(\fgets($this->input), "\r\n");
313 $this->lineIndex++;
314 if (!$nextLine) {
315 break;
316 }
317 if ($nextLine[0] === "\t" || $nextLine[0] === " ") {
318 $curLine = \substr($nextLine, 1);
319 $line .= $curLine;
320 $rawLine .= "\n " . $curLine;
321 } else {
322 $this->lineBuffer = $nextLine;
323 break;
324 }
325
326 }
327 $this->rawLine = $rawLine;
328 return $line;
329
330 }
331
337 protected function readProperty($line) {
338
339 if ($this->options & self::OPTION_FORGIVING) {
340 $propNameToken = 'A-Z0-9\-\._\\/';
341 } else {
342 $propNameToken = 'A-Z0-9\-\.';
343 }
344
345 $paramNameToken = 'A-Z0-9\-';
346 $safeChar = '^";:,';
347 $qSafeChar = '^"';
348
349 $regex = "/
350 ^(?P<name> [$propNameToken]+ ) (?=[;:]) # property name
351 |
352 (?<=:)(?P<propValue> .+)$ # property value
353 |
354 ;(?P<paramName> [$paramNameToken]+) (?=[=;:]) # parameter name
355 |
356 (=|,)(?P<paramValue> # parameter value
357 (?: [$safeChar]*) |
358 \"(?: [$qSafeChar]+)\"
359 ) (?=[;:,])
360 /xi";
361
362 //echo $regex, "\n"; die();
363 preg_match_all($regex, $line, $matches, PREG_SET_ORDER);
364
365 $property = [
366 'name' => null,
367 'parameters' => [],
368 'value' => null
369 ];
370
371 $lastParam = null;
372
380 foreach ($matches as $match) {
381
382 if (isset($match['paramValue'])) {
383 if ($match['paramValue'] && $match['paramValue'][0] === '"') {
384 $value = substr($match['paramValue'], 1, -1);
385 } else {
386 $value = $match['paramValue'];
387 }
388
389 $value = $this->unescapeParam($value);
390
391 if (is_null($lastParam)) {
392 throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions');
393 }
394 if (is_null($property['parameters'][$lastParam])) {
395 $property['parameters'][$lastParam] = $value;
396 } elseif (is_array($property['parameters'][$lastParam])) {
397 $property['parameters'][$lastParam][] = $value;
398 } else {
399 $property['parameters'][$lastParam] = [
400 $property['parameters'][$lastParam],
401 $value
402 ];
403 }
404 continue;
405 }
406 if (isset($match['paramName'])) {
407 $lastParam = strtoupper($match['paramName']);
408 if (!isset($property['parameters'][$lastParam])) {
409 $property['parameters'][$lastParam] = null;
410 }
411 continue;
412 }
413 if (isset($match['propValue'])) {
414 $property['value'] = $match['propValue'];
415 continue;
416 }
417 if (isset($match['name']) && $match['name']) {
418 $property['name'] = strtoupper($match['name']);
419 continue;
420 }
421
422 // @codeCoverageIgnoreStart
423 throw new \LogicException('This code should not be reachable');
424 // @codeCoverageIgnoreEnd
425
426 }
427
428 if (is_null($property['value'])) {
429 $property['value'] = '';
430 }
431 if (!$property['name']) {
432 if ($this->options & self::OPTION_IGNORE_INVALID_LINES) {
433 return false;
434 }
435 throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions');
436 }
437
438 // vCard 2.1 states that parameters may appear without a name, and only
439 // a value. We can deduce the value based on it's name.
440 //
441 // Our parser will get those as parameters without a value instead, so
442 // we're filtering these parameters out first.
443 $namedParameters = [];
444 $namelessParameters = [];
445
446 foreach ($property['parameters'] as $name => $value) {
447 if (!is_null($value)) {
448 $namedParameters[$name] = $value;
449 } else {
450 $namelessParameters[] = $name;
451 }
452 }
453
454 $propObj = $this->root->createProperty($property['name'], null, $namedParameters);
455
456 foreach ($namelessParameters as $namelessParameter) {
457 $propObj->add(null, $namelessParameter);
458 }
459
460 if (strtoupper($propObj['ENCODING']) === 'QUOTED-PRINTABLE') {
461 $propObj->setQuotedPrintableValue($this->extractQuotedPrintableValue());
462 } else {
464 if ($this->root->getDocumentType() === Document::VCARD21 && isset($propObj['CHARSET'])) {
465 // vCard 2.1 allows the character set to be specified per property.
466 $charset = (string)$propObj['CHARSET'];
467 }
468 switch (strtolower($charset)) {
469 case 'utf-8' :
470 break;
471 case 'iso-8859-1' :
472 $property['value'] = utf8_encode($property['value']);
473 break;
474 case 'windows-1252' :
475 $property['value'] = mb_convert_encoding($property['value'], 'UTF-8', $charset);
476 break;
477 default :
478 throw new ParseException('Unsupported CHARSET: ' . $propObj['CHARSET']);
479 }
480 $propObj->setRawMimeDirValue($property['value']);
481 }
482
483 return $propObj;
484
485 }
486
549 static function unescapeValue($input, $delimiter = ';') {
550
551 $regex = '# (?: (\\\\ (?: \\\\ | N | n | ; | , ) )';
552 if ($delimiter) {
553 $regex .= ' | (' . $delimiter . ')';
554 }
555 $regex .= ') #x';
556
557 $matches = preg_split($regex, $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
558
559 $resultArray = [];
560 $result = '';
561
562 foreach ($matches as $match) {
563
564 switch ($match) {
565 case '\\\\' :
566 $result .= '\\';
567 break;
568 case '\N' :
569 case '\n' :
570 $result .= "\n";
571 break;
572 case '\;' :
573 $result .= ';';
574 break;
575 case '\,' :
576 $result .= ',';
577 break;
578 case $delimiter :
579 $resultArray[] = $result;
580 $result = '';
581 break;
582 default :
583 $result .= $match;
584 break;
585
586 }
587
588 }
589
590 $resultArray[] = $result;
591 return $delimiter ? $resultArray : $result;
592
593 }
594
629 private function unescapeParam($input) {
630
631 return
632 preg_replace_callback(
633 '#(\^(\^|n|\'))#',
634 function($matches) {
635 switch ($matches[2]) {
636 case 'n' :
637 return "\n";
638 case '^' :
639 return '^';
640 case '\'' :
641 return '"';
642
643 // @codeCoverageIgnoreStart
644 }
645 // @codeCoverageIgnoreEnd
646 },
647 $input
648 );
649 }
650
661 private function extractQuotedPrintableValue() {
662
663 // We need to parse the raw line again to get the start of the value.
664 //
665 // We are basically looking for the first colon (:), but we need to
666 // skip over the parameters first, as they may contain one.
667 $regex = '/^
668 (?: [^:])+ # Anything but a colon
669 (?: "[^"]")* # A parameter in double quotes
670 : # start of the value we really care about
671 (.*)$
672 /xs';
673
674 preg_match($regex, $this->rawLine, $matches);
675
676 $value = $matches[1];
677 // Removing the first whitespace character from every line. Kind of
678 // like unfolding, but we keep the newline.
679 $value = str_replace("\n ", "\n", $value);
680
681 // Microsoft products don't always correctly fold lines, they may be
682 // missing a whitespace. So if 'forgiving' is turned on, we will take
683 // those as well.
684 if ($this->options & self::OPTION_FORGIVING) {
685 while (substr($value, -1) === '=') {
686 // Reading the line
687 $this->readLine();
688 // Grabbing the raw form
689 $value .= "\n" . $this->rawLine;
690 }
691 }
692
693 return $value;
694
695 }
696
697}
$result
An exception for terminatinating execution or to throw for unit testing.
The VCalendar component.
Definition: VCalendar.php:23
The VCard component.
Definition: VCard.php:18
const VCARD21
vCard 2.1.
Definition: Document.php:39
Exception thrown by parser when the end of the stream has been reached, before this was expected.
Exception thrown by Reader if an invalid object was attempted to be parsed.
parseLine($line)
Parses a line, and if it hits a component, it will also attempt to parse the entire component.
Definition: MimeDir.php:203
setCharset($charset)
By default all input will be assumed to be UTF-8.
Definition: MimeDir.php:107
readProperty($line)
Reads a property or component from a line.
Definition: MimeDir.php:337
parse($input=null, $options=0)
Parses an iCalendar or vCard file.
Definition: MimeDir.php:77
static $SUPPORTED_CHARSETS
The list of character sets we support when decoding.
Definition: MimeDir.php:60
setInput($input)
Sets the input buffer.
Definition: MimeDir.php:123
$lineIndex
The real current line number.
Definition: MimeDir.php:258
extractQuotedPrintableValue()
Gets the full quoted printable value.
Definition: MimeDir.php:661
parseDocument()
Parses an entire document.
Definition: MimeDir.php:148
readLine()
Reads a single line from the buffer.
Definition: MimeDir.php:284
unescapeParam($input)
Unescapes a parameter value.
Definition: MimeDir.php:629
static unescapeValue($input, $delimiter=';')
Unescapes a property value.
Definition: MimeDir.php:549
Abstract parser.
Definition: Parser.php:14
input
Definition: langcheck.php:166
$stream
PHP stream implementation.
echo;exit;}function LogoutNotification($SessionID){ global $ilDB;$q="SELECT session_id, data FROM usr_session WHERE expires > (\w+)\|/" PREG_SPLIT_NO_EMPTY PREG_SPLIT_DELIM_CAPTURE
$delimiter
Definition: showstats.php:16