summaryrefslogtreecommitdiff
path: root/IkiWiki/CGI.pm
blob: a8f54c781fc91c33fca5bfc85ae7edc896907860 (plain)
  1. #!/usr/bin/perl
  2. use warnings;
  3. use strict;
  4. package IkiWiki;
  5. sub page_locked ($$;$) { #{{{
  6. my $page=shift;
  7. my $session=shift;
  8. my $nonfatal=shift;
  9. my $user=$session->param("name");
  10. return if length $user && is_admin($user);
  11. foreach my $admin (@{$config{adminuser}}) {
  12. my $locked_pages=userinfo_get($admin, "locked_pages");
  13. if (globlist_match($page, userinfo_get($admin, "locked_pages"))) {
  14. return 1 if $nonfatal;
  15. error(htmllink("", $page, 1)." is locked by ".
  16. htmllink("", $admin, 1)." and cannot be edited.");
  17. }
  18. }
  19. return 0;
  20. } #}}}
  21. sub cgi_recentchanges ($) { #{{{
  22. my $q=shift;
  23. my $template=HTML::Template->new(
  24. filename => "$config{templatedir}/recentchanges.tmpl"
  25. );
  26. $template->param(
  27. title => "RecentChanges",
  28. indexlink => indexlink(),
  29. wikiname => $config{wikiname},
  30. changelog => [rcs_recentchanges(100)],
  31. );
  32. print $q->header, $template->output;
  33. } #}}}
  34. sub cgi_signin ($$) { #{{{
  35. my $q=shift;
  36. my $session=shift;
  37. eval q{use CGI::FormBuilder};
  38. my $form = CGI::FormBuilder->new(
  39. title => "signin",
  40. fields => [qw(do title page subpage from name password confirm_password email)],
  41. header => 1,
  42. method => 'POST',
  43. validate => {
  44. confirm_password => {
  45. perl => q{eq $form->field("password")},
  46. },
  47. email => 'EMAIL',
  48. },
  49. required => 'NONE',
  50. javascript => 0,
  51. params => $q,
  52. action => $q->request_uri,
  53. header => 0,
  54. template => (-e "$config{templatedir}/signin.tmpl" ?
  55. "$config{templatedir}/signin.tmpl" : "")
  56. );
  57. $form->field(name => "name", required => 0);
  58. $form->field(name => "do", type => "hidden");
  59. $form->field(name => "page", type => "hidden");
  60. $form->field(name => "title", type => "hidden");
  61. $form->field(name => "from", type => "hidden");
  62. $form->field(name => "subpage", type => "hidden");
  63. $form->field(name => "password", type => "password", required => 0);
  64. $form->field(name => "confirm_password", type => "password", required => 0);
  65. $form->field(name => "email", required => 0);
  66. if ($q->param("do") ne "signin") {
  67. $form->text("You need to log in first.");
  68. }
  69. if ($form->submitted) {
  70. # Set required fields based on how form was submitted.
  71. my %required=(
  72. "Login" => [qw(name password)],
  73. "Register" => [qw(name password confirm_password email)],
  74. "Mail Password" => [qw(name)],
  75. );
  76. foreach my $opt (@{$required{$form->submitted}}) {
  77. $form->field(name => $opt, required => 1);
  78. }
  79. # Validate password differently depending on how
  80. # form was submitted.
  81. if ($form->submitted eq 'Login') {
  82. $form->field(
  83. name => "password",
  84. validate => sub {
  85. length $form->field("name") &&
  86. shift eq userinfo_get($form->field("name"), 'password');
  87. },
  88. );
  89. $form->field(name => "name", validate => '/^\w+$/');
  90. }
  91. else {
  92. $form->field(name => "password", validate => 'VALUE');
  93. }
  94. # And make sure the entered name exists when logging
  95. # in or sending email, and does not when registering.
  96. if ($form->submitted eq 'Register') {
  97. $form->field(
  98. name => "name",
  99. validate => sub {
  100. my $name=shift;
  101. length $name &&
  102. ! userinfo_get($name, "regdate");
  103. },
  104. );
  105. }
  106. else {
  107. $form->field(
  108. name => "name",
  109. validate => sub {
  110. my $name=shift;
  111. length $name &&
  112. userinfo_get($name, "regdate");
  113. },
  114. );
  115. }
  116. }
  117. else {
  118. # First time settings.
  119. $form->field(name => "name", comment => "use FirstnameLastName");
  120. $form->field(name => "confirm_password", comment => "(only needed");
  121. $form->field(name => "email", comment => "for registration)");
  122. if ($session->param("name")) {
  123. $form->field(name => "name", value => $session->param("name"));
  124. }
  125. }
  126. if ($form->submitted && $form->validate) {
  127. if ($form->submitted eq 'Login') {
  128. $session->param("name", $form->field("name"));
  129. if (defined $form->field("do") &&
  130. $form->field("do") ne 'signin') {
  131. print $q->redirect(
  132. "$config{cgiurl}?do=".$form->field("do").
  133. "&page=".$form->field("page").
  134. "&title=".$form->field("title").
  135. "&subpage=".$form->field("subpage").
  136. "&from=".$form->field("from"));;
  137. }
  138. else {
  139. print $q->redirect($config{url});
  140. }
  141. }
  142. elsif ($form->submitted eq 'Register') {
  143. my $user_name=$form->field('name');
  144. if (userinfo_setall($user_name, {
  145. 'email' => $form->field('email'),
  146. 'password' => $form->field('password'),
  147. 'regdate' => time
  148. })) {
  149. $form->field(name => "confirm_password", type => "hidden");
  150. $form->field(name => "email", type => "hidden");
  151. $form->text("Registration successful. Now you can Login.");
  152. print $session->header();
  153. print misctemplate($form->title, $form->render(submit => ["Login"]));
  154. }
  155. else {
  156. error("Error saving registration.");
  157. }
  158. }
  159. elsif ($form->submitted eq 'Mail Password') {
  160. my $user_name=$form->field("name");
  161. my $template=HTML::Template->new(
  162. filename => "$config{templatedir}/passwordmail.tmpl"
  163. );
  164. $template->param(
  165. user_name => $user_name,
  166. user_password => userinfo_get($user_name, "password"),
  167. wikiurl => $config{url},
  168. wikiname => $config{wikiname},
  169. REMOTE_ADDR => $ENV{REMOTE_ADDR},
  170. );
  171. eval q{use Mail::Sendmail};
  172. my ($fromhost) = $config{cgiurl} =~ m!/([^/]+)!;
  173. sendmail(
  174. To => userinfo_get($user_name, "email"),
  175. From => "$config{wikiname} admin <".(getpwuid($>))[0]."@".$fromhost.">",
  176. Subject => "$config{wikiname} information",
  177. Message => $template->output,
  178. ) or error("Failed to send mail");
  179. $form->text("Your password has been emailed to you.");
  180. $form->field(name => "name", required => 0);
  181. print $session->header();
  182. print misctemplate($form->title, $form->render(submit => ["Login", "Register", "Mail Password"]));
  183. }
  184. }
  185. else {
  186. print $session->header();
  187. print misctemplate($form->title, $form->render(submit => ["Login", "Register", "Mail Password"]));
  188. }
  189. } #}}}
  190. sub cgi_prefs ($$) { #{{{
  191. my $q=shift;
  192. my $session=shift;
  193. eval q{use CGI::FormBuilder};
  194. my $form = CGI::FormBuilder->new(
  195. title => "preferences",
  196. fields => [qw(do name password confirm_password email locked_pages)],
  197. header => 0,
  198. method => 'POST',
  199. validate => {
  200. confirm_password => {
  201. perl => q{eq $form->field("password")},
  202. },
  203. email => 'EMAIL',
  204. },
  205. required => 'NONE',
  206. javascript => 0,
  207. params => $q,
  208. action => $q->request_uri,
  209. template => (-e "$config{templatedir}/prefs.tmpl" ?
  210. "$config{templatedir}/prefs.tmpl" : "")
  211. );
  212. my @buttons=("Save Preferences", "Logout", "Cancel");
  213. my $user_name=$session->param("name");
  214. $form->field(name => "do", type => "hidden");
  215. $form->field(name => "name", disabled => 1,
  216. value => $user_name, force => 1);
  217. $form->field(name => "password", type => "password");
  218. $form->field(name => "confirm_password", type => "password");
  219. $form->field(name => "locked_pages", size => 50,
  220. comment => "(".htmllink("", "GlobList", 1).")");
  221. if (! is_admin($user_name)) {
  222. $form->field(name => "locked_pages", type => "hidden");
  223. }
  224. if (! $form->submitted) {
  225. $form->field(name => "email", force => 1,
  226. value => userinfo_get($user_name, "email"));
  227. $form->field(name => "locked_pages", force => 1,
  228. value => userinfo_get($user_name, "locked_pages"));
  229. }
  230. if ($form->submitted eq 'Logout') {
  231. $session->delete();
  232. print $q->redirect($config{url});
  233. return;
  234. }
  235. elsif ($form->submitted eq 'Cancel') {
  236. print $q->redirect($config{url});
  237. return;
  238. }
  239. elsif ($form->submitted eq "Save Preferences" && $form->validate) {
  240. foreach my $field (qw(password email locked_pages)) {
  241. if (length $form->field($field)) {
  242. userinfo_set($user_name, $field, $form->field($field)) || error("failed to set $field");
  243. }
  244. }
  245. $form->text("Preferences saved.");
  246. }
  247. print $session->header();
  248. print misctemplate($form->title, $form->render(submit => \@buttons));
  249. } #}}}
  250. sub cgi_editpage ($$) { #{{{
  251. my $q=shift;
  252. my $session=shift;
  253. eval q{use CGI::FormBuilder};
  254. my $form = CGI::FormBuilder->new(
  255. fields => [qw(do rcsinfo subpage from page content comments)],
  256. header => 1,
  257. method => 'POST',
  258. validate => {
  259. content => '/.+/',
  260. },
  261. required => [qw{content}],
  262. javascript => 0,
  263. params => $q,
  264. action => $q->request_uri,
  265. table => 0,
  266. template => "$config{templatedir}/editpage.tmpl"
  267. );
  268. my @buttons=("Save Page", "Preview", "Cancel");
  269. my ($page)=$form->param('page')=~/$config{wiki_file_regexp}/;
  270. if (! defined $page || ! length $page || $page ne $q->param('page') ||
  271. $page=~/$config{wiki_file_prune_regexp}/ || $page=~/^\//) {
  272. error("bad page name");
  273. }
  274. $page=lc($page);
  275. my $file=$page.$config{default_pageext};
  276. my $newfile=1;
  277. if (exists $pagesources{lc($page)}) {
  278. $file=$pagesources{lc($page)};
  279. $newfile=0;
  280. }
  281. $form->field(name => "do", type => 'hidden');
  282. $form->field(name => "from", type => 'hidden');
  283. $form->field(name => "rcsinfo", type => 'hidden');
  284. $form->field(name => "subpage", type => 'hidden');
  285. $form->field(name => "page", value => "$page", force => 1);
  286. $form->field(name => "comments", type => "text", size => 80);
  287. $form->field(name => "content", type => "textarea", rows => 20,
  288. cols => 80);
  289. $form->tmpl_param("can_commit", $config{rcs});
  290. $form->tmpl_param("indexlink", indexlink());
  291. $form->tmpl_param("helponformattinglink",
  292. htmllink("", "HelpOnFormatting", 1));
  293. if (! $form->submitted) {
  294. $form->field(name => "rcsinfo", value => rcs_prepedit($file),
  295. force => 1);
  296. }
  297. if ($form->submitted eq "Cancel") {
  298. print $q->redirect("$config{url}/".htmlpage($page));
  299. return;
  300. }
  301. elsif ($form->submitted eq "Preview") {
  302. require IkiWiki::Render;
  303. $form->tmpl_param("page_preview",
  304. htmlize($config{default_pageext},
  305. linkify($form->field('content'), $page)));
  306. }
  307. else {
  308. $form->tmpl_param("page_preview", "");
  309. }
  310. $form->tmpl_param("page_conflict", "");
  311. if (! $form->submitted || $form->submitted eq "Preview" ||
  312. ! $form->validate) {
  313. if ($form->field("do") eq "create") {
  314. if (exists $pagesources{lc($page)}) {
  315. # hmm, someone else made the page in the
  316. # meantime?
  317. print $q->redirect("$config{url}/".htmlpage($page));
  318. return;
  319. }
  320. my @page_locs;
  321. my $best_loc;
  322. my ($from)=$form->param('from')=~/$config{wiki_file_regexp}/;
  323. if (! defined $from || ! length $from ||
  324. $from ne $form->param('from') ||
  325. $from=~/$config{wiki_file_prune_regexp}/ || $from=~/^\//) {
  326. @page_locs=$best_loc=$page;
  327. }
  328. else {
  329. my $dir=$from."/";
  330. $dir=~s![^/]+/$!!;
  331. if (length $form->param('subpage') ||
  332. $page eq 'discussion') {
  333. $best_loc="$from/$page";
  334. }
  335. else {
  336. $best_loc=$dir.$page;
  337. }
  338. push @page_locs, $dir.$page;
  339. push @page_locs, "$from/$page";
  340. while (length $dir) {
  341. $dir=~s![^/]+/$!!;
  342. push @page_locs, $dir.$page;
  343. }
  344. @page_locs = grep {
  345. ! exists $pagesources{lc($_)} &&
  346. ! page_locked($_, $session, 1)
  347. } @page_locs;
  348. }
  349. $form->tmpl_param("page_select", 1);
  350. $form->field(name => "page", type => 'select',
  351. options => \@page_locs, value => $best_loc);
  352. $form->title("creating ".pagetitle($page));
  353. }
  354. elsif ($form->field("do") eq "edit") {
  355. page_locked($page, $session);
  356. if (! defined $form->field('content') ||
  357. ! length $form->field('content')) {
  358. my $content="";
  359. if (exists $pagesources{lc($page)}) {
  360. $content=readfile("$config{srcdir}/$pagesources{lc($page)}");
  361. $content=~s/\n/\r\n/g;
  362. }
  363. $form->field(name => "content", value => $content,
  364. force => 1);
  365. }
  366. $form->tmpl_param("page_select", 0);
  367. $form->field(name => "page", type => 'hidden');
  368. $form->title("editing ".pagetitle($page));
  369. }
  370. print $form->render(submit => \@buttons);
  371. }
  372. else {
  373. # save page
  374. page_locked($page, $session);
  375. my $content=$form->field('content');
  376. $content=~s/\r\n/\n/g;
  377. $content=~s/\r/\n/g;
  378. writefile("$config{srcdir}/$file", $content);
  379. my $message="web commit ";
  380. if (length $session->param("name")) {
  381. $message.="by ".$session->param("name");
  382. }
  383. else {
  384. $message.="from $ENV{REMOTE_ADDR}";
  385. }
  386. if (defined $form->field('comments') &&
  387. length $form->field('comments')) {
  388. $message.=": ".$form->field('comments');
  389. }
  390. if ($config{rcs}) {
  391. if ($newfile) {
  392. rcs_add($file);
  393. }
  394. # prevent deadlock with post-commit hook
  395. unlockwiki();
  396. # presumably the commit will trigger an update
  397. # of the wiki
  398. my $conflict=rcs_commit($file, $message,
  399. $form->field("rcsinfo"));
  400. if (defined $conflict) {
  401. $form->field(name => "rcsinfo", value => rcs_prepedit($file),
  402. force => 1);
  403. $form->tmpl_param("page_conflict", 1);
  404. $form->field("content", value => $conflict, force => 1);
  405. $form->field("do", "edit)");
  406. $form->tmpl_param("page_select", 0);
  407. $form->field(name => "page", type => 'hidden');
  408. $form->title("editing $page");
  409. print $form->render(submit => \@buttons);
  410. return;
  411. }
  412. }
  413. else {
  414. require IkiWiki::Render;
  415. refresh();
  416. saveindex();
  417. }
  418. # The trailing question mark tries to avoid broken
  419. # caches and get the most recent version of the page.
  420. print $q->redirect("$config{url}/".htmlpage($page)."?updated");
  421. }
  422. } #}}}
  423. sub cgi () { #{{{
  424. eval q{use CGI};
  425. eval q{use CGI::Session};
  426. my $q=CGI->new;
  427. my $do=$q->param('do');
  428. if (! defined $do || ! length $do) {
  429. error("\"do\" parameter missing");
  430. }
  431. # Things that do not need a session.
  432. if ($do eq 'recentchanges') {
  433. cgi_recentchanges($q);
  434. return;
  435. }
  436. CGI::Session->name("ikiwiki_session");
  437. my $oldmask=umask(077);
  438. my $session = CGI::Session->new("driver:db_file", $q,
  439. { FileName => "$config{wikistatedir}/sessions.db" });
  440. umask($oldmask);
  441. # Everything below this point needs the user to be signed in.
  442. if ((! $config{anonok} && ! defined $session->param("name") ||
  443. ! defined $session->param("name") ||
  444. ! userinfo_get($session->param("name"), "regdate")) || $do eq 'signin') {
  445. cgi_signin($q, $session);
  446. # Force session flush with safe umask.
  447. my $oldmask=umask(077);
  448. $session->flush;
  449. umask($oldmask);
  450. return;
  451. }
  452. if ($do eq 'create' || $do eq 'edit') {
  453. cgi_editpage($q, $session);
  454. }
  455. elsif ($do eq 'prefs') {
  456. cgi_prefs($q, $session);
  457. }
  458. elsif ($do eq 'blog') {
  459. # munge page name to be valid, no matter what freeform text
  460. # is entered
  461. my $page=lc($q->param('title'));
  462. $page=~y/ /_/;
  463. $page=~s/([^-A-Za-z0-9_:+])/"__".ord($1)."__"/eg;
  464. # if the page already exist, munge it to be unique
  465. my $from=$q->param('from');
  466. my $add="";
  467. while (exists $oldpagemtime{"$from/$page$add"}) {
  468. $add=1 unless length $add;
  469. $add++;
  470. }
  471. $q->param('page', $page.$add);
  472. # now run same as create
  473. $q->param('do', 'create');
  474. cgi_editpage($q, $session);
  475. }
  476. else {
  477. error("unknown do parameter");
  478. }
  479. } #}}}
  480. 1