private-pages.pl 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. # Copyright (C) 2012–2013 Alex Schroeder <alex@gnu.org>
  2. #
  3. # This program is free software; you can redistribute it and/or modify it under
  4. # the terms of the GNU General Public License as published by the Free Software
  5. # Foundation; either version 3 of the License, or (at your option) any later
  6. # version.
  7. #
  8. # This program is distributed in the hope that it will be useful, but WITHOUT
  9. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  10. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  11. #
  12. # You should have received a copy of the GNU General Public License along with
  13. # this program. If not, see <http://www.gnu.org/licenses/>.
  14. use strict;
  15. use v5.10;
  16. =head1 Private Pages Extension
  17. This module allows you to hide the content of particular pages in Oddmuse.
  18. Unlike the I<Hidden Pages Extension>, this is not based on the user's role of
  19. editor or administrator. Instead, every page can have a different password by
  20. beginning it with #PASSWORD XYZZY where XYZZY is the password required to read
  21. it. Multiple passwords can be supplied, separated by spaces.
  22. Note that all the meta information of the private page remains public: The
  23. I<name> of the page, the fact that is has been edited, the author, the
  24. revision, the content of past revisions that have not been protected by a
  25. password all remain visible to other users.
  26. Notes:
  27. =over
  28. =item * If you're protecting a comment page, people can still leave comments
  29. -- they just can't read the resulting page.
  30. =back
  31. =cut
  32. AddModuleDescription('private-pages.pl', 'Private Pages Extension');
  33. our (%IndexHash, %Page, $OpenPageName, $Now, @MyRules);
  34. sub PrivatePageLocked {
  35. my $text = shift;
  36. my ($line) = split(/\n/, $text, 1);
  37. my @token = split(/\s+/, $line);
  38. my $lock = 0;
  39. if (shift(@token) eq '#PASSWORD') {
  40. my $pwd = GetParam('pwd', '');
  41. $lock = 1;
  42. foreach (@token) {
  43. if ($pwd eq $_) {
  44. $lock = 0;
  45. last;
  46. }
  47. }
  48. }
  49. return $lock;
  50. }
  51. *OldPrivatePagesUserCanEdit = \&UserCanEdit;
  52. *UserCanEdit = \&NewPrivatePagesUserCanEdit;
  53. sub NewPrivatePagesUserCanEdit {
  54. my ($id, $editing, @rest) = @_;
  55. my $result = OldPrivatePagesUserCanEdit($id, $editing, @rest);
  56. # bypass OpenPage and GetPageContent (these are redefined below)
  57. if ($result > 0 and $editing and $IndexHash{$id}) {
  58. my $data = ParseData(ReadFileOrDie(GetPageFile($id)));
  59. if (PrivatePageLocked($data->{text})) {
  60. return 0;
  61. }
  62. }
  63. return $result;
  64. }
  65. *OldPrivatePageNewText = \&NewText;
  66. sub NewPrivatePageNewText {
  67. return Ts('This page is password protected. If you know the password, you can %s. Once you have done that, return and reload this page.',
  68. '[' . ScriptUrl('action=password') . ' '
  69. . T('supply the password now') . ']');
  70. }
  71. # prevent unauthorized reading
  72. # If we leave $Page{revision} set, PrintWikiToHTML will save the new
  73. # PrivatePageMessage as the new page content. If we don't set $Page{revision},
  74. # BrowsePage() will show NewText(). Therefore we need to override NewText(). If
  75. # we have no $Page{ts}, PageDeletable will return 1. As a workaround, we set a
  76. # timestamp. Aging of the page doesn't matter since the text starts with
  77. # #PASSWORD and therefore cannot be the empty string or $DeletedPage.
  78. *OldPrivatePagesOpenPage = \&OpenPage;
  79. *OpenPage = \&NewPrivatePagesOpenPage;
  80. sub NewPrivatePagesOpenPage {
  81. OldPrivatePagesOpenPage(@_);
  82. if (PrivatePageLocked($Page{text})) {
  83. %Page = (); # reset everything
  84. $Page{ts} = $Now;
  85. *NewText = \&NewPrivatePageNewText;
  86. } else {
  87. *NewText = \&OldPrivatePageNewText;
  88. }
  89. return $OpenPageName;
  90. }
  91. # prevent reading of page content by other code
  92. *OldPrivatePagesGetPageContent = \&GetPageContent;
  93. *GetPageContent = \&NewPrivatePagesGetPageContent;
  94. sub NewPrivatePagesGetPageContent {
  95. my $text = OldPrivatePagesGetPageContent(@_);
  96. if (PrivatePageLocked($text)) {
  97. return NewPrivatePageNewText();
  98. }
  99. return $text;
  100. }
  101. # prevent reading of old revisions
  102. *OldPrivatePagesGetTextRevision = \&GetTextRevision;
  103. *GetTextRevision = \&NewPrivatePagesGetTextRevision;
  104. sub NewPrivatePagesGetTextRevision {
  105. my ($page, $revision) = OldPrivatePagesGetTextRevision(@_);
  106. if (PrivatePageLocked($page->{text})) {
  107. return ({text => NewPrivatePageNewText()}, $revision); # XXX faking a page object like this is not good
  108. }
  109. return wantarray ? ($page, $revision) : $page;
  110. }
  111. # hide #PASSWORD
  112. push(@MyRules, \&PrivatePageRule);
  113. sub PrivatePageRule {
  114. if (pos == 0 && m/\G#PASSWORD.*\n/cg) {
  115. return '';
  116. }
  117. return;
  118. }
  119. # prevent leaking of edit summary
  120. *OldPrivatePagesGetSummary = \&GetSummary;
  121. *GetSummary = \&NewPrivatePagesGetSummary;
  122. sub NewPrivatePagesGetSummary {
  123. my $text = GetParam('text', '');
  124. if ($text and $text =~ /^#PASSWORD\b/
  125. # no text means aftertext is set (leaving a comment)
  126. or $Page{text} =~ /^#PASSWORD\b/) {
  127. # if no summary was set, set something in order to avoid the default
  128. return '';
  129. }
  130. return OldPrivatePagesGetSummary();
  131. }