commit fc91d0f1e89a9791833c0937147039f7009ca706 from: jrmu date: Tue Mar 07 05:00:27 2023 UTC Daily backup commit - 3281ec97b6c959eef4aee8593b1f58a12dbbc56e commit + fc91d0f1e89a9791833c0937147039f7009ca706 blob - 3e84a31ee7ee6d92626fd8c07440e037e843aaa5 blob + 9a9c3d661b475c86272bad8c4d122c0e8ca9f175 --- README.txt +++ README.txt @@ -11,9 +11,9 @@ PmWiki is distributed with the following directories: pub/guiedit/ Files for the Edit Form's GUIEdit module scripts/ Scripts that are part of PmWiki wikilib.d/ Bundled wiki pages, including - * a default Home Page - * PmWiki documentation pages - * some Site-oriented pages + * a default Home Page + * PmWiki documentation pages + * Site-oriented interface and configuration pages After PmWiki is installed the following directories may also exist: @@ -23,16 +23,19 @@ After PmWiki is installed the following directories ma For quick installation advice, see docs/INSTALL.txt. For more extensive information about installing PmWiki, visit - http://pmwiki.org/wiki/PmWiki/Installation + https://pmwiki.org/wiki/PmWiki/Installation For information about running PmWiki in standalone mode without requiring a webserver, visit - http://pmwiki.org/wiki/Cookbook/Standalone + https://pmwiki.org/wiki/Cookbook/Standalone -PmWiki is Copyright 2001-2006 Patrick R. Michaud +PmWiki is Copyright 2001-2022 Patrick R. Michaud pmichaud@pobox.com -http://www.pmichaud.com/ +https://www.pmichaud.com/ +Since 2009, PmWiki has been maintained and updated +by Petko Yotov 5ko@5ko.fr, https://www.pmwiki.org/petko + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or @@ -45,4 +48,4 @@ GNU General Public License for more details. The GNU General Public License is distributed with this program (see docs/COPYING.txt) and it is also available online at -http://www.fsf.org/licensing/licenses/gpl.txt . +https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt . blob - bc37dceaf3e969016b15d37c9e153b4d389f2c66 blob + 39b3e8253f3e738aa8648d28c61df6e8dd2a74f3 --- docs/DOCUMENTATION.txt +++ docs/DOCUMENTATION.txt @@ -6,5 +6,5 @@ the documentation is available through PmWiki itself - see the "PmWiki.DocumentationIndex" page on your site. The documentation is also available online at -http://www.pmwiki.org/wiki/PmWiki/DocumentationIndex . +https://www.pmwiki.org/wiki/PmWiki/DocumentationIndex . blob - 41dd12e608a87258a0df253631739e0d89c8f72b blob + 125adaaaf2a542554acb312b8c3e6a6ac9cb3626 --- docs/INSTALL.txt +++ docs/INSTALL.txt @@ -1,7 +1,7 @@ This is the INSTALL.txt file for PmWiki. This document provides convenient steps so an administrator can have a PmWiki site up and running quickly. More extensive information about installing PmWiki -is available at http://www.pmwiki.org/wiki/PmWiki/Installation . +is available at https://www.pmwiki.org/wiki/PmWiki/Installation . Once your site is up and running you will be able to read the bundled documentation pages. @@ -12,7 +12,7 @@ customized installation: 1a) Put the software in a location accessible by your webserver. 1b) PmWiki can also be run if no webserver is installed. See - http://pmwiki.org/wiki/Cookbook/Standalone + https://pmwiki.org/wiki/Cookbook/Standalone 2) Point your browser to pmwiki.php. blob - d8a70b6d428f0c7b66fffef8a49b2c7fbace5e44 blob + 82ea6ef095ebae8bdd5f7677642e9c11abdb97ef --- docs/UPGRADE.txt +++ docs/UPGRADE.txt @@ -2,12 +2,12 @@ This UPGRADE.txt file is a command-line syntax reminde experienced PmWiki administrators. For full documentation on upgrading Pmwiki, see the bundled PmWiki.Upgrades page or visit - http://www.pmwiki.org/wiki/PmWiki/Upgrades + https://www.pmwiki.org/wiki/PmWiki/Upgrades See also these related pages: - http://www.pmwiki.org/wiki/PmWiki/BackupAndRestore - http://www.pmwiki.org/wiki/PmWiki/Subversion + https://www.pmwiki.org/wiki/PmWiki/BackupAndRestore + https://www.pmwiki.org/wiki/PmWiki/Subversion The examples assume your PmWiki site is in a ./pmwiki/ directory (a directory named "pmwiki" immediately below the @@ -25,15 +25,15 @@ Or, to keep backups organized by date: The latest release is available here: - http://www.pmwiki.org/pub/pmwiki/pmwiki-latest.tgz - http://www.pmwiki.org/pub/pmwiki/pmwiki-latest.zip + https://www.pmwiki.org/pub/pmwiki/pmwiki-latest.tgz + https://www.pmwiki.org/pub/pmwiki/pmwiki-latest.zip Example download commands: - wget http://www.pmwiki.org/pub/pmwiki/pmwiki-latest.tgz - lftpget http://www.pmwiki.org/pub/pmwiki/pmwiki-latest.tgz - links http://www.pmwiki.org/pub/pmwiki/pmwiki-latest.tgz - lynx http://www.pmwiki.org/pub/pmwiki/pmwiki-latest.tgz + wget https://www.pmwiki.org/pub/pmwiki/pmwiki-latest.tgz + lftpget https://www.pmwiki.org/pub/pmwiki/pmwiki-latest.tgz + links https://www.pmwiki.org/pub/pmwiki/pmwiki-latest.tgz + lynx https://www.pmwiki.org/pub/pmwiki/pmwiki-latest.tgz Expanding the archive: blob - 5fedcdf2da597d8c339de56196864c6cd8e3fa45 blob + fdc7482af8a707957d907b8d41b8226da58ab29c --- docs/sample-config.php +++ docs/sample-config.php @@ -20,29 +20,29 @@ $WikiTitle = 'PmWiki'; ## details about this setting and other ways to create nicer-looking urls. # $EnablePathInfo = 1; -## $PageLogoUrl is the URL for a logo image -- you can change this -## to your own logo if you wish. +## $PageLogoUrl is the URL for a logo image -- you can change this +## to your own logo if you wish. # $PageLogoUrl = "$PubDirUrl/skins/pmwiki/pmwiki-32.gif"; -## If you want to have a custom skin, then set $Skin to the name -## of the directory (in pub/skins/) that contains your skin files. -## See PmWiki.Skins and Cookbook.Skins. -# $Skin = 'pmwiki-responsive'; +## If you want to have a custom skin, then set $Skin to the name +## of the directory (in pub/skins/) that contains your skin files. +## See PmWiki.Skins and Cookbook.Skins. +$Skin = 'pmwiki-responsive'; -## You'll probably want to set an administrative password that you -## can use to get into password-protected pages. Also, by default -## the "attr" passwords for the PmWiki and Main groups are locked, so -## an admin password is a good way to unlock those. See PmWiki.Passwords -## and PmWiki.PasswordsAdmin. +## You'll probably want to set an administrative password that you +## can use to get into password-protected pages. Also, by default +## the "attr" passwords for the PmWiki and Main groups are locked, so +## an admin password is a good way to unlock those. See PmWiki.Passwords +## and PmWiki.PasswordsAdmin. # $DefaultPasswords['admin'] = pmcrypt('secret'); -## Unicode (UTF-8) allows the display of all languages and all alphabets. -## Highly recommended for new wikis. +## Unicode (UTF-8) allows the display of all languages and all alphabets. +## Highly recommended for new wikis. include_once("scripts/xlpage-utf-8.php"); -## If you're running a publicly available site and allow anyone to -## edit without requiring a password, you probably want to put some -## blocklists in place to avoid wikispam. See PmWiki.Blocklist. +## If you're running a publicly available site and allow anyone to +## edit without requiring a password, you probably want to put some +## blocklists in place to avoid wikispam. See PmWiki.Blocklist. # $EnableBlocklist = 1; # enable manual blocklists # $EnableBlocklist = 10; # enable automatic blocklists @@ -50,6 +50,17 @@ include_once("scripts/xlpage-utf-8.php"); ## to enable these buttons, set $EnableGUIButtons to 1. # $EnableGUIButtons = 1; +## This enables a message if editors have modified a page but try to +## move away from the edit form before saving the text. +$EnableNotSavedWarning = 1; # 1: warn editors; 0: disable warning + +## You can enable syntax highlighting for the documentation and/or +## for the edit form. +# $EnablePmSyntax = 1; # or 2, see documentation + +## For a basic table of contents, see page PmWiki/TableOfContents +# $PmTOC['Enable'] = 1; + ## To enable markup syntax from the Creole common wiki markup language ## (http://www.wikicreole.org/), include it here: # include_once("scripts/creole.php"); @@ -93,13 +104,13 @@ $UploadPermAdd = 0; # Recommended for most new install ## revision history. The default is 3650 (approximately 10 years). # $DiffKeepDays=30; # keep page history at least 30 days -## By default, viewers are prevented from seeing the existence -## of read-protected pages in search results and page listings, -## but this can be slow as PmWiki has to check the permissions -## of each page. Setting $EnablePageListProtect to zero will -## speed things up considerably, but it will also mean that -## viewers may learn of the existence of read-protected pages. -## (It does not enable them to access the contents of the pages.) +## By default, viewers are prevented from seeing the existence +## of read-protected pages in search results and page listings, +## but this can be slow as PmWiki has to check the permissions +## of each page. Setting $EnablePageListProtect to zero will +## speed things up considerably, but it will also mean that +## viewers may learn of the existence of read-protected pages. +## (It does not enable them to access the contents of the pages.) # $EnablePageListProtect = 0; ## The refcount.php script enables ?action=refcount, which helps to blob - d7c96c3167ac06ce19aa1359c424cc50c99f0a59 blob + 184233ef8de1411b062952ec1d2eaed5c365b5b4 --- pmwiki.php +++ pmwiki.php @@ -1,7 +1,7 @@ $[This post has been blocked by the administrator]"; $EditFields = array('text'); -$EditFunctions = array('EditTemplate', 'RestorePage', 'ReplaceOnSave', +$EditFunctions = array('AutoCheckToken', 'EditTemplate', 'RestorePage', 'ReplaceOnSave', 'SaveAttributes', 'PostPage', 'PostRecentChanges', 'AutoCreateTargets', 'PreviewPage'); $EnablePost = 1; @@ -77,13 +79,15 @@ $SpaceWikiWords = 0; $RCDelimPattern = ' '; $RecentChangesFmt = array( '$SiteGroup.AllRecentChanges' => - '* [[{$Group}.{$Name}]] . . . $CurrentTime $[by] $AuthorLink: [=$ChangeSummary=]', + '* [[{$Group}.{$Name}]] . . . $CurrentLocalTime $[by] $AuthorLink: [=$ChangeSummary=]', '$Group.RecentChanges' => - '* [[{$Group}/{$Name}]] . . . $CurrentTime $[by] $AuthorLink: [=$ChangeSummary=]'); + '* [[{$Group}/{$Name}]] . . . $CurrentLocalTime $[by] $AuthorLink: [=$ChangeSummary=]'); $UrlScheme = (@$_SERVER['HTTPS']=='on' || @$_SERVER['SERVER_PORT']==443) ? 'https' : 'http'; -$ScriptUrl = $UrlScheme.'://'.$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']; +$ScriptUrl = $UrlScheme.'://'.strval(@$_SERVER['HTTP_HOST']).strval(@$_SERVER['SCRIPT_NAME']); $PubDirUrl = preg_replace('#/[^/]*$#', '/pub', $ScriptUrl, 1); +SDV($FarmPubDirPrefix, 'PmFarmPubDirUrl'); +if (@$IsFarmArchive) SDV($FarmPubDirUrl, "$ScriptUrl/$FarmPubDirPrefix"); $HTMLVSpace = ""; $HTMLPNewline = ''; $MarkupFrame = array(); @@ -136,7 +140,7 @@ $FmtPV = array( '$Title' => 'FmtPageTitle(@$page["title"], $name, 0)', '$LastModifiedBy' => '@$page["author"]', '$LastModifiedHost' => '@$page["host"]', - '$LastModified' => 'strftime($GLOBALS["TimeFmt"], $page["time"])', + '$LastModified' => 'PSFT($GLOBALS["TimeFmt"], $page["time"])', '$LastModifiedSummary' => '@$page["csum"]', '$LastModifiedTime' => '$page["time"]', '$Description' => '@$page["description"]', @@ -145,6 +149,7 @@ $FmtPV = array( '$VersionNum' => '$GLOBALS["VersionNum"]', '$Version' => '$GLOBALS["Version"]', '$WikiTitle' => '$GLOBALS["WikiTitle"]', + '$PageLogoUrl' => 'strval(@$GLOBALS["PageLogoUrl"])', '$Author' => 'NoCache($GLOBALS["Author"])', '$AuthId' => 'NoCache($GLOBALS["AuthId"])', '$DefaultGroup' => '$GLOBALS["DefaultGroup"]', @@ -154,13 +159,17 @@ $FmtPV = array( '$PasswdRead' => 'PasswdVar($pn, "read")', '$PasswdEdit' => 'PasswdVar($pn, "edit")', '$PasswdAttr' => 'PasswdVar($pn, "attr")', + '$EnabledIMap' => 'implode("|", array_keys($GLOBALS["IMap"]))', # PmSyntax + '$GroupHomePage' => 'FmtGroupHome($pn,$group,$var)', + '$GroupHomePageName' => 'FmtGroupHome($pn,$group,$var)', + '$GroupHomePageTitle' => 'FmtGroupHome($pn,$group,$var)', + '$GroupHomePageTitlespaced' => 'FmtGroupHome($pn,$group,$var)', ); $SaveProperties = array('title', 'description', 'keywords'); $PageTextVarPatterns = array( 'var:' => '/^(:*[ \\t]*(\\w[-\\w]*)[ \\t]*:[ \\t]?)(.*)($)/m', '(:var:...:)' => '/(\\(: *(\\w[-\\w]*) *:(?!\\))\\s?)(.*?)(:\\))/s' ); - $WikiTitle = 'PmWiki'; $Charset = 'ISO-8859-1'; @@ -168,6 +177,10 @@ $HTTPHeaders = array( "Expires: Tue, 01 Jan 2002 00:00:00 GMT", "Cache-Control: no-store, no-cache, must-revalidate", "Content-type: text/html; charset=ISO-8859-1;"); +$HTTPHeaders['XFO'] = 'X-Frame-Options: SAMEORIGIN'; +$HTTPHeaders['CSP'] = "Content-Security-Policy: frame-ancestors 'self'; base-uri 'self'; object-src 'none';"; +$HTTPHeaders['XSSP'] = 'X-XSS-Protection: 1; mode=block'; + $CacheActions = array('browse','diff','print'); $EnableHTMLCache = 0; $NoHTMLCache = 0; @@ -232,20 +245,35 @@ $Conditions['name'] = "(boolean)MatchPageNames(\$pagename, FixGlob(\$condparm, '$1*.$2'))"; $Conditions['match'] = 'preg_match("!$condparm!",$pagename)'; $Conditions['authid'] = 'NoCache(@$GLOBALS["AuthId"] > "")'; -$Conditions['exists'] = "(boolean)ListPages(FixGlob( - str_replace(array('[[',']]'), array('', ''), \$condparm) , '$1*.$2'))"; $Conditions['equal'] = 'CompareArgs($condparm) == 0'; function CompareArgs($arg) { $arg = ParseArgs($arg); return strcmp(@$arg[''][0], @$arg[''][1]); } $Conditions['auth'] = 'NoCache(CondAuth($pagename, $condparm))'; function CondAuth($pagename, $condparm) { - global $HandleAuth; + global $AuthList, $HandleAuth; @list($level, $pn) = explode(' ', $condparm, 2); + if (@$level && $level[0] == '@') { # user belongs to @group1,@group2 + $keys = MatchNames(array_keys((array)@$AuthList), $level, true); + foreach($keys as $k) { + if (@$AuthList[$k] == 1 && $AuthList["-$k"] != 1) return true; + } + return false; + } $pn = ($pn > '') ? MakePageName($pagename, $pn) : $pagename; if (@$HandleAuth[$level]>'') $level = $HandleAuth[$level]; return (boolean)RetrieveAuthPage($pn, $level, false, READPAGE_CURRENT); } +$Conditions['exists'] = 'CondExists($condparm)'; +## This is an optimized version of the earlier conditional +## especially for pagelists +function CondExists($condparm, $caseinsensitive = true) { + static $ls = false; + if (!$ls) $ls = ListPages(); + $condparm = str_replace(array('[[',']]'), array('', ''), $condparm); + $glob = FixGlob($condparm, '$1*.$2'); + return (boolean)MatchPageNames($ls, $glob, $caseinsensitive); +} ## CondExpr handles complex conditions (expressions) ## Portions Copyright 2005 by D. Faure (dfaure@cpan.org) @@ -262,7 +290,12 @@ function CondExpr($pagename, $condname, $condparm) { if (preg_match("/^($CondExprOps)$/i", $t)) continue; if ($t) $terms[$i] = CondText($pagename, "if $t", 'TRUE') ? '1' : '0'; } - return @eval('return(' . implode(' ', $terms) . ');'); + + ## PITS:01480, (:if [ {$EmptyPV} and ... ]:) + $code = preg_replace('/(^\\s*|(and|x?or|&&|\\|\\||!)\\s+)(?=and|x?or|&&|\\|\\|)/', + '$1 0 ', trim(implode(' ', $terms))); + + return @eval("return( $code );"); } $Conditions['expr'] = 'CondExpr($pagename, $condname, $condparm)'; $Conditions['('] = 'CondExpr($pagename, $condname, $condparm)'; @@ -282,7 +315,7 @@ Markup('closeall', '_begin', '/^\\(:closeall:\\)$/', "MarkupMarkupClose"); function MarkupMarkupClose() { return '<:block>' . MarkupClose(); } -$ImgExtPattern="\\.(?:gif|jpg|jpeg|png|svgz?|GIF|JPG|JPEG|PNG|SVGZ?)"; +$ImgExtPattern="\\.(?:gif|jpg|jpeg|a?png|svgz?|GIF|JPG|JPEG|A?PNG|SVGZ?|webp|WEBP)"; $ImgTagFmt="\$LinkAlt"; $BlockMarkups = array( @@ -303,7 +336,7 @@ foreach(array('http:','https:','mailto:','ftp:','news: { $LinkFunctions[$m] = 'LinkIMap'; $IMap[$m]="$m$1"; } $LinkFunctions['<:page>'] = 'LinkPage'; -$q = preg_replace('/(\\?|%3f)([-\\w]+=)/i', '&$2', @$_SERVER['QUERY_STRING']); +$q = preg_replace('/(\\?|%3f)([-\\w]+=)/i', '&$2', strval(@$_SERVER['QUERY_STRING'])); if ($q != @$_SERVER['QUERY_STRING']) { unset($_GET); parse_str($q, $_GET); @@ -314,14 +347,14 @@ if (isset($_GET['action'])) $action = $_GET['action']; elseif (isset($_POST['action'])) $action = $_POST['action']; else $action = 'browse'; -$pagename = @$_REQUEST['n']; -if (!$pagename) $pagename = @$_REQUEST['pagename']; +$pagename = strval(@$_REQUEST['n']); +if (!$pagename) $pagename = strval(@$_REQUEST['pagename']); if (!$pagename && - preg_match('!^'.preg_quote($_SERVER['SCRIPT_NAME'],'!').'/?([^?]*)!', - $_SERVER['REQUEST_URI'],$match)) + preg_match('!^'.preg_quote(strval(@$_SERVER['SCRIPT_NAME']),'!').'/?([^?]*)!', + strval(@$_SERVER['REQUEST_URI']),$match)) $pagename = urldecode($match[1]); if (preg_match('/[\\x80-\\xbf]/',$pagename)) - $pagename=utf8_decode($pagename); + $pagename=pm_recode($pagename, 'UTF-8', 'WINDOWS-1252'); $pagename = preg_replace('![^[:alnum:]\\x80-\\xff]+$!','',$pagename); $pagename_unfiltered = $pagename; $pagename = preg_replace('![${}\'"\\\\]+!', '', $pagename); @@ -343,6 +376,44 @@ $DenyHtaccessContent = << 'image/gif', 'png' => 'image/png', 'svg' => 'image/svg+xml', + 'README' => 'text/plain', 'txt' => 'text/plain', + 'css' => 'text/css', 'js' => 'application/javascript', +)); +function pm_servefile($basedir, $path, $cachecontrol='no-cache') { + global $ServeFileExts; + header("X-Sent-Via: pm_servefile"); + $ext = preg_replace('!^.*[./]!', '', $path); + if (!isset($ServeFileExts[$ext]) || preg_match('/[?#${}]|\\.\\./', $path)) { + http_response_code(403); + die('Forbidden'); + } + $filepath = "$basedir/$path"; + if(!file_exists($filepath)) { + http_response_code(404); + die('File not found'); + } + + header("Cache-Control: $cachecontrol"); + header('Expires: '); + $filelastmod = gmdate('D, d M Y H:i:s \G\M\T', filemtime($filepath)); + if (@$_SERVER['HTTP_IF_MODIFIED_SINCE'] == $filelastmod) + { http_response_code(304); exit(); } + header("Last-Modified: $filelastmod"); + header("Content-Type: {$ServeFileExts[$ext]}"); + $length = filesize($filepath); + header("Content-Length: $length"); + readfile($filepath); + exit; +} + +if (strpos($pagename, "$FarmPubDirPrefix/")===0) { + $path = substr($pagename, strlen("$FarmPubDirPrefix/")); + pm_servefile("$FarmD/pub", $path); + exit; +} + if (file_exists("$FarmD/local/farmconfig.php")) include_once("$FarmD/local/farmconfig.php"); if (IsEnabled($EnableLocalConfig,1)) { @@ -352,8 +423,9 @@ if (IsEnabled($EnableLocalConfig,1)) { include_once('config.php'); } -SDV($CurrentTime, strftime($TimeFmt, $Now)); -SDV($CurrentTimeISO, strftime($TimeISOFmt, $Now)); +SDV($CurrentTime, PSFT($TimeFmt, $Now)); +SDV($CurrentLocalTime, PSFT("@$TimeISOZFmt", $Now, null, 'GMT')); +SDV($CurrentTimeISO, PSFT($TimeISOFmt, $Now)); if (IsEnabled($EnableStdConfig,1)) include_once("$FarmD/scripts/stdconfig.php"); @@ -367,19 +439,61 @@ if (isset($PostConfig) && is_array($PostConfig)) { } } -function pmsetcookie($name, $val="", $exp=0, $path="", $dom="", $secure=null, $httponly=null) { - global $EnableCookieSecure, $EnableCookieHTTPOnly, $SetCookieFunction; - if(IsEnabled($SetCookieFunction)) - return $SetCookieFunction($name, $val, $exp, $path, $dom, $secure, $httponly); +function pmsetcookie($name, $val="", $exp=0, $path="", $dom="", $secure=null, $httponly=null, $samesite=null) { + global $EnableCookieSecure, $EnableCookieHTTPOnly, $SetCookieFunction, $CookieSameSite; + if (IsEnabled($SetCookieFunction)) + return $SetCookieFunction($name, $val, $exp, $path, $dom, $secure, $httponly, $samesite); if (is_null($secure)) $secure = IsEnabled($EnableCookieSecure, false); if (is_null($httponly)) $httponly = IsEnabled($EnableCookieHTTPOnly, false); - setcookie($name, $val, $exp, $path, $dom, $secure, $httponly); + if (is_null($samesite)) $samesite = IsEnabled($CookieSameSite, 'Lax'); + + if (PHP_VERSION_ID>=70300) { + return setcookie($name, $val, array( + 'expires' => $exp, + 'path' => $path, + 'domain' => $dom, + 'secure' => $secure, + 'httponly' => $httponly, + 'samesite' => $samesite + )); + } + if (!$path) $path = '/'; + setcookie($name, $val, $exp, "$path; SameSite=$samesite", $dom, $secure, $httponly); } -if (IsEnabled($EnableCookieSecure, false)) - @ini_set('session.cookie_secure', $EnableCookieSecure); -if (IsEnabled($EnableCookieHTTPOnly, false)) - @ini_set('session.cookie_httponly', $EnableCookieHTTPOnly); +function pm_session_start($a = array()) { + global $EnableCookieSecure, $EnableCookieHTTPOnly, $CookieSameSite; + if (function_exists('session_status')) { + if (session_status() === PHP_SESSION_ACTIVE) return true; + } + + if (!headers_sent()){ + $params = session_get_cookie_params(); + if (isset($EnableCookieSecure) && !isset($a['secure'])) + $a['secure'] = $EnableCookieSecure; + if (isset($EnableCookieHTTPOnly) && !isset($a['httponly'])) + $a['httponly'] = $EnableCookieHTTPOnly; + if (!isset($a['samesite'])) $a['samesite'] = IsEnabled($CookieSameSite, 'Lax'); + SDVA($a, $params); + + if (PHP_VERSION_ID < 70300) { + if (!$a['path']) $a['path'] = '/'; + $a['path'] .= "; SameSite={$a['samesite']}"; + session_set_cookie_params( + $a['lifetime'], + $a['path'], + $a['domain'], + $a['secure'], + $a['httponly'] + ); + } + else { + session_set_cookie_params($a); + } + } + return @session_start(); +} + foreach((array)$InterMapFiles as $f) { $f = FmtPageName($f, $pagename); if (($v = @file($f))) @@ -429,12 +543,14 @@ function HandleDispatch($pagename, $action, $msg=NULL) ## helper functions function stripmagic($x) { + if(is_null($x)) return ''; $fn = 'get_magic_quotes_gpc'; if (!function_exists($fn)) return $x; if (is_array($x)) { foreach($x as $k=>$v) $x[$k] = stripmagic($v); return $x; } + $x = strval($x); return @$fn() ? stripslashes($x) : $x; } function pre_r(&$x) @@ -448,18 +564,26 @@ function PZZ($x,$y='') { return ''; } function PRR($x=NULL) { if ($x || is_null($x)) $GLOBALS['RedoMarkupLine']++; return $x; } function PUE($x) - { return preg_replace_callback('/[\\x80-\\xff \'"<>]/', "cb_pue", $x); } + { return $x? preg_replace_callback('/[\\x80-\\xff \'"<>]/', "cb_pue", $x) : ''; } function cb_pue($m) { return '%'.dechex(ord($m[0])); } -function PQA($x) { +function PQA($x, $keep=true, $styletoclass=false) { + global $EnableUnsafeInlineStyle; + if (!@$x) return ''; # PHP 8.1 $out = ''; + $s = array(); + $x = MarkupRestore($x); if (preg_match_all('/([a-zA-Z][-\\w]*)\\s*=\\s*("[^"]*"|\'[^\']*\'|\\S*)/', $x, $attr, PREG_SET_ORDER)) { foreach($attr as $a) { if (preg_match('/^on/i', $a[1])) continue; $val = preg_replace('/^([\'"]?)(.*)\\1$/', '$2', $a[2]); - $val = str_replace("'", ''', $val); - $out .= "{$a[1]}='$val' "; + $s[$a[1]] = $val; } + foreach($s as $key=>$val) { + $val = PHSC($val, ENT_QUOTES, null, false); + if ($keep) $val = Keep($val); + $out .= "$key='$val' "; + } } return $out; } @@ -474,7 +598,7 @@ function NoCache($x = '') { $GLOBALS['NoHTMLCache'] |= function ParseArgs($x, $optpat = '(?>(\\w+)[:=])') { $z = array(); preg_match_all("/($optpat|[-+])?(\"[^\"]*\"|'[^']*'|\\S+)/", - $x, $terms, PREG_SET_ORDER); + strval($x), $terms, PREG_SET_ORDER); foreach($terms as $t) { $v = preg_replace('/^([\'"])?(.*)\\1$/', '$2', $t[3]); if ($t[2]) { $z['#'][] = $t[2]; $z[$t[2]] = $v; } @@ -488,7 +612,164 @@ function PHSC($x, $flags=ENT_COMPAT, $enc=null, $dbl_e if (! is_array($x)) return @htmlspecialchars($x, $flags, $enc, $dbl_enc); foreach($x as $k=>$v) $x[$k] = PHSC($v, $flags, $enc, $dbl_enc); return $x; +} +function PSFT($fmt, $stamp=null, $locale=null, $tz=null) { # strftime() replacement + global $Now, $FTimeFmt, $TimeFmt, $EnableFTimeNew; + static $cached; + if (!@$cached) { + SDV($FTimeFmt, $TimeFmt); + $cached['dloc'] = setlocale(LC_TIME, 0); + $cached['dtz'] = date_default_timezone_get(); + $cached['dtzo'] = timezone_open($cached['dtz']); + $cached['formats'] = array( + '%d' => 'd', + '%e' => ' j', # day " 1"-"31" + '%u' => 'N', + '%w' => 'w', + '%m' => 'm', + '%s' => 'U', + '%n' => "\n", + '%t' => "\t", + '%V' => 'W', # ISO-8601 week number, starts Monday, first week with 4+ weekdays + + '%H' => 'H', + '%I' => 'h', # hour 01-12 + '%k' => ' G', # hour " 1"-"23" + '%l' => ' g', # hour " 1"-"12" + '%M' => 'i', + '%S' => 's', + '%R' => 'H:i', + '%T' => 'H:i:s', + '%r' => 'h:i:s A', + '%D' => 'm/d/y', + '%F' => 'Y-m-d', + + '%G' => 'o', # ISO-8601 year (like %V) + '%y' => 'y', # 21 + '%Y' => 'Y', # 2021 + + '%Z' => 'T', # tz: EST + '%z' => 'O', # tz offset: -0500 + ); + if (extension_loaded('intl')) { + $full = IntlDateFormatter::FULL; + $long = IntlDateFormatter::LONG; + $short = IntlDateFormatter::SHORT; + $none = IntlDateFormatter::NONE; + $medium = IntlDateFormatter::MEDIUM; + + $cached['iformats'] = array( + '%a' => array($full, $full, 'EEE'), # Mon + '%A' => array($full, $full, 'EEEE'), # Monday + '%h' => array($full, $full, 'MMM'), # Jan + '%b' => array($full, $full, 'MMM'), # Jan + '%B' => array($full, $full, 'MMMM'), # January + '%p' => array($full, $full, 'aa'), # AM/PM + '%P' => array($full, $full, 'aa'), # am/pm (no lowercase for intl am/pm) + '%c' => array($long, $short), # date time + '%x' => array($short, $none), # date + '%X' => array($none, $medium),# time + ); + } + else { + $cached['iformats'] = array(); + $cached['formats'] += array( + '%a' => 'D', # Mon + '%A' => 'l', # Monday + '%h' => 'M', # Jan + '%b' => 'M', # Jan + '%B' => 'F', # January + '%p' => 'a', # AM/PM + '%P' => 'A', # am/pm + '%c' => 'D M j H:i:s Y', # date time + '%x' => 'm/d/y', # date + '%X' => 'H:i:s', # time + ); + } + $cached['new'] = isset($EnableFTimeNew) + ? $EnableFTimeNew + : (PHP_VERSION_ID>=80100); + } + + if (@$fmt == '') $fmt = $FTimeFmt; + $stamp = is_numeric($stamp)? intval($stamp) : $Now; + + if(preg_match('/(? $tz, + 'timestamp' => $timestamp, + 'formats' => $cached['formats'], + 'iformats' => $cached['iformats'], + ); + if ($locale) $vars['locale'] = substr($locale, 0, 5); + + $cb = new PPRC($vars); + $fmt = preg_replace_callback('/(?format($formats[$fmt]); + if ($fmt[0]==' ' && strlen($fmt)>2) $fmt = substr($fmt, 1); + return $fmt; + } + if ($fmt=='%o') { # ordinal, PITS:01418 + if (!@$locale) $locale = 'C'; + $f = numfmt_create($locale, NumberFormatter::ORDINAL); + $o = $f->format($timestamp->format('j')); + return preg_replace('/\\d+/', '', $o); + } + if ($fmt=='%j') return sprintf('%03d',$timestamp->format('z')+1); + if ($fmt=='%C') return floor($timestamp->format('Y')/100); + if ($fmt=='%g') return sprintf('%02d', $timestamp->format('o') % 100); + if ($fmt=='%U') return cb_PSFT_UW($timestamp, $tz, 'Sunday'); + if ($fmt=='%W') return cb_PSFT_UW($timestamp, $tz, 'Monday'); + + if (isset($iformats[$fmt])) { + $ifmt = $iformats[$fmt]; + return datefmt_create(@$locale, $ifmt[0], $ifmt[1], $tz, null, @$ifmt[2])->format($timestamp); + } + return $fmt; +} +function cb_PSFT_UW($timestamp, $tz, $day) { # helper for %U %W + $first = strtotime(sprintf("%d-01 $day", $timestamp->format('Y'))); + $stamp1 = date_create("@$first"); + date_timezone_set($stamp1, timezone_open($tz)); + $days = $timestamp->format('z') - $stamp1->format('z'); + return sprintf('%02d', floor($days/7)+1); +} + +# Only called by old addon|skin|recipe needing update, see pmwiki.org/Troubleshooting function PCCF($code, $template = 'default', $args = '$m') { global $CallbackFnTemplates, $CallbackFunctions, $PCCFOverrideFunction; if ($PCCFOverrideFunction && is_callable($PCCFOverrideFunction)) @@ -505,31 +786,57 @@ function PCCF($code, $template = 'default', $args = '$ return $CallbackFunctions[$code]; } function PPRE($pat, $rep, $x) { + if (! function_exists('create_function')) return $x; $lambda = PCCF("return $rep;"); return preg_replace_callback($pat, $lambda, $x); } function PPRA($array, $x) { + if ($x==='' || is_null($x)) return ''; foreach((array)$array as $pat => $rep) { + # skip broken patterns rather than crash the PHP installation + $oldpat = preg_match('!^/.+/[^/]*e[^/]*$!', $pat); + if ($oldpat && PHP_VERSION_ID >= 50500) continue; + $fmt = $x; # for $FmtP - if (is_callable($rep) && $rep != '_') $x = preg_replace_callback($pat,$rep,$x); - else $x = preg_replace($pat,$rep,$x);# simple text OR called by old addon|skin|recipe needing update, see pmwiki.org/Troubleshooting + if (is_callable($rep) && $rep != '_') + $x = preg_replace_callback($pat,$rep,$x); + else + $x = preg_replace($pat,$rep,$x);# simple text OR called by old addon|skin|recipe needing update, see pmwiki.org/Troubleshooting } return $x; } +function PRCB($pat, $repl, $subj, $vars=null, $limit=-1, &$count=null, $flags=0) { + if (isset($vars)) { + $cb = new PPRC($vars, $repl); + $repl = array($cb, 'callback'); + } + return preg_replace_callback($pat, $repl, $subj, $limit, $count, $flags); +} ## callback functions class PPRC { # PmWiki preg replace callbacks + pass local vars var $vars; - function __construct($vars = false) { - if ($vars && !is_null($vars)) $this->vars = $vars; + var $cb; + function __construct($vars = null, $cb = null) { + if (!is_null($vars)) $this->vars = $vars; + if (!is_null($cb)) $this->cb = $cb; + } function pagevar($m) { # called from FmtPageName $pagename = $this->vars; return PageVar($pagename, $m[1]); } + function ftime($m) { # called from PSFT + $vars = $this->vars; + return cb_PSFT($m[0], $vars); + } + function callback($m) { + $cb = $this->cb; + return $cb($m, $this->vars); + } } # restores kept/protected strings -function cb_expandkpv($m) { return $GLOBALS['KPV'][$m[1]]; } +function cb_expandkpv($m) { return @$GLOBALS['KPV'][$m[1]]; } # make a string upper or lower case in various patterns function cb_toupper($m) { return strtoupper($m[1]); } @@ -544,6 +851,49 @@ function pmcrypt($str, $salt=null) { return crypt($str); } +# generate or check a random one-time token to prevent CSRF +function pmtoken($token = null) { + global $SessionMaxTokens, $PmTokenFn; + if (IsEnabled($PmTokenFn) && function_exists($PmTokenFn)) + return $PmTokenFn($token); + pm_session_start(); + if (!isset($_SESSION['pmtokens'])) $_SESSION['pmtokens'] = array(); + if (is_null($token)) { # create a one-time token + $len = mt_rand(20,30); + $token = ""; + while(strlen($token)<$len) { + $token .= chr(mt_rand(32,126)); + } + if (count($_SESSION['pmtokens'])) + $id = max(array_keys($_SESSION['pmtokens']))+1; + else $id = 0; + $_SESSION['pmtokens'][$id] = $token; + if (IsEnabled($SessionMaxTokens, 0)) { + $max = $SessionMaxTokens; + $_SESSION['pmtokens'] = array_slice($_SESSION['pmtokens'], -$max, $max, true); + } + return "$id:" . md5($token); + } + # else: check a token, if correct, delete it + @list($id, $hash) = explode(':', $token); + $id = intval($id); + if (isset($_SESSION['pmtokens'][$id]) && $hash == md5($_SESSION['pmtokens'][$id])) { + unset($_SESSION['pmtokens'][$id]); + return true; + } + return false; +} + +function PmNonce() { + if (function_exists('random_bytes')) { + $nonce = bin2hex(random_bytes(5)); + } + else $nonce = mt_rand(); + return $nonce; +} + + + function StopWatch($x) { global $StopWatch, $EnableStopWatch; if (!$EnableStopWatch) return; @@ -617,16 +967,26 @@ function DRange($when) { return array($t0, $t1); } +## FmtDateTimeZ converts a GMT datetime like @2022-01-08T10:07:08Z +## into a