sub error ($) { #{{{
if ($cgi) {
print "Content-type: text/html\n\n";
- print "Error: @_\n";
+ print misctemplate("Error", "<p>Error: @_</p>");
exit 1;
}
else {
} #}}}
sub debug ($) { #{{{
- print "@_\n" if $verbose;
+ if (! $cgi) {
+ print "@_\n" if $verbose;
+ }
+ else {
+ print STDERR "@_\n" if $verbose;
+ }
} #}}}
sub mtime ($) { #{{{
}
}
- return @links;
+ return sort { $a->{page} cmp $b->{page} } @links;
} #}}}
sub parentlinks ($) { #{{{
}
$path.="../";
}
+ unshift @ret, { url => $path , page => $wikiname };
return @ret;
} #}}}
sub indexlink () { #{{{
- return "<a href=\"$url\">$wikiname</a>/ ";
+ return "<a href=\"$url\">$wikiname</a>";
} #}}}
sub finalize ($$) { #{{{
if (length $cgiurl) {
$template->param(editurl => "$cgiurl?do=edit&page=$page");
- $template->param(recentchangesurl => "$cgiurl?do=recentchanges");
+ if ($svn) {
+ $template->param(recentchangesurl => "$cgiurl?do=recentchanges");
+ }
}
if (length $historyurl) {
$template->param(
title => $title,
- indexlink => $url,
wikiname => $wikiname,
parentlinks => [parentlinks($page)],
content => $content,
return $template->output;
} #}}}
+# Important security check. Make sure to call this before saving any files
+# to the source directory.
+sub check_overwrite ($$) { #{{{
+ my $dest=shift;
+ my $src=shift;
+
+ if (! exists $renderedfiles{$src} && -e $dest) {
+ error("$dest exists and was not rendered from $src before, not overwriting");
+ }
+} #}}}
+
sub render ($) { #{{{
my $file=shift;
$content=htmlize($type, $content);
$content=finalize($content, $page);
+ check_overwrite("$destdir/".htmlpage($page), $page);
writefile("$destdir/".htmlpage($page), $content);
$oldpagemtime{$page}=time;
$renderedfiles{$page}=htmlpage($page);
}
else {
$links{$file}=[];
+ check_overwrite("$destdir/$file", $file);
writefile("$destdir/$file", $content);
$oldpagemtime{$file}=time;
$renderedfiles{$file}=$file;
my $div=qr/^--------------------+$/;
my $infoline=qr/^r(\d+)\s+\|\s+([^\s]+)\s+\|\s+(\d+-\d+-\d+\s+\d+:\d+:\d+\s+[-+]?\d+).*/;
my $state='start';
- my ($rev, $user, $when, @pages, $message);
+ my ($rev, $user, $when, @pages, @message);
foreach (`LANG=C svn log -v '$svn_url'`) {
chomp;
if ($state eq 'start' && /$div/) {
$state='body';
}
elsif ($state eq 'body' && /$div/) {
- push @ret, { rev => $rev, user => $user,
- when => $when, message => $message,
+ my $committype="web";
+ if (defined $message[0] &&
+ $message[0]->{line}=~/^web commit by (\w+):?(.*)/) {
+ $user="$1";
+ $message[0]->{line}=$2;
+ }
+ else {
+ $committype="svn";
+ }
+
+ push @ret, { rev => $rev,
+ user => htmllink("", $user, 1),
+ committype => $committype,
+ when => $when, message => [@message],
pages => [@pages] } if @pages;
return @ret if @ret >= $num;
$state='header';
- $message=$rev=$user=$when=undef;
- @pages=();
+ $rev=$user=$when=undef;
+ @pages=@message=();
}
elsif ($state eq 'body') {
- $message.="$_<br>\n";
+ push @message, {line => $_},
}
}
}
print "successfully generated ikiwiki-wrap\n";
exit 0;
} #}}}
+
+sub misctemplate ($$) { #{{{
+ my $title=shift;
+ my $pagebody=shift;
+
+ my $template=HTML::Template->new(
+ filename => "$templatedir/misc.tmpl"
+ );
+ $template->param(
+ title => $title,
+ indexlink => indexlink(),
+ wikiname => $wikiname,
+ pagebody => $pagebody,
+ );
+ return $template->output;
+}#}}}
-sub html_recentchanges ($q) { #{{{
+sub cgi_recentchanges ($) { #{{{
my $q=shift;
my $template=HTML::Template->new(
- filename => "$templatedir/recentchanges.tmpl");
+ filename => "$templatedir/recentchanges.tmpl"
+ );
$template->param(
title => "RecentChanges",
- indexlink => $url,
+ indexlink => indexlink(),
wikiname => $wikiname,
changelog => [rcs_recentchanges(100)],
);
print $q->header, $template->output;
} #}}}
+sub userinfo_get ($$) { #{{{
+ my $user=shift;
+ my $field=shift;
+
+ eval q{use Storable};
+ my $userdata=eval{ Storable::lock_retrieve("$srcdir/.ikiwiki/userdb") };
+ if (! defined $userdata || ! ref $userdata ||
+ ! exists $userdata->{$user} || ! ref $userdata->{$user}) {
+ return "";
+ }
+ return $userdata->{$user}->{$field};
+} #}}}
+
+sub userinfo_set ($$) { #{{{
+ my $user=shift;
+ my $info=shift;
+
+ eval q{use Storable};
+ my $userdata=eval{ Storable::lock_retrieve("$srcdir/.ikiwiki/userdb") };
+ if (! defined $userdata || ! ref $userdata) {
+ $userdata={};
+ }
+ $userdata->{$user}=$info;
+ my $oldmask=umask(077);
+ my $ret=Storable::lock_store($userdata, "$srcdir/.ikiwiki/userdb");
+ umask($oldmask);
+ return $ret;
+} #}}}
+
sub cgi_signin ($$) { #{{{
my $q=shift;
my $session=shift;
header => 1,
method => 'POST',
validate => {
- name => '/^\w+$/',
confirm_password => {
perl => q{eq $form->field("password")},
},
javascript => 0,
params => $q,
action => $q->request_uri,
+ header => 0,
+ template => (-e "$templatedir/signin.tmpl" ? "$templatedir/signin.tmpl" : "")
);
- $form->sessionid($session->id);
$form->field(name => "name", required => 0);
$form->field(name => "do", type => "hidden");
$form->field(name => "page", type => "hidden");
$form->field(name => "password", type => "password", required => 0);
$form->field(name => "confirm_password", type => "password", required => 0);
$form->field(name => "email", required => 0);
- if ($session->param("name")) {
- $form->field(name => "name", value => $session->param("name"));
- }
if ($q->param("do") ne "signin") {
$form->text("You need to log in before you can edit pages.");
}
$form->field(name => $opt, required => 1);
}
- # Validate password differently depending on how form was
- # submitted.
+ # Validate password differently depending on how
+ # form was submitted.
if ($form->submitted eq 'Login') {
$form->field(
name => "password",
validate => sub {
- # TODO get real user password
- shift eq "foo";
+ length $form->field("name") &&
+ shift eq userinfo_get($form->field("name"), 'password');
},
);
+ $form->field(name => "name", validate => '/^\w+$/');
}
else {
$form->field(name => "password", validate => 'VALUE');
}
+ # And make sure the entered name exists when logging
+ # in or sending email, and does not when registering.
+ if ($form->submitted eq 'Register') {
+ $form->field(
+ name => "name",
+ validate => sub {
+ my $name=shift;
+ length $name &&
+ ! userinfo_get($name, "regdate");
+ },
+ );
+ }
+ else {
+ $form->field(
+ name => "name",
+ validate => sub {
+ my $name=shift;
+ length $name &&
+ userinfo_get($name, "regdate");
+ },
+ );
+ }
}
else {
- # Comments only shown first time.
+ # First time settings.
$form->field(name => "name", comment => "use FirstnameLastName");
$form->field(name => "confirm_password", comment => "(only needed");
$form->field(name => "email", comment => "for registration)");
+ if ($session->param("name")) {
+ $form->field(name => "name", value => $session->param("name"));
+ }
}
if ($form->submitted && $form->validate) {
if ($form->submitted eq 'Login') {
$session->param("name", $form->field("name"));
- if (defined $form->field("do")) {
- $q->redirect(
+ if (defined $form->field("do") &&
+ $form->field("do") ne 'signin') {
+ print $q->redirect(
"$cgiurl?do=".$form->field("do").
"&page=".$form->field("page"));
}
else {
- $q->redirect($url);
+ print $q->redirect($url);
}
}
elsif ($form->submitted eq 'Register') {
- # TODO: save registration info
- $form->field(name => "confirm_password", type => "hidden");
- $form->field(name => "email", type => "hidden");
- $form->text("Registration successful. Now you can Login.");
- print $form->render(submit => ["Login"]);;
+ my $user_name=$form->field('name');
+ if (userinfo_set($user_name, {
+ 'email' => $form->field('email'),
+ 'password' => $form->field('password'),
+ 'regdate' => time
+ })) {
+ $form->field(name => "confirm_password", type => "hidden");
+ $form->field(name => "email", type => "hidden");
+ $form->text("Registration successful. Now you can Login.");
+ print $session->header();
+ print misctemplate($form->title, $form->render(submit => ["Login"]));
+ }
+ else {
+ error("Error saving registration.");
+ }
}
elsif ($form->submitted eq 'Mail Password') {
- # TODO mail password
+ my $user_name=$form->field("name");
+ my $template=HTML::Template->new(
+ filename => "$templatedir/passwordmail.tmpl"
+ );
+ $template->param(
+ user_name => $user_name,
+ user_password => userinfo_get($user_name, "password"),
+ wikiurl => $url,
+ wikiname => $wikiname,
+ REMOTE_ADDR => $ENV{REMOTE_ADDR},
+ );
+
+ eval q{use Mail::Sendmail};
+ my ($fromhost) = $cgiurl =~ m!/([^/]+)!;
+ print STDERR "$< $> >>> $cgiurl ".(getpwuid($>))[0]."@".$fromhost."\n";
+ sendmail(
+ To => userinfo_get($user_name, "email"),
+ From => "$wikiname admin <".(getpwuid($>))[0]."@".$fromhost.">",
+ Subject => "$wikiname information",
+ Message => $template->output,
+ ) or error("Failed to send mail");
+
$form->text("Your password has been emailed to you.");
- print $form->render(submit => ["Login", "Register", "Mail Password"]);;
+ $form->field(name => "name", required => 0);
+ print $session->header();
+ print misctemplate($form->title, $form->render(submit => ["Login", "Register", "Mail Password"]));
}
}
else {
- print $form->render(submit => ["Login", "Register", "Mail Password"]);;
+ print $session->header();
+ print misctemplate($form->title, $form->render(submit => ["Login", "Register", "Mail Password"]));
}
} #}}}
-sub cgi () { #{{{
- eval q{use CGI};
- eval q{use CGI::Session};
-
- my $q=CGI->new;
- # session id has to be _sessionid for CGI::FormBuilder to work.
- # TODO: stop having the formbuilder emit cookies and change session
- # id to something else.
- CGI::Session->name("_sessionid");
- my $session = CGI::Session->new(undef, $q,
- { Directory=> "$srcdir/.ikiwiki/sessions" });
-
- my $do=$q->param('do');
- if (! defined $do || ! length $do) {
- error("\"do\" parameter missing");
- }
-
- if ($do eq 'recentchanges') {
- cgi_recentchanges($q);
- return;
- }
-
- if ((! $anonok && ! defined $session->param("name")) || $do eq 'signin') {
- cgi_signin($q, $session);
- return;
- }
+sub cgi_editpage ($$) { #{{{
+ my $q=shift;
+ my $session=shift;
+
+ eval q{use CGI::FormBuilder};
+ my $form = CGI::FormBuilder->new(
+ fields => [qw(do from page content comments)],
+ header => 1,
+ method => 'POST',
+ validate => {
+ content => '/.+/',
+ },
+ required => [qw{content}],
+ javascript => 0,
+ params => $q,
+ action => $q->request_uri,
+ table => 0,
+ template => "$templatedir/editpage.tmpl"
+ );
- my ($page)=$q->param('page')=~/$wiki_file_regexp/;
+ my ($page)=$form->param('page')=~/$wiki_file_regexp/;
if (! defined $page || ! length $page || $page ne $q->param('page') ||
$page=~/$wiki_file_prune_regexp/ || $page=~/^\//) {
error("bad page name");
}
$page=lc($page);
-
- my $action=$q->request_uri;
- $action=~s/\?.*//;
-
- if ($do eq 'create') {
- if (exists $pagesources{lc($page)}) {
- # hmm, someone else made the page in the meantime?
- print $q->redirect("$url/".htmlpage($page));
- }
- my @page_locs;
- my ($from)=$q->param('from')=~/$wiki_file_regexp/;
- if (! defined $from || ! length $from ||
- $from ne $q->param('from') ||
- $from=~/$wiki_file_prune_regexp/ || $from=~/^\//) {
- @page_locs=$page;
- }
- else {
- my $dir=$from."/";
- $dir=~s![^/]+/$!!;
- push @page_locs, $dir.$page;
- push @page_locs, "$from/$page";
- while (length $dir) {
+ $form->field(name => "do", type => 'hidden');
+ $form->field(name => "from", type => 'hidden');
+ $form->field(name => "page", value => "$page", force => 1);
+ $form->field(name => "comments", type => "text", size => 80);
+ $form->field(name => "content", type => "textarea", rows => 20,
+ cols => 80);
+
+ if ($form->submitted eq "Cancel") {
+ print $q->redirect("$url/".htmlpage($page));
+ return;
+ }
+ if (! $form->submitted || ! $form->validate) {
+ if ($form->field("do") eq "create") {
+ if (exists $pagesources{lc($page)}) {
+ # hmm, someone else made the page in the
+ # meantime?
+ print $q->redirect("$url/".htmlpage($page));
+ return;
+ }
+
+ my @page_locs;
+ my ($from)=$form->param('from')=~/$wiki_file_regexp/;
+ if (! defined $from || ! length $from ||
+ $from ne $form->param('from') ||
+ $from=~/$wiki_file_prune_regexp/ || $from=~/^\//) {
+ @page_locs=$page;
+ }
+ else {
+ my $dir=$from."/";
$dir=~s![^/]+/$!!;
push @page_locs, $dir.$page;
+ push @page_locs, "$from/$page";
+ while (length $dir) {
+ $dir=~s![^/]+/$!!;
+ push @page_locs, $dir.$page;
+ }
+ }
+
+ $form->tmpl_param("page_select", 1);
+ $form->field(name => "page", type => 'select',
+ options => \@page_locs);
+ $form->title("creating $page");
+ }
+ elsif ($form->field("do") eq "edit") {
+ my $content="";
+ if (exists $pagesources{lc($page)}) {
+ $content=readfile("$srcdir/$pagesources{lc($page)}");
+ $content=~s/\n/\r\n/g;
}
+ $form->tmpl_param("page_select", 0);
+ $form->field(name => "content", value => $content,
+ force => 1);
+ $form->field(name => "page", type => 'hidden');
+ $form->title("editing $page");
}
- $q->param("do", "save");
- print $q->header,
- $q->start_html("Creating $page"),
- $q->h1(indexlink()." Creating $page"),
- $q->start_form(-action => $action),
- $q->hidden('do'),
- "Select page location:",
- $q->popup_menu('page', \@page_locs),
- $q->textarea(-name => 'content',
- -default => "",
- -rows => 20,
- -columns => 80),
- $q->br,
- "Optional comment about this change:",
- $q->br,
- $q->textfield(-name => "comments", -size => 80),
- $q->br,
- $q->submit("Save Page"),
- $q->end_form,
- $q->end_html;
- }
- elsif ($do eq 'edit') {
- my $content="";
- if (exists $pagesources{lc($page)}) {
- $content=readfile("$srcdir/$pagesources{lc($page)}");
- $content=~s/\n/\r\n/g;
- }
- $q->param("do", "save");
- print $q->header,
- $q->start_html("Editing $page"),
- $q->h1(indexlink()." Editing $page"),
- $q->start_form(-action => $action),
- $q->hidden('do'),
- $q->hidden('page'),
- $q->textarea(-name => 'content',
- -default => $content,
- -rows => 20,
- -columns => 80),
- $q->br,
- "Optional comment about this change:",
- $q->br,
- $q->textfield(-name => "comments", -size => 80),
- $q->br,
- $q->submit("Save Page"),
- $q->end_form,
- $q->end_html;
- }
- elsif ($do eq 'save') {
+ $form->tmpl_param("can_commit", $svn);
+ $form->tmpl_param("indexlink", indexlink());
+ print $form->render(submit => ["Save Page", "Cancel"]);
+ }
+ else {
+ # save page
my $file=$page.$default_pagetype;
my $newfile=1;
if (exists $pagesources{lc($page)}) {
$newfile=0;
}
- my $content=$q->param('content');
+ my $content=$form->field('content');
$content=~s/\r\n/\n/g;
$content=~s/\r/\n/g;
writefile("$srcdir/$file", $content);
- my $message="web commit from $ENV{REMOTE_ADDR}";
- if (defined $q->param('comments')) {
- $message.=": ".$q->param('comments');
+ my $message="web commit ";
+ if ($session->param("name")) {
+ $message.="by ".$session->param("name");
+ }
+ else {
+ $message.="from $ENV{REMOTE_ADDR}";
+ }
+ if (defined $form->field('comments') &&
+ length $form->field('comments')) {
+ $message.=": ".$form->field('comments');
}
if ($svn) {
refresh();
}
- print $q->redirect("$url/".htmlpage($page));
+ # The trailing question mark tries to avoid broken
+ # caches and get the most recent version of the page.
+ print $q->redirect("$url/".htmlpage($page)."?");
+ }
+} #}}}
+
+sub cgi () { #{{{
+ eval q{use CGI};
+ eval q{use CGI::Session};
+
+ my $q=CGI->new;
+
+ my $do=$q->param('do');
+ if (! defined $do || ! length $do) {
+ error("\"do\" parameter missing");
+ }
+
+ # This does not need a session.
+ if ($do eq 'recentchanges') {
+ cgi_recentchanges($q);
+ return;
+ }
+
+ CGI::Session->name("ikiwiki_session");
+
+ my $oldmask=umask(077);
+ my $session = CGI::Session->new("driver:db_file", $q,
+ { FileName => "$srcdir/.ikiwiki/sessions.db" });
+ umask($oldmask);
+
+ # Everything below this point needs the user to be signed in.
+ if ((! $anonok && ! defined $session->param("name") ||
+ ! userinfo_get($session->param("name"), "regdate")) || $do eq 'signin') {
+ cgi_signin($q, $session);
+
+ # Force session flush with safe umask.
+ my $oldmask=umask(077);
+ $session->flush;
+ umask($oldmask);
+
+ return;
+ }
+
+ if ($do eq 'create' || $do eq 'edit') {
+ cgi_editpage($q, $session);
}
else {
error("unknown do parameter");