aboutsummaryrefslogtreecommitdiffstats
path: root/lib/activesupport_cache_extensions.rb
blob: 2791d5996a29adb3e744e88b219875a492d7673c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# lib/activesupport_cache_extensions.rb:
# Extensions / fixes to ActiveSupport::Cache
#
# Copyright (c) 2009 UK Citizens Online Democracy. All rights reserved.
# Email: hello@mysociety.org; WWW: http://www.mysociety.org/

# Monkeypatch! ./activesupport/lib/active_support/cache/file_store.rb

module ActiveSupport
    module Cache
        class FileStore < Store
            # We don't add the ".cache" file extension, as we want things like
            # .jpg files made by pdf2html to be picked up and rendered if
            # present.
            def real_file_path(name)
                '%s/%s' % [@cache_path, name.gsub('?', '.').gsub(':', '.')]
            end
        end
    end
end
r: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

# Handle email responses sent to us.
#
# This script is invoked as a pipe command, i.e. with the raw email message on stdin.
# - If a message is identified as a permanent bounce, the user is marked as having a
#   bounced address, and will not be sent any more messages.
# - If a message is identified as an out-of-office autoreply, it is discarded.
# - Any other messages are forwarded to config.get("FORWARD_NONBOUNCE_RESPONSES_TO")


# We want to avoid loading rails unless we need it, so we start by just loading the
# config file ourselves.
$alaveteli_dir = File.join(File.dirname(__FILE__), '..')
$:.push(File.join($alaveteli_dir, "commonlib", "rblib"))
load "config.rb"
MySociety::Config.set_file(File.join($alaveteli_dir, 'config', 'general'), true)
MySociety::Config.load_default

require 'rubygems'
if File.exist? File.join($alaveteli_dir,'vendor','rails','Rakefile')
    $:.push(File.join($alaveteli_dir, "vendor", "rails", "actionmailer", "lib", "action_mailer", "vendor", "tmail-1.2.7"))
    require 'tmail'
else
    require 'action_mailer'
end

def main(in_test_mode)
    Dir.chdir($alaveteli_dir) do
        raw_message = $stdin.read
        begin
            message = TMail::Mail.parse(raw_message)
        rescue
            # Error parsing message. Just pass it on, to be on the safe side.
            forward_on(raw_message) unless in_test_mode
            return 0
        end
        
        pfas = permanently_failed_addresses(message)
        if !pfas.empty?
            if in_test_mode
                puts pfas
            else
                pfas.each do |pfa|
                    record_bounce(pfa, raw_message)
                end
            end
            return 1
        end
        
        if is_oof? message
            # Discard out-of-office messages
            return 2
        end
        
        # Otherwise forward the message on
        forward_on(raw_message) unless in_test_mode
        return 0
    end
end

def permanently_failed_addresses(message)
    if message.header_string("Return-Path") == "<>"
        # Some sort of auto-response
    
        # Check for Exim’s X-Failed-Recipients header
        failed_recipients = message.header_string("X-Failed-Recipients")
        if !failed_recipients.nil?
            # The X-Failed-Recipients header contains the email address that failed
            # Check for the words "This is a permanent error." in the body, to indicate
            # a permanent failure
            if message.body =~ /This is a permanent error./
                return failed_recipients.split(/,\s*/)
            end
        end
        
        # Next, look for multipart/report
        if message.content_type == "multipart/report"
            permanently_failed_recipients = []
            message.parts.each do |part|
                if part.content_type == "message/delivery-status"
                    sections = part.body.split(/\r?\n\r?\n/)
                    # The first section is a generic header; subsequent sections
                    # represent a particular recipient. Since we 
                    sections[1..-1].each do |section|
                        if section !~ /^Status: (\d)/ || $1 != '5'
                            # Either we couldn’t find the Status field, or it was a transient failure
                            break
                        end
                        if section =~ /^Final-Recipient: rfc822;(.+)/
                            permanently_failed_recipients.push($1)
                        end
                    end
                end
            end
            if !permanently_failed_recipients.empty?
                return permanently_failed_recipients
            end
        end
    end
    
    return []
end

def is_oof?(message)
    # Check for out-of-office
    
    if message.header_string("X-POST-MessageClass") == "9; Autoresponder"
        return true
    end
    
    subject = message.header_string("Subject").downcase
    if message.header_string("Return-Path") == "<>"
        if subject.start_with? "out of office: "
            return true
        end
        if subject.start_with? "automatic reply: "
            return true
        end
    end
    
    if message.header_string("Auto-Submitted") == "auto-generated"
        if subject =~ /out of( the)? office/
            return true
        end
    end
    
    if subject.start_with? "out of office autoreply:"
        return true
    end
    if subject == "out of office"
        return true
    end
    if subject == "out of office reply"
        return true
    end
    if subject.end_with? "is out of the office"
        return true
    end
    return false
end

def forward_on(raw_message)
    forward_non_bounces_to = MySociety::Config.get("FORWARD_NONBOUNCE_RESPONSES_TO", "user-support@localhost")
    IO.popen("/usr/sbin/sendmail -i #{forward_non_bounces_to}", "w") do |f|
        f.write(raw_message);
        f.close;
    end
end

def load_rails
    require File.join('config', 'boot')
    require RAILS_ROOT + '/config/environment'
end

def record_bounce(email_address, bounce_message)
    load_rails
    User.record_bounce_for_email(email_address, bounce_message)
end

in_test_mode = (ARGV[0] == "--test")
status = main(in_test_mode)
exit(status) if in_test_mode