Compare commits

..

28 Commits

Author SHA1 Message Date
Alex Schroeder
4b33b3afeb Get rid of -r 2016-06-27 12:05:37 +02:00
Alex Schroeder
9beca5895a tarballs.pl: decode utf8 2016-06-24 12:29:19 +02:00
Alex Schroeder
1afc03eee1 tarballs.pl: a frontend to serve released files
There is a target in our Makefile to make a new release. This stores a
tarball with the appropriate release information in
https://oddmuse.org/releases. tarballs.pl offers an interface to serve
these files, or their individual member files, with a naive cache of
50 elements.

This is a Mojolicious application and is available here:
https://odddmuse.org/download
2016-06-23 23:41:41 +02:00
Alex Schroeder
331b03f894 Script to serve tarballs 2016-06-23 18:33:42 +02:00
Alex Schroeder
1c9b180b3a Merge git.sv.gnu.org:/srv/git/oddmuse 2016-06-23 00:47:10 +02:00
Alex Schroeder
57a16e85f8 meta.t: improve by skipping comments 2016-06-23 00:44:06 +02:00
Alex Schroeder
c7cd5bcc36 meta.t: improve by skipping comments 2016-06-23 00:38:23 +02:00
Alex Schroeder
f571007516 Fix issues discovered by meta.t 2016-06-23 00:34:56 +02:00
Alex Schroeder
fac3f03f7b meta.t: enforce file access rules 2016-06-23 00:31:52 +02:00
Alex Schroeder
7d85dd6570 toc.pl: use ToString and don't double-decode
ToString now takes more arguments.
2016-06-22 16:24:07 +02:00
Alex Schroeder
a91ef8602f Moving modules from utf8::encode to encode_utf8 2016-06-22 15:37:04 +02:00
Alex Schroeder
1bc670617e test.pl: move to encode_utf8 as well 2016-06-22 14:54:52 +02:00
Alex Schroeder
74288ba3f3 Moving from utf8::encode to encode_utf8 2016-06-22 14:43:28 +02:00
Alex Schroeder
bf2856011d Changing $foo/$bar to "$foo/$bar"
Why did this not cause a syntax error?
2016-06-19 15:56:34 +02:00
Alex Schroeder
ca974a902d latex.pl: Globbing nil requires double quotes to work 2016-06-19 15:55:46 +02:00
Alex Schroeder
f992897e7a opendir also requires bytes 2016-06-19 15:55:03 +02:00
Alex Schroeder
c121607f61 All open and tie calls need utf8::encode 2016-06-19 13:51:11 +02:00
Alex Schroeder
032c7aea73 bsd_glob replaced with Glob 2016-06-19 11:55:58 +02:00
Alex Schroeder
f6c419746c tags.pl: Storable also needs bytes in filename 2016-06-19 00:10:39 +02:00
Alex Schroeder
83f13a9a1a Use helper functions for accessing the file system
As we derive a lot of filenames from strings in UTF-8 encoded files, we
need to make sure that any filename that might might be set by a user –
including all the filenames containing a directory deriving from
$DataDir – are passed through utf8::encode. That is, every character
gets replaced with a sequence of one or more characters that represent
the individual bytes of the character and the UTF8 flag is turned off.

In other words, -d $DataDir might not work if $DataDir contains a UTF-8
encoded string. The solution is to use the following replacements:

-f $name            IsFile($name)
-e $name            IsFile($name)
-d $name            IsDir($name)
(stat($name))[9]    Modified($name)
-M $name            $Now - Modified($name)
-z $name            ZeroSize($name)
unlink $name        Unlink($name)
mkdir $name         CreateDir($name)
rmdir $name         RemoveDir($name)

(Using IsFile for -e is probably not ideal?)

If you don’t, and Oddmuse gets used with Mojolicious, and you use the
Namespaces Extension, and a namespace contains non-ASCII characters such
as ä, ö, or ü, these characters will end up as part of $DataDir and
trigger the problem.

I also wonder whether we should be using some other Perl library.
2016-06-17 14:49:34 +02:00
Alex Schroeder
2111af2ec8 Fix regular expression in Makefile
Unescaped left brace in regex is illegal in regex.
2016-06-16 09:43:08 +02:00
Alex Schroeder
648e6eb9bc Skip pygmentize if the binary is not found 2016-06-15 15:07:15 +02:00
Alex Schroeder
994b4e8051 Tests rely on English output
Set environment variable to en_US.UTF-8.
2016-06-15 14:47:20 +02:00
Alex Schroeder
f2f464b1ca test.pl: no warning about killing the server 2016-06-15 10:32:58 +02:00
Alex Schroeder
119d11b405 Merge branch 'master' of github.com:kensanata/oddmuse 2016-06-15 10:31:06 +02:00
Alex Schroeder
d7031198cd Merge github.com:kensanata/oddmuse 2016-06-14 13:14:12 +02:00
Alex Schroeder
187d4020f5 Make server.pl compatible with Alexine 2016-06-14 13:07:29 +02:00
Alex Schroeder
0a77bd0b47 All access to the file system needs bytes!
All occurence of	tuns into
-f $name		IsFile($name)
-e $name		IsFile($name)
-d $name		IsDir($name)
(stat($name))[9]	Modified($name)
-M $name		$Now - Modified($name)
unlink $name		Unlink($name)
mkdir $name		CreateDir($name)
rmdir $name		RemoveDir($name)

This change is incomplete. All the modules also need to be changed.
The benefit of this change is that t/mojolicious-namespaces.t passes.
2016-06-13 22:28:52 +02:00
49 changed files with 459 additions and 405 deletions

View File

@@ -25,7 +25,7 @@ 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

@@ -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 -f $fname;
ReportError(Ts('The page %s does not exist', $id), '400 BAD REQUEST') unless IsFile($fname);
my $newfname = GetPageFile($new);
ReportError(Ts('The page %s already exists', $new), '400 BAD REQUEST') if -f $newfname;
ReportError(Ts('The page %s already exists', $new), '400 BAD REQUEST') if IsFile($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 -d $kdir;
if IsDir($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 -d $rdir;
if IsDir($rdir);
}
# RecentChanges
OpenPage($new);

View File

@@ -29,7 +29,7 @@ $NewQuestion = 'Write your question here:';
sub IncrementInFile {
my $filename = shift;
sysopen my $fh, $filename, O_RDWR|O_CREAT or die "can't open $filename: $!";
sysopen my $fh, encode_utf8($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 -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 $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 $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', $backfile or die "Cannot open file $backfile $!\n";
Unlink($backfile); # Remove old database
tie my %backhash, 'MLDBM', encode_utf8($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', $backfile, O_CREAT|O_RDWR, oct(644) or die "Cannot open file $backfile $!\n";
tie my %backhash, 'MLDBM', encode_utf8($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,8 +77,7 @@ sub DoUnifiedDiff { # copied from DoDiff
RequestLockDir('diff') or return '';
WriteStringToFile($oldName, $_[0]);
WriteStringToFile($newName, $_[1]);
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
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!
$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 (-f "$DraftDir/" . GetParam('username', $q->remote_addr()) # draft exists
} elsif (IsFile("$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 (-f $draft) {
} elsif (IsFile($draft)) {
my $data = ParseData(ReadFileOrDie($draft));
unlink ($draft);
Unlink($draft);
$Message .= $q->p(T('Draft recovered'));
DoEdit($data->{id}, $data->{text}, 1);
} else {
@@ -76,22 +76,19 @@ push(@MyMaintenance, \&DraftCleanup);
sub DraftFiles {
return map {
my $x = $_;
$x = substr($x, length($DraftDir) + 1);
utf8::decode($x);
$x;
} bsd_glob("$DraftDir/*"), bsd_glob("$DraftDir/.*");
substr($_, length($DraftDir) + 1);
} Glob("$DraftDir/*"), Glob("$DraftDir/.*");
}
sub DraftCleanup {
print '<p>' . T('Draft Cleanup');
foreach my $draft (DraftFiles()) {
next if $draft eq '.' or $draft eq '..';
my $ts = (stat("$DraftDir/$draft"))[9];
my $ts = Modified("$DraftDir/$draft");
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,8 +27,7 @@ sub FixEncoding {
ValidIdOrDie($id);
RequestLockOrError();
OpenPage($id);
my $text = $Page{text};
utf8::decode($text);
my $text = decode_utf8($Page{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 (-f GetLockedPageFile($OpenPageName) or (InElement('div', '^class="crossbar"$') and
-f GetLockedPageFile($CrossbarPageName))) {
if (IsFile(GetLockedPageFile($OpenPageName)) or (InElement('div', '^class="crossbar"$') and
IsFile(GetLockedPageFile($CrossbarPageName)))) {
if (/\G(\&lt;form.*?\&lt;\/form\&gt;)/cgs) {
my $form = $1;
my $oldpos = pos;

View File

@@ -163,8 +163,7 @@ sub GdSecurityImageGenerate {
my ($imgData) = $img->out(force => 'png');
my $ticketId = Digest::MD5::md5_hex(rand());
CreateDir($GdSecurityImageDir);
my $file = GdSecurityImageGetImageFile($ticketId);
open my $fh, ">:raw", $file
open my $fh, ">:raw", encode_utf8(GdSecurityImageGetImageFile($ticketId))
or ReportError(Ts('Image storing failed. (%s)', $!), '500 INTERNAL SERVER ERROR');
print $fh $imgData;
#print $fh $png; ### experimental ###
@@ -187,9 +186,7 @@ sub GdSecurityImageIsValidId {
}
sub GdSecurityImageReadImageFile {
my $file = shift;
utf8::encode($file); # filenames are bytes!
if (open(my $IN, '<:raw', $file)) {
if (open(my $IN, '<:raw', encode_utf8(shift))) {
local $/ = undef; # Read complete files
my $data=<$IN>;
close $IN;
@@ -211,7 +208,7 @@ sub GdSecurityImageDoImage {
print $q->header(-type=>'image/png');
print $data;
unlink(GdSecurityImageGetImageFile($id));
Unlink(GdSecurityImageGetImageFile($id));
}
sub GdSecurityImageCleanup {
@@ -219,10 +216,10 @@ sub GdSecurityImageCleanup {
if (!GdSecurityImageIsValidId($id)) {
return;
}
my @files = (bsd_glob("$GdSecurityImageDir/*.png"), bsd_glob("$GdSecurityImageDir/*.ticket"));
my @files = (Glob("$GdSecurityImageDir/*.png"), Glob("$GdSecurityImageDir/*.ticket"));
foreach my $file (@files) {
if ($Now - (stat $file)[9] > $GdSecurityImageDuration) {
unlink($file);
if ($Now - Modified($file) > $GdSecurityImageDuration) {
Unlink($file);
}
}
}
@@ -255,7 +252,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;
chdir("$DataDir/page");
ChangeDir("$DataDir/page");
capture {
system($GitBinary, qw(add -A));
system($GitBinary, qw(commit -q -m), $message, "--author=$author <$GitMail>");
};
chdir($oldDir);
ChangeDir($oldDir);
}
sub GitInitRepository {
return if -d "$DataDir/page/.git";
return if IsDir("$DataDir/page/.git");
capture {
system($GitBinary, qw(init -q --), "$DataDir/page");
system($GitBinary, qw(init -q --), encode_utf8("$DataDir/page"));
};
GitCommit('Initial import', 'Oddmuse');
}

View File

@@ -80,7 +80,7 @@ sub GitRun {
my $exitStatus;
# warn join(' ', $GitBinary, @_) . "\n";
chdir($GitRepo);
ChangeDir($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, @_);
}
chdir($oldDir);
ChangeDir($oldDir);
return $exitStatus;
}
@@ -108,7 +108,7 @@ sub GitInitVariables {
}
sub GitInitRepository {
return if -d "$GitRepo/.git";
return if IsDir("$GitRepo/.git");
my $exception = shift;
CreateDir($GitRepo);
GitRun(qw(init --quiet));
@@ -187,17 +187,16 @@ sub DoGitCleanup {
}
sub GitCleanup {
if (-d $GitRepo) {
if (IsDir($GitRepo)) {
print $q->p('Git cleanup starting');
AllPagesList();
# delete all the files including all the files starting with a dot
opendir(DIR, $GitRepo) or ReportError("cannot open directory $GitRepo: $!");
opendir(DIR, encode_utf8($GitRepo)) or ReportError("cannot open directory $GitRepo: $!");
foreach my $file (readdir(DIR)) {
my $name = $file;
utf8::decode($name); # filenames are bytes
my $name = decode_utf8($file);
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 -r "$HtmlTemplateDir/$f.html";
return "$HtmlTemplateDir/$f.html" if IsFile("$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 -r "$HtmlTemplateDir/$f.html";
return "$HtmlTemplateDir/$f.html" if IsFile("$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 (-f GetLockedPageFile($OpenPageName)) {
if (IsFile(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 -e "$ImagifyDir/$fileName";
system('convert', %ImagifyParams, "caption:$str", "$ImagifyDir/$fileName") unless IsFile("$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 -e $dvipngPath) {
if (not -e $convertPath) {
if (not IsFile($dvipngPath)) {
if (not IsFile($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 -f "$LatexDir/$hash.png" or -z "$LatexDir/$hash.png") { #If file doesn't exist or is zero bytes
if (not IsFile("$LatexDir/$hash.png") or ZeroSize("$LatexDir/$hash.png")) {
# Then create the image
# read template and replace <math>
mkdir($LatexDir) unless -d $LatexDir;
if (not -f $LatexDefaultTemplateName) {
open (my $F, '>', $LatexDefaultTemplateName) or return '[Unable to write template]';
CreateDir($LatexDir);
if (not IsFile($LatexDefaultTemplateName)) {
open (my $F, '>', encode_utf8($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 (-d $dir) {
unlink (bsd_glob('$dir/*'));
if (IsDir($dir)) {
Unlink((Glob("$dir/*")));
} else {
mkdir($dir) or return "[Unable to create $dir]";
CreateDir($dir);
}
chdir ($dir) or return "[Unable to switch to $dir]";
ChangeDir($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 -f 'srender1.png' and not -z 'srender1.png') {
if (not $error and IsFile('srender1.png') and not ZeroSize('srender1.png')) {
my $png = ReadFileOrDie("srender1.png");
WriteStringToFile ("$LatexDir/$hash.png", $png);
} else {
$error = "[Error retrieving image for $latex]";
}
}
unlink (glob('*'));
chdir ($LatexDir);
rmdir ($dir);
Unlink(glob('*'));
ChangeDir($LatexDir);
RemoveDir($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 -f GetLockedPageFile($id);
PrintPage($id) if IsFile(GetLockedPageFile($id));
}
if (not $raw) {
print $q->end_p(), $q->end_div();

View File

@@ -75,9 +75,9 @@ sub LoadLanguage {
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 (-r $file) {
if (IsFile($file)) {
do $file;
do "$ConfigFile-$Lang{$_}" if -r "$ConfigFile-$Lang{$_}";
do "$ConfigFile-$Lang{$_}" if IsFile("$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, $RssDir)) { # cleanup if they should expire anyway
foreach (readdir(DIR)) {
unlink "$RssDir/$_" if $Now - (stat($_))[9] > $LnCacheHours * 3600;
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;
}
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 - (stat($todo{$uri}))[9] < $LnCacheHours * 3600) {
if ($Now - Modified($todo{$uri}) < $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, $UncomfirmedPasswordFile, $EmailSenderAddress, $EmailCommand, $EmailRegExp, $NotifyPendingRegistrations, $EmailConfirmationMessage, $ResetPasswordMessage, $LogoutForm, $ResetForm, $ChangePassForm, $RequireCamelUserName, $UsernameRegExp);
our ($RegistrationForm, $MinimumPasswordLength, $RegistrationsMustBeApproved, $LoginForm, $PasswordFile, $PasswordFileToUse, $PendingPasswordFile, $RequireLoginToEdit, $ConfirmEmailAddress, $UnconfirmedPasswordFile, $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;
$UncomfirmedPasswordFile = "$DataDir/uncomfirmed" unless defined $UncomfirmedPasswordFile;
$UnconfirmedPasswordFile = "$DataDir/uncomfirmed" unless defined $UnconfirmedPasswordFile;
$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
? $UncomfirmedPasswordFile : $PasswordFileToUse;
? $UnconfirmedPasswordFile : $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, '<', $PasswordFile)) {
if (open (my $PASSWD, '<', encode_utf8($PasswordFile))) {
while ( <$PASSWD> ) {
if ($_ =~ /^$username:/) {
return 1;
@@ -338,7 +338,7 @@ sub UserExists {
}
if ($RegistrationsMustBeApproved) {
if (open (my $PASSWD, '<', $PendingPasswordFile)) {
if (open (my $PASSWD, '<', encode_utf8($PendingPasswordFile))) {
while ( <$PASSWD> ) {
if ($_ =~ /^$username:/) {
return 1;
@@ -349,7 +349,7 @@ sub UserExists {
}
if ($ConfirmEmailAddress) {
if (open (my $PASSWD, '<', $UncomfirmedPasswordFile)) {
if (open (my $PASSWD, '<', encode_utf8($UnconfirmedPasswordFile))) {
while ( <$PASSWD> ) {
if ($_ =~ /^$username:/) {
return 1;
@@ -490,14 +490,13 @@ sub ConfirmUser {
my ($username, $key) = @_;
my $FileToUse = $RegistrationsMustBeApproved
? $PendingPasswordFile : $PasswordFileToUse;
if (open(my $PASSWD, '<', $UncomfirmedPasswordFile)) {
if (open(my $PASSWD, '<', encode_utf8($UnconfirmedPasswordFile))) {
while (<$PASSWD>) {
if ($_ =~ /^$username:(.*):(.*)/) {
if (crypt($1,$key) eq $key) {
AddUser($username,$1,$2,$FileToUse);
close $PASSWD;
RemoveUser($username,$UncomfirmedPasswordFile);
RemoveUser($username,$UnconfirmedPasswordFile);
if ($RegistrationsMustBeApproved) {
SendNotification($username);
}
@@ -515,8 +514,7 @@ sub RemoveUser {
my %passwords = ();
my %emails = ();
if (open (my $PASSWD, '<', $FileToUse)) {
if (open (my $PASSWD, '<', encode_utf8($FileToUse))) {
while ( <$PASSWD> ) {
if ($_ =~ /^(.*):(.*):(.*)$/) {
next if ($1 eq $username);
@@ -599,8 +597,7 @@ sub ChangePassword {
my %passwords = ();
my %emails = ();
if (open (my $PASSWD, '<', $PasswordFile)) {
if (open (my $PASSWD, '<', encode_utf8($PasswordFile))) {
while ( <$PASSWD> ) {
if ($_ =~ /^(.*):(.*):(.*)$/) {
$passwords{$1}=$2;
@@ -612,7 +609,7 @@ sub ChangePassword {
$passwords{$user} = $hash;
open (my $PASSWD, '>', $PasswordFile);
open (my $PASSWD, '>', encode_utf8($PasswordFile));
foreach my $key ( sort keys(%passwords)) {
print $PASSWD "$key:$passwords{$key}:$emails{$key}\n";
}
@@ -719,7 +716,7 @@ sub DoApprovePending {
}
} else {
print '<ul>';
if (open(my $PASSWD, '<', $PendingPasswordFile)) {
if (open(my $PASSWD, '<', encode_utf8($PendingPasswordFile))) {
while (<$PASSWD>) {
if ($_ =~ /^(.*):(.*):(.*)$/) {
print '<li>' . ScriptLink("action=approve_pending;user=$1;",$1) . ' - ' . $3 . '</li>';
@@ -740,8 +737,7 @@ sub DoApprovePending {
sub ApproveUser {
my ($username) = @_;
if (open(my $PASSWD, '<', $PendingPasswordFile)) {
if (open(my $PASSWD, '<', encode_utf8($PendingPasswordFile))) {
while (<$PASSWD>) {
if ($_ =~ /^$username:(.*):(.*)/) {
AddUser($username,$1,$2,$PasswordFile);

View File

@@ -56,14 +56,12 @@ 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", $MailFile;
tie my %h, "DB_File", encode_utf8($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", $MailFile;
tie my %h, "DB_File", encode_utf8($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", $MailFile;
tie my %h, "DB_File", encode_utf8($MailFile);
my @result = split(/$FS/, UrlDecode($h{UrlEncode($mail)}));
untie %h;
@result = sort @result;
@@ -303,8 +303,7 @@ sub DoMailSubscriptionList {
'<ul>';
}
require DB_File;
tie my %h, "DB_File", $MailFile;
tie my %h, "DB_File", encode_utf8($MailFile);
foreach my $encodedkey (sort keys %h) {
my @values = sort split(/$FS/, UrlDecode($h{$encodedkey}));
my $key = UrlDecode($encodedkey);
@@ -383,7 +382,7 @@ sub MailSubscribe {
return unless $mail and @pages;
# open the DB file
require DB_File;
tie my %h, "DB_File", $MailFile;
tie my %h, "DB_File", encode_utf8($MailFile);
# add to the mail entry
my %subscriptions = map {$_=>1} split(/$FS/, UrlDecode($h{UrlEncode($mail)}));
for my $id (@pages) {
@@ -442,7 +441,7 @@ sub MailUnsubscribe {
my ($mail, @pages) = @_;
return unless $mail and @pages;
require DB_File;
tie my %h, "DB_File", $MailFile;
tie my %h, "DB_File", encode_utf8($MailFile);
my %subscriptions = map {$_=>1} split(/$FS/, UrlDecode($h{UrlEncode($mail)}));
foreach my $id (@pages) {
delete $subscriptions{$id};
@@ -481,8 +480,7 @@ sub DoMailMigration {
$q->start_div({-class=>'content mailmigrate'});
require DB_File;
tie my %h, "DB_File", $MailFile;
tie my %h, "DB_File", encode_utf8($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 = bsd_glob("$ModuleDir/*.p[ml].disabled");
my @disabledFiles = Glob("$ModuleDir/*.p[ml].disabled");
if (@disabledFiles == 0) {
print T('Test / Always enabled / Always disabled'), $q->br();
my @files = bsd_glob("$ModuleDir/*.p[ml]");
my @files = 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 = bsd_glob("$ModuleDir/*.p[ml]");
my @files = 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 (bsd_glob("$ModuleDir/*.p[ml].disabled")) { # reenable all modules
for (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 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]")) {
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]")) {
my $curModule = fileparse($_);
ProcessModule($curModule);
}
@@ -58,7 +58,7 @@ sub ModuleUpdaterAction {
}
sub ModuleUpdaterApply {
for (bsd_glob("$TempDir/*.p[ml]")) {
for (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 bsd_glob("$TempDir/*.p[ml]"); # XXX same as above
Unlink(Glob("$TempDir/*.p[ml]")); # XXX same as above
print $q->br(), $q->strong('Done!');
}
@@ -81,15 +81,14 @@ sub ProcessModule {
. ' If this is your own module, please contribute it to Oddmuse!'), $q->br();
return;
}
open my $fh, ">", "$TempDir/$module" or die("Could not open file. $!");
utf8::encode($moduleData);
open my $fh, ">:utf8", encode_utf8("$TempDir/$module") or die("Could not open file $TempDir/$module: $!");
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();
@@ -109,7 +108,5 @@ sub ProcessModule {
}
sub DoModuleDiff {
my $diff = `diff -U 3 -- \Q$_[0]\E \Q$_[1]\E`;
utf8::decode($diff); # needs decoding
return $diff;
decode_utf8(`diff -U 3 -- \Q$_[0]\E \Q$_[1]\E`);
}

View File

@@ -85,9 +85,8 @@ sub NamespacesInitVariables {
# Do this before changing the $DataDir and $ScriptName
if ($UsePathInfo) {
$Namespaces{$NamespacesMain} = $ScriptName . '/';
foreach my $name (bsd_glob("$DataDir/*")) {
utf8::decode($name);
if (-d $name
foreach my $name (Glob("$DataDir/*")) {
if (IsDir($name)
and $name =~ m|/($InterSitePattern)$|
and $name ne $NamespacesMain
and $name ne $NamespacesSelf) {
@@ -99,8 +98,7 @@ sub NamespacesInitVariables {
$NamespaceCurrent = '';
my $ns = GetParam('ns', '');
if (not $ns and $UsePathInfo) {
my $path_info = $q->path_info();
utf8::decode($path_info);
my $path_info = decode_utf8($q->path_info());
# make sure ordinary page names are not matched!
if ($path_info =~ m|^/($InterSitePattern)(/.*)?|
and ($2 or $q->keywords or NamespaceRequiredByParameter())) {
@@ -137,7 +135,7 @@ sub NamespacesInitVariables {
$StaticUrl .= UrlEncode($NamespaceCurrent) . '/'
if substr($StaticUrl,-1) eq '/'; # from static-copy.pl
$WikiDescription .= "<p>Current namespace: $NamespaceCurrent</p>";
$LastUpdate = (stat($IndexFile))[9];
$LastUpdate = Modified($IndexFile);
CreateDir($DataDir);
}
$Namespaces{$NamespacesSelf} = $ScriptName . '?';
@@ -219,19 +217,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 $file (@rcfiles) {
open(my $F, '<:encoding(UTF-8)', $file);
foreach my $rcfile (@rcfiles) {
open(my $F, '<:encoding(UTF-8)', encode_utf8($rcfile));
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{$file}, $starttime,\%match, \%following));
push(@new, GetRcLinesFor($rcoldfiles{$rcfile}, $starttime,\%match, \%following));
}
push(@new, GetRcLinesFor($file, $starttime, \%match, \%following));
push(@new, GetRcLinesFor($rcfile, $starttime, \%match, \%following));
# strip rollbacks in each namespace separately
@new = StripRollbacks(@new);
# prepend the namespace to both pagename and author
my $ns = $namespaces{$file};
my $ns = $namespaces{$rcfile};
if ($ns) {
for (my $i = $#new; $i >= 0; $i--) {
# page id
@@ -436,8 +434,6 @@ 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.
if ($UsePathInfo and $id eq $NamespaceCurrent and not GetParam($id) and not GetParam('ns')) {
$id = undef;
}
return if $id and $UsePathInfo and $id eq $NamespaceCurrent and not GetParam($id) and not GetParam('ns');
return $id;
}

View File

@@ -18,7 +18,7 @@ use v5.10;
AddModuleDescription('near-links.pl', 'Near Links');
our ($q, %AdminPages, %InterSite, $CommentsPrefix, $DataDir, $UseCache, @MyFooters, @MyMaintenance, @MyInitVariables, @Debugging, $InterSitePattern, @UserGotoBarPages, @IndexOptions);
our ($q, $Now, %AdminPages, %InterSite, $CommentsPrefix, $DataDir, $UseCache, @MyFooters, @MyMaintenance, @MyInitVariables, @Debugging, $InterSitePattern, @UserGotoBarPages, @IndexOptions);
=head1 Near Links
@@ -128,7 +128,8 @@ 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 -f "$NearDir/$site" and -M "$NearDir/$site" < 0.5;
and IsFile("$NearDir/$site")
and $Now - Modified("$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(bsd_glob("$NotFoundHandlerDir/*"));
Unlink(Glob("$NotFoundHandlerDir/*"));
print $q->p(T('Done.'));
PrintFooter();
}
@@ -45,7 +45,7 @@ sub DoClearCache {
sub ReadLinkDb {
return if $LinkDbInit;
$LinkDbInit = 1;
return if not -f $LinkFile;
return if not IsFile($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 -d $NotFoundHandlerDir) {
mkdir($NotFoundHandlerDir);
if (not IsDir($NotFoundHandlerDir)) {
CreateDir($NotFoundHandlerDir);
} elsif ($Page{revision} == 1) {
NotFoundHandlerCacheUpdate($id);
} else {
# unlink PageName, PageName.en, PageName.de, etc.
unlink("$NotFoundHandlerDir/$id", bsd_glob("$NotFoundHandlerDir/$id.[a-z][a-z]"));
Unlink("$NotFoundHandlerDir/$id", 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", bsd_glob("$NotFoundHandlerDir/$source.[a-z][a-z]"));
Unlink("$NotFoundHandlerDir/$source", 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 UTF-X strings (after utf8::encode)
sub PadTo16Bytes { # use this only on bytes (after encode_utf8)
my ($data, $minLength) = @_;
my $endBytes = length($data) % 16;
$data .= "\0" x (16 - $endBytes) if $endBytes != 0;
@@ -62,8 +62,7 @@ sub NewPrivateWikiReadFile {
$q->p($errorMessage)) if not UserIsEditor();
PrivateWikiInit();
my $file = shift;
utf8::encode($file); # filenames are bytes!
if (open(my $IN, '<', $file)) {
if (open(my $IN, '<', encode_utf8($file))) {
local $/ = undef; # Read complete files
my $data = <$IN>;
close $IN;
@@ -72,8 +71,7 @@ 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+$//;
utf8::decode($copy);
return (1, $copy);
return (1, decode_utf8($copy));
}
return (0, '');
}
@@ -86,13 +84,12 @@ sub NewPrivateWikiWriteStringToFile {
$q->p($errorMessage)) if not UserIsEditor();
PrivateWikiInit();
my ($file, $string) = @_;
utf8::encode($file);
open(my $OUT, '>', $file) or ReportError(Ts('Cannot write %s', $file) . ": $!", '500 INTERNAL SERVER ERROR');
utf8::encode($string);
open(my $OUT, '>', encode_utf8($file))
or ReportError(Ts('Cannot write %s', $file) . ": $!", '500 INTERNAL SERVER ERROR');
my $iv = $random->random_bytes(16);
$cipher->set_iv($iv);
print $OUT $iv;
print $OUT $cipher->encrypt(PadTo16Bytes $string);
print $OUT $cipher->encrypt(PadTo16Bytes(encode_utf8($string)));
close($OUT);
}
@@ -109,9 +106,9 @@ sub AppendStringToFile {
*RefreshIndex = \&NewPrivateWikiRefreshIndex;
sub NewPrivateWikiRefreshIndex {
if (not -f $IndexFile) { # Index file does not exist yet, this is a new wiki
if (not IsFile($IndexFile)) { # Index file does not exist yet, this is a new wiki
my $fh;
open($fh, '>', $IndexFile) or die "Unable to open file $IndexFile : $!"; # 'touch' equivalent
open($fh, '>', encode_utf8($IndexFile)) or die "Unable to open file $IndexFile : $!"; # 'touch' equivalent
close($fh) or die "Unable to close file : $IndexFile $!";
return;
}
@@ -163,8 +160,7 @@ sub GetPrivatePageFile {
}
$cipher->set_iv($iv);
# We cannot use full byte range because of the filesystem limits
utf8::encode($id);
my $returnName = unpack "H*", $iv . $cipher->encrypt(PadTo16Bytes $id, 96); # to hex string
my $returnName = unpack "H*", $iv . $cipher->encrypt(PadTo16Bytes(encode_utf8($id), 96)); # to hex string
return $returnName;
}
@@ -216,14 +212,13 @@ 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 -f $oldName; unlink $newName if -f $newName; };
$LockCleaners{'diff'} = sub { Unlink($oldName) if IsFile($oldName); Unlink($newName) if IsFile($newName); };
OldPrivateWikiWriteStringToFile($oldName, $_[0]); # CHANGED Here we use the old sub!
OldPrivateWikiWriteStringToFile($newName, $_[1]); # CHANGED
my $diff_out = `diff -- \Q$oldName\E \Q$newName\E`;
utf8::decode($diff_out); # needs decoding
my $diff_out = decode_utf8(`diff -- \Q$oldName\E \Q$newName\E`);
$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;
}
@@ -237,15 +232,14 @@ 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 -f $name1; unlink $name2 if -f $name2; unlink $name3 if -f $name3;
Unlink($name1) if IsFile($name1); Unlink($name2) if IsFile($name2); Unlink($name3) if IsFile($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 = `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!
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!
ReleaseLockDir('merge');
return $output;
}

View File

@@ -52,11 +52,9 @@ sub DoPygmentize {
RequestLockDir('pygmentize') or return '';
WriteStringToFile("$TempDir/pygmentize", $contents);
my $output = `pygmentize $lexer -f html -O encoding=utf8 $args -- \Q$TempDir/pygmentize\E 2>&1`;
my $output = decode_utf8(`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 (-f $fname);
Unlink($fname) if (IsFile($fname));
return ''; # no error
}
@@ -79,7 +79,7 @@ sub GetRefererFile {
sub ReadReferers {
my $file = GetRefererFile(shift);
%Referers = ();
if (-f $file) {
if (IsFile($file)) {
my ($status, $data) = ReadFile($file);
%Referers = split(/$FS/, $data, -1) if $status;
}
@@ -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, '<', "$DataDir/$referencefile") || return(0);
open (my $RRR, '<', encode_utf8("$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, '>', "$DataDir/$referencefile");
open (my $RRR, '>', encode_utf8("$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, $SmileyDir)) {
if (opendir(DIR, encode_utf8($SmileyDir))) {
map {
if (/^((.*)\.$ImageExtensions$)/ and -f "$SmileyDir/$_") {
if (/^((.*)\.$ImageExtensions$)/ and IsFile("$SmileyDir/$_")) {
my $regexp = quotemeta("{$2}");
$Smilies{$regexp} = "$SmileyUrlPath/$1";
}

View File

@@ -131,23 +131,25 @@ 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;
open(my $fh, '>', "$StaticDir/$filename")
or ReportError(Ts('Cannot write %s', $filename));
$Page{text} =~ /^\#FILE ([^ \n]+) ?([^ \n]*)\n(.*)/s;
my $filename = StaticFileName($id);
my $file = "$StaticDir/$filename";
if ($data) {
binmode($fh);
open(my $fh, '>', encode_utf8($file))
or ReportError(Ts('Cannot write %s', $filename));
StaticFile($id, $fh, $mimetype, $data);
close($fh);
} elsif ($html) {
binmode($fh, ':encoding(UTF-8)');
open(my $fh, '>:encoding(UTF-8)', encode_utf8($file))
or ReportError(Ts('Cannot write %s', $filename));
StaticHtml($id, $fh);
close($fh);
} else {
print "no data for ";
}
close($fh);
chmod 0644,"$StaticDir/$filename";
ChangeMod(0644,"$StaticDir/$filename");
print $filename, $raw ? "\n" : $q->br();
}
@@ -279,7 +281,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';
my $StaticMimeTypes = '/etc/http/mime.types'; # all-ASCII characters
my %StaticFiles;
my $StaticAction = 0; # Are we doing action or not?
@@ -133,12 +133,11 @@ 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;
open(my $F, '>', "$StaticDir/$filename") or ReportError(Ts('Cannot write %s', $filename));
my $filename = StaticFileName($id);
open(my $F, '>', encode_utf8("$StaticDir/$filename")) or ReportError(Ts('Cannot write %s', $filename));
if ($data) {
StaticFile($id, $mimetype, $data, $F);
} elsif ($html) {
@@ -241,7 +240,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, %Action, %Page, $FreeLinkPattern, @MyInitVariables, @MyRules, @MyAdminCode, $DataDir, $ScriptName);
our ($q, $Now, %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($TagFile) } if -f $TagFile;
return %{ Storable::retrieve(encode_utf8($TagFile)) } if IsFile($TagFile);
}
@@ -92,7 +92,7 @@ sub TagReadHash {
sub TagWriteHash {
my $h = shift;
require Storable;
return Storable::store($h, $TagFile);
return Storable::store($h, encode_utf8($TagFile));
}
push(@MyRules, \&TagsRule);
@@ -338,7 +338,9 @@ Example:
$Action{reindex} = \&DoTagsReindex;
sub DoTagsReindex {
if (!UserIsAdmin() && (-f $TagFile) && ((-M $TagFile) < 0.5)) {
if (not UserIsAdmin()
and IsFile($TagFile)
and $Now - Modified($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 = bsd_glob($InstanceThrottleDir."/*");
my @pids = 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 (! -e "$ThumbnailCacheDir/$id/$size")
if (!IsFile("$ThumbnailCacheDir/$id/$size"))
{
GenerateThumbNail ($id, $size);
}
@@ -209,10 +209,8 @@ sub GenerateThumbNail {
my $filename = $ThumbnailTempDir . "/odd" . $id . "_" . $size;
# Decode the original image to a temp file
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');
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');
binmode($FD);
print $FD MIME::Base64::decode($data);
close($FD);
@@ -249,7 +247,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,18 +229,10 @@ my $TocCommentPattern = qr~\Q<!-- toc\E.*?\Q -->\E~;
# appropriate, and then printed at the very end.
sub NewTocApplyRules {
my ($html, $blocks, $flags);
{
local *STDOUT;
my $html_unfixed;
open( STDOUT, '>', \$html_unfixed) or die "Can't open memory file: $!";
binmode STDOUT, ":encoding(UTF-8)";
$html = ToString(sub{
# pass arguments on to OldTocApplyRules given that ToString takes a code ref
($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) {

View File

@@ -62,44 +62,40 @@ sub DoUpgrade {
print GetHeader('', T('Upgrading Database')),
$q->start_div({-class=>'content upgrade'});
if (-e $IndexFile) {
unlink $IndexFile;
if (IsFile($IndexFile)) {
Unlink($IndexFile);
}
print "<p>Renaming files...";
for my $ns ('', keys %InterSite) {
next unless -d "$DataDir/$ns";
next unless IsDir("$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 (bsd_glob("$dir/*/*"), bsd_glob("$dir/*/.*")) {
for my $old (Glob("$dir/*/*"), Glob("$dir/*/.*")) {
next if $old =~ /\/\.\.?$/;
my $oldname = $old;
utf8::decode($oldname);
print "<br />\n$oldname";
print "<br />\n$old";
my $new = $old;
$new =~ s!/([A-Z]|other)/!/!;
if ($old eq $new) {
print " does not fit the pattern!";
} elsif (not rename $old, $new) {
my $newname = $new;
utf8::decode($newname);
print " → $newname failed!";
} elsif (not Rename($old, $new)) {
print " → $new failed!";
}
}
for my $subdir (grep(/\/([A-Z]|other)$/, bsd_glob("$dir/*"), bsd_glob("$dir/.*"))) {
for my $subdir (grep(/\/([A-Z]|other)$/, Glob("$dir/*"), Glob("$dir/.*"))) {
next if substr($subdir, -2) eq '/.' or substr($subdir, -3) eq '/..';
rmdir $subdir; # ignore errors
RemoveDir($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 (-e "$uploadDir/$curFilename") { # keep adding random characters until we get unique filename
while (IsFile("$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,14 +71,12 @@ for (my $i=0; $q->param("fileToUpload$i"); $i++) {
} else {
squeak 'Error: Filename contains invalid characters'; # this should not happen
}
open(my $LOGFILE, '>>', $logFile) or squeak "$!";
open(my $LOGFILE, '>>', encode_utf8($logFile)) or squeak "$!";
print $LOGFILE $q->param("key") . ' ' . $ENV{REMOTE_ADDR} . ' ' . $curFilename . "\n";
close $LOGFILE;
my $uploadFileHandle = $q->upload("fileToUpload$i");
open(my $UPLOADFILE, '>', "$uploadDir/$curFilename") or squeak "$!";
open(my $UPLOADFILE, '>', encode_utf8("$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 = (stat($OddMuse::WebDavCache))[9];
my $update = Modified($OddMuse::WebDavCache);
if ($update and $OddMuse::LastUpdate == $update) {
my $data = OddMuse::ReadFileOrDie($OddMuse::WebDavCache);
map {

122
scripts/tarballs.pl Normal file
View File

@@ -0,0 +1,122 @@
#!/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

@@ -1,25 +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 => 3;
# editing pages
test_page(get_page('Test'),
'<title>Wiki: Test</title>',
'Status: 404 NOT FOUND');
test_page(update_page('Test', 'Muuu!', 'first edit', undef, undef,
'username=Alex'),
'<p>Muuu!</p>');

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,13 +20,14 @@ use utf8;
package OddMuse;
require 't/test.pl';
use Test::More tests => 11;
use Test::More tests => 29;
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;
@@ -35,20 +36,19 @@ unless (ok(@badModules == 0, 'Consistent file permissions of modules')) {
diag("▶▶▶ Use this command to fix it: chmod 644 @badModules");
}
@badModules = grep { ReadFile($_) !~ / ^ use \s+ strict; /mx } @modules;
@badModules = grep { $text{$_} !~ / ^ use \s+ strict; /mx } @modules;
unless (ok(@badModules == 0, '"use strict;" in modules')) {
diag(qq{$_ has no "use strict;"}) for @badModules;
}
@badModules = grep { ReadFile($_) !~ / ^ use \s+ v5\.10; /mx } @modules;
@badModules = grep { $text{$_} !~ / ^ 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 = ReadFile($_);
# warn "Looking at $_: " . length($code);
my $code = $text{$_};
# 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 { ReadFile($_) !~ / ^ AddModuleDescription\(' [^\']+ ', /mx } @modules;
@badModules = grep { $text{$_} !~ / ^ 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 { ReadFile($_) =~ / ^ package \s+ OddMuse; /imx } @modules;
@badModules = grep { $text{$_} =~ / ^ 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 { ReadFile($_) =~ / ^ use \s+ vars /mx } @modules;
@badModules = grep { $text{$_} =~ / ^ 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 { ReadFile($_) =~ / [ \t]+ $ /mx } @modules, @other;
@badModules = grep { $text{$_} =~ / [ \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 { ReadFile($_) =~ / This (program|file) is free software /x } @modules;
@badModules = grep { $text{$_} =~ / 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');
ReadFile($_) !~ /^AddModuleDescription\('$name.pl'/mx;
$text{$_} !~ /^AddModuleDescription\('$name.pl'/mx;
} @modules;
unless (ok(@badModules == 0, 'AddModuleDescription is used in every module')) {
diag(qq{$_ does not use AddModuleDescription}) for @badModules;
@@ -116,3 +116,49 @@ 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

@@ -52,18 +52,13 @@ $t->get_ok("$ScriptName/FiveWinds/Some_Page")
$t->get_ok("$ScriptName/Some_Page")
->content_like(qr/This is the Main namespace/);
TODO: {
local $TODO = "Some bug in namespaces.pl remains";
diag "Waiting for the lock dir in RefreshIndex...";
# 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/);
}
# 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

@@ -17,6 +17,7 @@ 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
@@ -56,8 +57,7 @@ $| = 1; # no output buffering
sub url_encode {
my $str = shift;
return '' unless $str;
utf8::encode($str); # turn to byte string
my @letters = split(//, $str);
my @letters = split(//, encode_utf8($str));
my @safe = ('a' .. 'z', 'A' .. 'Z', '0' .. '9', '-', '_', '.'); # shell metachars are unsafe
foreach my $letter (@letters) {
my $pattern = quotemeta($letter);
@@ -209,15 +209,8 @@ sub xpath_do {
skip("Cannot parse ".name($page).": $@", $#tests + 1) if $@;
foreach my $test (@tests) {
my $nodelist;
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) };
# libxml2 is not aware of UTF8 flag
eval { $nodelist = $doc->findnodes(encode_utf8($test)) };
if ($@) {
fail(&$check(1) ? "$test: $@" : "not $test: $@");
} elsif (ok(&$check($nodelist->size()),

View File

@@ -1,31 +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 => 3;
use utf8;
my $dir = $ENV{WikiDataDir};
$dir .= "-ä";
utf8::encode($dir); # bytes
$ENV{WikiDataDir} = $dir;
# editing pages
test_page(get_page('Test'),
'<title>Wiki: Test</title>',
'Status: 404 NOT FOUND');
test_page(update_page('Test', 'Muuu!', 'first edit', undef, undef,
'username=Alex'),
'<p>Muuu!</p>');

156
wiki.pl Executable file → Normal file
View File

@@ -38,6 +38,7 @@ 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)
@@ -66,7 +67,7 @@ our $UseConfig //= 1;
# Main wiki directory
our $DataDir;
$DataDir ||= $ENV{WikiDataDir} if $UseConfig;
$DataDir ||= decode_utf8($ENV{WikiDataDir}) if $UseConfig;
$DataDir ||= '/tmp/oddmuse'; # FIXME: /var/opt/oddmuse/wiki ?
our $ConfigFile;
@@ -227,17 +228,20 @@ sub Init {
}
sub InitModules {
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
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
}
}
}
}
sub InitConfig {
if ($UseConfig and $ConfigFile and not $INC{$ConfigFile} and -f $ConfigFile) {
if ($UseConfig and $ConfigFile and not $INC{$ConfigFile} and IsFile($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
}
@@ -250,7 +254,6 @@ 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
@@ -300,11 +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 = (stat($IndexFile))[9]; # always stat for multiple server processes
my $ts = Modified($IndexFile); # 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);
@IndexOptions = (['pages', T('Include normal pages'), 1, \&AllPagesList]);
foreach my $sub (@MyInitVariables) {
my $result = $sub->();
@@ -359,8 +363,7 @@ sub CookieRollbackFix {
sub GetParam {
my ($name, $default) = @_;
utf8::encode($name); # turn to byte string
my $result = $q->param($name);
my $result = $q->param(encode_utf8($name));
$result //= $default;
return QuoteHtml($result); # you need to unquote anything that can have <tags>
}
@@ -785,8 +788,7 @@ sub UnquoteHtml {
sub UrlEncode {
my $str = shift;
return '' unless $str;
utf8::encode($str); # turn to byte string
my @letters = split(//, $str);
my @letters = split(//, encode_utf8($str));
my %safe = map {$_ => 1} ('a' .. 'z', 'A' .. 'Z', '0' .. '9', '-', '_', '.', '!', '~', '*', "'", '(', ')', '#');
foreach my $letter (@letters) {
$letter = sprintf("%%%02x", ord($letter)) unless $safe{$letter};
@@ -796,8 +798,7 @@ sub UrlEncode {
sub UrlDecode {
my $str = shift;
$str =~ s/%([0-9a-f][0-9a-f])/chr(hex($1))/eg;
utf8::decode($str); # make internal string
return decode_utf8($str) if $str =~ s/%([0-9a-f][0-9a-f])/chr(hex($1))/eg;
return $str;
}
@@ -1012,7 +1013,7 @@ sub GetRss {
my $str = '';
if (GetParam('cache', $UseCache) > 0) {
foreach my $uri (keys %todo) { # read cached rss files if possible
if ($Now - (stat($todo{$uri}))[9] < $RssCacheHours * 3600) {
if ($Now - Modified($todo{$uri}) < $RssCacheHours * 3600) {
$data{$uri} = ReadFile($todo{$uri});
delete($todo{$uri}); # no need to fetch them below
}
@@ -1255,16 +1256,14 @@ sub PrintPageDiff { # print diff for open page
}
sub ToString {
my ($sub_ref) = @_;
my $sub_ref = shift;
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;
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.
return decode_utf8($output);
}
sub PageHtml {
@@ -1298,17 +1297,11 @@ sub Tss {
sub GetId {
my $id = UnquoteHtml(GetParam('id', GetParam('title', ''))); # id=x or title=x -> x
if (not $id) {
my @keywords = $q->keywords;
foreach my $keyword (@keywords) {
utf8::decode($keyword);
}
$id ||= join('_', @keywords); # script?p+q -> p_q
if (not $id and $q->keywords) {
$id = decode_utf8(join('_', $q->keywords)); # script?p+q -> p_q
}
if ($UsePathInfo) {
my $path = $q->path_info;
utf8::decode($path);
my @path = split(/\//, $path);
if ($UsePathInfo and $q->path_info) {
my @path = map { decode_utf8($_) } split(/\//, $q->path_info);
$id ||= pop(@path); # script/p/q -> q
foreach my $p (@path) {
SetParam($p, 1); # script/p/q -> p=1
@@ -1503,7 +1496,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)', $RcFile)) {
if (open(my $F, '<:encoding(UTF-8)', encode_utf8($RcFile))) {
my $line = <$F>;
($ts) = split(/$FS/, $line); # the first timestamp in the regular rc file
}
@@ -1587,7 +1580,7 @@ sub GetRcLinesFor {
rcclusteronly rcfilteronly match lang followup);
# parsing and filtering
my @result = ();
open(my $F, '<:encoding(UTF-8)', $file) or return ();
open(my $F, '<:encoding(UTF-8)', encode_utf8($file)) or return ();
while (my $line = <$F>) {
chomp($line);
my ($ts, $id, $minor, $summary, $host, $username, $revision,
@@ -2124,8 +2117,8 @@ sub DoAdminPage {
push(@menu, ScriptLink('action=maintain', T('Run maintenance'), 'maintain')) if $Action{maintain};
my @locks;
for my $pattern (@KnownLocks) {
for my $name (bsd_glob $pattern) {
if (-d $LockDir . $name) {
for my $name (Glob($pattern)) {
if (IsDir($LockDir . $name)) {
push(@locks, $name);
}
}
@@ -2135,7 +2128,7 @@ sub DoAdminPage {
};
if (UserIsAdmin()) {
if ($Action{editlock}) {
if (-f "$DataDir/noedit") {
if (IsFile("$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'));
@@ -2143,7 +2136,7 @@ sub DoAdminPage {
}
if ($id and $Action{pagelock}) {
my $title = NormalToFree($id);
if (-f GetLockedPageFile($id)) {
if (IsFile(GetLockedPageFile($id))) {
push(@menu, ScriptLink('action=pagelock;set=0;id=' . UrlEncode($id),
Ts('Unlock %s', $title), 'pagelock 0'));
} else {
@@ -2603,10 +2596,9 @@ sub DoDiff { # Actualy call the diff program
RequestLockDir('diff') or return '';
WriteStringToFile($oldName, $_[0]);
WriteStringToFile($newName, $_[1]);
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.
my $diff_out = decode_utf8(`diff -- \Q$oldName\E \Q$newName\E`);
ReleaseLockDir('diff');
$diff_out =~ s/\n\K\\ No newline.*\n//g; # Get rid of common complaint.
# No need to unlink temp files--next diff will just overwrite.
return $diff_out;
}
@@ -2781,7 +2773,7 @@ sub GetKeepDir {
}
sub GetKeepFiles {
return bsd_glob(GetKeepDir(shift) . '/*.kp'); # files such as 1.kp, 2.kp, etc.
return Glob(GetKeepDir(shift) . '/*.kp'); # files such as 1.kp, 2.kp, etc.
}
sub GetKeepRevisions {
@@ -2842,14 +2834,12 @@ 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 {
my $file = shift;
utf8::encode($file); # filenames are bytes!
if (open(my $IN, '<:encoding(UTF-8)', $file)) {
if (open(my $IN, '<:encoding(UTF-8)', encode_utf8(shift))) {
local $/ = undef; # Read complete files
my $data=<$IN>;
close $IN;
@@ -2870,8 +2860,7 @@ sub ReadFileOrDie {
sub WriteStringToFile {
my ($file, $string) = @_;
utf8::encode($file);
open(my $OUT, '>:encoding(UTF-8)', $file)
open(my $OUT, '>:encoding(UTF-8)', encode_utf8($file))
or ReportError(Ts('Cannot write %s', $file) . ": $!", '500 INTERNAL SERVER ERROR');
print $OUT $string;
close($OUT);
@@ -2879,18 +2868,27 @@ sub WriteStringToFile {
sub AppendStringToFile {
my ($file, $string) = @_;
utf8::encode($file);
open(my $OUT, '>>:encoding(UTF-8)', $file)
open(my $OUT, '>>:encoding(UTF-8)', encode_utf8($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) = @_;
utf8::encode($newdir);
return if -d $newdir;
mkdir($newdir, 0775)
return if IsDir($newdir);
mkdir(encode_utf8($newdir), 0775)
or ReportError(Ts('Cannot create %s', $newdir) . ": $!", '500 INTERNAL SERVER ERROR');
}
@@ -2906,9 +2904,11 @@ sub RequestLockDir {
CreateDir($TempDir);
my $lock = $LockDir . $name;
my $n = 0;
while (mkdir($lock, 0555) == 0) {
# Cannot use CreateDir because we don't want to skip mkdir if the directory
# already exists.
while (mkdir(encode_utf8($lock), 0555) == 0) {
if ($n++ >= $tries) {
my $ts = (stat($lock))[9];
my $ts = Modified($lock);
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 +2941,9 @@ sub CleanLock {
}
sub ReleaseLockDir {
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.
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.
}
sub RequestLockOrError {
@@ -2957,7 +2957,7 @@ sub ReleaseLock {
sub ForceReleaseLock {
my $pattern = shift;
my $forced;
foreach my $name (bsd_glob $pattern) {
foreach my $name (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 +3226,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 -f GetLockedPageFile($id);
return 0 if $LockOnCreation{$id} and not -f GetPageFile($id); # new page
return 0 if $id ne '' and IsFile(GetLockedPageFile($id));
return 0 if $LockOnCreation{$id} and not IsFile(GetPageFile($id)); # new page
return 1 if UserIsEditor();
return 0 if not $EditAllowed or -f $NoEditFile;
return 0 if not $EditAllowed or IsFile($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 +3352,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 -f $IndexFile and ReadIndex();
return @IndexList if not $refresh and IsFile($IndexFile) and ReadIndex();
# If open fails just refresh the index
RefreshIndex();
return @IndexList;
@@ -3376,11 +3376,10 @@ sub RefreshIndex {
@IndexList = ();
%IndexHash = ();
# If file exists and cannot be changed, error!
my $locked = RequestLockDir('index', undef, undef, -f $IndexFile);
foreach (bsd_glob("$PageDir/*.pg"), bsd_glob("$PageDir/.*.pg")) {
my $locked = RequestLockDir('index', undef, undef, IsFile($IndexFile));
foreach (Glob("$PageDir/*.pg"), Glob("$PageDir/.*.pg")) {
next unless m|/.*/(.+)\.pg$|;
my $id = $1;
utf8::decode($id);
push(@IndexList, $id);
$IndexHash{$id} = 1;
}
@@ -3449,9 +3448,9 @@ sub PageIsUploadedFile {
return if $OpenPageName eq $id;
if ($IndexHash{$id}) {
my $file = GetPageFile($id);
utf8::encode($file); # filenames are bytes!
open(my $FILE, '<:encoding(UTF-8)', $file)
or ReportError(Ts('Cannot open %s', $file) . ": $!", '500 INTERNAL SERVER ERROR');
open(my $FILE, '<:encoding(UTF-8)', encode_utf8($file))
or ReportError(Ts('Cannot open %s', GetPageFile($id))
. ": $!", '500 INTERNAL SERVER ERROR');
while (defined($_ = <$FILE>) and $_ !~ /^text: /) {
} # read lines until we get to the text key
close $FILE;
@@ -3786,7 +3785,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 -e $IndexFile and not unlink($IndexFile)) { # regenerate index on next request
if ($revision == 1 and IsFile($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.'));
@@ -3846,8 +3845,7 @@ 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 = `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
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`);
ReleaseLockDir('merge'); # don't unlink temp files--next merge will just overwrite.
return $output;
}
@@ -3871,7 +3869,7 @@ sub DoMaintain {
print GetHeader('', T('Run Maintenance')), $q->start_div({-class=>'content maintain'});
my $fname = "$DataDir/maintain";
if (not UserIsAdmin()) {
if ((-f $fname) and ((-M $fname) < 0.5)) {
if (IsFile($fname) and $Now - Modified($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();
@@ -3914,7 +3912,7 @@ sub DoMaintain {
}
if (opendir(DIR, $RssDir)) { # cleanup if they should expire anyway
foreach (readdir(DIR)) {
unlink "$RssDir/$_" if $Now - (stat($_))[9] > $RssCacheHours * 3600;
Unlink("$RssDir/$_") if $Now - Modified($_) > $RssCacheHours * 3600;
}
closedir DIR;
}
@@ -3947,8 +3945,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 -f $name;
rmdir $name if -d $name;
Unlink($name) if IsFile($name);
RemoveDir($name) if IsDir($name);
}
ReInit($id);
delete $IndexHash{$id};
@@ -3963,10 +3961,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(-f $fname ? T('Edit lock created.') : T('Edit lock removed.'));
print $q->p(IsFile($fname) ? T('Edit lock created.') : T('Edit lock removed.'));
PrintFooter();
}
@@ -3979,10 +3977,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(-f $fname ? Ts('Lock for %s created.', GetPageLink($id))
print $q->p(IsFile($fname) ? Ts('Lock for %s created.', GetPageLink($id))
: Ts('Lock for %s removed.', GetPageLink($id)));
PrintFooter();
}