Compare commits

..

No commits in common. "master" and "0.17.0" have entirely different histories.

32382 changed files with 65418 additions and 231461 deletions

223
CHANGELOG Normal file → Executable file
View file

@ -1,226 +1,3 @@
N.B.: You *must* run
ruby bundle
ruby bundle exec rake upgrade_instiki
after installing the new software, to enjoy the benefits of this new version.
------------------------------------------------------------------------------
* 0.19.3
New Features:
* Source view for Revisions
* Rails updated to 2.3.14 (Security)
* itextomml updated to 1.4.6
* Replace REXML with Nokogiri in Maruku and
in xhtml_safe_sanitize().
(Huge speedup in rendering long pages)
* MathJax updated to 1.1a final
Bugs Fixed:
* Bundler upgraded to 1.0.18
* Fix null search bug
* Better text/html serialization (thank you, Nokogiri)
* Fix Maruku footnote backlink (reported by Shamaoke)
* Fix Maruku link bug
* Fix Maruku image title bug
* Fix Maruku hrule, email address and header bugs
* Fix Maruku bold-in-italics bug
* Fix Maruku empty list-item bug
------------------------------------------------------------------------------
* 0.19.2
New Features:
* MathJax rendering for non-MathML capable browsers.
* RedCloth (Textile) upgraded to 4.x (now handled
by Bundler).
* Bundler upgraded to 1.0.7
* Rails updated to 2.3.11
Bugs Fixed:
* Redirects and categories of included pages should
not be inherited. (Suggestion of Andrew Stacey).
* Bug in Maruku equation handling (reported by Andrew Stacey).
* SVG-Edit updates and bug-fixes.
* Bug in editing S5 slideshows.
* Unvendor Rack
* Fix Maruku list-parsing bug (reported by Shamaoke)
* Validate Web address (Reported by Richard Marquez).
* Fix a well-formedness bug
------------------------------------------------------------------------------
* 0.19.1
New Features:
* From the "All" or Category listings, you can export selected pages (in any
desired order) to a single LaTeX file.
* LaTeX export supports \array{} (with no options) and a LaTeX-style optional
argument for \sqrt[]{}. The latter requires itextomml 1.4.5 or later.
* Updated to itextomml 1.4.5 (a bunch of new itex language features).
* Updated to Rails 2.3.10. (There were security issues in 2.3.9 which, happily,
did NOT affect Instiki 0.19. But 2.3.10 has other improvements, as well.)
Bugs Fixed:
* Several SVG-Edit bugs fixed.
* Removed some superfluous junk, to slim down the distribution (cuts the size of
the compressed .tar.gz nearly in half).
------------------------------------------------------------------------------
* 0.19
New Features:
* WYSIWYG SVG editing (via SVG-edit)
* One-click S5 templates
* Itex2MML is now a Rubygem. Latest is itextomml-1.4.2.
* Rails Metal itex endpoint
* HTML5 support
* Support IALs on Markdown list items
* Updated to Rails 2.3.9 and Erubis (now at 2.6.6)
* Updated for Rack 1.2.1, sqlite3-ruby 1.3.1
* Manages dependencies using Bundler. Before running Instiki for the first time
(and whenever you update), run
ruby bundle
rake upgrade_instiki
from the instiki directory. (You may need to run
ruby bundle exec rake upgrade_instiki
instead, if you get a complaint about your version of rake.)
Bugs Fixed:
* Works with Ruby 1.9.2
* Fixed a bug in non-Latin WikiWord processing. (Reported by Alexander Hambug)
* Fixed Cyrillic WikiWord support.
* More informative dnsbl lookup responses (suggested by Toby Bartels)
* Fixed a bug in LaTeX output
* No longer conflicts with sqlite3-ruby 1.3.x Rubygem
* Fixed some Category listing bugs
* Fixed an escaping bug in 'new' and 'edit' templates. (Reported by Toby Bartels)
* Allow special characters ('.', '/', etc) in page names.
* Fix BlahTeX/PNG path, so equations render in diff and
previous revision pages.
* Fix HTML Export feature so that uploaded files are
included, stylesheets load, etc.
* Uploaded files inclided in Markup Export.
* Fix Print View, so that uploaded images work.
* Fix some more Ruby 1.9 isues.
* Prevent page from being renamed to null.
* Fix Migration to work under PostgreSQL (from J. Zellman).
* Updated vendored plugins
------------------------------------------------------------------------------
* 0.18.1
New Features:
* (Markdown-Extra syle) fenced codeblocks. [From Jason Blevins]
* Fortran syntax colouring. [From Jason Blevins]
Bugs Fixed:
* Fixed some Ruby 1.9 encoding issues, with unicode page names,
author names and categories.
* Better display of inter-web wikilinks
* various syntax-colouring fixes
* Corrected length of wiki_references referenced_name (affects
MySQL users)
------------------------------------------------------------------------------
* 0.18
New Features:
* Syntax colouring: 'ansic', 'javascript', 'sqlite', 'yaml' and 'css' modes,
in addition to the existing 'html', 'xml', and 'ruby' modes,
* Source view [suggested by Andrew Stacey]
* Auto-resizing Textareas scale to fit viewing area.
* Instiki upgraded to Rails 2.3.5 and Rack 1.1.
* Now runs on Ruby 1.9. (If you're a Passenger user, you may need to upgrade to Passenger
2.2.8, which works around some bugs in Ruby 1.9.1.)
* Upgraded for itex2MML 1.3.19 (which works under Ruby 1.9, and has several new feautures,
relative to 1.3.15).
Bugs Fixed:
* Fixed a CSS bug, which screwed up printing (unless you used the "Print" view).
* Fixed a well-formedness bug in the page-name truncation algorithm [reported by Toby Bartels]
* Fixed a cache-sweeping bug [reported by Toby Bartels]
* Better accessibility.
* Improved log rotation under Passenger.
* Omit a (seemingly superfluous) javascript hack which causes Gecko-based browsers to request
/my_wiki/s5/null
when they load an s5 slideshow.
* Upgraded vendored sqlite3-ruby and rubyzip
* Move files when renaming a web (so that links to uploaded files don't break).
* Many Ruby 1.9 fixes, including removing the html5lib Sanitizer.
------------------------------------------------------------------------------
* 0.17.3
The most important facet of this release is a small change in the database
schema. Previously, people migrating from the default SQLite3 database to MySQL
ran the risk of silent data loss, because MySQL had a more strict interpretation
of the column types in the database. The new schema will prevent such problems.
rake upgrade_instiki
will seamlessly upgrade your existing database to the new schema.
New Features:
* Passenger support (including X-Sendfile support, if the Apache mod_xsendfile
module is installed).
* Update for itex2MML 1.3.13. (You should upgrade your itex2MML to the latest
version, too.)
Bugs Fixed:
* Refactored the Web model (from James Herdman).
* Clean malformed utf-8 strings, rather than complaining about them.
* Updated location of Textile help, since _why_the_lucky_stiff left the 'net.
* Fixed a TeX rendering bug.
* Updated list of XHTML+MathML named entities to match W3C Working Draft.
* Refactored the Sanitizer (speedup).
* Fix S5 Slideshows for non-root Instiki URLs.
* Work around a Rails flash bug.
* Links from published webs should work right (finally?).
* An important database migration for MySQL users.
------------------------------------------------------------------------------
* 0.17.2
Security: Updated to Rails 2.3.4
* Fixes Timing Weakness in Rails MessageVerifier and the Cookie Store
http://weblog.rubyonrails.org/2009/9/4/timing-weakness-in-ruby-on-rails
* Fixes XSS Vulnerability in Rails
http://weblog.rubyonrails.org/2009/9/4/xss-vulnerability-in-ruby-on-rails
New Features:
* Syntax colouring (`ruby` and `html`) for code blocks.
* Updated for itex2MML 1.3.10 (supports \rlap{} and \underline{}). You should upgrade that, too.
* Add a "Create New Page" Link to the Search Page. (Based on an idea by nowa)
* Updated to Rails 2.3.4
Bugs Fixed:
* Wikilinks to published webs should be to the published action. This didn't work
right for inter-web links. (Reported by Mike Shulman)
* Use .size, rather than .length for ActiveRecord associations. A huge memory saving
in building the recently_revised page.
* Refactor the upgrade_instiki rake task, to make it database-agnostic. (Many thanks to James Herdman)
* Web#files_path and Web#blatex_pngs_path now return Pathname objects. (Thanks, again, to James Herdman)
* Workaround for Mozilla Bug 449396. (Reported by Andrew Stacey)
* Correctly Set noindex,nofollow On /diff Pages.
* Page-renaming javascript deals correctly with page names containing ampersands, slashes, and other garbage.
* List of Wanted Pages should not include redirected pages.
* The Regexp, used in Maruku to detect "email" headers (used, e.g., for S5 slideshow metadata) could, for some inputs, interact badly with Instiki's Chunk Handler. Fixed.
* Ensure "rollback" locks page for editing.
* Generate relative URLs, when possible. (Patch by Dennis Knauf)
* Expire revisions of an edited page. Use a `before_save` hook to deal with the situation where a page's name has been changed.
------------------------------------------------------------------------------
* 0.17 * 0.17
New features: New features:

12
Gemfile
View file

@ -1,12 +0,0 @@
source "http://rubygems.org"
gem "sqlite3-ruby", :require => "sqlite3"
gem "itextomml", ">=1.4.7"
gem "rack", ">=1.1.0"
gem "mongrel", ">=1.2.0.pre2"
gem "rubyzip"
gem "RedCloth", ">=4.0.0"
gem "erubis"
gem "nokogiri"
gem "rake"
gem "rdoc"
gem "json"

79
README Normal file → Executable file
View file

@ -1,7 +1,7 @@
= Instiki = Instiki
Instiki is a wiki clone so pretty and easy to set up, you'll wonder if its really a wiki. Runs on Rails and focuses on portability and stability. Supports file uploads, PDF export, RSS, multiple users and password protection. Some use Instiki as a CMS (Content Management System) because of its ability to export static pages. Instiki is a wiki clone so pretty and easy to set up, you'll wonder if its really a wiki. Runs on Rails and focuses on portability and stability. Supports file uploads, PDF export, RSS, multiple users and password protection. Some use Instiki as a CMS (Content Management System) because of it's ability to export static pages.
Instiki lowers the barriers of interest for when you might consider using a wiki. It's so simple to get running that you'll find yourself using it for anything -- taking notes, brainstorming, organizing a gathering. Instiki lowers the barriers of interest for when you might consider using a wiki. It's so simple to get running that you'll find yourself using it for anything -- taking notes, brainstorming, organizing a gathering.
@ -15,32 +15,33 @@ Instiki on BeOS, Amiga OS, OS2, Zeta OS and support for various exotic Platforms
== 3 easy Steps to get the Instiki experience == 3 easy Steps to get the Instiki experience
Step 1. Get Instiki and run "ruby bundle" Step 1. Get Ruby, Download Instiki
Step 2. Run "instiki" Step 2. Run "instiki"
Step 3. Chuckle... "There's no step three!" (TM) Step 3. Chuckle... "There's no step three!" (TM)
== Details == Details
You need at least Ruby Version 1.8.6, and Rubygems 1.3.6, installed on your System. The second dependency is a Database System, but don't worry, the default sqlite3 will be installed for you, if it's not already installed. You can also use any other database system (MySQL, PostgreSQL, ...) supported by Rails. You need at least Ruby Version 1.8.4 installed on your System. The second dependency is a Database System, but don't worry, maybe you are already served.
=== If you are on Windows === If you are on Windows
- Get the *Ruby One-Click Installer - Windows* http://rubyforge.org/projects/rubyinstaller - Get the *Ruby One-Click Installer - Windows* http://rubyforge.org/projects/rubyinstaller
- Get Development Kit http://github.com/oneclick/rubyinstaller/wiki/development-kit
- In the Instiki directory, execute "ruby bundle"
- double-click instiki.bat or instiki.cmd and there you go! - double-click instiki.bat or instiki.cmd and there you go!
if you are running Windows 95, 98 or ME and cannot get instiki to run, try Version 0.11.pl1 which is the last instiki Version to support that old-style OS's. Please update to some Unix-OS or complain to the Ruby on Rails List at http://www.ruby-forum.com/forum/3 (Rails does not support your old Windows.)
=== If you are on Mac OSX === If you are on Mac OSX
On Leopard and Snow Leopard, you are all set. On Leopard, you are all set.
- run "sudo gem update --system" via the command-line.
- run "ruby bundle" in the instiki directory.
- run "ruby instiki" and there you go!
Tiger ships with a really old Ruby Version (1.8.2) and a broken Readline Library you have to Tiger ships with a really old Ruby Version (1.8.2) and a broken Readline Library you have to
- use the Ruby One-Click-Installer for OSX ( http://rubyosx.com ) if you don't already have macports' Ruby - use the Ruby One-Click-Installer for OSX ( http://rubyosx.com ) if you don't already have macports' Ruby
- make sure you read http://instiki.5uper.net/instiki/show/SQLite+issues+on+OSX
- run "ruby instiki.rb" via command-line in the directory
=== If you are on Linux === If you are on Linux
@ -48,12 +49,7 @@ Tiger ships with a really old Ruby Version (1.8.2) and a broken Readline Library
=== Any other System === Any other System
- get Ruby for your System, compile if nessesary: http://ruby-lang.org - get Ruby for your System, compile if nessesary: http://ruby-lang.org
- Depending on the version of Rubygems that came with your Ruby, you may need to
sudo gem update --system
- get SQLite or compile from http://sqlite.org (you can also use mysql or any other supported database system if you want) - get SQLite or compile from http://sqlite.org (you can also use mysql or any other supported database system if you want)
- run "ruby bundle"
- run instiki - run instiki
You're now running a perfectly suitable wiki on port 2500 that'll present you with one-step setup, followed by a textarea for the home page on http://localhost:2500 You're now running a perfectly suitable wiki on port 2500 that'll present you with one-step setup, followed by a textarea for the home page on http://localhost:2500
@ -72,16 +68,14 @@ You're now running a perfectly suitable wiki on port 2500 that'll present you wi
* Five markup choices: * Five markup choices:
Markdown-based choices [http://daringfireball.net/projects/markdown/syntax]: Markdown-based choices [http://daringfireball.net/projects/markdown/syntax]:
Markdown+itex2MML (the default; requires itex2MML) Markdown+itex2MML (the default; requires itex2MML)
Markdown+BlahTeX/PNG (requires blahtex and a working TeX installation) Markdown+BlahTeX/PNG (requires blahtex and a working TeX installation
Markdown Markdown
Textile [http://www.textism.com/tools/textile] Textile [http://www.textism.com/tools/textile]
RDoc [http://rdoc.sourceforge.net/doc] RDoc [http://rdoc.sourceforge.net/doc]
* Support for Math (using itex syntax [http://golem.ph.utexas.edu/~distler/blog/itex2MMLcommands.html])
* Support for WYSIWYG SVG editing -- embed SVG graphics right in your wiki page.
* Embedded webserver: uses Mongrel (if installed), or the bundled WEBrick webserver (if not). * Embedded webserver: uses Mongrel (if installed), or the bundled WEBrick webserver (if not).
* Internationalization: Wiki words in any latin, greek, cyrillian, or armenian characters * Internationalization: Wiki words in any latin, greek, cyrillian, or armenian characters
* Color diffs: Track changes through revisions * Color diffs: Track changes through revisions
* Runs on SQLite3 per default, can be configured to run on PostgreSQL, MySQL, DB2, Firebird, Openbase, Oracle, SQL Server or Sybase * Runs on SQLite per default, can be configured to run on PostgreSQL, MySQL, DB2, Firebird, Openbase, Oracle, SQL Server or Sybase
== Command-line options: == Command-line options:
@ -93,19 +87,56 @@ You're now running a perfectly suitable wiki on port 2500 that'll present you wi
* See CHANGELOG * See CHANGELOG
== Migrating from Instiki 0.11-0.18 to 0.19 == Migrating from Instiki 0.11-0.12 to 0.16.3
rake upgrade_instiki
== Migrating Instiki 0.10.2 storage to Instiki 0.11.0 database
1. Install Instiki 0.11 and check that it works (you should be able to create a web, edit and save a HomePage)
2. Execute
ruby script\import_storage \
-t /full/path/to/instiki0.10/storage \
-i /full/path/to/instiki0.10/installation \
-d sqlite (or mysql, or postgres, depending on what you use) \
-o instiki_import.sql
for example (Windows):
ruby script\import_storage -t c:\instiki-0.10.2\storage\2500 -i c:\instiki-0.10.2 -d sqlite -o instiki_import.sql
3. This will produce instiki_import.sql file in the current working directory.
Open it in a text editor and inspect carefully.
4. Connect to your production database (e.g., 'sqlite3 db\prod.db'),
and have it execute instiki_import.sql (e.g., '.read instiki_import.sql')
5. Execute ruby script\reset_references
(this script parses all pages for crosslinks between them, so it may take a few minutes)
6. Restart Instiki
7. Go over some pages, especially those with a lot of complex markup, and see if anything is broken.
The most common migration problem is this: if you open All Pages and see a lot of orphaned pages,
you forgot to run ruby script\reset_references after importing the data.
===Upgrading from Instiki-AR Beta 1
In Beta 2, we switch to ActiveRecord:Migrations. Therefore:
1. Back up your production database.
2. Open command-line session to your database and execute:
create table schema_info (version integer(11));
insert into schema_info (version) values (1);
3. Go back to the shell, change directory to the new Instiki and execute "rake migrate".
Step 2 creates a table that tells to ActiveRecord:Migrations that the current version
of this database is 1 (corresponding to Beta 1), and step 3 makes it up-to-date with
the current version of Instiki.
ruby bundle
ruby bundle exec rake upgrade_instiki
== Download the latest release from: == Download the latest release from:
* http://rubyforge.org/project/showfiles.php?group_id=186 * http://rubyforge.org/project/showfiles.php?group_id=186
== Visit the Instiki wiki: == Visit the "official" Instiki wiki:
* http://golem.ph.utexas.edu/wiki/instiki/ * http://instiki.org
== License: == License:

View file

@ -1,21 +0,0 @@
UPGRADING
=========
See the upgrading instructions
http://golem.ph.utexas.edu/wiki/instiki/show/Upgrading
for detailed instructions.
At a minimum, you need to backup your database.
After installing the new software and restoring your database, you need to run
ruby bundle
ruby bundle exec rake upgrade_instiki
from the commandline, to complete the upgrade. Doing a
ruby bundle update
will update the installed gems (used by Instiki) to the latest versions.

0
app/apis/.gitignore vendored
View file

View file

@ -138,4 +138,15 @@ class AdminController < ApplicationController
redirect_to :back redirect_to :back
end end
private
def is_post
unless (request.post? || ENV["RAILS_ENV"] == "test")
headers['Allow'] = 'POST'
render(:status => 405, :text => 'You must use an HTTP POST', :layout => 'error')
return false
end
return true
end
end end

View file

@ -1,7 +1,7 @@
# The filters added to this controller will be run for all controllers in the application. # The filters added to this controller will be run for all controllers in the application.
# Likewise will all the methods added be available for all controllers. # Likewise will all the methods added be available for all controllers.
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
# require 'dnsbl_check'
protect_forms_from_spam protect_forms_from_spam
before_filter :connect_to_model, :check_authorization, :setup_url_generator, :set_content_type_header, :set_robots_metatag before_filter :connect_to_model, :check_authorization, :setup_url_generator, :set_content_type_header, :set_robots_metatag
after_filter :remember_location, :teardown_url_generator after_filter :remember_location, :teardown_url_generator
@ -19,31 +19,13 @@ class ApplicationController < ActionController::Base
Wiki.new Wiki.new
end end
helper_method :xhtml_enabled?, :html_ext, :darken
protected protected
def xhtml_enabled? def check_authorization
in_a_web? and [:markdownMML, :markdownPNG, :markdown].include?(@web.markup) if in_a_web? and authorization_needed? and not authorized?
end redirect_to :controller => 'wiki', :action => 'login', :web => @web_name
return false
def html_ext end
if xhtml_enabled? && request.env.include?('HTTP_ACCEPT') &&
Mime::Type.parse(request.env["HTTP_ACCEPT"]).include?(Mime::XHTML)
'xhtml'
else
'html'
end
end
def darken(s)
n=s.length/3
s.scan( %r(\w{#{n},#{n}}) ).collect {|a| (a.hex * 2/3).to_s(16).rjust(n,'0')}.join
end
def check_authorization
redirect_to(:controller => 'wiki', :action => 'login',
:web => @web_name) if in_a_web? and authorization_needed? and not authorized?
end end
def connect_to_model def connect_to_model
@ -53,8 +35,10 @@ class ApplicationController < ActionController::Base
@author = cookies['author'] || 'AnonymousCoward' @author = cookies['author'] || 'AnonymousCoward'
if @web_name if @web_name
@web = @wiki.webs[@web_name] @web = @wiki.webs[@web_name]
render(:status => 404, :text => "Unknown web '#{@web_name}'", if @web.nil?
:layout => 'error') if @web.nil? render(:status => 404, :text => "Unknown web '#{@web_name}'", :layout => 'error')
return false
end
end end
end end
@ -103,8 +87,7 @@ class ApplicationController < ActionController::Base
original_options[:type] ||= (FILE_TYPES[File.extname(file_name)] or 'application/octet-stream') original_options[:type] ||= (FILE_TYPES[File.extname(file_name)] or 'application/octet-stream')
original_options[:disposition] ||= (DISPOSITION[original_options[:type]] or 'attachment') original_options[:disposition] ||= (DISPOSITION[original_options[:type]] or 'attachment')
original_options[:stream] ||= false original_options[:stream] ||= false
original_options[:x_sendfile] = true if request.env.include?('HTTP_X_SENDFILE_TYPE') && original_options[:x_sendfile] = true if request.env.include?('HTTP_X_SENDFILE_TYPE') && request.remote_addr == LOCALHOST
( request.remote_addr == LOCALHOST || defined?(PhusionPassenger) )
original_options original_options
end end
@ -157,7 +140,7 @@ class ApplicationController < ActionController::Base
<html xmlns="http://www.w3.org/1999/xhtml"><body> <html xmlns="http://www.w3.org/1999/xhtml"><body>
<h2>Internal Error</h2> <h2>Internal Error</h2>
<p>An application error occurred while processing your request.</p> <p>An application error occurred while processing your request.</p>
<!-- \n#{exception.to_s.purify.gsub!(/-{2,}/, '- -') }\n#{exception.backtrace.join("\n")}\n --> <!-- \n#{exception.to_s.is_utf8? ? exception.to_s.gsub!(/-{2,}/, '- -') : ''}\n#{exception.backtrace.join("\n")}\n -->
</body></html> </body></html>
EOL EOL
end end
@ -182,11 +165,13 @@ class ApplicationController < ActionController::Base
end end
end end
public
def set_content_type_header def set_content_type_header
response.charset = 'utf-8' response.charset = 'utf-8'
if %w(atom_with_content atom_with_headlines).include?(action_name) if %w(atom_with_content atom_with_headlines).include?(action_name)
response.content_type = Mime::ATOM response.content_type = Mime::ATOM
elsif %w(tex tex_list).include?(action_name) elsif %w(tex).include?(action_name)
response.content_type = Mime::TEXT response.content_type = Mime::TEXT
elsif xhtml_enabled? elsif xhtml_enabled?
if request.user_agent =~ /Validator/ or request.env.include?('HTTP_ACCEPT') && if request.user_agent =~ /Validator/ or request.env.include?('HTTP_ACCEPT') &&
@ -204,8 +189,14 @@ class ApplicationController < ActionController::Base
end end
end end
def xhtml_enabled?
in_a_web? and (@web.markup == :markdownMML or @web.markup == :markdownPNG or @web.markup == :markdown)
end
protected
def set_robots_metatag def set_robots_metatag
if controller_name == 'wiki' and %w(show published s5).include? action_name and !(params[:mode] == 'diff') if controller_name == 'wiki' and %w(show published).include? action_name
@robots_metatag_value = 'index,follow' @robots_metatag_value = 'index,follow'
else else
@robots_metatag_value = 'noindex,nofollow' @robots_metatag_value = 'noindex,nofollow'
@ -242,16 +233,6 @@ class ApplicationController < ActionController::Base
(@web.published? and action_name == 's5') (@web.published? and action_name == 's5')
end end
def is_post
unless (request.post? || Rails.env.test?)
layout = 'error'
layout = false if %w(tex tex_list).include?(action_name)
headers['Allow'] = 'POST'
render(:status => 405, :text => 'You must use an HTTP POST', :layout => layout)
end
return true
end
end end
module Mime module Mime
@ -276,10 +257,10 @@ end
module Instiki module Instiki
module VERSION #:nodoc: module VERSION #:nodoc:
MAJOR = 0 MAJOR = 0
MINOR = 19 MINOR = 17
TINY = 3 TINY = 0
SUFFIX = '(MML+)' SUFFIX = '(MML+)'
PRERELEASE = false PRERELEASE = false
if PRERELEASE if PRERELEASE
STRING = [MAJOR, MINOR].join('.') + PRERELEASE + SUFFIX STRING = [MAJOR, MINOR].join('.') + PRERELEASE + SUFFIX
else else
@ -287,22 +268,3 @@ module Instiki
end end
end end
end end
# Monkey patch, to make Hash#key work in Ruby 1.8
class Hash
alias_method(:key, :index) unless method_defined?(:key)
end
# Monkey patch, to ensure ActionCache doesn't muck with the content-type header.
module ActionController #:nodoc:
module Caching
module Actions
class ActionCacheFilter
private
def set_content_type!(controller, extension)
return
end
end
end
end
end

View file

@ -2,17 +2,17 @@ module CacheSweepingHelper
def expire_cached_page(web, page_name) def expire_cached_page(web, page_name)
expire_action :controller => 'wiki', :web => web.address, expire_action :controller => 'wiki', :web => web.address,
:action => %w(show published s5 tex print history source), :id => page_name :action => %w(show published s5 tex print history), :id => page_name
expire_action :controller => 'wiki', :web => web.address, expire_action :controller => 'wiki', :web => web.address,
:action => 'show', :id => page_name, :mode => 'diff' :action => 'show', :id => page_name, :mode => 'diff'
end end
def expire_cached_summary_pages(web) def expire_cached_summary_pages(web)
categories = WikiReference.list_categories(web) categories = WikiReference.all(:conditions => "link_type = 'C'")
%w(recently_revised list).each do |action| %w(recently_revised list).each do |action|
expire_action :controller => 'wiki', :web => web.address, :action => action expire_action :controller => 'wiki', :web => web.address, :action => action
categories.each do |category| categories.each do |category|
expire_action :controller => 'wiki', :web => web.address, :action => action, :category => category expire_action :controller => 'wiki', :web => web.address, :action => action, :category => category.referenced_name
end end
end end
@ -26,14 +26,12 @@ module CacheSweepingHelper
end end
def expire_cached_revisions(page) def expire_cached_revisions(page)
page.rev_ids.count.times do |i| page.revisions.length.times do |i|
revno = i+1 revno = i+1
expire_action :controller => 'wiki', :web => page.web.address, expire_action :controller => 'wiki', :web => page.web.address,
:action => 'revision', :id => page.name, :rev => revno :action => 'revision', :id => page.name, :rev => revno
expire_action :controller => 'wiki', :web => page.web.address, expire_action :controller => 'wiki', :web => page.web.address,
:action => 'revision', :id => page.name, :rev => revno, :mode => 'diff' :action => 'revision', :id => page.name, :rev => revno, :mode => 'diff'
expire_action :controller => 'wiki', :web => page.web.address,
:action => 'source', :id => page.name, :rev => revno
end end
end end

View file

@ -1,14 +1,14 @@
# Controller responsible for serving files and pictures. # Controller responsible for serving files and pictures.
require 'zip/zip' require 'zip/zip'
require 'instiki_stringsupport' require 'stringsupport'
class FileController < ApplicationController class FileController < ApplicationController
layout 'default' layout 'default'
before_filter :check_authorized before_filter :dnsbl_check, :check_authorized
before_filter :check_allow_uploads, :dnsbl_check, :except => [:file, :blahtex_png] before_filter :check_allow_uploads, :except => [:file, :blahtex_png]
def file def file
@file_name = params['id'] @file_name = params['id']
@ -18,9 +18,6 @@ class FileController < ApplicationController
new_file = @web.wiki_files.create(params['file']) new_file = @web.wiki_files.create(params['file'])
if new_file.valid? if new_file.valid?
flash[:info] = "File '#{@file_name}' successfully uploaded" flash[:info] = "File '#{@file_name}' successfully uploaded"
WikiReference.pages_that_link_to_file(@web, @file_name).each do |page|
RevisionSweeper.expire_page(@web, page)
end
redirect_to(params['referring_page']) redirect_to(params['referring_page'])
else else
# pass the file with errors back into the form # pass the file with errors back into the form
@ -29,7 +26,7 @@ class FileController < ApplicationController
end end
else else
# no form supplied, this is a request to download the file # no form supplied, this is a request to download the file
file = @web.files_path.join(@file_name) file = @web.files_path + '/' + @file_name
if File.exists?(file) if File.exists?(file)
send_file(file) send_file(file)
else else
@ -41,7 +38,7 @@ class FileController < ApplicationController
end end
def blahtex_png def blahtex_png
send_file(@web.blahtex_pngs_path.join(params['id'])) send_file(@web.blahtex_pngs_path + '/' + params['id'])
end end
def delete def delete
@ -91,37 +88,50 @@ class FileController < ApplicationController
protected protected
def check_authorized def check_authorized
unless authorized? or @web.published? if authorized? or @web.published?
return true
else
@hide_navigation = true @hide_navigation = true
render(:status => 403, :text => 'This web is private', :layout => true) render(:status => 403, :text => 'This web is private', :layout => true)
return false
end end
end end
def check_allow_uploads def check_allow_uploads
if @web render(:status => 404, :text => "Web #{params['web'].inspect} not found", :layout => 'error') and return false unless @web
if @web.allow_uploads? and authorized? if @web.allow_uploads? and authorized?
return true return true
else
@hide_navigation = true
render(:status => 403, :text => 'File uploads are blocked by the webmaster', :layout => true)
return false
end
else else
render(:status => 404, :text => "Web #{params['web'].inspect} not found", :layout => 'error') @hide_navigation = true
render(:status => 403, :text => 'File uploads are blocked by the webmaster', :layout => true)
return false
end end
end end
private private
def is_post
unless (request.post? || ENV["RAILS_ENV"] == "test")
headers['Allow'] = 'POST'
render(:status => 405, :text => 'You must use an HTTP POST', :layout => 'error')
return false
end
return true
end
def import_from_archive(archive) def import_from_archive(archive)
logger.info "Importing pages from #{archive}" logger.info "Importing pages from #{archive}"
zip = Zip::ZipInputStream.open(archive) zip = Zip::ZipInputStream.open(archive)
while (entry = zip.get_next_entry) do while (entry = zip.get_next_entry) do
ext_length = File.extname(entry.name).length ext_length = File.extname(entry.name).length
page_name = entry.name[0..-(ext_length + 1)].purify page_name = entry.name[0..-(ext_length + 1)]
page_content = entry.get_input_stream.read.purify page_content = entry.get_input_stream.read
logger.info "Processing page '#{page_name}'" logger.info "Processing page '#{page_name}'"
begin begin
if !page_content.is_utf8?
logger.info "Page '#{page_name}' contains non-utf8 character data. Skipping."
next
end
existing_page = @wiki.read_page(@web.address, page_name) existing_page = @wiki.read_page(@web.address, page_name)
if existing_page if existing_page
if existing_page.content == page_content if existing_page.content == page_content

View file

@ -6,42 +6,23 @@ class RevisionSweeper < ActionController::Caching::Sweeper
observe Revision, Page observe Revision, Page
def before_save(record)
if record.is_a?(Revision)
expire_cached_page(record.page.web, record.page.name)
expire_cached_revisions(record.page)
end
end
def after_save(record) def after_save(record)
if record.is_a?(Revision) if record.is_a?(Revision)
expire_caches(record.page) expire_caches(record.page)
end end
end end
def after_create(record)
if record.is_a?(Page)
WikiReference.pages_that_reference(record.web, record.name).each do |page_name|
expire_cached_page(record.web, page_name)
end
end
end
def after_delete(record) def after_delete(record)
if record.is_a?(Page) if record.is_a?(Page)
expire_caches(record) expire_caches(record)
end end
end end
def self.expire_page(web, page_name)
new.expire_cached_page(web, page_name)
end
private private
def expire_caches(page) def expire_caches(page)
expire_cached_summary_pages(page.web) expire_cached_summary_pages(page.web)
pages_to_expire = ([page.name] + pages_to_expire = ([page.name] + WikiReference.pages_that_reference(page.web, page.name) +
WikiReference.pages_redirected_to(page.web, page.name) + WikiReference.pages_redirected_to(page.web, page.name) +
WikiReference.pages_that_include(page.web, page.name)).uniq WikiReference.pages_that_include(page.web, page.name)).uniq
pages_to_expire.each { |page_name| expire_cached_page(page.web, page_name) } pages_to_expire.each { |page_name| expire_cached_page(page.web, page_name) }

View file

@ -9,7 +9,7 @@ class WebSweeper < ActionController::Caching::Sweeper
def after_save(record) def after_save(record)
if record.is_a?(Web) if record.is_a?(Web)
web = record web = record
web.pages.find_each { |page| expire_cached_page(web, page.name) } web.pages.each { |page| expire_cached_page(web, page.name) }
expire_cached_summary_pages(web) expire_cached_summary_pages(web)
elsif record.is_a?(WikiFile) elsif record.is_a?(WikiFile)
record.web.pages_that_link_to_file(record.file_name).each do |page| record.web.pages_that_link_to_file(record.file_name).each do |page|

View file

@ -1,26 +1,25 @@
require 'fileutils' require 'fileutils'
require 'maruku' require 'maruku'
require 'maruku/ext/math'
require 'zip/zip' require 'zip/zip'
require 'instiki_stringsupport' require 'stringsupport'
require 'resolv' require 'resolv'
class WikiController < ApplicationController class WikiController < ApplicationController
before_filter :load_page before_filter :load_page
before_filter :dnsbl_check, :only => [:edit, :new, :save, :export_html, :export_markup] before_filter :dnsbl_check, :only => [:edit, :new, :save, :export_html, :export_markup]
caches_action :show, :published, :authors, :tex, :s5, :print, :recently_revised, :list, :file_list, :source, caches_action :show, :published, :authors, :tex, :s5, :print, :recently_revised, :list, :file_list,
:history, :revision, :atom_with_content, :atom_with_headlines, :if => Proc.new { |c| c.send(:do_caching?) } :history, :revision, :atom_with_content, :atom_with_headlines, :if => Proc.new { |c| c.send(:do_caching?) }
cache_sweeper :revision_sweeper cache_sweeper :revision_sweeper
layout 'default', :except => [:atom_with_content, :atom_with_headlines, :atom, :source, :tex, :s5, :export_html] layout 'default', :except => [:atom_with_content, :atom_with_headlines, :atom, :tex, :s5, :export_html]
def index def index
if @web_name if @web_name
redirect_home redirect_home
elsif not @wiki.setup? elsif not @wiki.setup?
redirect_to :controller => 'admin', :action => 'create_system' redirect_to :controller => 'admin', :action => 'create_system'
elsif @wiki.webs.size == 1 elsif @wiki.webs.length == 1
redirect_home @wiki.webs.values.first.address redirect_home @wiki.webs.values.first.address
else else
redirect_to :action => 'web_list' redirect_to :action => 'web_list'
@ -68,65 +67,42 @@ class WikiController < ApplicationController
end end
def export_html def export_html
stylesheet = File.read(File.join(RAILS_ROOT, 'public', 'stylesheets', 'instiki.css'))
export_pages_as_zip(html_ext) do |page| export_pages_as_zip(html_ext) do |page|
renderer = PageRenderer.new(page.current_revision)
renderer = PageRenderer.new(page.revisions.last)
rendered_page = <<-EOL rendered_page = <<-EOL
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg-flat.dtd" > <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg-flat.dtd" >
<html xmlns="http://www.w3.org/1999/xhtml"> <html xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<title>#{page.plain_name} in #{@web.name}</title> <title>#{page.plain_name} in #{@web.name}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src="public/javascripts/page_helper.js" type="text/javascript"></script> <style type="text/css">
<link href="public/stylesheets/instiki.css" media="all" rel="stylesheet" type="text/css" /> h1#pageName, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover {
<link href="public/stylesheets/syntax.css" media="all" rel="stylesheet" type="text/css" /> color: ##{@web ? @web.color : "393" };
<style type="text/css"> }
h1#pageName, div.info, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover, [actiontype="toggle"]:hover, #TextileHelp h3 { .newWikiWord { background-color: white; font-style: italic; }
color: ##{@web ? @web.color : "393"}; #{stylesheet}
} </style>
a:visited.existingWikiWord { <style type="text/css">
color: ##{darken(@web ? @web.color : "393")}; #{@web.additional_style}
} </style>
</style> </head>
<body>
<style type="text/css"><!--/*--><![CDATA[/*><!--*/ <h1 id="pageName">
#{@web ? @web.additional_style : ''} <span class="webName">#{@web.name}</span><br />
/*]]>*/--></style> #{page.plain_name}
<script src="public/javascripts/prototype.js" type="text/javascript"></script> </h1>
<script src="public/javascripts/effects.js" type="text/javascript"></script> #{renderer.display_content_for_export}
<script src="public/javascripts/dragdrop.js" type="text/javascript"></script> <div class="byline">
<script src="public/javascripts/controls.js" type="text/javascript"></script> #{page.revisions? ? "Revised" : "Created" } on #{ page.revised_at.strftime('%B %d, %Y %H:%M:%S') }
<script src="public/javascripts/application.js" type="text/javascript"></script> by
#{ UrlGenerator.new(self).make_link(page.author.name, @web, nil, { :mode => :export }) }
</head> </div>
<body> </body>
<div id="Container"> </html>
<div id="Content"> EOL
<h1 id="pageName">
#{xhtml_enabled? ? %{<span id="svg_logo"><svg version="1.1" width="100%" height="100%" viewBox='0 -1 180 198' xmlns='http://www.w3.org/2000/svg'>
<path id="svg_logo_path" fill="##{@web ? @web.color : "393"}" stroke-width='0.5' stroke='#000' d='
M170,60c4,11-1,20-12,25c-9,4-25,3-20,15c5,5,15,0,24,1c11,1,21,11,14,21c-10,15-35,6-48-1c-5-3-27-23-32-10c-1,13,15,10,22,16
c11,4,24,14,34,20c12,10,7,25-9,23c-11-1-22-9-30-16c-5-5-13-18-21-9c-2,6,2,11,5,14c9,9,22,14,22,31c-2,8-12,8-18,4c-4-3-9-8-11-13
c-3-6-5-18-12-18c-14-1-5,28-18,30c-9,2-13-9-12-16c1-14,12-24,21-31c5-4,17-13,10-20c-9-10-19,12-23,16c-7,7-17,16-31,15
c-9-1-18-9-11-17c5-7,14-4,23-6c6-1,15-8,8-15c-5-6-57,2-42-24c7-12,51,4,61,6c6,1,17,4,18-4c2-11-12-7-21-8c-21-2-49-14-49-34
c0-5,3-11,8-11C31,42,34,65,42,67c6,1,9-3,8-9C49,49,38,40,40,25c1-5,4-15,13-14c10,2,11,18,13,29c1,8,0,24,7,28c15,0,5-22,4-30
C74,23,78,7,87,1c8-4,14,1,16,9c2,11-8,21-2,30c8,2,11-6,14-12c9-14,36-18,30,5c-3,9-12,19-21,24c-6,4-22,10-23,19c-2,14,15,2,18-2
c9-9,20-18,33-22C159,52,166,54,170,60' />
</svg></span>} : ''}
<span class="webName">#{@web.name}</span><br />
#{page.plain_name}
</h1>
#{renderer.display_content_for_export}
<div class="byline">
#{page.revisions? ? "Revised" : "Created" } on #{ page.revised_at.strftime('%B %d, %Y %H:%M:%S') }
by
#{ UrlGenerator.new(self).make_link(@web, page.author.name, @web, nil, { :mode => :export }) }
</div>
</div>
</div>
</body>
</html>
EOL
rendered_page rendered_page
end end
end end
@ -184,34 +160,10 @@ EOL
def atom_with_headlines def atom_with_headlines
render_atom(hide_description = true) render_atom(hide_description = true)
end end
def tex_list
return unless is_post
if [:markdownMML, :markdownPNG, :markdown].include?(@web.markup)
@tex_content = ''
# Ruby 1.9.x has ordered hashes; 1.8.x doesn't. So let's just parse the query ourselves.
ordered_params = ActiveSupport::OrderedHash[*request.raw_post.split('&').collect {|k_v| k_v.split('=').collect {|x| CGI::unescape(x)}}.flatten]
ordered_params.each do |name, p|
if p == 'tex' && @web.has_page?(name)
@tex_content << "\\section*\{#{Maruku.new(name).to_latex.strip}\}\n\n"
@tex_content << Maruku.new(@web.page(name).content).to_latex
end
end
else
@tex_content = 'TeX export only supported with the Markdown text filters.'
end
if @tex_content == ''
flash[:error] = "You didn't select any pages to export."
redirect_to :back
return
end
expire_action :controller => 'wiki', :web => @web.address, :action => 'list', :category => params['category']
render(:layout => 'tex')
end
def search def search
@query = params['query'] ? params['query'].purify : '' @query = params['query']
render(:text => "Your query string was not valid utf-8", :layout => 'error', :status => 400) and return unless @query.is_utf8?
@title_results = @web.select { |page| page.name =~ /#{@query}/i }.sort @title_results = @web.select { |page| page.name =~ /#{@query}/i }.sort
@results = @web.select { |page| page.content =~ /#{@query}/i }.sort @results = @web.select { |page| page.content =~ /#{@query}/i }.sort
all_pages_found = (@results + @title_results).uniq all_pages_found = (@results + @title_results).uniq
@ -228,7 +180,7 @@ EOL
end end
def edit def edit
if @page.nil? if @page.nil? or not @page_name.is_utf8?
redirect_home redirect_home
elsif @page.locked?(Time.now) and not params['break_lock'] elsif @page.locked?(Time.now) and not params['break_lock']
redirect_to :web => @web_name, :action => 'locked', :id => @page_name redirect_to :web => @web_name, :action => 'locked', :id => @page_name
@ -238,10 +190,12 @@ EOL
end end
def locked def locked
render(:text => 'Page name is not valid utf-8.', :status => 400, :layout => 'error') unless @page_name.is_utf8?
# to template # to template
end end
def new def new
render(:text => 'Page name is not valid utf-8.', :status => 400, :layout => 'error') unless @page_name.is_utf8?
# to template # to template
end end
@ -262,7 +216,7 @@ EOL
redirect_home redirect_home
end end
@link_mode ||= :show @link_mode ||= :show
@renderer = PageRenderer.new(@page.current_revision) @renderer = PageRenderer.new(@page.revisions.last)
# to template # to template
end end
@ -276,7 +230,7 @@ EOL
@page ||= wiki.read_page(@web_name, @page_name) @page ||= wiki.read_page(@web_name, @page_name)
@link_mode ||= :publish @link_mode ||= :publish
if @page if @page
@renderer = PageRenderer.new(@page.current_revision) @renderer = PageRenderer.new(@page.revisions.last)
else else
real_page = WikiReference.page_that_redirects_for(@web, @page_name) real_page = WikiReference.page_that_redirects_for(@web, @page_name)
if real_page if real_page
@ -296,30 +250,37 @@ EOL
def rollback def rollback
get_page_and_revision get_page_and_revision
if @page.locked?(Time.now) and not params['break_lock']
redirect_to :web => @web_name, :action => 'locked', :id => @page_name
else
@page.lock(Time.now, @author)
end
end end
def save def save
render(:status => 404, :text => 'Undefined page name', :layout => 'error') and return if @page_name.nil? render(:status => 404, :text => 'Undefined page name', :layout => 'error') and return if @page_name.nil? or not @page_name.is_utf8?
return unless is_post unless (request.post? || ENV["RAILS_ENV"] == "test")
author_name = params['author'].purify headers['Allow'] = 'POST'
render(:status => 405, :text => 'You must use an HTTP POST', :layout => 'error')
return
end
author_name = params['author']
author_name = 'AnonymousCoward' if author_name =~ /^\s*$/ author_name = 'AnonymousCoward' if author_name =~ /^\s*$/
begin begin
the_content = params['content'].purify raise Instiki::ValidationError.new('Your name was not valid utf-8') unless author_name.is_utf8?
prev_content = '' raise Instiki::ValidationError.new('Your name cannot contain a "."') if author_name.include? '.'
cookies['author'] = { :value => author_name, :expires => Time.utc(2030) }
the_content = params['content']
filter_spam(the_content) filter_spam(the_content)
cookies['author'] = { :value => author_name.dup.as_bytes, :expires => Time.utc(2030) } unless the_content.is_utf8?
if @page
the_content = @page.content
else
the_content = ''
end
raise Instiki::ValidationError.new('Your content was not valid utf-8.')
end
if @page if @page
new_name = params['new_name'] ? params['new_name'].purify : @page_name new_name = params['new_name'] || @page_name
new_name = @page_name if new_name.empty? raise Instiki::ValidationError.new('Your new title was not valid utf-8.') unless new_name.is_utf8?
prev_content = @page.current_revision.content raise Instiki::ValidationError.new('Your new title cannot contain a "."') if new_name.include? '.'
raise Instiki::ValidationError.new('A page named "' + new_name.escapeHTML + '" already exists.') if raise Instiki::ValidationError.new('A page named "' + new_name.escapeHTML + '" already exists.') if @page_name != new_name && @web.has_page?(new_name)
@page_name != new_name && @web.has_page?(new_name)
wiki.revise_page(@web_name, @page_name, new_name, the_content, Time.now, wiki.revise_page(@web_name, @page_name, new_name, the_content, Time.now,
Author.new(author_name, remote_ip), PageRenderer.new) Author.new(author_name, remote_ip), PageRenderer.new)
@page.unlock @page.unlock
@ -332,15 +293,11 @@ EOL
rescue Instiki::ValidationError => e rescue Instiki::ValidationError => e
flash[:error] = e.to_s flash[:error] = e.to_s
logger.error e logger.error e
param_hash = {:web => @web_name, :id => @page_name}
# Work around Rails bug: flash will not display if query string is longer than 10192 bytes
param_hash.update( :content => the_content ) if the_content &&
CGI::escape(the_content).length < 10183 && the_content != prev_content
if @page if @page
@page.unlock @page.unlock
redirect_to param_hash.update( :action => 'edit' ) redirect_to :action => 'edit', :web => @web_name, :id => @page_name, :content => the_content
else else
redirect_to param_hash.update( :action => 'new' ) redirect_to :action => 'new', :web => @web_name, :id => @page_name, :content => the_content
end end
end end
end end
@ -348,7 +305,7 @@ EOL
def show def show
if @page if @page
begin begin
@renderer = PageRenderer.new(@page.current_revision) @renderer = PageRenderer.new(@page.revisions.last)
@show_diff = (params[:mode] == 'diff') @show_diff = (params[:mode] == 'diff')
render :action => 'page' render :action => 'page'
# TODO this rescue should differentiate between errors due to rendering and errors in # TODO this rescue should differentiate between errors due to rendering and errors in
@ -363,7 +320,7 @@ EOL
end end
end end
else else
if not @page_name.nil? and not @page_name.empty? if not @page_name.nil? and @page_name.is_utf8? and not @page_name.empty?
real_page = WikiReference.page_that_redirects_for(@web, @page_name) real_page = WikiReference.page_that_redirects_for(@web, @page_name)
if real_page if real_page
flash[:info] = "Redirected from \"#{@page_name}\"." flash[:info] = "Redirected from \"#{@page_name}\"."
@ -383,8 +340,8 @@ EOL
if @page if @page
@revisions_by_day = Hash.new { |h, day| h[day] = [] } @revisions_by_day = Hash.new { |h, day| h[day] = [] }
@revision_numbers = Hash.new { |h, id| h[id] = [] } @revision_numbers = Hash.new { |h, id| h[id] = [] }
revision_number = @page.rev_ids.size revision_number = @page.revisions.length
@page.rev_ids.reverse.each do |rev| @page.revisions.reverse.each do |rev|
day = Date.new(rev.revised_at.year, rev.revised_at.month, rev.revised_at.day) day = Date.new(rev.revised_at.year, rev.revised_at.month, rev.revised_at.day)
@revisions_by_day[day] << rev @revisions_by_day[day] << rev
@revision_numbers[rev.id] = revision_number @revision_numbers[rev.id] = revision_number
@ -392,7 +349,7 @@ EOL
end end
render :action => 'history' render :action => 'history'
else else
if not @page_name.nil? and not @page_name.empty? if not @page_name.nil? and @page_name.is_utf8? and not @page_name.empty?
redirect_to :web => @web_name, :action => 'new', :id => @page_name redirect_to :web => @web_name, :action => 'new', :id => @page_name
else else
render :text => 'Page name is not specified', :status => 404, :layout => 'error' render :text => 'Page name is not specified', :status => 404, :layout => 'error'
@ -400,22 +357,17 @@ EOL
end end
end end
def source
@revision = @page.revisions[params['rev'].to_i - 1] if params['rev']
end
def tex def tex
if [:markdownMML, :markdownPNG, :markdown].include?(@web.markup) if @web.markup == :markdownMML or @web.markup == :markdownPNG or @web.markup == :markdown
@tex_content = Maruku.new(@page.content).to_latex @tex_content = Maruku.new(@page.content).to_latex
else else
@tex_content = 'TeX export only supported with the Markdown text filters.' @tex_content = 'TeX export only supported with the Markdown text filters.'
end end
render(:layout => 'tex')
end end
def s5 def s5
if [:markdownMML, :markdownPNG, :markdown].include?(@web.markup) if @web.markup == :markdownMML || @web.markup == :markdownPNG || @web.markup == :markdown
my_rendered = PageRenderer.new(@page.current_revision) my_rendered = PageRenderer.new(@page.revisions.last)
@s5_content = my_rendered.display_s5 @s5_content = my_rendered.display_s5
@s5_theme = my_rendered.s5_theme @s5_theme = my_rendered.s5_theme
else else
@ -424,6 +376,15 @@ EOL
end end
end end
def html_ext
if xhtml_enabled? && request.env.include?('HTTP_ACCEPT') &&
Mime::Type.parse(request.env["HTTP_ACCEPT"]).include?(Mime::XHTML)
'xhtml'
else
'html'
end
end
protected protected
def do_caching? def do_caching?
@ -431,7 +392,7 @@ EOL
end end
def load_page def load_page
@page_name = params['id'] ? params['id'].purify : nil @page_name = params['id']
@page = @wiki.read_page(@web_name, @page_name) if @page_name @page = @wiki.read_page(@web_name, @page_name) if @page_name
end end
@ -462,32 +423,22 @@ EOL
file_prefix = "#{@web.address}-#{file_type}-" file_prefix = "#{@web.address}-#{file_type}-"
timestamp = @web.revised_at.strftime('%Y-%m-%d-%H-%M-%S') timestamp = @web.revised_at.strftime('%Y-%m-%d-%H-%M-%S')
file_path = @wiki.storage_path.join(file_prefix + timestamp + '.zip') file_path = File.join(@wiki.storage_path, file_prefix + timestamp + '.zip')
tmp_path = "#{file_path}.tmp" tmp_path = "#{file_path}.tmp"
Zip::ZipFile.open(tmp_path, Zip::ZipFile::CREATE) do |zip_out| Zip::ZipOutputStream.open(tmp_path) do |zip_out|
@web.select.by_name.each do |page| @web.select.by_name.each do |page|
zip_out.get_output_stream("#{CGI.escape(page.name)}.#{file_type}") do |f| zip_out.put_next_entry("#{CGI.escape(page.name)}.#{file_type}")
f.puts(block.call(page)) zip_out.puts(block.call(page))
end
end end
# add an index file, and the stylesheet and javascript directories, if exporting to HTML # add an index file, if exporting to HTML
if file_type.to_s.downcase == html_ext if file_type.to_s.downcase == html_ext
zip_out.get_output_stream("index.#{html_ext}") do |f| zip_out.put_next_entry "index.#{html_ext}"
f.puts "<html xmlns='http://www.w3.org/1999/xhtml'><head>" + zip_out.puts "<html xmlns='http://www.w3.org/1999/xhtml'><head>" +
"<meta http-equiv=\"Refresh\" content=\"0;URL=HomePage.#{html_ext}\" /></head></html>" "<META HTTP-EQUIV=\"Refresh\" CONTENT=\"0;URL=HomePage.#{file_type}\"></head></html>"
end
dir = Rails.root.join('public')
Dir["#{dir}/{images,javascripts,s5,stylesheets}/**/*"].each do |f|
zip_out.add "public#{f.sub(dir.to_s,'')}", f
end
end
files = @web.files_path
Dir["#{files}/**/*"].each do |f|
zip_out.add "files#{f.sub(files.to_s,'')}", f
end end
end end
FileUtils.rm_rf(Dir[@wiki.storage_path.join(file_prefix + '*.zip').to_s]) FileUtils.rm_rf(Dir[File.join(@wiki.storage_path, file_prefix + '*.zip')])
FileUtils.mv(tmp_path, file_path) FileUtils.mv(tmp_path, file_path)
send_file file_path send_file file_path
end end
@ -506,7 +457,7 @@ EOL
if params['rev'] if params['rev']
@revision_number = params['rev'].to_i @revision_number = params['rev'].to_i
else else
@revision_number = @page.rev_ids.size @revision_number = @page.revisions.length
end end
@revision = @page.revisions[@revision_number - 1] @revision = @page.revisions[@revision_number - 1]
end end
@ -552,7 +503,11 @@ EOL
def rss_with_content_allowed? def rss_with_content_allowed?
@web.password.nil? or @web.published? @web.password.nil? or @web.published?
end end
def truncate(text, length = 30, truncate_string = '...')
if text.length > length then text[0..(length - 3)] + truncate_string else text end
end
def filter_spam(content) def filter_spam(content)
@@spam_patterns ||= load_spam_patterns @@spam_patterns ||= load_spam_patterns
@@spam_patterns.each do |pattern| @@spam_patterns.each do |pattern|
@ -561,9 +516,9 @@ EOL
end end
def load_spam_patterns def load_spam_patterns
spam_patterns_file = Rails.root.join('config', 'spam_patterns.txt') spam_patterns_file = "#{RAILS_ROOT}/config/spam_patterns.txt"
if File.exists?(spam_patterns_file) if File.exists?(spam_patterns_file)
spam_patterns_file.readlines.inject([]) { |patterns, line| patterns << Regexp.new(line.chomp, Regexp::IGNORECASE) } File.readlines(spam_patterns_file).inject([]) { |patterns, line| patterns << Regexp.new(line.chomp, Regexp::IGNORECASE) }
else else
[] []
end end

View file

@ -1,6 +1,5 @@
# The methods added to this helper will be available to all templates in the application. # The methods added to this helper will be available to all templates in the application.
module ApplicationHelper module ApplicationHelper
require 'instiki_stringsupport'
# Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
# where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and # where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and
@ -31,7 +30,7 @@ require 'instiki_stringsupport'
end end
end end
html_options.join("\n").html_safe html_options.join("\n")
end end
# Creates a hyperlink to a Wiki page, without checking if the page exists or not # Creates a hyperlink to a Wiki page, without checking if the page exists or not
@ -39,39 +38,39 @@ require 'instiki_stringsupport'
link_to( link_to(
text || page.plain_name, text || page.plain_name,
{:web => @web.address, :action => 'show', :id => page.name, :only_path => true}, {:web => @web.address, :action => 'show', :id => page.name, :only_path => true},
html_options).html_safe html_options)
end end
# Creates a hyperlink to a Wiki page, or to a "new page" form if the page doesn't exist yet # Creates a hyperlink to a Wiki page, or to a "new page" form if the page doesn't exist yet
def link_to_page(page_name, web = @web, text = nil, options = {}) def link_to_page(page_name, web = @web, text = nil, options = {})
raise 'Web not defined' if web.nil? raise 'Web not defined' if web.nil?
UrlGenerator.new(@controller).make_link(@web, page_name, web, text, UrlGenerator.new(@controller).make_link(page_name, web, text,
options.merge(:base_url => "#{base_url}/#{web.address}")).html_safe options.merge(:base_url => "#{base_url}/#{web.address}"))
end end
def author_link(page, options = {}) def author_link(page, options = {})
UrlGenerator.new(@controller).make_link(@web, page.author.name, page.web, nil, options).purify.html_safe UrlGenerator.new(@controller).make_link(page.author.name, page.web, nil, options)
end end
# Create a hyperlink to a particular revision of a Wiki page # Create a hyperlink to a particular revision of a Wiki page
def link_to_revision(page, revision_number, text = nil, mode = nil, html_options = {}) def link_to_revision(page, revision_number, text = nil, mode = nil, html_options = {})
revision_number == page.rev_ids.size ? revision_number == page.revisions.length ?
link_to( link_to(
text || page.plain_name, text || page.plain_name,
{:web => @web.address, :action => 'show', :id => page.name, {:web => @web.address, :action => 'show', :id => page.name,
:mode => mode}, html_options).html_safe : :mode => mode}, html_options) :
link_to( link_to(
text || page.plain_name + "(rev # #{revision_number})".html_safe, text || page.plain_name + "(rev # #{revision_number})",
{:web => @web.address, :action => 'revision', :id => page.name, {:web => @web.address, :action => 'revision', :id => page.name,
:rev => revision_number, :mode => mode}, html_options).html_safe :rev => revision_number, :mode => mode}, html_options)
end end
# Create a hyperlink to the history of a particular Wiki page # Create a hyperlink to the history of a particular Wiki page
def link_to_history(page, text = nil, html_options = {}) def link_to_history(page, text = nil, html_options = {})
link_to( link_to(
text || page.plain_name + "(history)".html_safe, text || page.plain_name + "(history)",
{:web => @web.address, :action => 'history', :id => page.name}, {:web => @web.address, :action => 'history', :id => page.name},
html_options).html_safe html_options)
end end
def base_url def base_url
@ -84,19 +83,19 @@ require 'instiki_stringsupport'
if @categories.empty? if @categories.empty?
'' ''
else else
("<div id=\"categories\">\n" + "<div id=\"categories\">\n" +
'<strong>Categories</strong>:' + '<strong>Categories</strong>:' +
'[' + link_to_unless_current('Any', :web => @web.address, :action => self.action_name, :category => nil) + "]\n" + '[' + link_to_unless_current('Any', :web => @web.address, :action => self.action_name, :category => nil) + "]\n" +
@categories.map { |c| @categories.map { |c|
link_to_unless_current(c.html_safe, :web => @web.address, :action => self.action_name, :category => c) link_to_unless_current(c, :web => @web.address, :action => self.action_name, :category => c)
}.join(', ') + "\n" + }.join(', ') + "\n" +
'</div>').html_safe '</div>'
end end
end end
# Performs HTML escaping on text, but keeps linefeeds intact (by replacing them with <br/>) # Performs HTML escaping on text, but keeps linefeeds intact (by replacing them with <br/>)
def escape_preserving_linefeeds(text) def escape_preserving_linefeeds(text)
h(text).gsub(/\n/, '<br/>').as_utf8.html_safe h(text).gsub(/\n/, '<br/>')
end end
def format_date(date, include_time = true) def format_date(date, include_time = true)
@ -109,22 +108,7 @@ require 'instiki_stringsupport'
end end
def rendered_content(page) def rendered_content(page)
PageRenderer.new(page.current_revision).display_content PageRenderer.new(page.revisions.last).display_content
end
def truncate(text, *args)
options = args.extract_options!
options.reverse_merge!(:length => 30, :omission => "...")
return text.html_safe if text.num_chars <= options[:length]
len = options[:length] - options[:omission].as_utf8.num_chars
t = ''
text.split.collect do |word|
if t.num_chars + word.num_chars <= len
t << word + ' '
else
return (t.chop + options[:omission]).html_safe
end
end
end end
end end

View file

@ -6,7 +6,7 @@ module WikiHelper
menu << back_for_revision if @revision_number > 1 menu << back_for_revision if @revision_number > 1
menu << current_revision menu << current_revision
menu << see_or_hide_changes_for_revision if @revision_number > 1 menu << see_or_hide_changes_for_revision if @revision_number > 1
menu << history if @page.rev_ids.size > 1 menu << history if @page.revisions.length > 1
menu << rollback menu << rollback
menu menu
end end
@ -15,11 +15,11 @@ module WikiHelper
menu = [] menu = []
menu << edit_page menu << edit_page
menu << edit_web if @page.name == "HomePage" menu << edit_web if @page.name == "HomePage"
if @page.rev_ids.size > 1 if @page.revisions.length > 1
menu << back_for_page menu << back_for_page
menu << see_or_hide_changes_for_page menu << see_or_hide_changes_for_page
end end
menu << history if @page.rev_ids.size > 1 menu << history if @page.revisions.length > 1
menu menu
end end
@ -40,15 +40,15 @@ module WikiHelper
end end
def forward def forward
if @revision_number < @page.rev_ids.size - 1 if @revision_number < @page.revisions.length - 1
link_to('Forward in time', link_to('Forward in time',
{:web => @web.address, :action => 'revision', :id => @page.name, :rev => @revision_number + 1}, {:web => @web.address, :action => 'revision', :id => @page.name, :rev => @revision_number + 1},
{:class => 'navlink', :accesskey => 'F', :id => 'to_next_revision', :rel => 'nofollow'}) + {:class => 'navlink', :accesskey => 'F', :id => 'to_next_revision', :rel => 'nofollow'}) +
" <span class='revisions'>(#{@revision.page.rev_ids.size - @revision_number} more)</span> ".html_safe " <span class='revisions'>(#{@revision.page.revisions.length - @revision_number} more)</span> "
else else
link_to('Forward in time', {:web => @web.address, :action => 'show', :id => @page.name}, link_to('Forward in time', {:web => @web.address, :action => 'show', :id => @page.name},
{:class => 'navlink', :accesskey => 'F', :id => 'to_next_revision', :rel => 'nofollow'}) + {:class => 'navlink', :accesskey => 'F', :id => 'to_next_revision', :rel => 'nofollow'}) +
" <span class='revisions'>(to current)</span>".html_safe " <span class='revisions'>(to current)</span>"
end end
end end
@ -56,15 +56,15 @@ module WikiHelper
link_to('Back in time', link_to('Back in time',
{:web => @web.address, :action => 'revision', :id => @page.name, :rev => @revision_number - 1}, {:web => @web.address, :action => 'revision', :id => @page.name, :rev => @revision_number - 1},
{:class => 'navlink', :id => 'to_previous_revision', :rel => 'nofollow'}) + {:class => 'navlink', :id => 'to_previous_revision', :rel => 'nofollow'}) +
" <span class='revisions'>(#{@revision_number - 1} more)</span>".html_safe " <span class='revisions'>(#{@revision_number - 1} more)</span>"
end end
def back_for_page def back_for_page
link_to('Back in time', link_to('Back in time',
{:web => @web.address, :action => 'revision', :id => @page.name, {:web => @web.address, :action => 'revision', :id => @page.name,
:rev => @page.rev_ids.size - 1}, :rev => @page.revisions.length - 1},
{:class => 'navlink', :accesskey => 'B', :id => 'to_previous_revision', :rel => 'nofollow'}) + {:class => 'navlink', :accesskey => 'B', :id => 'to_previous_revision', :rel => 'nofollow'}) +
" <span class='revisions'>(#{@page.rev_ids.size - 1} #{@page.rev_ids.size - 1 == 1 ? 'revision' : 'revisions'})</span>".html_safe " <span class='revisions'>(#{@page.revisions.length - 1} #{@page.revisions.length - 1 == 1 ? 'revision' : 'revisions'})</span>"
end end
def current_revision def current_revision
@ -91,4 +91,6 @@ module WikiHelper
{:class => 'navlink', :id => 'rollback', :rel => 'nofollow'}) {:class => 'navlink', :id => 'rollback', :rel => 'nofollow'})
end end
end end

View file

@ -1,75 +0,0 @@
# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)
require 'instiki_stringsupport'
class Itex
def self.call(env)
if env["PATH_INFO"] =~ /^\/itex/
[200, {"Content-Type" => "application/xml"}, [response(env)]]
else
[404, {"Content-Type" => "text/html"}, ["Not Found"]]
end
end
private
# plugable XML parser; falls back to REXML
begin
require 'nokogiri'
def self.xmlparse(text)
Nokogiri::XML(text) { |config| config.strict }
end
rescue LoadError
require 'rexml/document'
def self.xmlparse(text)
REXML::Document.new(text)
end
end
#error message to return
def self.error(str)
"<math xmlns='http://www.w3.org/1998/Math/MathML' display='inline'><merror><mtext>" +
str + "</mtext></merror></math>"
end
# itex2MML parser
begin
require 'itextomml'
def self.parse_itex(tex, filter)
Itex2MML::Parser.new.send(filter, tex).to_utf8
end
rescue LoadError
def self.parse_itex(tex, filter)
error("Please install the itex2MML Ruby bindings.")
end
end
# the actual response
def self.response(env)
params = Rack::Request.new(env).params
tex = (params['tex'] || '').purify.strip
case params['display']
when 'block'
filter = :block_filter
else
filter = :inline_filter
end
return "<math xmlns='http://www.w3.org/1998/Math/MathML' display='" +
filter.to_s[/(.*?)_filter/] + "'/>" if tex == ''
begin
doc = parse_itex(tex, filter)
# make sure the result is well-formed, before sending it off
begin
xmlparse(doc)
rescue
return error("Ill-formed XML.")
end
return doc
rescue Itex2MML::Error => e
error(e.to_s)
rescue
error("Unknown Error")
end
end
end

View file

@ -3,7 +3,7 @@ class Author < String
attr_reader :name attr_reader :name
def initialize(name, ip = nil) def initialize(name, ip = nil)
@ip = ip @ip = ip
super(name.as_utf8) super(name)
end end
def name=(value) def name=(value)

View file

@ -1,17 +1,11 @@
class Page < ActiveRecord::Base class Page < ActiveRecord::Base
belongs_to :web belongs_to :web
has_many :revisions, :order => 'id', :dependent => :destroy has_many :revisions, :order => 'id', :dependent => :destroy
#In many cases, we don't need to instantiate the full revisions (with all that textual data)
has_many :rev_ids, :order => 'id', :class_name => 'Revision', :select => 'id, revised_at, page_id, author, ip'
has_many :wiki_references, :order => 'referenced_name' has_many :wiki_references, :order => 'referenced_name'
has_one :current_revision, :class_name => 'Revision', :order => 'id DESC' has_one :current_revision, :class_name => 'Revision', :order => 'id DESC'
def name
read_attribute(:name).as_utf8
end
def revise(content, name, time, author, renderer) def revise(content, name, time, author, renderer)
revisions_size = new_record? ? 0 : rev_ids.size revisions_size = new_record? ? 0 : revisions.size
if (revisions_size > 0) and content == current_revision.content and name == self.name if (revisions_size > 0) and content == current_revision.content and name == self.name
raise Instiki::ValidationError.new( raise Instiki::ValidationError.new(
"You have tried to save page '#{name}' without changing its content") "You have tried to save page '#{name}' without changing its content")
@ -48,11 +42,11 @@ class Page < ActiveRecord::Base
end end
def revisions? def revisions?
rev_ids.size > 1 revisions.size > 1
end end
def previous_revision(revision) def previous_revision(revision)
revision_index = rev_ids.each_with_index do |rev, index| revision_index = revisions.each_with_index do |rev, index|
if rev.id == revision.id if rev.id == revision.id
break index break index
else else
@ -74,10 +68,6 @@ class Page < ActiveRecord::Base
wiki_references.select { |ref| ref.wiki_word? }.map { |ref| ref.referenced_name } wiki_references.select { |ref| ref.wiki_word? }.map { |ref| ref.referenced_name }
end end
def categories
wiki_references.select { |ref| ref.category? }.map { |ref| ref.referenced_name }
end
def linked_from def linked_from
web.select.pages_that_link_to(name) web.select.pages_that_link_to(name)
end end
@ -92,7 +82,7 @@ class Page < ActiveRecord::Base
# Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page". # Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page".
def plain_name def plain_name
web.brackets_only? ? name.escapeHTML.html_safe : WikiWords.separate(name).escapeHTML.html_safe web.brackets_only? ? CGI.escapeHTML(name) : CGI.escapeHTML(WikiWords.separate(name))
end end
LOCKING_PERIOD = 30.minutes LOCKING_PERIOD = 30.minutes
@ -114,7 +104,7 @@ class Page < ActiveRecord::Base
end end
def to_param def to_param
name.as_utf8 name
end end
private private

View file

@ -83,17 +83,12 @@ class PageSet < Array
# Returns all the wiki words in this page set for which # Returns all the wiki words in this page set for which
# there are no pages in this page set's web # there are no pages in this page set's web
def wanted_pages def wanted_pages
known_pages = (web.select.names + redirected_names).uniq wiki_words - web.select.names
wiki_words - known_pages
end end
def names def names
self.map { |page| page.name } self.map { |page| page.name }
end end
def redirected_names
self.wiki_words.select {|name| web.has_redirect_for?(name) }.uniq.sort
end
def wiki_words def wiki_words
self.inject([]) { |wiki_words, page| self.inject([]) { |wiki_words, page|

View file

@ -1,8 +1,4 @@
class Revision < ActiveRecord::Base class Revision < ActiveRecord::Base
belongs_to :page belongs_to :page
composed_of :author, :mapping => [ %w(author name), %w(ip ip) ] composed_of :author, :mapping => [ %w(author name), %w(ip ip) ]
def content
read_attribute(:content).as_utf8
end
end end

View file

@ -1,75 +1,48 @@
require 'instiki_stringsupport'
class Web < ActiveRecord::Base class Web < ActiveRecord::Base
## Associations has_many :pages, :dependent => :destroy
has_many :pages, :dependent => :destroy
has_many :wiki_files, :dependent => :destroy has_many :wiki_files, :dependent => :destroy
has_many :revisions, :through => :pages
## Hooks
before_save :sanitize_markup
after_save :create_files_directory
before_validation :validate_address
## Validations
validates_uniqueness_of :address, :message => 'already exists'
validates_length_of :color, :in => 3..6
## Methods
# @return [Wiki] a new Wiki instance
def wiki def wiki
Wiki.new Wiki.new
end end
def settings_changed?(markup, safe_mode, brackets_only) def settings_changed?(markup, safe_mode, brackets_only)
self.markup != markup || self.markup != markup ||
self.safe_mode != safe_mode || self.safe_mode != safe_mode ||
self.brackets_only != brackets_only self.brackets_only != brackets_only
end end
def add_page(name, content, time, author, renderer) def add_page(name, content, time, author, renderer)
page = page(name) || pages.build(:name => name) page = page(name) || Page.new(:web => self, :name => name)
page.revise(content, name, time, author, renderer) page.revise(content, name, time, author, renderer)
end end
# @return [Array<String>] a collection of all the names of the authors that
# have ever contributed to the pages for this Web
def authors def authors
revisions.all( connection.select_all(
:select => "DISTINCT revisions.author", 'SELECT DISTINCT r.author AS author ' +
:order => "1" 'FROM revisions r ' +
).collect(&:author) 'JOIN pages p ON p.id = r.page_id ' +
'WHERE p.web_id = ' + self.id.to_s +
' ORDER by 1 '
).collect { |row| row['author'] }
end end
def categories def categories
select.map { |page| page.categories }.flatten.uniq.sort select.map { |page| page.categories }.flatten.uniq.sort
end end
# @param [String] name the name of some associated Page record to find
# @return [Page, nil] the associated Page record, or +nil+ if no record is
# found with the provided name
def page(name) def page(name)
pages.find_by_name(name) pages.first(:conditions => ['name = ?', name])
end end
# @return [Page] the last associated Page record
def last_page def last_page
pages.last return Page.first(:order => 'id desc', :conditions => ['web_id = ?', self.id])
end end
# @param [String] name the name of some potential Page record
# @return [Boolean] whether or not a given Page record exists with a given
# name
def has_page?(name) def has_page?(name)
pages.exists?(:name => name) Page.count(:conditions => ['web_id = ? AND name = ?', id, name]) > 0
end end
def has_redirect_for?(name) def has_redirect_for?(name)
WikiReference.page_that_redirects_for(self, name) WikiReference.page_that_redirects_for(self, name)
end end
@ -79,11 +52,11 @@ class Web < ActiveRecord::Base
end end
def has_file?(file_name) def has_file?(file_name)
wiki_files.exists?(:file_name => file_name) WikiFile.find_by_file_name(file_name) != nil
end end
def file_list(sort_order="file_name") def file_list(sort_order = 'file_name')
wiki_files.all(:order => sort_order) WikiFile.all(:order => sort_order, :conditions => ['web_id = ?', id])
end end
def pages_that_link_to(page_name) def pages_that_link_to(page_name)
@ -94,95 +67,82 @@ class Web < ActiveRecord::Base
WikiReference.pages_that_link_to_file(self, file_name) WikiReference.pages_that_link_to_file(self, file_name)
end end
# @param [String] file_name the name of some WikiFile of interest
# @return [String, nil] the description of some WikiFile of interest, nil if
# the WikiFile could not be found
def description(file_name) def description(file_name)
wiki_files.find_by_file_name(file_name).try(:description) file = WikiFile.find_by_file_name(file_name)
file.description if file
end end
# @return [Symbol] the type of markup used by this Web
def markup def markup
self[:markup].to_sym read_attribute('markup').to_sym
end end
# @return [Hash] a Hash wherein the key is some author's name, and the
# values are an array of page names for that author.
def page_names_by_author def page_names_by_author
data = revisions.all( connection.select_all(
:select => "DISTINCT revisions.author AS author, pages.name AS page_name", 'SELECT DISTINCT r.author AS author, p.name AS page_name ' +
:order => "pages.name" 'FROM revisions r ' +
) 'JOIN pages p ON r.page_id = p.id ' +
"WHERE p.web_id = #{self.id} " +
data.inject({}) do |result, revision| 'ORDER by p.name'
result[revision.author] ||= [] ).inject({}) { |result, row|
result[revision.author] << revision.page_name author, page_name = row['author'], row['page_name']
result result[author] = [] unless result.has_key?(author)
end result[author] << page_name
result
}
end end
# OPTIMIZE Use the +delete_all+ with conditions for extra efficiency
def remove_pages(pages_to_be_removed) def remove_pages(pages_to_be_removed)
pages_to_be_removed.each { |p| p.destroy } pages_to_be_removed.each { |p| p.destroy }
end end
def revised_at def revised_at
select.most_recent_revision select.most_recent_revision
end end
def select(&condition) def select(&condition)
PageSet.new(self, pages, condition) PageSet.new(self, pages, condition)
end end
def select_all def select_all
PageSet.new(self, pages, nil) PageSet.new(self, pages, nil)
end end
# @return [String] uses the +address+ attribute for this record's parameter name
def to_param def to_param
address address
end end
# Called by an +after_save+ hook. Creates the directory that houses this
# Web's associated files.
#
# TODO Move this into the WikiFile model
def create_files_directory def create_files_directory
return unless allow_uploads == 1 return unless allow_uploads == 1
dummy_file = self.wiki_files.build(:file_name => '0', :description => '0', :content => '0')
dummy_file = wiki_files.build(
:file_name => "0",
:description => "0",
:content => "0"
)
File.umask(0002) File.umask(0002)
dir = File.dirname(dummy_file.content_path)
begin begin
dummy_file.content_path.parent.mkpath require 'fileutils'
FileUtils.mkdir_p dir
dummy_file.save dummy_file.save
dummy_file.destroy dummy_file.destroy
rescue => e rescue => e
logger.error "Failed create files directory for #{address}: #{e}" logger.error("Failed create files directory for #{self.address}: #{e}")
raise "Instiki could not create directory to store uploaded files. " + raise "Instiki could not create directory to store uploaded files. " +
"Please make sure that Instiki is allowed to create directory " + "Please make sure that Instiki is allowed to create directory " +
"#{dummy_file.content_path.expand_path} and add files to it." "#{File.expand_path(dir)} and add files to it."
end end
end end
# @return [Pathname] the path to the files for this record
def files_path def files_path
path = Rails.root.join("webs")
if default_web? if default_web?
path.join("files") "#{RAILS_ROOT}/webs/files"
else else
path.join(address, "files") "#{RAILS_ROOT}/webs/#{self.address}/files"
end end
end end
# @return [Pathname] the path to PNGs for this record
def blahtex_pngs_path def blahtex_pngs_path
files_path.join("pngs") if default_web?
"#{RAILS_ROOT}/webs/files/pngs"
else
"#{RAILS_ROOT}/webs/#{self.address}/files/pngs"
end
end end
private private
@ -191,31 +151,31 @@ class Web < ActiveRecord::Base
def wiki_words def wiki_words
pages.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq pages.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
end end
# Returns an array of all the page names on this web # Returns an array of all the page names on this web
def page_names def page_names
pages.map { |p| p.name } pages.map { |p| p.name }
end end
protected protected
before_save :sanitize_markup
after_save :create_files_directory
before_validation :validate_address
validates_uniqueness_of :address
validates_length_of :color, :in => 3..6
def sanitize_markup def sanitize_markup
self.markup = markup.to_s self.markup = markup.to_s
end end
def validate_address def validate_address
if ['create_system', 'create_web', 'delete_web', 'delete_files', 'web_list', ''].include?(address)
self.errors.add(:address, 'is not a valid address')
raise Instiki::ValidationError.new("\"#{address.purify.escapeHTML}\" #{errors.on(:address)}")
end
unless address == CGI.escape(address) unless address == CGI.escape(address)
self.errors.add(:address, 'should contain only valid URI characters') self.errors.add(:address, 'should contain only valid URI characters')
raise Instiki::ValidationError.new("#{self.class.human_attribute_name('address')} #{errors.on(:address)}") raise Instiki::ValidationError.new("#{self.class.human_attribute_name('address')} #{errors.on(:address)}")
end end
end end
# @return [Boolean] whether or not this record is considered the default Web
def default_web? def default_web?
defined?(DEFAULT_WEB) && address == DEFAULT_WEB defined? DEFAULT_WEB and self.address == DEFAULT_WEB
end end
end end

View file

@ -1,7 +1,7 @@
class Wiki class Wiki
cattr_accessor :storage_path, :logger cattr_accessor :storage_path, :logger
self.storage_path = Rails.root.join('storage') self.storage_path = "#{RAILS_ROOT}/storage/"
def authenticate(password) def authenticate(password)
password == (system.password || 'instiki') password == (system.password || 'instiki')
@ -27,28 +27,14 @@ class Wiki
if not (web = Web.find_by_address(old_address)) if not (web = Web.find_by_address(old_address))
raise Instiki::ValidationError.new("Web with address '#{old_address}' does not exist") raise Instiki::ValidationError.new("Web with address '#{old_address}' does not exist")
end end
old_files_path = web.files_path
web.update_attributes(:address => new_address, :name => name, :markup => markup, :color => color, web.update_attributes(:address => new_address, :name => name, :markup => markup, :color => color,
:additional_style => additional_style, :safe_mode => safe_mode, :password => password, :published => published, :additional_style => additional_style, :safe_mode => safe_mode, :password => password, :published => published,
:brackets_only => brackets_only, :count_pages => count_pages, :allow_uploads => allow_uploads, :max_upload_size => max_upload_size) :brackets_only => brackets_only, :count_pages => count_pages, :allow_uploads => allow_uploads, :max_upload_size => max_upload_size)
@webs = nil @webs = nil
raise Instiki::ValidationError.new("There is already a web with address '#{new_address}'") unless web.errors.on(:address).nil? raise Instiki::ValidationError.new("There is already a web with address '#{new_address}'") unless web.errors.on(:address).nil?
web web
move_files(old_files_path, web.files_path)
end end
def move_files(old_path, new_path)
return if new_path == old_path
default_path = Rails.root.join("webs", "files")
FileUtils.rmdir(new_path) if File.exist?(new_path)
if [old_path, new_path].include? default_path
File.rename(old_path, new_path)
FileUtils.rmdir(old_path.parent) unless old_path == default_path
else
File.rename(old_path.parent, new_path.parent)
end
end
def read_page(web_address, page_name) def read_page(web_address, page_name)
ApplicationController.logger.debug "Reading page '#{page_name}' from web '#{web_address}'" ApplicationController.logger.debug "Reading page '#{page_name}' from web '#{web_address}'"

View file

@ -9,12 +9,13 @@ class WikiFile < ActiveRecord::Base
validates_length_of :description, :maximum=>255 validates_length_of :description, :maximum=>255
def self.find_by_file_name(file_name) def self.find_by_file_name(file_name)
first(:conditions => ['file_name = ?', file_name]) find(:first, :conditions => ['file_name = ?', file_name])
end end
SANE_FILE_NAME = /^[a-zA-Z0-9\-_\. ]*$/
def validate def validate
if file_name if file_name
if ! WikiFile.is_valid?(file_name) if file_name !~ SANE_FILE_NAME
errors.add("file_name", "is invalid. Only latin characters, digits, dots, underscores, " + errors.add("file_name", "is invalid. Only latin characters, digits, dots, underscores, " +
"dashes and spaces are accepted") "dashes and spaces are accepted")
elsif file_name == '.' or file_name == '..' elsif file_name == '.' or file_name == '..'
@ -45,7 +46,7 @@ class WikiFile < ActiveRecord::Base
end end
def content_path def content_path
web.files_path.join(file_name) web.files_path + '/' + file_name
end end
def write_content_to_file def write_content_to_file
@ -57,10 +58,7 @@ class WikiFile < ActiveRecord::Base
require 'fileutils' require 'fileutils'
FileUtils.rm_f(content_path) if File.exists?(content_path) FileUtils.rm_f(content_path) if File.exists?(content_path)
end end
SANE_FILE_NAME = /^[a-zA-Z0-9\-_\. ]*$/
def self.is_valid?(name)
name =~ SANE_FILE_NAME
end
end end

View file

@ -12,10 +12,6 @@ class WikiReference < ActiveRecord::Base
belongs_to :page belongs_to :page
validates_inclusion_of :link_type, :in => [LINKED_PAGE, WANTED_PAGE, REDIRECTED_PAGE, INCLUDED_PAGE, CATEGORY, AUTHOR, FILE, WANTED_FILE] validates_inclusion_of :link_type, :in => [LINKED_PAGE, WANTED_PAGE, REDIRECTED_PAGE, INCLUDED_PAGE, CATEGORY, AUTHOR, FILE, WANTED_FILE]
def referenced_name
read_attribute(:referenced_name).as_utf8
end
def self.link_type(web, page_name) def self.link_type(web, page_name)
if web.has_page?(page_name) || self.page_that_redirects_for(web, page_name) if web.has_page?(page_name) || self.page_that_redirects_for(web, page_name)
LINKED_PAGE LINKED_PAGE
@ -46,7 +42,7 @@ class WikiReference < ActiveRecord::Base
query = 'SELECT name FROM pages JOIN wiki_references ' + query = 'SELECT name FROM pages JOIN wiki_references ' +
'ON pages.id = wiki_references.page_id ' + 'ON pages.id = wiki_references.page_id ' +
'WHERE wiki_references.referenced_name = ? ' + 'WHERE wiki_references.referenced_name = ? ' +
"AND wiki_references.link_type in ('#{FILE}','#{WANTED_FILE}') " + "AND wiki_references.link_type in ('#{FILE}') " +
"AND pages.web_id = '#{web.id}'" "AND pages.web_id = '#{web.id}'"
names = connection.select_all(sanitize_sql([query, file_name])).map { |row| row['name'] } names = connection.select_all(sanitize_sql([query, file_name])).map { |row| row['name'] }
end end
@ -78,9 +74,9 @@ class WikiReference < ActiveRecord::Base
"AND wiki_references.link_type = '#{REDIRECTED_PAGE}' " + "AND wiki_references.link_type = '#{REDIRECTED_PAGE}' " +
"AND pages.web_id = '#{web.id}'" "AND pages.web_id = '#{web.id}'"
row = connection.select_one(sanitize_sql([query, page_name])) row = connection.select_one(sanitize_sql([query, page_name]))
row['name'].as_utf8 if row row['name'] if row
end end
def self.pages_in_category(web, category) def self.pages_in_category(web, category)
query = query =
"SELECT name FROM pages JOIN wiki_references " + "SELECT name FROM pages JOIN wiki_references " +
@ -88,7 +84,7 @@ class WikiReference < ActiveRecord::Base
"WHERE wiki_references.referenced_name = ? " + "WHERE wiki_references.referenced_name = ? " +
"AND wiki_references.link_type = '#{CATEGORY}' " + "AND wiki_references.link_type = '#{CATEGORY}' " +
"AND pages.web_id = '#{web.id}'" "AND pages.web_id = '#{web.id}'"
names = connection.select_all(sanitize_sql([query, category])).map { |row| row['name'].as_utf8 } names = connection.select_all(sanitize_sql([query, category])).map { |row| row['name'] }
end end
def self.list_categories(web) def self.list_categories(web)
@ -97,7 +93,7 @@ class WikiReference < ActiveRecord::Base
"ON wiki_references.page_id = pages.id " + "ON wiki_references.page_id = pages.id " +
"WHERE wiki_references.link_type = '#{CATEGORY}' " + "WHERE wiki_references.link_type = '#{CATEGORY}' " +
"AND pages.web_id = '#{web.id}'" "AND pages.web_id = '#{web.id}'"
connection.select_all(query).map { |row| row['referenced_name'].as_utf8 } connection.select_all(query).map { |row| row['referenced_name'] }
end end
def wiki_word? def wiki_word?
@ -132,8 +128,4 @@ class WikiReference < ActiveRecord::Base
link_type == WANTED_FILE link_type == WANTED_FILE
end end
def category?
link_type == CATEGORY
end
end end

View file

@ -18,7 +18,7 @@
onchange="proposeAddress();" /> &#xa0;&#xa0; onchange="proposeAddress();" /> &#xa0;&#xa0;
<label for="address">Address:</label> <input type="text" class="disableAutoComplete" id="address" name="address" value="<%= @web.address %>" <label for="address">Address:</label> <input type="text" class="disableAutoComplete" id="address" name="address" value="<%= @web.address %>"
onchange="cleanAddress();" /> onchange="cleanAddress();" />
<em>(Letters and digits only)</em> <small><em>(Letters and digits only)</em></small>
</div> </div>
<h2 style="margin-bottom: 3px">Specialize</h2> <h2 style="margin-bottom: 3px">Specialize</h2>
@ -46,18 +46,18 @@
}, @web.color) %> }, @web.color) %>
</select> </select>
<p> <p>
<input type="checkbox" class="disableAutoComplete" id="safe_mode" name="safe_mode" <%= raw 'checked="checked"' if @web.safe_mode? %> /> <input type="checkbox" class="disableAutoComplete" id="safe_mode" name="safe_mode" <%= 'checked="checked"' if @web.safe_mode? %> />
<label for="safe_mode">Safe mode <label for="safe_mode">Safe mode
<em>- strip HTML tags and stylesheet options from the content of all pages</em></label> <em>- strip HTML tags and stylesheet options from the content of all pages</em></label>
<br/> <br/>
<input type="checkbox" class="disableAutoComplete" id="brackets_only" name="brackets_only" <%= raw 'checked="checked"' if @web.brackets_only? %> /> <input type="checkbox" class="disableAutoComplete" id="brackets_only" name="brackets_only" <%= 'checked="checked"' if @web.brackets_only? %> />
<label for="brackets_only">Brackets only <label for="brackets_only">Brackets only
<em>- require all wiki words to be as [[wiki word]], WikiWord links won't be created</em></label> <em>- require all wiki words to be as [[wiki word]], WikiWord links won't be created</em></label>
<br/> <br/>
<input type="checkbox" class="disableAutoComplete" id="count_pages" name="count_pages" <%= raw 'checked="checked"' if @web.count_pages? %> /> <input type="checkbox" class="disableAutoComplete" id="count_pages" name="count_pages" <%= 'checked="checked"' if @web.count_pages? %> />
<label for="count_pages">Count pages</label> <label for="count_pages">Count pages</label>
<br/> <br/>
<input type="checkbox" class="disableAutoComplete" name="allow_uploads" <%= raw 'checked="checked"' if @web.allow_uploads? %> /> <input type="checkbox" class="disableAutoComplete" name="allow_uploads" <%= 'checked="checked"' if @web.allow_uploads? %> />
Allow uploads of no more than Allow uploads of no more than
<input type="text" class="disableAutoComplete" name="max_upload_size" value="<%= @web.max_upload_size %>" <input type="text" class="disableAutoComplete" name="max_upload_size" value="<%= @web.max_upload_size %>"
size="20" /> size="20" />
@ -71,8 +71,7 @@
Stylesheet tweaks &gt;&gt;</a> Stylesheet tweaks &gt;&gt;</a>
<em> <em>
- add or change styles used by this web; styles defined here take precedence over - add or change styles used by this web; styles defined here take precedence over
instiki.css.<br/> instiki.css. Hint: View HTML source of a page you want to style to find ID names on individual
Hint: View HTML source of a page you want to style to find ID names on individual
tags.</em> tags.</em>
<br/> <br/>
<textarea id="additionalStyle" class="disableAutoComplete" cols="50" rows="20" <textarea id="additionalStyle" class="disableAutoComplete" cols="50" rows="20"
@ -97,10 +96,10 @@
<div class="help"> <div class="help">
You can turn on a read-only version of this web that's accessible even when the regular web You can turn on a read-only version of this web that's accessible even when the regular web
is password protected. is password protected.
The published version is accessible through URLs like /<%= @web.address %>/published/HomePage. The published version is accessible through URLs like /wiki/published/HomePage.
</div> </div>
<div class="inputBox"> <div class="inputBox">
<input type="checkbox" id="published" name="published" class="disableAutoComplete" <%= raw 'checked="checked"' if @web.published? %> /> <input type="checkbox" id="published" name="published" class="disableAutoComplete" <%= 'checked="checked"' if @web.published? %> />
<label for="published">Publish this web</label> <label for="published">Publish this web</label>
</div> </div>

View file

@ -1,5 +1,5 @@
<%- <%-
@title = "Delete #{@file_name}".html_safe @title = "Delete #{@file_name}"
@hide_navigation = true @hide_navigation = true
-%> -%>

View file

@ -15,7 +15,7 @@
<input type="text" name="author" id="authorName" value="<%= @author %>" <input type="text" name="author" id="authorName" value="<%= @author %>"
onclick="this.value == 'AnonymousCoward' ? this.value = '' : true" /> onclick="this.value == 'AnonymousCoward' ? this.value = '' : true" />
<%- if @page -%> <%- if @page -%>
| <%= link_to 'Cancel', :web => @web.address, :action => 'file'%> <em>(unlocks page)</em> | <%= link_to 'Cancel', :web => @web.address, :action => 'file'%> <small>(unlocks page)</small>
<%- end -%> <%- end -%>
</p> </p>

View file

@ -16,42 +16,19 @@
<%= javascript_include_tag 'page_helper' %> <%= javascript_include_tag 'page_helper' %>
<%= stylesheet_link_tag 'instiki', :media => 'all' unless @inline_style %>
<%= stylesheet_link_tag 'syntax', :media => 'all' unless @inline_style %>
<style type="text/css"> <style type="text/css">
h1#pageName, div.info, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover, [actiontype="toggle"]:hover, #TextileHelp h3 { h1#pageName, div.info, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover, #TextileHelp h3 {
color: #<%= @web ? @web.color : "393" %>; color: #<%= @web ? @web.color : "393" %>;
} }
a:visited.existingWikiWord { <%= File.read(RAILS_ROOT + '/public/stylesheets/instiki.css') if @inline_style %>
color: #<%= darken(@web ? @web.color : "393") %>;
}
<%= Rails.root.join('public', 'stylesheets', 'instiki.css').read if @inline_style %>
<%= Rails.root.join('public', 'stylesheets', 'syntax.css').read if @inline_style %>
</style> </style>
<%= "<style type='text/css'>#{@style_additions}</style>".html_safe if @style_additions %> <%= stylesheet_link_tag 'instiki' unless @inline_style %>
<%= "<style type='text/css'>#{@style_additions}</style>" if @style_additions %>
<style type="text/css"><!--/*--><![CDATA[/*><!--*/ <style type="text/css"><!--/*--><![CDATA[/*><!--*/
<%= @web && @web.additional_style ? @web.additional_style.html_safe : '' %> <%= @web ? @web.additional_style : '' %>
/*]]>*/--></style> /*]]>*/--></style>
<%= javascript_include_tag :defaults %> <%= javascript_include_tag :defaults %>
<%= csrf_meta_tag %>
<%- if @web -%> <%- if @web -%>
<%- if @web.markup == :markdownMML -%>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
MathML: { useMathMLspacing: true },
"HTML-CSS": { scale: 90 }
});
if (window._onload_fired_) MathJax.Hub.Startup.onload();
</script>
<script type="text/javascript">
if (!(Prototype.Browser.Gecko || navigator.userAgent.match(/MathPlayer/))) {
var s = document.createElement('script');
s.src = "<%= compute_public_path('MathJax.js', 'MathJax').split('?')[0] %>?config=MML_HTMLorMML";
document.querySelector('head').appendChild(s);
window.addEventListener("load", function(){window._onload_fired_ = true} , false);
};
</script>
<%- end -%>
<%= auto_discovery_link_tag(:atom, {:controller => 'wiki', :web => @web.address, :action => 'atom_with_headlines'}, <%= auto_discovery_link_tag(:atom, {:controller => 'wiki', :web => @web.address, :action => 'atom_with_headlines'},
:title => 'Atom with headlines') %> :title => 'Atom with headlines') %>
<%= auto_discovery_link_tag(:atom, {:controller => 'wiki', :web => @web.address, :action => 'atom_with_content'}, <%= auto_discovery_link_tag(:atom, {:controller => 'wiki', :web => @web.address, :action => 'atom_with_content'},
@ -64,7 +41,7 @@
<div id="Container"> <div id="Container">
<div id="Content"> <div id="Content">
<h1 id="pageName"> <h1 id="pageName">
<%= render(:file => 'svg_logo') if xhtml_enabled? %> <%= render(:file => 'svg_logo') if @controller.xhtml_enabled? %>
<%- if @page and (@page.name == 'HomePage') and %w( show published print ).include?(@action_name) -%> <%- if @page and (@page.name == 'HomePage') and %w( show published print ).include?(@action_name) -%>
<%= h(@web.name) + (@show_diff ? ' (changes)' : '') %> <%= h(@web.name) + (@show_diff ? ' (changes)' : '') %>
<%- elsif @web -%> <%- elsif @web -%>

View file

@ -12,10 +12,10 @@
h1#pageName, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover, #TextileHelp h3 { h1#pageName, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover, #TextileHelp h3 {
color: #<%= @web ? @web.color : "393" %>; color: #<%= @web ? @web.color : "393" %>;
} }
<%= Rails.root.join('public', 'stylesheets', 'instiki.css').read if @inline_style %> <%= File.read(RAILS_ROOT + '/public/stylesheets/instiki.css') if @inline_style %>
</style> </style>
<%= stylesheet_link_tag 'instiki', :media => 'all' unless @inline_style %> <%= stylesheet_link_tag 'instiki' unless @inline_style %>
<style type="text/css"><!--/*--><![CDATA[/*><!--*/ <style type="text/css"><!--/*--><![CDATA[/*><!--*/
<%= @style_additions %> <%= @style_additions %>
@ -31,11 +31,7 @@
</h1> </h1>
<div id="Error-Content"> <div id="Error-Content">
<%= if :raw <%= h @content_for_layout %>
@content_for_layout
else
h @content_for_layout
end %>
</div> <!-- Error-Content --> </div> <!-- Error-Content -->

View file

@ -1,265 +1 @@
\documentclass[12pt,titlepage]{article}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{amsthm}
\usepackage{mathtools}
<%- if @tex_content =~ /\\mathbb\{[^}]*[a-z0-9]+[^}]*\}/ -%>
\usepackage{mathbbol}
<% end -%>
\usepackage{graphicx}
\usepackage{color}
\usepackage{ucs}
\usepackage[utf8x]{inputenc}
\usepackage{xparse}
\usepackage{hyperref}
%----Macros----------
%
% Unresolved issues:
%
% \righttoleftarrow
% \lefttorightarrow
%
% \color{} with HTML colorspec
% \bgcolor
% \array with options (without options, it's equivalent to the matrix environment)
% Of the standard HTML named colors, white, black, red, green, blue and yellow
% are predefined in the color package. Here are the rest.
\definecolor{aqua}{rgb}{0, 1.0, 1.0}
\definecolor{fuschia}{rgb}{1.0, 0, 1.0}
\definecolor{gray}{rgb}{0.502, 0.502, 0.502}
\definecolor{lime}{rgb}{0, 1.0, 0}
\definecolor{maroon}{rgb}{0.502, 0, 0}
\definecolor{navy}{rgb}{0, 0, 0.502}
\definecolor{olive}{rgb}{0.502, 0.502, 0}
\definecolor{purple}{rgb}{0.502, 0, 0.502}
\definecolor{silver}{rgb}{0.753, 0.753, 0.753}
\definecolor{teal}{rgb}{0, 0.502, 0.502}
% Because of conflicts, \space and \mathop are converted to
% \itexspace and \operatorname during preprocessing.
% itex: \space{ht}{dp}{wd}
%
% Height and baseline depth measurements are in units of tenths of an ex while
% the width is measured in tenths of an em.
\makeatletter
\newdimen\itex@wd%
\newdimen\itex@dp%
\newdimen\itex@thd%
\def\itexspace#1#2#3{\itex@wd=#3em%
\itex@wd=0.1\itex@wd%
\itex@dp=#2ex%
\itex@dp=0.1\itex@dp%
\itex@thd=#1ex%
\itex@thd=0.1\itex@thd%
\advance\itex@thd\the\itex@dp%
\makebox[\the\itex@wd]{\rule[-\the\itex@dp]{0cm}{\the\itex@thd}}}
\makeatother
% \tensor and \multiscript
\makeatletter
\newif\if@sup
\newtoks\@sups
\def\append@sup#1{\edef\act{\noexpand\@sups={\the\@sups #1}}\act}%
\def\reset@sup{\@supfalse\@sups={}}%
\def\mk@scripts#1#2{\if #2/ \if@sup ^{\the\@sups}\fi \else%
\ifx #1_ \if@sup ^{\the\@sups}\reset@sup \fi {}_{#2}%
\else \append@sup#2 \@suptrue \fi%
\expandafter\mk@scripts\fi}
\def\tensor#1#2{\reset@sup#1\mk@scripts#2_/}
\def\multiscripts#1#2#3{\reset@sup{}\mk@scripts#1_/#2%
\reset@sup\mk@scripts#3_/}
\makeatother
% \slash
\makeatletter
\newbox\slashbox \setbox\slashbox=\hbox{$/$}
\def\itex@pslash#1{\setbox\@tempboxa=\hbox{$#1$}
\@tempdima=0.5\wd\slashbox \advance\@tempdima 0.5\wd\@tempboxa
\copy\slashbox \kern-\@tempdima \box\@tempboxa}
\def\slash{\protect\itex@pslash}
\makeatother
% math-mode versions of \rlap, etc
% from Alexander Perlis, "A complement to \smash, \llap, and lap"
% http://math.arizona.edu/~aprl/publications/mathclap/
\def\clap#1{\hbox to 0pt{\hss#1\hss}}
\def\mathllap{\mathpalette\mathllapinternal}
\def\mathrlap{\mathpalette\mathrlapinternal}
\def\mathclap{\mathpalette\mathclapinternal}
\def\mathllapinternal#1#2{\llap{$\mathsurround=0pt#1{#2}$}}
\def\mathrlapinternal#1#2{\rlap{$\mathsurround=0pt#1{#2}$}}
\def\mathclapinternal#1#2{\clap{$\mathsurround=0pt#1{#2}$}}
% Renames \sqrt as \oldsqrt and redefine root to result in \sqrt[#1]{#2}
\let\oldroot\root
\def\root#1#2{\oldroot #1 \of{#2}}
\renewcommand{\sqrt}[2][]{\oldroot #1 \of{#2}}
% Manually declare the txfonts symbolsC font
\DeclareSymbolFont{symbolsC}{U}{txsyc}{m}{n}
\SetSymbolFont{symbolsC}{bold}{U}{txsyc}{bx}{n}
\DeclareFontSubstitution{U}{txsyc}{m}{n}
% Manually declare the stmaryrd font
\DeclareSymbolFont{stmry}{U}{stmry}{m}{n}
\SetSymbolFont{stmry}{bold}{U}{stmry}{b}{n}
% Declare specific arrows from txfonts without loading the full package
\makeatletter
\def\re@DeclareMathSymbol#1#2#3#4{%
\let#1=\undefined
\DeclareMathSymbol{#1}{#2}{#3}{#4}}
\re@DeclareMathSymbol{\neArrow}{\mathrel}{symbolsC}{116}
\re@DeclareMathSymbol{\neArr}{\mathrel}{symbolsC}{116}
\re@DeclareMathSymbol{\seArrow}{\mathrel}{symbolsC}{117}
\re@DeclareMathSymbol{\seArr}{\mathrel}{symbolsC}{117}
\re@DeclareMathSymbol{\nwArrow}{\mathrel}{symbolsC}{118}
\re@DeclareMathSymbol{\nwArr}{\mathrel}{symbolsC}{118}
\re@DeclareMathSymbol{\swArrow}{\mathrel}{symbolsC}{119}
\re@DeclareMathSymbol{\swArr}{\mathrel}{symbolsC}{119}
\re@DeclareMathSymbol{\nequiv}{\mathrel}{symbolsC}{46}
\re@DeclareMathSymbol{\Perp}{\mathrel}{symbolsC}{121}
\re@DeclareMathSymbol{\Vbar}{\mathrel}{symbolsC}{121}
\re@DeclareMathSymbol{\sslash}{\mathrel}{stmry}{12}
\re@DeclareMathSymbol{\invamp}{\mathrel}{symbolsC}{77}
\re@DeclareMathSymbol{\parr}{\mathrel}{symbolsC}{77}
\makeatother
% Widecheck
\makeatletter
\DeclareRobustCommand\widecheck[1]{{\mathpalette\@widecheck{#1}}}
\def\@widecheck#1#2{%
\setbox\z@\hbox{\m@th$#1#2$}%
\setbox\tw@\hbox{\m@th$#1%
\widehat{%
\vrule\@width\z@\@height\ht\z@
\vrule\@height\z@\@width\wd\z@}$}%
\dp\tw@-\ht\z@
\@tempdima\ht\z@ \advance\@tempdima2\ht\tw@ \divide\@tempdima\thr@@
\setbox\tw@\hbox{%
\raise\@tempdima\hbox{\scalebox{1}[-1]{\lower\@tempdima\box
\tw@}}}%
{\ooalign{\box\tw@ \cr \box\z@}}}
\makeatother
% \mathraisebox{voffset}[height][depth]{something}
\makeatletter
\NewDocumentCommand\mathraisebox{moom}{%
\IfNoValueTF{#2}{\def\@temp##1##2{\raisebox{#1}{$\m@th##1##2$}}}{%
\IfNoValueTF{#3}{\def\@temp##1##2{\raisebox{#1}[#2]{$\m@th##1##2$}}%
}{\def\@temp##1##2{\raisebox{#1}[#2][#3]{$\m@th##1##2$}}}}%
\mathpalette\@temp{#4}}
\makeatletter
% udots (taken from yhmath)
\makeatletter
\def\udots{\mathinner{\mkern2mu\raise\p@\hbox{.}
\mkern2mu\raise4\p@\hbox{.}\mkern1mu
\raise7\p@\vbox{\kern7\p@\hbox{.}}\mkern1mu}}
\makeatother
%% Fix array
\newcommand{\itexarray}[1]{\begin{matrix}#1\end{matrix}}
%% \itexnum is a noop
\newcommand{\itexnum}[1]{#1}
%% Renaming existing commands
\newcommand{\underoverset}[3]{\underset{#1}{\overset{#2}{#3}}}
\newcommand{\widevec}{\overrightarrow}
\newcommand{\darr}{\downarrow}
\newcommand{\nearr}{\nearrow}
\newcommand{\nwarr}{\nwarrow}
\newcommand{\searr}{\searrow}
\newcommand{\swarr}{\swarrow}
\newcommand{\curvearrowbotright}{\curvearrowright}
\newcommand{\uparr}{\uparrow}
\newcommand{\downuparrow}{\updownarrow}
\newcommand{\duparr}{\updownarrow}
\newcommand{\updarr}{\updownarrow}
\newcommand{\gt}{>}
\newcommand{\lt}{<}
\newcommand{\map}{\mapsto}
\newcommand{\embedsin}{\hookrightarrow}
\newcommand{\Alpha}{A}
\newcommand{\Beta}{B}
\newcommand{\Zeta}{Z}
\newcommand{\Eta}{H}
\newcommand{\Iota}{I}
\newcommand{\Kappa}{K}
\newcommand{\Mu}{M}
\newcommand{\Nu}{N}
\newcommand{\Rho}{P}
\newcommand{\Tau}{T}
\newcommand{\Upsi}{\Upsilon}
\newcommand{\omicron}{o}
\newcommand{\lang}{\langle}
\newcommand{\rang}{\rangle}
\newcommand{\Union}{\bigcup}
\newcommand{\Intersection}{\bigcap}
\newcommand{\Oplus}{\bigoplus}
\newcommand{\Otimes}{\bigotimes}
\newcommand{\Wedge}{\bigwedge}
\newcommand{\Vee}{\bigvee}
\newcommand{\coproduct}{\coprod}
\newcommand{\product}{\prod}
\newcommand{\closure}{\overline}
\newcommand{\integral}{\int}
\newcommand{\doubleintegral}{\iint}
\newcommand{\tripleintegral}{\iiint}
\newcommand{\quadrupleintegral}{\iiiint}
\newcommand{\conint}{\oint}
\newcommand{\contourintegral}{\oint}
\newcommand{\infinity}{\infty}
\newcommand{\bottom}{\bot}
\newcommand{\minusb}{\boxminus}
\newcommand{\plusb}{\boxplus}
\newcommand{\timesb}{\boxtimes}
\newcommand{\intersection}{\cap}
\newcommand{\union}{\cup}
\newcommand{\Del}{\nabla}
\newcommand{\odash}{\circleddash}
\newcommand{\negspace}{\!}
\newcommand{\widebar}{\overline}
\newcommand{\textsize}{\normalsize}
\renewcommand{\scriptsize}{\scriptstyle}
\newcommand{\scriptscriptsize}{\scriptscriptstyle}
\newcommand{\mathfr}{\mathfrak}
\newcommand{\statusline}[2]{#2}
\newcommand{\tooltip}[2]{#2}
\newcommand{\toggle}[2]{#2}
% Theorem Environments
\theoremstyle{plain}
\newtheorem{theorem}{Theorem}
\newtheorem{lemma}{Lemma}
\newtheorem{prop}{Proposition}
\newtheorem{cor}{Corollary}
\newtheorem*{utheorem}{Theorem}
\newtheorem*{ulemma}{Lemma}
\newtheorem*{uprop}{Proposition}
\newtheorem*{ucor}{Corollary}
\theoremstyle{definition}
\newtheorem{defn}{Definition}
\newtheorem{example}{Example}
\newtheorem*{udefn}{Definition}
\newtheorem*{uexample}{Example}
\theoremstyle{remark}
\newtheorem{remark}{Remark}
\newtheorem{note}{Note}
\newtheorem*{uremark}{Remark}
\newtheorem*{unote}{Note}
%-------------------------------------------------------------------
\begin{document}
%-------------------------------------------------------------------
<%= @content_for_layout %> <%= @content_for_layout %>
\end{document}

View file

@ -1,3 +1,15 @@
<h3>Markdown+itex2MML formatting tips (<a target="_new" href="http://daringfireball.net/projects/markdown/syntax">basics</a>, <a target="_new" href="http://michelf.com/projects/php-markdown/extra/">extended syntax</a>, <a target="_new" href="http://maruku.rubyforge.org/proposal.html">metadata</a>, <a target="_new" href="http://golem.ph.utexas.edu/instiki/show/Theorems">theorems</a>)</h3> <h3>Markdown+itex2MML formatting tips (<a target="_new" href="http://daringfireball.net/projects/markdown/syntax">basics</a>, <a target="_new" href="http://maruku.rubyforge.org/maruku.html#extra">extended syntax</a>, <a target="_new" href="http://maruku.rubyforge.org/proposal.html">metadata</a>, <a target="_new" href="http://golem.ph.utexas.edu/instiki/show/Theorems">theorems</a>)</h3>
<p>For a complete list of LaTeX commands supported here, see the <a href="http://golem.ph.utexas.edu/~distler/blog/itex2MMLcommands.html">itex2MML Commands Summary</a>.</p> <p>For a complete list of LaTeX commands supported here, see the <a href="http://golem.ph.utexas.edu/~distler/blog/itex2MMLcommands.html">itex2MML Commands Summary</a>.</p>
<%= render(:file => "markdown_table") -%> <table cellspacing="0" cellpadding="0">
<tr><td>_your text_</td><td class="arrow">&#x2192;</td><td><em>your text</em></td></tr>
<tr><td>**your text**</td><td class="arrow">&#x2192;</td><td><strong>your text</strong></td></tr>
<tr><td>`my code`</td><td class="arrow">&#x2192;</td><td><code>my code</code></td></tr>
<tr><td>* Bulleted list<br />* Second item</td><td class="arrow">&#x2192;</td><td>&#8226; Bulleted list<br />&#8226; Second item</td></tr>
<tr><td>1. Numbered list<br />1. Second item</td><td class="arrow">&#x2192;</td><td>1. Numbered list<br />2. Second item</td></tr>
<tr><td>Definition list<br />: is useful</td><td class="arrow">&#x2192;</td><td><dl style="display:inline"><dt>Definition list</dt><dd>is useful</dd></dl></td></tr>
<tr><td>[link name](URL)</td><td class="arrow">&#x2192;</td><td><a href="URL">link name</a></td></tr>
<tr><td>![Alt text](URL)</td><td class="arrow">&#x2192;</td><td>Image</td></tr>
<tr><td>## Header ##<br />### Subheader ###<br />#### Etc. ####</td><td class="arrow">&#x2192;</td><td><b><span style="font-size:1.2em">Header</span><br /><span style="font-size:1.1em">Subheader</span><br /><span style="font-size:1em">Etc.</span></b></td></tr>
<tr><td>***</td><td class="arrow">&#x2192;</td><td>Horizontal ruler</td></tr>
<tr><td>&lt;http://url><br />&lt;email@add.com></td><td class="arrow">&#x2192;</td><td>Auto-linked</td></tr>
</table>

View file

@ -1,3 +1,16 @@
<h3>Markdown+blahtex/PNG formatting tips (<a target="_new" href="http://daringfireball.net/projects/markdown/syntax">basics</a>, <a target="_new" href="http://michelf.com/projects/php-markdown/extra/">extended syntax</a>, <a target="_new" href="http://maruku.rubyforge.org/proposal.html">metadata</a>)</h3> <h3>Markdown+blahtex/PNG formatting tips (<a target="_new" href="http://daringfireball.net/projects/markdown/syntax">basics</a>, <a target="_new" href="http://maruku.rubyforge.org/#extra">extended syntax</a>, <a target="_new" href="http://maruku.rubyforge.org/proposal.html">metadata</a>)</h3>
<p>For a list of the LaTeX commands supported here, see the <a href="http://www.blahtex.org/manual.html">BlahTeX manual</a>.</p> <table cellspacing="0" cellpadding="0">
<%= render(:file => "markdown_table") -%> <tr><td>_your text_</td><td class="arrow">&#x2192;</td><td><em>your text</em></td></tr>
<tr><td>**your text**</td><td class="arrow">&#x2192;</td><td><strong>your text</strong></td></tr>
<tr><td>`my code`</td><td class="arrow">&#x2192;</td><td><code>my code</code></td></tr>
<tr><td>$LaTeX code$</td><td class="arrow">&#x2192;</td><td>Insert an inline <br /> math expression</td></tr>
<tr><td>\[LaTeX code\]</td><td class="arrow">&#x2192;</td><td>Insert a math <br /> expression on <br /> its own line</td></tr>
<tr><td>* Bulleted list<br />* Second item</td><td class="arrow">&#x2192;</td><td>&#8226; Bulleted list<br />&#8226; Second item</td></tr>
<tr><td>1. Numbered list<br />1. Second item</td><td class="arrow">&#x2192;</td><td>1. Numbered list<br />2. Second item</td></tr>
<tr><td>Definition list<br />: is useful</td><td class="arrow">&#x2192;</td><td><dl style="display:inline"><dt>Definition list</dt><dd>is useful</dd></dl></td></tr>
<tr><td>[link name](URL)</td><td class="arrow">&#x2192;</td><td><a href="URL">link name</a></td></tr>
<tr><td>![Alt text](URL)</td><td class="arrow">&#x2192;</td><td>Image</td></tr>
<tr><td>## Header ##<br />### Subheader ###<br />#### Etc. ####</td><td class="arrow">&#x2192;</td><td><b><span style="font-size:1.2em">Header</span><br /><span style="font-size:1.1em">Subheader</span><br /><span style="font-size:1em">Etc.</span></b></td></tr>
<tr><td>***</td><td class="arrow">&#x2192;</td><td>Horizontal ruler</td></tr>
<tr><td>&lt;http://url><br />&lt;email@add.com></td><td class="arrow">&#x2192;</td><td>Auto-linked</td></tr>
</table>

View file

@ -1,2 +1,14 @@
<h3>Markdown formatting tips (<a target="_new" href="http://daringfireball.net/projects/markdown/syntax">basics</a>, <a target="_new" href="http://michelf.com/projects/php-markdown/extra/">extended syntax</a>, <a target="_new" href="http://maruku.rubyforge.org/proposal.html">metadata</a>)</h3> <h3>Markdown formatting tips (<a target="_new" href="http://daringfireball.net/projects/markdown/syntax">basics</a>, <a target="_new" href="http://maruku.rubyforge.org/#extra">extended syntax</a>, <a target="_new" href="http://maruku.rubyforge.org/proposal.html">metadata</a>)</h3>
<%= render(:file => "markdown_table") -%> <table cellspacing="0" cellpadding="0">
<tr><td>_your text_</td><td class="arrow">&#x2192;</td><td><em>your text</em></td></tr>
<tr><td>**your text**</td><td class="arrow">&#x2192;</td><td><strong>your text</strong></td></tr>
<tr><td>`my code`</td><td class="arrow">&#x2192;</td><td><code>my code</code></td></tr>
<tr><td>* Bulleted list<br />* Second item</td><td class="arrow">&#x2192;</td><td>&#8226; Bulleted list<br />&#8226; Second item</td></tr>
<tr><td>1. Numbered list<br />1. Second item</td><td class="arrow">&#x2192;</td><td>1. Numbered list<br />2. Second item</td></tr>
<tr><td>Definition list<br />: is useful</td><td class="arrow">&#x2192;</td><td><dl style="display:inline"><dt>Definition list</dt><dd>is useful</dd></dl></td></tr>
<tr><td>[link name](URL)</td><td class="arrow">&#x2192;</td><td><a href="URL">link name</a></td></tr>
<tr><td>![Alt text](URL)</td><td class="arrow">&#x2192;</td><td>Image</td></tr>
<tr><td>## Header ##<br />### Subheader ###<br />#### Etc. ####</td><td class="arrow">&#x2192;</td><td><b><span style="font-size:1.2em">Header</span><br /><span style="font-size:1.1em">Subheader</span><br /><span style="font-size:1em">Etc.</span></b></td></tr>
<tr><td>***</td><td class="arrow">&#x2192;</td><td>Horizontal ruler</td></tr>
<tr><td>&lt;http://url><br />&lt;email@add.com></td><td class="arrow">&#x2192;</td><td>Auto-linked</td></tr>
</table>

View file

@ -1,17 +0,0 @@
<table cellspacing="0" cellpadding="0">
<tr><td>_your text_</td><td class="arrow">&#x2192;</td><td><em>your text</em></td></tr>
<tr><td>**your text**</td><td class="arrow">&#x2192;</td><td><strong>your text</strong></td></tr>
<tr><td>`my code`</td><td class="arrow">&#x2192;</td><td><code>my code</code></td></tr>
<tr><td>* Bulleted list<br />* Second item</td><td class="arrow">&#x2192;</td><td>&#8226; Bulleted list<br />&#8226; Second item</td></tr>
<tr><td>1. Numbered list<br />1. Second item</td><td class="arrow">&#x2192;</td><td>1. Numbered list<br />2. Second item</td></tr>
<tr><td>Definition list<br />: is useful</td><td class="arrow">&#x2192;</td><td><dl style="display:inline"><dt>Definition list</dt><dd>is useful</dd></dl></td></tr>
<tr><td>[link name](URL)</td><td class="arrow">&#x2192;</td><td><a href="URL">link name</a></td></tr>
<tr><td>![Alt text](URL)</td><td class="arrow">&#x2192;</td><td>Image</td></tr>
<tr><td>## Header ##<br />### Subheader ###<br />#### Subsubhead####<br/>##### Etc. #####</td><td class="arrow">&#x2192;</td>
<td><b><span style="font-size:1.73em">Header</span>
<br/><span style="font-size:1.44em">Subheader</span><br/><span style="font-size:1.2em">Subsubhead</span>
<br/><span style="font-size:1em">Etc.</span></b></td></tr>
<tr><td>***</td><td class="arrow">&#x2192;</td><td>Horizontal rule</td></tr>
<tr><td>Some text[^fine] here.<br/>[^fine]: The fine print.</td><td class="arrow">&#x2192;</td><td>A footnote</td></tr>
<tr><td>*[LA]: Los Angeles<br/>*[ppm]: parts per million</td><td class="arrow">&#x2192;</td><td>Abbreviations</td></tr>
</table>

View file

@ -9,8 +9,7 @@ end
%> %>
<div class="navigation"> <div class="navigation">
<span class="skipNav"><a href='#navEnd'>Skip the Navigation Links</a> | </span> <% if params['action'] != 'published' then %>
<%- if params['action'] != 'published' then -%>
<%= list_item 'Home Page', {:action => 'show', :id => 'HomePage'}, 'Home, Sweet Home', 'H' %> | <%= list_item 'Home Page', {:action => 'show', :id => 'HomePage'}, 'Home, Sweet Home', 'H' %> |
<%= list_item 'All Pages', {:action => 'list'}, 'Alphabetically sorted list of pages', 'A' %> | <%= list_item 'All Pages', {:action => 'list'}, 'Alphabetically sorted list of pages', 'A' %> |
<%= list_item 'Recently Revised', {:action =>'recently_revised'}, 'Pages sorted by when they were last changed', 'U' %> | <%= list_item 'Recently Revised', {:action =>'recently_revised'}, 'Pages sorted by when they were last changed', 'U' %> |
@ -23,9 +22,8 @@ end
onfocus="this.value == 'Search' ? this.value = '' : true" onfocus="this.value == 'Search' ? this.value = '' : true"
onblur="this.value == '' ? this.value = 'Search' : true" /></fieldset> onblur="this.value == '' ? this.value = 'Search' : true" /></fieldset>
<% end %> <% end %>
<%- else -%> <% else %>
<%= list_item 'Home Page', {:action => 'published', :id => 'HomePage'}, 'Home, Sweet Home', 'H' %> | <%= list_item 'Home Page', {:action => 'published', :id => 'HomePage'}, 'Home, Sweet Home', 'H' %> |
<%= list_item 'Feeds', {:action => 'feeds'}, 'Subscribe to changes by Atom' %> <%= list_item 'Feeds', {:action => 'feeds'}, 'Subscribe to changes by Atom' %>
<% end-%> <% end%>
<span id='navEnd'></span>
</div> </div>

View file

@ -1,4 +1,4 @@
<span id="svg_logo"><svg version="1.1" width="100%" height="100%" viewBox='0 -1 180 198' xmlns='http://www.w3.org/2000/svg'> <span id="svg_logo"><svg version="1.1" width="100%" height="100%" viewBox='0 0 180 197' xmlns='http://www.w3.org/2000/svg'>
<path id="svg_logo_path" fill="#<%= @web ? @web.color : "393" %>" stroke-width='0.5' stroke='#000' d=' <path id="svg_logo_path" fill="#<%= @web ? @web.color : "393" %>" stroke-width='0.5' stroke='#000' d='
M170,60c4,11-1,20-12,25c-9,4-25,3-20,15c5,5,15,0,24,1c11,1,21,11,14,21c-10,15-35,6-48-1c-5-3-27-23-32-10c-1,13,15,10,22,16 M170,60c4,11-1,20-12,25c-9,4-25,3-20,15c5,5,15,0,24,1c11,1,21,11,14,21c-10,15-35,6-48-1c-5-3-27-23-32-10c-1,13,15,10,22,16
c11,4,24,14,34,20c12,10,7,25-9,23c-11-1-22-9-30-16c-5-5-13-18-21-9c-2,6,2,11,5,14c9,9,22,14,22,31c-2,8-12,8-18,4c-4-3-9-8-11-13 c11,4,24,14,34,20c12,10,7,25-9,23c-11-1-22-9-30-16c-5-5-13-18-21-9c-2,6,2,11,5,14c9,9,22,14,22,31c-2,8-12,8-18,4c-4-3-9-8-11-13

View file

@ -1,19 +1,20 @@
<h3>Textile formatting tips (<a href="http://redcloth.org/hobix.com/textile/quick.html" onclick="quickRedReference(); return false;">advanced</a>)</h3> <h3>Textile formatting tips (<a href="http://hobix.com/textile/quick.html" onclick="quickRedReference(); return false;">advanced</a>)</h3>
<table cellspacing="0" cellpadding="0"> <table cellspacing="0" cellpadding="0">
<tr><td>_your text_</td><td class="arrow">&rarr;</td><td><em>your text</em></td></tr> <tr><td>_your text_</td><td class="arrow">&rarr;</td><td><em>your text</em></td></tr>
<tr><td>*your text*</td><td class="arrow">&rarr;</td><td><strong>your text</strong></td></tr> <tr><td>*your text*</td><td class="arrow">&rarr;</td><td><strong>your text</strong></td></tr>
<tr><td style="padding-right:0.75em">%{color:red}hello%</td><td class="arrow">&rarr;</td><td><span style="color: red;">hello</span></td></tr> <tr><td>%{color:red}hello%</td><td class="arrow">&rarr;</td><td><span style="color: red;">hello</span></td></tr>
<tr><td>* Bulleted list<br />* Second item</td><td class="arrow">&rarr;</td><td>&#8226; Bulleted list<br />&#8226; Second item</td></tr> <tr><td>* Bulleted list<br />* Second item</td><td class="arrow">&rarr;</td><td>&#8226; Bulleted list<br />&#8226; Second item</td></tr>
<tr><td># Numbered list<br /># Second item</td><td class="arrow">&rarr;</td><td>1. Numbered list<br />2. Second item</td></tr> <tr><td># Numbered list<br /># Second item</td><td class="arrow">&rarr;</td><td>1. Numbered list<br />2. Second item</td></tr>
<tr><td>"linkname":URL</td><td class="arrow">&rarr;</td><td><a href="URL">linkname</a></td></tr> <tr><td>"linkname":URL</td><td class="arrow">&rarr;</td><td><a href="URL">linkname</a></td></tr>
<tr><td>|a|table|row|<br />|b|table|row|</td><td class="arrow">&rarr;</td><td>Table</td></tr> <tr><td>|a|table|row|<br />|b|table|row|</td><td class="arrow">&rarr;</td><td>Table</td></tr>
<tr><td>http://url<br />email@address.com</td><td class="arrow">&rarr;</td><td>Auto-linked</td></tr>
<tr><td>!imageURL!</td><td class="arrow">&rarr;</td><td>Image</td></tr> <tr><td>!imageURL!</td><td class="arrow">&rarr;</td><td>Image</td></tr>
</table> </table>
<script language="JavaScript"> <script language="JavaScript">
function quickRedReference() { function quickRedReference() {
window.open( window.open(
"http://redcloth.org/hobix.com/textile/quick.html", "http://hobix.com/textile/quick.html",
"redRef", "redRef",
"height=600,width=550,channelmode=0,dependent=0," + "height=600,width=550,channelmode=0,dependent=0," +
"directories=0,fullscreen=0,location=0,menubar=0," + "directories=0,fullscreen=0,location=0,menubar=0," +

View file

@ -1,13 +1,13 @@
<%- unless @page.linked_from.empty? -%> <%- unless @page.linked_from.empty? -%>
<span class="linked"> <span class="linked">
| Linked from: | Linked from:
<%= @page.linked_from.collect { |referring_page| link_to_existing_page referring_page }.join(", ").html_safe %> <%= @page.linked_from.collect { |referring_page| link_to_existing_page referring_page }.join(", ") %>
</span> </span>
<%- end -%> <%- end -%>
<%- unless @page.included_from.empty? -%> <%- unless @page.included_from.empty? -%>
<span class="linked"> <span class="linked">
| Included from: | Included from:
<%= @page.included_from.collect { |referring_page| link_to_existing_page referring_page }.join(", ").html_safe %> <%= @page.included_from.collect { |referring_page| link_to_existing_page referring_page }.join(", ") %>
</span> </span>
<%- end -%> <%- end -%>

View file

@ -5,7 +5,7 @@
<li> <li>
<%= link_to_page author.purify %> <%= link_to_page author.purify %>
co- or authored: co- or authored:
<%= raw @page_names_by_author[author].collect { |page_name| link_to_page(page_name) }.sort.join ', ' %> <%= @page_names_by_author[author].collect { |page_name| link_to_page(page_name) }.sort.join ', ' %>
</li> </li>
<%- end -%> <%- end -%>
</ul> </ul>

View file

@ -1,12 +1,12 @@
<%- <%-
@title = "Editing #{@page.name.escapeHTML}".html_safe @title = "Editing #{@page.name.escapeHTML}"
@content_width = 720 @content_width = 720
@hide_navigation = true @hide_navigation = true
-%> -%>
<div id="MarkupHelp"> <div id="MarkupHelp">
<%= render(:file => "#{@web.markup}_help") -%> <%= render(:file => "#{@web.markup}_help") %>
<%= render(:file => 'wiki_words_help') unless @web.brackets_only? -%> <%= render(:file => 'wiki_words_help') %>
</div> </div>
<% form_tag({ :action => 'save', :web => @web.address, :id => @page.name }, <% form_tag({ :action => 'save', :web => @web.address, :id => @page.name },
@ -14,7 +14,7 @@
'accept-charset' => 'utf-8' }) do %> 'accept-charset' => 'utf-8' }) do %>
<div> <div>
<textarea name="content" id="content" rows="24" cols="60"><%= h(flash[:content] || <textarea name="content" id="content" rows="24" cols="60"><%= h(flash[:content] ||
(params['content'] ? params['content'] : @page.content).purify) %></textarea> ((params['content'] && params['content'].is_utf8?) ? params['content'] : @page.content).purify) %></textarea>
<% if @page_name != 'HomePage' -%> <% if @page_name != 'HomePage' -%>
<p> <p>
<%= check_box_tag :alter_title, value = "1", checked=false, <%= check_box_tag :alter_title, value = "1", checked=false,
@ -46,19 +46,18 @@ function toggleVisibility() {
var span = document.getElementById('title_change'); var span = document.getElementById('title_change');
if (span.style.display =='inline') { if (span.style.display =='inline') {
span.style.display ='none'; span.style.display ='none';
document.getElementById('new_name').value = "<%= escape_javascript(@page.name) %>"; document.getElementById('new_name').value = "<%= @page.name %>";
var content = document.getElementById('content').value var content = document.getElementById('content').value
document.getElementById('content').value = content.replace(/\[\[!redirects <%= Regexp.escape(@page.name).gsub('/', '\\/') %>\]\]\n/, '') document.getElementById('content').value = content.replace(/\[\[!redirects <%= Regexp.escape(@page.name) %>\]\]\n/, '')
} }
else else
span.style.display ='inline' span.style.display ='inline'
} }
function addRedirect(){ function addRedirect(){
var e = document.getElementById('new_name').value; if (document.getElementById('new_name').value != "<%= @page.name %>" ) {
if ( e != "<%= escape_javascript(@page.name) %>" && e != '') {
var content = document.getElementById('content'); var content = document.getElementById('content');
content.value = '[[!redirects <%= escape_javascript(@page.name) %>]]\n' + content.value content.value = '[[!redirects <%= @page.name %>]]\n' + content.value
} }
} }
@ -69,11 +68,5 @@ function cleanAuthorName() {
} }
document.forms["editForm"].elements["content"].focus(); document.forms["editForm"].elements["content"].focus();
<%- if [:markdownMML, :markdownPNG, :markdown].include?(@web.markup) -%>
setupSVGedit('<%= compute_public_path("editor/svg-editor.html", "svg-edit").split(/\?/)[0] %>');
<%- unless @page.categories.include?('S5-slideshow') -%>
addS5button('<%= @page.name.escapeHTML %>');
<%- end -%>
<%- end -%>
//--><!]]> //--><!]]>
</script> </script>

View file

@ -20,7 +20,7 @@
<a href="<%= url_for :web => @web.address, :action => 'files', <a href="<%= url_for :web => @web.address, :action => 'files',
:id => file.file_name %>"><%= file.file_name%></a> (<%= file.created_at.asctime %>) <span class="linked"><%= "Linked to by: " unless :id => file.file_name %>"><%= file.file_name%></a> (<%= file.created_at.asctime %>) <span class="linked"><%= "Linked to by: " unless
@web.pages_that_link_to_file(file.file_name).empty? -%> @web.pages_that_link_to_file(file.file_name).empty? -%>
<%= @web.pages_that_link_to_file(file.file_name).collect { |referring_page| link_to_page(referring_page) }.join(", ").html_safe %></span> <%= @web.pages_that_link_to_file(file.file_name).collect { |referring_page| link_to_page(referring_page) }.join(", ") %></span>
</li> </li>
<%- end -%> <%- end -%>
</ul> </ul>

View file

@ -1,4 +1,4 @@
<%- @title = @page.plain_name + " (history)".html_safe -%> <%- @title = @page.plain_name + " (history)" -%>
<%- @show_footer = true -%> <%- @show_footer = true -%>
<%- @revisions_by_day.keys.sort.reverse.each do |day| -%> <%- @revisions_by_day.keys.sort.reverse.each do |day| -%>
@ -7,7 +7,7 @@
<%- for rev in @revisions_by_day[day] -%> <%- for rev in @revisions_by_day[day] -%>
<li> <li>
<%= link_to_revision(rev.page, @revision_numbers[rev.id], <%= link_to_revision(rev.page, @revision_numbers[rev.id],
text= (rev.page.rev_ids.size == @revision_numbers[rev.id] ? text= (rev.page.revisions.length == @revision_numbers[rev.id] ?
"Current" : "Current" :
"Revision #{@revision_numbers[rev.id]}" ) "Revision #{@revision_numbers[rev.id]}" )
) %> ) %>

View file

@ -6,34 +6,10 @@
<%- unless @pages_that_are_orphaned.empty? && @page_names_that_are_wanted.empty? -%> <%- unless @pages_that_are_orphaned.empty? && @page_names_that_are_wanted.empty? -%>
<h2> <h2>
All Pages All Pages
<br/><span class="pageType">All pages in <%= raw @set_name %> listed alphabetically</span> <br/><span class="pageType">All pages in <%= @set_name %> listed alphabetically</span>
</h2> </h2>
<%- end -%> <%- end -%>
<% if [:markdownMML, :markdownPNG, :markdown].include?(@web.markup) -%>
<% form_tag({ :controller => 'wiki', :action => 'tex_list', :web => @web.address },
{'method' => 'post', 'accept-charset' => 'utf-8' }) do
%>
<div>
<% if params['category'] -%>
<input type="hidden" name="category" value="<%= params['category'] %>"/>
<%- end -%>
<ul id="sortable_pages">
<% @pages_in_category.each do |page| %>
<% content_tag_for :li, page do %>
<input type="checkbox" name="<%= page.name %>" value="tex"/>
<%= link_to_existing_page page, truncate(page.plain_name, :length => 35) %>
<% end %>
<% end %>
</ul>
<%= sortable_element('sortable_pages', {:onUpdate => 'function(){}'}) %>
<label for="commit"> Export selected pages (drag to re-order them) to a LaTeX file.</label>
<%= submit_tag("Export") %>
</div>
<%- end -%>
<%- else -%>
<ul> <ul>
<%- @pages_in_category.each do |page| -%> <%- @pages_in_category.each do |page| -%>
<li> <li>
@ -41,7 +17,6 @@
</li> </li>
<%- end -%> <%- end -%>
</ul> </ul>
<%- end -%>
<%- if @web.count_pages? -%> <%- if @web.count_pages? -%>
<% total_chars = @pages_in_category.characters %> <% total_chars = @pages_in_category.characters %>
@ -55,7 +30,7 @@
Wanted Pages Wanted Pages
<br/> <br/>
<span class="pageType"> <span class="pageType">
Nonexistent pages that other pages in <%= raw @set_name %> reference Nonexistent pages that other pages in <%= @set_name %> reference
</span> </span>
</h2> </h2>
@ -66,7 +41,7 @@
wanted by wanted by
<%= @web.select.pages_that_reference(wanted_page_name).collect { |referring_page| <%= @web.select.pages_that_reference(wanted_page_name).collect { |referring_page|
link_to_existing_page referring_page link_to_existing_page referring_page
}.join(", ").html_safe }.join(", ")
%> %>
</li> </li>
<%- end -%> <%- end -%>
@ -76,7 +51,7 @@
<%- unless @pages_that_are_orphaned.empty? -%> <%- unless @pages_that_are_orphaned.empty? -%>
<h2> <h2>
Orphaned Pages Orphaned Pages
<br/><span class="pageType">Pages in <%= raw @set_name %> that no other page reference</span> <br/><span class="pageType">Pages in <%= @set_name %> that no other page reference</span>
</h2> </h2>
<ul style="margin-bottom: 35px"> <ul style="margin-bottom: 35px">

View file

@ -1,4 +1,4 @@
<%- @title = "#{@page.plain_name} is locked".html_safe -%> <%- @title = "#{@page.plain_name} is locked" -%>
<p> <p>
<%= link_to_page(h(@page.locked_by.purify)) %> <%= link_to_page(h(@page.locked_by.purify)) %>

View file

@ -1,12 +1,12 @@
<%- <%-
@title = "Creating #{WikiWords.separate(@page_name).escapeHTML}".html_safe @title = "Creating #{CGI.escapeHTML(WikiWords.separate(@page_name))}"
@content_width = 720 @content_width = 720
@hide_navigation = true @hide_navigation = true
-%> -%>
<div id="MarkupHelp"> <div id="MarkupHelp">
<%= render(:file => "#{@web.markup}_help") -%> <%= render(:file => "#{@web.markup}_help") %>
<%= render(:file => 'wiki_words_help') unless @web.brackets_only? -%> <%= render(:file => 'wiki_words_help') %>
</div> </div>
<div id="editForm"> <div id="editForm">
@ -14,7 +14,7 @@
{ 'id' => 'editForm', 'method' => 'post', 'onsubmit' => 'cleanAuthorName();', 'accept-charset' => 'utf-8' }) do %> { 'id' => 'editForm', 'method' => 'post', 'onsubmit' => 'cleanAuthorName();', 'accept-charset' => 'utf-8' }) do %>
<textarea name="content" id="content" rows="24" cols="60"><%= h(flash[:content] || <textarea name="content" id="content" rows="24" cols="60"><%= h(flash[:content] ||
params['content'] ? params['content'].purify : '' ) %></textarea> ( (params['content'] && params['content'].is_utf8?) ? params['content'] : '').purify ) %></textarea>
<div id="editFormButtons"> <div id="editFormButtons">
<input type="submit" value="Submit" accesskey="s"/> as <input type="submit" value="Submit" accesskey="s"/> as
<%= text_field_tag :author, @author, <%= text_field_tag :author, @author,
@ -31,8 +31,4 @@ function cleanAuthorName() {
} }
} }
document.forms["editForm"].elements["content"].focus(); document.forms["editForm"].elements["content"].focus();
<%- if [:markdownMML, :markdownPNG, :markdown].include?(@web.markup) -%>
setupSVGedit('<%= compute_public_path("editor/svg-editor.html", "svg-edit").split(/\?/)[0] %>');
addS5button('<%= @page_name.escapeHTML %>');
<%- end -%>
</script> </script>

View file

@ -1,13 +1,13 @@
<%- <%-
@title = @page.plain_name @title = @page.plain_name
@title += ' (changes)' if @show_diff @title += ' (changes)' if @show_diff
@show_footer = true @show_footer = true
-%> -%>
<div id="revision"> <div id="revision">
<%- if @show_diff -%> <%- if @show_diff -%>
<p class="show_diff"> <p class="show_diff">
Showing changes from revision #<%= @page.rev_ids.size - 1 %> to #<%= @page.rev_ids.size %>: Showing changes from revision #<%= @page.revisions.size - 1 %> to #<%= @page.revisions.size %>:
<ins class="diffins">Added</ins> | <del class="diffdel">Removed</del> | <del class="diffmod">Chan</del><ins class="diffmod">ged</ins> <ins class="diffins">Added</ins> | <del class="diffdel">Removed</del> | <del class="diffmod">Chan</del><ins class="diffmod">ged</ins>
</p> </p>
<%= @renderer.display_diff %> <%= @renderer.display_diff %>
@ -18,7 +18,7 @@
<div class="byline"> <div class="byline">
<%= @page.revisions? ? "Revised" : "Created" %> on <%= format_date(@page.revised_at) %> <%= @page.revisions? ? "Revised" : "Created" %> on <%= format_date(@page.revised_at) %>
by <%= author_link(@page) %> by <%= author_link(@page).purify %>
<%= "(#{@page.author.ip})" if @page.author.respond_to?(:ip) %> <%= "(#{@page.author.ip})" if @page.author.respond_to?(:ip) %>
<% if @web.count_pages? %> <% if @web.count_pages? %>
<% total_chars = @page.content.length %> <% total_chars = @page.content.length %>
@ -28,7 +28,7 @@
<div class="navigation navfoot"> <div class="navigation navfoot">
<%= raw navigation_menu_for_page.join(' | ') %> <%= navigation_menu_for_page.join(' | ') %>
<span class="views"> <span class="views">
| Views: | Views:
@ -45,9 +45,6 @@
{:id => 'view_S5'} %> {:id => 'view_S5'} %>
<%- end -%> <%- end -%>
<%- end -%> <%- end -%>
|
<%= link_to 'Source', {:web => @web.address, :action => 'source', :id => @page.name},
{:id => 'view_source', :rel => 'nofollow' } %>
</span> </span>
<%= render :partial => 'inbound_links' %> <%= render :partial => 'inbound_links' %>

View file

@ -2,9 +2,10 @@
@title = @page.plain_name @title = @page.plain_name
@hide_navigation = true @hide_navigation = true
@style_additions = ".newWikiWord { background-color: white; font-style: italic; }" @style_additions = ".newWikiWord { background-color: white; font-style: italic; }"
@inline_style = true
-%> -%>
<%= @renderer.display_content %> <%= @renderer.display_content_for_export %>
<div class="byline"> <div class="byline">
<%= @page.revisions? ? "Revised" : "Created" %> on <%= format_date(@page.revised_at) %> <%= @page.revisions? ? "Revised" : "Created" %> on <%= format_date(@page.revised_at) %>

View file

@ -8,9 +8,9 @@
<%- for page in @pages_by_day[day] -%> <%- for page in @pages_by_day[day] -%>
<li> <li>
<%= link_to_existing_page page %> <%= link_to_existing_page page %>
<%- if page.rev_ids.size > 1 %> <%- if page.revisions.length > 1 %>
<span class="views"> <span class="views">
( <%= link_to_revision(page, page.rev_ids.size, text='diff', ( <%= link_to_revision(page, page.revisions.length, text='diff',
mode='diff') %> | <%= link_to_history(page, text='history') %> ) mode='diff') %> | <%= link_to_history(page, text='history') %> )
</span> </span>
<%- end -%> <%- end -%>

View file

@ -1,5 +1,5 @@
<%- <%-
@title = "#{@page.plain_name} (Rev ##{@revision_number}#{@show_diff ? ', changes' : ''})".html_safe @title = "#{@page.plain_name} (Rev ##{@revision_number}#{@show_diff ? ', changes' : ''})"
-%> -%>
@ -21,11 +21,6 @@
</div> </div>
<div class="navigation navfoot"> <div class="navigation navfoot">
<%= raw navigation_menu_for_revision.join(' | ') %> <%= navigation_menu_for_revision.join(' | ') %>
<span class="views">
| View:
<%= link_to 'Source', {:web => @web.address, :action => 'source', :id => @page.name, :rev => @revision_number},
{:id => 'view_source', :rel => 'nofollow' } %>
</span>
<%= render :partial => 'inbound_links' %> <%= render :partial => 'inbound_links' %>
</div> </div>

View file

@ -1,12 +1,14 @@
<%- <%-
@title = "Rollback to #{@page.plain_name} Rev ##{@revision_number}".html_safe @title = "Rollback to #{@page.plain_name} Rev ##{@revision_number}"
@content_width = 720 @content_width = 720
@hide_navigation = true @hide_navigation = true
-%> -%>
<%= "<p style='color:red'>Please correct the error that caused this error in rendering:<br/><small>#{params["msg"]}</small></p>" if params["msg"] %>
<div id="MarkupHelp"> <div id="MarkupHelp">
<%= render(:file => "#{@web.markup}_help") -%> <%= render(:file => "#{@web.markup}_help") %>
<%= render(:file => 'wiki_words_help') unless @web.brackets_only? -%> <%= render(:file => 'wiki_words_help') %>
</div> </div>
<% form_tag({:web => @web.address, :action => 'save', :id => @page.name}, <% form_tag({:web => @web.address, :action => 'save', :id => @page.name},

View file

@ -1,4 +1,5 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg-flat.dtd" > <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg-flat.dtd" >
<html xmlns="http://www.w3.org/1999/xhtml"> <html xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
@ -12,33 +13,15 @@
<meta name="defaultView" content="slideshow" /> <meta name="defaultView" content="slideshow" />
<meta name="controlVis" content="hidden" /> <meta name="controlVis" content="hidden" />
<!-- style sheet links --> <!-- style sheet links -->
<%= stylesheet_link_tag "/stylesheets/syntax.css", :media => 'all', :id => 'syntaxStyle' %> <link rel="stylesheet" href="/s5/themes/<%=@s5_theme%>/slides.css" type="text/css" media="projection" id="slideProj" />
<%= stylesheet_link_tag "/s5/ui/core/outline.css", :media => 'screen', :id => 'outlineStyle' %> <link rel="stylesheet" href="/s5/ui/core/outline.css" type="text/css" media="screen" id="outlineStyle" />
<%= stylesheet_link_tag "/s5/ui/core/print.css", :media => 'print', :id => 'slidePrint' %> <link rel="stylesheet" href="/s5/ui/core/print.css" type="text/css" media="print" id="slidePrint" />
<%= stylesheet_link_tag "/s5/ui/core/opera.css", :media => 'projection', :id => 'operaFix' %> <link rel="stylesheet" href="/s5/ui/core/opera.css" type="text/css" media="projection" id="operaFix" />
<%= stylesheet_link_tag "/s5/ui/core/math.css", :media => 'all', :id => 'mathStyle' %> <link rel="stylesheet" href="/s5/ui/core/math.css" type="text/css" media="all" id="mathStyle" />
<%= stylesheet_link_tag "/s5/themes/#{@s5_theme}/slides.css", :media => 'projection', :id => 'slideProj' %>
<!-- S5 JS --> <!-- S5 JS -->
<%= javascript_include_tag 'prototype' %> <script src="/s5/ui/core/slides.js" type="text/javascript"></script>
<%- if @web.markup == :markdownMML -%> <script src="/javascripts/prototype.js" type="text/javascript"></script>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
MathML: { useMathMLspacing: true },
"HTML-CSS": { scale: 90 }
});
if (window._onload_fired_) MathJax.Hub.Startup.onload();
</script>
<script type="text/javascript">
if (!(Prototype.Browser.Gecko || navigator.userAgent.match(/MathPlayer/))) {
var s = document.createElement('script');
s.src = "<%= compute_public_path('MathJax.js', 'MathJax').split('?')[0] %>?config=MML_HTMLorMML";
document.querySelector('head').appendChild(s);
window.addEventListener("load", function(){window._onload_fired_ = true} , false);
};
</script>
<%- end -%>
<%= javascript_include_tag "/s5/ui/core/slides.js" %>
</head> </head>
<body> <body>

View file

@ -1,4 +1,4 @@
<%- @title = "Search results for \"#{h @query}\"".html_safe -%> <%- @title = "Search results for \"#{h params["query"]}\"" -%>
<%- unless @title_results.empty? -%> <%- unless @title_results.empty? -%>
<h2><%= @title_results.length %> page(s) containing search string in the page name:</h2> <h2><%= @title_results.length %> page(s) containing search string in the page name:</h2>
@ -24,7 +24,7 @@
<%- end -%> <%- end -%>
<%- if (@results + @title_results).empty? -%> <%- if (@results + @title_results).empty? -%>
<h2>No pages contain "<%= h(@query).html_safe %>" </h2> <h2>No pages contain "<%= h params["query"] %>" </h2>
<p> <p>
Perhaps you should try expanding your query. Remember that Instiki searches for entire Perhaps you should try expanding your query. Remember that Instiki searches for entire
phrases, so if you search for "all that jazz" it will not match pages that contain these phrases, so if you search for "all that jazz" it will not match pages that contain these
@ -35,7 +35,4 @@
expression. That's actually what Instiki uses, so go right ahead and flex your expression. That's actually what Instiki uses, so go right ahead and flex your
"[a-z]*Leet?RegExpSkill(s|z)" "[a-z]*Leet?RegExpSkill(s|z)"
</p> </p>
<p>
<b>Create a new page, named:</b> "<span class='newWikiWord'><%= link_to h(@query).html_safe, :web => @web.address, :action => 'new', :id => @query %></span>"
</p>
<%- end -%> <%- end -%>

View file

@ -1,64 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg-flat.dtd" >
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>
<%- if @page and (@page.name == 'HomePage') -%>
<%= h(@web.name) %>
<%- else @web -%>
<%= @page.plain_name %> in <%= h @web.name %>
<%- end -%>
</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="robots" content="<%= @robots_metatag_value %>" />
<style type="text/css">
h1#pageName, div.info, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover, [actiontype="toggle"]:hover, #TextileHelp h3 {
color: #<%= @web ? @web.color : "393" %>;
}
<%= Rails.root.join('public', 'stylesheets', 'instiki.css').read if @inline_style %>
</style>
<%= stylesheet_link_tag 'instiki', :media => 'all' unless @inline_style %>
<%= "<style type='text/css'>#{@style_additions}</style>".html_safe if @style_additions %>
<style type="text/css"><!--/*--><![CDATA[/*><!--*/
<%= @web ? @web.additional_style : '' %>
/*]]>*/--></style>
<%= javascript_include_tag :defaults %>
<script type="text/javascript">
<!--//--><![CDATA[//><!--
function updateSize(elt, w, h) {
// adjust to the size of the user's browser area.
// w and h are the original, unadjusted, width and height per row/column
var parentheight = document.viewport.getHeight();
var parentwidth = $('Container').getWidth();
elt.writeAttribute({'cols': Math.floor(parentwidth/w) - 1,
'rows': Math.floor(parentheight/h) - 2 });
elt.setStyle({Width: parentwidth, Height: parentheight});
}
function resizeableTextarea() {
//make the textarea resize to fit available space
$$('textarea#content').each( function(textarea) {
var w = textarea.getWidth()/textarea.getAttribute('cols');
var h = textarea.getStyle('lineHeight').replace(/(\d*)px/, "$1");
Event.observe(window, 'resize', function(){ updateSize(textarea, w, h) });
updateSize(textarea, w, h);
Form.Element.focus(textarea);
});
}
window.onload = function (){
resizeableTextarea();
}
//--><!]]>
</script>
</head>
<body>
<div id="Container">
<textarea id='content' readonly=' readonly' rows='24' cols='60' ><%= (@revision ? @revision.content : @page.content).purify %></textarea>
</div> <!-- Container -->
</body>
</html>

View file

@ -1,3 +1,239 @@
\documentclass[12pt,titlepage]{article}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{amsthm}
<%- if @tex_content =~ /\\mathbb\{[^}]*[a-z0-9]+[^}]*\}/ -%>
\usepackage{mathbbol}
<% end -%>
\usepackage{graphicx}
\usepackage{color}
\usepackage{ucs}
\usepackage[utf8x]{inputenc}
\usepackage{hyperref}
%----Macros----------
%
% Unresolved issues:
%
% \righttoleftarrow
% \lefttorightarrow
%
% \color{} with HTML colorspec
% \bgcolor
% \array
% Of the standard HTML named colors, white, black, red, green, blue and yellow
% are predefined in the color package. Here are the rest.
\definecolor{aqua}{rgb}{0, 1.0, 1.0}
\definecolor{fuschia}{rgb}{1.0, 0, 1.0}
\definecolor{gray}{rgb}{0.502, 0.502, 0.502}
\definecolor{lime}{rgb}{0, 1.0, 0}
\definecolor{maroon}{rgb}{0.502, 0, 0}
\definecolor{navy}{rgb}{0, 0, 0.502}
\definecolor{olive}{rgb}{0.502, 0.502, 0}
\definecolor{purple}{rgb}{0.502, 0, 0.502}
\definecolor{silver}{rgb}{0.753, 0.753, 0.753}
\definecolor{teal}{rgb}{0, 0.502, 0.502}
% Because of conflicts, \space and \mathop are converted to
% \itexspace and \operatorname during preprocessing.
% itex: \space{ht}{dp}{wd}
%
% Height and baseline depth measurements are in units of tenths of an ex while
% the width is measured in tenths of an em.
\makeatletter
\newdimen\itex@wd%
\newdimen\itex@dp%
\newdimen\itex@thd%
\def\itexspace#1#2#3{\itex@wd=#3em%
\itex@wd=0.1\itex@wd%
\itex@dp=#2ex%
\itex@dp=0.1\itex@dp%
\itex@thd=#1ex%
\itex@thd=0.1\itex@thd%
\advance\itex@thd\the\itex@dp%
\makebox[\the\itex@wd]{\rule[-\the\itex@dp]{0cm}{\the\itex@thd}}}
\makeatother
% \tensor and \multiscript
\makeatletter
\newif\if@sup
\newtoks\@sups
\def\append@sup#1{\edef\act{\noexpand\@sups={\the\@sups #1}}\act}%
\def\reset@sup{\@supfalse\@sups={}}%
\def\mk@scripts#1#2{\if #2/ \if@sup ^{\the\@sups}\fi \else%
\ifx #1_ \if@sup ^{\the\@sups}\reset@sup \fi {}_{#2}%
\else \append@sup#2 \@suptrue \fi%
\expandafter\mk@scripts\fi}
\def\tensor#1#2{\reset@sup#1\mk@scripts#2_/}
\def\multiscripts#1#2#3{\reset@sup{}\mk@scripts#1_/#2%
\reset@sup\mk@scripts#3_/}
\makeatother
% \slash
\makeatletter
\newbox\slashbox \setbox\slashbox=\hbox{$/$}
\def\itex@pslash#1{\setbox\@tempboxa=\hbox{$#1$}
\@tempdima=0.5\wd\slashbox \advance\@tempdima 0.5\wd\@tempboxa
\copy\slashbox \kern-\@tempdima \box\@tempboxa}
\def\slash{\protect\itex@pslash}
\makeatother
% Renames \sqrt as \oldsqrt and redefine root to result in \sqrt[#1]{#2}
\let\oldroot\root
\def\root#1#2{\oldroot #1 \of{#2}}
% Manually declare the txfonts symbolsC font
\DeclareSymbolFont{symbolsC}{U}{txsyc}{m}{n}
\SetSymbolFont{symbolsC}{bold}{U}{txsyc}{bx}{n}
\DeclareFontSubstitution{U}{txsyc}{m}{n}
% Manually declare the stmaryrd font
\DeclareSymbolFont{stmry}{U}{stmry}{m}{n}
\SetSymbolFont{stmry}{bold}{U}{stmry}{b}{n}
% Declare specific arrows from txfonts without loading the full package
\makeatletter
\def\re@DeclareMathSymbol#1#2#3#4{%
\let#1=\undefined
\DeclareMathSymbol{#1}{#2}{#3}{#4}}
\re@DeclareMathSymbol{\neArrow}{\mathrel}{symbolsC}{116}
\re@DeclareMathSymbol{\neArr}{\mathrel}{symbolsC}{116}
\re@DeclareMathSymbol{\seArrow}{\mathrel}{symbolsC}{117}
\re@DeclareMathSymbol{\seArr}{\mathrel}{symbolsC}{117}
\re@DeclareMathSymbol{\nwArrow}{\mathrel}{symbolsC}{118}
\re@DeclareMathSymbol{\nwArr}{\mathrel}{symbolsC}{118}
\re@DeclareMathSymbol{\swArrow}{\mathrel}{symbolsC}{119}
\re@DeclareMathSymbol{\swArr}{\mathrel}{symbolsC}{119}
\re@DeclareMathSymbol{\nequiv}{\mathrel}{symbolsC}{46}
\re@DeclareMathSymbol{\Perp}{\mathrel}{symbolsC}{121}
\re@DeclareMathSymbol{\Vbar}{\mathrel}{symbolsC}{121}
\re@DeclareMathSymbol{\sslash}{\mathrel}{stmry}{12}
\re@DeclareMathSymbol{\invamp}{\mathrel}{symbolsC}{77}
\re@DeclareMathSymbol{\parr}{\mathrel}{symbolsC}{77}
\makeatother
% Widecheck
\makeatletter
\DeclareRobustCommand\widecheck[1]{{\mathpalette\@widecheck{#1}}}
\def\@widecheck#1#2{%
\setbox\z@\hbox{\m@th$#1#2$}%
\setbox\tw@\hbox{\m@th$#1%
\widehat{%
\vrule\@width\z@\@height\ht\z@
\vrule\@height\z@\@width\wd\z@}$}%
\dp\tw@-\ht\z@
\@tempdima\ht\z@ \advance\@tempdima2\ht\tw@ \divide\@tempdima\thr@@
\setbox\tw@\hbox{%
\raise\@tempdima\hbox{\scalebox{1}[-1]{\lower\@tempdima\box
\tw@}}}%
{\ooalign{\box\tw@ \cr \box\z@}}}
\makeatother
% udots (taken from yhmath)
\makeatletter
\def\udots{\mathinner{\mkern2mu\raise\p@\hbox{.}
\mkern2mu\raise4\p@\hbox{.}\mkern1mu
\raise7\p@\vbox{\kern7\p@\hbox{.}}\mkern1mu}}
\makeatother
%% Renaming existing commands
\newcommand{\underoverset}[3]{\underset{#1}{\overset{#2}{#3}}}
\newcommand{\widevec}{\overrightarrow}
\newcommand{\darr}{\downarrow}
\newcommand{\nearr}{\nearrow}
\newcommand{\nwarr}{\nwarrow}
\newcommand{\searr}{\searrow}
\newcommand{\swarr}{\swarrow}
\newcommand{\curvearrowbotright}{\curvearrowright}
\newcommand{\uparr}{\uparrow}
\newcommand{\downuparrow}{\updownarrow}
\newcommand{\duparr}{\updownarrow}
\newcommand{\updarr}{\updownarrow}
\newcommand{\gt}{>}
\newcommand{\lt}{<}
\newcommand{\map}{\mapsto}
\newcommand{\embedsin}{\hookrightarrow}
\newcommand{\Alpha}{A}
\newcommand{\Beta}{B}
\newcommand{\Zeta}{Z}
\newcommand{\Eta}{H}
\newcommand{\Iota}{I}
\newcommand{\Kappa}{K}
\newcommand{\Mu}{M}
\newcommand{\Nu}{N}
\newcommand{\Rho}{P}
\newcommand{\Tau}{T}
\newcommand{\Upsi}{\Upsilon}
\newcommand{\omicron}{o}
\newcommand{\lang}{\langle}
\newcommand{\rang}{\rangle}
\newcommand{\Union}{\bigcup}
\newcommand{\Intersection}{\bigcap}
\newcommand{\Oplus}{\bigoplus}
\newcommand{\Otimes}{\bigotimes}
\newcommand{\Wedge}{\bigwedge}
\newcommand{\Vee}{\bigvee}
\newcommand{\coproduct}{\coprod}
\newcommand{\product}{\prod}
\newcommand{\closure}{\overline}
\newcommand{\integral}{\int}
\newcommand{\doubleintegral}{\iint}
\newcommand{\tripleintegral}{\iiint}
\newcommand{\quadrupleintegral}{\iiiint}
\newcommand{\conint}{\oint}
\newcommand{\contourintegral}{\oint}
\newcommand{\infinity}{\infty}
\renewcommand{\empty}{\emptyset}
\newcommand{\bottom}{\bot}
\newcommand{\minusb}{\boxminus}
\newcommand{\plusb}{\boxplus}
\newcommand{\timesb}{\boxtimes}
\newcommand{\intersection}{\cap}
\newcommand{\union}{\cup}
\newcommand{\Del}{\nabla}
\newcommand{\odash}{\circleddash}
\newcommand{\negspace}{\!}
\newcommand{\widebar}{\overline}
\newcommand{\textsize}{\normalsize}
\renewcommand{\scriptsize}{\scriptstyle}
\newcommand{\scriptscriptsize}{\scriptscriptstyle}
\newcommand{\mathfr}{\mathfrak}
\newcommand{\statusline}[2]{#2}
\newcommand{\toggle}[2]{#1}
% Theorem Environments
\theoremstyle{plain}
\newtheorem{theorem}{Theorem}
\newtheorem{lemma}{Lemma}
\newtheorem{prop}{Proposition}
\newtheorem{cor}{Corollary}
\newtheorem*{utheorem}{Theorem}
\newtheorem*{ulemma}{Lemma}
\newtheorem*{uprop}{Proposition}
\newtheorem*{ucor}{Corollary}
\theoremstyle{definition}
\newtheorem{defn}{Definition}
\newtheorem{example}{Example}
\newtheorem*{udefn}{Definition}
\newtheorem*{uexample}{Example}
\theoremstyle{remark}
\newtheorem{remark}{Remark}
\newtheorem{note}{Note}
\newtheorem*{uremark}{Remark}
\newtheorem*{unote}{Note}
%-------------------------------------------------------------------
\begin{document}
%-------------------------------------------------------------------
\section*{<%= @page.name %>} \section*{<%= @page.name %>}
<%= @tex_content.html_safe %> <%= @tex_content %>
\end{document}

View file

@ -1 +0,0 @@
<%= @tex_content.html_safe %>

View file

@ -15,11 +15,11 @@
<%- end -%> <%- end -%>
<div class="byline" style="margin-bottom: 0px"> <div class="byline" style="margin-bottom: 0px">
<%= web.pages.size %> page<% if web.pages.size != 1 %>s<% end %> by <%= web.authors.size %> author<% if web.authors.size != 1 %>s<% end %> <%= web.pages.length %> page<% if web.pages.length != 1 %>s<% end %> by <%= web.authors.length %> author<% if web.authors.length != 1 %>s<% end %>
- Last Update: <%= web.last_page.nil? ? format_date(web.created_at) : format_date(web.last_page.revised_at) %><br/> - Last Update: <%= web.last_page.nil? ? format_date(web.created_at) : format_date(web.last_page.revised_at) %><br/>
<%- if ! web.last_page.nil? -%> <%- if ! web.last_page.nil? -%>
Last Document: <%= link_to_page(web.last_page.name,web) %> Last Document: <%= link_to_page(web.last_page.name,web) %>
<%= web.last_page.revisions? ? "Revised" : "Created" %> by <%= author_link(web.last_page) %> (<%= web.last_page.current_revision.ip %>) <%= web.last_page.revisions? ? "Revised" : "Created" %> by <%= author_link(web.last_page).purify %> (<%= web.last_page.current_revision.ip %>)
<%- end -%> <%- end -%>
</div> </div>
</div> </div>

View file

@ -1,262 +1,207 @@
# == Introduction
#
# This module provides sanitization of XHTML+MathML+SVG
# and of inline style attributes. Its genesis is {described here}[http://golem.ph.utexas.edu/~distler/blog/archives/001181.html].
#
# Uses the {HTML5lib parser}[http://code.google.com/p/html5lib/], so that the parsing behaviour should
# resemble that of browsers.
#
# sanitize_xhtml() is a case-sensitive sanitizer, suitable for XHTML
# sanitize_html() is a case-insensitive sanitizer suitable for HTML
# sanitize_rexml() sanitizes a REXML tree, returning a string
# safe_sanitize_xhtml() makes extra-sure that the result is well-formed XHTML
# by running the output of sanitize_xhtml() through REXML
#
# == Files
#
# {sanitize.rb}[http://golem.ph.utexas.edu/~distler/code/instiki/svn/lib/sanitize.rb],
# {HTML5lib}[http://golem.ph.utexas.edu/~distler/code/instiki/svn/vendor/plugins/HTML5lib/]
#
# == Author
#
# {Jacques Distler}[http://golem.ph.utexas.edu/~distler/]
#
# == License
#
# Ruby License
module Sanitize module Sanitize
require 'html5/html5parser' # This module provides sanitization of XHTML+MathML+SVG
require 'html5/liberalxmlparser' # and of inline style attributes.
require 'html5/treewalkers' #
require 'html5/treebuilders' # Based heavily on Sam Ruby's code in the Universal FeedParser.
require 'html5/serializer'
require 'html5/sanitizer'
require 'stringsupport.rb'
include HTML5 require 'html/tokenizer'
require 'node'
# Sanitize a string, parsed using XHTML parsing rules. acceptable_elements = ['a', 'abbr', 'acronym', 'address', 'area', 'b',
# 'big', 'blockquote', 'br', 'button', 'caption', 'center', 'cite',
# :call-seq: 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl', 'dt',
# sanitize_xhtml(string) -> string 'em', 'fieldset', 'font', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
# sanitize_xhtml(string, {:encoding => 'iso-8859-1', :to_tree => true}) -> REXML::Document 'hr', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'map',
# 'menu', 'ol', 'optgroup', 'option', 'p', 'pre', 'q', 's', 'samp',
# Unless otherwise specified, the string is assumed to be utf-8 encoded. 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'table',
# By default, the output is a string. But, optionally, you can return a REXML tree. 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'tr', 'tt', 'u',
# 'ul', 'var']
# The string returned is utf-8 encoded. If you want, you can use iconv to convert it to some other encoding.
# (REXML trees are always utf-8 encoded.) mathml_elements = ['maction', 'math', 'merror', 'mfrac', 'mi',
def sanitize_xhtml(html, options = {}) 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom',
@encoding = 'utf-8' 'mprescripts', 'mroot', 'mrow', 'mspace', 'msqrt', 'mstyle', 'msub',
@treebuilder = TreeBuilders::REXML::TreeBuilder 'msubsup', 'msup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder',
@to_tree = false 'munderover', 'none']
options.each do |name, value|
next unless %w(encoding treebuilder to_tree).include? name.to_s svg_elements = ['a', 'animate', 'animateColor', 'animateMotion',
if name.to_s == 'treebuilder' 'animateTransform', 'circle', 'defs', 'desc', 'ellipse', 'font-face',
@treebuilder = HTML5lib::TreeBuilders.get_tree_builder(value) 'font-face-name', 'font-face-src', 'g', 'glyph', 'hkern', 'image',
else 'linearGradient', 'line', 'marker', 'metadata', 'missing-glyph',
instance_variable_set("@#{name}", value) 'mpath', 'path', 'polygon', 'polyline', 'radialGradient', 'rect',
end 'set', 'stop', 'svg', 'switch', 'text', 'title', 'tspan', 'use']
end
if @encoding == 'utf-8' acceptable_attributes = ['abbr', 'accept', 'accept-charset', 'accesskey',
parsed = XHTMLParser.parse_fragment(html.to_utf8, {:tokenizer => HTMLSanitizer, 'action', 'align', 'alt', 'axis', 'border', 'cellpadding',
:lowercase_element_name => false, :lowercase_attr_name => false, 'cellspacing', 'char', 'charoff', 'charset', 'checked', 'cite', 'class',
:encoding => @encoding, :tree => @treebuilder }) 'clear', 'cols', 'colspan', 'color', 'compact', 'coords', 'datetime',
else 'dir', 'disabled', 'enctype', 'for', 'frame', 'headers', 'height',
parsed = XHTMLParser.parse_fragment(html.to_ncr, {:tokenizer => HTMLSanitizer, 'href', 'hreflang', 'hspace', 'id', 'ismap', 'label', 'lang',
:lowercase_element_name => false, :lowercase_attr_name => false, 'longdesc', 'maxlength', 'media', 'method', 'multiple', 'name',
:encoding => @encoding, :tree => @treebuilder }) 'nohref', 'noshade', 'nowrap', 'prompt', 'readonly', 'rel', 'rev',
end 'rows', 'rowspan', 'rules', 'scope', 'selected', 'shape', 'size',
return parsed if @to_tree 'span', 'src', 'start', 'style', 'summary', 'tabindex', 'target', 'title',
return parsed.to_s 'type', 'usemap', 'valign', 'value', 'vspace', 'width', 'xml:lang']
end
mathml_attributes = ['actiontype', 'align', 'columnalign', 'columnalign',
'columnalign', 'columnlines', 'columnspacing', 'columnspan', 'depth',
'display', 'displaystyle', 'equalcolumns', 'equalrows', 'fence',
'fontstyle', 'fontweight', 'frame', 'height', 'linethickness', 'lspace',
'mathbackground', 'mathcolor', 'mathvariant', 'mathvariant', 'maxsize',
'minsize', 'other', 'rowalign', 'rowalign', 'rowalign', 'rowlines',
'rowspacing', 'rowspan', 'rspace', 'scriptlevel', 'selection',
'separator', 'stretchy', 'width', 'width', 'xlink:href', 'xlink:show',
'xlink:type', 'xmlns', 'xmlns:xlink']
svg_attributes = ['accent-height', 'accumulate', 'additive', 'alphabetic',
'arabic-form', 'ascent', 'attributeName', 'attributeType',
'baseProfile', 'bbox', 'begin', 'by', 'calcMode', 'cap-height',
'class', 'color', 'color-rendering', 'content', 'cx', 'cy', 'd', 'dx',
'dy', 'descent', 'display', 'dur', 'end', 'fill', 'fill-rule',
'font-family', 'font-size', 'font-stretch', 'font-style', 'font-variant',
'font-weight', 'from', 'fx', 'fy', 'g1', 'g2', 'glyph-name',
'gradientUnits', 'hanging', 'height', 'horiz-adv-x', 'horiz-origin-x',
'id', 'ideographic', 'k', 'keyPoints', 'keySplines', 'keyTimes',
'lang', 'marker-end', 'marker-mid', 'marker-start', 'markerHeight',
'markerUnits', 'markerWidth', 'mathematical', 'max', 'min', 'name',
'offset', 'opacity', 'orient', 'origin', 'overline-position',
'overline-thickness', 'panose-1', 'path', 'pathLength', 'points',
'preserveAspectRatio', 'r', 'refX', 'refY', 'repeatCount', 'repeatDur',
'requiredExtensions', 'requiredFeatures', 'restart', 'rotate', 'rx',
'ry', 'slope', 'stemh', 'stemv', 'stop-color', 'stop-opacity',
'strikethrough-position', 'strikethrough-thickness', 'stroke',
'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap',
'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity',
'stroke-width', 'systemLanguage', 'target',
'text-anchor', 'to', 'transform', 'type', 'u1', 'u2',
'underline-position', 'underline-thickness', 'unicode',
'unicode-range', 'units-per-em', 'values', 'version', 'viewBox',
'visibility', 'width', 'widths', 'x', 'x-height', 'x1', 'x2',
'xlink:actuate', 'xlink:arcrole', 'xlink:href', 'xlink:role',
'xlink:show', 'xlink:title', 'xlink:type', 'xml:base', 'xml:lang',
'xml:space', 'xmlns', 'xmlns:xlink', 'y', 'y1', 'y2', 'zoomAndPan']
attr_val_is_uri = ['href', 'src', 'cite', 'action', 'longdesc', 'xlink:href']
# Sanitize a string, parsed using XHTML parsing rules. Reparse the result to acceptable_css_properties = ['azimuth', 'background-color',
# ensure well-formedness. 'border-bottom-color', 'border-collapse', 'border-color',
# 'border-left-color', 'border-right-color', 'border-top-color', 'clear',
# :call-seq: 'color', 'cursor', 'direction', 'display', 'elevation', 'float', 'font',
# safe_sanitize_xhtml(string) -> string 'font-family', 'font-size', 'font-style', 'font-variant', 'font-weight',
# 'height', 'letter-spacing', 'line-height', 'overflow', 'pause',
# Unless otherwise specified, the string is assumed to be utf-8 encoded. 'pause-after', 'pause-before', 'pitch', 'pitch-range', 'richness',
# 'speak', 'speak-header', 'speak-numeral', 'speak-punctuation',
# The string returned is utf-8 encoded. If you want, you can use iconv to convert it to some other encoding. 'speech-rate', 'stress', 'text-align', 'text-decoration', 'text-indent',
# (REXML trees are always utf-8 encoded.) 'unicode-bidi', 'vertical-align', 'voice-family', 'volume',
def safe_sanitize_xhtml(html, options = {}) 'white-space', 'width']
options[:to_tree] = false
sanitized = sanitize_xhtml(html, options)
doc = REXML::Document.new("<div xmlns='http://www.w3.org/1999/xhtml'>#{sanitized}</div>")
sanitized = doc.to_s.gsub(/\A<div xmlns='http:\/\/www.w3.org\/1999\/xhtml'>(.*)<\/div>\Z/m, '\1')
rescue REXML::ParseException
sanitized = sanitized.escapeHTML
end
# Sanitize a string, parsed using HTML parsing rules. acceptable_css_keywords = ['auto', 'aqua', 'black', 'block', 'blue',
# 'bold', 'both', 'bottom', 'brown', 'center', 'collapse', 'dashed',
# :call-seq: 'dotted', 'fuchsia', 'gray', 'green', '!important', 'italic', 'left',
# sanitize_html( string ) -> string 'lime', 'maroon', 'medium', 'none', 'navy', 'normal', 'nowrap', 'olive',
# sanitize_html( string, {:encoding => 'iso-8859-1', :to_tree => true} ) -> REXML::Document 'pointer', 'purple', 'red', 'right', 'solid', 'silver', 'teal', 'top',
# 'transparent', 'underline', 'white', 'yellow']
# Unless otherwise specified, the string is assumed to be utf-8 encoded.
# By default, the output is a string. But, optionally, you can return a REXML tree.
#
# The string returned is utf-8 encoded. If you want, you can use iconv to convert it to some other encoding.
# (REXML trees are always utf-8 encoded.)
def sanitize_html(html, options = {})
@encoding = 'utf-8'
@treebuilder = TreeBuilders::REXML::TreeBuilder
@to_tree = false
options.each do |name, value|
next unless %w(encoding treebuilder to_tree).include? name.to_s
if name.to_s == 'treebuilder'
@treebuilder = HTML5lib::TreeBuilders.get_tree_builder(value)
else
instance_variable_set("@#{name}", value)
end
end
if @encoding == 'utf-8'
parsed = HTMLParser.parse_fragment(html.to_utf8, {:tokenizer => HTMLSanitizer,
:encoding => @encoding, :tree => @treebuilder })
else
parsed = HTMLParser.parse_fragment(html.to_ncr, {:tokenizer => HTMLSanitizer,
:encoding => @encoding, :tree => @treebuilder })
end
return parsed if @to_tree
return parsed.to_s
end
# Sanitize a REXML tree. The output is a string. acceptable_svg_properties = [ 'fill', 'fill-opacity', 'fill-rule',
# 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin',
# :call-seq: 'stroke-opacity']
# sanitize_rexml(tree) -> string
#
def sanitize_rexml(tree)
tokens = TreeWalkers.get_tree_walker('rexml2').new(tree)
XHTMLSerializer.serialize(tokens, {:encoding=>'utf-8',
:space_before_trailing_solidus => true,
:inject_meta_charset => false,
:sanitize => true})
end
end
require 'rexml/element' acceptable_protocols = [ 'ed2k', 'ftp', 'http', 'https', 'irc',
module REXML #:nodoc: 'mailto', 'news', 'gopher', 'nntp', 'telnet', 'webcal',
class Element 'xmpp', 'callto', 'feed', 'urn', 'aim', 'rsync', 'tag',
'ssh', 'sftp', 'rtsp', 'afs' ]
# Convert XHTML+MathML Named Entities in a REXML::Element to Numeric Character References ALLOWED_ELEMENTS = acceptable_elements + mathml_elements + svg_elements unless defined?(ALLOWED_ELEMENTS)
# ALLOWED_ATTRIBUTES = acceptable_attributes + mathml_attributes + svg_attributes unless defined?(ALLOWED_ATTRIBUTES)
# :call-seq: ALLOWED_CSS_PROPERTIES = acceptable_css_properties unless defined?(ALLOWED_CSS_PROPERTIES)
# tree.to_ncr -> REXML::Element ALLOWED_CSS_KEYWORDS = acceptable_css_keywords unless defined?(ALLOWED_CSS_KEYWORDS)
# ALLOWED_SVG_PROPERTIES = acceptable_svg_properties unless defined?(ALLOWED_SVG_PROPERTIES)
# REXML, typically, converts NCRs to utf-8 characters, which is what you'll see when you ALLOWED_PROTOCOLS = acceptable_protocols unless defined?(ALLOWED_PROTOCOLS)
# access the resulting REXML document. ATTR_VAL_IS_URI = attr_val_is_uri unless defined?(ATTR_VAL_IS_URI)
#
# Note that this method needs to traverse the entire tree, converting text nodes and attributes
# for each element. This can be SLOW. It will often be faster to serialize to a string and then
# use String.to_ncr instead.
#
def to_ncr
self.each_element { |el|
el.texts.each_index {|i|
el.texts[i].value = el.texts[i].to_s.to_ncr
}
el.attributes.each { |name,val|
el.attributes[name] = val.to_ncr
}
el.to_ncr if el.has_elements?
}
return self
end
# Convert XHTML+MathML Named Entities in a REXML::Element to UTF-8
#
# :call-seq:
# tree.to_utf8 -> REXML::Element
#
# Note that this method needs to traverse the entire tree, converting text nodes and attributes
# for each element. This can be SLOW. It will often be faster to serialize to a string and then
# use String.to_utf8 instead.
#
def to_utf8
self.each_element { |el|
el.texts.each_index {|i|
el.texts[i].value = el.texts[i].to_s.to_utf8
}
el.attributes.each { |name,val|
el.attributes[name] = val.to_utf8
}
el.to_utf8 if el.has_elements?
}
return self
end
end # Sanitize the +html+, escaping all elements not in ALLOWED_ELEMENTS, and stripping out all
end # attributes not in ALLOWED_ATTRIBUTES. Style attributes are parsed, and a restricted set,
# specified by ALLOWED_CSS_PROPERTIES and ALLOWED_CSS_KEYWORDS, are allowed through.
# attributes in ATTR_VAL_IS_URI are scanned, and only URI schemes specified in
# ALLOWED_PROTOCOLS are allowed.
# You can adjust what gets sanitized, by defining these constant arrays before this Module is loaded.
#
# sanitize_html('<script> do_nasty_stuff() </script>')
# => &lt;script> do_nasty_stuff() &lt;/script>
# sanitize_html('<a href="javascript: sucker();">Click here for $100</a>')
# => <a>Click here for $100</a>
def sanitize_html(html)
if html.index("<")
tokenizer = HTML::Tokenizer.new(html)
new_text = ""
module HTML5 #:nodoc: all while token = tokenizer.next
module TreeWalkers node = XHTML::Node.parse(nil, 0, 0, token, false)
new_text << case node.tag?
private when true
if ALLOWED_ELEMENTS.include?(node.name)
class << self if node.closing != :close
def [](name) node.attributes.delete_if { |attr,v| !ALLOWED_ATTRIBUTES.include?(attr) }
case name.to_s.downcase ATTR_VAL_IS_URI.each do |attr|
when 'rexml' val_unescaped = CGI.unescapeHTML(node.attributes[attr].to_s).gsub(/[\000-\040\177\s]+|\302*[\200-\240]/,'').downcase
require 'html5/treewalkers/rexml' if val_unescaped =~ /^[a-z0-9][-+.a-z0-9]*:/ and !ALLOWED_PROTOCOLS.include?(val_unescaped.split(':')[0])
REXML::TreeWalker node.attributes.delete attr
when 'rexml2' end
REXML2::TreeWalker end
else if node.attributes['style']
raise "Unknown TreeWalker #{name}" node.attributes['style'] = sanitize_css(node.attributes['style'])
end end
end end
node.to_s
alias :get_tree_walker :[] else
end node.to_s.gsub(/</, "&lt;")
end
module REXML2 else
class TreeWalker < HTML5::TreeWalkers::NonRecursiveTreeWalker node.to_s.gsub(/</, "&lt;")
private
def node_details(node)
case node
when ::REXML::Document
[:DOCUMENT]
when ::REXML::Element
if !node.name
[:DOCUMENT_FRAGMENT]
else
[:ELEMENT, node.name,
node.attributes.map {|name,value| [name,value.to_utf8]},
node.has_elements? || node.has_text?]
end end
when ::REXML::Text
[:TEXT, node.value.to_utf8]
when ::REXML::Comment
[:COMMENT, node.string]
when ::REXML::DocType
[:DOCTYPE, node.name, node.public, node.system]
when ::REXML::XMLDecl
[nil]
else
[:UNKNOWN, node.class.inspect]
end end
end
def first_child(node) html = new_text
node.children.first
end
def next_sibling(node)
node.next_sibling
end
def parent(node)
node.parent
end end
html
end end
end
end def sanitize_css(style)
end # disallow urls
style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ')
# gauntlet
if style !~ /^([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*$/
style = ''
return style
end
if style !~ /^(\s*[-\w]+\s*:\s*[^:;]*(;|$))*$/
style = ''
return style
end
clean = []
style.scan(/([-\w]+)\s*:\s*([^:;]*)/) do |prop,val|
if ALLOWED_CSS_PROPERTIES.include?(prop.downcase)
clean << prop + ': ' + val + ';'
elsif ['background','border','margin','padding'].include?(prop.split('-')[0].downcase)
goodval = true
val.split().each do |keyword|
if !ALLOWED_CSS_KEYWORDS.include?(keyword) and
keyword !~ /^(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$/
goodval = false
end
end
if goodval
clean << prop + ': ' + val + ';'
end
elsif ALLOWED_SVG_PROPERTIES.include?(prop.downcase)
clean << prop + ': ' + val + ';'
end
end
style = clean.join(' ')
end
end

View file

@ -1,189 +1,187 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
require File.expand_path(File.dirname(__FILE__) + '/../test_helper') require File.expand_path(File.join(File.dirname(__FILE__), '/../test_helper'))
require 'sanitize' require 'sanitize'
require 'json'
class SanitizeTest < Test::Unit::TestCase class SanitizeTest < Test::Unit::TestCase
include Sanitize include Sanitize
def setup def setup
end end
def do_sanitize_xhtml stream Sanitize::ALLOWED_ELEMENTS.each do |tag_name|
safe_sanitize_xhtml(stream)
end
def check_sanitization(input, htmloutput, xhtmloutput, rexmloutput)
assert_equal htmloutput, do_sanitize_xhtml(input)
end
def rexml_doc(string)
REXML::Document.new(
"<div xmlns='http://www.w3.org/1999/xhtml'>#{string}</div>")
end
def my_rex(string)
sanitize_rexml(rexml_doc(string.to_utf8)).gsub(/\A<div xmlns="http:\/\/www.w3.org\/1999\/xhtml">(.*)<\/div>\Z/m, '\1')
end
def test_sanitize_named_entities
input = '<p>Greek &phis; &phi;, double-struck &Aopf;, numeric &#x1D538; &#8279;, uppercase &TRADE; &LT;</p>'
output = "<p>Greek \317\225 \317\206, double-struck \360\235\224\270, numeric \360\235\224\270 \342\201\227, uppercase \342\204\242 &lt;</p>"
output2 = "<p>Greek \317\225 \317\206, double-struck \360\235\224\270, numeric &#x1D538; &#8279;, uppercase \342\204\242 &lt;</p>"
assert_equal(output, sanitize_xhtml(input))
assert_equal(output, sanitize_html(input))
assert_equal(output, my_rex(input))
assert_equal(output2, input.to_utf8)
end
def test_sanitize_malformed_utf8
input = "<p>\357elephant &AMP; \302ivory</p>"
output = "<p>\357\277\275elephant &amp; \357\277\275ivory</p>"
check_sanitization(input, output, output, output)
end
Sanitizer::ALLOWED_ELEMENTS.each do |tag_name|
define_method "test_should_allow_#{tag_name}_tag" do define_method "test_should_allow_#{tag_name}_tag" do
input = "<#{tag_name} title='1'>foo <bad>bar</bad> baz</#{tag_name}>" assert_equal "<#{tag_name} title=\"1\">foo &lt;bad>bar&lt;/bad> baz</#{tag_name}>",
htmloutput = "<#{tag_name.downcase} title='1'>foo &lt;bad&gt;bar&lt;/bad&gt; baz</#{tag_name.downcase}>" sanitize_html("<#{tag_name} title='1'>foo <bad>bar</bad> baz</#{tag_name}>")
xhtmloutput = "<#{tag_name} title='1'>foo &lt;bad&gt;bar&lt;/bad&gt; baz</#{tag_name}>"
rexmloutput = xhtmloutput
if %w[caption colgroup optgroup option tbody td tfoot th thead tr].include?(tag_name)
htmloutput = "foo &lt;bad&gt;bar&lt;/bad&gt; baz"
xhtmloutput = htmloutput
elsif tag_name == 'col'
htmloutput = "foo &lt;bad&gt;bar&lt;/bad&gt; baz"
xhtmloutput = htmloutput
rexmloutput = "<col title='1' />"
elsif tag_name == 'table'
htmloutput = "foo &lt;bad&gt;bar&lt;/bad&gt;baz<table title='1'> </table>"
xhtmloutput = htmloutput
elsif tag_name == 'image'
htmloutput = "<img title='1'/>foo &lt;bad&gt;bar&lt;/bad&gt; baz"
xhtmloutput = htmloutput
rexmloutput = "<image title='1'>foo &lt;bad&gt;bar&lt;/bad&gt; baz</image>"
elsif VOID_ELEMENTS.include?(tag_name)
htmloutput = "<#{tag_name} title='1'/>foo &lt;bad&gt;bar&lt;/bad&gt; baz"
xhtmloutput = htmloutput
htmloutput += '<br/>' if tag_name == 'br'
rexmloutput = "<#{tag_name} title='1' />"
end
check_sanitization(input, xhtmloutput, xhtmloutput, rexmloutput)
end end
end end
Sanitizer::ALLOWED_ELEMENTS.each do |tag_name| Sanitize::ALLOWED_ELEMENTS.each do |tag_name|
define_method "test_should_forbid_#{tag_name.upcase}_tag" do define_method "test_should_forbid_#{tag_name.upcase}_tag" do
input = "<#{tag_name.upcase} title='1'>foo <bad>bar</bad> baz</#{tag_name.upcase}>" assert_equal "&lt;#{tag_name.upcase} title=\"1\">foo &lt;bad>bar&lt;/bad> baz&lt;/#{tag_name.upcase}>",
output = "&lt;#{tag_name.upcase} title=\"1\"&gt;foo &lt;bad&gt;bar&lt;/bad&gt; baz&lt;/#{tag_name.upcase}&gt;" sanitize_html("<#{tag_name.upcase} title='1'>foo <bad>bar</bad> baz</#{tag_name.upcase}>")
xhtmloutput = "&lt;#{tag_name.upcase} title='1'&gt;foo &lt;bad&gt;bar&lt;/bad&gt; baz&lt;/#{tag_name.upcase}&gt;"
check_sanitization(input, output, xhtmloutput, output)
end end
end end
Sanitizer::ALLOWED_ATTRIBUTES.each do |attribute_name| Sanitize::ALLOWED_ATTRIBUTES.each do |attribute_name|
next if attribute_name == 'style' || attribute_name.include?(':') if attribute_name != 'style'
define_method "test_should_allow_#{attribute_name}_attribute" do define_method "test_should_allow_#{attribute_name}_attribute" do
input = "<p #{attribute_name}='foo'>foo <bad>bar</bad> baz</p>" assert_equal "<p #{attribute_name}=\"foo\">foo &lt;bad>bar&lt;/bad> baz</p>",
output = "<p #{attribute_name}='foo'>foo &lt;bad&gt;bar&lt;/bad&gt; baz</p>" sanitize_html("<p #{attribute_name}='foo'>foo <bad>bar</bad> baz</p>")
htmloutput = "<p #{attribute_name.downcase}='foo'>foo &lt;bad&gt;bar&lt;/bad&gt; baz</p>" end
check_sanitization(input, output, output, output)
end end
end end
Sanitizer::ALLOWED_ATTRIBUTES.each do |attribute_name| Sanitize::ALLOWED_ATTRIBUTES.each do |attribute_name|
define_method "test_should_forbid_#{attribute_name.upcase}_attribute" do define_method "test_should_forbid_#{attribute_name.upcase}_attribute" do
input = "<p #{attribute_name.upcase}='display: none;'>foo <bad>bar</bad> baz</p>" assert_equal "<p>foo &lt;bad>bar&lt;/bad> baz</p>",
output = "<p>foo &lt;bad&gt;bar&lt;/bad&gt; baz</p>" sanitize_html("<p #{attribute_name.upcase}='display: none;'>foo <bad>bar</bad> baz</p>")
check_sanitization(input, output, output, output)
end end
end end
Sanitizer::ALLOWED_PROTOCOLS.each do |protocol| Sanitize::ALLOWED_PROTOCOLS.each do |protocol|
define_method "test_should_allow_#{protocol}_uris" do define_method "test_should_allow_#{protocol}_uris" do
input = %(<a href="#{protocol}">foo</a>) assert_equal "<a href=\"#{protocol}\">foo</a>",
output = "<a href='#{protocol}'>foo</a>" sanitize_html(%(<a href="#{protocol}">foo</a>))
check_sanitization(input, output, output, output)
end end
end end
Sanitizer::ALLOWED_PROTOCOLS.each do |protocol| Sanitize::ALLOWED_PROTOCOLS.each do |protocol|
define_method "test_should_allow_uppercase_#{protocol}_uris" do define_method "test_should_allow_uppercase_#{protocol}_uris" do
input = %(<a href="#{protocol.upcase}">foo</a>) assert_equal "<a href=\"#{protocol.upcase}\">foo</a>",
output = "<a href='#{protocol.upcase}'>foo</a>" sanitize_html(%(<a href="#{protocol.upcase}">foo</a>))
check_sanitization(input, output, output, output)
end end
end end
Sanitizer::SVG_ALLOW_LOCAL_HREF.each do |tag_name| def test_should_allow_anchors
next unless Sanitizer::ALLOWED_ELEMENTS.include?(tag_name) assert_equal "<a href=\"foo\">&lt;script>baz&lt;/script></a>",
define_method "test_#{tag_name}_should_allow_local_href_with_ns_decl" do sanitize_html("<a href='foo' onclick='bar'><script>baz</script></a>")
input = %(<#{tag_name} xlink:href="#foo" xmlns:xlink='http://www.w3.org/1999/xlink'/>) end
output = "<#{tag_name.downcase} xlink:href='#foo' xmlns:xlink='http://www.w3.org/1999/xlink'/>"
xhtmloutput = "<#{tag_name} xlink:href='#foo' xmlns:xlink='http://www.w3.org/1999/xlink'/>"
check_sanitization(input, xhtmloutput, xhtmloutput, xhtmloutput)
end
define_method "test_#{tag_name}_should_allow_local_href_with_newline_and_ns_decl" do # RFC 3986, sec 4.2
input = %(<#{tag_name} xlink:href="\n#foo" xmlns:xlink='http://www.w3.org/1999/xlink'/>) def test_allow_colons_in_path_component
output = "<#{tag_name.downcase} xlink:href='\n#foo' xmlns:xlink='http://www.w3.org/1999/xlink'/>" assert_equal "<a href=\"./this:that\">foo</a>",
xhtmloutput = "<#{tag_name} xlink:href='\n#foo' xmlns:xlink='http://www.w3.org/1999/xlink'/>" sanitize_html("<a href=\"./this:that\">foo</a>")
check_sanitization(input, xhtmloutput, xhtmloutput, xhtmloutput) end
end
define_method "test_#{tag_name}_should_forbid_local_href_without_ns_decl" do %w(src width height alt).each do |img_attr|
input = %(<#{tag_name} xlink:href="#foo"/>) define_method "test_should_allow_image_#{img_attr}_attribute" do
output = "&lt;#{tag_name.downcase} xlink:href='#foo'/>" assert_equal "<img #{img_attr}=\"foo\" />",
xhtmloutput = "&lt;#{tag_name} xlink:href=&#39;#foo&#39;&gt;&lt;/#{tag_name}&gt;" sanitize_html("<img #{img_attr}='foo' onclick='bar' />")
check_sanitization(input, xhtmloutput, xhtmloutput, xhtmloutput)
end
define_method "test_#{tag_name}_should_forbid_local_href_with_newline_without_ns_decl" do
input = %(<#{tag_name} xlink:href="\n#foo"/>)
output = "&lt;#{tag_name.downcase} xlink:href='\n#foo'/>"
xhtmloutput = "&lt;#{tag_name} xlink:href=&#39;\n#foo&#39;&gt;&lt;/#{tag_name}&gt;"
check_sanitization(input, xhtmloutput, xhtmloutput, xhtmloutput)
end
define_method "test_#{tag_name}_should_forbid_nonlocal_href_with_ns_decl" do
input = %(<#{tag_name} xlink:href="http://bad.com/foo" xmlns:xlink='http://www.w3.org/1999/xlink'/>)
output = "<#{tag_name.downcase} xmlns:xlink='http://www.w3.org/1999/xlink'/>"
xhtmloutput = "<#{tag_name} xmlns:xlink='http://www.w3.org/1999/xlink'/>"
check_sanitization(input, xhtmloutput, xhtmloutput, xhtmloutput)
end
define_method "test_#{tag_name}_should_forbid_nonlocal_href_with_newline_and_ns_decl" do
input = %(<#{tag_name} xlink:href="\nhttp://bad.com/foo" xmlns:xlink='http://www.w3.org/1999/xlink'/>)
output = "<#{tag_name.downcase} xmlns:xlink='http://www.w3.org/1999/xlink'/>"
xhtmloutput = "<#{tag_name} xmlns:xlink='http://www.w3.org/1999/xlink'/>"
check_sanitization(input, xhtmloutput, xhtmloutput, xhtmloutput)
end end
end end
def test_should_handle_astral_plane_characters def test_should_handle_non_html
input = "<p>&#x1d4b5; &#x1d538;</p>" assert_equal 'abc', sanitize_html("abc")
output = "<p>\360\235\222\265 \360\235\224\270</p>" end
check_sanitization(input, output, output, output)
input = "<p><tspan>\360\235\224\270</tspan> a</p>" def test_should_handle_blank_text
output = "<p><tspan>\360\235\224\270</tspan> a</p>" assert_equal '', sanitize_html('')
check_sanitization(input, output, output, output) end
[%w(img src), %w(a href)].each do |(tag, attr)|
define_method "test_should_strip_#{attr}_attribute_in_#{tag}_with_bad_protocols" do
assert_equal %(<#{tag} title="1">boo</#{tag}>), sanitize_html(%(<#{tag} #{attr}="javascript:XSS" title="1">boo</#{tag}>))
end
end
[%w(img src), %w(a href)].each do |(tag, attr)|
define_method "test_should_strip_#{attr}_attribute_in_#{tag}_with_bad_protocols_and_whitespace" do
assert_equal %(<#{tag} title="1">boo</#{tag}>), sanitize_html(%(<#{tag} #{attr}=" javascript:XSS" title="1">boo</#{tag}>))
end
end
[%(<img src="javascript:alert('XSS');" />),
%(<img src=javascript:alert('XSS') />),
%(<img src="JaVaScRiPt:alert('XSS')" />),
%(<img src='javascript:alert(&quot;XSS&quot;)' />),
%(<img src='javascript:alert(String.fromCharCode(88,83,83))' />),
%(<img src='&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;' />),
%(<img src='&#0000106;&#0000097;&#0000118;&#0000097;&#0000115;&#0000099;&#0000114;&#0000105;&#0000112;&#0000116;&#0000058;&#0000097;&#0000108;&#0000101;&#0000114;&#0000116;&#0000040;&#0000039;&#0000088;&#0000083;&#0000083;&#0000039;&#0000041' />),
%(<img src='&#x6A;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3A;&#x61;&#x6C;&#x65;&#x72;&#x74;&#x28;&#x27;&#x58;&#x53;&#x53;&#x27;&#x29' />),
%(<img src="jav\tascript:alert('XSS');" />),
%(<img src="jav&#x09;ascript:alert('XSS');" />),
%(<img src="jav&#x0A;ascript:alert('XSS');" />),
%(<img src="jav&#x0D;ascript:alert('XSS');" />),
%(<img src=" &#14; javascript:alert('XSS');" />),
%(<img src="&#x20;javascript:alert('XSS');" />),
%(<img src="&#xA0;javascript:alert('XSS');" />)].each_with_index do |img_hack, i|
define_method "test_should_not_fall_for_xss_image_hack_#{i}" do
assert_equal "<img />", sanitize_html(img_hack)
end
end
def test_should_sanitize_tag_broken_up_by_null
assert_equal "&lt;scr>alert(\"XSS\")&lt;/scr>", sanitize_html(%(<scr\0ipt>alert(\"XSS\")</scr\0ipt>))
end end
JSON::parse(open(File.expand_path(File.join(File.dirname(__FILE__), '/../sanitizer.dat'))).read).each do |test| def test_should_sanitize_invalid_script_tag
define_method "test_#{test['name']}" do assert_equal "&lt;script />&lt;/script>", sanitize_html(%(<script/XSS SRC="http://ha.ckers.org/xss.js"></script>))
check_sanitization( end
test['input'],
test['output'], def test_should_sanitize_script_tag_with_multiple_open_brackets
test['xhtml'] || test['output'], assert_equal "&lt;&lt;script>alert(\"XSS\");//&lt;&lt;/script>", sanitize_html(%(<<script>alert("XSS");//<</script>))
test['rexml'] || test['output'] assert_equal %(&lt;iframe src="http:" />&lt;), sanitize_html(%(<iframe src=http://ha.ckers.org/scriptlet.html\n<))
) end
end
end def test_should_sanitize_unclosed_script
assert_equal "&lt;script src=\"http:\" /><b>", sanitize_html(%(<script src=http://ha.ckers.org/xss.js?<b>))
end
def test_should_sanitize_half_open_scripts
assert_equal "<img>", sanitize_html(%(<img src="javascript:alert('XSS')"))
end
def test_should_not_fall_for_ridiculous_hack
img_hack = %(<img\nsrc\n=\n"\nj\na\nv\na\ns\nc\nr\ni\np\nt\n:\na\nl\ne\nr\nt\n(\n'\nX\nS\nS\n'\n)\n"\n />)
assert_equal "<img />", sanitize_html(img_hack)
end
def test_platypus
assert_equal %(<a href=\"http://www.ragingplatypus.com/\" style=\"display: block; width: 100%; height: 100%; background-color: black; background-image: ; background-x: center; background-y: center;\">never trust your upstream platypus</a>),
sanitize_html(%(<a href="http://www.ragingplatypus.com/" style="display:block; position:absolute; left:0; top:0; width:100%; height:100%; z-index:1; background-color:black; background-image:url(http://www.ragingplatypus.com/i/cam-full.jpg); background-x:center; background-y:center; background-repeat:repeat;">never trust your upstream platypus</a>))
end
def test_xul
assert_equal %(<p style="">fubar</p>),
sanitize_html(%(<p style="-moz-binding:url('http://ha.ckers.org/xssmoz.xml#xss')">fubar</p>))
end
def test_input_image
assert_equal %(<input type="image" />),
sanitize_html(%(<input type="image" src="javascript:alert('XSS');" />))
end
def test_non_alpha_non_digit
assert_equal "&lt;script />&lt;/script>",
sanitize_html(%(<script/XSS src="http://ha.ckers.org/xss.js"></script>))
assert_equal "<a>foo</a>",
sanitize_html('<a onclick!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>foo</a>')
assert_equal "<img />",
sanitize_html('<img/src="http://ha.ckers.org/xss.js"/>')
end
def test_img_dynsrc_lowsrc
assert_equal "<img />",
sanitize_html(%(<img dynsrc="javascript:alert('XSS')" />))
assert_equal "<img />",
sanitize_html(%(<img lowsrc="javascript:alert('XSS')" />))
end
def test_div_background_image_unicode_encoded
assert_equal '<div style="">foo</div>',
sanitize_html(%(<div style="background-image:\0075\0072\006C\0028'\006a\0061\0076\0061\0073\0063\0072\0069\0070\0074\003a\0061\006c\0065\0072\0074\0028.1027\0058.1053\0053\0027\0029'\0029">foo</div>))
end
def test_div_expression
assert_equal '<div style="">foo</div>',
sanitize_html(%(<div style="width: expression(alert('XSS'));">foo</div>))
end
def test_img_vbscript
assert_equal '<img />',
sanitize_html(%(<img src='vbscript:msgbox("XSS")' />))
end
end end

21
bundle
View file

@ -1,21 +0,0 @@
#!/usr/bin/env ruby
#
# This file was generated by RubyGems.
#
# The application 'bundler' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'rubygems'
vend = File.join(File.dirname(__FILE__), 'vendor')
Gem.use_paths File.join(vend, 'bundle', File.basename(Gem.dir)), (Gem.path + [File.join(vend, 'plugins', 'bundler')])
version = ">= 0"
if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
version = $1
ARGV.shift
end
gem 'bundler', version
load Gem.bin_path('bundler', 'bundle', version)

0
cache/.gitignore vendored
View file

View file

View file

@ -1,7 +1,7 @@
# Don't change this file! # Don't change this file!
# Configure your app in config/environment.rb and config/environments/*.rb # Configure your app in config/environment.rb and config/environments/*.rb
RAILS_ROOT = File.join(File.dirname(__FILE__), '..') unless defined?(RAILS_ROOT) RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
module Rails module Rails
class << self class << self
@ -21,7 +21,7 @@ module Rails
end end
def vendor_rails? def vendor_rails?
File.exist?(File.join(RAILS_ROOT, 'vendor', 'rails')) File.exist?("#{RAILS_ROOT}/vendor/rails")
end end
def preinitialize def preinitialize
@ -29,7 +29,7 @@ module Rails
end end
def preinitializer_path def preinitializer_path
File.join(RAILS_ROOT, 'config', 'preinitializer.rb') "#{RAILS_ROOT}/config/preinitializer.rb"
end end
end end
@ -42,9 +42,8 @@ module Rails
class VendorBoot < Boot class VendorBoot < Boot
def load_initializer def load_initializer
require File.join(RAILS_ROOT, 'vendor', 'rails', 'railties', 'lib', 'initializer') require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
Rails::Initializer.run(:install_gem_spec_stubs) Rails::Initializer.run(:install_gem_spec_stubs)
Rails::GemDependency.add_frozen_gem_path
end end
end end
@ -68,7 +67,7 @@ module Rails
class << self class << self
def rubygems_version def rubygems_version
Gem::RubyGemsVersion rescue nil Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion
end end
def gem_version def gem_version
@ -82,15 +81,15 @@ module Rails
end end
def load_rubygems def load_rubygems
min_version = '1.3.6'
require 'rubygems' require 'rubygems'
unless rubygems_version >= min_version
$stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) unless rubygems_version >= '0.9.4'
$stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.)
exit 1 exit 1
end end
rescue LoadError rescue LoadError
$stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) $stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org)
exit 1 exit 1
end end
@ -100,25 +99,11 @@ module Rails
private private
def read_environment_rb def read_environment_rb
File.read(File.join(RAILS_ROOT, 'config', 'environment.rb')) File.read("#{RAILS_ROOT}/config/environment.rb")
end end
end end
end end
end end
class Rails::Boot
def run
load_initializer
Rails::Initializer.class_eval do
def load_gems
@bundler_loaded ||= Bundler.require :default, Rails.env
end
end
Rails::Initializer.run(:set_load_path)
end
end
# All that for this: # All that for this:
Rails.boot! Rails.boot!

View file

@ -3,9 +3,9 @@
#### ####
# Make sure we are using the latest rexml # Make sure we are using the latest rexml
rexml_versions = ['', File.join(File.dirname(__FILE__), '..', 'vendor', 'plugins', 'rexml', 'lib', '')].collect { |v| rexml_versions = ['', File.dirname(__FILE__) + '/../vendor/plugins/rexml/lib/'].collect { |v|
`ruby -r "#{v + 'rexml/rexml'}" -e 'p REXML::VERSION'`.split('.').collect {|n| n.to_i} } `ruby -r #{v + 'rexml/rexml'} -e 'p REXML::VERSION'`.split('.').collect {|n| n.to_i} }
$:.unshift(File.join(File.dirname(__FILE__), '..', 'vendor', 'plugins', 'rexml', 'lib')) if (rexml_versions[0] <=> rexml_versions[1]) == -1 $:.unshift(File.dirname(__FILE__) + '/../vendor/plugins/rexml/lib') if (rexml_versions[0] <=> rexml_versions[1]) == -1
require File.join(File.dirname(__FILE__), 'boot') require File.join(File.dirname(__FILE__), 'boot')
@ -18,15 +18,15 @@ Rails::Initializer.run do |config|
# in a file, for reuse between server restarts. If you want to # in a file, for reuse between server restarts. If you want to
# change the key, just delete the file, and it will be regenerated # change the key, just delete the file, and it will be regenerated
# on the next restart. Doing so will invalitate all existing sessions. # on the next restart. Doing so will invalitate all existing sessions.
secret_file = Rails.root.join("secret") secret_file = File.join(RAILS_ROOT, "secret")
if File.exist?(secret_file) if File.exist?(secret_file)
secret = secret_file.read secret = File.read(secret_file)
else else
secret = ActiveSupport::SecureRandom.hex(64) secret = ActiveSupport::SecureRandom.hex(64)
File.open(secret_file, 'w', 0600) { |f| f.write(secret) } File.open(secret_file, 'w', 0600) { |f| f.write(secret) }
end end
config.action_controller.session = { config.action_controller.session = {
:key => "instiki_session", :session_key => "instiki_session",
:secret => secret :secret => secret
} }
@ -58,11 +58,7 @@ end
require_dependency 'instiki_errors' require_dependency 'instiki_errors'
#require 'jcode' #require 'jcode'
# Miscellaneous monkey patches (here be dragons ...)
require 'caching_stuff' require 'caching_stuff'
require 'logging_stuff'
require 'rack_stuff'
#Additional Mime-types #Additional Mime-types
mime_types = YAML.load_file(File.join(File.dirname(__FILE__), 'mime_types.yml')) mime_types = YAML.load_file(File.join(File.dirname(__FILE__), 'mime_types.yml'))

View file

@ -8,7 +8,7 @@ config.cache_classes = true
#### ####
# This rotates the log file, keeping 25 files, of 1MB each. # This rotates the log file, keeping 25 files, of 1MB each.
config.action_controller.logger = Logger.new(Rails.root.join('log', "#{RAILS_ENV}.log"), 25, 1024000) config.action_controller.logger = Logger.new("#{RAILS_ROOT}/log/#{RAILS_ENV}.log", 25, 1024000)
# Unfortunately, the above does not work well under Mongrel, as the default Ruby logger class # Unfortunately, the above does not work well under Mongrel, as the default Ruby logger class
# does no locking and you will have several processes running, each wanting to write to (and # does no locking and you will have several processes running, each wanting to write to (and

View file

@ -1,23 +0,0 @@
begin
require "rubygems"
vend = File.join(File.dirname(__FILE__), '..', 'vendor')
Gem.use_paths File.join(vend, 'bundle', File.basename(Gem.dir)), (Gem.path + [File.join(vend, 'plugins', 'bundler')])
require "bundler"
rescue LoadError
raise "Could not load the bundler gem. Install it with `gem install bundler`."
end
if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.24")
raise RuntimeError, "Your bundler version is too old for Rails 2.3." +
"Run `gem install bundler` to upgrade."
end
begin
# Set up load paths for all bundled gems
ENV["BUNDLE_GEMFILE"] = File.join(File.dirname(File.dirname(__FILE__)), 'Gemfile')
Bundler.setup
rescue Bundler::GemNotFound
raise RuntimeError, "Bundler couldn't find some gems." +
"Did you run `bundle install`?"
end

View file

@ -8,9 +8,6 @@ def connect_to_web(map, generic_path, generic_routing_options)
map.connect(generic_path, generic_routing_options) map.connect(generic_path, generic_routing_options)
end end
# :id's can be arbitrary junk
id_regexp = /.+/
ActionController::Routing::Routes.draw do |map| ActionController::Routing::Routes.draw do |map|
map.connect 'create_system', :controller => 'admin', :action => 'create_system' map.connect 'create_system', :controller => 'admin', :action => 'create_system'
map.connect 'create_web', :controller => 'admin', :action => 'create_web' map.connect 'create_web', :controller => 'admin', :action => 'create_web'
@ -28,14 +25,12 @@ ActionController::Routing::Routes.draw do |map|
connect_to_web map, ':web/import/:id', :controller => 'file', :action => 'import' connect_to_web map, ':web/import/:id', :controller => 'file', :action => 'import'
connect_to_web map, ':web/login', :controller => 'wiki', :action => 'login' connect_to_web map, ':web/login', :controller => 'wiki', :action => 'login'
connect_to_web map, ':web/web_list', :controller => 'wiki', :action => 'web_list' connect_to_web map, ':web/web_list', :controller => 'wiki', :action => 'web_list'
connect_to_web map, ':web/show/diff/:id', :controller => 'wiki', :action => 'show', :mode => 'diff', :requirements => {:id => id_regexp} connect_to_web map, ':web/show/diff/:id', :controller => 'wiki', :action => 'show', :mode => 'diff'
connect_to_web map, ':web/revision/diff/:id/:rev', :controller => 'wiki', :action => 'revision', :mode => 'diff', connect_to_web map, ':web/revision/diff/:id/:rev', :controller => 'wiki', :action => 'revision', :mode => 'diff', :requirements => { :rev => /\d*/}
:requirements => { :rev => /\d+/, :id => id_regexp} connect_to_web map, ':web/revision/:id/:rev', :controller => 'wiki', :action => 'revision', :requirements => { :rev => /\d*/}
connect_to_web map, ':web/revision/:id/:rev', :controller => 'wiki', :action => 'revision', :requirements => { :rev => /\d+/, :id => id_regexp}
connect_to_web map, ':web/source/:id/:rev', :controller => 'wiki', :action => 'source', :requirements => { :rev => /\d+/, :id => id_regexp}
connect_to_web map, ':web/list/:category', :controller => 'wiki', :action => 'list', :requirements => { :category => /.*/}, :category => nil connect_to_web map, ':web/list/:category', :controller => 'wiki', :action => 'list', :requirements => { :category => /.*/}, :category => nil
connect_to_web map, ':web/recently_revised/:category', :controller => 'wiki', :action => 'recently_revised', :requirements => { :category => /.*/}, :category => nil connect_to_web map, ':web/recently_revised/:category', :controller => 'wiki', :action => 'recently_revised', :requirements => { :category => /.*/}, :category => nil
connect_to_web map, ':web/:action/:id', :controller => 'wiki', :requirements => {:id => id_regexp} connect_to_web map, ':web/:action/:id', :controller => 'wiki'
connect_to_web map, ':web/:action', :controller => 'wiki' connect_to_web map, ':web/:action', :controller => 'wiki'
connect_to_web map, ':web', :controller => 'wiki', :action => 'index' connect_to_web map, ':web', :controller => 'wiki', :action => 'index'

View file

@ -1,3 +0,0 @@
require "rubygems"
vend = File.join(File.dirname(__FILE__), '..', 'vendor')
Gem.use_paths File.join(vend, 'bundle', File.basename(Gem.dir)), (Gem.path + [File.join(vend, 'plugins', 'bundler')])

View file

@ -1,13 +0,0 @@
class ModifyTextTypes < ActiveRecord::Migration
def self.up
unless adapter_name.eql?('PostgreSQL')
change_column :revisions, :content, :text, :limit => 16777215
end
change_column :pages, :name, :string, :limit => 255
change_column :webs, :additional_style, :text
end
def self.down
raise ActiveRecord::IrreversibleMigration
end
end

View file

@ -1,9 +0,0 @@
class ModifyReferencedNameType < ActiveRecord::Migration
def self.up
change_column :wiki_references, :referenced_name, :string, :limit => 255
end
def self.down
raise ActiveRecord::IrreversibleMigration
end
end

Binary file not shown.

View file

@ -1,33 +1,26 @@
# This file is auto-generated from the current state of the database. Instead of editing this file, # This file is autogenerated. Instead of editing this file, please use the
# please use the migrations feature of Active Record to incrementally modify your database, and # migrations feature of ActiveRecord to incrementally modify your database, and
# then regenerate this schema definition. # then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your database schema. If you need
# to create the application database on another system, you should be using db:schema:load, not running
# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20100101192755) do ActiveRecord::Schema.define(:version => 2) do
create_table "pages", :force => true do |t| create_table "pages", :force => true do |t|
t.datetime "created_at", :null => false t.column "created_at", :datetime, :null => false
t.datetime "updated_at", :null => false t.column "updated_at", :datetime, :null => false
t.integer "web_id", :default => 0, :null => false t.column "web_id", :integer, :default => 0, :null => false
t.string "locked_by", :limit => 60 t.column "locked_by", :string, :limit => 60
t.string "name" t.column "name", :string, :limit => 60
t.datetime "locked_at" t.column "locked_at", :datetime
end end
create_table "revisions", :force => true do |t| create_table "revisions", :force => true do |t|
t.datetime "created_at", :null => false t.column "created_at", :datetime, :null => false
t.datetime "updated_at", :null => false t.column "updated_at", :datetime, :null => false
t.datetime "revised_at", :null => false t.column "revised_at", :datetime, :null => false
t.integer "page_id", :default => 0, :null => false t.column "page_id", :integer, :default => 0, :null => false
t.text "content", :limit => 16777215, :default => "", :null => false t.column "content", :text, :default => "", :null => false
t.string "author", :limit => 60 t.column "author", :string, :limit => 60
t.string "ip", :limit => 60 t.column "ip", :string, :limit => 60
end end
add_index "revisions", ["author"], :name => "index_revisions_on_author" add_index "revisions", ["author"], :name => "index_revisions_on_author"
@ -35,51 +28,51 @@ ActiveRecord::Schema.define(:version => 20100101192755) do
add_index "revisions", ["page_id"], :name => "index_revisions_on_page_id" add_index "revisions", ["page_id"], :name => "index_revisions_on_page_id"
create_table "sessions", :force => true do |t| create_table "sessions", :force => true do |t|
t.string "session_id" t.column "session_id", :string
t.text "data" t.column "data", :text
t.datetime "updated_at" t.column "updated_at", :datetime
end end
add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id" add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id"
create_table "system", :force => true do |t| create_table "system", :force => true do |t|
t.string "password", :limit => 60 t.column "password", :string, :limit => 60
end end
create_table "webs", :force => true do |t| create_table "webs", :force => true do |t|
t.datetime "created_at", :null => false t.column "created_at", :datetime, :null => false
t.datetime "updated_at", :null => false t.column "updated_at", :datetime, :null => false
t.string "name", :limit => 60, :default => "", :null => false t.column "name", :string, :limit => 60, :default => "", :null => false
t.string "address", :limit => 60, :default => "", :null => false t.column "address", :string, :limit => 60, :default => "", :null => false
t.string "password", :limit => 60 t.column "password", :string, :limit => 60
t.text "additional_style", :limit => 255 t.column "additional_style", :string
t.integer "allow_uploads", :default => 1 t.column "allow_uploads", :integer, :default => 1
t.integer "published", :default => 0 t.column "published", :integer, :default => 0
t.integer "count_pages", :default => 0 t.column "count_pages", :integer, :default => 0
t.string "markup", :limit => 50, :default => "markdownMML" t.column "markup", :string, :limit => 50, :default => "markdownMML"
t.string "color", :limit => 6, :default => "008B26" t.column "color", :string, :limit => 6, :default => "008B26"
t.integer "max_upload_size", :default => 100 t.column "max_upload_size", :integer, :default => 100
t.integer "safe_mode", :default => 0 t.column "safe_mode", :integer, :default => 0
t.integer "brackets_only", :default => 0 t.column "brackets_only", :integer, :default => 0
end end
create_table "wiki_files", :force => true do |t| create_table "wiki_files", :force => true do |t|
t.datetime "created_at", :null => false t.column "created_at", :datetime, :null => false
t.datetime "updated_at", :null => false t.column "updated_at", :datetime, :null => false
t.integer "web_id", :null => false t.column "web_id", :integer, :null => false
t.string "file_name", :null => false t.column "file_name", :string, :null => false
t.string "description", :null => false t.column "description", :string, :null => false
end end
create_table "wiki_references", :force => true do |t| create_table "wiki_references", :force => true do |t|
t.datetime "created_at", :null => false t.column "created_at", :datetime, :null => false
t.datetime "updated_at", :null => false t.column "updated_at", :datetime, :null => false
t.integer "page_id", :default => 0, :null => false t.column "page_id", :integer, :default => 0, :null => false
t.string "referenced_name", :default => "", :null => false t.column "referenced_name", :string, :limit => 60, :default => "", :null => false
t.string "link_type", :limit => 1, :default => "", :null => false t.column "link_type", :string, :limit => 1, :default => "", :null => false
end end
add_index "wiki_references", ["page_id"], :name => "index_wiki_references_on_page_id"
add_index "wiki_references", ["referenced_name"], :name => "index_wiki_references_on_referenced_name" add_index "wiki_references", ["referenced_name"], :name => "index_wiki_references_on_referenced_name"
add_index "wiki_references", ["page_id"], :name => "index_wiki_references_on_page_id"
end end

View file

View file

@ -8,7 +8,7 @@ module CachingStuff
module Caching module Caching
module Fragments module Fragments
def fragment_cache_key(key) def fragment_cache_key(key)
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key.merge(:host=>"")).split(":///").last : key.split('/')[1..-1].join('/').gsub(/\?format=.*/,''), :views) ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key.merge(:host=>"")).split(":///").last : key.split('/')[1..-1].join('/'), :views)
end end
end end
end end

View file

@ -1,5 +1,5 @@
require 'chunks/chunk' require 'chunks/chunk'
require 'instiki_stringsupport' require 'stringsupport'
# The category chunk looks for "category: news" on a line by # The category chunk looks for "category: news" on a line by
# itself and parses the terms after the ':' as categories. # itself and parses the terms after the ':' as categories.
@ -17,9 +17,8 @@ class Category < Chunk::Abstract
def initialize(match_data, content) def initialize(match_data, content)
super(match_data, content) super(match_data, content)
@content = content
@hidden = match_data[1] @hidden = match_data[1]
@list = match_data[2].split(',').map { |c| clean = c.purify.strip.escapeHTML; clean if clean != ''} @list = match_data[2].split(',').map { |c| c.to_s.is_utf8? ? c.strip.escapeHTML : nil }
@list.compact! @list.compact!
@unmask_text = '' @unmask_text = ''
if @hidden if @hidden
@ -32,7 +31,6 @@ def initialize(match_data, content)
# TODO move presentation of page metadata to controller/view # TODO move presentation of page metadata to controller/view
def url(category) def url(category)
%{<a class="category_link" href="#{@content.url_generator.url_for :web => @content.web.address, %{<a class="category_link" href="../list/#{category}">#{category}</a>}
:action => 'list', :only_path => true}/#{CGI.escape(category)}">#{category}</a>}
end end
end end

View file

@ -11,7 +11,7 @@ module Chunk
# Rails's default utf-8 support causes problems here. So, for Chunk::Abstract class, turn off # Rails's default utf-8 support causes problems here. So, for Chunk::Abstract class, turn off
# multibyte character support. # multibyte character support.
$KCODE = 'n' unless ''.respond_to?(:force_encoding) $KCODE = 'n'
# automatically construct the array of derivatives of Chunk::Abstract # automatically construct the array of derivatives of Chunk::Abstract
@derivatives = [] @derivatives = []
@ -45,16 +45,14 @@ module Chunk
# Find all the chunks of the given type in content # Find all the chunks of the given type in content
# Each time the pattern is matched, create a new # Each time the pattern is matched, create a new
# chunk for it, and replace the occurrence of the chunk # chunk for it, and replace the occurance of the chunk
# in this content with its mask. # in this content with its mask.
def self.apply_to(content) def self.apply_to(content)
text = content.to_str content.gsub!( self.pattern ) do |match|
text.gsub!( self.pattern ) do |match|
new_chunk = self.new($~, content) new_chunk = self.new($~, content)
content.add_chunk(new_chunk) content.add_chunk(new_chunk)
new_chunk.mask new_chunk.mask
end end
content.replace text
end end
# should contain only [a-z0-9] # should contain only [a-z0-9]
@ -63,7 +61,7 @@ module Chunk
end end
def unmask def unmask
@content.replace @content.sub(mask){|s| s.replace @unmask_text} @content.sub!(mask){|s| s.replace @unmask_text}
end end
def rendered? def rendered?
@ -75,7 +73,7 @@ module Chunk
end end
def revert def revert
@content.replace @content.sub(mask){|s| s.replace @text} @content.sub!(mask){|s| s.replace @text}
# unregister # unregister
@content.delete_chunk(self) @content.delete_chunk(self)
end end

View file

@ -1,12 +1,11 @@
$: << File.dirname(__FILE__) + "../../lib" $: << File.dirname(__FILE__) + "../../lib"
require_dependency 'chunks/chunk' require_dependency 'chunks/chunk'
require 'instiki_stringsupport' require 'stringsupport'
require 'maruku' require 'maruku'
require 'maruku/ext/math' require 'maruku/ext/math'
require_dependency 'rdocsupport' require_dependency 'rdocsupport'
require 'redcloth' require 'redcloth'
require 'oldredcloth'
# The markup engines are Chunks that call the one of RedCloth # The markup engines are Chunks that call the one of RedCloth
# or RDoc to convert text. This markup occurs when the chunk is required # or RDoc to convert text. This markup occurs when the chunk is required
@ -31,7 +30,6 @@ module Engines
class Textile < AbstractEngine class Textile < AbstractEngine
def mask def mask
@content.as_utf8
redcloth = RedCloth.new(@content, [:hard_breaks] + @content.options[:engine_opts]) redcloth = RedCloth.new(@content, [:hard_breaks] + @content.options[:engine_opts])
redcloth.filter_html = false redcloth.filter_html = false
redcloth.no_span_caps = false redcloth.no_span_caps = false
@ -41,16 +39,15 @@ module Engines
class Markdown < AbstractEngine class Markdown < AbstractEngine
def mask def mask
text = @content.as_utf8.to_str.delete("\r").to_utf8
# If the request is for S5, call Maruku accordingly (without math) # If the request is for S5, call Maruku accordingly (without math)
if @content.options[:mode] == :s5 if @content.options[:mode] == :s5
my_content = Maruku.new(text, my_content = Maruku.new(@content.delete("\r").to_utf8,
{:math_enabled => false, :content_only => true, {:math_enabled => false, :content_only => true,
:author => @content.options[:engine_opts][:author], :author => @content.options[:engine_opts][:author],
:title => @content.options[:engine_opts][:title]}) :title => @content.options[:engine_opts][:title]})
@content.options[:renderer].s5_theme = my_content.s5_theme @content.options[:renderer].s5_theme = my_content.s5_theme
else else
html = Maruku.new(text, {:math_enabled => false}).to_html html = Maruku.new(@content.delete("\r").to_utf8, {:math_enabled => false}).to_html
html.gsub(/\A<div class="maruku_wrapper_div">\n?(.*?)\n?<\/div>\Z/m, '\1') html.gsub(/\A<div class="maruku_wrapper_div">\n?(.*?)\n?<\/div>\Z/m, '\1')
end end
@ -59,10 +56,9 @@ module Engines
class MarkdownMML < AbstractEngine class MarkdownMML < AbstractEngine
def mask def mask
text = @content.as_utf8.to_str.delete("\r").to_utf8
# If the request is for S5, call Maruku accordingly # If the request is for S5, call Maruku accordingly
if @content.options[:mode] == :s5 if @content.options[:mode] == :s5
my_content = Maruku.new(text, my_content = Maruku.new(@content.delete("\r").to_utf8,
{:math_enabled => true, {:math_enabled => true,
:math_numbered => ['\\[','\\begin{equation}'], :math_numbered => ['\\[','\\begin{equation}'],
:content_only => true, :content_only => true,
@ -71,11 +67,9 @@ module Engines
@content.options[:renderer].s5_theme = my_content.s5_theme @content.options[:renderer].s5_theme = my_content.s5_theme
my_content.to_s5 my_content.to_s5
else else
(t = Time.now; nil) html = Maruku.new(@content.delete("\r").to_utf8,
html = Maruku.new(text,
{:math_enabled => true, {:math_enabled => true,
:math_numbered => ['\\[','\\begin{equation}']}).to_html :math_numbered => ['\\[','\\begin{equation}']}).to_html
(ApplicationController.logger.info("Maruku took " + (Time.now-t).to_s + " seconds."); nil)
html.gsub(/\A<div class="maruku_wrapper_div">\n?(.*?)\n?<\/div>\Z/m, '\1') html.gsub(/\A<div class="maruku_wrapper_div">\n?(.*?)\n?<\/div>\Z/m, '\1')
end end
end end
@ -83,31 +77,30 @@ module Engines
class MarkdownPNG < AbstractEngine class MarkdownPNG < AbstractEngine
def mask def mask
text = @content.as_utf8.to_str.delete("\r").to_utf8
# If the request is for S5, call Maruku accordingly # If the request is for S5, call Maruku accordingly
if @content.options[:mode] == :s5 if @content.options[:mode] == :s5
my_content = Maruku.new(text, my_content = Maruku.new(@content.delete("\r").to_utf8,
{:math_enabled => true, {:math_enabled => true,
:math_numbered => ['\\[','\\begin{equation}'], :math_numbered => ['\\[','\\begin{equation}'],
:html_math_output_mathml => false, :html_math_output_mathml => false,
:html_math_output_png => true, :html_math_output_png => true,
:html_png_engine => 'blahtex', :html_png_engine => 'blahtex',
:html_png_dir => @content.web.files_path.join('pngs').to_s, :html_png_dir => @content.web.files_path + '/pngs',
:html_png_url => @content.options[:png_url], :html_png_url => '../files/pngs/',
:content_only => true, :content_only => true,
:author => @content.options[:engine_opts][:author], :author => @content.options[:engine_opts][:author],
:title => @content.options[:engine_opts][:title]}) :title => @content.options[:engine_opts][:title]})
@content.options[:renderer].s5_theme = my_content.s5_theme @content.options[:renderer].s5_theme = my_content.s5_theme
my_content.to_s5 my_content.to_s5
else else
html = Maruku.new(text, html = Maruku.new(@content.delete("\r").to_utf8,
{:math_enabled => true, {:math_enabled => true,
:math_numbered => ['\\[','\\begin{equation}'], :math_numbered => ['\\[','\\begin{equation}'],
:html_math_output_mathml => false, :html_math_output_mathml => false,
:html_math_output_png => true, :html_math_output_png => true,
:html_png_engine => 'blahtex', :html_png_engine => 'blahtex',
:html_png_dir => @content.web.files_path.join('pngs').to_s, :html_png_dir => @content.web.files_path + '/pngs',
:html_png_url => @content.options[:png_url]}).to_html :html_png_url => '../files/pngs/'}).to_html
html.gsub(/\A<div class="maruku_wrapper_div">\n?(.*?)\n?<\/div>\Z/m, '\1') html.gsub(/\A<div class="maruku_wrapper_div">\n?(.*?)\n?<\/div>\Z/m, '\1')
end end
end end
@ -115,8 +108,7 @@ module Engines
class Mixed < AbstractEngine class Mixed < AbstractEngine
def mask def mask
@content.as_utf8 redcloth = RedCloth.new(@content, @content.options[:engine_opts])
redcloth = OldRedCloth.new(@content.to_str, @content.options[:engine_opts])
redcloth.filter_html = false redcloth.filter_html = false
redcloth.no_span_caps = false redcloth.no_span_caps = false
html = redcloth.to_html html = redcloth.to_html
@ -125,7 +117,7 @@ module Engines
class RDoc < AbstractEngine class RDoc < AbstractEngine
def mask def mask
html = RDocSupport::RDocFormatter.new(@content.as_utf8.to_str).to_html html = RDocSupport::RDocFormatter.new(@content).to_html
end end
end end

View file

@ -36,8 +36,7 @@ class Include < WikiChunk::WikiReference
else else
raise "Unsupported rendering mode #{@mode.inspect}" raise "Unsupported rendering mode #{@mode.inspect}"
end end
# redirects and categories of included pages should not be inherited @content.merge_chunks(included_content)
@content.merge_chunks(included_content.delete_chunks!([Redirect, Category]))
clear_include_list clear_include_list
return included_content.pre_rendered return included_content.pre_rendered
else else

View file

@ -18,7 +18,7 @@ module Literal
# A literal chunk that protects 'code' and 'pre' tags from wiki rendering. # A literal chunk that protects 'code' and 'pre' tags from wiki rendering.
class Pre < AbstractLiteral class Pre < AbstractLiteral
PRE_BLOCKS = "a|pre|code|math" PRE_BLOCKS = "a|pre|code|math"
PRE_PATTERN = Regexp.new('<('+PRE_BLOCKS+')\b[^>]*?(>.*?</\1>|/>)', Regexp::MULTILINE) PRE_PATTERN = Regexp.new('<('+PRE_BLOCKS+')\b[^>]*?>.*?</\1>', Regexp::MULTILINE)
def self.pattern() PRE_PATTERN end def self.pattern() PRE_PATTERN end
end end
@ -30,9 +30,9 @@ module Literal
# A literal chunk that protects equations from wiki rendering. # A literal chunk that protects equations from wiki rendering.
class Math < AbstractLiteral class Math < AbstractLiteral
MATH_START = "(?:\\\\\\[|\\${1,2}|\\\\begin\\{equation\\})" MATH_START = '(\${1,2}|' + Regexp.escape('\[') + '|\\begin\{equation\})'
MATH_END = "(?:\\\\\\]|\\${1,2}|\\\\end\\{equation\\})" MATH_END = '(\${1,2}|' + Regexp.escape('\]') + '|\\end\{equation\})'
MATH_PATTERN = Regexp.new( '(' + MATH_START + "(?:\\\\\\$|(?!\\$|\\\\\\]|\\\\end\\{equation\\}).)+?" + MATH_END + ')', Regexp::MULTILINE) MATH_PATTERN = Regexp.new(MATH_START + '([^$]|\\\$)+?' + MATH_END, Regexp::MULTILINE)
def self.pattern() MATH_PATTERN end def self.pattern() MATH_PATTERN end
end end

View file

@ -1,5 +1,5 @@
require 'chunks/chunk' require 'chunks/chunk'
require 'sanitizer' require 'sanitize'
# This chunks allows certain parts of a wiki page to be hidden from the # This chunks allows certain parts of a wiki page to be hidden from the
# rest of the rendering pipeline. It should be run at the beginning # rest of the rendering pipeline. It should be run at the beginning
@ -17,7 +17,7 @@ require 'sanitizer'
class NoWiki < Chunk::Abstract class NoWiki < Chunk::Abstract
include Sanitizer include Sanitize
NOWIKI_PATTERN = Regexp.new('<nowiki>(.*?)</nowiki>', Regexp::MULTILINE) NOWIKI_PATTERN = Regexp.new('<nowiki>(.*?)</nowiki>', Regexp::MULTILINE)
def self.pattern() NOWIKI_PATTERN end def self.pattern() NOWIKI_PATTERN end
@ -26,7 +26,7 @@ class NoWiki < Chunk::Abstract
def initialize(match_data, content) def initialize(match_data, content)
super super
@plain_text = @unmask_text = safe_xhtml_sanitize(match_data[1]) @plain_text = @unmask_text = safe_sanitize_xhtml(match_data[1])
end end
end end

View file

@ -1,5 +1,4 @@
require 'chunks/chunk' require 'chunks/chunk'
require 'instiki_stringsupport'
# Contains all the methods for finding and replacing wiki related links. # Contains all the methods for finding and replacing wiki related links.
module WikiChunk module WikiChunk
@ -34,8 +33,7 @@ module WikiChunk
end end
def self.apply_to(content) def self.apply_to(content)
text = content.as_utf8.to_str content.gsub!( self.pattern ) do |matched_text|
text.gsub!( self.pattern ) do |matched_text|
chunk = self.new($~, content) chunk = self.new($~, content)
if chunk.textile_url? if chunk.textile_url?
# do not substitute # do not substitute
@ -45,7 +43,6 @@ module WikiChunk
chunk.mask chunk.mask
end end
end end
content.replace text
end end
def textile_url? def textile_url?
@ -73,9 +70,7 @@ module WikiChunk
attr_reader :escaped_text attr_reader :escaped_text
unless defined? WIKI_WORD unless defined? WIKI_WORD
WIKI_WORD = ''.respond_to?(:force_encoding) ? WIKI_WORD = Regexp.new('(":)?(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, 'u')
Regexp.new('(":)?(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0) :
Regexp.new('(":)?(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, 'u')
end end
def self.pattern def self.pattern
@ -157,7 +152,6 @@ module WikiChunk
if web_match if web_match
@web_name = normalize_whitespace(web_match[1]) @web_name = normalize_whitespace(web_match[1])
@page_name = web_match[2] @page_name = web_match[2]
@link_text = @page_name if @link_text == web_match[0]
end end
end end

File diff suppressed because it is too large Load diff

View file

@ -1,20 +0,0 @@
class Logger
class LogDevice
private
def shift_log_age
#For Passenger, restart the server when rotating log files.
FileUtils.touch Rails.root.join("tmp", "restart.txt") if defined?(PhusionPassenger)
(@shift_age-3).downto(0) do |i|
if FileTest.exist?("#{@filename}.#{i}")
File.rename("#{@filename}.#{i}", "#{@filename}.#{i+1}")
end
end
@dev.close
File.rename("#{@filename}", "#{@filename}.0")
@dev = create_logfile(@filename)
return true
end
end
end

View file

Binary file not shown.

View file

@ -77,9 +77,9 @@ module XHTML #:nodoc:
# Return a textual representation of the node. # Return a textual representation of the node.
def to_s def to_s
s = [] s = ""
@children.each { |child| s << child.to_s } @children.each { |child| s << child.to_s }
s.join s
end end
# Return false (subclasses must override this to provide specific matching # Return false (subclasses must override this to provide specific matching
@ -150,19 +150,13 @@ module XHTML #:nodoc:
end end
if scanner.skip(/!\[CDATA\[/) if scanner.skip(/!\[CDATA\[/)
unless scanner.skip_until(/\]\]>/) scanner.scan_until(/\]\]>/)
if strict
raise "expected ]]> (got #{scanner.rest.inspect} for #{content})"
else
scanner.skip_until(/\Z/)
end
end
return CDATA.new(parent, line, pos, scanner.pre_match.gsub(/<!\[CDATA\[/, '')) return CDATA.new(parent, line, pos, scanner.pre_match.gsub(/<!\[CDATA\[/, ''))
end end
closing = ( scanner.scan(/\//) ? :close : nil ) closing = ( scanner.scan(/\//) ? :close : nil )
return Text.new(parent, line, pos, content) unless name = scanner.scan(/[\w:-]+/) return Text.new(parent, line, pos, content) unless name = scanner.scan(/[\w:-]+/)
name
unless closing unless closing
scanner.skip(/\s*/) scanner.skip(/\s*/)
@ -171,18 +165,17 @@ module XHTML #:nodoc:
value = true value = true
if scanner.scan(/\s*=\s*/) if scanner.scan(/\s*=\s*/)
if delim = scanner.scan(/['"]/) if delim = scanner.scan(/['"]/)
v = [] value = ""
while text = scanner.scan(/[^#{delim}\\]+|./) while text = scanner.scan(/[^#{delim}\\]+|./)
case text case text
when "\\" then when "\\" then
v << text value << text
v << scanner.getch value << scanner.getch
when delim when delim
break break
else v << text else value << text
end end
end end
value = v.join
else else
value = scanner.scan(/[^\s>\/]+/) value = scanner.scan(/[^\s>\/]+/)
end end
@ -272,7 +265,7 @@ module XHTML #:nodoc:
# itself. # itself.
class CDATA < Text #:nodoc: class CDATA < Text #:nodoc:
def to_s def to_s
"<![CDATA[#{super}]]>" "<![CDATA[#{super}]>"
end end
end end
@ -316,20 +309,22 @@ module XHTML #:nodoc:
# Returns a textual representation of the node # Returns a textual representation of the node
def to_s def to_s
s = ''
if @closing == :close if @closing == :close
"</#{@name}>" unless self.childless? s = "</#{@name}>" unless self.childless?
else else
s = ["<#{@name}"] s = "<#{@name}"
@attributes.sort.each do |k,v| atlist = @attributes.sort
s << " #{k}" atlist.each do |att|
s << "='#{v}'" if String === v s << " #{att[0]}"
s << "='#{att[1]}'" if String === att[1]
end end
s << "/" if (@children.empty? && @closing == :self) or self.childless? s << "/" if (@children.empty? && @closing == :self) or self.childless?
s << ">" s << ">"
@children.each { |child| s << child.to_s } @children.each { |child| s << child.to_s }
s << "</#{@name}>" unless @closing == :self or self.childless? or @children.empty? s << "</#{@name}>" if @closing != :self && !@closing.nil? && !@children.empty?
s.join
end end
s
end end
# If either the node or any of its children meet the given conditions, the # If either the node or any of its children meet the given conditions, the

File diff suppressed because it is too large Load diff

View file

@ -57,7 +57,7 @@ class PageRenderer
diffs = '' diffs = ''
diff_doc.write(diffs, -1, true, true) diff_doc.write(diffs, -1, true, true)
diffs.gsub(/\A<div class='xhtmldiff_wrapper'>(.*)<\/div>\Z/m, '\1').html_safe diffs.gsub(/\A<div class='xhtmldiff_wrapper'>(.*)<\/div>\Z/m, '\1')
else else
display_content display_content
end end

View file

@ -1,65 +0,0 @@
require 'webrick'
require 'stringio'
require 'rack/content_length'
require 'tempfile'
module Rack
module Handler
class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
def self.run(app, options={})
options[:BindAddress] = options.delete(:Host) if options[:Host]
@server = ::WEBrick::HTTPServer.new(options)
@server.mount "/", Rack::Handler::WEBrick, app
trap(:INT) { @server.shutdown }
trap(:TERM) { @server.shutdown }
yield @server if block_given?
@server.start
end
end
end
if Rack.release <= "1.2"
# The Tempfile bug is fixed in the bundled version of Rack
class RewindableInput
def make_rewindable
# Buffer all data into a tempfile. Since this tempfile is private to this
# RewindableInput object, we chmod it so that nobody else can read or write
# it. On POSIX filesystems we also unlink the file so that it doesn't
# even have a file entry on the filesystem anymore, though we can still
# access it because we have the file handle open.
@rewindable_io = Tempfile.new('RackRewindableInput')
@rewindable_io.chmod(0000)
@rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding)
@rewindable_io.binmode
if filesystem_has_posix_semantics? && !tempfile_unlink_contains_bug?
@rewindable_io.unlink
@unlinked = true
end
buffer = ""
while @io.read(1024 * 4, buffer)
entire_buffer_written_out = false
while !entire_buffer_written_out
written = @rewindable_io.write(buffer)
entire_buffer_written_out = written == Rack::Utils.bytesize(buffer)
if !entire_buffer_written_out
buffer.slice!(0 .. written - 1)
end
end
end
@rewindable_io.rewind
end
def tempfile_unlink_contains_bug?
# The tempfile library as included in Ruby 1.9.1-p152 and later
# contains a bug: unlinking an open Tempfile object also closes
# it, which breaks our expected POSIX semantics. This problem
# has been fixed in Ruby 1.9.2, but the Ruby team chose not to
# include the bug fix in later versions of the 1.9.1 series.
ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"
ruby_engine == "ruby" && RUBY_VERSION == "1.9.1" && RUBY_PATCHLEVEL >= 152
end
end
end
end

View file

@ -39,7 +39,7 @@ class RDocMarkup < SM::SimpleMarkup
end end
def convert(text, handler) def convert(text, handler)
super.sub(/^\n{0,1}<p>\n{0,1}/, '').sub(/\n{0,1}<\/p>\n{0,1}$/, '') super.sub(/^<p>\n/, '').sub(/<\/p>$/, '')
end end
end end

1113
lib/redcloth.rb Normal file

File diff suppressed because it is too large Load diff

2497
lib/sanitize.rb Normal file

File diff suppressed because it is too large Load diff

View file

@ -7,86 +7,76 @@ module Sanitizer
require 'action_controller/vendor/html-scanner/html/tokenizer' require 'action_controller/vendor/html-scanner/html/tokenizer'
require 'node' require 'node'
require 'instiki_stringsupport' require 'stringsupport'
require 'set'
require 'nokogiri'
acceptable_elements = Set.new %w[a abbr acronym address area article aside acceptable_elements = %w[a abbr acronym address area audio b big blockquote br
audio b big blockquote br button canvas caption center cite code button caption center cite code col colgroup dd del dfn dir div dl dt
col colgroup command datalist dd del details dfn dialog dir div dl dt em fieldset font form h1 h2 h3 h4 h5 h6 hr i img input ins kbd label
em fieldset figcaption figure font footer form h1 h2 h3 h4 h5 h6 header legend li map menu ol optgroup option p pre q s samp select small span
hgroup hr i img input ins kbd label legend li map mark menu meter nav strike strong sub sup table tbody td textarea tfoot th thead tr tt u
ol optgroup option p pre progress q rp rt ruby s samp section select small ul var video]
source span strike strong sub summary sup table tbody td textarea tfoot
th thead time tr tt u ul var video wbr]
mathml_elements = Set.new %w[annotation annotation-xml maction math menclose merror mathml_elements = %w[annotation annotation-xml maction math merror mfrac
mfrac mfenced mi mmultiscripts mn mo mover mpadded mphantom mprescripts mroot mfenced mi mmultiscripts mn mo mover mpadded mphantom mprescripts mroot
mrow mspace msqrt mstyle msub msubsup msup mtable mtd mtext mtr munder mrow mspace msqrt mstyle msub msubsup msup mtable mtd mtext mtr munder
munderover none semantics] munderover none semantics]
svg_elements = Set.new %w[a animate animateColor animateMotion animateTransform svg_elements = %w[a animate animateColor animateMotion animateTransform
circle clipPath defs desc ellipse feGaussianBlur filter font-face circle clipPath defs desc ellipse font-face font-face-name font-face-src
font-face-name font-face-src foreignObject g glyph hkern linearGradient foreignObject g glyph hkern linearGradient line marker metadata
line marker mask metadata missing-glyph mpath path pattern polygon missing-glyph mpath path polygon polyline radialGradient rect set
polyline radialGradient rect set stop svg switch text textPath title tspan use] stop svg switch text title tspan use]
acceptable_attributes = Set.new %w[abbr accept accept-charset accesskey action acceptable_attributes = %w[abbr accept accept-charset accesskey action
align alt autocomplete axis bgcolor border cellpadding cellspacing char charoff align alt axis border cellpadding cellspacing char charoff charset
checked cite class clear cols colspan color compact contenteditable contextmenu checked cite class clear cols colspan color compact controls coords datetime
controls coords datetime dir disabled draggable enctype face for formaction frame dir disabled enctype for frame headers height href hreflang hspace id
headers height high href hreflang hspace icon id ismap label list lang longdesc ismap label lang longdesc loop maxlength media method multiple name nohref
loop low max maxlength media method min multiple name nohref noshade nowrap open noshade nowrap poster prompt readonly rel rev rows rowspan rules scope
optimumpattern placeholder poster preload pubdate radiogroup readonly rel selected shape size span src start style summary tabindex target title
required rev reversed rows rowspan rules spellcheck scope type usemap valign value vspace width xml:lang]
selected shape size span src start step style summary tabindex target title
type usemap valign value vspace width wrap xml:lang]
mathml_attributes = Set.new %w[actiontype align close mathml_attributes = %w[actiontype align close columnalign columnalign
columnalign columnlines columnspacing columnspan depth display columnalign columnlines columnspacing columnspan depth display
displaystyle encoding equalcolumns equalrows fence fontstyle fontweight displaystyle encoding equalcolumns equalrows fence fontstyle fontweight
frame height linethickness lspace mathbackground mathcolor mathvariant frame height linethickness lspace mathbackground mathcolor mathvariant
maxsize minsize notation open other rowalign mathvariant maxsize minsize open other rowalign rowalign rowalign
rowlines rowspacing rowspan rspace scriptlevel selection separator rowlines rowspacing rowspan rspace scriptlevel selection separator
separators stretchy width voffset xlink:href xlink:show xlink:type xmlns separators stretchy width width xlink:href xlink:show xlink:type xmlns
xmlns:xlink] xmlns:xlink]
svg_attributes = Set.new %w[accent-height accumulate additive alphabetic svg_attributes = %w[accent-height accumulate additive alphabetic
arabic-form ascent attributeName attributeType baseProfile bbox begin arabic-form ascent attributeName attributeType baseProfile bbox begin
by calcMode cap-height class clip-path clip-rule color by calcMode cap-height class clip-path clip-rule color color-rendering
color-interpolation-filters color-rendering
content cx cy d dx dy descent display dur end fill fill-opacity fill-rule content cx cy d dx dy descent display dur end fill fill-opacity fill-rule
filterRes filterUnits font-family font-size font-stretch font-style font-family font-size font-stretch font-style font-variant font-weight from
font-variant font-weight from fx fy g1 g2 glyph-name gradientUnits fx fy g1 g2 glyph-name gradientUnits hanging height horiz-adv-x horiz-origin-x
hanging height horiz-adv-x horiz-origin-x id ideographic k keyPoints id ideographic k keyPoints keySplines keyTimes lang marker-end
keySplines keyTimes lang marker-end marker-mid marker-start marker-mid marker-start markerHeight markerUnits markerWidth
markerHeight markerUnits markerWidth maskContentUnits maskUnits mathematical max min name offset opacity orient origin
mathematical max method min name offset opacity orient origin overline-position overline-thickness panose-1 path pathLength points
overline-position overline-thickness panose-1 path pathLength preserveAspectRatio r refX refY repeatCount repeatDur
patternContentUnits patternTransform patternUnits points requiredExtensions requiredFeatures restart rotate rx ry slope stemh
preserveAspectRatio primitiveUnits r refX refY repeatCount repeatDur stemv stop-color stop-opacity strikethrough-position
requiredExtensions requiredFeatures restart rotate rx ry se:connector strikethrough-thickness stroke stroke-dasharray stroke-dashoffset
se:nonce slope spacing stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity
startOffset stdDeviation stemh stemv stop-color stop-opacity stroke-width systemLanguage target text-anchor to transform type u1
strikethrough-position strikethrough-thickness stroke stroke-dasharray u2 underline-position underline-thickness unicode unicode-range
stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit units-per-em values version viewBox visibility width widths x
stroke-opacity stroke-width systemLanguage target text-anchor x-height x1 x2 xlink:actuate xlink:arcrole xlink:href xlink:role
to transform type u1 u2 underline-position underline-thickness xlink:show xlink:title xlink:type xml:base xml:lang xml:space xmlns
unicode unicode-range units-per-em values version viewBox xmlns:xlink y y1 y2 zoomAndPan]
visibility width widths x x-height x1 x2 xlink:actuate
xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type
xml:base xml:lang xml:space xmlns xmlns:xlink xmlns:se y y1 y2 zoomAndPan]
attr_val_is_uri = Set.new %w[href src cite action formaction longdesc xlink:href xml:base] attr_val_is_uri = %w[href src cite action longdesc xlink:href xml:base]
svg_attr_val_allows_ref = Set.new %w[clip-path color-profile cursor fill SVG_ATTR_VAL_ALLOWS_REF = %w[clip-path color-profile cursor fill
filter marker marker-start marker-mid marker-end mask stroke] filter marker marker-start marker-mid marker-end mask stroke]
svg_allow_local_href = Set.new %w[altGlyph animate animateColor animateMotion SVG_ALLOW_LOCAL_HREF = %w[altGlyph animate animateColor animateMotion
animateTransform cursor feImage filter linearGradient pattern animateTransform cursor feImage filter linearGradient pattern
radialGradient textpath tref set use] radialGradient textpath tref set use]
acceptable_css_properties = Set.new %w[azimuth background-color acceptable_css_properties = %w[azimuth background-color
border-bottom-color border-collapse border-color border-left-color border-bottom-color border-collapse border-color border-left-color
border-right-color border-top-color clear color cursor direction border-right-color border-top-color clear color cursor direction
display elevation float font font-family font-size font-style display elevation float font font-family font-size font-style
@ -96,20 +86,19 @@ module Sanitizer
text-align text-decoration text-indent unicode-bidi vertical-align text-align text-decoration text-indent unicode-bidi vertical-align
voice-family volume white-space width] voice-family volume white-space width]
acceptable_css_keywords = Set.new %w[auto aqua black block blue bold both bottom acceptable_css_keywords = %w[auto aqua black block blue bold both bottom
brown center collapse dashed dotted fuchsia gray green !important brown center collapse dashed dotted fuchsia gray green !important
italic left lime maroon medium none navy normal nowrap olive pointer italic left lime maroon medium none navy normal nowrap olive pointer
purple red right solid silver teal top transparent underline white purple red right solid silver teal top transparent underline white
yellow] yellow]
acceptable_svg_properties = Set.new %w[fill fill-opacity fill-rule stroke acceptable_svg_properties = %w[fill fill-opacity fill-rule stroke
stroke-width stroke-linecap stroke-linejoin stroke-opacity] stroke-width stroke-linecap stroke-linejoin stroke-opacity]
acceptable_protocols = Set.new %w[ed2k ftp http https irc mailto news gopher nntp acceptable_protocols = %w[ed2k ftp http https irc mailto news gopher nntp
telnet webcal xmpp callto feed urn aim rsync tag ssh sftp rtsp afs] telnet webcal xmpp callto feed urn aim rsync tag ssh sftp rtsp afs]
SHORTHAND_CSS_PROPERTIES = Set.new %w[background border margin padding] VOID_ELEMENTS = %w[img br hr link meta area base basefont
VOID_ELEMENTS = Set.new %w[img br hr link meta area base basefont
col frame input isindex param] col frame input isindex param]
ALLOWED_ELEMENTS = acceptable_elements + mathml_elements + svg_elements unless defined?(ALLOWED_ELEMENTS) ALLOWED_ELEMENTS = acceptable_elements + mathml_elements + svg_elements unless defined?(ALLOWED_ELEMENTS)
@ -119,76 +108,67 @@ module Sanitizer
ALLOWED_SVG_PROPERTIES = acceptable_svg_properties unless defined?(ALLOWED_SVG_PROPERTIES) ALLOWED_SVG_PROPERTIES = acceptable_svg_properties unless defined?(ALLOWED_SVG_PROPERTIES)
ALLOWED_PROTOCOLS = acceptable_protocols unless defined?(ALLOWED_PROTOCOLS) ALLOWED_PROTOCOLS = acceptable_protocols unless defined?(ALLOWED_PROTOCOLS)
ATTR_VAL_IS_URI = attr_val_is_uri unless defined?(ATTR_VAL_IS_URI) ATTR_VAL_IS_URI = attr_val_is_uri unless defined?(ATTR_VAL_IS_URI)
SVG_ATTR_VAL_ALLOWS_REF = svg_attr_val_allows_ref unless defined?(SVG_ATTR_VAL_ALLOWS_REF)
SVG_ALLOW_LOCAL_HREF = svg_allow_local_href unless defined?(SVG_ALLOW_LOCAL_HREF)
# Sanitize the +html+, escaping all elements not in ALLOWED_ELEMENTS, and stripping out all # Sanitize the +html+, escaping all elements not in ALLOWED_ELEMENTS, and stripping out all
# attributes not in ALLOWED_ATTRIBUTES. Style attributes are parsed, and a restricted set, # attributes not in ALLOWED_ATTRIBUTES. Style attributes are parsed, and a restricted set,
# specified by ALLOWED_CSS_PROPERTIES and ALLOWED_CSS_KEYWORDS, are allowed through. # specified by ALLOWED_CSS_PROPERTIES and ALLOWED_CSS_KEYWORDS, are allowed through.
# Attributes in ATTR_VAL_IS_URI are scanned, and only uri schemes specified in # attributes in ATTR_VAL_IS_URI are scanned, and only URI schemes specified in
# ALLOWED_PROTOCOLS are allowed. # ALLOWED_PROTOCOLS are allowed.
# Certain SVG attributes (SVG_ATTR_VAL_ALLOWS_REF) may take a url as a value. These are restricted to
# fragment-id's (in-document references). Certain SVG elements (SVG_ALLOW_LOCAL_HREF) allow href attributes
# which, again, are restricted to be fragment-id's.
#
# You can adjust what gets sanitized, by defining these constant arrays before this Module is loaded. # You can adjust what gets sanitized, by defining these constant arrays before this Module is loaded.
# #
# xhtml_sanitize('<script> do_nasty_stuff() </script>') # sanitize_html('<script> do_nasty_stuff() </script>')
# => &lt;script> do_nasty_stuff() &lt;/script> # => &lt;script> do_nasty_stuff() &lt;/script>
# xhtml_sanitize('<a href="javascript: sucker();">Click here for $100</a>') # sanitize_html('<a href="javascript: sucker();">Click here for $100</a>')
# => <a>Click here for $100</a> # => <a>Click here for $100</a>
def xhtml_sanitize(html) def xhtml_sanitize(html)
return html unless sanitizeable?(html) if html.index("<")
tokenizer = HTML::Tokenizer.new(html.to_utf8) tokenizer = HTML::Tokenizer.new(html.to_utf8)
results = [] new_text = ""
while token = tokenizer.next while token = tokenizer.next
node = XHTML::Node.parse(nil, 0, 0, token, false) node = XHTML::Node.parse(nil, 0, 0, token, false)
results << case node.tag? new_text << case node.tag?
when true when true
if ALLOWED_ELEMENTS.include?(node.name) if ALLOWED_ELEMENTS.include?(node.name)
process_attributes_for node if node.attributes
node.to_s node.attributes.delete_if { |attr,v| !ALLOWED_ATTRIBUTES.include?(attr) }
ATTR_VAL_IS_URI.each do |attr|
val_unescaped = node.attributes[attr].to_s.unescapeHTML.gsub(/`|[\000-\040\177\s]+|\302[\200-\240]/,'').downcase
if val_unescaped =~ /^[a-z0-9][-+.a-z0-9]*:/ and !ALLOWED_PROTOCOLS.include?(val_unescaped.split(':')[0])
node.attributes.delete attr
end
end
SVG_ATTR_VAL_ALLOWS_REF.each do |attr|
node.attributes[attr] = node.attributes[attr].to_s.gsub(/url\s*\(\s*[^#\s][^)]+?\)/m, ' ') if node.attributes[attr]
end
if SVG_ALLOW_LOCAL_HREF.include?(node.name) && node.attributes['xlink:href'] && node.attributes['xlink:href'] =~ /^\s*[^#\s].*/m
node.attributes.delete 'xlink:href'
end
if node.attributes['style']
node.attributes['style'] = sanitize_css(node.attributes['style'])
end
node.attributes.each do |attr,val|
if String === val
node.attributes[attr] = val.unescapeHTML.escapeHTML
else
node.attributes.delete attr
end
end
end
node.to_s
else
node.to_s.gsub(/</, "&lt;").gsub(/>/, "&gt;")
end
else else
node.to_s.gsub(/</, "&lt;").gsub(/>/, "&gt;") node.to_s.unescapeHTML.escapeHTML
end end
else
node.to_s.unescapeHTML.escapeHTML
end end
html = new_text
end end
html
results.join
end end
def sanitizeable?(text)
!(text.nil? || text.empty? || !text.index("<"))
end
protected
def process_attributes_for(node)
return unless node.attributes
node.attributes.each do |attr,val|
if String === val && ALLOWED_ATTRIBUTES.include?(attr)
val = val.unescapeHTML.escapeHTML
else
node.attributes.delete attr; next
end
if attr == 'xlink:href' && SVG_ALLOW_LOCAL_HREF.include?(node.name) && val =~ /^\s*[^#\s]/m
node.attributes.delete attr; next
end
if ATTR_VAL_IS_URI.include?(attr)
val_unescaped = val.unescapeHTML.as_bytes.gsub(/`|[\000-\040\177\s]+|\302[\200-\240]/,'').downcase
if val_unescaped =~ /^[a-z0-9][-+.a-z0-9]*:/ && !ALLOWED_PROTOCOLS.include?(val_unescaped.split(':')[0])
node.attributes.delete attr; next
end
end
val = val.to_s.gsub(/url\s*\(\s*[^#\s][^)]+?\)/mi, ' ') if SVG_ATTR_VAL_ALLOWS_REF.include?(attr)
val = sanitize_css(val) if attr == 'style'
node.attributes[attr] = val
end
end
def sanitize_css(style) def sanitize_css(style)
# disallow urls # disallow urls
style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ') style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ')
@ -203,7 +183,7 @@ module Sanitizer
prop.downcase! prop.downcase!
if self.class.const_get("ALLOWED_CSS_PROPERTIES").include?(prop) if self.class.const_get("ALLOWED_CSS_PROPERTIES").include?(prop)
clean << "#{prop}: #{val};" clean << "#{prop}: #{val};"
elsif self.class.const_get("SHORTHAND_CSS_PROPERTIES").include?(prop.split('-')[0]) elsif %w[background border margin padding].include?(prop.split('-')[0])
clean << "#{prop}: #{val};" unless val.split().any? do |keyword| clean << "#{prop}: #{val};" unless val.split().any? do |keyword|
!self.class.const_get("ALLOWED_CSS_KEYWORDS").include?(keyword) and !self.class.const_get("ALLOWED_CSS_KEYWORDS").include?(keyword) and
keyword !~ /^(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$/ keyword !~ /^(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$/
@ -213,25 +193,6 @@ module Sanitizer
end end
end end
clean.join(' ') style = clean.join(' ')
end end
# Sanitize a string, parsed using XHTML parsing rules. Reparse the result to
# ensure well-formedness.
#
# :call-seq:
# safe_sanitize_xhtml(string) -> string
#
# Unless otherwise specified, the string is assumed to be utf-8 encoded.
#
# The string returned is utf-8 encoded. If you want, you can use iconv to convert it to some other encoding.
# (REXML trees are always utf-8 encoded.)
def safe_xhtml_sanitize(html, options = {})
sanitized = xhtml_sanitize(html.purify)
doc = Nokogiri::XML::Document.parse("<div xmlns='http://www.w3.org/1999/xhtml'>#{sanitized}</div>", nil, (options[:encoding] || 'UTF-8'), 0)
sanitized = doc.root.children.to_xml(:indent => (options[:indent] || 2), :save_with => 2 )
rescue Nokogiri::XML::SyntaxError
sanitized = sanitized.escapeHTML
end
end end

2283
lib/stringsupport.rb Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,34 +1,15 @@
require 'rake' require 'sqlite3'
desc "This task will perform necessary upgrades to your Instiki installation" task :upgrade_instiki do
task :upgrade_instiki => :environment do db = SQLite3::Database.new( "db/production.db.sqlite3" )
ENV['RAILS_ENV'] ||= 'production' db.execute( "select * from webs" ) do |row|
puts "Upgrading Instiki in #{ENV['RAILS_ENV']} environment." if File.exists?('public/' + row[4])
if File.exists?('webs/' + row[4])
InstikiUpgrade.migrate_db print "Warning! The directory webs/#{row[4]} already exists. Skipping.\n"
InstikiUpgrade.move_uploaded_files else
end File.rename('public/' + row[4], 'webs/' + row[4])
print "Moved: #{row[4]}\n"
class InstikiUpgrade
def self.migrate_db
ActiveRecord::Base.establish_connection ENV['RAILS_ENV']
Rake::Task["db:migrate"].invoke
end
def self.move_uploaded_files
Web.all.each do |web|
public_path = Rails.root.join("public", web.address)
if public_path.exist?
webs_path = Rails.root.join("webs", web.address)
if webs_path.exist?
puts "Warning! The directory #{webs_path} already exists. Skipping."
else
public_path.rename(webs_path)
puts "Moved #{public_path} to #{webs_path}"
end
end end
end end
end end
end end

View file

@ -1,4 +1,4 @@
require 'instiki_stringsupport' require 'stringsupport'
class AbstractUrlGenerator class AbstractUrlGenerator
@ -10,8 +10,7 @@ class AbstractUrlGenerator
# Create a link for the given page (or file) name and link text based # Create a link for the given page (or file) name and link text based
# on the render mode in options and whether the page (file) exists # on the render mode in options and whether the page (file) exists
# in the web. # in the web.
def make_link(current_web, asked_name, web, text = nil, options = {}) def make_link(asked_name, web, text = nil, options = {})
@web = current_web
mode = (options[:mode] || :show).to_sym mode = (options[:mode] || :show).to_sym
link_type = (options[:link_type] || :show).to_sym link_type = (options[:link_type] || :show).to_sym
@ -53,10 +52,7 @@ class AbstractUrlGenerator
raise "Unknown link type: #{link_type}" raise "Unknown link type: #{link_type}"
end end
end end
def url_for(hash = {})
@controller.url_for hash
end
end end
class UrlGenerator < AbstractUrlGenerator class UrlGenerator < AbstractUrlGenerator
@ -64,25 +60,24 @@ class UrlGenerator < AbstractUrlGenerator
private private
def file_link(mode, name, text, web_address, known_file, description) def file_link(mode, name, text, web_address, known_file, description)
return bad_filename(name) unless WikiFile.is_valid?(name)
case mode case mode
when :export when :export
if known_file if known_file
%{<a class="existingWikiWord" title="#{description}" href="files/#{CGI.escape(name)}">#{text}</a>} %{<a class="existingWikiWord" title="#{description}" href="#{CGI.escape(name)}.#{@controller.html_ext}">#{text}</a>}
else else
%{<span class="newWikiWord">#{text}</span>} %{<span class="newWikiWord">#{text}</span>}
end end
when :publish when :publish
if known_file if known_file
href = @controller.url_for :controller => 'file', :web => web_address, :action => 'file', href = @controller.url_for :controller => 'file', :web => web_address, :action => 'file',
:id => name, :only_path => true :id => name
%{<a class="existingWikiWord" title="#{description}" href="#{href}">#{text}</a>} %{<a class="existingWikiWord" title="#{description}" href="#{href}">#{text}</a>}
else else
%{<span class="newWikiWord">#{text}</span>} %{<span class="newWikiWord">#{text}</span>}
end end
else else
href = @controller.url_for :controller => 'file', :web => web_address, :action => 'file', href = @controller.url_for :controller => 'file', :web => web_address, :action => 'file',
:id => name, :only_path => true :id => name
if known_file if known_file
%{<a class="existingWikiWord" title="#{description}" href="#{href}">#{text}</a>} %{<a class="existingWikiWord" title="#{description}" href="#{href}">#{text}</a>}
else else
@ -92,38 +87,54 @@ class UrlGenerator < AbstractUrlGenerator
end end
def page_link(mode, name, text, web_address, known_page) def page_link(mode, name, text, web_address, known_page)
return %{<span class='wikilink-error'><b>Illegal link (target contains a '.'):</b> #{name}</span>} if name.include?('.')
case mode case mode
when :export when :export
if known_page if known_page
%{<a class="existingWikiWord" href="#{CGI.escape(name)}.#{html_ext}">#{text}</a>} %{<a class="existingWikiWord" href="#{CGI.escape(name)}.#{@controller.html_ext}">#{text}</a>}
else else
%{<span class="newWikiWord">#{text}</span>} %{<span class="newWikiWord">#{text}</span>}
end end
when :publish when :publish
if known_page if known_page
wikilink_for(mode, name, text, web_address) href = @controller.url_for :controller => 'wiki', :web => web_address, :action => 'published',
:id => name
%{<a class="existingWikiWord" href="#{href}">#{text}</a>}
else else
%{<span class="newWikiWord">#{text}</span>} %{<span class="newWikiWord">#{text}</span>}
end end
else when :show
if known_page if known_page
wikilink_for(mode, name, text, web_address) href = @controller.url_for :controller => 'wiki', :web => web_address, :action => 'show',
:id => name
%{<a class="existingWikiWord" href="#{href}">#{text}</a>}
else else
href = @controller.url_for :controller => 'wiki', :web => web_address, :action => 'new', href = @controller.url_for :controller => 'wiki', :web => web_address, :action => 'new',
:id => name, :only_path => true :id => name
%{<span class="newWikiWord">#{text}<a href="#{href}">?</a></span>}
end
else
if known_page
web = Web.find_by_address(web_address)
action = web.published? ? 'published' : 'show'
href = @controller.url_for :controller => 'wiki', :web => web_address, :action => action,
:id => name
%{<a class="existingWikiWord" href="#{href}">#{text}</a>}
else
href = @controller.url_for :controller => 'wiki', :web => web_address, :action => 'new',
:id => name
%{<span class="newWikiWord">#{text}<a href="#{href}">?</a></span>} %{<span class="newWikiWord">#{text}<a href="#{href}">?</a></span>}
end end
end end
end end
def pic_link(mode, name, text, web_address, known_pic) def pic_link(mode, name, text, web_address, known_pic)
return bad_filename(name) unless WikiFile.is_valid?(name)
href = @controller.url_for :controller => 'file', :web => web_address, :action => 'file', href = @controller.url_for :controller => 'file', :web => web_address, :action => 'file',
:id => name, :only_path => true :id => name
case mode case mode
when :export when :export
if known_pic if known_pic
%{<img alt="#{text}" src="files/#{CGI.escape(name)}" />} %{<img alt="#{text}" src="#{CGI.escape(name)}" />}
else else
%{<img alt="#{text}" src="no image" />} %{<img alt="#{text}" src="no image" />}
end end
@ -143,13 +154,12 @@ class UrlGenerator < AbstractUrlGenerator
end end
def media_link(mode, name, text, web_address, known_media, media_type) def media_link(mode, name, text, web_address, known_media, media_type)
return bad_filename(name) unless WikiFile.is_valid?(name)
href = @controller.url_for :controller => 'file', :web => web_address, :action => 'file', href = @controller.url_for :controller => 'file', :web => web_address, :action => 'file',
:id => name, :only_path => true :id => name
case mode case mode
when :export when :export
if known_media if known_media
%{<#{media_type} src="files/#{CGI.escape(name)}" controls="controls">#{text}</#{media_type}>} %{<#{media_type} src="#{CGI.escape(name)}" controls="controls">#{text}</#{media_type}>}
else else
text text
end end
@ -170,32 +180,12 @@ class UrlGenerator < AbstractUrlGenerator
def delete_link(mode, name, web_address, known_file) def delete_link(mode, name, web_address, known_file)
href = @controller.url_for :controller => 'file', :web => web_address, href = @controller.url_for :controller => 'file', :web => web_address,
:action => 'delete', :id => name, :only_oath => true :action => 'delete', :id => name
if mode == :show and known_file if mode == :show and known_file
%{<span class="deleteWikiWord"><a href="#{href}">Delete #{name}</a></span>} %{<span class="deleteWikiWord"><a href="#{href}">Delete #{name}</a></span>}
else else
%{<span class="deleteWikiWord">[[#{name}:delete]]</span>} %{<span class="deleteWikiWord">[[#{name}:delete]]</span>}
end end
end end
private
def bad_filename(name)
"<span class='badWikiWord'>[[invalid filename: #{name}]]</span>"
end
def wikilink_for(mode, name, text, web_address)
web = Web.find_by_address(web_address)
action = web.published? && (web != @web || [:publish, :s5].include?(mode) ) ? 'published' : 'show'
href = @controller.url_for :controller => 'wiki', :web => web_address, :action => action,
:id => name, :only_path => true
title = web == @web ? '' : %{ title="#{web_address}"}
%{<a class="existingWikiWord" href="#{href}"#{title}>#{text}</a>}
end
def html_ext
@html_ext ||= @controller.method(:html_ext).call
# Why method().call ? A Ruby 1.9.2preview1 bug:
# http://redmine.ruby-lang.org/issues/show/1802
end
end end

View file

@ -7,8 +7,6 @@ require_dependency 'chunks/wiki'
require_dependency 'chunks/literal' require_dependency 'chunks/literal'
require 'chunks/nowiki' require 'chunks/nowiki'
require 'sanitizer' require 'sanitizer'
require 'instiki_stringsupport'
require 'set'
# Wiki content is just a string that can process itself with a chain of # Wiki content is just a string that can process itself with a chain of
@ -54,10 +52,10 @@ module ChunkManager
def init_chunk_manager def init_chunk_manager
@chunks_by_type = Hash.new @chunks_by_type = Hash.new
Chunk::Abstract::derivatives.each{|chunk_type| Chunk::Abstract::derivatives.each{|chunk_type|
@chunks_by_type[chunk_type] = Set.new @chunks_by_type[chunk_type] = Array.new
} }
@chunks_by_id = Hash.new @chunks_by_id = Hash.new
@chunks = Set.new @chunks = []
@chunk_id = 0 @chunk_id = 0
end end
@ -116,7 +114,7 @@ class WikiContentStub < String
end end
end end
class WikiContent < ActiveSupport::SafeBuffer class WikiContent < String
include ChunkManager include ChunkManager
include Sanitizer include Sanitizer
@ -129,7 +127,7 @@ class WikiContent < ActiveSupport::SafeBuffer
:mode => :show :mode => :show
}.freeze }.freeze
attr_reader :web, :options, :revision, :not_rendered, :pre_rendered, :url_generator attr_reader :web, :options, :revision, :not_rendered, :pre_rendered
# Create a new wiki content string from the given one. # Create a new wiki content string from the given one.
# The options are explained at the top of this file. # The options are explained at the top of this file.
@ -144,12 +142,6 @@ class WikiContent < ActiveSupport::SafeBuffer
@options[:active_chunks] = (ACTIVE_CHUNKS - [WikiChunk::Word] ) if @web.brackets_only? @options[:active_chunks] = (ACTIVE_CHUNKS - [WikiChunk::Word] ) if @web.brackets_only?
@options[:hide_chunks] = (HIDE_CHUNKS - [Literal::Math] ) unless @options[:hide_chunks] = (HIDE_CHUNKS - [Literal::Math] ) unless
[Engines::MarkdownMML, Engines::MarkdownPNG].include?(@options[:engine]) [Engines::MarkdownMML, Engines::MarkdownPNG].include?(@options[:engine])
if @options[:engine] == Engines::MarkdownPNG
@options[:png_url] =
@options[:mode] == :export ? 'files/pngs/' :
(@url_generator.url_for :controller => 'file', :web => @web.address,
:action => 'file', :id => 'pngs', :only_path => true) + '/'
end
@not_rendered = @pre_rendered = nil @not_rendered = @pre_rendered = nil
@ -163,7 +155,7 @@ class WikiContent < ActiveSupport::SafeBuffer
def page_link(web_name, name, text, link_type) def page_link(web_name, name, text, link_type)
web = Web.find_by_name(web_name) || Web.find_by_address(web_name) || @web web = Web.find_by_name(web_name) || Web.find_by_address(web_name) || @web
@options[:link_type] = (link_type || :show) @options[:link_type] = (link_type || :show)
@url_generator.make_link(@web, name, web, text, @options) @url_generator.make_link(name, web, text, @options)
end end
def build_chunks def build_chunks
@ -182,20 +174,10 @@ class WikiContent < ActiveSupport::SafeBuffer
@options[:engine].apply_to(copy) @options[:engine].apply_to(copy)
copy.inside_chunks(@options[:hide_chunks]) do |id| copy.inside_chunks(@options[:hide_chunks]) do |id|
@chunks_by_id[id.to_i].revert if @chunks_by_id[id.to_i] @chunks_by_id[id.to_i].revert
end end
end end
def delete_chunks!(types)
types.each do |t|
@chunks_by_type[t].each do |c|
@pre_rendered.sub!(c.mask, '') if @pre_rendered
@chunks.delete(c)
end
end
self
end
def pre_render! def pre_render!
unless @pre_rendered unless @pre_rendered
@chunks_by_type[Include].each{|chunk| chunk.unmask } @chunks_by_type[Include].each{|chunk| chunk.unmask }
@ -207,10 +189,8 @@ class WikiContent < ActiveSupport::SafeBuffer
def render! def render!
pre_render! pre_render!
@options[:engine].apply_to(self) @options[:engine].apply_to(self)
as_utf8
# unmask in one go. $~[1] is the chunk id # unmask in one go. $~[1] is the chunk id
text = self.to_str gsub!(MASK_RE[ACTIVE_CHUNKS]) do
text.gsub!(MASK_RE[ACTIVE_CHUNKS]) do
chunk = @chunks_by_id[$~[1].to_i] chunk = @chunks_by_id[$~[1].to_i]
if chunk.nil? if chunk.nil?
# if we match a chunkmask that existed in the original content string # if we match a chunkmask that existed in the original content string
@ -220,8 +200,7 @@ class WikiContent < ActiveSupport::SafeBuffer
chunk.unmask_text chunk.unmask_text
end end
end end
self.replace xhtml_sanitize(text) self.replace xhtml_sanitize(self)
self.html_safe
end end
def page_name def page_name

View file

@ -1,28 +1,24 @@
#coding: utf-8 #coding: utf-8
# Contains all the methods for finding and replacing wiki words # Contains all the methods for finding and replacing wiki words
require 'instiki_stringsupport'
module WikiWords module WikiWords
# In order of appearance: Latin, greek, cyrillic, armenian # In order of appearance: Latin, greek, cyrillic, armenian
I18N_HIGHER_CASE_LETTERS = I18N_HIGHER_CASE_LETTERS =
"ÀÁÂÃÄÅĀĄĂÆÇĆČĈĊĎĐÈÉÊËĒĘĚĔĖĜĞĠĢĤĦÌÍÎÏĪĨĬĮİIJĴĶŁĽĹĻĿÑŃŇŅŊÒÓÔÕÖØŌŐŎŒŔŘŖŚŠŞŜȘŤŢŦȚÙÚÛÜŪŮŰŬŨŲŴŶŸȲÝŹŽŻ" + "ÀÁÂÃÄÅĀĄĂÆÇĆČĈĊĎĐÈÉÊËĒĘĚĔĖĜĞĠĢĤĦÌÍÎÏĪĨĬĮİIJĴĶŁĽĹĻĿÑŃŇŅŊÒÓÔÕÖØŌŐŎŒŔŘŖŚŠŞŜȘŤŢŦȚÙÚÛÜŪŮŰŬŨŲŴŶŸȲÝŹŽŻ" +
"ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ" + "ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ" +
"ЀЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯѠѢѤѦѨѪѬѮѰѲѴѶѸѺѼѾҀҊҌҎҐҒҔҖҘҚҜҞҠҢҤҦҨҪҬҮҰҲҴҶҸҺҼҾӀӁӃӅӇӉӋӍӐӒӔӖӘӚӜӞӠӢӤӦӨӪӬӮӰӲӴӶӸӺӼӾԀԂԄԆԈԊԌԎԐԒԔԖԘԚԜԞԠԢ" + "ΆΈΉΊΌΎΏѠѢѤѦѨѪѬѮѰѲѴѶѸѺѼѾҀҊҌҎҐҒҔҖҘҚҜҞҠҢҤҦҨҪҬҮҰҲҴҶҸҺҼҾӁӃӅӇӉӋӍӏӐӒӔӖӘӚӜӞӠӢӤӦӨӪӬӮӰӲӴӸЖ" +
"ԱԲԳԴԵԶԷԸԹԺԻԼԽԾԿՀՁՂՃՄՅՆՇՈՉՊՋՌՍՎՏՐՑՒՓՔՕՖ" "ԱԲԳԴԵԶԷԸԹԺԻԼԽԾԿՀՁՂՃՄՅՆՇՈՉՊՋՌՍՎՏՐՑՒՓՔՕՖ"
I18N_LOWER_CASE_LETTERS = I18N_LOWER_CASE_LETTERS =
"àáâãäåāąăæçćĉċčďđèéêëēęěĕėƒĝğġģĥħìíîïīĩĭįıijĵķĸłľĺļŀñńňņʼnŋòóôõöøōŏőœŕřŗśŝšşșťţŧțùúûüūůűŭũųŵýÿŷžżźÞþßſð" + "àáâãäåāąăæçćĉċčďđèéêëēęěĕėƒĝğġģĥħìíîïīĩĭįıijĵķĸłľĺļŀñńňņʼnŋòóôõöøōŏőœŕřŗśŝšşșťţŧțùúûüūůűŭũųŵýÿŷžżźÞþßſð" +
"άέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώΐ" + "άέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώΐ" +
"абвгдежзийклмнопрстуфхцчшщъыьэюяѐёђѓєѕіїјљњћќѝўџѡѣѥѧѩѫѭѯѱѳѵѷѹѻѽѿҁҋҍҏґғҕҗҙқҝҟҡңҥҧҩҫҭүұҳҵҷҹһҽҿӂӄӆӈӊӌӎӏӑӓӕӗәӛӝӟӡӣӥӧөӫӭӯӱӳӵӷӹӻӽӿԁԃԅԇԉԋԍԏԑԓԕԗԙԛԝԟԡԣ" + "абвгдежзийклмнопрстуфхцчшщъыьэюяѐёђѓєѕіїјљћќѝўџѡѣѥѧѩѫѭѯѱѳѵѷѹѻѽѿҁҋҌҍҏґғҕҗҙқҝҟҡңҥҧҩҫҭүұҳҵҷҹһҽҿӀӂӄӆӈӊӌӎӑӓӕӗәӛӝӟӡӣӥӧөӫӭӯӱӳӵӹ" +
"աբգդեզէըթժիլխծկհձղճմյնշոչպջռսվտրցւփքօֆև" "աբգդեզէըթժիլխծկհձղճմյնշոչպջռսվտրցւփքօֆև"
WIKI_WORD_PATTERN = '[A-Z' + I18N_HIGHER_CASE_LETTERS + ']+[a-z' + I18N_LOWER_CASE_LETTERS + ']+[A-Z' + I18N_HIGHER_CASE_LETTERS + WIKI_WORD_PATTERN = '[A-Z' + I18N_HIGHER_CASE_LETTERS + ']+[a-z' + I18N_LOWER_CASE_LETTERS + ']+[A-Z' + I18N_HIGHER_CASE_LETTERS + ']\w+'
'][A-Za-z0-9_' + I18N_HIGHER_CASE_LETTERS + I18N_LOWER_CASE_LETTERS + ']+'
CAMEL_CASED_WORD_BORDER = /([a-z#{I18N_LOWER_CASE_LETTERS}])([A-Z#{I18N_HIGHER_CASE_LETTERS}])/u CAMEL_CASED_WORD_BORDER = /([a-z#{I18N_LOWER_CASE_LETTERS}])([A-Z#{I18N_HIGHER_CASE_LETTERS}])/u
def self.separate(wiki_word) def self.separate(wiki_word)
wiki_word.dup.as_utf8.gsub(CAMEL_CASED_WORD_BORDER, '\1 \2') wiki_word.gsub(CAMEL_CASED_WORD_BORDER, '\1 \2')
end end
end end

View file

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

202
public/MathJax/LICENSE vendored
View file

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
This is release branch v1.1 of MathJax.

Some files were not shown because too many files have changed in this diff Show more