Compare commits

..

2 Commits
2.3.7 ... csp

Author SHA1 Message Date
Aleks-Daniel Jakimenko-Aleksejev
eda34108e1 All CSP parameters should start with -
This is best described by the docs:
“Each argument name is preceded by a dash. Neither case nor order matters in
the argument list. -type, -Type, and -TYPE are all acceptable. In fact, only
the first argument needs to begin with a dash. If a dash is present in the
first argument, CGI.pm assumes dashes for the subsequent ones.”

We construct our own hash %headers and then pass that to $q->header. Since
hash values are not ordered in any way (in fact the order is pretty much
random), sometimes CSP things will be at the front of the list. Since there
was no leading dashes, these were interpreted as something else.
2015-11-02 21:27:41 +02:00
Aleks-Daniel Jakimenko-Aleksejev
39a59e257f Content Security Policy
For now, it is turned off by default, but we should try to turn it on as soon
as we can (this will require us to make sure that nothing in the codebase is
using inline javascript).

By default, everything from 'self' is allowed. This is good enough and I don't
think that it should be stricter.

style-src is set to * (anywhere!) because of the default stylesheet (which is
fetched from oddmuse.org) and also because of the “css=” query option.

img-src is set to * because linking to external images is a very common thing.
2015-11-01 04:35:37 +02:00
75 changed files with 568 additions and 3701 deletions

View File

@@ -19,13 +19,9 @@ build:
clean:
rm -rf build
prove t/setup.pl
release:
perl stuff/release ~/oddmuse.org
build/wiki.pl: wiki.pl
perl -lne "s/(\\\$$q->a\(\{-href=>'http:\/\/www.oddmuse.org\/'\}, 'Oddmuse'\))/\\\$$q->a({-href=>'http:\/\/git.savannah.gnu.org\/cgit\/oddmuse.git\/tag\/?id=$(VERSION_NO)'}, 'wiki.pl') . ' ($(VERSION_NO)), see ' . \$$1/; print" < $< > $@
perl -lne "s/(\\\$$q->a\({-href=>'http:\/\/www.oddmuse.org\/'}, 'Oddmuse'\))/\\\$$q->a({-href=>'http:\/\/git.savannah.gnu.org\/cgit\/oddmuse.git\/tag\/?id=$(VERSION_NO)'}, 'wiki.pl') . ' ($(VERSION_NO)), see ' . \$$1/; print" < $< > $@
build/%-utf8.pl: modules/translations/%-utf8.pl
perl -lne "s/(AddModuleDescription\('[^']+', '[^']+')\)/\$$1, 'translations\/', '$(VERSION_NO)')/; print" < $< > $@

View File

@@ -1,14 +0,0 @@
#!/bin/bash
if test -z "$2" -o ! -z "$3"; then
echo "Usage: delete.sh USERNAME WIKI"
exit 1
fi
username=$1
wiki=$2
for p in $(curl "https://campaignwiki.org/wiki/$wiki?action=index;raw=1"); do
echo "Deleting: $p"
curl -F frodo=1 -F "title=$p" -F text=DeletedPage -F summary=Deleted -F username="$username" "https://campaignwiki.org/wiki/$wiki"
sleep 5
done

View File

