| 879510b4 | 07-Jun-2026 |
splitbrain <86426+splitbrain@users.noreply.github.com> |
Update deleted files |
| b12755b0 | 07-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
Make common_pageinfo tests independent of execution order
The pageinfo() tests relied on running in a fixed order: test_editor_and_externaledits mutated wiki:syntax (changelog entry + file mtime), a
Make common_pageinfo tests independent of execution order
The pageinfo() tests relied on running in a fixed order: test_editor_and_externaledits mutated wiki:syntax (changelog entry + file mtime), and the other tests relied on that page being pristine. Move the mutating test to its own page (wiki:dokuwiki) so the shared per-class data dir stays clean, prime the last_change metadata within each test instead of across tests, and fix the affected expectations. The suite now passes in isolation and in random/reverse order.
While here, modernize the file: - capture the pageinfo() result once instead of calling it twice - add public/protected visibility markers to all methods - convert array() to the short [] syntax - replace the deprecated addLogEntry() with the PageFile API it wraps
show more ...
|
| 2cc228b6 | 06-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
Read REMOTE_USER through $INPUT in the RPC servers
Route the logged-in check that selects between a 401 and 403 response through the $INPUT abstraction instead of reading the $_SERVER superglobal di
Read REMOTE_USER through $INPUT in the RPC servers
Route the logged-in check that selects between a 401 and 403 response through the $INPUT abstraction instead of reading the $_SERVER superglobal directly. This matches how the rest of both servers already read request data and lets tests exercise both branches by setting the server value via $INPUT.
show more ...
|
| 17acbd0f | 06-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
Cast the ACL permission to an integer when saving the rule table
The bulk update path pulled the permission map straight from input and wrote each value verbatim into a tab-separated ACL line, while
Cast the ACL permission to an integer when saving the rule table
The bulk update path pulled the permission map straight from input and wrote each value verbatim into a tab-separated ACL line, while the single-rule path already read it through an integer filter. A non-integer value could carry a newline or tab and inject extra rules into the file. Cast to int so the value can only ever be a single permission number.
This was not exploitable: the rule table is reachable only through the ACL admin plugin, which is superuser-only and CSRF-protected, and a superuser can already edit the ACL file directly. The cast is hardening for consistency with the single-rule path.
show more ...
|
| 92a018d9 | 06-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
Remove unused legacy image resize and crop functions
media_resize_imageIM(), media_crop_imageIM() and media_resize_imageGD() were superseded by the Slika library years ago (two were already marked d
Remove unused legacy image resize and crop functions
media_resize_imageIM(), media_crop_imageIM() and media_resize_imageGD() were superseded by the Slika library years ago (two were already marked deprecated in 2020). They had no callers left in core, and a search of the published plugin and template ecosystem found no external callers either.
show more ...
|
| 795d8419 | 06-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
Read the code block download offset as an integer
The named code/file block download link reads the codeblockOffset request parameter and adds it to the current block index to build the export link.
Read the code block download offset as an integer
The named code/file block download link reads the codeblockOffset request parameter and adds it to the current block index to build the export link. The value was read as a string and then used in arithmetic. On PHP 8 a non-numeric value turns that addition into a string + int operation, which throws a TypeError and produces a fatal error when rendering any page that contains a named code or file block.
The offset is now read with $INPUT->int(), which yields 0 for absent, empty or non-numeric input and the integer value otherwise, so the arithmetic is always int + int. The has() guard and zero initialiser are no longer needed.
show more ...
|
| fa03edf3 | 06-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
(security) Enforce per-page edit permission in the revert plugin
The revert manager is accessible to managers, not just admins. Its reversion loop called saveWikiText() for every submitted page id w
(security) Enforce per-page edit permission in the revert plugin
The revert manager is accessible to managers, not just admins. Its reversion loop called saveWikiText() for every submitted page id without checking the per-page ACL, relying only on the manager role. A manager denied edit on a namespace could therefore revert those pages to an older revision or blank them entirely (low severity).
The page listing already hid unreadable pages, but offered every readable page for reversion, including ones the manager could not edit. A hand-crafted POST could also target arbitrary ids regardless of what the listing showed.
Each id is now cleaned and checked for edit permission before it is reverted; ids that fail the check are silently skipped. The listing additionally only offers pages the manager actually has edit permission on, so the form and the action agree.
show more ...
|
| 37bd308a | 06-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
Add unit tests for the Draft class
The Draft class had no dedicated tests. Cover the save/retrieve round trip for whole-page and section edits, the cases where no draft is written (drafts disabled,
Add unit tests for the Draft class
The Draft class had no dedicated tests. Cover the save/retrieve round trip for whole-page and section edits, the cases where no draft is written (drafts disabled, nothing edited), draft deletion, reading a missing draft, and the constructor's purging of drafts that predate the current page revision.
show more ...
|
| 5d8c9d42 | 06-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
(security) Require a security token for the lock AJAX call
The lock AJAX call refreshes the edit lock and saves a draft, both of which change server state. It was gated only by the write ACL and, un
(security) Require a security token for the lock AJAX call
The lock AJAX call refreshes the edit lock and saves a draft, both of which change server state. It was gated only by the write ACL and, unlike the sibling draft-delete call, did not verify a security token (low severity).
A cross-site forged POST against a logged-in user could, within that user's own write permissions, take or hold an edit lock and store attacker-controlled draft content under their name.
The call now verifies the security token before taking the lock or saving the draft. Logged out users are unaffected, as no token is issued or checked for them. The edit lock timer now always sends the token with its refresh request, including when draft saving is disabled.
show more ...
|
| aabc4782 | 06-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
(security) Add CSRF protection and use Form class in popularity plugin
The plugin is accessible to managers, not just admins, and had two separate issues.
1. Missing CSRF token (low severity) Th
(security) Add CSRF protection and use Form class in popularity plugin
The plugin is accessible to managers, not just admins, and had two separate issues.
1. Missing CSRF token (low severity) The handler accepted the posted data and toggled autosubmit without checking a security token. A cross-site forged POST against a logged-in manager could enable autosubmit and trigger a submission of the wiki's data to the popularity server. 2. Reflected XSS (low severity) When a submission failed, the posted data was reflected back into a readonly textarea without escaping. A value closing the textarea early (eg. </textarea><script>...) could inject script into the manager's browser. To exploit this, not only a CSRF attack against an authenticated manager was needed, also the connection to the DokuWiki popularity server needed to fail.
The popularity plugin now verifies the security token before it sends data or toggles the autosubmit option.
The form is now built via the the Form API so the textarea value is escaped automatically. The security token is emitted automatically. The fallback browser-submission form posts to an external server and is built without the security token.
show more ...
|
| e8c9256a | 06-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
(security) Clean the media upload namespace in AJAX upload
The namespace passed to the AJAX backend was not cleaned correctly, resulting in two separate issues.
1. Theoretical Reflected XSS The
(security) Clean the media upload namespace in AJAX upload
The namespace passed to the AJAX backend was not cleaned correctly, resulting in two separate issues.
1. Theoretical Reflected XSS The raw namespace was reflected into the JSON response and injected into the mediamanager DOM. However since the media manager only passes cleaned namespaces to AJAX and the ajax backen only returns JSON, this issue was not exploitable. 2. Cross-namespace ACL bypass (medium severity) The uncleaned namespace was directly used to check ACLs. In a wiki where a user has upload permission in a namespace above a namespace where they don't have permissions (eg. upload allowed in :user:*, but upload denied in :user:secret:*) they could pass an upper case namespace (eg :user:SECRET) - no ACL does exist for this upper case namespace and the acl of the namespace above applies (:user). When the file is written a cleanID is applied to the full filename, turning the uppercase namespace into lowercase. This can allow users to write into a namespace they normally should not be allowed to write to, but it does require upload permissions in a higher namespace.
show more ...
|
| 39904235 | 04-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
Emit a guard newline in TextareaElement to preserve leading newlines
A browser's HTML parser ignores a single newline immediately after a <textarea> start tag, so a value whose first character is a
Emit a guard newline in TextareaElement to preserve leading newlines
A browser's HTML parser ignores a single newline immediately after a <textarea> start tag, so a value whose first character is a newline lost it on round-trip (e.g. saving a page or section whose source starts with a blank line silently dropped that line).
Emit a guard newline right after the start tag so the one the browser drops is absorbed and the value stays intact.
show more ...
|
| 4b31eadf | 04-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
fix (parsing): avoid newline loss on GFM section editing
The GFM header parsing returned a byte position pointing at the newline before the actual header resulting in the observed newline eatings as
fix (parsing): avoid newline loss on GFM section editing
The GFM header parsing returned a byte position pointing at the newline before the actual header resulting in the observed newline eatings as reported in https://github.com/dokuwiki/dokuwiki/pull/4636#issuecomment-4491970909
Additionally this fixes an oddity of DW header parsing which accidentally allowed text on the line before the opening = chars. Whitespace is still allowed.
show more ...
|
| 2e43b799 | 04-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
Render locale and plugin-bundled text as DokuWiki syntax
Static "intro" text shipped with DokuWiki, templates and plugins — inc/lang/*/*.txt, template locale files, each plugin's lang/<lc>/*.txt — i
Render locale and plugin-bundled text as DokuWiki syntax
Static "intro" text shipped with DokuWiki, templates and plugins — inc/lang/*/*.txt, template locale files, each plugin's lang/<lc>/*.txt — is authored in DokuWiki syntax. It is a core/plugin asset, not user content. When an admin configures the wiki for Markdown the DW link and monospace modes are not loaded, so these files render as literal text: [[wiki:syntax]] and ''Save'' pairs survive into the HTML.
Pin those entry points to the 'dw' flavour via the override added in the previous commit:
- p_locale_xhtml() and tpl_locale_xhtml() pass 'dw'. - PluginTrait::locale_xhtml() passes 'dw'. - PluginTrait::render_text() / PluginInterface::render_text() gain a $syntax parameter defaulting to 'dw'. The default is 'dw', not null, because the method predates GFM and its callers pass DW-syntax strings; plugins rendering user content opt back into the configured syntax with render_text($text, 'xhtml', null).
Locale output is now deterministic across a syntax switch, so its caches get over-invalidated but never under-invalidated — acceptable, as the locale render path is rare and cheap.
show more ...
|
| 16999ed1 | 04-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
parserutils: allow callers to override the parse syntax, key cache on it
With the registry now carrying the flavour as a parameter, expose that to callers: p_get_instructions(), p_cached_instruction
parserutils: allow callers to override the parse syntax, key cache on it
With the registry now carrying the flavour as a parameter, expose that to callers: p_get_instructions(), p_cached_instructions() and p_cached_output() gain an optional $syntax argument. null (the default) means "use the configured $conf['syntax']", preserving behaviour for every existing call site; an explicit flavour parses under it regardless of the wiki's configured preference.
This is what lets bundled assets render deterministically — e.g. a plugin forcing 'dw' on a document whose configured syntax is 'md'.
Because that case renders the same file under two flavours within one request, key both the in-request memo (the $run map) and the on-disk cache on $syntax so the two do not collide. The syntax enters the cache key only when passed explicitly (non-null); when null the key is unchanged, so existing caches are untouched. Plumbed through new optional $syntax args on CacheParser / CacheInstructions, appended to the key string.
show more ...
|
| 47a02a10 | 04-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
Parsing: make parse syntax a per-parse value, drop ModeInterface
The active parse's syntax flavour is a per-parse question, not process- global state: within a single request a plugin can render bun
Parsing: make parse syntax a per-parse value, drop ModeInterface
The active parse's syntax flavour is a per-parse question, not process- global state: within a single request a plugin can render bundled DokuWiki-syntax text inside an otherwise-Markdown page. Yet ModeRegistry was a singleton that read $conf['syntax'] and the $PARSER_MODES global, and every mode reached it through ModeRegistry::getInstance() — so the flavour lived in shared mutable state that two parses in one request would fight over.
Make the registry a short-lived value instead:
- ModeRegistry is constructed once per parse with an explicit $syntax and injected into Parser, Handler and every mode. getSyntax() / isDwPreferred() / isMdPreferred() consult $this->syntax; the DOKU_UNITTEST-gated mode-list cache hack is gone (each registry is fresh, nothing to invalidate). - p_get_instructions() is now the single place in the pipeline where $conf['syntax'] is read; from there the flavour travels as a parameter. No code under inc/Parsing/ reads $conf['syntax'] directly anymore — the five syntax-reading modes (Preformatted, GfmHr, GfmEscape, Externallink, GfmQuote) route through $this->registry.
Keep the two concepts apart, as documented in the ModeRegistry and AbstractMode docblocks: the user's configured *preference* stays in $conf['syntax'] for UI code (toolbar, settings), while the active parse's syntax is a parameter carried by the registry.
$PARSER_MODES is demoted to a deprecated, read-only mirror, published during loadPluginModes() — third-party syntax plugins (columnlist, alphalist2, phpwikify, skipentity) and the bundled info plugin read the global directly, often from their constructors, so the taxonomy must stay visible there. No core code reads the mirror.
Fold ModeInterface into AbstractMode while here: getSort()/handle() are abstract, the connect callbacks carry defaults, and the public $Lexer "FIXME should be done by setter" becomes setLexer()/getLexer() injected by Parser::addMode() alongside the registry. Nested-content resolution moves to the allowedCategories()/filterAllowedModes() hooks, resolved once when the registry is attached.
Tests build their own parser/registry through ParserTestBase::setSyntax() instead of mutating $conf and calling the removed ModeRegistry::reset().
show more ...
|
| 3e427828 | 02-Jun-2026 |
Wizzard <wizzardsk@gmail.com> |
Translation update (sk) |
| b8c2692f | 29-May-2026 |
Andreas Gohr <andi@splitbrain.org> |
fix(httputils): build a proper internal URL for nginx X-Accel-Redirect
Unlike Apache's mod_xsendfile and lighttpd's X-LIGHTTPD-send-file, which both accept an absolute file system path and stream th
fix(httputils): build a proper internal URL for nginx X-Accel-Redirect
Unlike Apache's mod_xsendfile and lighttpd's X-LIGHTTPD-send-file, which both accept an absolute file system path and stream the file directly, nginx treats X-Accel-Redirect as a URL: it performs an internal subrequest against the value and resolves it through `internal` location blocks in its own configuration. The redirect target therefore must be a request path nginx can map back to a file, not the file system path itself.
The previous implementation blindly stripped DOKU_INC's length from the file path, which produced a broken URL whenever a data directory had been relocated outside the DokuWiki tree.
Route the file through three cases instead: files inside DOKU_INC keep their path relative to the wiki root; files in a relocated mediadir, mediaolddir, cachedir or savedir are mapped back to their default URL below data/; everything else falls through an opt-in /_x_accel_redirect/ prefix.
Path segments are URL-encoded so filenames with spaces or other special characters resolve (fixes another potential bug in the previous implementation).
Nginx configuration:
For the default layout no extra configuration is needed - files keep their path relative to the wiki root and the regular wiki location already serves them. For relocated data directories admins must add an `internal` location below the wiki's URL space pointing at the actual file system path, e.g.:
location /data/media/ { internal; alias /srv/dokuwiki-media/; }
For the /_x_accel_redirect/ fallback (used when a plugin serves files from outside the wiki and its data directories), admins opt in by adding:
location /_x_accel_redirect/ { internal; alias /; }
When the wiki is installed under a sub path, prefix both locations accordingly (e.g. /wiki/data/media/ and /wiki/_x_accel_redirect/).
fixes: #2895
show more ...
|
| 54e76260 | 29-May-2026 |
Andreas Gohr <andi@splitbrain.org> |
chore: adjust overlong line |
| 7e687fd8 | 29-May-2026 |
Andreas Gohr <andi@splitbrain.org> |
fix(auth): scope media ACL checks to the namespace
Media files have no per-file ACLs; permissions must be evaluated against the namespace they live in. Several call sites passed the raw media ID to
fix(auth): scope media ACL checks to the namespace
Media files have no per-file ACLs; permissions must be evaluated against the namespace they live in. Several call sites passed the raw media ID to auth_quickaclcheck(), so a page-intended exact-ID rule (e.g. on wiki:secret.png) could silently apply to a media file sharing that ID.
Introduce mediaAclPath() that builds the correct namespace wildcard path (handling root-namespace media) and route all media-related ACL checks through it. Also normalize the lone `:X` sentinel variant in fetch.functions.php to the standard `:*` form.
fixes: #4647
show more ...
|
| 6e39b4e3 | 28-May-2026 |
Andreas Gohr <andi@splitbrain.org> |
refactor(search): extract LegacyIndexer wrapper for BC contract
Move the deprecated helpers (lookupKey, addMetaKeys, renameMetaValue, getPID, lookup) off Indexer and into a new LegacyIndexer wrapper
refactor(search): extract LegacyIndexer wrapper for BC contract
Move the deprecated helpers (lookupKey, addMetaKeys, renameMetaValue, getPID, lookup) off Indexer and into a new LegacyIndexer wrapper. The wrapper also restores the Doku_Indexer return contract (true|string) around addPage/deletePage/renamePage/clear so plugins using the legacy API keep working without try/catch.
idx_get_indexer() now returns the LegacyIndexer; getPages stays on Indexer because plugins call it directly on Indexer instances.
fixes #4645
show more ...
|
| 1dbc46ac | 27-May-2026 |
Tzipirigu <tzipirigu.radu25683@gmail.com> |
Translation update (ro) |
| 4f32c45b | 26-May-2026 |
Andreas Gohr <gohr@cosmocode.de> |
GfmLink: allow soft line break inside link text
The label character class explicitly forbade `\n`, so a CommonMark soft line break inside link text (e.g. `[link with<EOL>more](url)`) fell through to
GfmLink: allow soft line break inside link text
The label character class explicitly forbade `\n`, so a CommonMark soft line break inside link text (e.g. `[link with<EOL>more](url)`) fell through to literal text instead of producing a link. Loosen the class to accept a bare `\n` as long as it is not followed by a blank line — soft breaks are spec-allowed inside link text, blank lines are not, and refusing them also keeps `\n#`-anchored block modes (header, hr, ...) from being swallowed by a runaway link match.
The `\n` survives into the label string and renders as a literal line ending in HTML, which browsers display as a single space. This soft break behavior has been checked against https://spec.commonmark.org/dingus/
Note that this behavior differs from github where the line break is rendered as a hard break <br>.
show more ...
|
| 65dd2042 | 26-May-2026 |
Andreas Gohr <gohr@cosmocode.de> |
GfmEscape: defer \\<EOL> to DW Linebreak in mixed-syntax modes
Both GfmEscape (sort 5) and DW Linebreak (sort 140) can claim the two backslashes of `\\` followed by space/tab/newline. The lexer's ti
GfmEscape: defer \\<EOL> to DW Linebreak in mixed-syntax modes
Both GfmEscape (sort 5) and DW Linebreak (sort 140) can claim the two backslashes of `\\` followed by space/tab/newline. The lexer's tie-breaker picked GfmEscape, so DW's forced linebreak silently lost its delimiter under dw+md and md+dw. Add a negative lookahead that declines `\\[ \t\n]` whenever DW syntax is loaded — pure md keeps GFM-spec behavior. Mid-line `\\` (UNC paths etc.) still escapes.
show more ...
|
| 1536bca2 | 24-May-2026 |
Kishore D Rajegowda <kishore328@gmail.com> |
fix(locktimer): reset lock timer on paste and other input events
The lock-timer handler was bound only to `keypress`, which fires for character key presses but not for paste via right-click menu, dr
fix(locktimer): reset lock timer on paste and other input events
The lock-timer handler was bound only to `keypress`, which fires for character key presses but not for paste via right-click menu, drag-and-drop, or other non-keyboard input methods. Users editing a page solely by pasting could therefore lose their page lock after the configured timeout despite active editing.
Switch the binding to the `input` event, which fires on every value change of the textarea regardless of source (typing, paste via any method, cut, drag-drop, undo/redo). This is a superset of the previous behaviour, so no editing action regresses.
Fixes #3714
show more ...
|