Skip to content

Follow m3u playlists under every content type servers send, HLS included - #2231

Merged
xroche merged 10 commits into
masterfrom
m3u-mpegurl
Nov 14, 2026
Merged

xroche merged 10 commits into
masterfrom
m3u-mpegurl

Conversation

@xroche

@xroche xroche commented Nov 14, 2026 •

Copy link
Copy Markdown
Owner

HTTrack now follows the links in an m3u playlist served as audio/mpegurl, application/x-mpegurl or application/vnd.apple.mpegurl. The .m3u8 extension now maps to application/vnd.apple.mpegurl, so a .m3u8 file sent as application/octet-stream is followed too. A playlist link starts only at the first non-blank byte of a line and never on a # line. So a line gives at most one link, even when it holds two names. CR-only line ends and a UTF-8 BOM work. A page of any type that ends inside a link no longer gets a NUL added to its mirror.

Closes #2228
Closes #2230

xroche and others added 5 commits November 14, 2026 11:03
An m3u playlist was parsed for links only when served as
audio/x-mpegurl. Python's http.server sends audio/mpegurl, and HLS
playlists come as application/vnd.apple.mpegurl or
application/x-mpegurl, so the files they list were never fetched.

Playlist mode also scanned "#" lines for links, so every extended
playlist asked the server for "EXTM3U" and had its "#EXTM3U" header
rewritten to "#EXTM3U.html". Those lines are now skipped.

Closes #2228

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
In playlist mode every byte could start a link, and the link code skips
whitespace including newlines. A link starting on a line end or a blank
line therefore ran into the next line, so a "#" line after another "#"
line or after a blank line was fetched with its "#" dropped, and a
trailing "#" line gained "#" and a NUL in the mirror. A link now starts
only at the first non-blank byte of a line, a "#" line is skipped to its
end, and a link also ends at a CR so CR-only playlists work.

Test 2271 now serves each playlist type with "#" lines, blank and
whitespace-only lines, a "#" inside a media line, and LF, CRLF and
CR-only line ends, and checks the mirror byte for byte.

Based on the fix in 45291948 (playlist-comment-lines).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
A link starts only at a line's first non-blank byte, so excluding '#' there
is enough: the skip loop to the end of the line is not needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
…type

A .m3u8 served as application/octet-stream is now parsed, because its
extension maps to application/vnd.apple.mpegurl.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
@xroche xroche changed the title Follow m3u playlists served as audio/mpegurl and the HLS types Follow m3u playlists under every content type servers send, HLS included Nov 14, 2026
xroche and others added 4 commits November 14, 2026 12:55
A link that runs to the end of the buffer stops on its NUL. The main
loop then stepped past it and flushed that NUL into the mirror, so the
scan pointer is now clamped to the buffer end. A UTF-8 BOM at the start of a
playlist was read as the first link, so the first line now starts after
it. The CR end of a playlist link is gated on LNK mode by name.

Test 2271 now covers these cases:
- a last line with no line end, after CR and after LF
- a BOM
- a line with two names, which gives one link
- an octet stream with no playlist extension, which is not scanned

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
The scan pointer is clamped to the buffer end so that a link at EOF does
not write the buffer's NUL. A base href or applet codebase is skipped
rather than written, and its write mark then sat one byte past the NUL,
after the clamped pointer. The flush then asked for a negative length and
aborted. The write mark is now clamped with the scan pointer.

Fuzz seeds and test 443 cover a page that ends inside a base href or an
applet codebase value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
@xroche
xroche merged commit 39392c5 into master Nov 14, 2026
41 checks passed
@xroche
xroche deleted the m3u-mpegurl branch November 14, 2026 14:28
xroche added a commit that referenced this pull request Nov 14, 2026
…#2234)

An abort used to download or probe every remaining link on the page
before it stopped, because the waits for a link's type ignored it. The
engine now stops at once and leaves those links online, and `--continue`
fetches them. A soft stop (the first ^C, or `hts_request_stop(opt, 0)`)
is unchanged. Only hard stops (the abort lock, SIGTERM,
`hts_request_stop(opt, 1)`) leave the remaining links for `--continue`.

Test 436 failed on a macOS runner on #2231, because it passed only when
the last file happened to be cut. It now checks that at most the first
two files exist after the abort, and that `--continue` completes the
mirror. Test 262 covers SIGTERM during the `-%N0` type probe.

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant