<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>config/initializers/readonly_records.rb</filename>
    </added>
    <added>
      <filename>db/migrate/20081219130711_unescape_comment_fields.rb</filename>
    </added>
    <added>
      <filename>spec/controllers/mephisto_controller_spec.rb</filename>
    </added>
    <added>
      <filename>spec/models/readonly_spec.rb</filename>
    </added>
    <added>
      <filename>spec/models/user_spec.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -43,7 +43,10 @@ class AccountController &lt; ApplicationController
   def activate
     self.current_user = site.user_by_token(params[:id])
     if logged_in?
-      current_user.reset_token!
+      # TODO - See security comments on AuthenticatedSystem#login_from_cookie.
+      ActiveRecord::Base.with_writable_records do
+        current_user.reset_token!
+      end
     else
       flash[:error] = &quot;Invalid token.  Try resending your forgotten password request.&quot;
     end</diff>
      <filename>app/controllers/account_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -138,7 +138,7 @@ class Admin::ArticlesController &lt; Admin::BaseController
       with_site_timezone do
         date = Time.parse_from_attributes(params[:article], :published_at, :local)
         next unless date
-        params[:article].delete_if { |k, v| k.to_s =~ /^#{:published_at}/ }
+        params[:article].delete_if { |k, v| k.to_s =~ /\A#{:published_at}/ }
         params[:article][:published_at] = local_to_utc(date)
       end
     end</diff>
      <filename>app/controllers/admin/articles_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -3,6 +3,7 @@ class Admin::AssetsController &lt; Admin::BaseController
   skip_before_filter :login_required
   before_filter :find_asset, :except =&gt; [:index, :new, :create, :latest, :search, :upload, :clear_bucket]
   before_filter :login_required
+  before_filter :protect_action, :except =&gt; [:index, :new, :latest, :search]
 
   def index
     search_assets 24</diff>
      <filename>app/controllers/admin/assets_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -22,7 +22,7 @@ class Admin::BaseController &lt; ApplicationController
     end
 
     def find_and_sort_templates
-      @layouts, @templates = site.templates.partition { |t| t.dirname.to_s =~ /layouts$/ }
+      @layouts, @templates = site.templates.partition { |t| t.dirname.to_s =~ /layouts\z/ }
     end
     
     def self.clear_empty_templates_for(model, *attributes)</diff>
      <filename>app/controllers/admin/base_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -7,7 +7,7 @@ class Admin::DesignController &lt; Admin::BaseController
       return
     end
 
-    if params[:filename] =~ /\.(css|js)$/i
+    if params[:filename] =~ /\.(css|js)\z/i
       @resource = @theme.resources.write params[:filename], params[:data]
       redirect_to url_for_theme(:controller =&gt; 'resources', :action =&gt; 'edit', :filename =&gt; @resource.basename.to_s)
     else</diff>
      <filename>app/controllers/admin/design_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -3,6 +3,7 @@ class ApplicationController &lt; ActionController::Base
 
   include Mephisto::CachingMethods
   before_filter  :set_cache_root
+  around_filter  :get_requests_are_readonly
   helper_method  :site
   attr_reader    :site
 
@@ -14,6 +15,7 @@ class ApplicationController &lt; ActionController::Base
   end
   
   filter_parameter_logging &quot;password&quot;
+  filter_parameter_logging &quot;token&quot;
   
   protected
     helper_method :admin?
@@ -95,6 +97,21 @@ class ApplicationController &lt; ActionController::Base
       end
     end
 
+    # Much of a web browser's built-in protection against CSRF attacks
+    # assumes that all GET requests are &quot;safe&quot;.  Since we don't want to
+    # rely on getting this 100% right in every controller and plugin, let's
+    # just enforce this policy globally.  You can override this using
+    # &lt;code&gt;skip_filter :get_requests_are_readonly&lt;/code&gt;.
+    def get_requests_are_readonly
+      if request.method == :get
+        ActiveRecord::Base.with_readonly_records do
+          yield
+        end
+      else
+        yield
+      end
+    end
+
     def with_site_timezone
       old_tz = ENV['TZ']
       ENV['TZ'] = site.timezone.name</diff>
      <filename>app/controllers/application.rb</filename>
    </modified>
    <modified>
      <diff>@@ -6,7 +6,7 @@ class FeedController &lt; ApplicationController
   def feed
     sections = params[:sections].clone
     last = sections.last
-    sections.delete(last) if last =~ /\.xml$/
+    sections.delete(last) if last =~ /\.xml\z/
     @section_path = sections.blank? ? '' : sections.join('/')
     case last
       when 'all_comments.xml'</diff>
      <filename>app/controllers/feed_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -47,7 +47,19 @@ class MephistoController &lt; ApplicationController
         redirect_to site.permalink_for(@article) and return
       end
 
-      @comment = @article.comments.build(params[:comment].merge(:user_id =&gt; session[:user], :author_ip =&gt; request.remote_ip, :user_agent =&gt; request.user_agent, :referrer =&gt; request.referer))
+      # Since this input is utterly untrustworthy (no authenticity_token,
+      # session, or anything else required), build the record manually.
+      comment_data = {
+        :user_id =&gt; session[:user],
+        :author_ip =&gt; request.remote_ip,
+        :user_agent =&gt; request.user_agent,
+        :referrer =&gt; request.referer,
+        :author =&gt; params[:comment][:author],
+        :author_email =&gt; params[:comment][:author_email],
+        :author_url =&gt; params[:comment][:author_url],
+        :body =&gt; params[:comment][:body]
+      }
+      @comment = @article.comments.build(comment_data)
       @comment.check_approval site, request if @comment.valid?
       @comment.save!
       redirect_to dispatch_path(:path =&gt; (site.permalink_for(@article)[1..-1].split('/') &lt;&lt; 'comments' &lt;&lt; @comment.id.to_s), :anchor =&gt; @comment.dom_id)</diff>
      <filename>app/controllers/mephisto_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -2,16 +2,25 @@ class CommentDrop &lt; BaseDrop
   include Mephisto::Liquid::UrlMethods
   
   timezone_dates :published_at, :created_at
-  liquid_attributes.push(*[:author, :author_email, :author_ip, :title])
+  liquid_attributes.push(:title) # Not sure who uses this.
 
   def initialize(source)
     super
-    @liquid.update 'is_approved' =&gt; @source.approved?, 'body' =&gt; ActionView::Base.white_list_sanitizer.sanitize(@source.body_html)
+    @liquid.update('is_approved' =&gt; @source.approved?,
+                   'body' =&gt; ActionView::Base.white_list_sanitizer.sanitize(@source.body_html))
+
+    # We used to escape these fields when we saved them to the database.
+    # Now we've unescaped everything in the database, but we still need to
+    # preserve backwards compatibility with old themes, which expect these
+    # values to be escaped.  So we escape these fields manually here.
+    [:author, :author_email, :author_ip].each do |a|
+      @liquid.update(a.to_s =&gt; CGI.escapeHTML(@source.send(a) || ''))
+    end
   end
-  
+
   def author_url
     return nil if source.author_url.blank?
-    @source.author_url =~ /^https?:\/\// ? @source.author_url : &quot;http://&quot; + @source.author_url
+    CGI.escapeHTML(@source.author_url =~ /\Ahttps?:\/\// ? @source.author_url : &quot;http://&quot; + @source.author_url)
   end
 
   def url
@@ -23,7 +32,7 @@ class CommentDrop &lt; BaseDrop
   end
 
   def author_link
-    @source.author_url.blank? ? &quot;&lt;span&gt;#{@source.author}&lt;/span&gt;&quot; : %Q{&lt;a href=&quot;#{author_url}&quot;&gt;#{@source.author}&lt;/a&gt;}
+    @source.author_url.blank? ? &quot;&lt;span&gt;#{@liquid['author']}&lt;/span&gt;&quot; : %Q{&lt;a href=&quot;#{author_url}&quot;&gt;#{@liquid['author']}&lt;/a&gt;}
   end
   
   def presentation_class</diff>
      <filename>app/drops/comment_drop.rb</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@ module CoreFilters
 
   def parse_date(date)
     return Time.now.utc if date.blank?
-    date = &quot;#{date}-1&quot; if date.to_s =~ /^\d{4}-\d{1,2}$/ unless [Time, Date].include?(date.class)
+    date = &quot;#{date}-1&quot; if date.to_s =~ /\A\d{4}-\d{1,2}\z/ unless [Time, Date].include?(date.class)
     date = date.to_time
   end
 </diff>
      <filename>app/filters/core_filters.rb</filename>
    </modified>
    <modified>
      <diff>@@ -3,7 +3,7 @@ module ApplicationHelper
 
   def author_link_for(comment)
     return h(comment.author) if comment.author_url.blank?
-    link_to h(comment.author), &quot;#{'http://' unless comment.author_url =~ /^https?:\/\//}#{comment.author_url}&quot;
+    link_to h(comment.author), &quot;#{'http://' unless comment.author_url =~ /\Ahttps?:\/\//}#{comment.author_url}&quot;
   end
 
   def avatar_for(user, options = {})</diff>
      <filename>app/helpers/application_helper.rb</filename>
    </modified>
    <modified>
      <diff>@@ -14,11 +14,11 @@ class Asset &lt; ActiveRecord::Base
 
   class &lt;&lt; self
     def movie?(content_type)
-      content_type.to_s =~ /^video/ || extra_content_types[:movie].include?(content_type)
+      content_type.to_s =~ /\Avideo/ || extra_content_types[:movie].include?(content_type)
     end
         
     def audio?(content_type)
-      content_type.to_s =~ /^audio/ || extra_content_types[:audio].include?(content_type)
+      content_type.to_s =~ /\Aaudio/ || extra_content_types[:audio].include?(content_type)
     end
     
     def other?(content_type)
@@ -72,7 +72,7 @@ class Asset &lt; ActiveRecord::Base
 
   def public_filename_with_host(thumbnail = nil)
     returning public_filename_without_host(thumbnail) do |s|
-      s.gsub! /^\/assets\/[^\/]+\//, &quot;/assets/#{$1}&quot; if Site.multi_sites_enabled
+      s.gsub! /\A\/assets\/[^\/]+\//, &quot;/assets/#{$1}&quot; if Site.multi_sites_enabled
     end
   end
   alias_method_chain :public_filename, :host</diff>
      <filename>app/models/asset.rb</filename>
    </modified>
    <modified>
      <diff>@@ -7,14 +7,13 @@ class Comment &lt; Content
   before_validation :clean_up_author_url
   after_validation_on_create  :snag_article_attributes
   before_create  :check_comment_expiration
-  before_create  :sanitize_attributes
   before_save    :update_counter_cache
   before_destroy :decrement_counter_cache
   belongs_to :article
   has_one :event, :dependent =&gt; :destroy
   before_create  :check_if_previewing
 
-  attr_accessible :article, :article_id, :user_id, :user, :excerpt, :body, :author, :author_url, :author_email, :author_ip, :updater_id, :updater, :comment_age, :user_agent, :referrer, :preview
+  attr_accessible :article, :article_id, :user_id, :user, :excerpt, :body, :author, :author_url, :author_email, :author_ip, :user_agent, :referrer, :preview
   attr_accessor :preview
   class Previewing &lt; StandardError; end
 
@@ -78,12 +77,6 @@ class Comment &lt; Content
   end
 
   protected
-    def sanitize_attributes
-      [:author, :author_url, :author_email, :author_ip, :user_agent, :referrer].each do |a|
-        self.send(&quot;#{a}=&quot;, CGI::escapeHTML(self.send(a).to_s))
-      end
-    end
-
     def snag_article_attributes
       self.filter ||= article.site.filter
       [:site, :title, :published_at, :permalink].each { |a| self.send(&quot;#{a}=&quot;, article.send(a)) }</diff>
      <filename>app/models/comment.rb</filename>
    </modified>
    <modified>
      <diff>@@ -20,8 +20,8 @@ class Resources &lt; Attachments
   def [](filename)
     path = 
       case filename
-        when /\.js$/i  then 'javascripts'
-        when /\.css$/i then 'stylesheets'
+        when /\.js\z/i  then 'javascripts'
+        when /\.css\z/i then 'stylesheets'
         else                'images'
       end
       </diff>
      <filename>app/models/resources.rb</filename>
    </modified>
    <modified>
      <diff>@@ -260,8 +260,8 @@ class Site &lt; ActiveRecord::Base
     end
 
     def check_permalink_style
-      permalink_style.sub! /^\//, ''
-      permalink_style.sub! /\/$/, ''
+      permalink_style.sub! /\A\//, ''
+      permalink_style.sub! /\/\z/, ''
       pieces = permalink_style.split('/')
       errors.add :permalink_style, 'cannot have blank paths' if pieces.any?(&amp;:blank?)
       pieces.each do |p|</diff>
      <filename>app/models/site.rb</filename>
    </modified>
    <modified>
      <diff>@@ -7,8 +7,8 @@ class Templates &lt; Attachments
   end
   
   def [](template_name)
-    template_name = File.basename(template_name.to_s).sub /#{theme.extension}$/, ''
-    theme.path + &quot;#{template_name =~ /layout$/ ? 'layouts' : 'templates'}/#{template_name}#{theme.extension}&quot;
+    template_name = File.basename(template_name.to_s).sub /#{theme.extension}\z/, ''
+    theme.path + &quot;#{template_name =~ /layout\z/ ? 'layouts' : 'templates'}/#{template_name}#{theme.extension}&quot;
   end
 
   def collect_templates(template_type, *custom_templates)</diff>
      <filename>app/models/templates.rb</filename>
    </modified>
    <modified>
      <diff>@@ -9,7 +9,7 @@ class Theme
     dest        = options[:to].is_a?(Pathname) ? options[:to] : Pathname.new(options[:to] || '.')
     basename    = dest.basename.to_s
     if dest.exist? || basename == 'current'
-      basename  = basename =~ /(.*)_(\d+)$/ ? $1 : basename
+      basename  = basename =~ /(.*)_(\d+)\z/ ? $1 : basename
       number    = $2 ? $2.to_i + 1 : 2
       dirname   = dest.dirname
       dest      = dirname + &quot;#{basename}_#{number}&quot;
@@ -27,7 +27,7 @@ class Theme
         dir_path = Pathname.new(dest + dir)
         FileUtils.mkdir_p dir_path unless dir_path.exist?
         z.dir.entries(dir).each do |entry|
-          next unless entry =~ /(\.\w+)$/ &amp;&amp; allowed_extensions.include?($1)
+          next unless entry =~ /(\.\w+)\z/ &amp;&amp; allowed_extensions.include?($1)
           z.file.open(File.join(dir, entry)) { |zf| File.open(dir_path + entry, 'wb') { |f| f &lt;&lt; zf.read } }
         end
       end
@@ -47,7 +47,7 @@ class Theme
       @base_path = base
       @path      = Pathname.new(@base_path)
     end
-    layout = (@path + &quot;layouts&quot;).children(false).select {|v| v.to_s =~ /^layout/}[0] if (@path + &quot;layouts&quot;).directory?
+    layout = (@path + &quot;layouts&quot;).children(false).select {|v| v.to_s =~ /\Alayout/}[0] if (@path + &quot;layouts&quot;).directory?
     @extension = layout.extname if layout
   end
 </diff>
      <filename>app/models/theme.rb</filename>
    </modified>
    <modified>
      <diff>@@ -32,6 +32,7 @@ class User &lt; ActiveRecord::Base
 
   # Authenticates a user by their login name and unencrypted password.  Returns the user or nil.
   def self.authenticate_for(site, login, password)
+    return nil if site.nil? || login.nil? || login.blank? || password.nil? || password.blank?
     u = find(:first, @@membership_options.merge(
       :conditions =&gt; ['users.login = ? and (memberships.site_id = ? or users.admin = ?)', login, site.id, true]))
     u &amp;&amp; u.authenticated?(password) ? u : nil
@@ -55,6 +56,7 @@ class User &lt; ActiveRecord::Base
   end
 
   def self.find_by_token(site, token)
+    return nil if site.nil? || token.nil? || token.blank?
     find(:first, @@membership_options.merge(:conditions =&gt; ['token = ? and token_expires_at &gt; ? and (memberships.site_id = ? or users.admin = ?)', token, Time.now.utc, site.id, true]))
   end
   </diff>
      <filename>app/models/user.rb</filename>
    </modified>
    <modified>
      <diff>@@ -66,8 +66,13 @@ Rails::Initializer.run do |config|
     config.active_record.observers = [:article_observer, :comment_observer]
   end
 
-  # Allow table tags in untrusted HTML.
+  # Allow table tags in untrusted HTML, but block img tags to prevent
+  # SRC attributes from being used in CSRF attacks.
   config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td']
+  config.after_initialize do
+    ActionView::Base.sanitized_allowed_tags.delete 'img'
+    ActionView::Base.sanitized_allowed_attributes.delete 'src'
+  end
 
   # We're slowly moving the contents of vendor and vender/plugins into
   # vendor/gems by adding config.gem declarations.</diff>
      <filename>config/environment.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,5 +1,5 @@
 # This file is auto-generated from the current state of the database. Instead of editing this file, 
-# please use the migrations feature of ActiveRecord to incrementally modify your database, and
+# please use the migrations feature of Active Record to incrementally modify your database, and
 # then regenerate this schema definition.
 #
 # Note that this schema.rb definition is the authoritative source for your database schema. If you need
@@ -9,7 +9,7 @@
 #
 # It's strongly recommended to check this file into your version control system.
 
-ActiveRecord::Schema.define(:version =&gt; 76) do
+ActiveRecord::Schema.define(:version =&gt; 20081219130711) do
 
   create_table &quot;assets&quot;, :force =&gt; true do |t|
     t.string   &quot;content_type&quot;
@@ -110,8 +110,8 @@ ActiveRecord::Schema.define(:version =&gt; 76) do
     t.integer  &quot;assets_count&quot;,                  :default =&gt; 0
   end
 
-  add_index &quot;contents&quot;, [&quot;published_at&quot;], :name =&gt; &quot;idx_articles_published&quot;
   add_index &quot;contents&quot;, [&quot;article_id&quot;, &quot;approved&quot;, &quot;type&quot;], :name =&gt; &quot;idx_comments&quot;
+  add_index &quot;contents&quot;, [&quot;published_at&quot;], :name =&gt; &quot;idx_articles_published&quot;
 
   create_table &quot;events&quot;, :force =&gt; true do |t|
     t.string   &quot;mode&quot;</diff>
      <filename>db/schema.rb</filename>
    </modified>
    <modified>
      <diff>@@ -74,7 +74,17 @@ module AuthenticatedSystem
     def login_from_cookie
       return unless cookies[:token] &amp;&amp; !logged_in?
       self.current_user = site.user_by_token(cookies[:token])
-      cookies[:token] = { :value =&gt; self.current_user.reset_token! , :expires =&gt; self.current_user.token_expires_at } if logged_in?
+      # TODO - We allow the token to be changed on GET requests and we log
+      # the user in.  I haven't fully analyzed the consequences of allowing
+      # session and token updates on hostile GET requests triggered by CSRF
+      # attacks.  If this helps out in some kind of attack, it would affect
+      # almost every single web application in existence.
+      ActiveRecord::Base.with_writable_records do
+        cookies[:token] = {
+          :value =&gt; self.current_user.reset_token!,
+          :expires =&gt; self.current_user.token_expires_at
+        } if logged_in?
+      end
       true
     end
 </diff>
      <filename>lib/authenticated_system.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 module Format
   # yes this is valid ruby, even if textmate's highlighter can't grok it
-  DOMAIN = /^([a-z0-9]([-a-z0-9]*[a-z0-9])?\.)+((a[cdefgilmnoqrstuwxz]|aero|arpa)|(b[abdefghijmnorstvwyz]|biz)|(c[acdfghiklmnorsuvxyz]|cat|com|coop)|d[ejkmoz]|(e[ceghrstu]|edu)|f[ijkmor]|(g[abdefghilmnpqrstuwy]|gov)|(h[kmnrtu]#{RAILS_ENV=='test'?'|host':''})|(i[delmnoqrst]|info|int)|(j[emop]|jobs)|k[eghimnprwyz]|l[abcikrstuvy]|(m[acdghklmnopqrstuvwxyz]|mil|mobi|museum)|(n[acefgilopruz]|name|net)|(om|org)|(p[aefghklmnrstwy]|pro)|qa|r[eouw]|s[abcdeghijklmnortvyz]|(t[cdfghjklmnoprtvwz]|travel)|u[agkmsyz]|v[aceginu]|w[fs]|y[etu]|z[amw])$/ unless const_defined?(:DOMAIN)
-  STRING = /^[a-z0-9-]+$/
+  DOMAIN = /\A([a-z0-9]([-a-z0-9]*[a-z0-9])?\.)+((a[cdefgilmnoqrstuwxz]|aero|arpa)|(b[abdefghijmnorstvwyz]|biz)|(c[acdfghiklmnorsuvxyz]|cat|com|coop)|d[ejkmoz]|(e[ceghrstu]|edu)|f[ijkmor]|(g[abdefghilmnpqrstuwy]|gov)|(h[kmnrtu]#{RAILS_ENV=='test'?'|host':''})|(i[delmnoqrst]|info|int)|(j[emop]|jobs)|k[eghimnprwyz]|l[abcikrstuvy]|(m[acdghklmnopqrstuvwxyz]|mil|mobi|museum)|(n[acefgilopruz]|name|net)|(om|org)|(p[aefghklmnrstwy]|pro)|qa|r[eouw]|s[abcdeghijklmnortvyz]|(t[cdfghjklmnoprtvwz]|travel)|u[agkmsyz]|v[aceginu]|w[fs]|y[etu]|z[amw])\z/ unless const_defined?(:DOMAIN)
+  STRING = /\A[a-z0-9-]+\z/
   EMAIL  = /(\A(\s*)\Z)|(\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z)/i
 end
\ No newline at end of file</diff>
      <filename>lib/format.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,7 +1,7 @@
 # Object model of a plugin in plugins/vendor. May or may not have an internal Mephisto::Plugins::Plugin (an AR).
 module Mephisto
   class DirectoryPlugin
-    @@filter = /^mephisto_(\w+)$/
+    @@filter = /\Amephisto_(\w+)\z/
     attr_accessor :plugin, :path
     
     def self.scan</diff>
      <filename>lib/mephisto/directory_plugin.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,7 +1,7 @@
 module Mephisto
   class Dispatcher
     PERMALINK_OPTIONS = { :year =&gt; '\d{4}', :month =&gt; '\d{1,2}', :day =&gt; '\d{1,2}', :permalink =&gt; '[\w\-]+', :id =&gt; '\d+' }
-    PERMALINK_VAR     = /^:([a-z]+)$/
+    PERMALINK_VAR     = /\A:([a-z]+)\z/
 
     def self.run(site, path)
       # check for any bad urls like /foo//bar
@@ -80,7 +80,7 @@ module Mephisto
             result.first[var] = match[i+1]
           end
           result &lt;&lt; match[variables.size + 2] # comments | comments.xml | changes.xml
-          result.last.gsub!(/\/(.*)$/, '') if result.last
+          result.last.gsub!(/\/(.*)\z/, '') if result.last
           result &lt;&lt; match[variables.size + 4] # comment id
         end
       end</diff>
      <filename>lib/mephisto/dispatcher.rb</filename>
    </modified>
    <modified>
      <diff>@@ -29,7 +29,7 @@ module Mephisto
     end
     
     def mephisto_plugin?
-      name =~ /^mephisto_(\w+)$/
+      name =~ /\Amephisto_(\w+)\z/
     end
     
     def configurable?
@@ -37,7 +37,7 @@ module Mephisto
     end
     
     def mephisto_name
-      name.sub /^mephisto_/, ''
+      name.sub /\Amephisto_/, ''
     end
     alias :conf_name :mephisto_name
   end</diff>
      <filename>lib/mephisto/plugin.rb</filename>
    </modified>
    <modified>
      <diff>@@ -87,7 +87,7 @@ module Mephisto
     end
     
     protected
-      @@sanitize_path_regex = /^(\/)|(https?:\/\/)/
+      @@sanitize_path_regex = /\A(\/)|(https?:\/\/)/
       def self.sanitize_path(path)
         path =~ @@sanitize_path_regex ? path : &quot;/#{path.split(&quot;://&quot;).last}&quot;
       end</diff>
      <filename>lib/mephisto/routing.rb</filename>
    </modified>
    <modified>
      <diff>@@ -2,7 +2,7 @@ module XMLRPC
   module Convert
     def self.dateTime(str)
       case str
-      when /^(-?\d\d\d\d)-?(\d\d)-?(\d\d)T(\d\d):(\d\d):(\d\d)(?:Z|([+-])(\d\d):?(\d\d))?$/
+      when /\A(-?\d\d\d\d)-?(\d\d)-?(\d\d)T(\d\d):(\d\d):(\d\d)(?:Z|([+-])(\d\d):?(\d\d))?\z/
         a = [$1, $2, $3, $4, $5, $6].collect{|i| i.to_i}
         if $7
           ofs = $8.to_i*3600 + $9.to_i*60
@@ -16,7 +16,7 @@ module XMLRPC
           a = [ utc.year, utc.month, utc.day, utc.hour, utc.min, utc.sec ]
         end
         XMLRPC::DateTime.new(*a)
-      when /^(-?\d\d)-?(\d\d)-?(\d\d)T(\d\d):(\d\d):(\d\d)(Z|([+-]\d\d):(\d\d))?$/
+      when /\A(-?\d\d)-?(\d\d)-?(\d\d)T(\d\d):(\d\d):(\d\d)(Z|([+-]\d\d):(\d\d))?\z/
         a = [$1, $2, $3, $4, $5, $6].collect{|i| i.to_i}
         if a[0] &lt; 70
           a[0] += 2000</diff>
      <filename>lib/xmlrpc_patch.rb</filename>
    </modified>
    <modified>
      <diff>@@ -13,7 +13,7 @@ Site.blueprint do
   title              { Sham.title }
   host               { Sham.host }
   filter             { 'textile_filter' }
-  approve_comments   { false }
+  approve_comments   { true }
   comment_age        { 30 }
   timezone           { TZInfo::Timezone.new(&quot;America/New_York&quot;) }
   articles_per_page  { 15 }</diff>
      <filename>spec/blueprints.rb</filename>
    </modified>
    <modified>
      <diff>@@ -5,7 +5,7 @@ class CommentDropTest &lt; Test::Unit::TestCase
   
   def setup
     @comment = contents(:welcome_comment).to_liquid
-    @mock_comment = [:published_at, :created_at, :author, :author_email, :author_ip, :title, :approved?].inject({:body_html =&gt; 'foo'}) { |h, i| h.update i =&gt; true }
+    @mock_comment = [:published_at, :created_at, :title, :approved?].inject({:body_html =&gt; 'foo', :author =&gt; 'Bob', :author_email =&gt; 'bob@example.com', :author_ip =&gt; '127.0.0.1' }) { |h, i| h.update i =&gt; true }
   end
   
   def test_should_convert_comment_to_drop
@@ -36,10 +36,14 @@ class CommentDropTest &lt; Test::Unit::TestCase
     assert_equal %Q{&lt;a href=&quot;https://abc&quot;&gt;rico&lt;/a&gt;}, @comment.author_link
     @comment.source.author     = '&lt;strong&gt;rico&lt;/strong&gt;'
     @comment.source.author_url = '&lt;strong&gt;https://abc&lt;/strong&gt;'
-    @comment.source.send(:sanitize_attributes)
-    assert_equal %Q{&lt;a href=&quot;http://&amp;lt;strong&amp;gt;https://abc&amp;lt;/strong&amp;gt;&quot;&gt;&amp;lt;strong&amp;gt;rico&amp;lt;/strong&amp;gt;&lt;/a&gt;}, @comment.author_link
+    assert_equal %Q{&lt;a href=&quot;http://&amp;lt;strong&amp;gt;https://abc&amp;lt;/strong&amp;gt;&quot;&gt;&amp;lt;strong&amp;gt;rico&amp;lt;/strong&amp;gt;&lt;/a&gt;}, @comment.source.to_liquid.author_link
   end
   
+  def test_should_not_be_fooled_by_newlines_in_author_url
+    @comment.source.author_url = &quot;javascript:alert('Oops')\nhttp://&quot;
+    assert_equal &quot;http://javascript:alert('Oops')\nhttp://&quot;, @comment.author_url
+  end
+
   def test_should_show_filtered_text
     comment  = contents(:welcome).comments.create :body =&gt; '*test* comment', :author =&gt; 'bob', :author_ip =&gt; '127.0.0.1'
     assert_valid comment
@@ -48,6 +52,15 @@ class CommentDropTest &lt; Test::Unit::TestCase
     assert_equal '&lt;p&gt;&lt;strong&gt;test&lt;/strong&gt; comment&lt;/p&gt;', liquid.before_method(:body)
   end
   
+  def test_should_not_allow_img_tags
+    # img tags can be used in CSRF attacks against actions that
+    # accidentally allow GET requests to perform destructive actions.
+    comment = contents(:welcome_comment)
+    comment.body = 'a&lt;img src=&quot;/admin/site/destroy/1&quot; /&gt;b'
+    comment.save!
+    assert_equal '&lt;p&gt;ab&lt;/p&gt;', comment.to_liquid.before_method(:body)
+  end
+
   def test_comment_url
     t = Time.now.utc - 3.days
     assert_equal &quot;/#{t.year}/#{t.month}/#{t.day}/welcome-to-mephisto&quot;, @comment.url
@@ -74,4 +87,4 @@ class CommentDropTest &lt; Test::Unit::TestCase
         def stub.id() 55; end
       end
     end
-end
\ No newline at end of file
+end</diff>
      <filename>test/unit/comment_drop_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -27,9 +27,15 @@ class FlickrMacro &lt; FilteredColumn::Macros::Base
     if(caption.blank?)
       captioncode = &quot;&quot;
     else
-      captioncode = &quot;&lt;p class=\&quot;caption\&quot; style=\&quot;width:#{width}px\&quot;&gt;#{caption}&lt;/p&gt;&quot;
+      captioncode = &quot;&lt;p class=\&quot;caption\&quot; style=\&quot;width:#{h width}px\&quot;&gt;#{h caption}&lt;/p&gt;&quot;
     end
 
-    &quot;&lt;div style=\&quot;#{style}\&quot; class=\&quot;flickrplugin\&quot;&gt;&lt;a href=\&quot;#{imagelink}\&quot;&gt;&lt;img src=\&quot;#{imageurl}\&quot; width=\&quot;#{width}\&quot; height=\&quot;#{height}\&quot; alt=\&quot;#{alt}\&quot; title=\&quot;#{title}\&quot;/&gt;&lt;/a&gt;#{captioncode}&lt;/div&gt;&quot;
+    &quot;&lt;div style=\&quot;#{h style}\&quot; class=\&quot;flickrplugin\&quot;&gt;&lt;a href=\&quot;#{h imagelink}\&quot;&gt;&lt;img src=\&quot;#{h imageurl}\&quot; width=\&quot;#{h width}\&quot; height=\&quot;#{h height}\&quot; alt=\&quot;#{h alt}\&quot; title=\&quot;#{h title}\&quot;/&gt;&lt;/a&gt;#{captioncode}&lt;/div&gt;&quot;
   end
-end
\ No newline at end of file
+
+  private
+
+  def h str
+    CGI.escapeHTML(str)
+  end
+end</diff>
      <filename>vendor/plugins/filtered_column_flickr_macro/lib/flickr_macro.rb</filename>
    </modified>
    <modified>
      <diff>@@ -2,4 +2,3 @@ $:.unshift(File.dirname(__FILE__) + '/../lib')
 
 require 'test/unit'
 require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb'))
-require 'breakpoint'
\ No newline at end of file</diff>
      <filename>vendor/plugins/filtered_column_flickr_macro/test/abstract_unit.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>0c628dc7e2ff8b7529f43e4090d37adc8e86ba54</id>
    </parent>
    <parent>
      <id>24bfceaae2b1edf7c2f92fb9a7716a523ba7f417</id>
    </parent>
  </parents>
  <author>
    <name>Eric Kidd</name>
    <email>git@randomhacks.net</email>
  </author>
  <url>http://github.com/emk/mephisto/commit/f2712773d0f8beed1be8ec97fcd39a27f6ff4159</url>
  <id>f2712773d0f8beed1be8ec97fcd39a27f6ff4159</id>
  <committed-date>2008-12-20T08:04:57-08:00</committed-date>
  <authored-date>2008-12-20T08:04:57-08:00</authored-date>
  <message>Merge branch 'master' into new-plugins

Conflicts:

	lib/mephisto/plugin.rb</message>
  <tree>1e506c6f381b3ba951c3c9b76de90aeccf33aad0</tree>
  <committer>
    <name>Eric Kidd</name>
    <email>git@randomhacks.net</email>
  </committer>
</commit>
