ZM_Object's constructor treats an array as the object's row, and validInt()
and validCardinal() returned an array as they got it (preg_replace() does).
So wherever a request id reached `new ZM\X(...)`, raw or through those
validators, a crafted array built an object whose Id, UserId or MonitorId
the sender chose. The permission check then looked at those, and the delete
or update that followed used the Id. A user with Events=View deleted another
user's filter by posting Id[Id]=<filter>&Id[UserId]=<self> to the filter
delete action; the same shape reached events (MonitorId), users, reports,
snapshots and storage.
validInt() and validCardinal() now give '' for anything that isn't a single
value. The places that built an object from a raw $_REQUEST value (the
control and event ajax handlers, the event, filter, report, snapshot and
storage actions, view_hls, and the download and event detail modals) take
the id through validCardinal().
tests/php/test_request_ids_scalar.php covers the validators with arrays and
fails if web code builds an object straight from $_REQUEST, $_GET or $_POST.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
image.php, view_video.php and view_hls.php previously checked only the
coarse canView('Events') / canView('Snapshots') role before streaming
media for a user-supplied event id. An authenticated user denied access
to a monitor could still fetch event snapshots, captured frames,
recorded MP4s and HLS manifests for events belonging to that monitor by
calling the direct endpoints with the event id.
Call Event->canView() after loading the event and return 404 on denial
so the event id cannot be enumerated. view_video also validates
Event->Id() so unknown ids return 404 instead of an empty 200 body.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- When executing Length() and Duration() , do not write the result to the database.
- When assigning a value to the "data-video-duration-secs" attribute, first check the Length in $row['Length'] and, if it is 0, only then check the file's Duration.
- If the M3u8 file is missing or invalid, do not cache the server response.
The segment-URL rewrite captured "index.php?…" in $1 and then prepended
$base_url . '?', producing a double-prefixed URL that drops the view=
routing parameter. Players fetched the manifest itself instead of the
mp4 byte range, so Firefox showed a spinner and Chrome silently failed.
Capture only the query string (everything after "index.php?") for both
the bare segment URLs and the EXT-X-MAP URI rewrite.
Same fix that landed (unmerged) in PR #4803 and PR #4806; pulled in here
so the full HLS path can be reviewed and merged together.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fragment tracking:
- Rewrite detection to use avio_flush()+avio_tell() before each video
keyframe write, giving exact fragment boundaries. Fixes duplicate
entries and missing first fragment in the m3u8 manifest.
- Remove unused pending_fragment_dts_ and last_flush_pos_ members.
Live event playback:
- Set DefaultVideo to index.m3u8 at event INSERT time (no DB update
needed after videoStore opens)
- Rename incomplete file to include codec (incomplete.h264.mp4) so
canPlayCodec() works during recording
- Rewrite final m3u8 after video file rename to reference final name
- Add live retry handler in event.php: retries all error codes with
backoff (3s then 5s), max 30 retries, resets on successful playback
- Update progress bar duration from video element for live events
Fallback handling:
- view_video.php: when DefaultVideo is m3u8 and no file param given,
search event dir for actual mp4 (final name then incomplete)
- view_hls.php: reject m3u8 with no EXTINF entries (event just started)
- event.php: require m3u8 file to exist on disk before offering HLS,
fall back to direct MP4 otherwise
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Write a single continuous fragmented MP4 per event and generate an HLS
m3u8 manifest with byte-range references into that file. This enables
seamless browser playback via video.js's built-in http-streaming (VHS)
without needing separate segment files.
C++ changes:
- VideoStore tracks fragment boundaries (moof+mdat byte offsets and
durations) as packets are written, by monitoring avio_tell() around
keyframe writes in write_packet()
- Add writeM3U8() method that generates EXT-X-VERSION:7 byte-range
manifests with EXT-X-MAP for the init segment
- Event writes a live m3u8 (no EXT-X-ENDLIST) on each new fragment
for in-progress viewing, and a final VOD manifest at event close
- Change movflags to frag_keyframe+empty_moov+default_base_moof
(default_base_moof required by HLS fMP4 spec, faststart removed
as it's meaningless with empty_moov)
PHP/web changes:
- New view_hls.php endpoint serves pre-built m3u8 with auth tokens
- event.php detects index.m3u8 and uses HLS as primary source with
direct MP4 as fallback
- CSRF exemption for view_hls in index.php
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>