\n";
125 | $class = $mail['flowed'] ? ' class="flowed"' : '';
126 | echo " \n";
127 |
128 | /*
129 | * If there was no text part of the message, see what we can do about creating
130 | * one from a text/html part, or just inject a note that there was no text to
131 | * avoid further errors.
132 | */
133 | if (!array_key_exists('text', $mail)) {
134 | if (array_key_exists('html', $mail)) {
135 | /*
136 | * This just aggressively strips out all tags. For the examples at
137 | * hand, this looked okay-ish. Better than nothing, at least, and
138 | * should be totally safe because all of the text get re-encoded
139 | * later.
140 | */
141 | // This makes HTML from Fastmail retain paragraph breaks
142 | $text = preg_replace('#
#', "\n\n", $mail['html']);
143 | // And this avoids extra linebreaks from another example (Android?)
144 | $text = preg_replace("#\n
\n#", "\n", $text);
145 | $mail['text'] = html_entity_decode(strip_tags($text), encoding: 'UTF-8');
146 | } else {
147 | $mail['text'] = "> There was no text content in this message.";
148 | }
149 | }
150 |
151 | $lines = preg_split("@(?<=\r\n|\n)@", $mail['text']);
152 | $insig = $is_commit = $is_diff = 0;
153 | $level = 0;
154 | $in_flow = $was_flowed = false;
155 | $in_code_block = false;
156 |
157 | foreach ($lines as $line) {
158 | # Trim end of line
159 | $line = preg_replace('/\r?\n$/', '', $line);
160 |
161 | # fix lines that started with a period and got escaped
162 | if (str_starts_with($line, "..")) {
163 | $line = substr($line, 1);
164 | }
165 |
166 | # Notice commit messages so we can highlight the diffs
167 | if (str_starts_with($line, 'Commit: https://github.com/php')) {
168 | $is_commit = 1;
169 | }
170 |
171 | # We don't use htmlentities() because it seems like overkill and that
172 | # makes all of the later substitutions more complicated.
173 | $line = htmlspecialchars($line, ENT_QUOTES|ENT_SUBSTITUTE|ENT_HTML5, "utf-8");
174 |
175 | # Turn bare links, not within [] or (), to HTML links
176 | $line = preg_replace(
177 | "/(^|[^[(])((mailto|https?|ftp|nntp|news):.+?)(>|\\s|\\)|\\.\\s|$)/",
178 | "\\1\\2\\4",
179 | $line
180 | );
181 |
182 | # Turn Markdown links to HTML links
183 | $line = preg_replace(
184 | "/\[((mailto|https?|ftp|nntp|news):.+?)\]\((.+?)\)/",
185 | "\\3",
186 | $line
187 | );
188 |
189 | # Highlight inline code
190 | $line = preg_replace(
191 | "/`([^`]+?)`/",
192 | "\\1",
193 | $line
194 | );
195 |
196 | # Begin signature when we see the tell-tale '-- ' line
197 | if (!$insig && $line == "-- ") {
198 | echo "";
199 | $insig = 1;
200 | }
201 |
202 | # In commit messages, highlight lines that start with + or -
203 | if (!$insig && $is_commit && preg_match('/^[-+]/', $line, $m)) {
204 | $is_diff = 1;
205 | echo '';
206 | }
207 |
208 | # This gets a little tricker -- "flowed" messages basically have long
209 | # quoted lines broken up, so we can put quoted blocks in levels of
210 | # blocks instead of highlighting them per-line
211 |
212 | if (!$insig && $mail['flowed']) {
213 | $flowed = false;
214 | $new_level = 0;
215 |
216 | if (preg_match('/^((\s*>)+)(.*)/', $line, $m)) {
217 | $new_level = substr_count($m[1], $m[2]);
218 | $line = $m[3];
219 | }
220 |
221 | # Trim leading space (a format=flowed thing)
222 | if (str_starts_with($line, ' ')) {
223 | $line = substr($line, 1);
224 | }
225 |
226 | # A "flowed" line ends with a space. We also remove it if DelSp = "Yes".
227 | if (str_ends_with($line, ' ')) {
228 | $flowed = true;
229 | if ($mail['delsp']) {
230 | $line = substr($line, 0, -1);
231 | }
232 | }
233 |
234 | # If this line had more quoting, go ahead and open to that level
235 | if ($new_level && $new_level > $level) {
236 | foreach (range($level + 1, $new_level) as $this_level) {
237 | echo "
";
238 | }
239 | $level = $new_level;
240 | $in_flow = true;
241 | }
242 | # Otherwise if we are in a flow, but this line's level is lower (but
243 | # not 0), we need to close up the higher levels
244 | elseif ($in_flow && $new_level && $new_level < $level) {
245 | echo str_repeat('
', $level - $new_level);
246 | $level = $new_level;
247 | }
248 |
249 | # Handle indented code blocks
250 | if (preg_match('/( |\xC2\xA0){4}/', $line)) {
251 | if (!$in_code_block) {
252 | echo '
';
253 | $in_code_block = true;
254 | }
255 | } elseif (!$flowed && !$was_flowed) {
256 | if ($in_code_block && is_bool($in_code_block)) {
257 | echo '';
258 | $in_code_block = false;
259 | }
260 | }
261 |
262 | # Handle ``` delimited code blocks
263 | if (preg_match('/^```(\w+)?$/', $line, $m)) {
264 | if ($in_code_block) {
265 | echo '';
266 | $in_code_block = false;
267 | continue;
268 | } else {
269 | $language = $m[1] ?? 'php';
270 | echo "
";
271 | $in_code_block = $language;
272 | continue;
273 | }
274 | }
275 |
276 | # Hey, it's the actual line of text!
277 | echo $line;
278 |
279 | # If the line is fixed, we close a flow or just add a newline
280 | if (!$flowed) {
281 | if ($level != $new_level) {
282 | # Close out code block if we were in one
283 | if ($in_code_block) {
284 | echo '';
285 | $in_code_block = false;
286 | }
287 | echo str_repeat("
", $level) . "\n";
288 | $level = 0;
289 | $in_flow = false;
290 | } else {
291 | echo "\n";
292 | }
293 | }
294 |
295 | $was_flowed = $flowed;
296 | }
297 | # Otherwise we're in a signature or not flowed
298 | else {
299 | if (!$insig && preg_match('/^((\s*\w*?> ?)+)/', $line, $m)) {
300 | $level = substr_count($m[1], '>') % 4;
301 | echo "", wordwrap($line, 100, "\n" . $m[1]), "";
302 | } else {
303 | echo wordwrap($line, 100);
304 | }
305 | echo "\n";
306 | }
307 |
308 | # If this line was a diff, close out the
309 | if ($is_diff) {
310 | $is_diff = 0;
311 | echo '';
312 | }
313 | }
314 |
315 | if ($in_code_block) {
316 | echo '';
317 | }
318 | if ($insig) {
319 | echo "";
320 | $insig = 0;
321 | }
322 | if ($mail['flowed'] && $level) {
323 | echo str_repeat('', $level);
324 | }
325 |
326 | echo "
";
327 |
328 | if (!empty($mail['attachment'])) {
329 | foreach ($mail['attachment'] as $mimecount => $attachment) {
330 | $mimetype = $attachment['mimetype'];
331 | $name = $attachment['filename'];
332 |
333 | if ($mimetype == 'text/plain') {
334 | echo htmlspecialchars($attachment['data']);
335 | continue;
336 | }
337 |
338 | if (!empty($attachment['description'])) {
339 | $description = trim($attachment['description']) . " ";
340 | } else {
341 | $description = '';
342 | }
343 |
344 | $description .= $name;
345 | $link_desc = "[$mimetype]";
346 | if (strlen($description)) {
347 | $link_desc .= " " . $description;
348 | }
349 |
350 | $dl_link = "/getpart.php?group=$group&article=$article&part=$mimecount";
351 | $link_desc = htmlspecialchars($link_desc, ENT_QUOTES, 'UTF-8');
352 |
353 | /* Attachment filename and mimetype might contain malicious characters */
354 | printf(
355 | 'Attachment: %s
' . "\n",
356 | $dl_link,
357 | htmlspecialchars($link_desc)
358 | );
359 | }
360 | }
361 |
362 | echo " \n";
363 | echo "
\n";
364 |
365 | try {
366 | $overview = $nntpClient->getThreadOverview($group, $article);
367 |
368 | $threads = new \PhpWeb\ThreadTree($overview['articles']);
369 | ?>
370 |