aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/activesupport_cache_extensions.rb2
-rw-r--r--lib/alaveteli_external_command.rb8
-rw-r--r--lib/alaveteli_localization.rb21
-rw-r--r--lib/configuration.rb126
-rw-r--r--lib/cookie_store_with_line_break_fix.rb19
-rw-r--r--lib/google_translate.rb18
-rw-r--r--lib/i18n_fixes.rb23
-rw-r--r--lib/mail_handler/backends/mail_backend.rb84
-rw-r--r--lib/mail_handler/backends/mail_extensions.rb93
-rw-r--r--lib/mail_handler/backends/tmail_backend.rb288
-rw-r--r--lib/mail_handler/backends/tmail_extensions.rb138
-rw-r--r--lib/mail_handler/mail_handler.rb25
-rw-r--r--lib/no_constraint_disabling.rb110
-rw-r--r--lib/normalize_string.rb86
-rw-r--r--lib/old_rubygems_patch.rb46
-rw-r--r--lib/patches/fixtures_constraint_disabling.rb21
-rw-r--r--lib/public_body_categories.rb2
-rw-r--r--lib/rack_quote_monkeypatch.rb65
-rw-r--r--lib/routing_filters.rb2
-rw-r--r--lib/sendmail_return_path.rb21
-rw-r--r--lib/tasks/.gitkeep0
-rw-r--r--lib/tasks/gettext.rake4
-rw-r--r--lib/tasks/rspec.rake148
-rw-r--r--lib/tasks/temp.rake272
-rw-r--r--lib/tasks/themes.rake22
-rw-r--r--lib/tasks/translation.rake58
-rw-r--r--lib/timezone_fixes.rb26
-rw-r--r--lib/willpaginate_extension.rb59
28 files changed, 762 insertions, 1025 deletions
diff --git a/lib/activesupport_cache_extensions.rb b/lib/activesupport_cache_extensions.rb
index f15d72894..2791d5996 100644
--- a/lib/activesupport_cache_extensions.rb
+++ b/lib/activesupport_cache_extensions.rb
@@ -2,7 +2,7 @@
# Extensions / fixes to ActiveSupport::Cache
#
# Copyright (c) 2009 UK Citizens Online Democracy. All rights reserved.
-# Email: francis@mysociety.org; WWW: http://www.mysociety.org/
+# Email: hello@mysociety.org; WWW: http://www.mysociety.org/
# Monkeypatch! ./activesupport/lib/active_support/cache/file_store.rb
diff --git a/lib/alaveteli_external_command.rb b/lib/alaveteli_external_command.rb
index 24b4b1aa8..fbdee8a62 100644
--- a/lib/alaveteli_external_command.rb
+++ b/lib/alaveteli_external_command.rb
@@ -8,6 +8,7 @@ module AlaveteliExternalCommand
# :stdin_string - stdin string to pass to the process
# :binary_output - boolean flag for treating the output as binary or text (only significant
# ruby 1.9 and above)
+ # :memory_limit - maximum amount of memory (in bytes) available to the process
def run(program_name, *args)
# Run an external program, and return its output.
# Standard error is suppressed unless the program
@@ -21,14 +22,14 @@ module AlaveteliExternalCommand
program_path = program_name
else
found = false
- Configuration::utility_search_path.each do |d|
+ AlaveteliConfiguration::utility_search_path.each do |d|
program_path = File.join(d, program_name)
if File.file? program_path and File.executable? program_path
found = true
break
end
end
- raise "Could not find #{program_name} in any of #{Configuration::utility_search_path.join(', ')}" if !found
+ raise "Could not find #{program_name} in any of #{AlaveteliConfiguration::utility_search_path.join(', ')}" if !found
end
xc = ExternalCommand.new(program_path, *args)
@@ -38,6 +39,9 @@ module AlaveteliExternalCommand
if opts.has_key? :binary_output
xc.binary_mode = opts[:binary_output]
end
+ if opts.has_key? :memory_limit
+ xc.memory_limit = opts[:memory_limit]
+ end
xc.run(opts[:stdin_string] || "", opts[:env] || {})
if xc.status != 0
diff --git a/lib/alaveteli_localization.rb b/lib/alaveteli_localization.rb
new file mode 100644
index 000000000..6daab124a
--- /dev/null
+++ b/lib/alaveteli_localization.rb
@@ -0,0 +1,21 @@
+class AlaveteliLocalization
+ class << self
+ def set_locales(available_locales, default_locale)
+ # fallback locale and available locales
+ available_locales = available_locales.split(/ /)
+ FastGettext.default_available_locales = available_locales
+ I18n.locale = default_locale
+ I18n.available_locales = available_locales.map { |locale_name| locale_name.to_sym }
+ I18n.default_locale = default_locale
+ end
+
+ def set_default_text_domain(name, path)
+ FastGettext.add_text_domain name, :path => path, :type => :po
+ FastGettext.default_text_domain = name
+ end
+
+ def set_default_locale_urls(include_default_locale_in_urls)
+ RoutingFilter::Locale.include_default_locale = include_default_locale_in_urls
+ end
+ end
+end
diff --git a/lib/configuration.rb b/lib/configuration.rb
index 4a0e0339b..03c4ac616 100644
--- a/lib/configuration.rb
+++ b/lib/configuration.rb
@@ -1,64 +1,78 @@
+require File.dirname(__FILE__) + '/../commonlib/rblib/config'
+
+# Load intial mySociety config
+if ENV["RAILS_ENV"] == "test"
+ MySociety::Config.set_file(File.join(File.dirname(__FILE__), '..', 'config', 'test'), true)
+else
+ MySociety::Config.set_file(File.join(File.dirname(__FILE__), '..', 'config', 'general'), true)
+end
+MySociety::Config.load_default
+
# Configuration values with defaults
# TODO: Make this return different values depending on the current rails environment
-module Configuration
- DEFAULTS = {
- :ADMIN_PASSWORD => '',
- :ADMIN_USERNAME => '',
- :AVAILABLE_LOCALES => '',
- :BLACKHOLE_PREFIX => 'do-not-reply-to-this-address',
- :BLOG_FEED => '',
- :CONTACT_EMAIL => 'contact@localhost',
- :CONTACT_NAME => 'Alaveteli',
- :COOKIE_STORE_SESSION_SECRET => 'this default is insecure as code is open source, please override for live sites in config/general; this will do for local development',
- :DEBUG_RECORD_MEMORY => false,
- :DEFAULT_LOCALE => '',
- :DISABLE_EMERGENCY_USER => false,
- :DOMAIN => 'localhost:3000',
- :EXCEPTION_NOTIFICATIONS_FROM => '',
- :EXCEPTION_NOTIFICATIONS_TO => '',
- :FORCE_REGISTRATION_ON_NEW_REQUEST => false,
- :FORCE_SSL => true,
- :FORWARD_NONBOUNCE_RESPONSES_TO => 'user-support@localhost',
- :FRONTPAGE_PUBLICBODY_EXAMPLES => '',
- :GA_CODE => '',
- :GAZE_URL => '',
- :HTML_TO_PDF_COMMAND => '',
- :INCLUDE_DEFAULT_LOCALE_IN_URLS => true,
- :INCOMING_EMAIL_DOMAIN => 'localhost',
- :INCOMING_EMAIL_PREFIX => '',
- :INCOMING_EMAIL_SECRET => 'dummysecret',
- :ISO_COUNTRY_CODE => 'GB',
- :MAX_REQUESTS_PER_USER_PER_DAY => '',
- :MTA_LOG_TYPE => 'exim',
- :NEW_RESPONSE_REMINDER_AFTER_DAYS => [3, 10, 24],
- :OVERRIDE_ALL_PUBLIC_BODY_REQUEST_EMAILS => '',
- :RAW_EMAILS_LOCATION => 'files/raw_emails',
- :READ_ONLY => '',
- :RECAPTCHA_PRIVATE_KEY => 'x',
- :RECAPTCHA_PUBLIC_KEY => 'x',
- :REPLY_LATE_AFTER_DAYS => 20,
- :REPLY_VERY_LATE_AFTER_DAYS => 40,
- :SITE_NAME => 'Alaveteli',
- :SKIP_ADMIN_AUTH => false,
- :SPECIAL_REPLY_VERY_LATE_AFTER_DAYS => 60,
- :THEME_BRANCH => false,
- :THEME_URL => "",
- :THEME_URLS => [],
- :TIME_ZONE => "UTC",
- :TRACK_SENDER_EMAIL => 'contact@localhost',
- :TRACK_SENDER_NAME => 'Alaveteli',
- :TWITTER_USERNAME => '',
- :TWITTER_WIDGET_ID => false,
- :USE_DEFAULT_BROWSER_LANGUAGE => true,
- :USE_GHOSTSCRIPT_COMPRESSION => false,
- :UTILITY_SEARCH_PATH => ["/usr/bin", "/usr/local/bin"],
- :VARNISH_HOST => '',
- :WORKING_OR_CALENDAR_DAYS => 'working',
- }
+module AlaveteliConfiguration
+ if !const_defined?(:DEFAULTS)
+
+ DEFAULTS = {
+ :ADMIN_PASSWORD => '',
+ :ADMIN_USERNAME => '',
+ :AVAILABLE_LOCALES => '',
+ :BLACKHOLE_PREFIX => 'do-not-reply-to-this-address',
+ :BLOG_FEED => '',
+ :CONTACT_EMAIL => 'contact@localhost',
+ :CONTACT_NAME => 'Alaveteli',
+ :COOKIE_STORE_SESSION_SECRET => 'this default is insecure as code is open source, please override for live sites in config/general; this will do for local development',
+ :DEBUG_RECORD_MEMORY => false,
+ :DEFAULT_LOCALE => '',
+ :DISABLE_EMERGENCY_USER => false,
+ :DOMAIN => 'localhost:3000',
+ :DONATION_URL => '',
+ :EXCEPTION_NOTIFICATIONS_FROM => '',
+ :EXCEPTION_NOTIFICATIONS_TO => '',
+ :FORCE_REGISTRATION_ON_NEW_REQUEST => false,
+ :FORCE_SSL => true,
+ :FORWARD_NONBOUNCE_RESPONSES_TO => 'user-support@localhost',
+ :FRONTPAGE_PUBLICBODY_EXAMPLES => '',
+ :GA_CODE => '',
+ :GAZE_URL => '',
+ :HTML_TO_PDF_COMMAND => '',
+ :INCLUDE_DEFAULT_LOCALE_IN_URLS => true,
+ :INCOMING_EMAIL_DOMAIN => 'localhost',
+ :INCOMING_EMAIL_PREFIX => '',
+ :INCOMING_EMAIL_SECRET => 'dummysecret',
+ :ISO_COUNTRY_CODE => 'GB',
+ :MAX_REQUESTS_PER_USER_PER_DAY => '',
+ :MTA_LOG_TYPE => 'exim',
+ :NEW_RESPONSE_REMINDER_AFTER_DAYS => [3, 10, 24],
+ :OVERRIDE_ALL_PUBLIC_BODY_REQUEST_EMAILS => '',
+ :RAW_EMAILS_LOCATION => 'files/raw_emails',
+ :READ_ONLY => '',
+ :RECAPTCHA_PRIVATE_KEY => 'x',
+ :RECAPTCHA_PUBLIC_KEY => 'x',
+ :REPLY_LATE_AFTER_DAYS => 20,
+ :REPLY_VERY_LATE_AFTER_DAYS => 40,
+ :SITE_NAME => 'Alaveteli',
+ :SKIP_ADMIN_AUTH => false,
+ :SPECIAL_REPLY_VERY_LATE_AFTER_DAYS => 60,
+ :THEME_BRANCH => false,
+ :THEME_URL => "",
+ :THEME_URLS => [],
+ :TIME_ZONE => "UTC",
+ :TRACK_SENDER_EMAIL => 'contact@localhost',
+ :TRACK_SENDER_NAME => 'Alaveteli',
+ :TWITTER_USERNAME => '',
+ :TWITTER_WIDGET_ID => false,
+ :USE_DEFAULT_BROWSER_LANGUAGE => true,
+ :USE_GHOSTSCRIPT_COMPRESSION => false,
+ :UTILITY_SEARCH_PATH => ["/usr/bin", "/usr/local/bin"],
+ :VARNISH_HOST => '',
+ :WORKING_OR_CALENDAR_DAYS => 'working',
+ }
+ end
- def Configuration.method_missing(name)
+ def AlaveteliConfiguration.method_missing(name)
key = name.to_s.upcase
if DEFAULTS.has_key?(key.to_sym)
MySociety::Config.get(key, DEFAULTS[key.to_sym])
diff --git a/lib/cookie_store_with_line_break_fix.rb b/lib/cookie_store_with_line_break_fix.rb
deleted file mode 100644
index dc623fbd0..000000000
--- a/lib/cookie_store_with_line_break_fix.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# See https://makandracards.com/makandra/9443-rails-2-s-cookiestore-produces-invalid-cookie-data-causing-tests-to-break
-
-# Should be able to remove this when we upgrade to Rails 3
-
-module ActionController
- module Session
- CookieStore.class_eval do
-
- def call_with_line_break_fix(*args)
- status, headers, body = call_without_line_break_fix(*args)
- headers['Set-Cookie'].gsub! "\n\n", "\n" if headers['Set-Cookie'].present?
- [ status, headers, body ]
- end
-
- alias_method_chain :call, :line_break_fix
-
- end
- end
-end \ No newline at end of file
diff --git a/lib/google_translate.rb b/lib/google_translate.rb
deleted file mode 100644
index 369e1de3b..000000000
--- a/lib/google_translate.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require 'rubygems'
-require 'net/http'
-require 'open-uri'
-require 'cgi'
-require 'json'
-
-def detect_language(request, translate_string)
- google_api_key = ''
- user_ip = URI.encode(request.env['REMOTE_ADDR'])
- translate_string = URI.encode(translate_string)
- url = "http://ajax.googleapis.com/ajax/services/language/detect?v=1.0&q=#{translate_string}&userip=#{user_ip}"
- if google_api_key != ''
- url += "&key=#{google_api_key}"
- end
- response = Net::HTTP.get_response(URI.parse(url))
- result = JSON.parse(response.body)
- result['responseData']['language']
-end
diff --git a/lib/i18n_fixes.rb b/lib/i18n_fixes.rb
index 82d1b2c3a..9c1206215 100644
--- a/lib/i18n_fixes.rb
+++ b/lib/i18n_fixes.rb
@@ -17,7 +17,7 @@ end
def n_(*keys)
# The last parameter should be the values to do the interpolation with
if keys.count > 3
- options = keys.pop
+ options = keys.pop
else
options = {}
end
@@ -33,7 +33,7 @@ def gettext_interpolate(string, values)
safe = string.html_safe?
string = string.to_str.gsub(MATCH) do
pattern, key = $1, $1.to_sym
-
+
if !values.include?(key)
raise I18n::MissingInterpolationArgument.new(pattern, string)
else
@@ -50,7 +50,7 @@ end
module I18n
- # used by Globalize plugin.
+ # used by Globalize plugin.
# XXX much of this stuff should (might?) be in newer versions of Rails
@@fallbacks = nil
class << self
@@ -120,7 +120,7 @@ module I18n
@defaults = defaults.map { |default| compute(default, false) }.flatten
end
attr_reader :defaults
-
+
def [](locale)
raise InvalidLocale.new(locale) if locale.nil?
locale = locale.to_sym
@@ -138,7 +138,7 @@ module I18n
end
protected
-
+
def compute(tags, include_defaults = true)
result = Array(tags).collect do |tag|
tags = I18n::Locale::Tag::Simple.tag(tag).self_and_parents.map! { |t| t.to_sym }
@@ -161,7 +161,18 @@ module GettextI18nRails
class Backend
def available_locales
FastGettext.available_locales.map{|l| l.to_sym} || []
- end
+ end
end
end
+# Monkeypatch Globalize to compensate for the way gettext_i18n_rails patches
+# I18n.locale= so that it changes underscores in locale names (as used in the gettext world)
+# to the dashes that I18n prefers
+module Globalize
+ class << self
+ def locale
+ read_locale || I18n.locale.to_s.gsub('-', '_').to_sym
+ end
+ end
+end
+
diff --git a/lib/mail_handler/backends/mail_backend.rb b/lib/mail_handler/backends/mail_backend.rb
index 0a12ab3bb..28c486e1b 100644
--- a/lib/mail_handler/backends/mail_backend.rb
+++ b/lib/mail_handler/backends/mail_backend.rb
@@ -1,4 +1,35 @@
require 'mail'
+require 'mapi/msg'
+require 'mapi/convert'
+
+module Mail
+ class Message
+
+ # The behaviour of the 'to' and 'cc' methods have changed
+ # between TMail and Mail; this monkey-patching restores the
+ # TMail behaviour. The key difference is that when there's an
+ # invalid address, e.g. '<foo@example.org', Mail returns the
+ # string as an ActiveSupport::Multibyte::Chars, whereas
+ # previously TMail would return nil.
+
+ alias_method :old_to, :to
+ alias_method :old_cc, :cc
+
+ def clean_addresses(old_method, val)
+ old_result = self.send(old_method, val)
+ old_result.class == Mail::AddressContainer ? old_result : nil
+ end
+
+ def to(val = nil)
+ self.clean_addresses :old_to, val
+ end
+
+ def cc(val = nil)
+ self.clean_addresses :old_cc, val
+ end
+
+ end
+end
module MailHandler
module Backends
@@ -38,7 +69,11 @@ module MailHandler
# Get the body of a mail part
def get_part_body(part)
- part.body.decoded
+ decoded = part.body.decoded
+ if part.content_type =~ /^text\//
+ decoded = convert_string_to_utf8_or_binary decoded, part.charset
+ end
+ decoded
end
# Return the first from field if any
@@ -60,7 +95,7 @@ module MailHandler
def get_from_address(mail)
first_from = first_from(mail)
if first_from
- if first_from.is_a?(String)
+ if first_from.is_a?(ActiveSupport::Multibyte::Chars)
return nil
else
return first_from.address
@@ -74,10 +109,10 @@ module MailHandler
def get_from_name(mail)
first_from = first_from(mail)
if first_from
- if first_from.is_a?(String)
+ if first_from.is_a?(ActiveSupport::Multibyte::Chars)
return nil
else
- return first_from.display_name ? eval(%Q{"#{first_from.display_name}"}) : nil
+ return (first_from.display_name || nil)
end
else
return nil
@@ -85,7 +120,7 @@ module MailHandler
end
def get_all_addresses(mail)
- envelope_to = mail['envelope-to'] ? [mail['envelope-to'].value] : []
+ envelope_to = mail['envelope-to'] ? [mail['envelope-to'].value.to_s] : []
((mail.to || []) +
(mail.cc || []) +
(envelope_to || [])).uniq
@@ -141,9 +176,14 @@ module MailHandler
end
elsif get_content_type(part) == 'application/ms-tnef'
# A set of attachments in a TNEF file
- part.rfc822_attachment = mail_from_tnef(part.body.decoded)
- if part.rfc822_attachment.nil?
- # Attached mail didn't parse, so treat as binary
+ begin
+ part.rfc822_attachment = mail_from_tnef(part.body.decoded)
+ if part.rfc822_attachment.nil?
+ # Attached mail didn't parse, so treat as binary
+ part.content_type = 'application/octet-stream'
+ end
+ rescue TNEFParsingError
+ part.rfc822_attachment = nil
part.content_type = 'application/octet-stream'
end
end
@@ -160,8 +200,11 @@ module MailHandler
part.parts.each{ |sub_part| expand_and_normalize_parts(sub_part, parent_mail) }
else
part_filename = get_part_file_name(part)
- charset = part.charset # save this, because overwriting content_type also resets charset
-
+ if part.has_charset?
+ original_charset = part.charset # save this, because overwriting content_type also resets charset
+ else
+ original_charset = nil
+ end
# Don't allow nil content_types
if get_content_type(part).nil?
part.content_type = 'application/octet-stream'
@@ -180,7 +223,9 @@ module MailHandler
# Use standard content types for Word documents etc.
part.content_type = normalise_content_type(get_content_type(part))
decode_attached_part(part, parent_mail)
- part.charset = charset
+ if original_charset
+ part.charset = original_charset
+ end
end
end
@@ -228,8 +273,15 @@ module MailHandler
def _get_attachment_leaves_recursive(part, within_rfc822_attachment, parent_mail)
leaves_found = []
if part.multipart?
- raise "no parts on multipart mail" if part.parts.size == 0
- if part.sub_type == 'alternative'
+ if part.parts.size == 0
+ # This is typically caused by a missing final
+ # MIME boundary, in which case the text of the
+ # message (including the opening MIME
+ # boundary) is in part.body, so just add this
+ # part as a leaf and treat it as text/plain:
+ part.content_type = "text/plain"
+ leaves_found += [part]
+ elsif part.sub_type == 'alternative'
best_part = choose_best_alternative(part)
leaves_found += _get_attachment_leaves_recursive(best_part,
within_rfc822_attachment,
@@ -315,8 +367,10 @@ module MailHandler
end
def address_from_string(string)
- Mail::Address.new(string).address
+ mail = Mail.new
+ mail.from = string
+ mail.from[0]
end
end
end
-end \ No newline at end of file
+end
diff --git a/lib/mail_handler/backends/mail_extensions.rb b/lib/mail_handler/backends/mail_extensions.rb
index f756abd1a..029331802 100644
--- a/lib/mail_handler/backends/mail_extensions.rb
+++ b/lib/mail_handler/backends/mail_extensions.rb
@@ -46,6 +46,15 @@ module Mail
self
end
+
+ def set_envelope_header
+ raw_string = raw_source.to_s
+ if match_data = raw_source.to_s.match(/\AFrom\s(#{TEXT}+)#{CRLF}/m)
+ set_envelope(match_data[1])
+ self.raw_source = raw_string.sub(match_data[0], "")
+ end
+ end
+
end
# A patched version of the parameter hash that handles nil values without throwing
@@ -64,4 +73,86 @@ module Mail
end.join(";\r\n\s")
end
end
-end \ No newline at end of file
+
+ # HACK: Backport encoding fixes for Ruby 1.8 from Mail 2.5
+ # Can be removed when we no longer support Ruby 1.8
+ class Ruby18
+ def Ruby18.b_value_decode(str)
+ match = str.match(/\=\?(.+)?\?[Bb]\?(.+)?\?\=/m)
+ if match
+ encoding = match[1]
+ str = Ruby18.decode_base64(match[2])
+ # Adding and removing trailing spaces is a workaround
+ # for Iconv.conv throwing an exception if it finds an
+ # invalid character at the end of the string, even
+ # with UTF-8//IGNORE:
+ # http://po-ru.com/diary/fixing-invalid-utf-8-in-ruby-revisited/
+ begin
+ str = Iconv.conv('UTF-8//IGNORE', fix_encoding(encoding), str + " ")[0...-4]
+ rescue Iconv::InvalidEncoding
+ end
+ end
+ str
+ end
+
+ def Ruby18.q_value_decode(str)
+ match = str.match(/\=\?(.+)?\?[Qq]\?(.+)?\?\=/m)
+ if match
+ encoding = match[1]
+ string = match[2].gsub(/_/, '=20')
+ # Remove trailing = if it exists in a Q encoding
+ string = string.sub(/\=$/, '')
+ str = Encodings::QuotedPrintable.decode(string)
+ # Adding and removing trailing spaces is a workaround
+ # for Iconv.conv throwing an exception if it finds an
+ # invalid character at the end of the string, even
+ # with UTF-8//IGNORE:
+ # http://po-ru.com/diary/fixing-invalid-utf-8-in-ruby-revisited/
+ str = Iconv.conv('UTF-8//IGNORE', fix_encoding(encoding), str + " ")[0...-4]
+ end
+ str
+ end
+
+ private
+
+ def Ruby18.fix_encoding(encoding)
+ case encoding.upcase
+ when 'UTF8'
+ 'UTF-8'
+ else
+ encoding
+ end
+ end
+ end
+ class Ruby19
+
+ def Ruby19.b_value_decode(str)
+ match = str.match(/\=\?(.+)?\?[Bb]\?(.+)?\?\=/m)
+ if match
+ encoding = match[1]
+ str = Ruby19.decode_base64(match[2])
+ # Rescue an ArgumentError arising from an unknown encoding.
+ begin
+ str.force_encoding(fix_encoding(encoding))
+ rescue ArgumentError
+ end
+ end
+ decoded = str.encode("utf-8", :invalid => :replace, :replace => "")
+ decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :replace => "").encode("utf-8")
+ end
+
+ def Ruby19.q_value_decode(str)
+ match = str.match(/\=\?(.+)?\?[Qq]\?(.+)?\?\=/m)
+ if match
+ encoding = match[1]
+ str = Encodings::QuotedPrintable.decode(match[2].gsub(/_/, '=20'))
+ # Backport line from mail 2.5 to strip a trailing = character
+ # Remove trailing = if it exists in a Q encoding
+ str = str.sub(/\=$/, '')
+ str.force_encoding(fix_encoding(encoding))
+ end
+ decoded = str.encode("utf-8", :invalid => :replace, :replace => "")
+ decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :replace => "").encode("utf-8")
+ end
+ end
+end
diff --git a/lib/mail_handler/backends/tmail_backend.rb b/lib/mail_handler/backends/tmail_backend.rb
deleted file mode 100644
index 1e241f261..000000000
--- a/lib/mail_handler/backends/tmail_backend.rb
+++ /dev/null
@@ -1,288 +0,0 @@
-module MailHandler
- module Backends
- module TmailBackend
-
- def backend()
- 'TMail'
- end
-
- # Turn raw data into a structured TMail::Mail object
- # Documentation at http://i.loveruby.net/en/projects/tmail/doc/
- def mail_from_raw_email(data)
- # Hack round bug in TMail's MIME decoding.
- # Report of TMail bug:
- # http://rubyforge.org/tracker/index.php?func=detail&aid=21810&group_id=4512&atid=17370
- copy_of_raw_data = data.gsub(/; boundary=\s+"/im,'; boundary="')
- TMail::Mail.parse(copy_of_raw_data)
- end
-
- # Extracts all attachments from the given TNEF file as a TMail::Mail object
- def mail_from_tnef(content)
- main = TMail::Mail.new
- main.set_content_type 'multipart', 'mixed', { 'boundary' => TMail.new_boundary }
- tnef_attachments(content).each do |attachment|
- tmail_attachment = TMail::Mail.new
- tmail_attachment['content-location'] = attachment[:filename]
- tmail_attachment.body = attachment[:content]
- main.parts << tmail_attachment
- end
- main
- end
-
- # Return a copy of the file name for the mail part
- def get_part_file_name(mail_part)
- part_file_name = TMail::Mail.get_part_file_name(mail_part)
- if part_file_name.nil?
- return nil
- end
- part_file_name = part_file_name.dup
- return part_file_name
- end
-
- # Get the body of a mail part
- def get_part_body(mail_part)
- mail_part.body
- end
-
- # Return the first from address if any
- def get_from_address(mail)
- if mail.from_addrs.nil? || mail.from_addrs.size == 0
- return nil
- end
- mail.from_addrs[0].spec
- end
-
- # Return the first from name if any
- def get_from_name(mail)
- mail.from_name_if_present
- end
-
- def get_all_addresses(mail)
- ((mail.to || []) +
- (mail.cc || []) +
- (mail.envelope_to || [])).uniq
- end
-
- def empty_return_path?(mail)
- return false if mail['return-path'].nil?
- return true if mail['return-path'].addr.to_s == '<>'
- return false
- end
-
- def get_auto_submitted(mail)
- mail['auto-submitted'] ? mail['auto-submitted'].body : nil
- end
-
- def get_content_type(part)
- part.content_type
- end
-
- def get_header_string(header, mail)
- mail.header_string(header)
- end
-
- # Number the attachments in depth first tree order, for use in URLs.
- # XXX This fills in part.rfc822_attachment and part.url_part_number within
- # all the parts of the email (see monkeypatches in lib/mail_handler/tmail_extensions and
- # lib/mail_handler/mail_extensions for how these attributes are added). ensure_parts_counted
- # must be called before using the attributes.
- def ensure_parts_counted(mail)
- mail.count_parts_count = 0
- _count_parts_recursive(mail, mail)
- # we carry on using these numeric ids for attachments uudecoded from within text parts
- mail.count_first_uudecode_count = mail.count_parts_count
- end
- def _count_parts_recursive(part, mail)
- if part.multipart?
- part.parts.each do |p|
- _count_parts_recursive(p, mail)
- end
- else
- part_filename = get_part_file_name(part)
- begin
- if part.content_type == 'message/rfc822'
- # An email attached as text
- # e.g. http://www.whatdotheyknow.com/request/64/response/102
- part.rfc822_attachment = mail_from_raw_email(part.body)
- elsif part.content_type == 'application/vnd.ms-outlook' || part_filename && AlaveteliFileTypes.filename_to_mimetype(part_filename) == 'application/vnd.ms-outlook'
- # An email attached as an Outlook file
- # e.g. http://www.whatdotheyknow.com/request/chinese_names_for_british_politi
- msg = Mapi::Msg.open(StringIO.new(part.body))
- part.rfc822_attachment = mail_from_raw_email(msg.to_mime.to_s)
- elsif part.content_type == 'application/ms-tnef'
- # A set of attachments in a TNEF file
- part.rfc822_attachment = mail_from_tnef(part.body)
- end
- rescue
- # If attached mail doesn't parse, treat it as text part
- part.rfc822_attachment = nil
- else
- unless part.rfc822_attachment.nil?
- _count_parts_recursive(part.rfc822_attachment, mail)
- end
- end
- if part.rfc822_attachment.nil?
- mail.count_parts_count += 1
- part.url_part_number = mail.count_parts_count
- end
- end
- end
-
- def get_attachment_attributes(mail)
- leaves = get_attachment_leaves(mail)
- # XXX we have to call ensure_parts_counted after get_attachment_leaves
- # which is really messy.
- ensure_parts_counted(mail)
- attachment_attributes = []
- for leaf in leaves
- body = get_part_body(leaf)
- # As leaf.body causes MIME decoding which uses lots of RAM, do garbage collection here
- # to prevent excess memory use. XXX not really sure if this helps reduce
- # peak RAM use overall. Anyway, maybe there is something better to do than this.
- GC.start
- if leaf.within_rfc822_attachment
- within_rfc822_subject = leaf.within_rfc822_attachment.subject
- # Test to see if we are in the first part of the attached
- # RFC822 message and it is text, if so add headers.
- # XXX should probably use hunting algorithm to find main text part, rather than
- # just expect it to be first. This will do for now though.
- if leaf.within_rfc822_attachment == leaf && leaf.content_type == 'text/plain'
- headers = ""
- for header in [ 'Date', 'Subject', 'From', 'To', 'Cc' ]
- if leaf.within_rfc822_attachment.header.include?(header.downcase)
- header_value = leaf.within_rfc822_attachment.header[header.downcase]
- if !header_value.blank?
- headers = headers + header + ": " + header_value.to_s + "\n"
- end
- end
- end
- # XXX call _convert_part_body_to_text here, but need to get charset somehow
- # e.g. http://www.whatdotheyknow.com/request/1593/response/3088/attach/4/Freedom%20of%20Information%20request%20-%20car%20oval%20sticker:%20Article%2020,%20Convention%20on%20Road%20Traffic%201949.txt
- body = headers + "\n" + body
-
- # This is quick way of getting all headers, but instead we only add some a) to
- # make it more usable, b) as at least one authority accidentally leaked security
- # information into a header.
- #attachment.body = leaf.within_rfc822_attachment.port.to_s
- end
- end
- attachment_attributes << {:url_part_number => leaf.url_part_number,
- :content_type => get_content_type(leaf),
- :filename => get_part_file_name(leaf),
- :charset => leaf.charset,
- :within_rfc822_subject => within_rfc822_subject,
- :body => body,
- :hexdigest => Digest::MD5.hexdigest(body) }
- end
- attachment_attributes
- end
-
- # (This risks losing info if the unchosen alternative is the only one to contain
- # useful info, but let's worry about that another time)
- def get_attachment_leaves(mail)
- return _get_attachment_leaves_recursive(mail, mail)
- end
- def _get_attachment_leaves_recursive(curr_mail, parent_mail, within_rfc822_attachment = nil)
- leaves_found = []
- if curr_mail.multipart?
- if curr_mail.parts.size == 0
- raise "no parts on multipart mail"
- end
-
- if curr_mail.sub_type == 'alternative'
- # Choose best part from alternatives
- best_part = nil
- # Take the last text/plain one, or else the first one
- curr_mail.parts.each do |m|
- if not best_part
- best_part = m
- elsif m.content_type == 'text/plain'
- best_part = m
- end
- end
- # Take an HTML one as even higher priority. (They tend
- # to render better than text/plain, e.g. don't wrap links here:
- # http://www.whatdotheyknow.com/request/amount_and_cost_of_freedom_of_in#incoming-72238 )
- curr_mail.parts.each do |m|
- if m.content_type == 'text/html'
- best_part = m
- end
- end
- leaves_found += _get_attachment_leaves_recursive(best_part, parent_mail, within_rfc822_attachment)
- else
- # Add all parts
- curr_mail.parts.each do |m|
- leaves_found += _get_attachment_leaves_recursive(m, parent_mail, within_rfc822_attachment)
- end
- end
- else
- # XXX Yuck. this section alters various content_types. That puts
- # it into conflict with ensure_parts_counted which it has to be
- # called both before and after. It will fail with cases of
- # attachments of attachments etc.
- charset = curr_mail.charset # save this, because overwriting content_type also resets charset
- # Don't allow nil content_types
- if curr_mail.content_type.nil?
- curr_mail.content_type = 'application/octet-stream'
- end
- # PDFs often come with this mime type, fix it up for view code
- if curr_mail.content_type == 'application/octet-stream'
- part_file_name = get_part_file_name(curr_mail)
- part_body = get_part_body(curr_mail)
- calc_mime = AlaveteliFileTypes.filename_and_content_to_mimetype(part_file_name, part_body)
- if calc_mime
- curr_mail.content_type = calc_mime
- end
- end
-
- # Use standard content types for Word documents etc.
- curr_mail.content_type = normalise_content_type(curr_mail.content_type)
- if curr_mail.content_type == 'message/rfc822'
- ensure_parts_counted(parent_mail) # fills in rfc822_attachment variable
- if curr_mail.rfc822_attachment.nil?
- # Attached mail didn't parse, so treat as text
- curr_mail.content_type = 'text/plain'
- end
- end
- if curr_mail.content_type == 'application/vnd.ms-outlook' || curr_mail.content_type == 'application/ms-tnef'
- ensure_parts_counted(parent_mail) # fills in rfc822_attachment variable
- if curr_mail.rfc822_attachment.nil?
- # Attached mail didn't parse, so treat as binary
- curr_mail.content_type = 'application/octet-stream'
- end
- end
- # If the part is an attachment of email
- if curr_mail.content_type == 'message/rfc822' || curr_mail.content_type == 'application/vnd.ms-outlook' || curr_mail.content_type == 'application/ms-tnef'
- ensure_parts_counted(parent_mail) # fills in rfc822_attachment variable
- leaves_found += _get_attachment_leaves_recursive(curr_mail.rfc822_attachment, parent_mail, curr_mail.rfc822_attachment)
- else
- # Store leaf
- curr_mail.within_rfc822_attachment = within_rfc822_attachment
- leaves_found += [curr_mail]
- end
- # restore original charset
- curr_mail.charset = charset
- end
- return leaves_found
- end
-
-
- def address_from_name_and_email(name, email)
- if !MySociety::Validate.is_valid_email(email)
- raise "invalid email " + email + " passed to address_from_name_and_email"
- end
- if name.nil?
- return TMail::Address.parse(email).to_s
- end
- # Botch an always quoted RFC address, then parse it
- name = name.gsub(/(["\\])/, "\\\\\\1")
- TMail::Address.parse('"' + name + '" <' + email + '>').to_s
- end
-
- def address_from_string(string)
- TMail::Address.parse(string).address
- end
-
- end
- end
-end \ No newline at end of file
diff --git a/lib/mail_handler/backends/tmail_extensions.rb b/lib/mail_handler/backends/tmail_extensions.rb
deleted file mode 100644
index 3576a8eca..000000000
--- a/lib/mail_handler/backends/tmail_extensions.rb
+++ /dev/null
@@ -1,138 +0,0 @@
-# lib/tmail_extensions.rb:
-# Extensions / fixes to TMail.
-#
-# Copyright (c) 2009 UK Citizens Online Democracy. All rights reserved.
-# Email: francis@mysociety.org; WWW: http://www.mysociety.org/
-
-require 'racc/parser'
-require 'tmail'
-require 'tmail/scanner'
-require 'tmail/utils'
-require 'tmail/interface'
-
-# Monkeypatch!
-
-# These mainly used in app/models/incoming_message.rb
-module TMail
- class Mail
- # Monkeypatch! Adding some extra members to store extra info in.
-
- attr_accessor :url_part_number
- attr_accessor :rfc822_attachment # when a whole email message is attached as text
- attr_accessor :within_rfc822_attachment # for parts within a message attached as text (for getting subject mainly)
- attr_accessor :count_parts_count
- attr_accessor :count_first_uudecode_count
-
- # Monkeypatch! (check to see if this becomes a standard function in
- # TMail::Mail, then use that, whatever it is called)
- def Mail.get_part_file_name(part)
- file_name = (part['content-location'] &&
- part['content-location'].body) ||
- part.sub_header("content-type", "name") ||
- part.sub_header("content-disposition", "filename")
- file_name = file_name.strip if file_name
- file_name
- end
-
- # Monkeypatch! Return the name part of from address, or nil if there isn't one
- def from_name_if_present
- if self.from && self.from_addrs[0].name
- return TMail::Unquoter.unquote_and_convert_to(self.from_addrs[0].name, "utf-8")
- else
- return nil
- end
- end
-
- # Monkeypatch! Generalisation of To:, Cc:
- def envelope_to(default = nil)
- # XXX assumes only one envelope-to, and no parsing needed
- val = self.header_string('envelope-to')
- return val ? [val,] : []
- end
-
- # Monkeypatch!
- # Bug fix to this function - is for message in humberside-police-odd-mime-type.email
- # Which was originally: https://secure.mysociety.org/admin/foi/request/show_raw_email/11209
- # See test in spec/lib/tmail_extensions.rb
- def set_content_type( str, sub = nil, param = nil )
- if sub
- main, sub = str, sub
- else
- main, sub = str.split(%r</>, 2)
- raise ArgumentError, "sub type missing: #{str.inspect}" unless sub
- end
- if h = @header['content-type']
- h.main_type = main
- h.sub_type = sub
- h.params.clear if !h.params.nil? # XXX this if statement is the fix # XXX disabled until works with test
- else
- store 'Content-Type', "#{main}/#{sub}"
- end
- @header['content-type'].params.replace param if param
- str
- end
- # Need to make sure this alias calls the Monkeypatch too
- alias content_type= set_content_type
-
- end
-
- module TextUtils
- # Monkeypatch! Much more aggressive list of characters to cause quoting
- # than in normal TMail. e.g. Have found real cases where @ needs quoting.
- # We list characters to allow, rather than characters not to allow.
- NEW_PHRASE_UNSAFE=/[^A-Za-z0-9!#\$%&'*+\-\/=?^_`{|}~ ]/n
- def quote_phrase( str )
- (NEW_PHRASE_UNSAFE === str) ? dquote(str) : str
- end
- end
-end
-
-# Monkeypatch! TMail 1.2.7.1 will parse only one address out of a list of addresses with
-# unquoted display parts https://github.com/mikel/tmail/issues#issue/9 - this monkeypatch
-# fixes this issue.
-module TMail
-
- class Parser < Racc::Parser
-
-module_eval <<'..end lib/tmail/parser.y modeval..id2dd1c7d21d', 'lib/tmail/parser.y', 340
-
- def self.special_quote_address(str) #:nodoc:
- # Takes a string which is an address and adds quotation marks to special
- # edge case methods that the RACC parser can not handle.
- #
- # Right now just handles two edge cases:
- #
- # Full stop as the last character of the display name:
- # Mikel L. <mikel@me.com>
- # Returns:
- # "Mikel L." <mikel@me.com>
- #
- # Unquoted @ symbol in the display name:
- # mikel@me.com <mikel@me.com>
- # Returns:
- # "mikel@me.com" <mikel@me.com>
- #
- # Any other address not matching these patterns just gets returned as is.
- case
- # This handles the missing "" in an older version of Apple Mail.app
- # around the display name when the display name contains a '@'
- # like 'mikel@me.com <mikel@me.com>'
- # Just quotes it to: '"mikel@me.com" <mikel@me.com>'
- when str =~ /\A([^"][^<]+@[^>]+[^"])\s(<.*?>)\Z/
- return "\"#{$1}\" #{$2}"
- # This handles cases where 'Mikel A. <mikel@me.com>' which is a trailing
- # full stop before the address section. Just quotes it to
- # '"Mikel A." <mikel@me.com>'
- when str =~ /\A(.*?\.)\s(<.*?>)\s*\Z/
- return "\"#{$1}\" #{$2}"
- else
- str
- end
- end
-
-..end lib/tmail/parser.y modeval..id2dd1c7d21d
- end # class Parser
-
-end # module TMail
-
-
diff --git a/lib/mail_handler/mail_handler.rb b/lib/mail_handler/mail_handler.rb
index 8b227b9ca..9c955cccd 100644
--- a/lib/mail_handler/mail_handler.rb
+++ b/lib/mail_handler/mail_handler.rb
@@ -3,16 +3,12 @@ require 'tmpdir'
module MailHandler
- if RUBY_VERSION.to_f >= 1.9
- require 'mail'
- require 'backends/mail_extensions'
- require 'backends/mail_backend'
- include Backends::MailBackend
- else
- require 'action_mailer'
- require 'backends/tmail_extensions'
- require 'backends/tmail_backend'
- include Backends::TmailBackend
+ require 'mail'
+ require 'backends/mail_extensions'
+ require 'backends/mail_backend'
+ include Backends::MailBackend
+
+ class TNEFParsingError < StandardError
end
# Returns a set of attachments from the given TNEF contents
@@ -21,14 +17,14 @@ module MailHandler
def tnef_attachments(content)
attachments = []
Dir.mktmpdir do |dir|
- IO.popen("#{`which tnef`.chomp} -K -C #{dir}", "wb") do |f|
+ IO.popen("tnef -K -C #{dir} 2> /dev/null", "wb") do |f|
f.write(content)
f.close
if $?.signaled?
raise IOError, "tnef exited with signal #{$?.termsig}"
end
if $?.exited? && $?.exitstatus != 0
- raise IOError, "tnef exited with status #{$?.exitstatus}"
+ raise TNEFParsingError, "tnef exited with status #{$?.exitstatus}"
end
end
found = 0
@@ -41,7 +37,7 @@ module MailHandler
end
end
if found == 0
- raise IOError, "tnef produced no attachments"
+ raise TNEFParsingError, "tnef produced no attachments"
end
end
attachments
@@ -84,7 +80,8 @@ module MailHandler
tempfile.flush
default_params = { :append_to => text, :binary_output => false }
if content_type == 'application/vnd.ms-word'
- AlaveteliExternalCommand.run("wvText", tempfile.path, tempfile.path + ".txt")
+ AlaveteliExternalCommand.run("wvText", tempfile.path, tempfile.path + ".txt",
+ { :memory_limit => 536870912 } )
# Try catdoc if we get into trouble (e.g. for InfoRequestEvent 2701)
if not File.exists?(tempfile.path + ".txt")
AlaveteliExternalCommand.run("catdoc", tempfile.path, default_params)
diff --git a/lib/no_constraint_disabling.rb b/lib/no_constraint_disabling.rb
new file mode 100644
index 000000000..d515a959a
--- /dev/null
+++ b/lib/no_constraint_disabling.rb
@@ -0,0 +1,110 @@
+# In order to work around the problem of the database use not having
+# the permission to disable referential integrity when loading fixtures,
+# we redefine disable_referential_integrity so that it doesn't try to
+# disable foreign key constraints, and redefine the
+# ActiveRecord::Fixtures.create_fixtures method to pay attention to the order
+# which fixture tables are passed so that foreign key constraints won't be
+# violated. The only lines that are changed from the initial definition
+# are those between the "***" comments
+require 'active_record/fixtures'
+require 'active_record/connection_adapters/postgresql_adapter'
+module ActiveRecord
+ module ConnectionAdapters
+ class PostgreSQLAdapter < AbstractAdapter
+ def disable_referential_integrity(&block)
+ transaction {
+ yield
+ }
+ end
+ end
+ end
+end
+
+module ActiveRecord
+ class Fixtures
+
+ def self.create_fixtures(fixtures_directory, table_names, class_names = {})
+ table_names = [table_names].flatten.map { |n| n.to_s }
+ table_names.each { |n|
+ class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/')
+ }
+
+ # FIXME: Apparently JK uses this.
+ connection = block_given? ? yield : ActiveRecord::Base.connection
+
+ files_to_read = table_names.reject { |table_name|
+ fixture_is_cached?(connection, table_name)
+ }
+
+ unless files_to_read.empty?
+ connection.disable_referential_integrity do
+ fixtures_map = {}
+
+ fixture_files = files_to_read.map do |path|
+ table_name = path.tr '/', '_'
+
+ fixtures_map[path] = ActiveRecord::Fixtures.new(
+ connection,
+ table_name,
+ class_names[table_name.to_sym] || table_name.classify,
+ File.join(fixtures_directory, path))
+ end
+
+ all_loaded_fixtures.update(fixtures_map)
+
+ connection.transaction(:requires_new => true) do
+ # Patch - replace this...
+ # ***
+ # fixture_files.each do |ff|
+ # conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection
+ # table_rows = ff.table_rows
+ #
+ # table_rows.keys.each do |table|
+ # conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
+ # end
+ #
+ # table_rows.each do |table_name,rows|
+ # rows.each do |row|
+ # conn.insert_fixture(row, table_name)
+ # end
+ # end
+ # end
+ # ***
+ # ... with this
+ fixture_files.reverse.each do |ff|
+ conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection
+ table_rows = ff.table_rows
+
+ table_rows.keys.each do |table|
+ conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
+ end
+ end
+
+ fixture_files.each do |ff|
+ conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection
+ table_rows = ff.table_rows
+ table_rows.each do |table_name,rows|
+ rows.each do |row|
+ conn.insert_fixture(row, table_name)
+ end
+ end
+ end
+ # ***
+
+ # Cap primary key sequences to max(pk).
+ if connection.respond_to?(:reset_pk_sequence!)
+ table_names.each do |table_name|
+ connection.reset_pk_sequence!(table_name.tr('/', '_'))
+ end
+ end
+ end
+
+ cache_fixtures(connection, fixtures_map)
+ end
+ end
+ cached_fixtures(connection, table_names)
+ end
+
+ end
+
+end
diff --git a/lib/normalize_string.rb b/lib/normalize_string.rb
new file mode 100644
index 000000000..f02b18ee0
--- /dev/null
+++ b/lib/normalize_string.rb
@@ -0,0 +1,86 @@
+require 'iconv' unless RUBY_VERSION.to_f >= 1.9
+require 'charlock_holmes'
+
+class EncodingNormalizationError < StandardError
+end
+
+def normalize_string_to_utf8(s, suggested_character_encoding=nil)
+
+ # Make a list of encodings to try:
+ to_try = []
+
+ guessed_encoding = CharlockHolmes::EncodingDetector.detect(s)[:encoding]
+ guessed_encoding ||= ''
+
+ # It's reasonably common for windows-1252 text to be mislabelled
+ # as ISO-8859-1, so try that first if charlock_holmes guessed
+ # that. However, it can also easily misidentify UTF-8 strings as
+ # ISO-8859-1 so we don't want to go with the guess by default...
+ to_try.push guessed_encoding if guessed_encoding.downcase == 'windows-1252'
+
+ to_try.push suggested_character_encoding if suggested_character_encoding
+ to_try.push 'UTF-8'
+ to_try.push guessed_encoding
+
+ to_try.each do |from_encoding|
+ if RUBY_VERSION.to_f >= 1.9
+ begin
+ s.force_encoding from_encoding
+ return s.encode('UTF-8') if s.valid_encoding?
+ rescue ArgumentError
+ # We get this is there are invalid bytes when
+ # interpreted as from_encoding at the point of
+ # the encode('UTF-8'); move onto the next one...
+ end
+ else
+ to_encoding = 'UTF-8'
+ begin
+ converted = Iconv.conv 'UTF-8', from_encoding, s
+ return converted
+ rescue Iconv::Failure
+ # We get this is there are invalid bytes when
+ # interpreted as from_encoding at the point of
+ # the Iconv.iconv; move onto the next one...
+ end
+ end
+ end
+ raise EncodingNormalizationError, "Couldn't find a valid character encoding for the string"
+
+end
+
+def convert_string_to_utf8_or_binary(s, suggested_character_encoding=nil)
+ # This function exists to help to keep consistent with the
+ # behaviour of earlier versions of Alaveteli: in the code as it
+ # is, there are situations where it's expected that we generally
+ # have a UTF-8 encoded string, but if the source data was
+ # unintepretable under any character encoding, the string may be
+ # binary data (i.e. invalid UTF-8). Such a string would then be
+ # mangled into valid UTF-8 by _sanitize_text for the purposes of
+ # display.
+
+ # This seems unsatisfactory to me - two better alternatives would
+ # be either: (a) to mangle the data into valid UTF-8 in this
+ # method or (b) to treat the 'text/*' attachment as
+ # 'application/octet-stream' instead. However, for the purposes
+ # of the transition to Ruby 1.9 and/or Rails 3 we just want the
+ # behaviour to be as similar as possible.
+
+ begin
+ result = normalize_string_to_utf8 s, suggested_character_encoding
+ rescue EncodingNormalizationError
+ result = s
+ s.force_encoding 'ASCII-8BIT' if RUBY_VERSION.to_f >= 1.9
+ end
+ result
+end
+
+def log_text_details(message, text)
+ if RUBY_VERSION.to_f >= 1.9
+ STDERR.puts "#{message}, we have text: #{text}, of class #{text.class} and encoding #{text.encoding}"
+ else
+ STDERR.puts "#{message}, we have text: #{text}, of class #{text.class}"
+ end
+ filename = "/var/tmp/#{Digest::MD5.hexdigest(text)}.txt"
+ File.open(filename, "wb") { |f| f.write text }
+ STDERR.puts "#{message}, the filename is: #{filename}"
+end
diff --git a/lib/old_rubygems_patch.rb b/lib/old_rubygems_patch.rb
deleted file mode 100644
index 3001a7381..000000000
--- a/lib/old_rubygems_patch.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-if File.exist? File.join(File.dirname(__FILE__),'..','vendor','rails','railties','lib','rails','gem_dependency.rb')
- require File.join(File.dirname(__FILE__),'..','vendor','rails','railties','lib','rails','gem_dependency.rb')
-else
- require 'rails/gem_dependency'
-end
-
-module Rails
- class GemDependency < Gem::Dependency
-
- # This definition of the requirement method is a patch
- if !method_defined?(:requirement)
- def requirement
- req = version_requirements
- end
- end
-
- def add_load_paths
- self.class.add_frozen_gem_path
- return if @loaded || @load_paths_added
- if framework_gem?
- @load_paths_added = @loaded = @frozen = true
- return
- end
-
- begin
- dep = Gem::Dependency.new(name, requirement)
- spec = Gem.source_index.find { |_,s| s.satisfies_requirement?(dep) }.last
- spec.activate # a way that exists
- rescue
- begin
- gem self.name, self.requirement # < 1.8 unhappy way
- # This second rescue is a patch - fall back to passing Rails::GemDependency to gem
- # for older rubygems
- rescue ArgumentError
- gem self
- end
- end
-
- @spec = Gem.loaded_specs[name]
- @frozen = @spec.loaded_from.include?(self.class.unpacked_path) if @spec
- @load_paths_added = true
- rescue Gem::LoadError
- end
- end
-
-end
diff --git a/lib/patches/fixtures_constraint_disabling.rb b/lib/patches/fixtures_constraint_disabling.rb
deleted file mode 100644
index 7d97e81f7..000000000
--- a/lib/patches/fixtures_constraint_disabling.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# An alternative way of disabling foreign keys in fixture loading in Postgres and
-# does not require superuser permissions
-# http://kopongo.com/2008/7/25/postgres-ri_constrainttrigger-error
-require 'active_record/connection_adapters/postgresql_adapter'
-module ActiveRecord
- module ConnectionAdapters
- class PostgreSQLAdapter < AbstractAdapter
- def disable_referential_integrity(&block)
- transaction {
- begin
- execute "SET CONSTRAINTS ALL DEFERRED"
- yield
- ensure
- execute "SET CONSTRAINTS ALL IMMEDIATE"
- end
- }
- end
- end
- end
-end
-
diff --git a/lib/public_body_categories.rb b/lib/public_body_categories.rb
index c6f0a6690..7f548b130 100644
--- a/lib/public_body_categories.rb
+++ b/lib/public_body_categories.rb
@@ -2,7 +2,7 @@
# Categorisations of public bodies.
#
# Copyright (c) 2009 UK Citizens Online Democracy. All rights reserved.
-# Email: francis@mysociety.org; WWW: http://www.mysociety.org/
+# Email: hello@mysociety.org; WWW: http://www.mysociety.org/
class PublicBodyCategories
diff --git a/lib/rack_quote_monkeypatch.rb b/lib/rack_quote_monkeypatch.rb
deleted file mode 100644
index b477ac0cb..000000000
--- a/lib/rack_quote_monkeypatch.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-# There's a bug in Rack 1.1.x which is fixed in Rack 1.2, but our
-# current version of Rails won't use that. So for now, monkeypatch,
-# This can be dropped when we move to Rails 3.
-#
-# See https://github.com/mysociety/alaveteli/issues/38 for Alaveteli
-# bug report
-#
-# More info about the monkeypatch:
-# http://thewebfellas.com/blog/2010/7/15/rails-2-3-8-rack-1-1-and-the-curious-case-of-the-missing-quotes
-
-module Rack
- module Utils
- def parse_query(qs, d = nil)
- params = {}
-
- (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
- k, v = p.split('=', 2).map { |x| unescape(x) }
- if cur = params[k]
- if cur.class == Array
- params[k] << v
- else
- params[k] = [cur, v]
- end
- else
- params[k] = v
- end
- end
-
- return params
- end
- module_function :parse_query
-
- def normalize_params(params, name, v = nil)
- name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
- k = $1 || ''
- after = $' || ''
-
- return if k.empty?
-
- if after == ""
- params[k] = v
- elsif after == "[]"
- params[k] ||= []
- raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
- params[k] << v
- elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
- child_key = $1
- params[k] ||= []
- raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
- if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
- normalize_params(params[k].last, child_key, v)
- else
- params[k] << normalize_params({}, child_key, v)
- end
- else
- params[k] ||= {}
- raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
- params[k] = normalize_params(params[k], after, v)
- end
-
- return params
- end
- module_function :normalize_params
- end
-end
diff --git a/lib/routing_filters.rb b/lib/routing_filters.rb
index 32dafc651..a9a62b8db 100644
--- a/lib/routing_filters.rb
+++ b/lib/routing_filters.rb
@@ -7,7 +7,7 @@ module RoutingFilter
end
# And override the generation logic to use FastGettext.locale
# rather than I18n.locale (the latter is what rails uses
- # internally and may look like `en_US`, whereas the latter is
+ # internally and may look like `en-US`, whereas the latter is
# was FastGettext and other POSIX-based systems use, and will
# look like `en_US`
def around_generate(*args, &block)
diff --git a/lib/sendmail_return_path.rb b/lib/sendmail_return_path.rb
deleted file mode 100644
index 23c4d4376..000000000
--- a/lib/sendmail_return_path.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# Monkeypatch!
-# Grrr, semantics of smtp and sendmail send should be the same with regard to setting return path
-
-# See test in spec/lib/sendmail_return_path_spec.rb
-
-module ActionMailer
- class Base
- def perform_delivery_sendmail(mail)
- sender = (mail['return-path'] && mail['return-path'].spec) || mail.from.first
-
- sendmail_args = sendmail_settings[:arguments].dup
- sendmail_args += " -f \"#{sender}\""
-
- IO.popen("#{sendmail_settings[:location]} #{sendmail_args}","w+") do |sm|
- sm.print(mail.encoded.gsub(/\r/, ''))
- sm.flush
- end
- end
- end
-end
-
diff --git a/lib/tasks/.gitkeep b/lib/tasks/.gitkeep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/lib/tasks/.gitkeep
diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake
index c73c2584e..ace7205ae 100644
--- a/lib/tasks/gettext.rake
+++ b/lib/tasks/gettext.rake
@@ -1,7 +1,3 @@
-# Rails won't automatically load rakefiles from gems - see
-# http://stackoverflow.com/questions/1878640/including-rake-tasks-in-gems
-Dir["#{Gem.searcher.find('gettext_i18n_rails').full_gem_path}/lib/tasks/**/*.rake"].each { |ext| load ext }
-
namespace :gettext do
desc 'Rewrite .po files into a consistent msgmerge format'
diff --git a/lib/tasks/rspec.rake b/lib/tasks/rspec.rake
deleted file mode 100644
index d4fd4a9ff..000000000
--- a/lib/tasks/rspec.rake
+++ /dev/null
@@ -1,148 +0,0 @@
-rspec_gem_dir = nil
-Dir["#{Rails.root}/vendor/gems/*"].each do |subdir|
- rspec_gem_dir = subdir if subdir.gsub("#{Rails.root}/vendor/gems/","") =~ /^(\w+-)?rspec-(\d+)/ && File.exist?("#{subdir}/lib/spec/rake/spectask.rb")
-end
-rspec_plugin_dir = File.expand_path(File.dirname(__FILE__) + '/../../vendor/plugins/rspec')
-
-if rspec_gem_dir && (test ?d, rspec_plugin_dir)
- raise "\n#{'*'*50}\nYou have rspec installed in both vendor/gems and vendor/plugins\nPlease pick one and dispose of the other.\n#{'*'*50}\n\n"
-end
-
-if rspec_gem_dir
- $LOAD_PATH.unshift("#{rspec_gem_dir}/lib")
-elsif File.exist?(rspec_plugin_dir)
- $LOAD_PATH.unshift("#{rspec_plugin_dir}/lib")
-end
-
-# Don't load rspec if running "rake gems:*"
-unless ARGV.any? {|a| a =~ /^gems/}
-
-begin
- require 'spec/rake/spectask'
-rescue MissingSourceFile
- module Spec
- module Rake
- class SpecTask
- if defined?(::Rake::DSL)
- include ::Rake::DSL
- end
- def initialize(name)
- task name do
- # if rspec-rails is a configured gem, this will output helpful material and exit ...
- require File.expand_path(File.join(File.dirname(__FILE__),"..","..","config","environment"))
-
- # ... otherwise, do this:
- raise <<-MSG
-
-#{"*" * 80}
-* You are trying to run an rspec rake task defined in
-* #{__FILE__},
-* but rspec can not be found in vendor/gems, vendor/plugins or system gems.
-#{"*" * 80}
-MSG
- end
- end
- end
- end
- end
-end
-
-Rake.application.instance_variable_get('@tasks').delete('default')
-
-spec_prereq = File.exist?(File.join(Rails.root, 'config', 'database.yml')) ? "db:test:prepare" : :noop
-task :noop do
-end
-
-task :default => :spec
-task :stats => "spec:statsetup"
-task :test => ['spec']
-task :cruise => ['spec']
-
-desc "Run all specs in spec directory (excluding plugin specs)"
-Spec::Rake::SpecTask.new(:spec => spec_prereq) do |t|
- t.spec_opts = ['--options', "\"#{Rails.root}/spec/spec.opts\""]
- t.spec_files = FileList['spec/**/*_spec.rb']
-end
-
-namespace :spec do
- desc "Run all specs in spec directory with RCov (excluding plugin specs)"
- Spec::Rake::SpecTask.new(:rcov) do |t|
- t.spec_opts = ['--options', "\"#{Rails.root}/spec/spec.opts\""]
- t.spec_files = FileList['spec/**/*_spec.rb']
- t.rcov = true
- t.rcov_opts = lambda do
- IO.readlines("#{Rails.root}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
- end
- end
-
- desc "Print Specdoc for all specs (excluding plugin specs)"
- Spec::Rake::SpecTask.new(:doc) do |t|
- t.spec_opts = ["--format", "specdoc", "--dry-run"]
- t.spec_files = FileList['spec/**/*_spec.rb']
- end
-
- desc "Print Specdoc for all plugin examples"
- Spec::Rake::SpecTask.new(:plugin_doc) do |t|
- t.spec_opts = ["--format", "specdoc", "--dry-run"]
- t.spec_files = FileList['vendor/plugins/**/spec/**/*_spec.rb'].exclude('vendor/plugins/rspec/*')
- end
-
- [:models, :controllers, :views, :helpers, :lib, :integration].each do |sub|
- desc "Run the code examples in spec/#{sub}"
- Spec::Rake::SpecTask.new(sub => spec_prereq) do |t|
- t.spec_opts = ['--options', "\"#{Rails.root}/spec/spec.opts\""]
- t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
- end
- end
-
- desc "Run the code examples in vendor/plugins (except RSpec's own)"
- Spec::Rake::SpecTask.new(:plugins => spec_prereq) do |t|
- t.spec_opts = ['--options', "\"#{Rails.root}/spec/spec.opts\""]
- t.spec_files = FileList['vendor/plugins/**/spec/**/*_spec.rb'].exclude('vendor/plugins/rspec/*').exclude("vendor/plugins/rspec-rails/*")
- end
-
- namespace :plugins do
- desc "Runs the examples for rspec_on_rails"
- Spec::Rake::SpecTask.new(:rspec_on_rails) do |t|
- t.spec_opts = ['--options', "\"#{Rails.root}/spec/spec.opts\""]
- t.spec_files = FileList['vendor/plugins/rspec-rails/spec/**/*_spec.rb']
- end
- end
-
- # Setup specs for stats
- task :statsetup do
- require 'code_statistics'
- ::STATS_DIRECTORIES << %w(Model\ specs spec/models) if File.exist?('spec/models')
- ::STATS_DIRECTORIES << %w(View\ specs spec/views) if File.exist?('spec/views')
- ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers) if File.exist?('spec/controllers')
- ::STATS_DIRECTORIES << %w(Helper\ specs spec/helpers) if File.exist?('spec/helpers')
- ::STATS_DIRECTORIES << %w(Library\ specs spec/lib) if File.exist?('spec/lib')
- ::STATS_DIRECTORIES << %w(Routing\ specs spec/routing) if File.exist?('spec/routing')
- ::STATS_DIRECTORIES << %w(Integration\ specs spec/integration) if File.exist?('spec/integration')
- ::CodeStatistics::TEST_TYPES << "Model specs" if File.exist?('spec/models')
- ::CodeStatistics::TEST_TYPES << "View specs" if File.exist?('spec/views')
- ::CodeStatistics::TEST_TYPES << "Controller specs" if File.exist?('spec/controllers')
- ::CodeStatistics::TEST_TYPES << "Helper specs" if File.exist?('spec/helpers')
- ::CodeStatistics::TEST_TYPES << "Library specs" if File.exist?('spec/lib')
- ::CodeStatistics::TEST_TYPES << "Routing specs" if File.exist?('spec/routing')
- ::CodeStatistics::TEST_TYPES << "Integration specs" if File.exist?('spec/integration')
- end
-
- namespace :db do
- namespace :fixtures do
- desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z."
- task :load => :environment do
- ActiveRecord::Base.establish_connection(Rails.env)
- base_dir = File.join(Rails.root, 'spec', 'fixtures')
- fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir
-
- require 'active_record/fixtures'
- (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/).map {|f| File.join(fixtures_dir, f) } : Dir.glob(File.join(fixtures_dir, '*.{yml,csv}'))).each do |fixture_file|
- Fixtures.create_fixtures(File.dirname(fixture_file), File.basename(fixture_file, '.*'))
- end
- end
- end
- end
-end
-
-end
diff --git a/lib/tasks/temp.rake b/lib/tasks/temp.rake
index e49a84ecb..fcabb23de 100644
--- a/lib/tasks/temp.rake
+++ b/lib/tasks/temp.rake
@@ -1,53 +1,255 @@
namespace :temp do
- desc 'Populate the request_classifications table from info_request_events'
- task :populate_request_classifications => :environment do
- InfoRequestEvent.find_each(:conditions => ["event_type = 'status_update'"]) do |classification|
- RequestClassification.create!(:created_at => classification.created_at,
- :user_id => classification.params[:user_id],
- :info_request_event_id => classification.id)
- end
+ def disable_duplicate_account(user, count, dryrun)
+ dupe_email = "duplicateemail#{count}@example.com"
+ puts "Updating #{user.email} to #{dupe_email} for user #{user.id}"
+ user.email = dupe_email
+ user.save! unless dryrun
end
- desc "Remove plaintext passwords from post_redirect params"
- task :remove_post_redirect_passwords => :environment do
- PostRedirect.find_each(:conditions => ['post_params_yaml is not null']) do |post_redirect|
- if post_redirect.post_params && post_redirect.post_params[:signchangeemail] && post_redirect.post_params[:signchangeemail][:password]
- params = post_redirect.post_params
- params[:signchangeemail].delete(:password)
- post_redirect.post_params = params
- post_redirect.save!
- end
+ desc "Re-extract any missing cached attachments"
+ task :reextract_missing_attachments, [:commit] => :environment do |t, args|
+ dry_run = args.commit.nil? || args.commit.empty?
+ total_messages = 0
+ messages_to_reparse = 0
+ IncomingMessage.find_each :include => :foi_attachments do |im|
+ reparse = im.foi_attachments.any? { |fa| ! File.exists? fa.filepath }
+ total_messages += 1
+ messages_to_reparse += 1 if reparse
+ if total_messages % 1000 == 0
+ puts "Considered #{total_messages} received emails."
+ end
+ unless dry_run
+ im.parse_raw_email! true if reparse
+ sleep 2
+ end
end
+ message = dry_run ? "Would reparse" : "Reparsed"
+ message += " #{messages_to_reparse} out of #{total_messages} received emails."
+ puts message
end
- desc 'Remove file caches for requests that are not publicly visible or have been destroyed'
- task :remove_obsolete_info_request_caches => :environment do
+ desc 'Cleanup accounts with a space in the email address'
+ task :clean_up_emails_with_spaces => :environment do
dryrun = ENV['DRYRUN'] == '0' ? false : true
- verbose = ENV['VERBOSE'] == '0' ? false : true
if dryrun
- puts "Running in dryrun mode"
+ puts "This is a dryrun"
end
- request_cache_path = File.join(Rails.root, 'cache', 'views', 'request', '*', '*')
- Dir.glob(request_cache_path) do |request_subdir|
- info_request_id = File.basename(request_subdir)
- puts "Looking for InfoRequest with id #{info_request_id}" if verbose
- begin
- info_request = InfoRequest.find(info_request_id)
- puts "Got InfoRequest #{info_request_id}" if verbose
- if ! info_request.all_can_view?
- puts "Deleting cache at #{request_subdir} for hidden/requester_only InfoRequest #{info_request_id}"
- if ! dryrun
- FileUtils.rm_rf(request_subdir)
+ count = 0
+ User.find_each do |user|
+ if / /.match(user.email)
+
+ email_without_spaces = user.email.gsub(' ', '')
+ existing = User.find_user_by_email(email_without_spaces)
+ # Another account exists with the canonical address
+ if existing
+ if user.info_requests.count == 0 and user.comments.count == 0 and user.track_things.count == 0
+ count += 1
+ disable_duplicate_account(user, count, dryrun)
+ elsif existing.info_requests.count == 0 and existing.comments.count == 0 and existing.track_things.count == 0
+ count += 1
+ disable_duplicate_account(existing, count, dryrun)
+ user.email = email_without_spaces
+ puts "Updating #{user.email} to #{email_without_spaces} for user #{user.id}"
+ user.save! unless dryrun
+ else
+ user.info_requests.each do |info_request|
+ info_request.user = existing
+ info_request.save! unless dryrun
+ puts "Moved request #{info_request.id} from user #{user.id} to #{existing.id}"
+ end
+
+ user.comments.each do |comment|
+ comment.user = existing
+ comment.save! unless dryrun
+ puts "Moved comment #{comment.id} from user #{user.id} to #{existing.id}"
+ end
+
+ user.track_things.each do |track_thing|
+ track_thing.tracking_user = existing
+ track_thing.save! unless dryrun
+ puts "Moved track thing #{track_thing.id} from user #{user.id} to #{existing.id}"
+ end
+
+ TrackThingsSentEmail.find_each(:conditions => ['user_id = ?', user]) do |sent_email|
+ sent_email.user = existing
+ sent_email.save! unless dryrun
+ puts "Moved track thing sent email #{sent_email.id} from user #{user.id} to #{existing.id}"
+
+ end
+
+ user.censor_rules.each do |censor_rule|
+ censor_rule.user = existing
+ censor_rule.save! unless dryrun
+ puts "Moved censor rule #{censor_rule.id} from user #{user.id} to #{existing.id}"
+ end
+
+ user.user_info_request_sent_alerts.each do |sent_alert|
+ sent_alert.user = existing
+ sent_alert.save! unless dryrun
+ puts "Moved sent alert #{sent_alert.id} from user #{user.id} to #{existing.id}"
+ end
+
+ count += 1
+ disable_duplicate_account(user, count, dryrun)
end
+ else
+ puts "Updating #{user.email} to #{email_without_spaces} for user #{user.id}"
+ user.email = email_without_spaces
+ user.save! unless dryrun
+ end
+ end
+ end
+ end
+
+ desc 'Create a CSV file of a random selection of raw emails, for comparing hexdigests'
+ task :random_attachments_hexdigests => :environment do
+
+ # The idea is to run this under the Rail 2 codebase, where
+ # Tmail was used to extract the attachements, and the task
+ # will output all of those file paths in a CSV file, and a
+ # list of the raw email files in another. The latter file is
+ # useful so that one can easily tar up the emails with:
+ #
+ # tar cvz -T raw-email-files -f raw_emails.tar.gz
+ #
+ # Then you can switch to the Rails 3 codebase, where
+ # attachment parsing is done via
+ # recompute_attachments_hexdigests
+
+ require 'csv'
+
+ File.open('raw-email-files', 'w') do |f|
+ CSV.open('attachment-hexdigests.csv', 'w') do |csv|
+ csv << ['filepath', 'i', 'url_part_number', 'hexdigest']
+ IncomingMessage.all(:order => 'RANDOM()', :limit => 1000).each do |incoming_message|
+ # raw_email.filepath fails unless the
+ # incoming_message has an associated request
+ next unless incoming_message.info_request
+ raw_email = incoming_message.raw_email
+ f.puts raw_email.filepath
+ incoming_message.foi_attachments.each_with_index do |attachment, i|
+ csv << [raw_email.filepath, i, attachment.url_part_number, attachment.hexdigest]
+ end
+ end
+ end
+ end
+
+ end
+
+
+ desc 'Check the hexdigests of attachments in emails on disk'
+ task :recompute_attachments_hexdigests => :environment do
+
+ require 'csv'
+ require 'digest/md5'
+
+ OldAttachment = Struct.new :filename, :attachment_index, :url_part_number, :hexdigest
+
+ filename_to_attachments = Hash.new {|h,k| h[k] = []}
+
+ header_line = true
+ CSV.foreach('attachment-hexdigests.csv') do |filename, attachment_index, url_part_number, hexdigest|
+ if header_line
+ header_line = false
+ else
+ filename_to_attachments[filename].push OldAttachment.new filename, attachment_index, url_part_number, hexdigest
+ end
+ end
+
+ total_attachments = 0
+ attachments_with_different_hexdigest = 0
+ files_with_different_numbers_of_attachments = 0
+ no_tnef_attachments = 0
+ no_parts_in_multipart = 0
+
+ multipart_error = "no parts on multipart mail"
+ tnef_error = "tnef produced no attachments"
+
+ # Now check each file:
+ filename_to_attachments.each do |filename, old_attachments|
+
+ # Currently it doesn't seem to be possible to reuse the
+ # attachment parsing code in Alaveteli without saving
+ # objects to the database, so reproduce what it does:
+
+ raw_email = nil
+ File.open(filename) do |f|
+ raw_email = f.read
+ end
+ mail = MailHandler.mail_from_raw_email(raw_email)
+
+ begin
+ attachment_attributes = MailHandler.get_attachment_attributes(mail)
+ rescue IOError => e
+ if e.message == tnef_error
+ puts "#{filename} #{tnef_error}"
+ no_tnef_attachments += 1
+ next
+ else
+ raise
+ end
+ rescue Exception => e
+ if e.message == multipart_error
+ puts "#{filename} #{multipart_error}"
+ no_parts_in_multipart += 1
+ next
+ else
+ raise
end
- rescue ActiveRecord::RecordNotFound
- puts "Deleting cache at #{request_subdir} for deleted InfoRequest #{info_request_id}"
- if ! dryrun
- FileUtils.rm_rf(request_subdir)
+ end
+
+ if attachment_attributes.length != old_attachments.length
+ puts "#{filename} the number of old attachments #{old_attachments.length} didn't match the number of new attachments #{attachment_attributes.length}"
+ files_with_different_numbers_of_attachments += 1
+ else
+ old_attachments.each_with_index do |old_attachment, i|
+ total_attachments += 1
+ attrs = attachment_attributes[i]
+ old_hexdigest = old_attachment.hexdigest
+ new_hexdigest = attrs[:hexdigest]
+ new_content_type = attrs[:content_type]
+ old_url_part_number = old_attachment.url_part_number.to_i
+ new_url_part_number = attrs[:url_part_number]
+ if old_url_part_number != new_url_part_number
+ puts "#{i} #{filename} old_url_part_number #{old_url_part_number}, new_url_part_number #{new_url_part_number}"
+ end
+ if old_hexdigest != new_hexdigest
+ body = attrs[:body]
+ # First, if the content type is one of
+ # text/plain, text/html or application/rtf try
+ # changing CRLF to LF and calculating a new
+ # digest - we generally don't worry about
+ # these changes:
+ new_converted_hexdigest = nil
+ if ["text/plain", "text/html", "application/rtf"].include? new_content_type
+ converted_body = body.gsub /\r\n/, "\n"
+ new_converted_hexdigest = Digest::MD5.hexdigest converted_body
+ puts "new_converted_hexdigest is #{new_converted_hexdigest}"
+ end
+ if (! new_converted_hexdigest) || (old_hexdigest != new_converted_hexdigest)
+ puts "#{i} #{filename} old_hexdigest #{old_hexdigest} wasn't the same as new_hexdigest #{new_hexdigest}"
+ puts " body was of length #{body.length}"
+ puts " content type was: #{new_content_type}"
+ path = "/tmp/#{new_hexdigest}"
+ f = File.new path, "w"
+ f.write body
+ f.close
+ puts " wrote body to #{path}"
+ attachments_with_different_hexdigest += 1
+ end
+ end
end
end
+
end
+
+ puts "total_attachments: #{total_attachments}"
+ puts "attachments_with_different_hexdigest: #{attachments_with_different_hexdigest}"
+ puts "files_with_different_numbers_of_attachments: #{files_with_different_numbers_of_attachments}"
+ puts "no_tnef_attachments: #{no_tnef_attachments}"
+ puts "no_parts_in_multipart: #{no_parts_in_multipart}"
+
end
end
diff --git a/lib/tasks/themes.rake b/lib/tasks/themes.rake
index 14aa15551..a8d16f108 100644
--- a/lib/tasks/themes.rake
+++ b/lib/tasks/themes.rake
@@ -9,15 +9,17 @@ namespace :themes do
File.join(plugin_dir, theme_name)
end
+ def checkout(commitish)
+ puts "Checking out #{commitish}" if verbose
+ system "git checkout #{commitish}"
+ end
+
def checkout_tag(version)
- checkout_command = "git checkout #{usage_tag(version)}"
- success = system(checkout_command)
- puts "Using tag #{usage_tag(version)}" if verbose && success
- success
+ checkout usage_tag(version)
end
def checkout_remote_branch(branch)
- system("git checkout origin/#{branch}")
+ checkout "origin/#{branch}"
end
def usage_tag(version)
@@ -31,7 +33,7 @@ namespace :themes do
if system(clone_command)
Dir.chdir install_path do
# First try to checkout a specific branch of the theme
- tag_checked_out = checkout_remote_branch(Configuration::theme_branch) if Configuration::theme_branch
+ tag_checked_out = checkout_remote_branch(AlaveteliConfiguration::theme_branch) if AlaveteliConfiguration::theme_branch
if !tag_checked_out
# try to checkout a tag exactly matching ALAVETELI VERSION
tag_checked_out = checkout_tag(ALAVETELI_VERSION)
@@ -94,10 +96,10 @@ namespace :themes do
desc "Install themes specified in the config file's THEME_URLS"
task :install => :environment do
verbose = true
- Configuration::theme_urls.each{ |theme_url| install_theme(theme_url, verbose) }
- if ! Configuration::theme_url.blank?
+ AlaveteliConfiguration::theme_urls.each{ |theme_url| install_theme(theme_url, verbose) }
+ if ! AlaveteliConfiguration::theme_url.blank?
# Old version of the above, for backwards compatibility
- install_theme(Configuration::theme_url, verbose, deprecated=true)
+ install_theme(AlaveteliConfiguration::theme_url, verbose, deprecated=true)
end
end
-end \ No newline at end of file
+end
diff --git a/lib/tasks/translation.rake b/lib/tasks/translation.rake
index ff07fc6f6..6458d9268 100644
--- a/lib/tasks/translation.rake
+++ b/lib/tasks/translation.rake
@@ -42,14 +42,14 @@ namespace :translation do
output_file = File.open(File.join(ENV['DIR'], 'message_preview.txt'), 'w')
# outgoing mailer
- request_email = OutgoingMailer.create_initial_request(info_request, initial_request)
+ request_email = OutgoingMailer.initial_request(info_request, initial_request)
write_email(request_email, 'Initial Request', output_file)
- followup_email = OutgoingMailer.create_followup(info_request, follow_up, nil)
+ followup_email = OutgoingMailer.followup(info_request, follow_up, nil)
write_email(followup_email, 'Follow up', output_file)
# contact mailer
- contact_email = ContactMailer.create_to_admin_message(info_request.user_name,
+ contact_email = ContactMailer.to_admin_message(info_request.user_name,
info_request.user.email,
'A test message',
'Hello!',
@@ -59,20 +59,20 @@ namespace :translation do
write_email(contact_email, 'Contact email (to admin)', output_file)
- user_contact_email = ContactMailer.create_user_message(info_request.user,
+ user_contact_email = ContactMailer.user_message(info_request.user,
info_request.user,
'http://www.example.com/user',
'A test message',
'Hello!')
write_email(user_contact_email, 'Contact email (user to user)', output_file)
- admin_contact_email = ContactMailer.create_from_admin_message(info_request.user,
+ admin_contact_email = ContactMailer.from_admin_message(info_request.user,
'A test message',
'Hello!')
write_email(admin_contact_email, 'Contact email (admin to user)', output_file)
# request mailer
- fake_response_email = RequestMailer.create_fake_response(info_request,
+ fake_response_email = RequestMailer.fake_response(info_request,
info_request.user,
"test body",
"attachment.txt",
@@ -89,98 +89,96 @@ namespace :translation do
response_mail = MailHandler.mail_from_raw_email(content)
response_mail.from = "authority@example.com"
- stopped_responses_email = RequestMailer.create_stopped_responses(info_request,
+ stopped_responses_email = RequestMailer.stopped_responses(info_request,
response_mail,
content)
write_email(stopped_responses_email,
'Bounce if someone sends email to a request that has had responses stopped',
output_file)
- requires_admin_email = RequestMailer.create_requires_admin(info_request)
+ requires_admin_email = RequestMailer.requires_admin(info_request)
write_email(requires_admin_email, 'Drawing admin attention to a response', output_file)
- new_response_email = RequestMailer.create_new_response(info_request, incoming_message)
+ new_response_email = RequestMailer.new_response(info_request, incoming_message)
write_email(new_response_email,
'Telling the requester that a new response has arrived',
output_file)
- overdue_alert_email = RequestMailer.create_overdue_alert(info_request, info_request.user)
+ overdue_alert_email = RequestMailer.overdue_alert(info_request, info_request.user)
write_email(overdue_alert_email,
'Telling the requester that the public body is late in replying',
output_file)
- very_overdue_alert_email = RequestMailer.create_very_overdue_alert(info_request, info_request.user)
+ very_overdue_alert_email = RequestMailer.very_overdue_alert(info_request, info_request.user)
write_email(very_overdue_alert_email,
'Telling the requester that the public body is very late in replying',
output_file)
- response_reminder_alert_email = RequestMailer.create_new_response_reminder_alert(info_request,
+ response_reminder_alert_email = RequestMailer.new_response_reminder_alert(info_request,
incoming_message)
write_email(response_reminder_alert_email,
'Telling the requester that they need to say if the new response contains info or not',
output_file)
- old_unclassified_email = RequestMailer.create_old_unclassified_updated(info_request)
+ old_unclassified_email = RequestMailer.old_unclassified_updated(info_request)
write_email(old_unclassified_email,
'Telling the requester that someone updated their old unclassified request',
output_file)
- not_clarified_alert_email = RequestMailer.create_not_clarified_alert(info_request, incoming_message)
+ not_clarified_alert_email = RequestMailer.not_clarified_alert(info_request, incoming_message)
write_email(not_clarified_alert_email,
'Telling the requester that they need to clarify their request',
output_file)
- comment_on_alert_email = RequestMailer.create_comment_on_alert(info_request, comment)
+ comment_on_alert_email = RequestMailer.comment_on_alert(info_request, comment)
write_email(comment_on_alert_email,
'Telling requester that somebody added an annotation to their request',
output_file)
- comment_on_alert_plural_email = RequestMailer.create_comment_on_alert_plural(info_request, 2, comment)
+ comment_on_alert_plural_email = RequestMailer.comment_on_alert_plural(info_request, 2, comment)
write_email(comment_on_alert_plural_email,
'Telling requester that somebody added multiple annotations to their request',
output_file)
# track mailer
- xapian_object = InfoRequest.full_search([InfoRequestEvent],
- track_thing.track_query,
- 'described_at',
- true,
- nil,
- 100,
- 1)
- event_digest_email = TrackMailer.create_event_digest(info_request.user,
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], track_thing.track_query,
+ :sort_by_prefix => 'described_at',
+ :sort_by_ascending => true,
+ :collapse_by_prefix => nil,
+ :limit => 100)
+ event_digest_email = TrackMailer.event_digest(info_request.user,
[[track_thing,
xapian_object.results,
xapian_object]])
write_email(event_digest_email, 'Alerts on things the user is tracking', output_file)
# user mailer
- site_name = Configuration::site_name
+ site_name = AlaveteliConfiguration::site_name
reasons = {
:web => "",
:email => _("Then you can sign in to {{site_name}}", :site_name => site_name),
:email_subject => _("Confirm your account on {{site_name}}", :site_name => site_name)
}
- confirm_login_email = UserMailer.create_confirm_login(info_request.user,
+ confirm_login_email = UserMailer.confirm_login(info_request.user,
reasons,
'http://www.example.com')
write_email(confirm_login_email, 'Confirm a user login', output_file)
- already_registered_email = UserMailer.create_already_registered(info_request.user,
+ already_registered_email = UserMailer.already_registered(info_request.user,
reasons,
'http://www.example.com')
write_email(already_registered_email, 'Tell a user they are already registered', output_file)
new_email = 'new_email@example.com'
- changeemail_confirm_email = UserMailer.create_changeemail_confirm(info_request.user,
+ changeemail_confirm_email = UserMailer.changeemail_confirm(info_request.user,
new_email,
'http://www.example.com')
write_email(changeemail_confirm_email,
'Confirm that the user wants to change their email',
output_file)
- changeemail_already_used = UserMailer.create_changeemail_already_used('old_email@example.com',
+ changeemail_already_used = UserMailer.changeemail_already_used('old_email@example.com',
new_email)
write_email(changeemail_already_used,
'Tell a user that the email they want to change to is already used',
@@ -189,4 +187,4 @@ namespace :translation do
output_file.close
end
-end \ No newline at end of file
+end
diff --git a/lib/timezone_fixes.rb b/lib/timezone_fixes.rb
deleted file mode 100644
index 1bf326ccd..000000000
--- a/lib/timezone_fixes.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# Taken from
-# https://rails.lighthouseapp.com/projects/8994/tickets/2946
-# http://github.com/rails/rails/commit/6f97ad07ded847f29159baf71050c63f04282170
-
-# Otherwise times get stored wrong during British Summer Time
-
-# Hopefully fixed in later Rails. There is a test in spec/lib/timezone_fixes_spec.rb
-
-# This fix is applied in Rails 3.x. So, should be possible to remove this then!
-
-# Monkeypatch!
-module ActiveRecord
- module ConnectionAdapters # :nodoc:
- module Quoting
- def quoted_date(value)
- if value.acts_like?(:time)
- zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
- value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value
- else
- value
- end.to_s(:db)
- end
- end
- end
-end
-
diff --git a/lib/willpaginate_extension.rb b/lib/willpaginate_extension.rb
deleted file mode 100644
index 3cdb0ae60..000000000
--- a/lib/willpaginate_extension.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-# this extension is loaded in environment.rb
-module WillPaginateExtension
- class LinkRenderer < WillPaginate::LinkRenderer
- def page_link(page, text, attributes = {})
- # Hack for admin pages, when proxied via https on mySociety servers, they
- # need a relative URL.
- url = url_for(page)
- if url.match(/\/admin.*(\?.*)/)
- url = $1
- end
- # Hack around our type-ahead search magic
- if url.match(/\/body\/search_ahead/)
- url.sub!("/body/search_ahead", "/select_authority")
- end
- @template.link_to text, url, attributes
- end
-
- # Returns URL params for +page_link_or_span+, taking the current GET params
- # and <tt>:params</tt> option into account.
- def url_for(page)
- page_one = page == 1
- unless @url_string and !page_one
- @url_params = {}
- # page links should preserve GET parameters
- stringified_merge @url_params, @template.params if @template.request.get?
- stringified_merge @url_params, @options[:params] if @options[:params]
- if complex = param_name.index(/[^\w-]/)
- page_param = parse_query_parameters("#{param_name}=#{page}")
-
- stringified_merge @url_params, page_param
- else
- @url_params[param_name] = page_one ? 1 : 2
- end
- # the following line makes pagination work on our specially munged search page
- combined = @template.request.path_parameters["combined"]
- @url_params["combined"] = combined if !combined.nil?
- url = @template.url_for(@url_params)
- return url if page_one
-
- if complex
- @url_string = url.sub(%r!((?:\?|&amp;)#{CGI.escape param_name}=)#{page}!, "\\1\0")
- return url
- else
- @url_string = url
- @url_params[param_name] = 3
- @template.url_for(@url_params).split(//).each_with_index do |char, i|
- if char == '3' and url[i, 1] == '2'
- @url_string[i] = "\0"
- break
- end
- end
- end
- end
- # finally!
- @url_string.sub "\0", page.to_s
- end
-
- end
-end