@@ -1,131 +0,0 @@
#! /usr/bin/perl -w
# Copyright (C) 2005-2016 Alex Schroeder <alex@gnu.org>
#
# 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 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
use Modern::Perl;
use LWP::UserAgent;
use utf8;
binmode(STDOUT, ":utf8");
my $ua = LWP::UserAgent->new;
sub url_encode {
my $str = shift;
return '' unless $str;
utf8::encode($str); # turn to byte string
my @letters = split(//, $str);
my %safe = map {$_ => 1} ('a' .. 'z', 'A' .. 'Z', '0' .. '9', '-', '_', '.', '!', '~', '*', "'", '(', ')', '#');
foreach my $letter (@letters) {
$letter = sprintf("%%%02x", ord($letter)) unless $safe{$letter};
}
return join('', @letters);
}
sub get_raw {
my $uri = shift;
my $response = $ua->get($uri);
return $response->content if $response->is_success;
}
sub get_wiki_page {
my ($wiki, $id, $password) = @_;
my $parameters = [
pwd => $password,
action => 'browse',
id => $id,
raw => 1,
];
my $response = $ua->post($wiki, $parameters);
return $response->decoded_content if $response->is_success;
die "Getting $id returned " . $response->status_line;
}
sub get_wiki_index {
my $wiki = shift;
my $parameters = [
search => "flickr.com",
context => 0,
raw => 1,
];
my $response = $ua->post($wiki, $parameters);
return $response->decoded_content if $response->is_success;
die "Getting the index returned " . $response->status_line;
}
sub post_wiki_page {
my ($wiki, $id, $username, $password, $text) = @_;
my $parameters = [
username => $username,
pwd => $password,
recent_edit => 'on',
text => $text,
title => $id,
];
my $response = $ua->post($wiki, $parameters);
die "Posting to $id returned " . $response->status_line unless $response->code == 302;
}
my %seen = ();
sub write_flickr {
my ($id, $flickr, $dir, $file) = @_;
say "Found $flickr";
warn "$file was seen before: " . $seen{$file} if $seen{$file};
die "$file contains unknown characters" if $file =~ /[^a-z0-9_.]/;
$seen{$file} = "$id used $flickr";
my $bytes = get_raw($flickr) or die("No data for $id");
open(my $fh, '>', "$dir/$file") or die "Cannot write $dir/$file";
binmode($fh);
print $fh $bytes;
close($fh);
}
sub convert_page {
my ($wiki, $pics, $dir, $username, $password, $id) = @_;
say $id;
my $text = get_wiki_page($wiki, $id, $password);
my $is_changed = 0;
while ($text =~ m!(https://[a-z0-9.]+.flickr.com/(?:[a-z0-9.]+/)?([a-z0-9_]+\.(?:jpg|png)))!) {
my $flickr = $1;
my $file = $2;
write_flickr($id, $flickr, $dir, $file);
$is_changed = 1;
my $re = quotemeta($flickr);
$text =~ s!$flickr!$pics/$file!g;
}
if ($is_changed) {
post_wiki_page($wiki, $id, $username, $password, $text);
} else {
# die "$id has no flickr matches?\n$text";
}
sleep(5);
}
sub convert_site {
my ($wiki, $pics, $dir, $username, $password) = @_;
my @ids = split(/\n/, get_wiki_index($wiki));
for my $id (@ids) {
convert_page($wiki, $pics, $dir, $username, $password, $id);
}
}
our $AdminPass;
do "/home/alex/password.pl";
convert_site('https://alexschroeder.ch/wiki',
'https://alexschroeder.ch/pics',
'/home/alex/alexschroeder.ch/pics',
'Alex Schroeder',
$AdminPass);

View File

@@ -38,10 +38,9 @@
;;; Code:
(eval-when-compile
'(progn
(require 'cl)
(require 'sgml-mode)
(require 'skeleton)))
(require 'cl)
(require 'sgml-mode)
(require 'skeleton))
(require 'goto-addr); URL regexp
(require 'info); link face
@@ -258,6 +257,24 @@ Example:
(defvar oddmuse-revision nil
"A variable to bind dynamically when calling `oddmuse-format-command'.")
(defun oddmuse-revision-put (wiki page rev)
"Store REV for WIKI and PAGE in `oddmuse-revisions'."
(let ((w (assoc wiki oddmuse-revisions)))
(unless w
(setq w (list wiki)
oddmuse-revisions (cons w oddmuse-revisions)))
(let ((p (assoc page w)))
(unless p
(setq p (list page))
(setcdr w (cons p (cdr w))))
(setcdr p rev))))
(defun oddmuse-revision-get (wiki page)
"Get revision for WIKI and PAGE in `oddmuse-revisions'."
(let ((w (assoc wiki oddmuse-revisions)))
(when w
(cdr (assoc page w)))))
;;; Helpers
(defsubst oddmuse-page-name (file)
@@ -283,7 +300,7 @@ Example:
(defun oddmuse-url (wiki pagename)
"Get the URL of oddmuse wiki."
(condition-case v
(concat (or (cadr (assoc wiki oddmuse-wikis)) (error "Wiki not found in `oddmuse-wikis'")) "/"
(concat (or (cadr (assoc wiki oddmuse-wikis)) (error)) "/"
(url-hexify-string pagename))
(error nil)))
@@ -517,7 +534,7 @@ as well."
((string-match "<title>Error</title>" status)
(if (string-match "<h1>\\(.*\\)</h1>" status)
(error "Error %s: %s" mesg (match-string 1 status))
(error "Error %s: Cause unknown" status)))
(error "Error %s: Cause unknown")))
(t
(message "%s...done" mesg))))))
@@ -719,7 +736,7 @@ Font-locking is controlled by `oddmuse-markup-functions'.
(set (make-local-variable 'sgml-tag-alist)
`(("b") ("code") ("em") ("i") ("strong") ("nowiki")
("pre" \n) ("tt") ("u")))
(set (make-local-variable 'skeleton-transformation-function) 'identity)
(set (make-local-variable 'skeleton-transformation) 'identity)
(make-local-variable 'oddmuse-wiki)
(make-local-variable 'oddmuse-page-name)
@@ -837,8 +854,11 @@ people have been editing the wiki in the mean time."
(set-buffer (get-buffer-create name))
(erase-buffer); in case of current-prefix-arg
(oddmuse-run "Loading" oddmuse-get-command wiki pagename)
(oddmuse-revision-put wiki pagename (oddmuse-get-latest-revision wiki pagename))
;; fix mode-line for VC in the new buffer because this is not a vc-checkout
(setq buffer-file-name (concat oddmuse-directory "/" wiki "/" pagename))
(vc-working-revision buffer-file-name 'oddmuse)
(vc-mode-line buffer-file-name 'oddmuse)
(pop-to-buffer (current-buffer))
;; check for a diff (this ends with display-buffer) and bury the
;; buffer if there are no hunks
(when (file-exists-p buffer-file-name)
@@ -849,9 +869,7 @@ people have been editing the wiki in the mean time."
;; this also changes the buffer name
(basic-save-buffer)
;; this makes sure that the buffer name is set correctly
(oddmuse-mode)
;; fix mode-line for VC in the new buffer because this is not a vc-checkout
(vc-mode-line buffer-file-name 'oddmuse))))
(oddmuse-mode))))
(defalias 'oddmuse-go 'oddmuse-edit)
@@ -891,11 +909,8 @@ Use a prefix argument to override this."
(and buffer-file-name (basic-save-buffer))
(oddmuse-run "Posting" oddmuse-post-command nil nil
(get-buffer-create " *oddmuse-response*") t 302)
;; force reload
(vc-file-setprop buffer-file-name 'vc-working-revision
(oddmuse-get-latest-revision oddmuse-wiki oddmuse-page-name))
;; fix mode-line for VC in the new buffer because this is not a vc-checkout
(vc-mode-line buffer-file-name 'oddmuse))
(oddmuse-revision-put oddmuse-wiki oddmuse-page-name
(oddmuse-get-latest-revision oddmuse-wiki oddmuse-page-name)))
;;;###autoload
(defun oddmuse-preview (&optional arg)

View File

@@ -47,8 +47,7 @@ For a list of possible values, see `vc-state'."
(defun vc-oddmuse-working-revision (file)
"The current revision based on `oddmuse-revisions'."
(with-oddmuse-file
(oddmuse-get-latest-revision wiki pagename)))
(oddmuse-revision-get oddmuse-wiki oddmuse-page-name))
(defun vc-oddmuse-checkout-model (files)
"No locking."
@@ -60,6 +59,10 @@ For a list of possible values, see `vc-state'."
(defun vc-oddmuse-register (files &optional rev comment)
"This always works.")
(defun vc-oddmuse-revert (file &optional contents-done)
"No idea"
nil)
(defvar vc-oddmuse-log-command
(concat "curl --silent %w"
" --form action=rc"
@@ -146,7 +149,7 @@ a version backup."
(with-oddmuse-file file
(let ((command (oddmuse-format-command vc-oddmuse-get-revision-command)))
(with-temp-buffer
(oddmuse-run "Loading" command wiki pagename)
(oddmuse-run "Loading" command)
(write-file file))))))
(defun vc-oddmuse-checkin (files rev comment)

View File

@@ -1,536 +0,0 @@
/* This file is in the public domain. */
html{ text-align: center; }
body, rss {
font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
font-style: normal;
font-size: 14pt;
padding: 1em 3em;
max-width: 72ex;
display: inline-block;
text-align: left;
color: #000;
background-color: #fff;
}
@media print {
body {
font-size: 12pt;
}
/* hide all the crap */
div.diff, div.diff+hr, div.refer, div.near, div.definition, div.sister,
div.cal, div.footer, span.specialdays, span.gotobar, a.edit, a.number span,
div.rc form, form.tiny, p.comment, p#plus1, div.g-plusone, div.content a.feed {
display:none;
}
div.content a.book,
div.content a.movie {
text-decoration: none;
}
a cite {
font-style: italic;
}
img[alt="RSS"] { display: none }
a.rss { font-size: 8pt }
}
/* headings: we can use larger sizes if we use a lighter color.
we cannot inherit the font-family because header and footer use a narrow font. */
h1, h2, h3, title {
font-family: inherit;
font-weight: normal;
}
h1, channel title {
font-size: 32pt;
margin: 1em 0 0.5em 0;
padding: 0.4em 0;
}
h2 {
font-size: 18pt;
margin: 2em 0 0 0;
padding: 0;
}
h3 {
font-size: inherit;
font-weight: bold;
padding: 0;
margin: 1em 0 0 0;
clear: both;
}
/* headers in the journal are smaller */
div.journal h1, item title {
font-size: inherit;
padding: 0;
clear: both;
border-bottom: 1px solid #000;
}
div.journal h2 {
font-family: inherit;
font-size: inherit;
}
div.journal h3 {
font-family: inherit;
font-size: inherit;
font-weight: inherit;
font-style: italic;
}
div.journal hr {
visibility: hidden;
}
p.more {
margin-top: 3em;
}
/* Links in headings appear on journal pages. */
h1 a, h2 a, h3 a {
color:inherit;
text-decoration:none;
font-weight: normal;
}
h1 a:visited, h2 a:visited, h3 a:visited {
color: inherit;
}
/* for download buttons and the like */
.button {
display: inline-block;
font-size: 120%;
cursor: pointer;
padding: 0.4em 0.6em;
text-shadow: 0px -1px 0px #ccc;
background-color: #cfa;
border: 1px solid #9d8;
border-radius: 5px;
box-shadow: 0px 1px 3px white inset, 0px 1px 3px black;
}
.button .icon {
color: #363;
text-shadow: 0px -1px 1px white, 0px 1px 3px #666;
}
.button a {
text-decoration: none;
font-weight: normal;
}
/* links */
a.pencil {
padding-left: 1ex;
text-decoration: none;
color: inherit;
visibility: hidden;
transition: visibility 0s 1s, opacity 1s linear;
opacity: 0;
}
*:hover > a.pencil {
visibility: visible;
transition: opacity .5s linear;
opacity: 1;
}
@media print {
a.pencil {
display: none;
}
}
a.number {
text-decoration: none;
}
/* stop floating content from flowing over the footer */
hr {
clear: both;
}
/* the distance between links in the navigation bars */
span.bar a {
margin-right: 1ex;
}
a img {
border: none;
}
/* search box in the top bar */
.header form, .header p {
display: inline;
white-space: nowrap;
}
label[for="searchlang"], #searchlang, .header input[type="submit"] {
/* don't use display: none! http://stackoverflow.com/questions/5665203/getting-iphone-go-button-to-submit-form */
visibility: hidden; position: absolute;
}
/* wrap on the iphone */
@media only screen and (max-device-width: 480px) {
}
.header input {
width: 10ex;
}
/* other form fields */
input[type="text"] {
padding: 0;
font-size: 80%;
line-height: 125%;
}
/* code */
textarea, pre, code, tt {
font-family: "Andale Mono", Monaco, "Courier New", Courier, monospace, "Symbola";
font-size: 80%;
}
pre {
overflow:hidden;
white-space: pre-wrap; /* CSS 3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
/* styling for divs that will be invisible when printing
when printing. */
div.header, div.footer, div.near, div.definition, p.comment, a.tag {
font-size: 14pt;
}
@media print {
div.header, div.footer, div.near, div.definition, p.comment, a.tag {
font-size: 8pt;
}
}
div.footer form.search {
display: none;
}
div.rc li + li {
margin-top: 1em;
}
div.rc li strong, table.history strong, strong.description {
font-family: inherit;
font-weight: inherit;
}
div.diff {
padding-left: 5%;
padding-right: 5%;
font-size: 12pt;
color: #000;
}
div.old {
background-color: #ffffaf;
}
div.new {
background-color: #cfffcf;
}
div.refer {
padding-left: 5%;
padding-right: 5%;
font-size: 12pt;
}
div.message {
background-color:#fee;
color:#000;
}
img.xml {
border:none;
padding:1px;
}
a.small img {
max-width:300px;
}
a.large img {
max-width:600px;
}
div.sister {
margin-right:1ex;
background-color:inherit;
}
div.sister p {
margin-top:0;
}
div.sister hr {
display:none;
}
div.sister img {
border:none;
}
div.near, div.definition {
background-color:#efe;
}
div.sidebar {
float:right;
border:1px dotted #000;
padding:0 1em;
}
div.sidebar ul {
padding-left:1em;
}
/* replacements, features */
ins {
font-style: italic;
text-decoration: none;
}
acronym, abbr {
letter-spacing:0.1em;
font-variant:small-caps;
}
/* Interlink prefix not shown */
a .site, a .separator {
display: none;
}
a cite { font:inherit; }
/* browser borkage */
textarea[name="text"] { width:97%; height:80%; }
textarea[name="summary"] { width:97%; height:3em; }
/* comments */
textarea[name="aftertext"] { width:97%; height:10em; }
div.commentshown {
font-size: 12pt;
padding: 2em 0;
}
div.commenthidden {
display:none;
}
div.commentshown {
display:block;
}
p.comment {
margin-bottom: 0;
}
div.comment {
font-size: 14pt;
}
div.comment h2 {
margin-top: 5em;
}
/* comment pages with username, homepage, and email subscription */
.comment form span { display: block; }
.comment form span label { display: inline-block; width: 10em; }
/* IE sucks */
.comment input#username,
.comment input#homepage,
.comment input#mail { width: 20em; }
/* cal */
div.month { padding:0; margin:0 2ex; }
body > div.month {
float:right;
background-color: inherit;
border:solid thin;
padding:0 1ex;
}
.year > .month {
float:left;
}
.footer {
clear:both;
}
.month .title a.local {
background-color: inherit;
}
.month a.local {
background-color: #ddf;
}
.month a.today {
background-color: #fdd;
}
.month a {
color:inherit;
font-weight:inherit;
text-decoration: none;
background-color: #eee;
}
/* history tables and other tables */
table.history {
border: none;
}
td.history {
border: none;
}
table.user {
border: none;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
padding: 1em;
margin: 1em 2em;
}
table.user tr td, table.user tr th {
border: none;
padding: 0.2em 0.5em;
vertical-align: top;
}
table.arab tr th {
font-weight:normal;
text-align:left;
vertical-align:top;
}
table.arab, table.arab tr th, table.arab tr td {
border:none;
}
th.nobreak {
white-space:nowrap;
}
table.full { width:99%; margin-left:1px; }
table.j td, table.j th, table tr td.j, table tr th.j, .j { text-align:justify; }
table.l td, table.l th, table tr td.l, table tr th.l, .l { text-align:left; }
table.r td, table.r th, table tr td.r, table tr th.r, .r { text-align:right; }
table.c td, table.c th, table tr td.c, table tr th.c, .c { text-align:center; }
table.t td { vertical-align: top; }
td.half { width:50%; }
td.third { width:33%; }
form table td { padding:5px; }
/* lists */
dd { padding-bottom:0.5ex; }
dl.inside dt { float:left; }
/* search */
div.search span.result { font-size:larger; }
div.search span.info { font-size:smaller; font-style:italic; }
div.search p.result { display:none; }
img.logo {
float: right;
margin: 0 0 0 1ex;
padding: 0;
border: 1px solid #000;
opacity: 0.3;
background-color:#ffe;
}
/* images */
div.content a.feed img, div.journal a.feed img,
div.content a img.smiley, div.journal a img.smiley, img.smiley,
div.content a.inline img, div.journal a.inline img,
div.content li a.image img, div.journal li a.image img {
margin: 0; padding: 0; border: none;
}
div.image a img {
margin-bottom: 0;
}
div.image span.caption {
margin: 0 1em;
}
img {
max-width: 100%;
}
.left { float:left; margin-right: 1em; }
.right { float:right; margin-left: 1em; }
.half img { height: 50%; width: 50%; }
.face img { width: 200px; }
div.left .left, div.right .right {
float:none;
}
.center { text-align:center; }
table.aside {
float:right;
width:40%;
margin-left: 1em;
padding: 1ex;
border: 1px dotted #666;
}
table.aside td {
text-align:left;
}
div.sidebar {
float:right; width: 250px;
text-align: right;
border: none;
margin: 1ex;
}
.bigsidebar {
float:right;
width: 500px;
border: none;
margin-left: 1ex;
font-size: 80%;
}
dl.irc dt { width:20ex; float:left; text-align:right; clear:left; }
dl.irc dt span.time { float:left; }
dl.irc dd { margin-left:22ex; }
/* portrait */
div.footer, div.comment, hr { clear: both; }
.portrait { float: left; font-size: small; margin-right: 1em; }
.portrait a { color: #999; }
div.left { float:left; margin:1em; padding: 0.5em; }
div.left p { display:table-cell; }
div.left p + p { display:table-caption; caption-side:bottom; }
p.table a { float:left; width:20ex; }
p.table + p { clear:both; }
/* rss */
channel * { display: block; }
channel title {
margin-top: 30pt;
}
copyright {
font-size: 14pt;
margin-top: 1em;
}
channel > link:before {
font-size: 18pt;
display: block;
margin: 1em;
padding: 0.5em;
content: "This is an RSS feed, designed to be read in a feed reader.";
color: red;
border: 1px solid red;
}
link, license {
font-size: 11pt;
margin-bottom: 9pt;
}
username:before { content: "Last edited by "; }
username:after { content: "."; }
generator:before { content: "Feed generated by "; }
generator:after { content: "."; }
channel description {
font-weight: bold;
}
item description {
font-style: italic;
font-weight: normal;
margin-bottom: 1em;
}
docs, language,
pubDate, lastBuildDate, ttl, guid, category, comments,
docs, image title, image link,
status, version, diff, history, importance {
display: none;
}

View File

@@ -321,6 +321,7 @@ div.sister {
float:left;
margin-right:1ex;
padding-right:1ex;
border-right:1px dashed;
}
div.sister p { padding:1ex; margin:0; }
div.sister hr { display:none; }

View File

@@ -47,8 +47,8 @@ textarea, pre, code, tt {
.browse { min-height: 3em; }
.header form, .header p { margin: 0; }
/* hide the buttons but don't use display:none because of
http://stackoverflow.com/questions/5665203/getting-iphone-go-button-to-submit-form
.header input[type="submit"] { position: absolute; visibility: hidden; } */
http://stackoverflow.com/questions/5665203/getting-iphone-go-button-to-submit-form */
.header input[type="submit"] { position: absolute; visibility: hidden; }
.header input { width: 5em; font-size: 80%; }
.footer { clear:both; font-size: 90%; }
.content input { font-size: 80%; line-height: 125%; }
@@ -201,12 +201,10 @@ div.message {
}
table.history { border-style:none; }
td.history { border-style:none; }
div.history span.dash + strong { font-weight: normal; }
span.result { font-size:larger; }
span.info { font-size:smaller; font-style:italic; }
div.rc hr { display: none; }
div.rc li { padding-bottom: 0.5em; }
div.rc li strong { font-weight: normal; }
/* Tables */
table.user {

View File

@@ -219,3 +219,50 @@ code {
background: #eee;
white-space: pre-wrap;
}
@font-face {
font-family: 'Gentium Basic';
font-style: normal;
font-weight: 700;
src: local('Gentium Basic Bold'), local('GentiumBasic-Bold'), url(/fonts/GenBasB.woff) format('woff');
}
@font-face {
font-family: 'Gentium Basic';
font-style: italic;
font-weight: 400;
src: local('Gentium Basic Italic'), local('GentiumBasic-Italic'), url(/fonts/GenBasI.woff) format('woff');
}
@font-face {
font-family: 'Gentium Basic';
font-style: italic;
font-weight: 700;
src: local('Gentium Basic Bold Italic'), local('GentiumBasic-BoldItalic'), url(/fonts/GenBasBI.woff) format('woff');
}
@font-face {
font-family: 'Gentium Basic';
font-style: normal;
font-weight: 400;
src: local('Gentium Basic'), local('GentiumBasic'), url(/fonts/GenBasR.woff) format('woff');
}
@font-face {
font-family: 'Gentium Plus';
font-style: normal;
font-weight: 400;
src: local('Gentium Plus'), local('GentiumPlus'), url(/fonts/GentiumPlus-R.woff) format('woff');
}
@font-face {
font-family: 'Gentium Plus';
font-style: italic;
font-weight: 400;
src: local('Gentium Plus Italic'), local('GentiumPlus-Italic'), url(/fonts/GentiumPlus-I.woff) format('woff');
}
@font-face {
font-family: 'Symbola';
src: local('Symbola'), url('/fonts/Symbola.woff') format('woff') url('/fonts/Symbola.ttf') format('truetype');
}

View File

@@ -44,7 +44,7 @@ sub AdminPowerDelete {
GetCluster($Page{text}));
}
# Regenerate index on next request
Unlink($IndexFile);
unlink($IndexFile);
ReleaseLock();
print $q->p(T('Main lock released.'));
PrintFooter();
@@ -61,30 +61,30 @@ sub AdminPowerRename {
print $q->p(T('Main lock obtained.'));
# page file -- only check for existing or missing pages here
my $fname = GetPageFile($id);
ReportError(Ts('The page %s does not exist', $id), '400 BAD REQUEST') unless IsFile($fname);
ReportError(Ts('The page %s does not exist', $id), '400 BAD REQUEST') unless -f $fname;
my $newfname = GetPageFile($new);
ReportError(Ts('The page %s already exists', $new), '400 BAD REQUEST') if IsFile($newfname);
ReportError(Ts('The page %s already exists', $new), '400 BAD REQUEST') if -f $newfname;
# Regenerate index on next request -- remove this before errors can occur!
Unlink($IndexFile);
unlink($IndexFile);
# page file
CreateDir($PageDir); # It might not exist yet
Rename($fname, $newfname)
rename($fname, $newfname)
or ReportError(Tss('Cannot rename %1 to %2', $fname, $newfname) . ": $!", '500 INTERNAL SERVER ERROR');
# keep directory
my $kdir = GetKeepDir($id);
my $newkdir = GetKeepDir($new);
CreateDir($KeepDir); # It might not exist yet (only the parent directory!)
Rename($kdir, $newkdir)
rename($kdir, $newkdir)
or ReportError(Tss('Cannot rename %1 to %2', $kdir, $newkdir) . ": $!", '500 INTERNAL SERVER ERROR')
if IsDir($kdir);
if -d $kdir;
# refer file
if (defined(&GetRefererFile)) {
my $rdir = GetRefererFile($id);
my $newrdir = GetRefererFile($new);
CreateDir($RefererDir); # It might not exist yet
Rename($rdir, $newrdir)
rename($rdir, $newrdir)
or ReportError(Tss('Cannot rename %1 to %2', $rdir, $newrdir) . ": $!", '500 INTERNAL SERVER ERROR')
if IsDir($rdir);
if -d $rdir;
}
# RecentChanges
OpenPage($new);

View File

@@ -29,7 +29,7 @@ $NewQuestion = 'Write your question here:';
sub IncrementInFile {
my $filename = shift;
sysopen my $fh, encode_utf8($filename), O_RDWR|O_CREAT or die "can't open $filename: $!";
sysopen my $fh, $filename, O_RDWR|O_CREAT or die "can't open $filename: $!";
flock $fh, LOCK_EX or die "can't flock $filename: $!";
my $num = <$fh> || 1;
seek $fh, 0, 0 or die "can't rewind $filename: $!";

View File

@@ -171,9 +171,9 @@ sub UserCanEditAutoLockFix {
return 0 if $id eq 'SampleUndefinedPage' or $id eq T('SampleUndefinedPage')
or $id eq 'Sample_Undefined_Page' or $id eq T('Sample_Undefined_Page');
return 1 if UserIsAdmin() || UserIsEditor();
return 0 if $id ne '' and IsFile(GetLockedPageFile($id));
return 0 if $LockOnCreation{$id} and not IsFile(GetPageFile($id)); # new page
return 0 if !$EditAllowed or IsFile($NoEditFile);
return 0 if $id ne '' and -f GetLockedPageFile($id);
return 0 if $LockOnCreation{$id} and not -f GetPageFile($id); # new page
return 0 if !$EditAllowed or -f $NoEditFile;
return 0 if $editing and UserIsBanned(); # this call is more expensive
return 0 if $EditAllowed >= 2 and (not $CommentsPrefix or $id !~ /^$CommentsPrefix/);
return 1 if $EditAllowed >= 3 and ($comment or (GetParam('aftertext', '') and not GetParam('text', '')));

View File

@@ -44,8 +44,8 @@ sub BacklinksMenu {
$Action{buildback} = \&BuildBacklinkDatabase;
sub BuildBacklinkDatabase {
print GetHttpHeader('text/plain');
Unlink($backfile); # Remove old database
tie my %backhash, 'MLDBM', encode_utf8($backfile) or die "Cannot open file $backfile $!\n";
unlink $backfile; # Remove old database
tie my %backhash, 'MLDBM', $backfile or die "Cannot open file $backfile $!\n";
log1("Starting Database Store Process ... please wait\n\n");
foreach my $name (AllPagesList()) {
@@ -101,7 +101,7 @@ sub GetBackLink {
our ($BacklinkBanned);
$BacklinkBanned = "HomePage|ScratchPad" if !$BacklinkBanned;
tie my %backhash, 'MLDBM', encode_utf8($backfile), O_CREAT|O_RDWR, oct(644) or die "Cannot open file $backfile $!\n";
tie my %backhash, 'MLDBM', $backfile, O_CREAT|O_RDWR, oct(644) or die "Cannot open file $backfile $!\n";
# Search database for matches
while ( my ($source, $hashes) = each %backhash ) {

View File

@@ -77,7 +77,8 @@ sub DoUnifiedDiff { # copied from DoDiff
RequestLockDir('diff') or return '';
WriteStringToFile($oldName, $_[0]);
WriteStringToFile($newName, $_[1]);
my $diff_out = decode_utf8(`diff -U 99999 -- \Q$oldName\E \Q$newName\E | tail -n +7`); # should be +4, but we always add extra line # TODO that workaround is ugly, fix it!
my $diff_out = `diff -U 99999 -- \Q$oldName\E \Q$newName\E | tail -n +7`; # should be +4, but we always add extra line # TODO that workaround is ugly, fix it!
utf8::decode($diff_out); # needs decoding
$diff_out =~ s/\n\K\\ No newline.*\n//g; # Get rid of common complaint.
ReleaseLockDir('diff');
# No need to unlink temp files--next diff will just overwrite.

View File

@@ -29,7 +29,7 @@ push(@MyInitVariables, \&DraftInit);
sub DraftInit {
if (GetParam('Draft', '')) {
SetParam('action', 'draft') ; # Draft button used
} elsif (IsFile("$DraftDir/" . GetParam('username', $q->remote_addr())) # draft exists
} elsif (-f "$DraftDir/" . GetParam('username', $q->remote_addr()) # draft exists
and $FooterNote !~ /action=draft/) { # take care of mod_perl persistence
$FooterNote = $q->p(ScriptLink('action=draft', T('Recover Draft'))) . $FooterNote;
}
@@ -47,9 +47,9 @@ sub DoDraft {
WriteStringToFile($draft, EncodePage(text=>$text, id=>$id));
SetParam('msg', T('Draft saved')); # invalidate cache
print GetHttpHeader('', T('Draft saved'), '204 NO CONTENT');
} elsif (IsFile($draft)) {
} elsif (-f $draft) {
my $data = ParseData(ReadFileOrDie($draft));
Unlink($draft);
unlink ($draft);
$Message .= $q->p(T('Draft recovered'));
DoEdit($data->{id}, $data->{text}, 1);
} else {
@@ -76,19 +76,22 @@ push(@MyMaintenance, \&DraftCleanup);
sub DraftFiles {
return map {
substr($_, length($DraftDir) + 1);
} Glob("$DraftDir/*"), Glob("$DraftDir/.*");
my $x = $_;
$x = substr($x, length($DraftDir) + 1);
utf8::decode($x);
$x;
} bsd_glob("$DraftDir/*"), bsd_glob("$DraftDir/.*");
}
sub DraftCleanup {
print '<p>' . T('Draft Cleanup');
foreach my $draft (DraftFiles()) {
next if $draft eq '.' or $draft eq '..';
my $ts = Modified("$DraftDir/$draft");
my $ts = (stat("$DraftDir/$draft"))[9];
if ($Now - $ts < 1209600) { # 14*24*60*60
print $q->br(), Tss("%1 was last modified %2 and was kept",
$draft, CalcTimeSince($Now - $ts));
} elsif (Unlink("$DraftDir/$draft")) {
} elsif (unlink("$DraftDir/$draft")) {
print $q->br(), Tss("%1 was last modified %2 and was deleted",
$draft, CalcTimeSince($Now - $ts));
} else {

View File

@@ -27,7 +27,8 @@ sub FixEncoding {
ValidIdOrDie($id);
RequestLockOrError();
OpenPage($id);
my $text = decode_utf8($Page{text});
my $text = $Page{text};
utf8::decode($text);
Save($id, $text, T('Fix character encoding'), 1) if $text ne $Page{text};
ReleaseLock();
ReBrowsePage($id);

View File

@@ -12,8 +12,8 @@ our ($q, $OpenPageName, @MyRules, $CrossbarPageName);
push(@MyRules, \&FormsRule);
sub FormsRule {
if (IsFile(GetLockedPageFile($OpenPageName)) or (InElement('div', '^class="crossbar"$') and
IsFile(GetLockedPageFile($CrossbarPageName)))) {
if (-f GetLockedPageFile($OpenPageName) or (InElement('div', '^class="crossbar"$') and
-f GetLockedPageFile($CrossbarPageName))) {
if (/\G(\&lt;form.*?\&lt;\/form\&gt;)/cgs) {
my $form = $1;
my $oldpos = pos;

View File

@@ -163,7 +163,8 @@ sub GdSecurityImageGenerate {
my ($imgData) = $img->out(force => 'png');
my $ticketId = Digest::MD5::md5_hex(rand());
CreateDir($GdSecurityImageDir);
open my $fh, ">:raw", encode_utf8(GdSecurityImageGetImageFile($ticketId))
my $file = GdSecurityImageGetImageFile($ticketId);
open my $fh, ">:raw", $file
or ReportError(Ts('Image storing failed. (%s)', $!), '500 INTERNAL SERVER ERROR');
print $fh $imgData;
#print $fh $png; ### experimental ###
@@ -186,7 +187,9 @@ sub GdSecurityImageIsValidId {
}
sub GdSecurityImageReadImageFile {
if (open(my $IN, '<:raw', encode_utf8(shift))) {
my $file = shift;
utf8::encode($file); # filenames are bytes!
if (open(my $IN, '<:raw', $file)) {
local $/ = undef; # Read complete files
my $data=<$IN>;
close $IN;
@@ -208,7 +211,7 @@ sub GdSecurityImageDoImage {
print $q->header(-type=>'image/png');
print $data;
Unlink(GdSecurityImageGetImageFile($id));
unlink(GdSecurityImageGetImageFile($id));
}
sub GdSecurityImageCleanup {
@@ -216,10 +219,10 @@ sub GdSecurityImageCleanup {
if (!GdSecurityImageIsValidId($id)) {
return;
}
my @files = (Glob("$GdSecurityImageDir/*.png"), Glob("$GdSecurityImageDir/*.ticket"));
my @files = (bsd_glob("$GdSecurityImageDir/*.png"), bsd_glob("$GdSecurityImageDir/*.ticket"));
foreach my $file (@files) {
if ($Now - Modified($file) > $GdSecurityImageDuration) {
Unlink($file);
if ($Now - (stat $file)[9] > $GdSecurityImageDuration) {
unlink($file);
}
}
}
@@ -252,7 +255,7 @@ sub GdSecurityImageCheck {
}
if (GdSecurityImageIsValidId($id)) {
Unlink(GdSecurityImageGetTicketFile($id));
unlink(GdSecurityImageGetTicketFile($id));
}
$GdSecurityImageId = GdSecurityImageGenerate();

View File

@@ -30,18 +30,18 @@ $GitMail = 'unknown@oddmuse.org';
sub GitCommit {
my ($message, $author) = @_;
my $oldDir = cwd;
ChangeDir("$DataDir/page");
chdir("$DataDir/page");
capture {
system($GitBinary, qw(add -A));
system($GitBinary, qw(commit -q -m), $message, "--author=$author <$GitMail>");
};
ChangeDir($oldDir);
chdir($oldDir);
}
sub GitInitRepository {
return if IsDir("$DataDir/page/.git");
return if -d "$DataDir/page/.git";
capture {
system($GitBinary, qw(init -q --), encode_utf8("$DataDir/page"));
system($GitBinary, qw(init -q --), "$DataDir/page");
};
GitCommit('Initial import', 'Oddmuse');
}

View File

@@ -80,7 +80,7 @@ sub GitRun {
my $exitStatus;
# warn join(' ', $GitBinary, @_) . "\n";
ChangeDir($GitRepo);
chdir($GitRepo);
if ($GitDebug) {
# TODO use ToString here
# capture the output of the git comand in a temporary file
@@ -99,7 +99,7 @@ sub GitRun {
} else {
$exitStatus = system($GitBinary, @_);
}
ChangeDir($oldDir);
chdir($oldDir);
return $exitStatus;
}
@@ -108,7 +108,7 @@ sub GitInitVariables {
}
sub GitInitRepository {
return if IsDir("$GitRepo/.git");
return if -d "$GitRepo/.git";
my $exception = shift;
CreateDir($GitRepo);
GitRun(qw(init --quiet));
@@ -187,16 +187,17 @@ sub DoGitCleanup {
}
sub GitCleanup {
if (IsDir($GitRepo)) {
if (-d $GitRepo) {
print $q->p('Git cleanup starting');
AllPagesList();
# delete all the files including all the files starting with a dot
opendir(DIR, encode_utf8($GitRepo)) or ReportError("cannot open directory $GitRepo: $!");
opendir(DIR, $GitRepo) or ReportError("cannot open directory $GitRepo: $!");
foreach my $file (readdir(DIR)) {
my $name = decode_utf8($file);
my $name = $file;
utf8::decode($name); # filenames are bytes
next if $file eq '.git' or $file eq '.' or $file eq '..' or $IndexHash{$name};
print $q->p("Deleting left over file $name");
Unlink("$GitRepo/$file") or ReportError("cannot delete $GitRepo/$name: $!");
unlink "$GitRepo/$file" or ReportError("cannot delete $GitRepo/$name: $!");
}
closedir DIR;
# write all the files again, just to be sure

View File

@@ -43,7 +43,7 @@ sub HtmlTemplate {
my $type = shift;
# return header.de.html, or header.html, or error.html, or report an error...
foreach my $f ((map { "$type.$_" } HtmlTemplateLanguage()), $type, "error") {
return "$HtmlTemplateDir/$f.html" if IsFile("$HtmlTemplateDir/$f.html");
return "$HtmlTemplateDir/$f.html" if -r "$HtmlTemplateDir/$f.html";
}
ReportError(Tss('Could not find %1.html template in %2', $type, $HtmlTemplateDir),
'500 INTERNAL SERVER ERROR');

View File

@@ -87,7 +87,7 @@ sub GetActionHtmlTemplate {
my $action = GetParam('action', 'browse');
# return browse.de.html, or browse.html, or error.html, or report an error...
foreach my $f ((map { "$action.$_" } HtmlTemplateLanguage()), $action, "error") {
return "$HtmlTemplateDir/$f.html" if IsFile("$HtmlTemplateDir/$f.html");
return "$HtmlTemplateDir/$f.html" if -r "$HtmlTemplateDir/$f.html";
}
ReportError(Tss('Could not find %1.html template in %2', $action, $HtmlTemplateDir),
'500 INTERNAL SERVER ERROR');

View File

@@ -31,7 +31,7 @@ push(@MyRules, \&HtmlLinksRule);
$RuleOrder{\&HtmlLinksRule} = 105;
sub HtmlLinksRule {
if (IsFile(GetLockedPageFile($OpenPageName))) {
if (-f GetLockedPageFile($OpenPageName)) {
$HtmlLinks = 1;
} else {
$HtmlLinks = 0;

View File

@@ -32,7 +32,7 @@ sub DivFooRule {
my $str = $1;
CreateDir($ImagifyDir);
my $fileName = sha256_hex($str) . '.' . $ImagifyFormat;
system('convert', %ImagifyParams, "caption:$str", "$ImagifyDir/$fileName") unless IsFile("$ImagifyDir/$fileName");
system('convert', %ImagifyParams, "caption:$str", "$ImagifyDir/$fileName") unless -e "$ImagifyDir/$fileName";
my $src = $ScriptName . "/imagify/" . UrlEncode($fileName);
return CloseHtmlEnvironments() . $q->img({-class => 'imagify', -src => $src, -alt => '(rendered text)'}) . AddHtmlEnvironment('p');
}

View File

@@ -131,8 +131,8 @@ sub MakeLaTeX {
# Select which binary to use for conversion of dvi to images
my $useConvert = 0;
if (not IsFile($dvipngPath)) {
if (not IsFile($convertPath)) {
if (not -e $dvipngPath) {
if (not -e $convertPath) {
return "[Error: dvipng binary and convert binary not found at $dvipngPath or $convertPath ]";
}
else {
@@ -155,13 +155,13 @@ sub MakeLaTeX {
}
# check cache
if (not IsFile("$LatexDir/$hash.png") or ZeroSize("$LatexDir/$hash.png")) {
if (not -f "$LatexDir/$hash.png" or -z "$LatexDir/$hash.png") { #If file doesn't exist or is zero bytes
# Then create the image
# read template and replace <math>
CreateDir($LatexDir);
if (not IsFile($LatexDefaultTemplateName)) {
open (my $F, '>', encode_utf8($LatexDefaultTemplateName)) or return '[Unable to write template]';
mkdir($LatexDir) unless -d $LatexDir;
if (not -f $LatexDefaultTemplateName) {
open (my $F, '>', $LatexDefaultTemplateName) or return '[Unable to write template]';
print $F $LatexDefaultTemplate;
close $F;
}
@@ -169,12 +169,12 @@ sub MakeLaTeX {
$template =~ s/<math>/$latex/gi;
#setup rendering directory
my $dir = "$LatexDir/$hash";
if (IsDir($dir)) {
Unlink((Glob("$dir/*")));
if (-d $dir) {
unlink (bsd_glob('$dir/*'));
} else {
CreateDir($dir);
mkdir($dir) or return "[Unable to create $dir]";
}
ChangeDir($dir) or return "[Unable to switch to $dir]";
chdir ($dir) or return "[Unable to switch to $dir]";
WriteStringToFile ("srender.tex", $template);
my $errorText = qx(latex srender.tex);
@@ -197,16 +197,16 @@ sub MakeLaTeX {
$error = "[dvipng error $? ($output)]" if $?;
}
if (not $error and IsFile('srender1.png') and not ZeroSize('srender1.png')) {
if (not $error and -f 'srender1.png' and not -z 'srender1.png') {
my $png = ReadFileOrDie("srender1.png");
WriteStringToFile ("$LatexDir/$hash.png", $png);
} else {
$error = "[Error retrieving image for $latex]";
}
}
Unlink(glob('*'));
ChangeDir($LatexDir);
RemoveDir($dir);
unlink (glob('*'));
chdir ($LatexDir);
rmdir ($dir);
return $error if $error;
}

View File

@@ -38,7 +38,7 @@ sub DoListLocked {
print $q->start_div({-class=>'content list locked'}), $q->start_p();
}
foreach my $id (AllPagesList()) {
PrintPage($id) if IsFile(GetLockedPageFile($id));
PrintPage($id) if -f GetLockedPageFile($id);
}
if (not $raw) {
print $q->end_p(), $q->end_div();

View File

@@ -29,7 +29,6 @@ our %TranslationsLibrary = (
'bg' => 'bulgarian-utf8.pl',
'ca' => 'catalan-utf8.pl',
'de' => 'german-utf8.pl',
'et' => 'estonian-utf8.pl',
'es' => 'spanish-utf8.pl',
'fi' => 'finnish-utf8.pl',
'fr' => 'french-utf8.pl',
@@ -73,11 +72,10 @@ sub LoadLanguage {
foreach (@prefs) {
last if $Lang{$_} eq 'en'; # the default
my $file = $TranslationsLibrary{$Lang{$_}};
next unless $file; # file is not listed, eg. there is no file for "de-ch"
$file = "$LoadLanguageDir/$file" if defined $LoadLanguageDir;
if (IsFile($file)) {
if (-r $file) {
do $file;
do "$ConfigFile-$Lang{$_}" if IsFile("$ConfigFile-$Lang{$_}");
do "$ConfigFile-$Lang{$_}" if -r "$ConfigFile-$Lang{$_}";
$CurrentLanguage = $Lang{$_};
last;
}

View File

@@ -64,9 +64,9 @@ You can change this expiry time by setting C<$LnCacheHours>.
push (@MyMaintenance, \&LnMaintenance);
sub LnMaintenance {
if (opendir(DIR, encode_utf8($RssDir))) { # cleanup if they should expire anyway
foreach my $file (readdir(DIR)) {
Unlink("$RssDir/$file") if $Now - Modified($file) > $LnCacheHours * 3600;
if (opendir(DIR, $RssDir)) { # cleanup if they should expire anyway
foreach (readdir(DIR)) {
unlink "$RssDir/$_" if $Now - (stat($_))[9] > $LnCacheHours * 3600;
}
closedir DIR;
}
@@ -157,7 +157,7 @@ sub LocalNamesInit {
my %data = ();
if (GetParam('cache', $UseCache) > 0) {
foreach my $uri (keys %todo) { # read cached rss files if possible
if ($Now - Modified($todo{$uri}) < $LnCacheHours * 3600) {
if ($Now - (stat($todo{$uri}))[9] < $LnCacheHours * 3600) {
$data{$uri} = ReadFile($todo{$uri});
delete($todo{$uri}); # no need to fetch them below
}

View File

@@ -22,7 +22,7 @@ use v5.10;
AddModuleDescription('login.pl', 'Login Module');
our ($q, %Action, $SiteName, @MyAdminCode, $IndexFile, $DataDir, $FullUrl);
our ($RegistrationForm, $MinimumPasswordLength, $RegistrationsMustBeApproved, $LoginForm, $PasswordFile, $PasswordFileToUse, $PendingPasswordFile, $RequireLoginToEdit, $ConfirmEmailAddress, $UnconfirmedPasswordFile, $EmailSenderAddress, $EmailCommand, $EmailRegExp, $NotifyPendingRegistrations, $EmailConfirmationMessage, $ResetPasswordMessage, $LogoutForm, $ResetForm, $ChangePassForm, $RequireCamelUserName, $UsernameRegExp);
our ($RegistrationForm, $MinimumPasswordLength, $RegistrationsMustBeApproved, $LoginForm, $PasswordFile, $PasswordFileToUse, $PendingPasswordFile, $RequireLoginToEdit, $ConfirmEmailAddress, $UncomfirmedPasswordFile, $EmailSenderAddress, $EmailCommand, $EmailRegExp, $NotifyPendingRegistrations, $EmailConfirmationMessage, $ResetPasswordMessage, $LogoutForm, $ResetForm, $ChangePassForm, $RequireCamelUserName, $UsernameRegExp);
my $EncryptedPassword = "";
@@ -40,7 +40,7 @@ $RegistrationsMustBeApproved = 1 unless defined $RegistrationsMustBeApproved;
$PendingPasswordFile = "$DataDir/pending" unless defined $PendingPasswordFile;
$ConfirmEmailAddress = 1 unless defined $ConfirmEmailAddress;
$UnconfirmedPasswordFile = "$DataDir/uncomfirmed" unless defined $UnconfirmedPasswordFile;
$UncomfirmedPasswordFile = "$DataDir/uncomfirmed" unless defined $UncomfirmedPasswordFile;
$EmailSenderAddress = "fletcher\@freeshell.org" unless defined $EmailSenderAddress;
$EmailCommand = "/usr/sbin/sendmail -oi -t" unless defined $EmailCommand;
@@ -71,7 +71,7 @@ $PasswordFileToUse = $RegistrationsMustBeApproved
? $PendingPasswordFile : $PasswordFile;
$PasswordFileToUse = $ConfirmEmailAddress
? $UnconfirmedPasswordFile : $PasswordFileToUse;
? $UncomfirmedPasswordFile : $PasswordFileToUse;
$RegistrationForm = <<'EOT' unless defined $RegistrationForm;
<p>Your Username should be a CamelCase form of your real name, e.g. JohnDoe.</p>
@@ -290,7 +290,7 @@ sub DoProcessLogin {
ReportError(T('Username and/or password are incorrect.'))
unless (AuthenticateUser($username,$pwd));
Unlink($IndexFile);
unlink($IndexFile);
print GetHeader('', Ts('Register for %s', $SiteName), '');
print '<div class="content">';
print Ts('Logged in as %s.', $username);
@@ -318,7 +318,7 @@ $Action{process_logout} = \&DoProcessLogout;
sub DoProcessLogout {
SetParam('pwd','');
SetParam('username','');
Unlink($IndexFile); # I shouldn't have to do this...
unlink($IndexFile); # I shouldn't have to do this...
print GetHeader('', Ts('Logged out of %s', $SiteName), '');
print '<div class="content">';
print T('You are now logged out.');
@@ -328,7 +328,7 @@ sub DoProcessLogout {
sub UserExists {
my $username = shift;
if (open (my $PASSWD, '<', encode_utf8($PasswordFile))) {
if (open (my $PASSWD, '<', $PasswordFile)) {
while ( <$PASSWD> ) {
if ($_ =~ /^$username:/) {
return 1;
@@ -338,7 +338,7 @@ sub UserExists {
}
if ($RegistrationsMustBeApproved) {
if (open (my $PASSWD, '<', encode_utf8($PendingPasswordFile))) {
if (open (my $PASSWD, '<', $PendingPasswordFile)) {
while ( <$PASSWD> ) {
if ($_ =~ /^$username:/) {
return 1;
@@ -349,7 +349,7 @@ sub UserExists {
}
if ($ConfirmEmailAddress) {
if (open (my $PASSWD, '<', encode_utf8($UnconfirmedPasswordFile))) {
if (open (my $PASSWD, '<', $UncomfirmedPasswordFile)) {
while ( <$PASSWD> ) {
if ($_ =~ /^$username:/) {
return 1;
@@ -490,13 +490,14 @@ sub ConfirmUser {
my ($username, $key) = @_;
my $FileToUse = $RegistrationsMustBeApproved
? $PendingPasswordFile : $PasswordFileToUse;
if (open(my $PASSWD, '<', encode_utf8($UnconfirmedPasswordFile))) {
if (open(my $PASSWD, '<', $UncomfirmedPasswordFile)) {
while (<$PASSWD>) {
if ($_ =~ /^$username:(.*):(.*)/) {
if (crypt($1,$key) eq $key) {
AddUser($username,$1,$2,$FileToUse);
close $PASSWD;
RemoveUser($username,$UnconfirmedPasswordFile);
RemoveUser($username,$UncomfirmedPasswordFile);
if ($RegistrationsMustBeApproved) {
SendNotification($username);
}
@@ -514,7 +515,8 @@ sub RemoveUser {
my %passwords = ();
my %emails = ();
if (open (my $PASSWD, '<', encode_utf8($FileToUse))) {
if (open (my $PASSWD, '<', $FileToUse)) {
while ( <$PASSWD> ) {
if ($_ =~ /^(.*):(.*):(.*)$/) {
next if ($1 eq $username);
@@ -597,7 +599,8 @@ sub ChangePassword {
my %passwords = ();
my %emails = ();
if (open (my $PASSWD, '<', encode_utf8($PasswordFile))) {
if (open (my $PASSWD, '<', $PasswordFile)) {
while ( <$PASSWD> ) {
if ($_ =~ /^(.*):(.*):(.*)$/) {
$passwords{$1}=$2;
@@ -609,7 +612,7 @@ sub ChangePassword {
$passwords{$user} = $hash;
open (my $PASSWD, '>', encode_utf8($PasswordFile));
open (my $PASSWD, '>', $PasswordFile);
foreach my $key ( sort keys(%passwords)) {
print $PASSWD "$key:$passwords{$key}:$emails{$key}\n";
}
@@ -716,7 +719,7 @@ sub DoApprovePending {
}
} else {
print '<ul>';
if (open(my $PASSWD, '<', encode_utf8($PendingPasswordFile))) {
if (open(my $PASSWD, '<', $PendingPasswordFile)) {
while (<$PASSWD>) {
if ($_ =~ /^(.*):(.*):(.*)$/) {
print '<li>' . ScriptLink("action=approve_pending;user=$1;",$1) . ' - ' . $3 . '</li>';
@@ -737,7 +740,8 @@ sub DoApprovePending {
sub ApproveUser {
my ($username) = @_;
if (open(my $PASSWD, '<', encode_utf8($PendingPasswordFile))) {
if (open(my $PASSWD, '<', $PendingPasswordFile)) {
while (<$PASSWD>) {
if ($_ =~ /^$username:(.*):(.*)/) {
AddUser($username,$1,$2,$PasswordFile);

View File

@@ -56,12 +56,14 @@ sub MacFixEncoding {
return unless %Namespaces;
my %hash = ();
for my $key (keys %Namespaces) {
utf8::decode($key);
$key = NFC($key);
$hash{$key} = $NamespaceRoot . '/' . $key . '/';
}
%Namespaces = %hash;
%hash = ();
for my $key (keys %InterSite) {
utf8::decode($key);
$key = NFC($key);
$hash{$key} = $Namespaces{$key} if $Namespaces{$key};
}

View File

@@ -120,7 +120,7 @@ sub MailIsSubscribed {
return 0 unless $mail;
# open the DB file
require DB_File;
tie my %h, "DB_File", encode_utf8($MailFile);
tie my %h, "DB_File", $MailFile;
my %subscribers = map {$_=>1} split(/$FS/, UrlDecode($h{UrlEncode($id)}));
untie %h;
return $subscribers{$mail};
@@ -197,7 +197,7 @@ sub NewMailDeletePage {
sub MailDeletePage {
my $id = shift;
require DB_File;
tie my %h, "DB_File", encode_utf8($MailFile);
tie my %h, "DB_File", $MailFile;
foreach my $mail (split(/$FS/, UrlDecode(delete $h{UrlEncode($id)}))) {
my %subscriptions = map {$_=>1} split(/$FS/, UrlDecode($h{UrlEncode($mail)}));
delete $subscriptions{$id};
@@ -274,7 +274,7 @@ sub MailSubscription {
my $mail = shift;
return unless $mail;
require DB_File;
tie my %h, "DB_File", encode_utf8($MailFile);
tie my %h, "DB_File", $MailFile;
my @result = split(/$FS/, UrlDecode($h{UrlEncode($mail)}));
untie %h;
@result = sort @result;
@@ -303,7 +303,8 @@ sub DoMailSubscriptionList {
'<ul>';
}
require DB_File;
tie my %h, "DB_File", encode_utf8($MailFile);
tie my %h, "DB_File", $MailFile;
foreach my $encodedkey (sort keys %h) {
my @values = sort split(/$FS/, UrlDecode($h{$encodedkey}));
my $key = UrlDecode($encodedkey);
@@ -382,7 +383,7 @@ sub MailSubscribe {
return unless $mail and @pages;
# open the DB file
require DB_File;
tie my %h, "DB_File", encode_utf8($MailFile);
tie my %h, "DB_File", $MailFile;
# add to the mail entry
my %subscriptions = map {$_=>1} split(/$FS/, UrlDecode($h{UrlEncode($mail)}));
for my $id (@pages) {
@@ -441,7 +442,7 @@ sub MailUnsubscribe {
my ($mail, @pages) = @_;
return unless $mail and @pages;
require DB_File;
tie my %h, "DB_File", encode_utf8($MailFile);
tie my %h, "DB_File", $MailFile;
my %subscriptions = map {$_=>1} split(/$FS/, UrlDecode($h{UrlEncode($mail)}));
foreach my $id (@pages) {
delete $subscriptions{$id};
@@ -480,7 +481,8 @@ sub DoMailMigration {
$q->start_div({-class=>'content mailmigrate'});
require DB_File;
tie my %h, "DB_File", encode_utf8($MailFile);
tie my %h, "DB_File", $MailFile;
my $found = 0;
foreach my $key (keys %h) {
if (index($key, '@') != -1) {

View File

@@ -54,10 +54,10 @@ sub BisectAction {
sub BisectInitialScreen {
print GetFormStart(undef, 'get', 'bisect');
print GetHiddenValue('action', 'bisect');
my @disabledFiles = Glob("$ModuleDir/*.p[ml].disabled");
my @disabledFiles = bsd_glob("$ModuleDir/*.p[ml].disabled");
if (@disabledFiles == 0) {
print T('Test / Always enabled / Always disabled'), $q->br();
my @files = Glob("$ModuleDir/*.p[ml]");
my @files = bsd_glob("$ModuleDir/*.p[ml]");
for (my $i = 0; $i < @files; $i++) {
my $moduleName = fileparse($files[$i]);
my @disabled = ($moduleName eq 'module-bisect.pl' ? (-disabled=>'disabled') : ());
@@ -78,7 +78,7 @@ sub BisectProcess {
my ($isGood) = @_;
my $parameterHandover = '';
BisectEnableAll();
my @files = Glob("$ModuleDir/*.p[ml]");
my @files = bsd_glob("$ModuleDir/*.p[ml]");
for (my $i = @files - 1; $i >= 0; $i--) { # handle user choices
if (GetParam("m$i") eq 'on') {
$parameterHandover .= GetHiddenValue("m$i", GetParam("m$i"));
@@ -131,7 +131,7 @@ sub BisectProcess {
}
sub BisectEnableAll {
for (Glob("$ModuleDir/*.p[ml].disabled")) { # reenable all modules
for (bsd_glob("$ModuleDir/*.p[ml].disabled")) { # reenable all modules
my $oldName = $_;
s/\.disabled$//;
print Ts('Enabling %s', (fileparse($_))[0]), '...', $q->br() if $_[0];

View File

@@ -40,8 +40,8 @@ sub ModuleUpdaterAction {
if (GetParam('ok')) {
ModuleUpdaterApply();
} else {
Unlink(Glob("$TempDir/*.p[ml]")); # XXX is it correct to use $TempDir for such stuff? What if something else puts .pm files there?
for (Glob("$ModuleDir/*.p[ml]")) {
unlink bsd_glob("$TempDir/*.p[ml]"); # XXX is it correct to use $TempDir for such stuff? What if something else puts .pm files there?
for (bsd_glob("$ModuleDir/*.p[ml]")) {
my $curModule = fileparse($_);
ProcessModule($curModule);
}
@@ -58,7 +58,7 @@ sub ModuleUpdaterAction {
}
sub ModuleUpdaterApply {
for (Glob("$TempDir/*.p[ml]")) {
for (bsd_glob("$TempDir/*.p[ml]")) {
my $moduleName = fileparse($_);
if (move($_, "$ModuleDir/$moduleName")) {
print $q->strong("Module $moduleName updated successfully!"), $q->br();
@@ -66,7 +66,7 @@ sub ModuleUpdaterApply {
print $q->strong("Unable to replace module $moduleName: $!"), $q->br();
}
}
Unlink(Glob("$TempDir/*.p[ml]")); # XXX same as above
unlink bsd_glob("$TempDir/*.p[ml]"); # XXX same as above
print $q->br(), $q->strong('Done!');
}
@@ -81,14 +81,15 @@ sub ProcessModule {
. ' If this is your own module, please contribute it to Oddmuse!'), $q->br();
return;
}
open my $fh, ">:utf8", encode_utf8("$TempDir/$module") or die("Could not open file $TempDir/$module: $!");
open my $fh, ">", "$TempDir/$module" or die("Could not open file. $!");
utf8::encode($moduleData);
print $fh $moduleData;
close $fh;
my $diff = DoModuleDiff("$ModuleDir/$module", "$TempDir/$module");
if (not $diff) {
print $q->strong('This module is up to date, there is no need to update it.'), $q->br();
Unlink("$TempDir/$module");
unlink "$TempDir/$module";
return;
}
print $q->strong('There is a newer version of this module. Here is a diff:'), $q->br();
@@ -108,5 +109,7 @@ sub ProcessModule {
}
sub DoModuleDiff {
decode_utf8(`diff -U 3 -- \Q$_[0]\E \Q$_[1]\E`);
my $diff = `diff -U 3 -- \Q$_[0]\E \Q$_[1]\E`;
utf8::decode($diff); # needs decoding
return $diff;
}

View File

@@ -85,8 +85,9 @@ sub NamespacesInitVariables {
# Do this before changing the $DataDir and $ScriptName
if ($UsePathInfo) {
$Namespaces{$NamespacesMain} = $ScriptName . '/';
foreach my $name (Glob("$DataDir/*")) {
if (IsDir($name)
foreach my $name (bsd_glob("$DataDir/*")) {
utf8::decode($name);
if (-d $name
and $name =~ m|/($InterSitePattern)$|
and $name ne $NamespacesMain
and $name ne $NamespacesSelf) {
@@ -98,7 +99,8 @@ sub NamespacesInitVariables {
$NamespaceCurrent = '';
my $ns = GetParam('ns', '');
if (not $ns and $UsePathInfo) {
my $path_info = decode_utf8($q->path_info());
my $path_info = $q->path_info();
utf8::decode($path_info);
# make sure ordinary page names are not matched!
if ($path_info =~ m|^/($InterSitePattern)(/.*)?|
and ($2 or $q->keywords or NamespaceRequiredByParameter())) {
@@ -135,8 +137,13 @@ sub NamespacesInitVariables {
$StaticUrl .= UrlEncode($NamespaceCurrent) . '/'
if substr($StaticUrl,-1) eq '/'; # from static-copy.pl
$WikiDescription .= "<p>Current namespace: $NamespaceCurrent</p>";
$LastUpdate = Modified($IndexFile);
CreateDir($DataDir);
# override LastUpdate
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks)
= stat($IndexFile);
$LastUpdate = $mtime;
CreateDir($DataDir); # Create directory if it doesn't exist
ReportError(Ts('Cannot create %s', $DataDir) . ": $!", '500 INTERNAL SERVER ERROR')
unless -d $DataDir;
}
$Namespaces{$NamespacesSelf} = $ScriptName . '?';
# reinitialize
@@ -217,19 +224,19 @@ sub NewNamespaceGetRcLines { # starttime, hash of seen pages to use as a second
# opening a rcfile, compare the first timestamp with the
# starttime. If any rcfile exists with no timestamp before the
# starttime, we need to open its rcoldfile.
foreach my $rcfile (@rcfiles) {
open(my $F, '<:encoding(UTF-8)', encode_utf8($rcfile));
foreach my $file (@rcfiles) {
open(my $F, '<:encoding(UTF-8)', $file);
my $line = <$F>;
my ($ts) = split(/$FS/, $line); # the first timestamp in the regular rc file
my @new;
if (not $ts or $ts > $starttime) { # we need to read the old rc file, too
push(@new, GetRcLinesFor($rcoldfiles{$rcfile}, $starttime,\%match, \%following));
push(@new, GetRcLinesFor($rcoldfiles{$file}, $starttime,\%match, \%following));
}
push(@new, GetRcLinesFor($rcfile, $starttime, \%match, \%following));
push(@new, GetRcLinesFor($file, $starttime, \%match, \%following));
# strip rollbacks in each namespace separately
@new = StripRollbacks(@new);
# prepend the namespace to both pagename and author
my $ns = $namespaces{$rcfile};
my $ns = $namespaces{$file};
if ($ns) {
for (my $i = $#new; $i >= 0; $i--) {
# page id
@@ -434,6 +441,8 @@ sub NamespacesNewGetId {
# In this case GetId() will have set the parameter Test to 1.
# http://example.org/cgi-bin/wiki.pl/Test?rollback-1234=foo
# This doesn't set the Test parameter.
return if $id and $UsePathInfo and $id eq $NamespaceCurrent and not GetParam($id) and not GetParam('ns');
if ($UsePathInfo and $id eq $NamespaceCurrent and not GetParam($id) and not GetParam('ns')) {
$id = undef;
}
return $id;
}

View File

@@ -18,7 +18,7 @@ use v5.10;
AddModuleDescription('near-links.pl', 'Near Links');
our ($q, $Now, %AdminPages, %InterSite, $CommentsPrefix, $DataDir, $UseCache, @MyFooters, @MyMaintenance, @MyInitVariables, @Debugging, $InterSitePattern, @UserGotoBarPages, @IndexOptions);
our ($q, %AdminPages, %InterSite, $CommentsPrefix, $DataDir, $UseCache, @MyFooters, @MyMaintenance, @MyInitVariables, @Debugging, $InterSitePattern, @UserGotoBarPages, @IndexOptions);
=head1 Near Links
@@ -128,8 +128,7 @@ sub NearLinksMaintenance {
# skip if less than 12h old and caching allowed (the default)
foreach my $site (keys %NearSite) {
next if GetParam('cache', $UseCache) > 0
and IsFile("$NearDir/$site")
and $Now - Modified("$NearDir/$site") < 0.5;
and -f "$NearDir/$site" and -M "$NearDir/$site" < 0.5;
print $q->p(Ts('Getting page index file for %s.', $site));
my $data = GetRaw($NearSite{$site});
print $q->p($q->strong(Ts('%s returned no data, or LWP::UserAgent is not available.',

View File

@@ -35,7 +35,7 @@ $Action{clearcache} = \&DoClearCache;
sub DoClearCache {
print GetHeader('', QuoteHtml(T('Clearing Cache')), '');
Unlink(Glob("$NotFoundHandlerDir/*"));
unlink(bsd_glob("$NotFoundHandlerDir/*"));
print $q->p(T('Done.'));
PrintFooter();
}
@@ -45,7 +45,7 @@ sub DoClearCache {
sub ReadLinkDb {
return if $LinkDbInit;
$LinkDbInit = 1;
return if not IsFile($LinkFile);
return if not -f $LinkFile;
my $data = ReadFileOrDie($LinkFile);
map { my ($id, @links) = split; $LinkDb{$id} = \@links } split(/\n/, $data);
}
@@ -101,13 +101,13 @@ sub NewNotFoundHandlerSave {
my $id = $args[0];
OldNotFoundHandlerSave(@args);
RefreshLinkDb(); # for the open page
if (not IsDir($NotFoundHandlerDir)) {
CreateDir($NotFoundHandlerDir);
if (not -d $NotFoundHandlerDir) {
mkdir($NotFoundHandlerDir);
} elsif ($Page{revision} == 1) {
NotFoundHandlerCacheUpdate($id);
} else {
# unlink PageName, PageName.en, PageName.de, etc.
Unlink("$NotFoundHandlerDir/$id", Glob("$NotFoundHandlerDir/$id.[a-z][a-z]"));
unlink("$NotFoundHandlerDir/$id", bsd_glob("$NotFoundHandlerDir/$id.[a-z][a-z]"));
}
}
@@ -132,7 +132,7 @@ sub NotFoundHandlerCacheUpdate {
foreach my $source (keys %LinkDb) {
warn "Examining $source\n";
if (grep(/$target/, @{$LinkDb{$source}})) {
Unlink("$NotFoundHandlerDir/$source", Glob("$NotFoundHandlerDir/$source.[a-z][a-z]"));
unlink("$NotFoundHandlerDir/$source", bsd_glob("$NotFoundHandlerDir/$source.[a-z][a-z]"));
warn "Unlinking $source\n";
}
}

View File

@@ -41,7 +41,7 @@ sub PrivateWikiInit {
}
}
sub PadTo16Bytes { # use this only on bytes (after encode_utf8)
sub PadTo16Bytes { # use this only on UTF-X strings (after utf8::encode)
my ($data, $minLength) = @_;
my $endBytes = length($data) % 16;
$data .= "\0" x (16 - $endBytes) if $endBytes != 0;
@@ -62,7 +62,8 @@ sub NewPrivateWikiReadFile {
$q->p($errorMessage)) if not UserIsEditor();
PrivateWikiInit();
my $file = shift;
if (open(my $IN, '<', encode_utf8($file))) {
utf8::encode($file); # filenames are bytes!
if (open(my $IN, '<', $file)) {
local $/ = undef; # Read complete files
my $data = <$IN>;
close $IN;
@@ -71,7 +72,8 @@ sub NewPrivateWikiReadFile {
$data = $cipher->decrypt(substr $data, 16);
my $copy = $data; # copying is required, see https://github.com/briandfoy/crypt-rijndael/issues/5
$copy =~ s/\0+$//;
return (1, decode_utf8($copy));
utf8::decode($copy);
return (1, $copy);
}
return (0, '');
}
@@ -84,12 +86,13 @@ sub NewPrivateWikiWriteStringToFile {
$q->p($errorMessage)) if not UserIsEditor();
PrivateWikiInit();
my ($file, $string) = @_;
open(my $OUT, '>', encode_utf8($file))
or ReportError(Ts('Cannot write %s', $file) . ": $!", '500 INTERNAL SERVER ERROR');
utf8::encode($file);
open(my $OUT, '>', $file) or ReportError(Ts('Cannot write %s', $file) . ": $!", '500 INTERNAL SERVER ERROR');
utf8::encode($string);
my $iv = $random->random_bytes(16);
$cipher->set_iv($iv);
print $OUT $iv;
print $OUT $cipher->encrypt(PadTo16Bytes(encode_utf8($string)));
print $OUT $cipher->encrypt(PadTo16Bytes $string);
close($OUT);
}
@@ -106,9 +109,9 @@ sub AppendStringToFile {
*RefreshIndex = \&NewPrivateWikiRefreshIndex;
sub NewPrivateWikiRefreshIndex {
if (not IsFile($IndexFile)) { # Index file does not exist yet, this is a new wiki
if (not -f $IndexFile) { # Index file does not exist yet, this is a new wiki
my $fh;
open($fh, '>', encode_utf8($IndexFile)) or die "Unable to open file $IndexFile : $!"; # 'touch' equivalent
open($fh, '>', $IndexFile) or die "Unable to open file $IndexFile : $!"; # 'touch' equivalent
close($fh) or die "Unable to close file : $IndexFile $!";
return;
}
@@ -160,7 +163,8 @@ sub GetPrivatePageFile {
}
$cipher->set_iv($iv);
# We cannot use full byte range because of the filesystem limits
my $returnName = unpack "H*", $iv . $cipher->encrypt(PadTo16Bytes(encode_utf8($id), 96)); # to hex string
utf8::encode($id);
my $returnName = unpack "H*", $iv . $cipher->encrypt(PadTo16Bytes $id, 96); # to hex string
return $returnName;
}
@@ -212,13 +216,14 @@ sub DoDiff { # Actualy call the diff program
my $oldName = "$TempDir/old";
my $newName = "$TempDir/new";
RequestLockDir('diff') or return '';
$LockCleaners{'diff'} = sub { Unlink($oldName) if IsFile($oldName); Unlink($newName) if IsFile($newName); };
$LockCleaners{'diff'} = sub { unlink $oldName if -f $oldName; unlink $newName if -f $newName; };
OldPrivateWikiWriteStringToFile($oldName, $_[0]); # CHANGED Here we use the old sub!
OldPrivateWikiWriteStringToFile($newName, $_[1]); # CHANGED
my $diff_out = decode_utf8(`diff -- \Q$oldName\E \Q$newName\E`);
my $diff_out = `diff -- \Q$oldName\E \Q$newName\E`;
utf8::decode($diff_out); # needs decoding
$diff_out =~ s/\n\K\\ No newline.*\n//g; # Get rid of common complaint.
# CHANGED We have to unlink the files because we don't want to store them in plaintext!
Unlink($oldName, $newName); # CHANGED
unlink $oldName, $newName; # CHANGED
ReleaseLockDir('diff');
return $diff_out;
}
@@ -232,14 +237,15 @@ sub MergeRevisions { # merge change from file2 to file3 into file1
CreateDir($TempDir);
RequestLockDir('merge') or return T('Could not get a lock to merge!');
$LockCleaners{'merge'} = sub { # CHANGED
Unlink($name1) if IsFile($name1); Unlink($name2) if IsFile($name2); Unlink($name3) if IsFile($name3);
unlink $name1 if -f $name1; unlink $name2 if -f $name2; unlink $name3 if -f $name3;
};
OldPrivateWikiWriteStringToFile($name1, $file1); # CHANGED
OldPrivateWikiWriteStringToFile($name2, $file2); # CHANGED
OldPrivateWikiWriteStringToFile($name3, $file3); # CHANGED
my ($you, $ancestor, $other) = (T('you'), T('ancestor'), T('other'));
my $output = decode_utf8(`diff3 -m -L \Q$you\E -L \Q$ancestor\E -L \Q$other\E -- \Q$name1\E \Q$name2\E \Q$name3\E`);
Unlink($name1, $name2, $name3); # CHANGED unlink temp files -- we don't want to store them in plaintext!
my $output = `diff3 -m -L \Q$you\E -L \Q$ancestor\E -L \Q$other\E -- \Q$name1\E \Q$name2\E \Q$name3\E`;
utf8::decode($output); # needs decoding
unlink $name1, $name2, $name3; # CHANGED unlink temp files -- we don't want to store them in plaintext!
ReleaseLockDir('merge');
return $output;
}

View File

@@ -52,9 +52,11 @@ sub DoPygmentize {
RequestLockDir('pygmentize') or return '';
WriteStringToFile("$TempDir/pygmentize", $contents);
my $output = decode_utf8(`pygmentize $lexer -f html -O encoding=utf8 $args -- \Q$TempDir/pygmentize\E 2>&1`);
my $output = `pygmentize $lexer -f html -O encoding=utf8 $args -- \Q$TempDir/pygmentize\E 2>&1`;
ReleaseLockDir('pygmentize');
utf8::decode($output);
if ($?) {
$output = $q->p($q->strong($output)) # "sh: pygmentize: command not found"
. $q->pre($contents);

View File

@@ -65,7 +65,7 @@ sub RefererNewDeletePage {
return $status if $status; # this would be the error message
my $id = shift;
my $fname = GetRefererFile($id);
Unlink($fname) if (IsFile($fname));
unlink($fname) if (-f $fname);
return ''; # no error
}
@@ -79,7 +79,7 @@ sub GetRefererFile {
sub ReadReferers {
my $file = GetRefererFile(shift);
%Referers = ();
if (IsFile($file)) {
if (-f $file) {
my ($status, $data) = ReadFile($file);
%Referers = split(/$FS/, $data, -1) if $status;
}
@@ -154,7 +154,7 @@ sub PageContentToTitle {
$title =~ s!\s+! !g;
$title =~ s!^ !!;
$title =~ s! $!!;
$title = substr($title, 0, $RefererTitleLimit) . "..."
$title = substring($title, 0, $RefererTitleLimit) . "..."
if length($title) > $RefererTitleLimit;
return $title;
}
@@ -187,7 +187,7 @@ sub WriteReferers {
CreateDir($RefererDir);
WriteStringToFile($file, $data);
} else {
Unlink($file); # just try it, doesn't matter if it fails
unlink $file; # just try it, doesn't matter if it fails
}
ReleaseLockDir('refer_' . $id);
}

View File

@@ -32,7 +32,7 @@ my $dummy = RelationRead();
sub RelationRead {
# return scalar(@RelationLinking) if (scalar(@RelationLinking));
open (my $RRR, '<', encode_utf8("$DataDir/$referencefile")) || return(0);
open (my $RRR, '<', "$DataDir/$referencefile") || return(0);
while (<$RRR>) {
chomp;
my ($a,$b,$c) = split(';');
@@ -172,7 +172,7 @@ $Action{'updaterelates'} = sub {
else {
print "no new source<br />\n";
}
open (my $RRR, '>', encode_utf8("$DataDir/$referencefile"));
open (my $RRR, '>', "$DataDir/$referencefile");
print "<br />\n";
foreach my $t (@RelationLinking) {
next unless (defined($t));

View File

@@ -30,9 +30,9 @@ $SmileyUrlPath = '/pics'; # path where all the smileys can be found (URL)
push(@MyInitVariables, \&SmileyDirInit);
sub SmileyDirInit {
if (opendir(DIR, encode_utf8($SmileyDir))) {
if (opendir(DIR, $SmileyDir)) {
map {
if (/^((.*)\.$ImageExtensions$)/ and IsFile("$SmileyDir/$_")) {
if (/^((.*)\.$ImageExtensions$)/ and -f "$SmileyDir/$_") {
my $regexp = quotemeta("{$2}");
$Smilies{$regexp} = "$SmileyUrlPath/$1";
}

View File

@@ -81,7 +81,7 @@ sub StaticScriptLink {
my %params;
if ($action !~ /=/) {
# the page might not exist, eg. if called via GetAuthorLink
$params{'-href'} = StaticFileName($action) if $IndexHash{UrlDecode($action)};
$params{-href} = StaticFileName($action) if $IndexHash{$action};
}
$params{'-class'} = $class if $class;
$params{'-name'} = UrlEncode($name) if $name;
@@ -131,25 +131,23 @@ sub StaticFileName {
sub StaticWriteFile {
my ($id, $html) = @_;
my $raw = GetParam('raw', 0);
my $filename = StaticFileName($id);
OpenPage($id);
my ($mimetype, $encoding, $data) =
$Page{text} =~ /^\#FILE ([^ \n]+) ?([^ \n]*)\n(.*)/s;
my $filename = StaticFileName($id);
my $file = "$StaticDir/$filename";
$Page{text} =~ /^\#FILE ([^ \n]+) ?([^ \n]*)\n(.*)/s;
open(my $fh, '>', "$StaticDir/$filename")
or ReportError(Ts('Cannot write %s', $filename));
if ($data) {
open(my $fh, '>', encode_utf8($file))
or ReportError(Ts('Cannot write %s', $filename));
binmode($fh);
StaticFile($id, $fh, $mimetype, $data);
close($fh);
} elsif ($html) {
open(my $fh, '>:encoding(UTF-8)', encode_utf8($file))
or ReportError(Ts('Cannot write %s', $filename));
binmode($fh, ':encoding(UTF-8)');
StaticHtml($id, $fh);
close($fh);
} else {
print "no data for ";
}
ChangeMod(0644,"$StaticDir/$filename");
close($fh);
chmod 0644,"$StaticDir/$filename";
print $filename, $raw ? "\n" : $q->br();
}
@@ -281,7 +279,7 @@ sub StaticDeleteFile {
%StaticMimeTypes = StaticMimeTypes() unless %StaticMimeTypes;
# we don't care if the files or $StaticDir don't exist -- just delete!
for my $f (map { "$StaticDir/$id.$_" } (values %StaticMimeTypes, 'html')) {
Unlink($f); # delete copies with different extensions
unlink $f; # delete copies with different extensions
}
}

View File

@@ -33,7 +33,7 @@ $StaticUrl = '' unless defined $StaticUrl; # change this!
$StaticAlways = 0 unless defined $StaticAlways;
# 1 = uploaded files only, 2 = all pages
my $StaticMimeTypes = '/etc/http/mime.types'; # all-ASCII characters
my $StaticMimeTypes = '/etc/http/mime.types';
my %StaticFiles;
my $StaticAction = 0; # Are we doing action or not?
@@ -133,11 +133,12 @@ sub StaticWriteFile {
my $id = shift;
my $raw = GetParam('raw', 0);
my $html = GetParam('html', 1);
my $filename = StaticFileName($id);
OpenPage($id);
my ($mimetype, $data) = $Page{text} =~ /^\#FILE ([^ \n]+)\n(.*)/s;
return unless $html or $data;
my $filename = StaticFileName($id);
open(my $F, '>', encode_utf8("$StaticDir/$filename")) or ReportError(Ts('Cannot write %s', $filename));
open(my $F, '>', "$StaticDir/$filename") or ReportError(Ts('Cannot write %s', $filename));
if ($data) {
StaticFile($id, $mimetype, $data, $F);
} elsif ($html) {
@@ -240,7 +241,7 @@ sub StaticDeleteFile {
%StaticMimeTypes = StaticMimeTypes() unless %StaticMimeTypes;
# we don't care if the files or $StaticDir don't exist -- just delete!
for my $f (map { "$StaticDir/$id.$_" } (values %StaticMimeTypes, 'html')) {
Unlink($f); # delete copies with different extensions
unlink $f; # delete copies with different extensions
}
}

View File

@@ -64,7 +64,7 @@ Example:
=cut
our ($q, $Now, %Action, %Page, $FreeLinkPattern, @MyInitVariables, @MyRules, @MyAdminCode, $DataDir, $ScriptName);
our ($q, %Action, %Page, $FreeLinkPattern, @MyInitVariables, @MyRules, @MyAdminCode, $DataDir, $ScriptName);
our ($TagUrl, $TagFeed, $TagFeedIcon, $TagFile);
push(@MyInitVariables, \&TagsInit);
@@ -84,7 +84,7 @@ sub TagsGetLink {
sub TagReadHash {
require Storable;
return %{ Storable::retrieve(encode_utf8($TagFile)) } if IsFile($TagFile);
return %{ Storable::retrieve($TagFile) } if -f $TagFile;
}
@@ -92,7 +92,7 @@ sub TagReadHash {
sub TagWriteHash {
my $h = shift;
require Storable;
return Storable::store($h, encode_utf8($TagFile));
return Storable::store($h, $TagFile);
}
push(@MyRules, \&TagsRule);
@@ -338,9 +338,7 @@ Example:
$Action{reindex} = \&DoTagsReindex;
sub DoTagsReindex {
if (not UserIsAdmin()
and IsFile($TagFile)
and $Now - Modified($TagFile) < 0.5) {
if (!UserIsAdmin() && (-f $TagFile) && ((-M $TagFile) < 0.5)) {
ReportError(T('Rebuilding index not done.'), '403 FORBIDDEN',
0, T('(Rebuilding the index can only be done once every 12 hours.)'));
}

View File

@@ -52,12 +52,12 @@ sub NewDoBrowseRequest {
# limit the script to a maximum of $InstanceThrottleLimit instances
sub DoInstanceThrottle {
my @pids = Glob($InstanceThrottleDir."/*");
my @pids = bsd_glob($InstanceThrottleDir."/*");
# Go over all pids: validate each pid by sending signal 0, unlink
# pidfile if pid does not exist and return 0. Count the number of
# zeros (= removed files = zombies) with grep.
my $zombies = grep /^0$/,
(map {/(\d+)$/ and kill 0,$1 or Unlink($_) and 0} @pids);
(map {/(\d+)$/ and kill 0,$1 or unlink and 0} @pids);
if (scalar(@pids)-$zombies >= $InstanceThrottleLimit) {
ReportError(Ts('Too many instances. Only %s allowed.',
$InstanceThrottleLimit),
@@ -80,5 +80,5 @@ sub CreatePidFile {
sub RemovePidFile {
my $file = "$InstanceThrottleDir/$$";
# not fatal
Unlink($file);
unlink $file;
}

View File

@@ -102,7 +102,7 @@ sub ThumbNailSupportRule {
{
if (!IsFile("$ThumbnailCacheDir/$id/$size"))
if (! -e "$ThumbnailCacheDir/$id/$size")
{
GenerateThumbNail ($id, $size);
}
@@ -209,8 +209,10 @@ sub GenerateThumbNail {
my $filename = $ThumbnailTempDir . "/odd" . $id . "_" . $size;
# Decode the original image to a temp file
open(my $FD, '>', encode_utf8($filename)) or ReportError(Ts("Could not open %s for writing whilst trying to save image before creating thumbnail. Check write permissions.",$filename), '500 INTERNAL SERVER ERROR');
open(my $FD, '>', $filename) or ReportError(Ts("Could not open %s for writing whilst trying to save image before creating thumbnail. Check write permissions.",$filename), '500 INTERNAL SERVER ERROR');
binmode($FD);
print $FD MIME::Base64::decode($data);
close($FD);
@@ -247,7 +249,7 @@ sub GenerateThumbNail {
ReportError(Ts("Failed to parse %s.", $convert), '500 INTERNAL SERVER ERROR');
}
Unlink($filename);
unlink($filename);
# save tag to page
#$Page{'thumbnail_' . $size} = '#FILE ' . $type . ' created=' . $Now . ' revision=' . $Page{'revision'} . ' size=' . $scaled_size_x . 'x' . $scaled_size_y . "\n" . $thumbnail_data;

View File

@@ -229,10 +229,18 @@ my $TocCommentPattern = qr~\Q<!-- toc\E.*?\Q -->\E~;
# appropriate, and then printed at the very end.
sub NewTocApplyRules {
my ($html, $blocks, $flags);
$html = ToString(sub{
# pass arguments on to OldTocApplyRules given that ToString takes a code ref
{
local *STDOUT;
my $html_unfixed;
open( STDOUT, '>', \$html_unfixed) or die "Can't open memory file: $!";
binmode STDOUT, ":encoding(UTF-8)";
($blocks, $flags) = OldTocApplyRules(@_);
}, @_);
close STDOUT;
utf8::decode($blocks);
# do not delete!
$html = $html_unfixed; # this is a workarond for perl bug
utf8::decode($html); # otherwise UTF8 characters are SOMETIMES not decoded.
}
# If there are at least two HTML headers on this page, insert a table of
# contents.
if ($TocHeaderNumber > 2) {

File diff suppressed because it is too large Load Diff

View File

@@ -89,9 +89,9 @@ Ungültige Seite %s (Darf nicht mit .lck enden)
Invalid Page %s
Ungültige Seite %s
There are no comments, yet. Be the first to leave a comment!
Es gibt noch keine Kommentare, sei der Erste der einen hinterlässt!
Welcome!
Willkommen!
This page does not exist, but you can %s.
Diese Seite gibt es nicht, aber du kannst %s.
create it now
@@ -343,9 +343,9 @@ Die Sperre wurde %s gesetzt.
Maybe the user running this script is no longer allowed to remove the lock directory?
Vielleicht darf der user, welcher dieses script ausführt, das Sperr-Verzeichnis nicht löschen?
Sometimes locks are left behind if a job crashes.
Manchmal bleiben Sperren erhalten, wenn ein Prozess abbricht.
After ten minutes, you could try to unlock the wiki.
Nach zehn Minuten kann versucht werden, das Wiki zu entsperren.
This operation may take several seconds...
Das könnte einige Sekunden dauern...
Forced unlock of %s lock.
@@ -435,7 +435,7 @@ Grund unbekannt.
%s pages found.
%s Seiten gefunden.
Preview: %s
Vorschau: %s
Replaced: %s
Ersetzt: %s
Search for: %s
@@ -786,7 +786,7 @@ Kommentar hinzufügen
ordinary changes
normale Änderungen
%s days
%s Tage
################################################################################
# modules/edit-paragraphs.pl
################################################################################
@@ -1398,9 +1398,9 @@ Portrait
# modules/preview.pl
################################################################################
Pages with changed HTML
Seiten mit geändertem HTML
Preview changes in HTML output
Vorschau der Änderungen der HTML-Ausgabe
################################################################################
# modules/private-pages.pl
################################################################################

View File

@@ -1,162 +0,0 @@
# Copyright (C) 2016 Alex Schroeder <alex@gnu.org>
#
# 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 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
#
# The source for the data is unknown.
use utf8;
use strict;
AddModuleDescription('national-days-de.pl', 'Special Days') if defined &AddModuleDescription;
our %SpecialDays = (
'1-1' => 'Sudán, Día Nacional, Haití, Día de Independencia, Cuba, Día de Liberación',
'1-4' => 'Myanmar, Día de Independencia',
'1-9' => 'Islas de Mariana del Norte, Día Nacional',
'1-26' => 'Australia, Día de Australia',
'1-31' => 'Nauru, Día Nacional',
'2-4' => 'Sri Lanka, Día de Conmemoración de la Independencia',
'2-6' => 'Nueva Zelanda, Día de Waitangi',
'2-7' => 'Grenada, Día de Independencia',
'2-11' => 'Irán, Victoria de la Revolución Islámica en Irán',
'2-16' => 'Lituania, Día de Independencia',
'2-18' => 'Gambia, Día Nacional',
'2-22' => 'Santa Lucía, Día Nacional',
'2-23' => 'Guyana, Día Nacional, Brunei Darussalam, Día Nacional',
'2-24' => 'Estonia, Día de Independencia',
'2-25' => 'Kuwait, Día Nacional',
'2-27' => 'República Dominicana, Día de Independencia',
'3-3' => 'Bulgaria, Día Nacional',
'3-6' => 'Ghana, Día de Independencia',
'3-12' => 'Mauritius, Día Nacional',
'3-17' => 'Irlanda, Día de San Patricio',
'3-20' => 'Tunisia, Aniversario de la Independencia de la República de Tunisia',
'3-21' => 'Namibia, Día de Independencia',
'3-23' => 'Pakistán, Día Nacional',
'3-25' => 'Grecia, Día de Independencia',
'3-26' => 'Bangladesh, Día de Independencia',
'4-4' => 'Senegal, Día de Independencia',
'4-16' => 'Dinamarca, Cumpleaños de la Reina',
'4-17' => 'Syria, Día Nacional',
'4-18' => 'Zimbabwe, Día Nacional',
'4-19' => 'Sierra Leona, Día de la República',
'4-26' => 'Tanzania, Día de la Unión, Israel, Día de Independencia',
'4-27' => 'República Federal de Yugoslavia, Día Nacional, Togo, Togolais Día Nacional, Suráfrica, Día de la Libertad',
'4-30' => 'Los Países Bajos, Cumpleaños Oficial de Su Majestad la Reina Beatriz',
'5-1' => 'Islas Marshall, Día Nacional',
'5-3' => 'Polonia, Día Nacional',
'5-9' => 'Unión Europea, Día de Europa',
'5-14' => 'Paraguay, Día Nacional',
'5-17' => 'Noruega, Día de la Constitución',
'5-20' => 'Camerún, Día Nacional',
'5-22' => 'Yemen, Día Nacional',
'5-24' => 'Eritrea, Día de Independencia',
'5-25' => 'Jordania, Día de Independencia, Argentina, Día Nacional',
'5-26' => 'Georgia, Día Nacional',
'5-28' => 'Etiopía, Día Nacional, Azerbaijan, Día Nacional',
'6-1' => 'Samoa, Día de Independencia',
'6-2' => 'Italia, Fundación de la República',
'6-4' => 'Tonga, Día de Emancipación',
'6-6' => 'Suecia, Día Nacional',
'6-10' => 'Portugal, Día de Portugal, Día de Camões y Día de las Comunidades Portuguesas',
'6-12' => 'Filipinas, Día de Independencia, Rusia, Día Nacional',
'6-17' => 'Islandia, Día Nacional',
'6-18' => 'Seychelles, Día Nacional',
'6-23' => 'Luxemburgo, Día Nacional y Cumpleaños Oficial de H.R.H. el Gran Duque',
'6-25' => 'Croacia, Día Nacional, Eslovenia, Día Nacional, Mozambique, Día de Independencia',
'6-26' => 'Madagascar, Día Nacional',
'6-27' => 'Djibouti, Día de Independencia',
'6-30' => 'República Democrática del Congo, Día de Independencia',
'7-1' => 'Burundi, Día Nacional, Canadá, Día de Canadá',
'7-3' => 'Belarus, Día Nacional',
'7-4' => 'Estados Unidos de América, Día de Independencia',
'7-5' => 'Rwanda, Día de Liberación, Cape Verde, Día Nacional, Venezuela, Día Nacional',
'7-6' => 'Malawi, Día Nacional, Comoros, Día Nacional',
'7-7' => 'Nepal, Cumpleaños del Rey y Día Nacional, Islas Solomon, Día Nacional',
'7-10' => 'Bahamas, Día de Independencia',
'7-11' => 'Mongolia, Aniversario de la Revolución de las Gentes de Mongolia',
'7-12' => 'Sao Tome & Principe, Día Nacional, Kiribati, Día Nacional',
'7-14' => 'Francia, Día de Bastillas',
'7-17' => 'Irak, Día Nacional',
'7-20' => 'Colombia, Día Nacional',
'7-21' => 'Bélgica, Ascenso del Rey Leopoldo I (1831)',
'7-23' => 'Egipto, Aniversario de la Revolución',
'7-26' => 'Liberia, Día Nacional, Maldives, Día Nacional',
'7-28' => 'Perú, Día de Independencia',
'7-30' => 'Vanuatu, Día de Independencia, Marruecos, Festival del Trono',
'8-1' => 'Benin, Día Nacional, Suiza, Fundación de la Confederación Suiza',
'8-4' => 'Islas Cook, Día Nacional',
'8-5' => 'Jamaica, Día Nacional',
'8-6' => 'Bolivia, Día de Independencia',
'8-7' => 'Cote D\'Ivoire, Día Nacional',
'8-9' => 'Singapur, Día Nacional',
'8-10' => 'Ecuador, Día Nacional',
'8-11' => 'Chad, Día Nacional',
'8-15' => 'República de Korea, Día Nacional, Liechtenstein, Día Nacional, India, Día Nacional, República del Congo, Día de Independencia',
'8-17' => 'Indonesia, Proclamación de Independencia, Gabon, Día Nacional',
'8-19' => 'Afganistán, Día de Independencia',
'8-20' => 'Hungaria, Día Nacional',
'8-24' => 'Ucrania, Día Nacional',
'8-25' => 'Uruguay, Día de Independencia',
'8-27' => 'Moldova, Día Nacional',
'8-31' => 'Kyrgyzstan, Día Nacional, Trinidad y Tobago, Día Nacional, Malasia, Día Nacional',
'9-1' => 'Uzbekistán, Día Nacional, Eslovakia, Día de la Constitución',
'9-2' => 'Vietnam, Día Nacional, Libyan Arab Jamahiriya, Día Nacional',
'9-3' => 'Qatar, Día Nacional, San Marino, Día de Fundación Nacional',
'9-6' => 'Swaziland, Día Nacional',
'9-7' => 'Brazil, Día de Independencia',
'9-8' => 'Andorra, Día Nacional, Antigua República de Macedonia Yugoslava, Día de Independencia',
'9-9' => 'República Democrática de Korea, Día Nacional, Tajikistán, Día Nacional',
'9-15' => 'Guatemala, Día de Independencia, Honduras, Día de Independencia, El Salvador, Día de Independencia, Costa Rica, Día de Independencia, Nicaragua, Día de Independencia',
'9-16' => 'Papua Nueva Guinea, Día de Independencia, Méjico, Proclamación de Independencia',
'9-18' => 'Chile, Día Nacional',
'9-19' => 'St Kitts Nevis, Día de Independencia',
'9-21' => 'Belize, Día Nacional, Malta, Día de Independencia, Armenia, Día Nacional',
'9-22' => 'República de Mali, Proclamación de Independencia',
'9-23' => 'Arabia Saudita, Día Nacional',
'9-24' => 'Guinea Bissau, Día de Independencia',
'9-30' => 'Botswana, Día Nacional',
'10-1' => 'Guinea, Día de Independencia, China, Día Nacional, Palau, Día Nacional, Tuvalu, Día de Independencia, Cyprus, Día Nacional, Nigeria, Día Nacional',
'10-3' => 'Alemania, Día Nacional',
'10-4' => 'Lesotho, Día Nacional',
'10-9' => 'Uganda, Día de Independencia',
'10-10' => 'Fiji, Día Nacional',
'10-12' => 'Guinea Ecuatorial, Día de Independencia, España, Día Nacional',
'10-19' => 'Niue, Día Nacional',
'10-22' => 'Holy See, Aniversario, Ministerio Pontífice de Su Santidad el Papa Juan Pablo II',
'10-24' => 'Zambia, Día de Independencia',
'10-26' => 'Austria, Día Nacional',
'10-27' => 'Turkmenistán, Día Nacional, San Vincente y las Granadinas, Día Nacional',
'10-28' => 'República Checa, Día Nacional',
'10-29' => 'Turquía, Día de la República',
'11-1' => 'Antigua y Barbuda, Día Nacional, Algeria, Aniversario de la Revolución',
'11-3' => 'Panamá, Día Nacional, Dominica, Día Nacional, Estados Federados de Micronesia, Día Nacional',
'11-9' => 'Cambodia, Día de Independencia',
'11-11' => 'Angola, Día Nacional',
'11-18' => 'Omán, Día Nacional, Latvia, Proclamación de Independencia',
'11-19' => 'Mónaco, Día Nacional',
'11-22' => 'Lebanon, Día de Independencia',
'11-25' => 'Bosnia y Herzegovina, Día del Estado, Surinam, Día Nacional',
'11-28' => 'Mauritania, Día Nacional, Albania, Día Nacional',
'11-30' => 'Barbados, Día de Independencia',
'12-1' => 'Rumania, Día Nacional, República de África Central, Día Nacional',
'12-2' => 'Emiratos Árabes Unidos, Día Nacional, Laos, Día Nacional',
'12-5' => 'Tailandia, Cumpleaños del Rey',
'12-6' => 'Finlandia, Día de Independencia',
'12-11' => 'Burkina Faso, Día Nacional',
'12-12' => 'Kenya, Día de Jamhuri',
'12-16' => 'Bahrain, Día Nacional, Kazakhstán, Día Nacional',
'12-17' => 'Bután, Día Nacional',
'12-18' => 'Nigeria, Día Nacional',
'12-23' => 'Japón, Cumpleaños del Emperador',
);

View File

@@ -691,6 +691,11 @@ Clustermap
Pages without a Cluster
################################################################################
# modules/comment-div-wrapper.pl
################################################################################
Comments:
################################################################################
# modules/commentcount.pl
################################################################################
@@ -698,11 +703,6 @@ Comments on
Comment on
################################################################################
# modules/comment-div-wrapper.pl
################################################################################
Comments:
################################################################################
# modules/compilation.pl
################################################################################
@@ -1239,7 +1239,7 @@ Test / Always enabled / Always disabled
Start
Bisecting proccess is already active.
Bisection proccess is already active.
Stop
@@ -1280,11 +1280,6 @@ You linked more than %s times to the same domain. It would seem that only a spam
Namespaces
################################################################################
# modules/nearlink-create.pl
################################################################################
(create locally)
################################################################################
# modules/near-links.pl
################################################################################
@@ -1304,6 +1299,11 @@ EditNearLinks
The same page on other sites:
################################################################################
# modules/nearlink-create.pl
################################################################################
(create locally)
################################################################################
# modules/no-question-mark.pl
################################################################################
@@ -1662,5 +1662,4 @@ Edit %s.
################################################################################
Tags:
#
END_OF_TRANSLATION

View File

@@ -62,40 +62,44 @@ sub DoUpgrade {
print GetHeader('', T('Upgrading Database')),
$q->start_div({-class=>'content upgrade'});
if (IsFile($IndexFile)) {
Unlink($IndexFile);
if (-e $IndexFile) {
unlink $IndexFile;
}
print "<p>Renaming files...";
for my $ns ('', keys %InterSite) {
next unless IsDir("$DataDir/$ns");
next unless -d "$DataDir/$ns";
print "<br />\n<strong>$ns</strong>" if $ns;
for my $dirname ($PageDir, $KeepDir, $RefererDir, $JoinerDir, $JoinerEmailDir) {
next unless $dirname;
my $dir = $dirname; # copy in order not to modify the original
$dir =~ s/^$DataDir/$DataDir\/$ns/ if $ns;
for my $old (Glob("$dir/*/*"), Glob("$dir/*/.*")) {
for my $old (bsd_glob("$dir/*/*"), bsd_glob("$dir/*/.*")) {
next if $old =~ /\/\.\.?$/;
print "<br />\n$old";
my $oldname = $old;
utf8::decode($oldname);
print "<br />\n$oldname";
my $new = $old;
$new =~ s!/([A-Z]|other)/!/!;
if ($old eq $new) {
print " does not fit the pattern!";
} elsif (not Rename($old, $new)) {
print " → $new failed!";
} elsif (not rename $old, $new) {
my $newname = $new;
utf8::decode($newname);
print " → $newname failed!";
}
}
for my $subdir (grep(/\/([A-Z]|other)$/, Glob("$dir/*"), Glob("$dir/.*"))) {
for my $subdir (grep(/\/([A-Z]|other)$/, bsd_glob("$dir/*"), bsd_glob("$dir/.*"))) {
next if substr($subdir, -2) eq '/.' or substr($subdir, -3) eq '/..';
RemoveDir($subdir); # ignore errors
rmdir $subdir; # ignore errors
}
}
}
print $q->end_p();
if (Rename("$ModuleDir/upgrade.pl", "$ModuleDir/upgrade.done")) {
if (rename "$ModuleDir/upgrade.pl", "$ModuleDir/upgrade.done") {
print $q->p(T("Upgrade complete."))
} else {
print $q->p(T("Upgrade complete. Please remove $ModuleDir/upgade.pl, now."))

View File

@@ -60,7 +60,7 @@ for (my $i=0; $q->param("fileToUpload$i"); $i++) {
$curFilename = $name . $extension;
while (IsFile("$uploadDir/$curFilename")) { # keep adding random characters until we get unique filename
while (-e "$uploadDir/$curFilename") { # keep adding random characters until we get unique filename
squeak 'Error: Cannot save file with such filename' if length $curFilename >= 150; # cannot find available filename after so many attempts
$name .= $additionalChars[rand @additionalChars];
$curFilename = $name . $extension;
@@ -71,12 +71,14 @@ for (my $i=0; $q->param("fileToUpload$i"); $i++) {
} else {
squeak 'Error: Filename contains invalid characters'; # this should not happen
}
open(my $LOGFILE, '>>', encode_utf8($logFile)) or squeak "$!";
open(my $LOGFILE, '>>', $logFile) or squeak "$!";
print $LOGFILE $q->param("key") . ' ' . $ENV{REMOTE_ADDR} . ' ' . $curFilename . "\n";
close $LOGFILE;
my $uploadFileHandle = $q->upload("fileToUpload$i");
open(my $UPLOADFILE, '>', encode_utf8("$uploadDir/$curFilename")) or squeak "$!";
open(my $UPLOADFILE, '>', "$uploadDir/$curFilename") or squeak "$!";
binmode $UPLOADFILE;
while (<$uploadFileHandle>) {
print $UPLOADFILE;

View File

@@ -394,7 +394,7 @@ sub propfind {
sub propfind_data {
my %data = ();
my $update = Modified($OddMuse::WebDavCache);
my $update = (stat($OddMuse::WebDavCache))[9];
if ($update and $OddMuse::LastUpdate == $update) {
my $data = OddMuse::ReadFileOrDie($OddMuse::WebDavCache);
map {

View File

@@ -1,188 +0,0 @@
#!/usr/bin/env perl6
use Net::IRC::Bot;
use Net::IRC::Modules::Autoident;
use Net::IRC::Modules::Tell;
use Net::IRC::CommandHandler;
sub wikiLink($page is copy) {
$page ~~ s:g/\s/_/; # quick and dirty
returnhttps://oddmuse.org/wiki/$page;
}
class Intermap {
has $.intermapLink is rw = https://oddmuse.org/wiki/Local_Intermap?raw=1;
has %!intermap;
method update {
# TODO https breaks HTTP::UserAgent, workaround with curl
my $proc = run(curl, $!intermapLink, :out);
my $text = $proc.out.slurp-rest;
$proc.out.close; # RT #126561
return False unless $proc;
for $text ~~ m:global ^^ \h+ $<name>=\S+ \s+ $<value>=.+? $$ {
%!intermap{~$_<name>} = ~$_<value>; # TODO map!
}
return True;
}
method said ($e) {
self.update if not %!intermap or $e.what ~~ / update intermap /; # lazy init
for $e.what ~~ m:global $<name>=<-[\s :]>+ : $<value>=\S+ { # quick and dirty
next unless %!intermap{.<name>}:exists;
my $link = %!intermap{~.<name>};
my $replacement = $_<value>;
$link ~~ s{ \%s | $ } = $replacement;
$e.msg: $link;
}
}
}
class Pages {
method said ($e) {
for $e.what ~~ m:global [[ $<page>=<-[ \] ]>+ ]] { # quick and dirty
$e.msg: wikiLink ~.<page>;
}
}
}
class Sorry {
has $.answers is rw = « I'm so sorry! Please forgive me!
I should have done better!
I promise that it won't happen again! »;
method said ($e) {
if $e.what ~~ / ^ "{ $e.bot.nick }" [:|,] / {
$e.msg: $!answers.pick;
}
}
}
class RecentChanges {
has $.delay is rw = 30;
has $.url is rw = https://oddmuse.org/wiki?action=rss;all=0;showedit=0;rollback=1;from=;
has $!last = time;
method joined ($e) {
start loop {
sleep $!delay;
self.process: $e;
}
}
method process ($e) {
my $newLast = time;
# TODO https breaks HTTP::UserAgent, workaround with curl
my $proc = run(curl, $!url ~ $!last, :out);
my $xml = $proc.out.slurp-rest;
$proc.out.close; # RT #126561
return False unless $proc;
$!last = $newLast;
use XML;
for from-xml($xml).elements(:TAG<item>, :RECURSE) {
my $title = ~.elements(:TAG<title>, :SINGLE).contents;
my $desc = ~.elements(:TAG<description>, :SINGLE).contents;
my $author = ~.elements(:TAG<dc:contributor>, :SINGLE).contents;
$e.msg:Wiki: [$title] <$author> $desc ({wikiLink $title});
}
return True;
}
}
class RecentCommits {
has $.delay is rw = 30;
has $.url = https://github.com/kensanata/oddmuse.git;
has $.repo = repo;
method joined ($e) {
start {
if $!repo.IO !~~ :e {
fail unless run(git, clone, $!url, $!repo);
}
loop {
sleep $!delay;
self.process: $e;
}
}
}
method process ($e) {
my $proc1 = run(git, --git-dir, $!repo ~ /.git, fetch) ;
return False unless $proc1;
my $proc2 = run(git, --git-dir, $!repo ~ /.git, log,
--pretty=format:Commit: %s (https://github.com/kensanata/oddmuse/commit/%h),
...origin, :out);
$e.msg: $_ for $proc2.out;
$proc2.out.close; # RT #126561
return False unless $proc2;
run(git, --git-dir, $!repo ~ /.git, merge, -q);
return True;
}
}
class Backlog does Net::IRC::CommandHandler {
has $.limit is rw = 60 * 60 * 48;
has $.delay is rw = 30; # seconds before file deletion
has $.path is rw = backlogs/;
has $.link is rw = http://alexine.oddmuse.org/backlogs/; # TODO https
has %.messages = ();
multi method said ($e) {
%!messages{$e.where} = [] unless %!messages{$e.where}:exists;
%!messages{$e.where}.push: { when => time, who => $e.who<nick>, what => $e.what };
self.clean;
}
method clean {
for %!messages.values -> $value { # each channel
for $value.kv -> $index, $elem { # each message
last if time - $elem<when> < $!limit;
LAST { $value.splice(0, $index) } # at least one message will be kept
}
}
}
method backlog ($e, $match) is cmd {
self.clean;
mkdir $!path unless $!path.IO ~~ :d;
my $name = ^2**128 .pick.base(36);
my $fh = open$!path/$name, :w;
$fh.say(<{.<who>}> {.<what>}) for @(%!messages{$e.where});
$fh.close;
$e.msg:$!link$name;
Promise.in($!delay).then: { unlink$!path/$name};
}
method forget ($e, $match) is cmd {
%!messages{$e.where} = [];
$e.msg: OK, we didn't have this conversation.;
}
}
sub MAIN(Str :$nick = alexine, Str :$password is copy = , Str :$channel = #oddmuse) {
$password = prompt Nickserv password: unless $password;
Net::IRC::Bot.new(
nick => $nick,
username => $nick,
realname => $nick,
server => irc.freenode.org,
channels => [ $channel ],
debug => True,
modules => (
Intermap.new(),
Pages.new(),
Sorry.new(),
RecentChanges.new(),
#RecentCommits.new(),
Backlog.new(prefix => .),
Net::IRC::Modules::Tell.new(prefix => .),
Net::IRC::Modules::Autoident.new(password => $password),
),
).run;
}

View File

@@ -21,7 +21,6 @@ TEST_LOG="$WORKING_DIRECTORY/log"
ODDMUSE_TEST_LOCATION="$WORKING_DIRECTORY/oddmuse-for-tests/"
GIT_LOCATION="$WORKING_DIRECTORY/"
LAST_COMMIT_FILE="$WORKING_DIRECTORY/last_commit"
LAST_STATUS_FILE="$WORKING_DIRECTORY/last_status"
FIRST_TESTABLE_COMMIT='1c0801bd6ca23de71c7c360a18a648c2b953f1da'
RESULT_FILE="$WORKING_DIRECTORY/output"
WIKIPUT='../config/oddmuse/scripts/cli/wikiput'
@@ -50,9 +49,7 @@ while :; do
"${git[@]}" reset --hard origin/master # starting our search from the last commit
[[ -f $LAST_COMMIT_FILE ]] || echo "$FIRST_TESTABLE_COMMIT" > "$LAST_COMMIT_FILE"
[[ -f $LAST_STATUS_FILE ]] || echo 0 > "$LAST_STATUS_FILE"
lastCommit=$(< "$LAST_COMMIT_FILE")
lastStatus=$(< "$LAST_STATUS_FILE")
logOutput=$("${git[@]}" log --topo-order --pretty=oneline | grep --before 1 -m 1 "^$lastCommit")
(($(wc -l <<< "$logOutput") < 2)) && exit 0 # No more commits to process, good!
@@ -68,16 +65,14 @@ while :; do
printf "%s\n" "$output" > "$RESULT_FILE"
# echo "Duration: $((duration/60))m$((duration%60))s Status: $status" >> "$RESULT_FILE"
printf "%s\n" "$currentCommit" > "$LAST_COMMIT_FILE"
printf "%s\n" "$status" > "$LAST_STATUS_FILE"
"${gitRepo[@]}" add -- "$(readlink -m -- "$RESULT_FILE")" "$(readlink -m -- "$LAST_COMMIT_FILE")" "$(readlink -m -- "$LAST_STATUS_FILE")"
"${gitRepo[@]}" add -- "$(readlink -m -- "$RESULT_FILE")" "$(readlink -m -- "$LAST_COMMIT_FILE")"
"${gitRepo[@]}" commit -m "Test status at $currentCommit (automated commit)"
"${gitRepo[@]}" push
if (( status == 0 )); then
(( lastStatus == 0 )) && minor='-m' || minor='' # we will use unquoted variable on purpose
"$WIKIPUT" $minor -u "$USER_NAME" -s 'Tests PASSED' -z 'ham' "$WIKI_LOCATION/$STATUS_PAGE" <<< $'TEST STATUS **OK**\n\n'"Commit:${currentCommit:0:7} see [[$OUT_PAGE|test log]]"
"$WIKIPUT" -m -u "$USER_NAME" -s 'Tests PASSED' -z 'ham' "$WIKI_LOCATION/$STATUS_PAGE" <<< $'TEST STATUS **OK**\n\n'"Commit:${currentCommit:0:7} see [[$OUT_PAGE|test log]]"
else
"$WIKIPUT" -u "$USER_NAME" -s 'Tests FAILED' -z 'ham' "$WIKI_LOCATION/$STATUS_PAGE" <<< $'TEST STATUS **FAIL**\n\n'"Commit:${currentCommit:0:7} see [[$OUT_PAGE|test log]]"
fi

View File

@@ -1,122 +0,0 @@
#!/usr/bin/env perl
use Mojolicious::Lite;
use Mojo::Cache;
use Archive::Tar;
use File::Basename;
use Encode qw(decode_utf8);
my $dir = "/home/alex/oddmuse.org/releases";
my $cache = Mojo::Cache->new(max_keys => 50);
get '/' => sub {
my $c = shift;
my @tarballs = sort map {
my ($name, $path, $suffix) = fileparse($_, '.tar.gz');
$name;
} <$dir/*.tar.gz>;
$c->render(template => 'index', tarballs => \@tarballs);
} => 'main';
get '/#tarball' => sub {
my $c = shift;
my $tarball = $c->param('tarball');
my $files = $cache->get($tarball);
if (not $files) {
$c->app->log->info("Reading $tarball.tar.gz");
my $tar = Archive::Tar->new;
$tar->read("$dir/$tarball.tar.gz");
my @files = sort grep /./, map {
my @e = split('/', $_->name);
$e[1];
} $tar->get_files();
$files = \@files;
$cache->set($tarball => $files);
}
$c->render(template => 'release', tarball=> $tarball, files => $files);
} => 'release';
get '/#tarball/#file' => sub {
my $c = shift;
my $tarball = $c->param('tarball');
my $file = $c->param('file');
my $text = $cache->get("$tarball/$file");
if (not $text) {
$c->app->log->info("Reading $tarball/$file");
my $tar = Archive::Tar->new;
$tar->read("$dir/$tarball.tar.gz");
$text = decode_utf8($tar->get_content("$tarball/$file"));
$cache->set("$tarball/$file" => $text);
}
$c->render(template => 'file', format => 'txt', content => $text);
} => 'file';
app->start;
__DATA__
@@ index.html.ep
% layout 'default';
% title 'Oddmuse Releases';
<h1>Oddmuse Releases</h1>
<p>Welcome! This is where you get access to tarballs and files in released
versions of Oddmuse.</p>
<ul>
% for my $tarball (@$tarballs) {
<li>
<a href="https://oddmuse.org/releases/<%= $tarball %>.tar.gz"><%= $tarball %>.tar.gz</a>
(files for <%= link_to release => {tarball => $tarball} => begin %>\
<%= $tarball =%><%= end %>)
</li>
% }
</ul>
@@ release.html.ep
% layout 'default';
% title 'Release';
<h1>Files for <%= $tarball %></h1>
<p>
Back to the list of <%= link_to 'releases' => 'main' %>.
Remember,
%= link_to file => {file => 'wiki.pl'} => begin
wiki.pl
% end
is the main script.
<ul>
% for my $file (@$files) {
<li>
%= link_to file => {file => $file} => begin
%= $file
% end
% }
</ul>
@@ file.txt.ep
%layout 'file';
<%== $content %>
@@ layouts/default.html.ep
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
%= stylesheet '/tarballs.css'
%= stylesheet begin
body {
padding: 1em;
font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
}
% end
<meta name="viewport" content="width=device-width">
</head>
<body>
<%= content %>
<hr>
<p>
<a href="https://oddmuse.org/">Oddmuse</a>&#x2003;
<%= link_to 'Releases' => 'main' %>&#x2003;
<a href="https://alexschroeder.ch/wiki/Contact">Alex Schroeder</a>
</body>
</html>

View File

@@ -85,7 +85,7 @@ Examples
--------
Journal pages only:
$0 --match '^\\d{4}-\\d{2}-\\d{2}'
$0 --match '^\d{4}-\d{2}-\d{2}'
Skip comment pages:
$0 --match '^(?!Comments_on_)'

View File

@@ -1,49 +1,36 @@
#!/usr/bin/env perl
#! /usr/bin/env perl
# Copyright (C) 2015-2016 Alex Schroeder <alex@gnu.org>
# This script only works with a version of Mojolicious::Plugin::CGI better than
# the official 0.23. One version would be my fork:
# https://github.com/kensanata/mojolicious-plugin-cgi
# 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 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
# If you use the fork, you might want to simply add its lib directory to your
# libraries instead of installing it?
# use lib '/Users/alex/src/mojolicious-plugin-cgi/lib';
use Mojolicious::Lite;
# This needs to be in a different section, sometimes?
plugin CGI => {
support_semicolon_in_query_string => 1,
};
plugin CGI => {
route => '/wiki',
# We need this for older versions of Mojolicious::Plugin::CGI
script => 'wiki.pl',
run => \&OddMuse::DoWikiRequest,
before => sub {
no warnings;
$OddMuse::RunCGI = 0;
# The default data directory is determined by the environment variable
# WikiDataDir and falls back to the following
# $OddMuse::DataDir = '/tmp/oddmuse';
use warnings;
$OddMuse::DataDir = '/tmp/oddmuse';
require 'wiki.pl' unless defined &OddMuse::DoWikiRequest;
},
env => {},
# path to where STDERR from cgi script goes
errlog => ($ENV{WikiDataDir} || '/tmp/oddmuse')
. "/wiki.log",
errlog => 'wiki.log', # path to where STDERR from cgi script goes
};
get '/' => sub {
my $self = shift;
$self->redirect_to('/wiki');
};
app->start;

View File

@@ -1,63 +0,0 @@
#! /usr/bin/perl
my $usage = q{expire-pans.pl
Usage: this script expects to be run in a directory with a spammer.log file as
produced by the LogBannedContent module.
<https://oddmuse.org/wiki/LogBannedContent_Module>
In the same directory, it expects at least one of BannedContent, BannedHosts or
BannedRegexps. It will work on all three, though. These must be the raw text
files of the wiki.
Here's how you might get them from Emacs Wiki, for example.
wget https://www.emacswiki.org/spammer.log
wget https://www.emacswiki.org/emacs/raw/BannedContent
wget https://www.emacswiki.org/emacs/raw/BannedHosts
wget https://www.emacswiki.org/emacs/raw/BannedRegexps
};
die $usage if ! -f 'spammer.log'
|| !(-f 'BannedContent' || -f 'BannedHosts' || -f 'BannedRegexps');
my $fh;
my @bans;
warn "Reading spammer.log...\n";
open($fh, '<:utf8', 'spammer.log') or die "Cannot read spammer.log: $!";
for my $line (<$fh>) {
push(@bans, $line);
}
close($fh);
for my $file (qw(BannedContent BannedHosts BannedRegexps)) {
warn "Reading $file...\n";
if (open($fh, '<:utf8', $file)) {
my $count = 0;
my $used = 0;
my @out;
for my $line (<$fh>) {
if ($line =~ m/^\s*([^#]+?)\s*(#\s*(\d\d\d\d-\d\d-\d\d\s*)?(.*))?$/) {
$count++;
my ($regexp, $comment) = ($1, $4);
foreach my $ban (@bans) {
if (index($ban, $regexp) > -1) {
$used++;
push(@out, $line);
last;
}
}
} else {
push(@out, $line);
}
}
close ($fh);
warn "$count regular expressions checked\n";
warn "$used regular expressions were used\n";
warn "Writing $file-new...\n";
open ($fh, '>:utf8', "$file-new")
or die "Cannot write $file-new: $!";
print $fh join("", @out);
close $fh;
}
}

View File

@@ -1,63 +0,0 @@
#!/bin/env perl
# Copyright (C) 2015 Alex Schroeder <alex@gnu.org>
# 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 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
use strict;
use warnings;
use version;
my $dir = shift;
unless (-d $dir) {
die <<"EOT";
Usage: $0 DIR [RELEASE]
DIR is the directory where the tarballs for each tag are created.
It must already exist.
If an optional RELEASE such as 2.3.0 is provided, then only tags
equal or greater than 2.3.0 will be considered. The default is 2.3.0.
EOT
}
my $min = version->parse(shift || "2.3.0");
my @tags = grep { /\d+\.\d+\.\d+/ and version->parse($_) > $min }
split(/\n/, qx{git tag --list});
unless (@tags) {
die "git tag --list produced no list of tags\n";
}
for my $tag (@tags) {
my $fname = "$dir/oddmuse-$tag.tar.gz";
unless (-f $fname) {
system("git", "checkout", $tag) == 0
or die "Failed to git checkout $tag\n";
system("make", "prepare") == 0
or die "Failed to run make prepare for tag $tag\n";
system("mv", "build", "oddmuse-$tag") == 0
or die "Failed to rename the build directory to oddmuse-$tag\n";
system("tar", "czf", "oddmuse-$tag.tar.gz", "oddmuse-$tag") == 0
or die "Failed to build tarball oddmuse-$tag.tar.gz\n";
system("mv", "oddmuse-$tag.tar.gz", $fname) == 0
or die "Failed to move the tarball oddmuse-$tag.tar.gz\n";
system("rm", "-rf", "oddmuse-$tag") == 0
or die "Failed to remove the directory oddmuse-$tag\n";
}
}
system("git", "checkout", "master") == 0
or die "Failed to git checkout master\n";

View File

@@ -22,9 +22,45 @@ use XML::Atom::Client;
use XML::Atom::Entry;
use XML::Atom::Person;
sub random_port {
use Errno qw( EADDRINUSE );
use Socket qw( PF_INET SOCK_STREAM INADDR_ANY sockaddr_in );
my $family = PF_INET;
my $type = SOCK_STREAM;
my $proto = getprotobyname('tcp') or die "getprotobyname: $!";
my $host = INADDR_ANY; # Use inet_aton for a specific interface
for my $i (1..3) {
my $port = 1024 + int(rand(65535 - 1024));
socket(my $sock, $family, $type, $proto) or die "socket: $!";
my $name = sockaddr_in($port, $host) or die "sockaddr_in: $!";
setsockopt($sock, SOL_SOCKET, SO_REUSEADDR, 1);
bind($sock, $name)
and close($sock)
and return $port;
die "bind: $!" if $! != EADDRINUSE;
print "Port $port in use, retrying...\n";
}
die "Tried 3 random ports and failed.\n"
}
my $port = random_port();
$ScriptName = "http://localhost:$port";
AppendStringToFile($ConfigFile, "\$ScriptName = $ScriptName;\n");
add_module('atom.pl');
start_server();
# Fork a test server with the new config file and the module
my $pid = fork();
if (!defined $pid) {
die "Cannot fork: $!";
} elsif ($pid == 0) {
use Config;
my $secure_perl_path = $Config{perlpath};
exec($secure_perl_path, "stuff/server.pl", "wiki.pl", $port) or die "Cannot exec: $!";
}
# Give the child time to start
sleep 1;
@@ -33,7 +69,7 @@ sleep 1;
my $ua = LWP::UserAgent->new;
my $response = $ua->get("$ScriptName?action=version");
ok($response->is_success, "There is a wiki running at $ScriptName");
like($response->content, qr/\batom\.pl/, "The server has the atom extension installed");
like($response->content, qr/\batom\.pl/, "The has the atom extension installed");
# Testing the Atom API
my $api = XML::Atom::Client->new;
@@ -143,3 +179,8 @@ sub trim {
}
ok(trim($result->content->body) eq ("<p>" . trim($content) . '</p>'), 'verify content');
ok($result->author->name eq $username, 'verify author');
END {
# kill server
kill 'KILL', $pid;
}

10
t/css.t
View File

@@ -20,13 +20,13 @@ AppendStringToFile($ConfigFile, "\$StyleSheetPage = 'css';\n");
# Default
xpath_test(get_page('HomePage'),
'//link[@type="text/css"][@rel="stylesheet"][@href="https://oddmuse.org/default.css"]');
'//link[@type="text/css"][@rel="stylesheet"][@href="http://www.oddmuse.org/default.css"]');
# StyleSheetPage
update_page('css', "em { font-weight: bold; }", 'some css', 0, 1);
$page = get_page('HomePage');
negative_xpath_test($page,
'//link[@type="text/css"][@rel="stylesheet"][@href="https://oddmuse.org/default.css"]');
'//link[@type="text/css"][@rel="stylesheet"][@href="http://www.oddmuse.org/default.css"]');
xpath_test($page,
'//link[@type="text/css"][@rel="stylesheet"][@href="http://localhost/wiki.pl?action=browse;id=css;raw=1;mime-type=text/css"]');
@@ -34,7 +34,7 @@ xpath_test($page,
AppendStringToFile($ConfigFile, "\$StyleSheet = 'http://example.org/test.css';\n");
$page = get_page('HomePage');
negative_xpath_test($page,
'//link[@type="text/css"][@rel="stylesheet"][@href="https://oddmuse.org/default.css"]',
'//link[@type="text/css"][@rel="stylesheet"][@href="http://www.oddmuse.org/default.css"]',
'//link[@type="text/css"][@rel="stylesheet"][@href="http://localhost/wiki.pl?action=browse;id=css;raw=1;mime-type=text/css"]');
xpath_test($page,
'//link[@type="text/css"][@rel="stylesheet"][@href="http://example.org/test.css"]');
@@ -43,7 +43,7 @@ xpath_test($page,
AppendStringToFile($ConfigFile, "\$StyleSheet = ['http://example.org/test.css', 'http://example.org/another.css'];\n");
$page = get_page('HomePage');
negative_xpath_test($page,
'//link[@type="text/css"][@rel="stylesheet"][@href="https://oddmuse.org/default.css"]',
'//link[@type="text/css"][@rel="stylesheet"][@href="http://www.oddmuse.org/default.css"]',
'//link[@type="text/css"][@rel="stylesheet"][@href="http://localhost/wiki.pl?action=browse;id=css;raw=1;mime-type=text/css"]');
xpath_test($page,
'//link[@type="text/css"][@rel="stylesheet"][@href="http://example.org/test.css"]');
@@ -53,7 +53,7 @@ xpath_test($page,
# Parameter
$page = get_page('action=browse id=HomePage css=http://example.org/my.css');
negative_xpath_test($page,
'//link[@type="text/css"][@rel="stylesheet"][@href="https://oddmuse.org/default.css"]',
'//link[@type="text/css"][@rel="stylesheet"][@href="http://www.oddmuse.org/default.css"]',
'//link[@type="text/css"][@rel="stylesheet"][@href="http://localhost/wiki.pl?action=browse;id=css;raw=1;mime-type=text/css"]',
'//link[@type="text/css"][@rel="stylesheet"][@href="http://example.org/test.css"]');
xpath_test($page,

View File

@@ -23,8 +23,6 @@ SKIP: {
add_module('git.pl');
$ENV{LANG} = "en_US.UTF-8"; # test relies on English output
if (qx($GitBinary --version) !~ /git version/) {
skip "$GitBinary not found", 16;
}

View File

@@ -1,5 +1,4 @@
# Copyright (C) 2015 Alex-Daniel Jakimenko <alex.jakimenko@gmail.com>
# Copyright (C) 2016 Alex Schroeder <alex@gnu.org>
#
# 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
@@ -15,7 +14,7 @@
require 't/test.pl';
package OddMuse;
use Test::More tests => 6;
use Test::More tests => 1;
use File::Basename;
@@ -24,10 +23,9 @@ require("$ModuleDir/load-lang.pl");
my %choosable_translations = reverse %TranslationsLibrary;
my @missing = (); # missing in load-lang.pl
my $count = 0;
foreach (bsd_glob("modules/translations/*.p[ml]")) {
my $filename = fileparse($_);
$count++;
next if exists $choosable_translations{$filename};
next if $filename eq 'new-utf8.pl'; # placeholder
next if $filename =~ /^month-names/; # month names are located in translations/ for whatever reason
@@ -39,20 +37,4 @@ unless (ok(@missing == 0, 'All translations are listed')) {
diag("$_ is not listed in load-lang.pl") for @missing;
}
unless (ok($count > 0, "$count translations were found")) {
diag("if 0 then the \$LoadLanguageDir was not set correctly");
}
test_page(get_page('Test'), 'Edit this page');
CreateDir($LoadLanguageDir);
WriteStringToFile("$LoadLanguageDir/german-utf8.pl", ReadFileOrDie("modules/translations/german-utf8.pl"));
# AppendStringToFile("$LoadLanguageDir/german-utf8.pl", "warn 'reading german-utf8.pl';\n");
$ENV{'HTTP_ACCEPT_LANGUAGE'} = 'de-ch,de;q=0.7,en;q=0.3';
test_page(get_page('action=version'), 'load-lang.pl');
my $page = get_page('Test');
test_page($page, 'Diese Seite bearbeiten');
test_page_negative($page, 'Edit this page');
# TODO test the module itself

View File

@@ -1,5 +1,5 @@
# Copyright (C) 2015-2016 Alex Schroeder <alex@gnu.com>
# Copyright (C) 2015 Alex Jakimenko <alex.jakimenko@gmail.com>
# Copyright (C) 2015 Alex Schroeder <alex@gnu.com>
#
# 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
@@ -20,14 +20,13 @@ use utf8;
package OddMuse;
require 't/test.pl';
use Test::More tests => 29;
use Test::More tests => 11;
use File::Basename;
use Pod::Strip;
use Pod::Simple::TextContent;
my @modules = grep { $_ ne 'modules/404handler.pl' } <modules/*.pl>;
my @other = 'wiki.pl';
my %text = (map { $_ => ReadFileOrDie($_) } @modules, @other);
my @badModules;
@badModules = grep { (stat $_)[2] != oct '100644' } @modules;
@@ -36,19 +35,20 @@ unless (ok(@badModules == 0, 'Consistent file permissions of modules')) {
diag("▶▶▶ Use this command to fix it: chmod 644 @badModules");
}
@badModules = grep { $text{$_} !~ / ^ use \s+ strict; /mx } @modules;
@badModules = grep { ReadFile($_) !~ / ^ use \s+ strict; /mx } @modules;
unless (ok(@badModules == 0, '"use strict;" in modules')) {
diag(qq{$_ has no "use strict;"}) for @badModules;
}
@badModules = grep { $text{$_} !~ / ^ use \s+ v5\.10; /mx } @modules;
@badModules = grep { ReadFile($_) !~ / ^ use \s+ v5\.10; /mx } @modules;
unless (ok(@badModules == 0, '"use v5.10;" in modules')) {
diag(qq{$_ has no "use v5.10;"}) for @badModules;
diag(q{Minimum perl version for the core is v5.10, it seems like there is no reason not to have "use v5.10;" everywhere else.});
}
@badModules = grep {
my $code = $text{$_};
my $code = ReadFile($_);
# warn "Looking at $_: " . length($code);
# check Perl source code
my $perl;
@@ -72,39 +72,39 @@ ok(@badModules == 0, 'utf8 in modules');
SKIP: {
skip 'documentation tests, we did not try to document every module yet', 1;
@badModules = grep { $text{$_} !~ / ^ AddModuleDescription\(' [^\']+ ', /mx } @modules;
@badModules = grep { ReadFile($_) !~ / ^ AddModuleDescription\(' [^\']+ ', /mx } @modules;
unless (ok(@badModules == 0, 'link to the documentation in modules')) {
diag(qq{$_ has no link to the documentation}) for @badModules;
}
}
@badModules = grep { $text{$_} =~ / ^ package \s+ OddMuse; /imx } @modules;
@badModules = grep { ReadFile($_) =~ / ^ package \s+ OddMuse; /imx } @modules;
unless (ok(@badModules == 0, 'no "package OddMuse;" in modules')) {
diag(qq{$_ has "package OddMuse;"}) for @badModules;
diag(q{When we do "do 'somemodule.pl';" it ends up being in the same namespace of a caller, so there is no need to use "package OddMuse;"});
}
@badModules = grep { $text{$_} =~ / ^ use \s+ vars /mx } @modules;
@badModules = grep { ReadFile($_) =~ / ^ use \s+ vars /mx } @modules;
unless (ok(@badModules == 0, 'no "use vars" in modules')) {
diag(qq{$_ is using "use vars"}) for @badModules;
diag('▶▶▶ Use "our ($var, ...)" instead of "use vars qw($var ...)"');
diag(q{▶▶▶ Use this command to do automatic conversion: perl -0pi -e 's/^([\t ]*)use vars qw\s*\(\s*(.*?)\s*\);/$x = $2; $x =~ s{(?<=\w)\b(?!$)}{,}g;"$1our ($x);"/gems' } . "@badModules");
}
@badModules = grep { $text{$_} =~ / [ \t]+ $ /mx } @modules, @other;
@badModules = grep { ReadFile($_) =~ / [ \t]+ $ /mx } @modules, @other;
unless (ok(@badModules == 0, 'no trailing whitespace in modules (and other perl files)')) {
diag(qq{$_ has trailing whitespace}) for @badModules;
diag(q{▶▶▶ Use this command to do automatic trailing whitespace removal: perl -pi -e 's/[ \t]+$//g' } . "@badModules");
}
@badModules = grep { $text{$_} =~ / This (program|file) is free software /x } @modules;
@badModules = grep { ReadFile($_) =~ / This (program|file) is free software /x } @modules;
unless (ok(@badModules == 0, 'license is specified in every module')) {
diag(qq{$_ has no license specified}) for @badModules;
}
@badModules = grep {
my ($name, $path, $suffix) = fileparse($_, '.pl');
$text{$_} !~ /^AddModuleDescription\('$name.pl'/mx;
ReadFile($_) !~ /^AddModuleDescription\('$name.pl'/mx;
} @modules;
unless (ok(@badModules == 0, 'AddModuleDescription is used in every module')) {
diag(qq{$_ does not use AddModuleDescription}) for @badModules;
@@ -116,49 +116,3 @@ unless (ok(@badModules == 0, 'modules are syntatically correct')) {
diag(qq{$_ has syntax errors}) for @badModules;
diag("▶▶▶ Use this command to see the problems: for f in @badModules; do perl -c \$f; done");
}
my %changes = (
'-f' => 'IsFile',
'-e' => 'IsFile',
'-r' => 'IsFile',
'-d' => 'IsDir',
'-z' => 'ZeroSize',
'-M' => '$Now - Modified',
'unlink' => 'Unlink',
'stat(.*)[9]' => 'Modified',
'bsd_glob' => 'Glob',
'chmod' => 'ChangeMod',
'rename' => 'Rename',
'rmdir' => 'RemoveDir',
'chdir' => 'ChangeDir',
'mkdir' => 'CreateDir',
);
for my $re (sort keys %changes) {
@badModules = grep {
my $text = $text{$_};
$text =~s/#.*\n//g; # get rid of comments
$text =~s/Tss?\([^\)]+//g; # getting rid of "rename" in strings
$text =~s/\{\w+\}//g; # getting rid of "rename" in $Action{rename}
$text =~s/'\w+'//g; # getting rid of "rename" in 'rename'
not ($_ eq 'modules/pygmentize.pl' and $re eq '-f'
or $_ eq 'modules/static-copy.pl' and $re eq 'chmod'
or $_ eq 'modules/static-hybrid.pl' and $re eq 'chmod')
and (substr($re, 0, 1) eq '-' and $text =~ /[ (] $re \s/x
or $re eq 'stat(.*)[9]' and $text =~ /\b $re /x
or $re =~ /^\w+$/ and $text =~ /\b $re \b/x);
} @modules;
unless (ok(@badModules == 0, "modules do not use $re")) {
diag(qq{$_ uses $re instead of $changes{$re}}) for @badModules;
}
}
for my $fun ('open.*,.*[<>]', 'sysopen', 'tie', 'opendir') {
@badModules = grep {
my @lines = map { s/#.*//; $_ } split(/\n/, $text{$_});
grep(!/encode_utf8/, grep(/\b $fun \b/x, @lines));
} @modules;
unless (ok(@badModules == 0, qq{modules use encode_utf8 with $fun})) {
diag(qq{$_ does not use encode_utf8 with $fun}) for @badModules;
}
}

View File

@@ -1,64 +0,0 @@
# Copyright (C) 2016 Alex Schroeder <alex@gnu.org>
#
# 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 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
package OddMuse;
use Test::More;
use Test::Mojo;
require 't/test.pl';
add_module('namespaces.pl');
start_mojolicious_server();
sleep(1);
my $t = Test::Mojo->new;
# Installation worked
$t->get_ok("$ScriptName?action=version")
->content_like(qr/namespaces\.pl/);
# Edit a page in the Main namespace
$t->post_ok("$ScriptName"
=> form => {title => 'Some_Page',
text => 'This is the Main namespace.'})
->status_is(302);
$t->get_ok("$ScriptName/Some_Page")
->status_is(200)
->content_like(qr/This is the Main namespace/);
# Edit a page in the Five Winds namespace
$t->post_ok("$ScriptName/FiveWinds"
=> form => {title => 'Some_Page',
text => 'This is the Five Winds namespace.'})
->status_is(302);
$t->get_ok("$ScriptName/FiveWinds/Some_Page")
->status_is(200)
->content_like(qr/This is the Five Winds namespace/);
# This didn't overwrite the Main namespace.
$t->get_ok("$ScriptName/Some_Page")
->content_like(qr/This is the Main namespace/);
# Umlauts
$t->post_ok("$ScriptName/F%C3%BCnfWinde"
=> form => {title => 'Some_Page',
text => 'Wir sind im Namensraum Fünf Winde.'})
->status_is(302);
$t->get_ok("$ScriptName/F%C3%BCnfWinde/Some_Page")
->status_is(200)
->content_like(qr/Wir sind im Namensraum Fünf Winde/);
done_testing();

View File

@@ -1,35 +0,0 @@
# Copyright (C) 2016 Alex Schroeder <alex@gnu.org>
#
# 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 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
package OddMuse;
use Test::More;
use Test::Mojo;
require 't/test.pl';
start_mojolicious_server();
sleep(1);
my $t = Test::Mojo->new;
$t->get_ok("$ScriptName")->status_is(404)->content_like(qr/Welcome!/);
$t->get_ok("$ScriptName?action=admin")->status_is(200);
$t->post_ok("$ScriptName"
=> form => {title => 'HomePage', text => 'This is a test.'})
->status_is(302);
$t->get_ok("$ScriptName")->status_is(200)->content_like(qr/This is a test/);
done_testing();

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2016 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2015 Alex Schroeder <alex@gnu.org>
#
# 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
@@ -18,14 +18,8 @@ use Test::More tests => 2;
add_module('pygmentize.pl');
SKIP: {
if (qx(pygmentize -V) !~ /Pygments version/) {
skip "pygmentize not found", 2;
}
$ENV{PATH} = '.'; # pygmentize is not installed in the current directory
$page = apply_rules(newlines('{{{\ntest\n}}}\n'));
test_page($page,
'\bsh\b.*\bpygmentize\b.*\bnot found\b',
'<pre>test</pre>');
}
$ENV{PATH} = '.'; # pygmentize is not installed in the current directory
$page = apply_rules(newlines('{{{\ntest\n}}}\n'));
test_page($page,
'\bsh\b.*\bpygmentize\b.*\bnot found\b',
'<pre>test</pre>');

View File

@@ -15,7 +15,7 @@
require 't/test.pl';
package OddMuse;
use Test::More tests => 89;
use Test::More tests => 72;
use utf8; # tests contain UTF-8 characters and it matters
add_module('mac.pl');
@@ -116,17 +116,6 @@ xpath_test($page, '//a[@class="more"][@href="http://localhost/wiki.pl?search=Som
$page = get_page('search=Something preview=1 offset=10 num=10 replace=Other pwd=foo');
test_page($page, map { "Page_$_" } ('K' .. 'M'));
# Now do the replacement
$page = get_page('search=Something replace=Other pwd=foo');
test_page($page, 'Replaced: Something &#x2192; Other', '13 pages found',
map { "Page_$_" } ('A' .. 'M'));
# Verify that the change has been made
test_page(get_page('search=Other'), 'Search for: Other', '13 pages found');
# Replace with backreferences, where the replacement pattern is no longer found.
# Take 'fuuz and barz.' and replace ([a-z]+)z with x$1 results in 'xfuu and xbar.'
test_page(get_page('"search=([a-z]%2b)z" replace=x%241 pwd=foo'), '1 pages found');

View File

@@ -1,33 +0,0 @@
# Copyright (C) 2016 Alex Schroeder <alex@gnu.org>
#
# 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 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
require 't/test.pl';
package OddMuse;
use Test::More tests => 4;
use LWP::UserAgent;
start_server();
# Give the child time to start
sleep 1;
# Check whether the child is up and running
my $ua = LWP::UserAgent->new;
my $response = $ua->get("$ScriptName?action=version");
ok($response->is_success, "There is a wiki running at $ScriptName");
like($response->content, qr/Oddmuse/, "It self-identifies as Oddmuse");
ok($ua->get("$ScriptName?title=Test;text=Testing")->is_success, "Page saved");
like($ua->get("$ScriptName/Test")->content, qr/Testing/, "Content verified");

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env perl
# Copyright (C) 20062016 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2006, 2007 Alex Schroeder <alex@emacswiki.org>
#
# 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
@@ -16,71 +16,19 @@
require 't/test.pl';
package OddMuse;
use Test::More tests => 9;
# basic test
use Test::More tests => 6;
add_module('sidebar.pl');
test_page(update_page($SidebarName, 'mu'), '<div class="sidebar"><p>mu</p></div>');
test_page(get_page('HomePage'), '<div class="sidebar"><p>mu</p></div>');
# with images
add_module('image.pl');
# enable uploads
AppendStringToFile($ConfigFile, "\$UploadAllowed = 1;\n");
update_page('pic', "#FILE image/png\niVBORw0KGgoAAAA");
xpath_test(update_page($SidebarName, '[[image:pic|picture|Target]]'),
'//div[@class="sidebar"]/p/a[@class="image"][@href="http://localhost/wiki.pl/Target"]/img[@class="upload"][@src="http://localhost/wiki.pl/download/pic"][@alt="picture"]');
# with static-copy
add_module('static-copy.pl');
AppendStringToFile($ConfigFile, q{
$StaticAlways = 1;
$StaticDir = $DataDir . '/static';
$StaticUrl = '/static/';
%StaticMimeTypes = ('image/png' => 'png', );
@UploadTypes = ('image/png', );
});
update_page('pic', "DeletedPage");
update_page('pic', "#FILE image/png\niVBORw0KGgoAAAA", undef, 0, 1);
ok(-f "$DataDir/static/pic.png", "$DataDir/static/pic.png exists");
xpath_test(update_page($SidebarName, '[[image:pic|a picture|Target]]'),
'//div[@class="sidebar"]/p/a[@class="image"][@href="http://localhost/wiki.pl/Target"]/img[@class="upload"][@src="/static/pic.png"][@alt="a picture"]');
# with forms
add_module('forms.pl');
# Markup the sidebar page prior to locking the sidebar page. This should ensure
# that forms on that page are not interpreted.
test_page(update_page($SidebarName, '<form><h1>mu</h1></form>'),
'<div class="sidebar"><p>&lt;form&gt;&lt;h1&gt;mu&lt;/h1&gt;&lt;/form&gt;</p></div>');
# Lock the sidebar page, mark it up again, and ensure that forms on that page
# are now interpreted.
xpath_test(get_page("action=pagelock id=$SidebarName set=1 pwd=foo"),
'//p/text()[string()="Lock for "]/following-sibling::a[@href="http://localhost/wiki.pl/SideBar"][@class="local"][text()="SideBar"]/following-sibling::text()[string()=" created."]');
test_page(get_page("action=browse id=$SidebarName cache=0"), #update_page($SidebarName, '<form><h1>mu</h1></form>'),
'<div class="sidebar"><form><h1>mu</h1></form></div>');
# While rendering the SideBar as part of the HomePage, it should still
# be considered "locked", and therefore the form should render
# correctly.
test_page(get_page('HomePage'),
'<div class="sidebar"><form><h1>mu</h1></form></div>');
# FIXME: Due to the recent refactoring of the Table of Contents module, the
# Sidebar module is now known **not** to work as expected with that module.
# This would appear to be an unavoidable consequence of that refactoring... The
# Sidebar module, as currently implemented, **cannot** be made to work with the
# Table of Contents module. As such, we disable all prior tests against the
# Table of Contents module. It's hardly ideal. (But then, what is?)
#FIXME: Due to the recent refactoring of the Table of Contents module, the
#Sidebar module is now known **not** to work as expected with that module.
#This would appear to be an unavoidable consequence of that refactoring... The
#Sidebar module, as currently implemented, **cannot** be made to work with the
#Table of Contents module. As such, we disable all prior tests against the
#Table of Contents module. It's hardly ideal. (But then, what is?)
# with toc
@@ -124,3 +72,24 @@ test_page(get_page('HomePage'),
# remove_rule(\&TocRule);
# remove_rule(\&UsemodRule);
# with forms
add_module('forms.pl');
# Markup the sidebar page prior to locking the sidebar page. This should ensure
# that forms on that page are not interpreted.
test_page(update_page($SidebarName, '<form><h1>mu</h1></form>'),
'<div class="sidebar"><p>&lt;form&gt;&lt;h1&gt;mu&lt;/h1&gt;&lt;/form&gt;</p></div>');
# Lock the sidebar page, mark it up again, and ensure that forms on that page
# are now interpreted.
xpath_test(get_page("action=pagelock id=$SidebarName set=1 pwd=foo"),
'//p/text()[string()="Lock for "]/following-sibling::a[@href="http://localhost/wiki.pl/SideBar"][@class="local"][text()="SideBar"]/following-sibling::text()[string()=" created."]');
test_page(get_page("action=browse id=$SidebarName cache=0"), #update_page($SidebarName, '<form><h1>mu</h1></form>'),
'<div class="sidebar"><form><h1>mu</h1></form></div>');
# While rendering the SideBar as part of the HomePage, it should still
# be considered "locked", and therefore the form should render
# correctly.
test_page(get_page('HomePage'),
'<div class="sidebar"><form><h1>mu</h1></form></div>');

View File

@@ -16,7 +16,7 @@
require 't/test.pl';
package OddMuse;
use utf8;
use Test::More tests => 38;
use Test::More tests => 36;
add_module('static-copy.pl');
@@ -167,17 +167,3 @@ xpath_test(update_page('test_image', '[[image/right:bar baz]]'),
# Next, using a real page. The image type is used appropriately.
xpath_test(update_page('test_image', '[[image/right:Logo]]'),
'//a[@class="image right"][@href="http://localhost/wiki.pl/Logo"]/img[@class="upload"][@src="/static/Logo.png"][@alt="Logo"]');
my $weirdPage = 'Ï_lövé_¥ǫµnĩçȯḑë';
update_page($weirdPage, 'Some text');
update_page('Unicode', '[[' . $weirdPage . ']]');
get_page('action=static raw=1 pwd=foo html=1'); # generate static files
my ($status, $data) = ReadFile("$DataDir/static/Unicode.html");
xpath_test(get_page('Unicode'),
'//a[@class="local"]' . '[@href="http://localhost/wiki.pl/' . UrlEncode($weirdPage) . '"]');
xpath_test($data,
'//a[@class="local"]' . '[@href="' . UrlEncode($weirdPage) . '.html"]');

View File

@@ -17,7 +17,6 @@ package OddMuse;
use lib '.';
use XML::LibXML;
use utf8;
use Encode qw(encode_utf8 decode_utf8);
use vars qw($raw);
# Test::More explains how to fix wide character in print issues
@@ -57,7 +56,8 @@ $| = 1; # no output buffering
sub url_encode {
my $str = shift;
return '' unless $str;
my @letters = split(//, encode_utf8($str));
utf8::encode($str); # turn to byte string
my @letters = split(//, $str);
my @safe = ('a' .. 'z', 'A' .. 'Z', '0' .. '9', '-', '_', '.'); # shell metachars are unsafe
foreach my $letter (@letters) {
my $pattern = quotemeta($letter);
@@ -209,8 +209,15 @@ sub xpath_do {
skip("Cannot parse ".name($page).": $@", $#tests + 1) if $@;
foreach my $test (@tests) {
my $nodelist;
# libxml2 is not aware of UTF8 flag
eval { $nodelist = $doc->findnodes(encode_utf8($test)) };
my $bytes = $test;
# utf8::encode: Converts in-place the character sequence to the
# corresponding octet sequence in *UTF-X*. The UTF8 flag is
# turned off, so that after this operation, the string is a byte
# string. (I have no idea why this is necessary, but there you
# go. See encoding.t tests and make sure the page file is
# encoded correctly.)
utf8::encode($bytes);
eval { $nodelist = $doc->findnodes($bytes) };
if ($@) {
fail(&$check(1) ? "$test: $@" : "not $test: $@");
} elsif (ok(&$check($nodelist->size()),
@@ -346,73 +353,6 @@ sub clear_pages {
write_config_file();
}
# Find an unused port
sub random_port {
use Errno qw( EADDRINUSE );
use Socket qw( PF_INET SOCK_STREAM INADDR_ANY sockaddr_in );
my $family = PF_INET;
my $type = SOCK_STREAM;
my $proto = getprotobyname('tcp') or die "getprotobyname: $!";
my $host = INADDR_ANY; # Use inet_aton for a specific interface
for my $i (1..3) {
my $port = 1024 + int(rand(65535 - 1024));
socket(my $sock, $family, $type, $proto) or die "socket: $!";
my $name = sockaddr_in($port, $host) or die "sockaddr_in: $!";
setsockopt($sock, SOL_SOCKET, SO_REUSEADDR, 1);
bind($sock, $name)
and close($sock)
and return $port;
die "bind: $!" if $! != EADDRINUSE;
print "Port $port in use, retrying...\n";
}
die "Tried 3 random ports and failed.\n"
}
my $pid;
# Fork a simple test server
sub start_server {
die "A server already exists: $pid\n" if $pid;
my $port = random_port();
$ScriptName = "http://localhost:$port";
AppendStringToFile($ConfigFile, "\$ScriptName = '$ScriptName';\n");
$pid = fork();
if (!defined $pid) {
die "Cannot fork: $!";
} elsif ($pid == 0) {
use Config;
my $secure_perl_path = $Config{perlpath};
exec($secure_perl_path, "stuff/server.pl", "wiki.pl", $port) or die "Cannot exec: $!";
}
}
# Fork a Mojolicious server
sub start_mojolicious_server {
die "A server already exists: $pid\n" if $pid;
my $port = random_port();
my $listen = "http://127.0.0.1:$port";
$ScriptName = "http://127.0.0.1:$port/wiki";
AppendStringToFile($ConfigFile, "\$ScriptName = '$ScriptName';\n");
$pid = fork();
if (!defined $pid) {
die "Cannot fork: $!";
} elsif ($pid == 0) {
use Config;
my $secure_perl_path = $Config{perlpath};
exec($secure_perl_path, "server.pl", "daemon", "-l", $listen)
or die "Cannot exec: $!";
}
}
END {
# kill server
if ($pid) {
kill 'KILL', $pid or warn "Could not kill server $pid";
}
}
sub RunAndTerminate { # runs a command for 1 second and then sends SIGTERM
my $pid = fork();
if (not $pid) { # child
@@ -434,4 +374,3 @@ sub AppendToConfig {
}
1;

181
wiki.pl Normal file → Executable file
View File

@@ -38,7 +38,6 @@ use utf8; # in case anybody ever adds UTF8 characters to the source
use CGI qw/-utf8/;
use CGI::Carp qw(fatalsToBrowser);
use File::Glob ':glob';
use Encode qw(encode_utf8 decode_utf8);
use sigtrap 'handler' => \&HandleSignals, 'normal-signals', 'error-signals';
local $| = 1; # Do not buffer output (localized for mod_perl)
@@ -67,7 +66,7 @@ our $UseConfig //= 1;
# Main wiki directory
our $DataDir;
$DataDir ||= decode_utf8($ENV{WikiDataDir}) if $UseConfig;
$DataDir ||= $ENV{WikiDataDir} if $UseConfig;
$DataDir ||= '/tmp/oddmuse'; # FIXME: /var/opt/oddmuse/wiki ?
our $ConfigFile;
@@ -102,6 +101,9 @@ our $EditPass //= ''; # Whitespace separated passwords.
our $PassHashFunction //= ''; # Name of the function to create hashes
our $PassSalt //= ''; # Salt will be added to any password before hashing
our $UseCsp = 0; # 1 = enable Content Security Policy # TODO should be enabled by default
our %CspDirectives = ('default-src' => ["'self'"], 'style-src' => ['*'], 'img-src' => ['*']); # CSP directives
our $BannedHosts = 'BannedHosts'; # Page for banned hosts
our $BannedCanRead = 1; # 1 = banned cannot edit, 0 = banned cannot read
our $BannedContent = 'BannedContent'; # Page for banned content (usually for link-ban)
@@ -228,20 +230,17 @@ sub Init {
}
sub InitModules {
if ($UseConfig and $ModuleDir and IsDir($ModuleDir)) {
foreach my $lib (Glob("$ModuleDir/*.p[ml]")) {
if (not $MyInc{$lib}) {
$MyInc{$lib} = 1; # Cannot use %INC in mod_perl settings
my $file = encode_utf8($lib);
do $file;
$Message .= CGI::p("$lib: $@") if $@; # no $q exists, yet
}
if ($UseConfig and $ModuleDir and -d $ModuleDir) {
foreach my $lib (bsd_glob("$ModuleDir/*.p[ml]")) {
do $lib unless $MyInc{$lib};
$MyInc{$lib} = 1; # Cannot use %INC in mod_perl settings
$Message .= CGI::p("$lib: $@") if $@; # no $q exists, yet
}
}
}
sub InitConfig {
if ($UseConfig and $ConfigFile and not $INC{$ConfigFile} and IsFile($ConfigFile)) {
if ($UseConfig and $ConfigFile and not $INC{$ConfigFile} and -f $ConfigFile) {
do $ConfigFile; # these options must be set in a wrapper script or via the environment
$Message .= CGI::p("$ConfigFile: $@") if $@; # remember, no $q exists, yet
}
@@ -254,6 +253,7 @@ sub InitConfig {
}
sub InitDirConfig {
utf8::decode($DataDir); # just in case, eg. "WikiDataDir=/tmp/Zürich♥ perl wiki.pl"
$PageDir = "$DataDir/page"; # Stores page data
$KeepDir = "$DataDir/keep"; # Stores kept (old) page data
$TempDir = "$DataDir/temp"; # Temporary files and locks
@@ -303,12 +303,12 @@ sub InitVariables { # Init global session variables for mod_perl!
delete $PlainTextPages{''}; # $ConfigPage and others might be empty.
CreateDir($DataDir); # Create directory if it doesn't exist
$Now = time; # Reset in case script is persistent
my $ts = Modified($IndexFile); # always stat for multiple server processes
my $ts = (stat($IndexFile))[9]; # always stat for multiple server processes
ReInit() if not $ts or $LastUpdate != $ts; # reinit if another process changed files (requires $DataDir)
$LastUpdate = $ts;
unshift(@MyRules, \&MyRules) if defined(&MyRules) && (not @MyRules or $MyRules[0] != \&MyRules);
@MyRules = sort {$RuleOrder{$a} <=> $RuleOrder{$b}} @MyRules; # default is 0
ReportError(Ts('Cannot create %s', $DataDir) . ": $!", '500 INTERNAL SERVER ERROR') unless IsDir($DataDir);
ReportError(Ts('Cannot create %s', $DataDir) . ": $!", '500 INTERNAL SERVER ERROR') unless -d $DataDir;
@IndexOptions = (['pages', T('Include normal pages'), 1, \&AllPagesList]);
foreach my $sub (@MyInitVariables) {
my $result = $sub->();
@@ -363,7 +363,8 @@ sub CookieRollbackFix {
sub GetParam {
my ($name, $default) = @_;
my $result = $q->param(encode_utf8($name));
utf8::encode($name); # turn to byte string
my $result = $q->param($name);
$result //= $default;
return QuoteHtml($result); # you need to unquote anything that can have <tags>
}
@@ -788,7 +789,8 @@ sub UnquoteHtml {
sub UrlEncode {
my $str = shift;
return '' unless $str;
my @letters = split(//, encode_utf8($str));
utf8::encode($str); # turn to byte string
my @letters = split(//, $str);
my %safe = map {$_ => 1} ('a' .. 'z', 'A' .. 'Z', '0' .. '9', '-', '_', '.', '!', '~', '*', "'", '(', ')', '#');
foreach my $letter (@letters) {
$letter = sprintf("%%%02x", ord($letter)) unless $safe{$letter};
@@ -798,7 +800,8 @@ sub UrlEncode {
sub UrlDecode {
my $str = shift;
return decode_utf8($str) if $str =~ s/%([0-9a-f][0-9a-f])/chr(hex($1))/eg;
$str =~ s/%([0-9a-f][0-9a-f])/chr(hex($1))/eg;
utf8::decode($str); # make internal string
return $str;
}
@@ -1013,7 +1016,7 @@ sub GetRss {
my $str = '';
if (GetParam('cache', $UseCache) > 0) {
foreach my $uri (keys %todo) { # read cached rss files if possible
if ($Now - Modified($todo{$uri}) < $RssCacheHours * 3600) {
if ($Now - (stat($todo{$uri}))[9] < $RssCacheHours * 3600) {
$data{$uri} = ReadFile($todo{$uri});
delete($todo{$uri}); # no need to fetch them below
}
@@ -1256,14 +1259,16 @@ sub PrintPageDiff { # print diff for open page
}
sub ToString {
my $sub_ref = shift;
my ($sub_ref) = @_;
my $output;
open(my $outputFH, '>:encoding(UTF-8)', \$output) or die "Can't open memory file: $!";
my $oldFH = select $outputFH;
$sub_ref->(@_);
$sub_ref->();
select $oldFH;
close $outputFH;
return decode_utf8($output);
my $output_fixed = $output; # do not delete!
utf8::decode($output_fixed); # this is a workarond for perl bug
return $output_fixed; # otherwise UTF8 characters are SOMETIMES not decoded.
}
sub PageHtml {
@@ -1297,11 +1302,17 @@ sub Tss {
sub GetId {
my $id = UnquoteHtml(GetParam('id', GetParam('title', ''))); # id=x or title=x -> x
if (not $id and $q->keywords) {
$id = decode_utf8(join('_', $q->keywords)); # script?p+q -> p_q
if (not $id) {
my @keywords = $q->keywords;
foreach my $keyword (@keywords) {
utf8::decode($keyword);
}
$id ||= join('_', @keywords); # script?p+q -> p_q
}
if ($UsePathInfo and $q->path_info) {
my @path = map { decode_utf8($_) } split(/\//, $q->path_info);
if ($UsePathInfo) {
my $path = $q->path_info;
utf8::decode($path);
my @path = split(/\//, $path);
$id ||= pop(@path); # script/p/q -> q
foreach my $p (@path) {
SetParam($p, 1); # script/p/q -> p=1
@@ -1496,7 +1507,7 @@ sub GetRcLines { # starttime, hash of seen pages to use as a second return value
my @result = ();
my $ts;
# check the first timestamp in the default file, maybe read old log file
if (open(my $F, '<:encoding(UTF-8)', encode_utf8($RcFile))) {
if (open(my $F, '<:encoding(UTF-8)', $RcFile)) {
my $line = <$F>;
($ts) = split(/$FS/, $line); # the first timestamp in the regular rc file
}
@@ -1580,7 +1591,7 @@ sub GetRcLinesFor {
rcclusteronly rcfilteronly match lang followup);
# parsing and filtering
my @result = ();
open(my $F, '<:encoding(UTF-8)', encode_utf8($file)) or return ();
open(my $F, '<:encoding(UTF-8)', $file) or return ();
while (my $line = <$F>) {
chomp($line);
my ($ts, $id, $minor, $summary, $host, $username, $revision,
@@ -2117,8 +2128,8 @@ sub DoAdminPage {
push(@menu, ScriptLink('action=maintain', T('Run maintenance'), 'maintain')) if $Action{maintain};
my @locks;
for my $pattern (@KnownLocks) {
for my $name (Glob($pattern)) {
if (IsDir($LockDir . $name)) {
for my $name (bsd_glob $pattern) {
if (-d $LockDir . $name) {
push(@locks, $name);
}
}
@@ -2128,7 +2139,7 @@ sub DoAdminPage {
};
if (UserIsAdmin()) {
if ($Action{editlock}) {
if (IsFile("$DataDir/noedit")) {
if (-f "$DataDir/noedit") {
push(@menu, ScriptLink('action=editlock;set=0', T('Unlock site'), 'editlock 0'));
} else {
push(@menu, ScriptLink('action=editlock;set=1', T('Lock site'), 'editlock 1'));
@@ -2136,7 +2147,7 @@ sub DoAdminPage {
}
if ($id and $Action{pagelock}) {
my $title = NormalToFree($id);
if (IsFile(GetLockedPageFile($id))) {
if (-f GetLockedPageFile($id)) {
push(@menu, ScriptLink('action=pagelock;set=0;id=' . UrlEncode($id),
Ts('Unlock %s', $title), 'pagelock 0'));
} else {
@@ -2295,6 +2306,12 @@ sub GetHttpHeader {
$headers{-Content_Encoding} = $encoding if $encoding;
my $cookie = Cookie();
$headers{-cookie} = $cookie if $cookie;
if ($UseCsp) {
my $csp = join '; ', map { join ' ', $_, @{$CspDirectives{$_}} } sort keys %CspDirectives;
$headers{'-Content-Security-Policy'} = $csp;
$headers{'-X-Content-Security-Policy'} = $csp; # required for IE
$headers{'-X-Webkit-CSP'} = $csp; # required for UC browser
}
if ($q->request_method() eq 'HEAD') {
print $q->header(%headers), "\n\n"; # add newlines for FCGI because of exit()
exit; # total shortcut -- HEAD never expects anything other than the header!
@@ -2369,7 +2386,7 @@ sub GetCss { # prevent javascript injection
if ($IndexHash{$StyleSheetPage} and not @css) {
push (@css, "$ScriptName?action=browse;id=" . UrlEncode($StyleSheetPage) . ";raw=1;mime-type=text/css")
}
push (@css, 'https://oddmuse.org/default.css') unless @css;
push (@css, 'http://www.oddmuse.org/default.css') unless @css;
return join('', map { qq(<link type="text/css" rel="stylesheet" href="$_" />) } @css);
}
@@ -2596,9 +2613,10 @@ sub DoDiff { # Actualy call the diff program
RequestLockDir('diff') or return '';
WriteStringToFile($oldName, $_[0]);
WriteStringToFile($newName, $_[1]);
my $diff_out = decode_utf8(`diff -- \Q$oldName\E \Q$newName\E`);
ReleaseLockDir('diff');
my $diff_out = `diff -- \Q$oldName\E \Q$newName\E`;
utf8::decode($diff_out); # needs decoding
$diff_out =~ s/\n\K\\ No newline.*\n//g; # Get rid of common complaint.
ReleaseLockDir('diff');
# No need to unlink temp files--next diff will just overwrite.
return $diff_out;
}
@@ -2773,7 +2791,7 @@ sub GetKeepDir {
}
sub GetKeepFiles {
return Glob(GetKeepDir(shift) . '/*.kp'); # files such as 1.kp, 2.kp, etc.
return bsd_glob(GetKeepDir(shift) . '/*.kp'); # files such as 1.kp, 2.kp, etc.
}
sub GetKeepRevisions {
@@ -2834,12 +2852,14 @@ sub ExpireKeepFiles { # call with opened page
my $keep = GetKeptRevision($revision);
next if $keep->{'keep-ts'} >= $expirets;
next if $KeepMajor and $keep->{revision} == $Page{lastmajor};
Unlink(GetKeepFile($OpenPageName, $revision));
unlink GetKeepFile($OpenPageName, $revision);
}
}
sub ReadFile {
if (open(my $IN, '<:encoding(UTF-8)', encode_utf8(shift))) {
my $file = shift;
utf8::encode($file); # filenames are bytes!
if (open(my $IN, '<:encoding(UTF-8)', $file)) {
local $/ = undef; # Read complete files
my $data=<$IN>;
close $IN;
@@ -2860,7 +2880,8 @@ sub ReadFileOrDie {
sub WriteStringToFile {
my ($file, $string) = @_;
open(my $OUT, '>:encoding(UTF-8)', encode_utf8($file))
utf8::encode($file);
open(my $OUT, '>:encoding(UTF-8)', $file)
or ReportError(Ts('Cannot write %s', $file) . ": $!", '500 INTERNAL SERVER ERROR');
print $OUT $string;
close($OUT);
@@ -2868,27 +2889,18 @@ sub WriteStringToFile {
sub AppendStringToFile {
my ($file, $string) = @_;
open(my $OUT, '>>:encoding(UTF-8)', encode_utf8($file))
utf8::encode($file);
open(my $OUT, '>>:encoding(UTF-8)', $file)
or ReportError(Ts('Cannot write %s', $file) . ": $!", '500 INTERNAL SERVER ERROR');
print $OUT $string;
close($OUT);
}
sub IsFile { return -f encode_utf8(shift); }
sub IsDir { return -d encode_utf8(shift); }
sub ZeroSize { return -z encode_utf8(shift); }
sub Unlink { return unlink(map { encode_utf8($_) } @_); }
sub Modified { return (stat(encode_utf8(shift)))[9]; }
sub Glob { return map { decode_utf8($_) } bsd_glob(encode_utf8(shift)); }
sub ChangeMod { return chmod(shift, map { encode_utf8($_) } @_); }
sub Rename { return rename(encode_utf8($_[0]), encode_utf8($_[1])); }
sub RemoveDir { return rmdir(encode_utf8(shift)); }
sub ChangeDir { return chdir(encode_utf8(shift)); }
sub CreateDir {
my ($newdir) = @_;
return if IsDir($newdir);
mkdir(encode_utf8($newdir), 0775)
utf8::encode($newdir);
return if -d $newdir;
mkdir($newdir, 0775)
or ReportError(Ts('Cannot create %s', $newdir) . ": $!", '500 INTERNAL SERVER ERROR');
}
@@ -2904,11 +2916,9 @@ sub RequestLockDir {
CreateDir($TempDir);
my $lock = $LockDir . $name;
my $n = 0;
# Cannot use CreateDir because we don't want to skip mkdir if the directory
# already exists.
while (mkdir(encode_utf8($lock), 0555) == 0) {
while (mkdir($lock, 0555) == 0) {
if ($n++ >= $tries) {
my $ts = Modified($lock);
my $ts = (stat($lock))[9];
if ($Now - $ts > $LockExpiration and $LockExpires{$name} and not $retried) { # XXX should we remove this now?
ReleaseLockDir($name); # try to expire lock (no checking)
return 1 if RequestLockDir($name, undef, undef, undef, 1);
@@ -2941,9 +2951,9 @@ sub CleanLock {
}
sub ReleaseLockDir {
my $name = shift; # We don't check whether we succeeded.
RemoveDir($LockDir . $name); # Before fixing, make sure we only call this
delete $Locks{$name}; # when we know the lock exists.
my $name = shift; # We don't check whether we succeeded.
rmdir($LockDir . $name); # Before fixing, make sure we only call this
delete $Locks{$name}; # when we know the lock exists.
}
sub RequestLockOrError {
@@ -2957,7 +2967,7 @@ sub ReleaseLock {
sub ForceReleaseLock {
my $pattern = shift;
my $forced;
foreach my $name (Glob($pattern)) {
foreach my $name (bsd_glob $pattern) {
# First try to obtain lock (in case of normal edit lock)
$forced = 1 unless RequestLockDir($name, 5, 3, 0);
ReleaseLockDir($name); # Release the lock, even if we didn't get it. This should not happen.
@@ -3226,10 +3236,10 @@ sub UserCanEdit {
return 0 if $id eq 'SampleUndefinedPage' or $id eq T('SampleUndefinedPage')
or $id eq 'Sample_Undefined_Page' or $id eq T('Sample_Undefined_Page');
return 1 if UserIsAdmin();
return 0 if $id ne '' and IsFile(GetLockedPageFile($id));
return 0 if $LockOnCreation{$id} and not IsFile(GetPageFile($id)); # new page
return 0 if $id ne '' and -f GetLockedPageFile($id);
return 0 if $LockOnCreation{$id} and not -f GetPageFile($id); # new page
return 1 if UserIsEditor();
return 0 if not $EditAllowed or IsFile($NoEditFile);
return 0 if not $EditAllowed or -f $NoEditFile;
return 0 if $editing and UserIsBanned(); # this call is more expensive
return 0 if $EditAllowed >= 2 and (not $CommentsPattern or $id !~ /$CommentsPattern/);
return 1 if $EditAllowed >= 3 and GetParam('recent_edit', '') ne 'on' # disallow minor comments
@@ -3352,7 +3362,7 @@ sub AllPagesList {
my $refresh = GetParam('refresh', 0);
return @IndexList if @IndexList and not $refresh;
SetParam('refresh', 0) if $refresh;
return @IndexList if not $refresh and IsFile($IndexFile) and ReadIndex();
return @IndexList if not $refresh and -f $IndexFile and ReadIndex();
# If open fails just refresh the index
RefreshIndex();
return @IndexList;
@@ -3376,10 +3386,11 @@ sub RefreshIndex {
@IndexList = ();
%IndexHash = ();
# If file exists and cannot be changed, error!
my $locked = RequestLockDir('index', undef, undef, IsFile($IndexFile));
foreach (Glob("$PageDir/*.pg"), Glob("$PageDir/.*.pg")) {
my $locked = RequestLockDir('index', undef, undef, -f $IndexFile);
foreach (bsd_glob("$PageDir/*.pg"), bsd_glob("$PageDir/.*.pg")) {
next unless m|/.*/(.+)\.pg$|;
my $id = $1;
utf8::decode($id);
push(@IndexList, $id);
$IndexHash{$id} = 1;
}
@@ -3407,11 +3418,6 @@ sub DoSearch {
if (GetParam('preview', '')) { # Preview button was used
print GetHeader('', Ts('Preview: %s', $string . " &#x2192; " . $replacement));
print $q->start_div({-class=>'content replacement'});
print GetFormStart(undef, 'post', 'replace');
print GetHiddenValue('search', $string);
print GetHiddenValue('replace', $replacement);
print GetHiddenValue('delete', GetParam('delete', 0));
print $q->submit(-value=>T('Go!')) . $q->end_form();
@results = ReplaceAndDiff($re, UnquoteHtml($replacement));
} else {
print GetHeader('', Ts('Replaced: %s', $string . " &#x2192; " . $replacement));
@@ -3448,9 +3454,9 @@ sub PageIsUploadedFile {
return if $OpenPageName eq $id;
if ($IndexHash{$id}) {
my $file = GetPageFile($id);
open(my $FILE, '<:encoding(UTF-8)', encode_utf8($file))
or ReportError(Ts('Cannot open %s', GetPageFile($id))
. ": $!", '500 INTERNAL SERVER ERROR');
utf8::encode($file); # filenames are bytes!
open(my $FILE, '<:encoding(UTF-8)', $file)
or ReportError(Ts('Cannot open %s', $file) . ": $!", '500 INTERNAL SERVER ERROR');
while (defined($_ = <$FILE>) and $_ !~ /^text: /) {
} # read lines until we get to the text key
close $FILE;
@@ -3583,7 +3589,7 @@ sub SearchExtract {
sub ReplaceAndSave {
my ($from, $to) = @_;
RequestLockOrError(); # fatal
my @result = Replace($from, $to, 1, sub {
my @result = Replace($from, $to, sub {
my ($id, $new) = @_;
Save($id, $new, $from . ' → ' . $to, 1, ($Page{host} ne $q->remote_addr()));
});
@@ -3593,7 +3599,7 @@ sub ReplaceAndSave {
sub ReplaceAndDiff {
my ($from, $to) = @_;
my @found = Replace($from, $to, 0, sub {
my @found = Replace($from, $to, sub {
my ($id, $new) = @_;
print $q->h2(GetPageLink($id)), $q->div({-class=>'diff'}, ImproveDiff(DoDiff($Page{text}, $new)));
});
@@ -3609,7 +3615,7 @@ sub ReplaceAndDiff {
}
sub Replace {
my ($from, $to, $all, $func) = @_; # $func takes $id and $new text
my ($from, $to, $func) = @_; # $func takes $id and $new text
my $lang = GetParam('lang', '');
my $num = GetParam('num', 10);
my $offset = GetParam('offset', 0);
@@ -3629,7 +3635,7 @@ sub Replace {
};
if (s/$from/$replacement->()/egi) { # allows use of backreferences
push (@result, $id);
$func->($id, $_) if $all or @result > $offset and @result <= $offset + $num;
$func->($id, $_) if @result > $offset and @result <= $offset + $num;
}
}
return @result;
@@ -3785,7 +3791,7 @@ sub Save { # call within lock, with opened page
my $revision = $Page{revision} + 1;
my $old = $Page{text};
my $olddiff = $Page{'diff-major'} == '1' ? $Page{'diff-minor'} : $Page{'diff-major'};
if ($revision == 1 and IsFile($IndexFile) and not Unlink($IndexFile)) { # regenerate index on next request
if ($revision == 1 and -e $IndexFile and not unlink($IndexFile)) { # regenerate index on next request
SetParam('msg', Ts('Cannot delete the index file %s.', $IndexFile)
. ' ' . T('Please check the directory permissions.')
. ' ' . T('Your changes were not saved.'));
@@ -3845,7 +3851,8 @@ sub MergeRevisions { # merge change from file2 to file3 into file1
WriteStringToFile($name2, $file2);
WriteStringToFile($name3, $file3);
my ($you, $ancestor, $other) = (T('you'), T('ancestor'), T('other'));
my $output = decode_utf8(`diff3 -m -L \Q$you\E -L \Q$ancestor\E -L \Q$other\E -- \Q$name1\E \Q$name2\E \Q$name3\E`);
my $output = `diff3 -m -L \Q$you\E -L \Q$ancestor\E -L \Q$other\E -- \Q$name1\E \Q$name2\E \Q$name3\E`;
utf8::decode($output); # needs decoding
ReleaseLockDir('merge'); # don't unlink temp files--next merge will just overwrite.
return $output;
}
@@ -3869,7 +3876,7 @@ sub DoMaintain {
print GetHeader('', T('Run Maintenance')), $q->start_div({-class=>'content maintain'});
my $fname = "$DataDir/maintain";
if (not UserIsAdmin()) {
if (IsFile($fname) and $Now - Modified($fname) < 0.5) {
if ((-f $fname) and ((-M $fname) < 0.5)) {
print $q->p(T('Maintenance not done.') . ' ' . T('(Maintenance can only be done once every 12 hours.)')
. ' ', T('Remove the "maintain" file or wait.')), $q->end_div();
PrintFooter();
@@ -3912,7 +3919,7 @@ sub DoMaintain {
}
if (opendir(DIR, $RssDir)) { # cleanup if they should expire anyway
foreach (readdir(DIR)) {
Unlink("$RssDir/$_") if $Now - Modified($_) > $RssCacheHours * 3600;
unlink "$RssDir/$_" if $Now - (stat($_))[9] > $RssCacheHours * 3600;
}
closedir DIR;
}
@@ -3945,8 +3952,8 @@ sub DeletePage { # Delete must be done inside locks.
ValidIdOrDie($id);
AppendStringToFile($DeleteFile, "$id\n");
foreach my $name (GetPageFile($id), GetKeepFiles($id), GetKeepDir($id), GetLockedPageFile($id), $IndexFile) {
Unlink($name) if IsFile($name);
RemoveDir($name) if IsDir($name);
unlink $name if -f $name;
rmdir $name if -d $name;
}
ReInit($id);
delete $IndexHash{$id};
@@ -3961,10 +3968,10 @@ sub DoEditLock {
if (GetParam("set", 1)) {
WriteStringToFile($fname, 'editing locked.');
} else {
Unlink($fname);
unlink($fname);
}
utime time, time, $IndexFile; # touch index file
print $q->p(IsFile($fname) ? T('Edit lock created.') : T('Edit lock removed.'));
print $q->p(-f $fname ? T('Edit lock created.') : T('Edit lock removed.'));
PrintFooter();
}
@@ -3977,10 +3984,10 @@ sub DoPageLock {
if (GetParam('set', 1)) {
WriteStringToFile($fname, 'editing locked.');
} else {
Unlink($fname);
unlink($fname);
}
utime time, time, $IndexFile; # touch index file
print $q->p(IsFile($fname) ? Ts('Lock for %s created.', GetPageLink($id))
print $q->p(-f $fname ? Ts('Lock for %s created.', GetPageLink($id))
: Ts('Lock for %s removed.', GetPageLink($id)));
PrintFooter();
}