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