1
+ <?php
2
+ /**
3
+ * Questo file fa parte del progetto epod4.
4
+ * Il codice è fornito senza alcuna garanzia e distribuito
5
+ * con licenza di tipo open source.
6
+ * Per le informazioni sui diritti e le informazioni sulla licenza
7
+ * consultare il file LICENSE che deve essere distribuito
8
+ * insieme a questo codice.
9
+ *
10
+ * (c) Luca Saba <[email protected] >
11
+ *
12
+ * Created by PhpStorm.
13
+ * User: luca
14
+ * Date: 13/12/17
15
+ * Time: 10.21
16
+ */
17
+
18
+ namespace PhpPec \Parser ;
19
+
20
+ /**
21
+ * Class PostacertParser
22
+ * Effettua il parsing di postacert.eml
23
+ * Quello che ci serve è il testo della mail originale.
24
+ * @package PhpPec\Parser
25
+ */
26
+ class PostacertParser
27
+ {
28
+ /**
29
+ * La regular expression per il boundary di una multipart mime mail
30
+ */
31
+ const MULTIPART_REGEX ='/Content-Type: multipart\/(mixed|alternative);\s+boundary="([\w \\\'\(\)\+,\.\-@\=\/?]+)"/ ' ;
32
+
33
+ private $ sevenBitChars = [
34
+ '=20 ' , // space.
35
+ '=E2=80=99 ' , // single quote.
36
+ '=0A ' , // line break.
37
+ '=A0 ' , // non-breaking space.
38
+ '=C2=A0 ' , // non-breaking space.
39
+ "= \r\n" , // joined line.
40
+ '=E2=80=A6 ' , // ellipsis.
41
+ '=E2=80=A2 ' , // bullet.
42
+ ];
43
+
44
+ private $ sevenBitSubstitute = [
45
+ ' ' ,
46
+ "' " ,
47
+ "\r\n" ,
48
+ ' ' ,
49
+ ' ' ,
50
+ '' ,
51
+ '… ' ,
52
+ '• '
53
+ ];
54
+
55
+ /**
56
+ * Il contenuto di postacert.eml
57
+ * @var string
58
+ */
59
+ private $ content ;
60
+
61
+ /**
62
+ * @var array
63
+ */
64
+ private $ boundaries ;
65
+
66
+ /**
67
+ * @var array
68
+ */
69
+ private $ types ;
70
+
71
+ /**
72
+ * @var array
73
+ */
74
+ private $ fragments ;
75
+
76
+ public function __construct ($ content )
77
+ {
78
+ $ this ->content = $ content ;
79
+ $ this ->boundaries = array ();
80
+ $ this ->types = array ();
81
+ $ this ->fragments = array ();
82
+ $ this ->parse ();
83
+ }
84
+
85
+ /**
86
+ * The body must then contain
87
+ * one or more body parts, each preceded by a boundary delimiter line,
88
+ * and the last one followed by a closing boundary delimiter line.
89
+ * After its boundary delimiter line, each body part then consists of a
90
+ * header area, a blank line, and a body area.
91
+ * --------------------------------------------------------------------
92
+ * The boundary delimiter line is then defined as a line
93
+ * consisting entirely of two hyphen characters ("-", decimal value 45)
94
+ * followed by the boundary parameter value from the Content-Type header
95
+ * field, optional linear whitespace, and a terminating CRLF.
96
+ * --------------------------------------------------------------------
97
+ * boundary := 0*69<bchars> bcharsnospace
98
+ * bchars := bcharsnospace / " "
99
+ * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
100
+ * "+" / "_" / "," / "-" / "." /
101
+ * "/" / ":" / "=" / "?"
102
+ */
103
+ private function parse ()
104
+ {
105
+ preg_match_all ($ this ::MULTIPART_REGEX , $ this ->content , $ matches );
106
+ /**
107
+ * $matches dovrebbe essere un array composto da 3 elementi
108
+ * $matches[0] contiene tutte le stringhe che combaciano con la regex
109
+ * $matches[1] contiene tutti i tipo di multipart
110
+ * $matches[2] contiene tutti i boundary
111
+ */
112
+ if (count ($ matches ) != 3 ) {
113
+ return ;
114
+ }
115
+ for ($ i = 0 ; $ i < count ($ matches [0 ]); $ i ++) {
116
+ $ this ->boundaries [] = '-- ' .$ matches [2 ][$ i ];
117
+ $ this ->types [] = $ matches [1 ][$ i ];
118
+ }
119
+
120
+ /**
121
+ * Leggo postacert.eml riga per riga alla ricerca dei vari frammenti
122
+ */
123
+ $ numeroFrammento = 0 ;
124
+ $ partiMessaggio = array ();
125
+ foreach (preg_split ("/(( \r? \n)|( \r\n?))/ " , $ this ->content ) as $ line ){
126
+ if (in_array (trim ($ line ), $ this ->boundaries )) {
127
+ $ numeroFrammento ++;
128
+ continue ;
129
+ }
130
+ if (isset ($ partiMessaggio [$ numeroFrammento ])) {
131
+ $ partiMessaggio [$ numeroFrammento ] .= $ line ."\n" ;
132
+ } else {
133
+ $ partiMessaggio [$ numeroFrammento ] = $ line ."\n" ;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * A questo punto mi servono i frammenti il cui content type sia
139
+ * text/plain o text/html
140
+ */
141
+ $ this ->fragments = array ();
142
+ foreach ($ partiMessaggio as $ parteMessaggio ) {
143
+ if (preg_match ('/Content-Type: text\/(html|plain)/ ' , $ parteMessaggio , $ matches )) {
144
+ // Ripulisco il frammento dall'eventuale finale di parte
145
+ foreach ($ this ->boundaries as $ boundary ) {
146
+ $ parteMessaggio = str_replace ("$ boundary-- \n" , '' , $ parteMessaggio );
147
+ }
148
+
149
+ // Gestisco l'eventuale encoding del contenuto:
150
+ preg_match ('/Content-Transfer-Encoding: (\w+)/ ' , $ parteMessaggio , $ encoding );
151
+ if (count ($ encoding ) > 0 ) {
152
+ // Elimino l'intestazione della parte del messaggio
153
+ $ parteMessaggio = trim (preg_replace ('/^Content[\w-\s:\/;=]+\n\n/ ' , '' , $ parteMessaggio ));
154
+ switch (strtolower ($ encoding [1 ])) {
155
+ case 'base64 ' :
156
+ $ parteMessaggio = base64_decode ($ parteMessaggio );
157
+ break ;
158
+ case '7bit ' :
159
+ //$parteMessaggio = str_replace($this->sevenBitChars, $this->sevenBitSubstitute, $parteMessaggio);
160
+ $ parteMessaggio = mb_convert_encoding ($ parteMessaggio , 'UTF-8 ' , 'UTF-7 ' );
161
+ break ;
162
+ case '8bit ' :
163
+ $ parteMessaggio = imap_8bit ($ parteMessaggio );
164
+ break ;
165
+ case 'quoted ' :
166
+ $ parteMessaggio = utf8_encode (quoted_printable_decode ($ parteMessaggio ));
167
+ break ;
168
+ default :
169
+ $ parteMessaggio .= strtolower ($ encoding [1 ]);
170
+ }
171
+ }
172
+
173
+ $ this ->fragments [] = [
174
+ 'contenuto ' => $ parteMessaggio ,
175
+ 'tipo ' => $ matches [1 ]
176
+ ];
177
+ }
178
+ }
179
+ }
180
+
181
+ public function getFragments ()
182
+ {
183
+ return $ this ->fragments ;
184
+ }
185
+ }
0 commit comments