summaryrefslogtreecommitdiff
path: root/IkiWiki/CGI.pm
blob: 6fd1f650665bf51461c65fe66a27c46d8e28fdd0 (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. my $newfile=1;
  284. if (exists $pagesources{lc($page)}) {
  285. $file=$pagesources{lc($page)};
  286. $newfile=0;
  287. }
  288. $form->field(name => "do", type => 'hidden');
  289. $form->field(name => "from", type => 'hidden');
  290. $form->field(name => "rcsinfo", type => 'hidden');
  291. $form->field(name => "subpage", type => 'hidden');
  292. $form->field(name => "page", value => "$page", force => 1);
  293. $form->field(name => "comments", type => "text", size => 80);
  294. $form->field(name => "content", type => "textarea", rows => 20,
  295. cols => 80);
  296. $form->tmpl_param("can_commit", $config{rcs});
  297. $form->tmpl_param("indexlink", indexlink());
  298. $form->tmpl_param("helponformattinglink",
  299. htmllink("", "HelpOnFormatting", 1));
  300. $form->tmpl_param("styleurl", styleurl());
  301. if (! $form->submitted) {
  302. $form->field(name => "rcsinfo", value => rcs_prepedit($file),
  303. force => 1);
  304. }
  305. if ($form->submitted eq "Cancel") {
  306. print $q->redirect("$config{url}/".htmlpage($page));
  307. return;
  308. }
  309. elsif ($form->submitted eq "Preview") {
  310. require IkiWiki::Render;
  311. $form->tmpl_param("page_preview",
  312. htmlize($config{default_pageext},
  313. linkify($form->field('content'), $page)));
  314. }
  315. else {
  316. $form->tmpl_param("page_preview", "");
  317. }
  318. $form->tmpl_param("page_conflict", "");
  319. if (! $form->submitted || $form->submitted eq "Preview" ||
  320. ! $form->validate) {
  321. if ($form->field("do") eq "create") {
  322. if (exists $pagesources{lc($page)}) {
  323. # hmm, someone else made the page in the
  324. # meantime?
  325. print $q->redirect("$config{url}/".htmlpage($page));
  326. return;
  327. }
  328. my @page_locs;
  329. my $best_loc;
  330. my ($from)=$form->param('from')=~/$config{wiki_file_regexp}/;
  331. if (! defined $from || ! length $from ||
  332. $from ne $form->param('from') ||
  333. $from=~/$config{wiki_file_prune_regexp}/ ||
  334. $from=~/^\// ||
  335. $form->submitted eq "Preview") {
  336. @page_locs=$best_loc=$page;
  337. }
  338. else {
  339. my $dir=$from."/";
  340. $dir=~s![^/]+/$!!;
  341. if ((defined $form->param('subpage') && length $form->param('subpage')) ||
  342. $page eq 'discussion') {
  343. $best_loc="$from/$page";
  344. }
  345. else {
  346. $best_loc=$dir.$page;
  347. }
  348. push @page_locs, $dir.$page;
  349. push @page_locs, "$from/$page";
  350. while (length $dir) {
  351. $dir=~s![^/]+/$!!;
  352. push @page_locs, $dir.$page;
  353. }
  354. @page_locs = grep {
  355. ! exists $pagesources{lc($_)} &&
  356. ! page_locked($_, $session, 1)
  357. } @page_locs;
  358. }
  359. $form->tmpl_param("page_select", 1);
  360. $form->field(name => "page", type => 'select',
  361. options => \@page_locs, value => $best_loc);
  362. $form->title("creating ".pagetitle($page));
  363. }
  364. elsif ($form->field("do") eq "edit") {
  365. page_locked($page, $session);
  366. if (! defined $form->field('content') ||
  367. ! length $form->field('content')) {
  368. my $content="";
  369. if (exists $pagesources{lc($page)}) {
  370. $content=readfile(srcfile($pagesources{lc($page)}));
  371. $content=~s/\n/\r\n/g;
  372. }
  373. $form->field(name => "content", value => $content,
  374. force => 1);
  375. }
  376. $form->tmpl_param("page_select", 0);
  377. $form->field(name => "page", type => 'hidden');
  378. $form->title("editing ".pagetitle($page));
  379. }
  380. print $form->render(submit => \@buttons);
  381. }
  382. else {
  383. # save page
  384. page_locked($page, $session);
  385. my $content=$form->field('content');
  386. $content=~s/\r\n/\n/g;
  387. $content=~s/\r/\n/g;
  388. writefile("$config{srcdir}/$file", $content);
  389. my $message="web commit ";
  390. if (length $session->param("name")) {
  391. $message.="by ".$session->param("name");
  392. }
  393. else {
  394. $message.="from $ENV{REMOTE_ADDR}";
  395. }
  396. if (defined $form->field('comments') &&
  397. length $form->field('comments')) {
  398. $message.=": ".$form->field('comments');
  399. }
  400. if ($config{rcs}) {
  401. if ($newfile) {
  402. rcs_add($file);
  403. }
  404. # prevent deadlock with post-commit hook
  405. unlockwiki();
  406. # presumably the commit will trigger an update
  407. # of the wiki
  408. my $conflict=rcs_commit($file, $message,
  409. $form->field("rcsinfo"));
  410. if (defined $conflict) {
  411. $form->field(name => "rcsinfo", value => rcs_prepedit($file),
  412. force => 1);
  413. $form->tmpl_param("page_conflict", 1);
  414. $form->field("content", value => $conflict, force => 1);
  415. $form->field("do", "edit)");
  416. $form->tmpl_param("page_select", 0);
  417. $form->field(name => "page", type => 'hidden');
  418. $form->title("editing $page");
  419. print $form->render(submit => \@buttons);
  420. return;
  421. }
  422. }
  423. else {
  424. require IkiWiki::Render;
  425. refresh();
  426. saveindex();
  427. }
  428. # The trailing question mark tries to avoid broken
  429. # caches and get the most recent version of the page.
  430. print $q->redirect("$config{url}/".htmlpage($page)."?updated");
  431. }
  432. } #}}}
  433. sub cgi () { #{{{
  434. eval q{use CGI};
  435. eval q{use CGI::Session};
  436. my $q=CGI->new;
  437. my $do=$q->param('do');
  438. if (! defined $do || ! length $do) {
  439. error("\"do\" parameter missing");
  440. }
  441. # Things that do not need a session.
  442. if ($do eq 'recentchanges') {
  443. cgi_recentchanges($q);
  444. return;
  445. }
  446. CGI::Session->name("ikiwiki_session_$config{wikiname}");
  447. my $oldmask=umask(077);
  448. my $session = CGI::Session->new("driver:db_file", $q,
  449. { FileName => "$config{wikistatedir}/sessions.db" });
  450. umask($oldmask);
  451. # Everything below this point needs the user to be signed in.
  452. if ((! $config{anonok} && ! defined $session->param("name") ||
  453. ! defined $session->param("name") ||
  454. ! userinfo_get($session->param("name"), "regdate")) || $do eq 'signin') {
  455. cgi_signin($q, $session);
  456. # Force session flush with safe umask.
  457. my $oldmask=umask(077);
  458. $session->flush;
  459. umask($oldmask);
  460. return;
  461. }
  462. if ($do eq 'create' || $do eq 'edit') {
  463. cgi_editpage($q, $session);
  464. }
  465. elsif ($do eq 'prefs') {
  466. cgi_prefs($q, $session);
  467. }
  468. elsif ($do eq 'blog') {
  469. my $page=titlepage(lc($q->param('title')));
  470. # if the page already exists, munge it to be unique
  471. my $from=$q->param('from');
  472. my $add="";
  473. while (exists $oldpagemtime{"$from/$page$add"}) {
  474. $add=1 unless length $add;
  475. $add++;
  476. }
  477. $q->param('page', $page.$add);
  478. # now run same as create
  479. $q->param('do', 'create');
  480. cgi_editpage($q, $session);
  481. }
  482. else {
  483. error("unknown do parameter");
  484. }
  485. } #}}}
  486. 1