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