summaryrefslogtreecommitdiff
path: root/IkiWiki/CGI.pm
blob: b98e9e0a1b5f45ad2cd6cc6f785a39c616a1e676 (plain)
  1. #!/usr/bin/perl
  2. package IkiWiki;
  3. use warnings;
  4. use strict;
  5. use IkiWiki;
  6. use IkiWiki::UserInfo;
  7. use open qw{:utf8 :std};
  8. use Encode;
  9. sub printheader ($) {
  10. my $session=shift;
  11. if ($config{sslcookie}) {
  12. print $session->header(-charset => 'utf-8',
  13. -cookie => $session->cookie(-httponly => 1, -secure => 1));
  14. }
  15. else {
  16. print $session->header(-charset => 'utf-8',
  17. -cookie => $session->cookie(-httponly => 1));
  18. }
  19. }
  20. sub prepform {
  21. my $form=shift;
  22. my $buttons=shift;
  23. my $session=shift;
  24. my $cgi=shift;
  25. if (exists $hooks{formbuilder}) {
  26. run_hooks(formbuilder => sub {
  27. shift->(form => $form, cgi => $cgi, session => $session,
  28. buttons => $buttons);
  29. });
  30. }
  31. return $form;
  32. }
  33. sub showform ($$$$;@) {
  34. my $form=prepform(@_);
  35. shift;
  36. my $buttons=shift;
  37. my $session=shift;
  38. my $cgi=shift;
  39. printheader($session);
  40. print misctemplate($form->title, $form->render(submit => $buttons), @_);
  41. }
  42. sub redirect ($$) {
  43. my $q=shift;
  44. eval q{use URI};
  45. my $url=URI->new(shift);
  46. if (! $config{w3mmode}) {
  47. print $q->redirect($url);
  48. }
  49. else {
  50. print "Content-type: text/plain\n";
  51. print "W3m-control: GOTO $url\n\n";
  52. }
  53. }
  54. sub decode_cgi_utf8 ($) {
  55. # decode_form_utf8 method is needed for 5.01
  56. if ($] < 5.01) {
  57. my $cgi = shift;
  58. foreach my $f ($cgi->param) {
  59. $cgi->param($f, map { decode_utf8 $_ } $cgi->param($f));
  60. }
  61. }
  62. }
  63. sub decode_form_utf8 ($) {
  64. if ($] >= 5.01) {
  65. my $form = shift;
  66. foreach my $f ($form->field) {
  67. my @value=map { decode_utf8($_) } $form->field($f);
  68. $form->field(name => $f,
  69. value => \@value,
  70. force => 1,
  71. );
  72. }
  73. }
  74. }
  75. # Check if the user is signed in. If not, redirect to the signin form and
  76. # save their place to return to later.
  77. sub needsignin ($$) {
  78. my $q=shift;
  79. my $session=shift;
  80. if (! defined $session->param("name") ||
  81. ! userinfo_get($session->param("name"), "regdate")) {
  82. $session->param(postsignin => $ENV{QUERY_STRING});
  83. cgi_signin($q, $session);
  84. cgi_savesession($session);
  85. exit;
  86. }
  87. }
  88. sub cgi_signin ($$;$) {
  89. my $q=shift;
  90. my $session=shift;
  91. my $returnhtml=shift;
  92. decode_cgi_utf8($q);
  93. eval q{use CGI::FormBuilder};
  94. error($@) if $@;
  95. my $form = CGI::FormBuilder->new(
  96. title => "signin",
  97. name => "signin",
  98. charset => "utf-8",
  99. method => 'POST',
  100. required => 'NONE',
  101. javascript => 0,
  102. params => $q,
  103. action => $config{cgiurl},
  104. header => 0,
  105. template => {type => 'div'},
  106. stylesheet => 1,
  107. );
  108. my $buttons=["Login"];
  109. $form->field(name => "do", type => "hidden", value => "signin",
  110. force => 1);
  111. decode_form_utf8($form);
  112. run_hooks(formbuilder_setup => sub {
  113. shift->(form => $form, cgi => $q, session => $session,
  114. buttons => $buttons);
  115. });
  116. decode_form_utf8($form);
  117. if ($form->submitted) {
  118. $form->validate;
  119. }
  120. if ($returnhtml) {
  121. $form=prepform($form, $buttons, $session, $q);
  122. return $form->render(submit => $buttons);
  123. }
  124. showform($form, $buttons, $session, $q);
  125. }
  126. sub cgi_postsignin ($$) {
  127. my $q=shift;
  128. my $session=shift;
  129. # Continue with whatever was being done before the signin process.
  130. if (defined $session->param("postsignin")) {
  131. my $postsignin=CGI->new($session->param("postsignin"));
  132. $session->clear("postsignin");
  133. cgi($postsignin, $session);
  134. cgi_savesession($session);
  135. exit;
  136. }
  137. else {
  138. if ($config{sslcookie} && ! $q->https()) {
  139. error(gettext("probable misconfiguration: sslcookie is set, but you are attempting to login via http, not https"));
  140. }
  141. else {
  142. error(gettext("login failed, perhaps you need to turn on cookies?"));
  143. }
  144. }
  145. }
  146. sub cgi_prefs ($$) {
  147. my $q=shift;
  148. my $session=shift;
  149. needsignin($q, $session);
  150. decode_cgi_utf8($q);
  151. # The session id is stored on the form and checked to
  152. # guard against CSRF.
  153. my $sid=$q->param('sid');
  154. if (! defined $sid) {
  155. $q->delete_all;
  156. }
  157. elsif ($sid ne $session->id) {
  158. error(gettext("Your login session has expired."));
  159. }
  160. eval q{use CGI::FormBuilder};
  161. error($@) if $@;
  162. my $form = CGI::FormBuilder->new(
  163. title => "preferences",
  164. name => "preferences",
  165. header => 0,
  166. charset => "utf-8",
  167. method => 'POST',
  168. validate => {
  169. email => 'EMAIL',
  170. },
  171. required => 'NONE',
  172. javascript => 0,
  173. params => $q,
  174. action => $config{cgiurl},
  175. template => {type => 'div'},
  176. stylesheet => 1,
  177. fieldsets => [
  178. [login => gettext("Login")],
  179. [preferences => gettext("Preferences")],
  180. [admin => gettext("Admin")]
  181. ],
  182. );
  183. my $buttons=["Save Preferences", "Logout", "Cancel"];
  184. decode_form_utf8($form);
  185. run_hooks(formbuilder_setup => sub {
  186. shift->(form => $form, cgi => $q, session => $session,
  187. buttons => $buttons);
  188. });
  189. decode_form_utf8($form);
  190. $form->field(name => "do", type => "hidden", value => "prefs",
  191. force => 1);
  192. $form->field(name => "sid", type => "hidden", value => $session->id,
  193. force => 1);
  194. $form->field(name => "email", size => 50, fieldset => "preferences");
  195. my $user_name=$session->param("name");
  196. if (! $form->submitted) {
  197. $form->field(name => "email", force => 1,
  198. value => userinfo_get($user_name, "email"));
  199. }
  200. if ($form->submitted eq 'Logout') {
  201. $session->delete();
  202. redirect($q, $config{url});
  203. return;
  204. }
  205. elsif ($form->submitted eq 'Cancel') {
  206. redirect($q, $config{url});
  207. return;
  208. }
  209. elsif ($form->submitted eq 'Save Preferences' && $form->validate) {
  210. if (defined $form->field('email')) {
  211. userinfo_set($user_name, 'email', $form->field('email')) ||
  212. error("failed to set email");
  213. }
  214. $form->text(gettext("Preferences saved."));
  215. }
  216. showform($form, $buttons, $session, $q);
  217. }
  218. sub cgi_custom_failure ($$$) {
  219. my $q=shift;
  220. my $httpstatus=shift;
  221. my $message=shift;
  222. print $q->header(
  223. -status => $httpstatus,
  224. -charset => 'utf-8',
  225. );
  226. print $message;
  227. # Internet Explod^Hrer won't show custom 404 responses
  228. # unless they're >= 512 bytes
  229. print ' ' x 512;
  230. exit;
  231. }
  232. sub check_banned ($$) {
  233. my $q=shift;
  234. my $session=shift;
  235. my $banned=0;
  236. my $name=$session->param("name");
  237. if (defined $name &&
  238. grep { $name eq $_ } @{$config{banned_users}}) {
  239. $banned=1;
  240. }
  241. foreach my $b (@{$config{banned_users}}) {
  242. if (pagespec_match("", $b,
  243. ip => $ENV{REMOTE_ADDR},
  244. name => defined $name ? $name : "",
  245. )) {
  246. $banned=1;
  247. last;
  248. }
  249. }
  250. if ($banned) {
  251. $session->delete();
  252. cgi_savesession($session);
  253. cgi_custom_failure(
  254. $q, "403 Forbidden",
  255. gettext("You are banned."));
  256. }
  257. }
  258. sub cgi_getsession ($) {
  259. my $q=shift;
  260. eval q{use CGI::Session; use HTML::Entities};
  261. error($@) if $@;
  262. CGI::Session->name("ikiwiki_session_".encode_entities($config{wikiname}));
  263. my $oldmask=umask(077);
  264. my $session = eval {
  265. CGI::Session->new("driver:DB_File", $q,
  266. { FileName => "$config{wikistatedir}/sessions.db" })
  267. };
  268. if (! $session || $@) {
  269. error($@." ".CGI::Session->errstr());
  270. }
  271. umask($oldmask);
  272. return $session;
  273. }
  274. # To guard against CSRF, the user's session id (sid)
  275. # can be stored on a form. This function will check
  276. # (for logged in users) that the sid on the form matches
  277. # the session id in the cookie.
  278. sub checksessionexpiry ($$) {
  279. my $q=shift;
  280. my $session = shift;
  281. if (defined $session->param("name")) {
  282. my $sid=$q->param('sid');
  283. if (! defined $sid || $sid ne $session->id) {
  284. error(gettext("Your login session has expired."));
  285. }
  286. }
  287. }
  288. sub cgi_savesession ($) {
  289. my $session=shift;
  290. # Force session flush with safe umask.
  291. my $oldmask=umask(077);
  292. $session->flush;
  293. umask($oldmask);
  294. }
  295. sub cgi (;$$) {
  296. my $q=shift;
  297. my $session=shift;
  298. eval q{use CGI};
  299. error($@) if $@;
  300. $CGI::DISABLE_UPLOADS=$config{cgi_disable_uploads};
  301. if (! $q) {
  302. binmode(STDIN);
  303. $q=CGI->new;
  304. binmode(STDIN, ":utf8");
  305. run_hooks(cgi => sub { shift->($q) });
  306. }
  307. my $do=$q->param('do');
  308. if (! defined $do || ! length $do) {
  309. my $error = $q->cgi_error;
  310. if ($error) {
  311. error("Request not processed: $error");
  312. }
  313. else {
  314. error("\"do\" parameter missing");
  315. }
  316. }
  317. # Need to lock the wiki before getting a session.
  318. lockwiki();
  319. loadindex();
  320. if (! $session) {
  321. $session=cgi_getsession($q);
  322. }
  323. # Auth hooks can sign a user in.
  324. if ($do ne 'signin' && ! defined $session->param("name")) {
  325. run_hooks(auth => sub {
  326. shift->($q, $session)
  327. });
  328. if (defined $session->param("name")) {
  329. # Make sure whatever user was authed is in the
  330. # userinfo db.
  331. if (! userinfo_get($session->param("name"), "regdate")) {
  332. userinfo_setall($session->param("name"), {
  333. email => "",
  334. password => "",
  335. regdate => time,
  336. }) || error("failed adding user");
  337. }
  338. }
  339. }
  340. check_banned($q, $session);
  341. run_hooks(sessioncgi => sub { shift->($q, $session) });
  342. if ($do eq 'signin') {
  343. cgi_signin($q, $session);
  344. cgi_savesession($session);
  345. }
  346. elsif ($do eq 'prefs') {
  347. cgi_prefs($q, $session);
  348. }
  349. elsif (defined $session->param("postsignin") || $do eq 'postsignin') {
  350. cgi_postsignin($q, $session);
  351. }
  352. else {
  353. error("unknown do parameter");
  354. }
  355. }
  356. # Does not need to be called directly; all errors will go through here.
  357. sub cgierror ($) {
  358. my $message=shift;
  359. print "Content-type: text/html\n\n";
  360. print misctemplate(gettext("Error"),
  361. "<p class=\"error\">".gettext("Error").": $message</p>");
  362. die $@;
  363. }
  364. 1