or ) never finished rendering. The arrow branch * of parseUniversalPattern() entered on that character without advancing the * index, so the loop ran until the 30 s PHP limit and the request answered * HTTP 500 with an empty body. No attacker is needed: any document that is * expected to contain angle brackets triggered it. * - FIX: Layer 7 of validateRequestedFile() accepted ASCII only, so a file whose * name or directory carried non-Latin letters, or a space, was refused with * "Access denied or file not found" and status 200. Cyrillic names were 17 of * the 556 paths under one browsed tree - documents that the listing offered * and the viewer then refused. The same rule stands in mdvDirSegments() and in * the updater's dirSegments(); all three were widened together because it is * one rule in three places. Containment, traversal, separator, control * character and extension checks are unchanged, and the mask still cannot * admit the configuration file, which is not a .md name. * v2.15.1: the copy control carries the same .copy-btn class as the code and quote controls, so the one delegated handler in js/md/md.js serves it too; it reads data-copy-source when the control has no rendered block to copy. No second handler anywhere. * v2.15.0: a copy control for the document being read. * - FEATURE: FEATURE_COPY_BUTTON (default on) shows a button that puts the * source .md into the clipboard, so a reader can paste the document rather * than save it. It sits to the left of the download control. * - SAFETY: the button asks for the very request the download control uses * (?file=...&download=1), which validateRequestedFile() has already been * deciding for every other read. The page therefore does not carry a second * copy of the document, and the readable set is unchanged. * - NOTE: like the two download toggles this is an interface gate rather than * access control, and it is read-only: nothing here writes to disk. * v2.14.0: two download toggles, and the read path they need. * - FEATURE: FEATURE_DOWNLOAD_ICON (default on) shows a download control on * every row of the file browser; FEATURE_DOWNLOAD_BUTTON (default on) shows * one in the document toolbar, to the left of the width and font controls. * - FEATURE: a GET carrying download=1 next to a validated ?file= answers with * the bytes of that same file, as an attachment, instead of the viewer page. * The request is validated by validateRequestedFile() unchanged, so the * readable set does not widen by a single path. The type is announced as * application/octet-stream with nosniff: a .md is text, and sending it as one * under a filename chosen by the file's own author is how it would become a * script in the browser's eyes. * - NOTE: both toggles are interface gates, not access control. Requesting * ?file=...&download=1 by hand still answers for any file the validator * already allows; forbidding the read outright would need a new * installation-level key. * v2.13.0: a GET whose path carries a doubled slash is redirected once to the clean path. A page opened at //extensions/... keeps it, and every relative link built from it inherits the doubled slash - which is what turned a document link into a protocol-relative URL whose host was the first path segment. POST is left alone. * Changelog v2.12.0: * - FEATURE: Per-directory .md.ini. A directory inside the browse root may * carry its own .md.ini; the settings that apply to a document are the base * file next to md.php with every .md.ini found from the browse root down to * the document's directory applied on top of it (a deeper file wins). * - FEATURE: Only DISABLE_UPLOAD, DISABLE_CLIPBOARD and * DISABLE_SAVE_CLIPBOARD_TO_FILE are read from a directory file. BROWSE_DIR, * API_KEY, ALLOW_UPDATE, ALLOW_RESTORE and ALLOW_CREATE_INDEX_PHP_LINK stay * installation-level: they are ignored there and reported to the error log. * - FEATURE: The interface flags and the new MDV_CONFIG.currentDir value follow * the directory being shown, so a subdirectory can allow or forbid upload and * clipboard saving on its own. * - SAFETY: A directory file is only read when its realpath() is inside * realpath(BROWSE_DIR); a path that climbs out with ".." is refused. Values * are parsed as strictly as in the base file and an unusable one is skipped * with a log entry. * * Changelog v2.11.0: * - DOCS: The .md.ini key reference and the generated template now state that * "Upload .md" and "Save to File" write into BROWSE_DIR when it is configured, * and fall back to uploads.md/ next to md.php when it is not. The matching * updater.php v3.10.0 change makes that behaviour real; viewer logic is * unchanged in this release. * * Changelog v2.10.0: * - FEATURE: New optional .md.ini key BROWSE_DIR. A viewer installed in a * subdirectory can now list and open documents that live in another * directory (for example /share/ in the site root) instead of the * directory md.php itself sits in. * - FEATURE: BROWSE_DIR accepts an absolute path, a DOCUMENT_ROOT-relative * path or an md.php-relative path. The value is trimmed, optional * surrounding quotes are stripped, and the first candidate that resolves * to an existing readable directory wins (checked in that order: the value * as-is when absolute, DOCUMENT_ROOT + value, md.php directory + value). * - SAFETY: An empty or missing BROWSE_DIR keeps the previous behaviour. * A value longer than 255 bytes or containing a null byte is discarded. * When nothing resolves, the viewer writes a warning to the error log and * falls back to the md.php directory. The directory-containment * (realpath whitelist) check in validateRequestedFile() is unchanged, so * path traversal stays blocked. * * Changelog v2.9.5: * - FIXED: Stylesheet, script and updater URLs are now relative to the * directory the page is served from. MD.Viewer installed in a * subdirectory (for example /extensions/md-viewer/) no longer requests * css/md/*.css and js/md/*.js from the site root and no longer 404s. * * Changelog v2.9.4: * - REFACTORED: Front-end assets moved out of the assets/ directory. JavaScript * now lives in js/md/ and CSS in css/md/. Every and
Clipboard Preview
Clipboard Preview — saving disabled by server