Running with Ruby

Tag: ActionMailer

Padrino Mailer + undefined method “default_encoding”

Pracując nad mailingiem w pewnej aplikacji napisanej w Padrino natrafiłem na “głupi” błąd:

NoMethodError: undefined method `default_encoding' for Mail::Message:Class

Z takim oto stacktracem:

NoMethodError: undefined method `default_encoding' for Mail::Message:Class
	/gems/sinatra-1.1.3/lib/sinatra/base.rb:466:in `render'
	/gems/padrino-core-0.9.19/lib/padrino-core/application/rendering.rb:123:in `render'
	/gems/padrino-mailer-0.9.19/lib/padrino-mailer/ext.rb:228:in `render'
	/home/mencio/app/mailers/signup_mailer.rb:9
	/gems/padrino-mailer-0.9.19/lib/padrino-mailer/base.rb:57:in `instance_exec'
	/gems/padrino-mailer-0.9.19/lib/padrino-mailer/base.rb:57:in `email'
	/gems/padrino-mailer-0.9.19/lib/padrino-mailer/helpers.rb:82:in `call'
	/gems/padrino-mailer-0.9.19/lib/padrino-mailer/helpers.rb:82:in `deliver'
	/gems/padrino-mailer-0.9.19/lib/padrino-mailer/helpers.rb:34:in `deliver'
	/home/mencio/app/controllers/signups.rb:9:in `POST /signup'
	/gems/padrino-core-0.9.19/lib/padrino-core/application/routing.rb:340:in `call'
	/gems/padrino-core-0.9.19/lib/padrino-core/application/routing.rb:340:in `route'
	/gems/sinatra-1.1.3/lib/sinatra/base.rb:649:in `instance_eval'
	/gems/sinatra-1.1.3/lib/sinatra/base.rb:649:in `route_eval'
	/gems/padrino-core-0.9.19/lib/padrino-core/application/routing.rb:605:in `route!'
	/gems/padrino-core-0.9.19/lib/padrino-core/application/routing.rb:605:in `catch'
	/gems/padrino-core-0.9.19/lib/padrino-core/application/routing.rb:605:in `route!'
	/gems/padrino-core-0.9.19/lib/padrino-core/application/routing.rb:596:in `catch'
	/gems/padrino-core-0.9.19/lib/padrino-core/application/routing.rb:596:in `route!'
	/gems/sinatra-1.1.3/lib/sinatra/base.rb:766:in `dispatch!'
	/gems/sinatra-1.1.3/lib/sinatra/base.rb:559:in `call!'
	/gems/sinatra-1.1.3/lib/sinatra/base.rb:731:in `instance_eval'
	/gems/sinatra-1.1.3/lib/sinatra/base.rb:731:in `invoke'
	/gems/sinatra-1.1.3/lib/sinatra/base.rb:731:in `catch'
	/gems/sinatra-1.1.3/lib/sinatra/base.rb:731:in `invoke'
	/gems/sinatra-1.1.3/lib/sinatra/base.rb:559:in `call!'
	/gems/sinatra-1.1.3/lib/sinatra/base.rb:544:in `call'
	/gems/rack-flash-0.1.1/lib/rack/flash.rb:148:in `call'
	/gems/padrino-core-0.9.19/lib/padrino-core/reloader.rb:31:in `call'
	/gems/padrino-core-0.9.19/lib/padrino-core/logger.rb:282:in `call'
	/gems/warden-1.0.3/lib/warden/manager.rb:35:in `call'
	/gems/warden-1.0.3/lib/warden/manager.rb:34:in `catch'
	/gems/warden-1.0.3/lib/warden/manager.rb:34:in `call'
	/gems/rack-less-1.5.0/lib/rack/less/base.rb:40:in `call!'
	/gems/rack-less-1.5.0/lib/rack/less/base.rb:24:in `call'
	/gems/rack-1.2.1/lib/rack/showexceptions.rb:24:in `call'
	/gems/rack-1.2.1/lib/rack/methodoverride.rb:24:in `call'
	/gems/rack-1.2.1/lib/rack/session/cookie.rb:37:in `call'
	/gems/sinatra-1.1.3/lib/sinatra/base.rb:1173:in `call'
	/gems/sinatra-1.1.3/lib/sinatra/base.rb:1199:in `synchronize'
	/gems/sinatra-1.1.3/lib/sinatra/base.rb:1173:in `call'
	/gems/padrino-core-0.9.19/lib/padrino-core/router.rb:71:in `call'
	/gems/padrino-core-0.9.19/lib/padrino-core/router.rb:64:in `each'
	/gems/padrino-core-0.9.19/lib/padrino-core/router.rb:64:in `call'
	/gems/rack-1.2.1/lib/rack/content_length.rb:13:in `call'
	/gems/rack-1.2.1/lib/rack/chunked.rb:15:in `call'
	/gems/thin-1.2.8/lib/thin/connection.rb:84:in `pre_process'
	/gems/thin-1.2.8/lib/thin/connection.rb:82:in `catch'
	/gems/thin-1.2.8/lib/thin/connection.rb:82:in `pre_process'
	/gems/thin-1.2.8/lib/thin/connection.rb:57:in `process'
	/gems/thin-1.2.8/lib/thin/connection.rb:42:in `receive_data'
	/gems/eventmachine-0.12.10/lib/eventmachine.rb:256:in `run_machine'
	/gems/eventmachine-0.12.10/lib/eventmachine.rb:256:in `run'
	/gems/thin-1.2.8/lib/thin/backends/base.rb:61:in `start'
	/gems/thin-1.2.8/lib/thin/server.rb:159:in `start'
	/gems/rack-1.2.1/lib/rack/handler/thin.rb:14:in `run'
	/gems/padrino-core-0.9.19/lib/padrino-core/server.rb:43:in `build'
	/gems/padrino-core-0.9.19/lib/padrino-core/server.rb:14:in `run!'
	/gems/padrino-core-0.9.19/lib/padrino-core/cli/adapter.rb:29:in `start'
	/gems/padrino-core-0.9.19/lib/padrino-core/cli/base.rb:23:in `start'
	/gems/thor-0.14.6/lib/thor/task.rb:22:in `send'
	/gems/thor-0.14.6/lib/thor/task.rb:22:in `run'
	/gems/thor-0.14.6/lib/thor/invocation.rb:118:in `invoke_task'
	/gems/thor-0.14.6/lib/thor.rb:263:in `dispatch'
	/gems/thor-0.14.6/lib/thor/base.rb:389:in `start'
	/gems/padrino-core-0.9.19/bin/padrino:8
	/gems/bin/padrino:19:in `load'
	/gems/bin/padrino:19

Niestety nie znalazłem żadnej wartościowej informacji na temat tego jak to naprawić, tak więc napisałem (brzydki ;) ) workaround który dopisujemy w app/app.rb:

module Mail
  class Message
    def self.default_encoding
    end
  end
end

Przynajmniej na “teraz” rozwiązanie to wydaje się działać.

Wyciek bazy sdu.pl i mailing do poszkodowanych z pomocą Rubiego (nie Railsów)

Wczoraj ktoś znalazł w Google zrzut z bazy danych, zawierającej loginy, hasła i e-maile użytkowników pewnego portalu (około 2,5 tysiąca). Przerażające jest to, że hasła nie były nawet hashowane (nie mówiąc już o soleniu). Wprawdzie baza była dość stara (2006 rok), jednak mając na uwadze dobro użytkowników, postanowiłem rozesłać im wiadomość e-mail informującą o konieczności zmiany haseł w swoich skrzynkach.

Oczywiście wykorzystałem do tego język Ruby. Mailing postanowiłem zrobić z serwera SMTP (maile rozsyłane z localhosta często automatycznie trafiają do spamu). Aby to w miarę sprawnie rozesłać, postanowiłem użyć biblioteki ActionMailer należącej do frameworka Ruby on Rails. Cały framework nie był mi potrzebny, więc załączyłem tylko to co trzeba.

Pierwszą rzeczą jaką należy zrobić, jest dołączenie bibliotek oraz konfiguracja ActionMailera:

#coding: utf-8
require 'action_mailer'

ActionMailer::Base.smtp_settings = {
    :domain => "nasza.domena.pl",
    :user_name=> "nasz@email.pl",
    :password=> "naszehaslo",
    :address=> "nasz.adres.pl",
    :port=> 587,
    :authentication=> :plain,
    :enable_starttls_auto=> true
}

Następnie tworzymy sobie klasę służącą do wysyłania emaili:

class Mailing < ActionMailer::Base
  def warning(to)
    recipients  to
    from    'nasz@adres.email'
    subject 'Wyciek bazy danych'
    body    'Treść wiadomości ostrzegawczej'
  end
end

Mamy już jak wysyłać wiadomość, musimy jeszcze mieć do kogo. Skorzystamy z wyrażenia regularnego, które z danego wiersza kodu, wyciągnie nam wszystkie adresy e-mail. Zadeklarujemy też sobie tablicę do której będziemy te adresy wrzucać:

reg = /[A-Z0-9._%-]+@([A-Z0-9-]+\.)+[A-Z]{2,4}/i
emails = []

Następnie otwieramy nasz plik z e-mailami (np. właśnie dump z bazy), i odczytując wiersz po wierszu, wyciągamy wszystkie adresy e-mail:

File.open("plik_zrodlowy.sql", "r") do |infile|
   while (line = infile.gets)
      next unless  line.scan(/#{reg}/).size > 0
      line.gsub(/#{reg}/).each { |em| emails << em }
   end
end

Wyciąganie odbywa się bardzo prosto. Najpierw sprawdzamy czy w danym wierszu istnieją jakiekolwiek dopasowania (adresy e-mail) i jeśli tak, to wrzucamy je do tablicy emails. Jeśli nie ma, przechodzimy do następnej linii.

Mamy już jak wysyłać, mamy też do kogo. Nie pozostaje nam nic innego jak to rozesłać. Zastosowałem poniżej najprostszą metodę możliwą. Jeden wątek i pętelka. Wydajniej byłoby zrobić to np w 2-3 wątkach, jednak było już późno i mi się nie chciało ;)

i = 0
emails.uniq.each do |email|
	i+=1
	begin
		Mailing.warning(email).deliver
	rescue Exception=>e
		puts e
	end
	puts "#{i} - #{email}"
end

W tym fragmencie interesujące są dwie rzeczy. Po pierwsze dzięki wywołaniu metody uniq na e-mailach, pozbywamy się powtórzeń, co gwarantuje nam że wiadomość zostanie wysłana tylko raz do danego odbiorcy.

Po drugie, korzystamy z bloku z przechwytywaniem wyjątków. Dzięki temu, nawet jeśli ActionMailer zwróci nam jakiś błąd przy którejś z wiadomości, rozsyłanie nie zostanie przerwane.

To by było na tyle. Przypominam tylko o tym, żeby nadmiernie nie spamować użytkowników ;)

Copyright © 2018 Running with Ruby

Theme by Anders NorenUp ↑