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