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