Completed
Push — master ( 147214...53e4d6 )
by Zaahid
09:05
created

UUDecodeStreamFilter::filterBucketLines()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 10
cts 10
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 3
nop 2
crap 3
1
<?php
2
/**
3
 * This file is part of the ZBateson\MailMimeParser project.
4
 *
5
 * @license http://opensource.org/licenses/bsd-license.php BSD
6
 */
7
namespace ZBateson\MailMimeParser\Stream;
8
9
use php_user_filter;
10
11
/**
12
 * Stream filter converts uuencoded text to its raw binary.
13
 *
14
 * @author Zaahid Bateson
15
 */
16
class UUDecodeStreamFilter extends php_user_filter
17
{
18
    /**
19
     * Name used when registering with stream_filter_register.
20
     */
21
    const STREAM_FILTER_NAME = 'mailmimeparser-uudecode';
22
    
23
    /**
24
     * @var string Leftovers from the last incomplete line that was parsed, to
25
     *      be prepended to the next line read.
26
     */
27
    private $leftover = '';
28
    
29
    /**
30
     * Returns an array of complete lines (including line endings) from the 
31
     * passed $bucket object.
32
     * 
33
     * If the last line on $bucket is incomplete, it's assigned to
34
     * $this->leftover and prepended to the first element of the first line in
35
     * the next call to getLines.
36
     * 
37
     * @param object $bucket
38
     * @return string[]
39
     */
40 7
    private function getLines($bucket)
41
    {
42 7
        $lines = preg_split(
43 7
            '/([^\r\n]+[\r\n]+)/',
44 7
            $bucket->data,
45 7
            -1,
46 7
            PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
47 7
        );
48 7
        if (!empty($this->leftover)) {
49 1
            $lines[0] = $this->leftover . $lines[0];
50 1
            $this->leftover = '';
51 1
        }
52 7
        $last = end($lines);
53 7
        if ($last[strlen($last) - 1] !== "\n") {
54 1
            $this->leftover = array_pop($lines);
55 1
        }
56 7
        return $lines;
57
    }
58
    
59
    /**
60
     * Returns true if the passed $line is empty or matches the beginning header
61
     * pattern for a uuencoded message.
62
     * 
63
     * @param string $line
64
     * @return bool
65
     */
66 7
    private function isEmptyOrStartLine($line)
67
    {
68 7
        return (empty($line) || preg_match('/^begin \d{3} .*$/', $line));
69
    }
70
    
71
    /**
72
     * Returns true if the passed $line is either a backtick character '`' or
73
     * the string 'end' signifying the end of the uuencoded message.
74
     * 
75
     * @param string $line
76
     * @return bool
77
     */
78 7
    private function isEndLine($line)
79
    {
80 7
        return ($line === '`' || $line === 'end');
81
    }
82
    
83
    /**
84
     * Filters a single line of encoded input.  Returns NULL if the end has been
85
     * reached.
86
     * 
87
     * @param string $line
88
     * @return string the decoded line
89
     */
90 7
    private function filterLine($line)
91
    {
92 7
        $cur = ltrim(rtrim($line, "\t\n\r\0\x0B"));
93 7
        if ($this->isEmptyOrStartLine($cur)) {
94 7
            return '';
95 7
        } elseif ($this->isEndLine($cur)) {
96 7
            return null;
97
        }
98 7
        return convert_uudecode($cur);
99
    }
100
    
101
    /**
102
     * Filters the lines in the passed $lines array, returning a concatenated
103
     * string of decoded lines.
104
     * 
105
     * @param array $lines
106
     * @param int $consumed
107
     * @return string
108
     */
109 7
    private function filterBucketLines(array $lines, &$consumed)
110
    {
111 7
        $data = '';
112 7
        foreach ($lines as $line) {
113 7
            $consumed += strlen($line);
114 7
            $filtered = $this->filterLine($line);
115 7
            if ($filtered === null) {
116 7
                break;
117
            }
118 7
            $data .= $filtered;
119 7
        }
120 7
        return $data;
121
    }
122
    
123
    /**
124
     * Filter implementation converts encoding before returning PSFS_PASS_ON.
125
     * 
126
     * @param resource $in
127
     * @param resource $out
128
     * @param int $consumed
129
     * @param bool $closing
130
     * @return int
131
     */
132 7
    public function filter($in, $out, &$consumed, $closing)
133
    {
134 7
        while ($bucket = stream_bucket_make_writeable($in)) {
135 7
            $lines = $this->getLines($bucket);
136 7
            $converted = $this->filterBucketLines($lines, $consumed);
137
            
138
            // $this->stream is undocumented.  It was found looking at HHVM's source code
139
            // for its convert.iconv.* implementation in ConvertIconFilter and explained
140
            // somewhat in this StackOverflow page: http://stackoverflow.com/a/31132646/335059
141
            // declaring a member variable called 'stream' breaks the PHP implementation (5.5.9
142
            // at least).
143 7
            stream_bucket_append($out, stream_bucket_new($this->stream, $converted));
1 ignored issue
show
Bug introduced by
The property stream does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
144 7
        }
145 7
        return PSFS_PASS_ON;
146
    }
147
}
148