Rails mailer error with inline attachment - ruby-on-rails

I have a very rare behavior in Action Mailer, I had implement a mailer action like 5 months ago and it was working, but yesterday, for some strange reason, it crashed.
The problem
I have a mail layout, in order to use it in all my emails, in it I render an image that is attached previously by a before filter
Layout = app/views/layouts/email.html.erb
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Visionamos</title>
<link rel="stylesheet" type="text/css" href=<%="/assets/email.css" %>>
</head>
<body>
<table>
<tr>
<td id="head">
<table>
<tr class="image">
<td><%= image_tag(attachments['visionamos.png'].url) %></td>
...
..
.
User Mailer = app/mailers/users.rb
class UsuariosMailer < ActionMailer::Base
include AbstractController::Callbacks # include controller callbacks
default :from => "monitoreo#visionamos.com"
layout "mail" #Set email layout
before_filter :add_inline_attachments! # Add image header for all actions
def otp_password(user, otp_key)
#usuario = user
#code = otp_key
email_with_name = "#{#usuario.nombre} <#{#usuario.email}>"
mail(:to => email_with_name, :subject => "One time password, Plataforma Visionamos")
end
private
def add_inline_attachments!
attachments.inline['visionamos.png'] = File.read("#{Rails.root}/app/assets/images/visionamos.png")
end
end
Now, when I try to send the email I'm, getting this error
NoMethodError - undefined method `match' for nil:NilClass:
mail (2.5.4) lib/mail/utilities.rb:112:in `unbracket'
mail (2.5.4) lib/mail/part.rb:29:in `cid'
mail (2.5.4) lib/mail/part.rb:33:in `url'
app/views/layouts/mail.html.erb:13:in `_app_views_layouts_mail_html_erb__573848672563180413_70191451095440'
<td><%= image_tag(attachments['visionamos.png'].url) %></td>
But the image is attached to the email
>> attachments['visionamos.png']
=> #<Mail::Part:70191451538040, Multipart: false, Headers: <Content-Type: image/png; filename="visionamos.png">, <Content-Transfer-Encoding: binary>, <Content-Disposition: inline; filename="visionamos.png">, <content-id: >>
My DevEnv
Mac with Maverics
Ruby 2.0 + Rails 3.2.16
Plus
The email is working in my amazon ec2 instance, in my coworkers environments (ubuntu and mac)
If I delete the image_tag method in layout, the email is sent and the image is show as attachment, no inline
Update!!!
I've tried #Gene solution but even the email is sent, the images are normal attachments, no inline, so looking deeply, I found this
>> attachments.inline['visionamos.png'].header
=> #<Mail::Header:0x00000106cf6870 #errors=[], #charset=nil, #raw_source="", #fields=[#<Mail::Field:0x00000106cf60c8 #field=#<Mail::ContentTypeField:0x00000106cf5fd8 #charset=nil, #main_type="image", #sub_type="png", #parameters={"filename"=>"visionamos.png"}, #name="Content-Type", #length=nil, #tree=nil, #element=#<Mail::ContentTypeElement:0x00000106cf5d30 #main_type="image", #sub_type="png", #parameters=[{"filename"=>"visionamos.png"}]>, #value="image/png; filename=\"visionamos.png\"", #filename="visionamos.png">, #field_order_id=23>, #<Mail::Field:0x00000106d17390 #field=#<Mail::ContentTransferEncodingField:0x00000106d172a0 #charset=nil, #name="Content-Transfer-Encoding", #length=nil, #tree=nil, #element=#<Mail::ContentTransferEncodingElement:0x00000106d16ff8 #encoding="binary">, #value="binary">, #field_order_id=24>, #<Mail::Field:0x00000106d14a78 #field=#<Mail::ContentDispositionField:0x00000106d14960 #charset=nil, #name="Content-Disposition", #length=nil, #tree=nil, #element=#<Mail::ContentDispositionElement:0x00000106d145c8 #disposition_type="inline", #parameters=[{"filename"=>"visionamos.png"}]>, #value="inline; filename=\"visionamos.png\"", #parameters={"filename"=>"visionamos.png"}, #filename="visionamos.png">, #field_order_id=26>, #<Mail::Field:0x00000106d3e8f0 #field=#<Mail::UnstructuredField:0x00000106d5ef60 #errors=[["content-id", nil, #<Mail::Field::ParseError: Mail::MessageIdsElement can not parse |<52fe636fae8a6_686085098c087130#MacBook Pro de Ruben.mail>|
Reason was: Expected one of !, #, $, %, &, ', *, +, -, /, =, ?, ^, _, `, {, |, }, ~, #, ., ", > at line 1, column 40 (byte 40) after <52fe636fae8a6_686085098c087130#MacBook>]], #charset=#, #name="content-id", #length=nil, #tree=nil, #element=nil, #value="">, #field_order_id=100>]>
The interesting part is
#<Mail::Field::ParseError: Mail::MessageIdsElement can not parse |<52fe636fae8a6_686085098c087130#MacBook Pro de Ruben.mail>|
Reason was: Expected one of !, #, $, %, &, ', *, +, -, /, =, ?, ^, _, `, {, |, }, ~, #, ., ", > at line 1, column 40 (byte 40) after <52fe636fae8a6_686085098c087130#MacBook>]],

I looked at the mail source.
This error can only occur if the content id field is nil. However calling .url should be setting the content id to an empty string unless has_content_id? is returning true, meaning there's already a content id field in the multipart header.
This is not happening, so we must have a strange case where the header object is reporting has_content_id? true yet is returning a content_id of nil.
Try setting the content id field explicitly just after you set the graphic.
attachments['visionamos.png'].header['content-id'] = 'logo.graphic'
If this works, there's still the puzzle of why it's necessary. Did you make any other changes to mailer configuration or code? Did you upgrade any gems?
Addition responding to question edit
The header parser seems to be failing because there are spaces in the id ...#MacBook Pro de Ruben.mail. Try re-naming the computer with no spaces! I guess this constitutes a bug in mail. Spaces should be elided or replaced with a legal character.
My guess is that this will also fix the original problem, and you won't need to set the content-id manually any more. Hence another guess: you changed machine name or moved development to a new machine. and that's when the bug appeared!

sudo scutil --set HostName 'mymachine'
fixed this for me.
scutil --get HostName
was returning (not set)

Related

Can't test a mailer if the message is Cyrillic because of content_transfer_encoding

Working on www.railstutorial.org/book Chapter 10
I created UserMailer and all the stuff and everything worked as it should.
Then I changed the text of the message to Cyrillic and test couldn't match "assert_match user.name, mail.body.encoded".
I figured out this:
The mail template can have a non-English word in the first paragraph. This
%p Здравствуйте, #{#user.name}!
%p Well
=link_to "Activate", edit_account_activation_url(#user.activation_token, email: #user.email)
works ok. In rails console I can see this (excerpt):
Content-Type: text/html;\r\n charset=UTF-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n\r\n=D0=97=D0=B4=D1=80=D0=B0=D0=B2=D1=81=D1=82=D0=B2=D1=83=D0=B9=D1=82=D0=B5=\r\n, Test User!\r\n\r\nWell\r\nhttp://localhost:3000/account_activations/yYZR6sI1_dy5ha3mBf1s=\r\niw/edit?email=3Dtest%40example.com\">Activate
If I add another non-Latin word somewhere (I added オープンソース to the second p), the encoding changes
Content-Type: text/html;\r\n charset=UTF-8\r\nContent-Transfer-Encoding: base64\r \n\r\nPHA+0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1LCBUZXN0IFVzZXIhPC9wPgo8\r\ncD5XZWxsIOOCquODvOODl+ODs+OCveODvOOCuTwvcD4KPGEgaHJlZj0iaHR0\r\ncDovL2xvY2FsaG9zdDozMDAwL2FjY291bnRfYWN0aXZhdGlvbnMveVlaUjZz\r\nSTFfZHk1aGEzbUJmMXNpdy9lZGl0P2VtYWlsPXRlc3QlNDBleGFtcGxlLmNv\r\nbSI+QWN0aXZhdGU8L2E+Cg==\r\n
and the test fails.
I tried to make the mailer use the encoding I like
class UserMailer < ActionMailer::Base
default from: "me#here.is", content_transfer_encoding: "quoted-printable"
but without success.
What should I do?
You can use assert_match user.name, mail.text_part.decoded

Rails: image_tag helper / umlaut in file name throws error in production

I am uploading an image with a file name containing an umlaut via dragonfly in a Rails 3 app on Heroku. Then I'm trying to display the image using
image_tag #model.image.url, …
In development everything works just fine, but in production I'm getting:
incompatible character encodings: UTF-8 and ASCII-8BIT
.bundle/gems/ruby/1.9.1/gems/actionpack-3.0.7/lib/action_view/helpers/tag_helper.rb:129:in `*'
After reading a bit I've added
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8
in environment.rb but the problem remains.
What is the proper way to go about this? Do I have to fix the file name when uploading? I was under the impression this should work just fine in Rails 3?
Well, you could try something like url.force_encoding('utf8')
You could also simply sanitize the url in the model before saving it to the database - that's what I did. And, yes, I sometimes stumble over this in the weirdest places, too.
This is what my model looked like:
# encoding: UTF-8
class Page < ActiveRecord::Base
before_save :sanitize_title
private
def sanitize_title
self.title = self.title.force_encoding('UTF-8').downcase.gsub(/[ \-äöüß]/, ' ' => '_', '-' => '_', 'ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'ß' => 'ss').gsub(/[^a-z_]/,'')
end
end
This will replace the German umlaute with their ASCII counterparts, convert spaces to underscores and drop everything else.
The first line # encoding: UTF-8 is important or ruby will complain of non-ASCII characters in the model.rb file...
In addition to #Rhywden's answer, here my solution specific for Dragonfly:
image_accessor :image do :after_assign
after_assign{|i| i.name = sanitize_filename(image.name) }
end
def sanitize_filename(filename)
filename.strip.tap do |name|
name.sub! /\A.*(\\|\/)/, ''
name.gsub! /[^\w\.\-]/, '_'
end
end
Details here http://markevans.github.com/dragonfly/file.Models.html and here http://guides.rubyonrails.org/security.html#file-uploads .

Mechanize - How to follow or "click" Meta refreshes in rails

I have a bit trouble with Mechanize.
When a submit a form with Mechanize. I am come to a page with one meta refresh and there is no links.
My question is how do i follow the meta refresh?
I have tried to allow meta refresh but then i get a socket error.
Sample code
require 'mechanize'
agent = WWW::Mechanize.new
agent.get("http://euroads.dk")
form = agent.page.forms.first
form.username = "username"
form.password = "password"
form.submit
page = agent.get("http://www.euroads.dk/system/index.php?showpage=login")
agent.page.body
The response:
<html>
<head>
<META HTTP-EQUIV=\"Refresh\" CONTENT=\"0;URL=index.php?showpage=m_frontpage\">
</head>
</html>
Then I try:
redirect_url = page.parser.at('META[HTTP-EQUIV=\"Refresh\"]')[
"0;URL=index.php?showpage=m_frontpage\"][/url=(.+)/, 1]
But I get:
NoMethodError: Undefined method '[]' for nil:NilClass
Internally, Mechanize uses Nokogiri to handle parsing of the HTML into a DOM. You can get at the Nokogiri document so you can use either XPath or CSS accessors to dig around in a returned page.
This is how to get the redirect URL with Nokogiri only:
require 'nokogiri'
html = <<EOT
<html>
<head>
<meta http-equiv="refresh" content="2;url=http://www.example.com/">
</meta>
</head>
<body>
foo
</body>
</html>
EOT
doc = Nokogiri::HTML(html)
redirect_url = doc.at('meta[http-equiv="refresh"]')['content'][/url=(.+)/, 1]
redirect_url # => "http://www.example.com/"
doc.at('meta[http-equiv="refresh"]')['content'][/url=(.+)/, 1] breaks down to: Find the first occurrence (at) of the CSS accessor for the <meta> tag with an http-equiv attribute of refresh. Take the content attribute of that tag and return the string following url=.
This is some Mechanize code for a typical use. Because you gave no sample code to base mine on you'll have to work from this:
agent = Mechanize.new
page = agent.get('http://www.examples.com/')
redirect_url = page.parser.at('meta[http-equiv="refresh"]')['content'][/url=(.+)/, 1]
page = agent.get(redirect_url)
EDIT: at('META[HTTP-EQUIV=\"Refresh\"]')
Your code has the above at(). Notice that you are escaping the double-quotes inside a single-quoted string. That results in a backslash followed by a double-quote in the string which is NOT what my sample uses, and is my first guess for why you're getting the error you are. Nokogiri can't find the tag because there is no <meta http-equiv=\"Refresh\"...>.
EDIT: Mechanize has a built-in way to handle meta-refresh, by setting:
agent.follow_meta_refresh = true
It also has a method to parse the meta tag and return the content. From the docs:
parse(content, uri)
Parses the delay and url from the content attribute of a meta tag. Parse requires the uri of the current page to infer a url when no url is specified. If a block is given, the parsed delay and url will be passed to it for further processing.
Returns nil if the delay and url cannot be parsed.
# <meta http-equiv="refresh" content="5;url=http://example.com/" />
uri = URI.parse('http://current.com/')
Meta.parse("5;url=http://example.com/", uri) # => ['5', 'http://example.com/']
Meta.parse("5;url=", uri) # => ['5', 'http://current.com/']
Meta.parse("5", uri) # => ['5', 'http://current.com/']
Meta.parse("invalid content", uri) # => nil
Mechanize treats meta refresh elements just like links without text. Thus, your code can be as simple as this:
page = agent.get("http://www.euroads.dk/system/index.php?showpage=login")
page.meta_refresh.first.click

Unexpected line-breaks when sending XML attachments using ActionMailer

My application stores a lot of XML files. A background job periodically sends some of those XML files to a specific mailbox. The mailer code is dead-simple:
class MailSender < ActionMailer::Base
default :from => AppConfig.mail_from
smtp_settings :address => AppConfig.smtp_host,
:username => AppConfig.smtp_user,
:password => AppConfig.smtp_pass
def send_xml(record)
f = record.filename.gsub("\\", "/") # converts \ to /
f_short = arq.gsub(/.*\//, "") # extracts only the filename
f_phys = "#{AppConfig.xml_root}#{arq}" # builds the physical filename
headers["Return-Receipt-To"] = AppConfig.return_receipt
attachments[f_short] = File.read(f_phys) if File.exists?(f_phys)
mail :subject => "...",
:to => AppConfig.mail_to
end
end
However, for some reason, those XML are getting corrupted on transmission: the first line break gets added at column 987, and the following are added at column 990. After each break, a space is inserted. I think the picture says for itself:
col 1 col 990
|.................................................|
<?xml version="1.0" ... </IE><IM>321505493301<
/IM><CNAE>4744001< ... 00</pCOFINS><vCOFINS>0.00
</vCOFINS></COFINS ... /prod><imposto><ICMS><ICM
S40><orig>0</orig> ... <infAdic><infCpl>Permite
I tried calling File.read myself on rails console, it works fine, no line breaks are added. So I assume the error should lie on the ActionMailer. Any tips?
Edit for clarification: Most of the XML document lie on a big, single line. I can't change it, since the XML are digitally signed - any change, including adding line breaks and indentation, breaks the digital signature.
Answering the question that gave me the 'Thumbleweed' badge :)
I ended up encoding the file myself, and it's now working fine:
attachments[f_short] = {
:encoding => 'base64',
:content => Base64.encode64( File.read(f_phys) ).chomp
} if File.exists?(f_phys)

How to replace HTML nodes using Nokogiri

I have an HTML file, in which, all
<div class="replace-me">
</div>
must be replaced with
<video src='my_video.mov'></video>
The code is:
doc.css("div.replace-me").each do |div|
div.replace "<video src='my_video.mov'></video>"
end
It's simple, but, unfortunately, it does't work for me. Nokogiri crashes with the following error:
undefined method `children' for nil:NilClass
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/whiny_nil.rb:52:in `method_missing'
/Library/Ruby/Gems/1.8/gems/nokogiri-1.4.2/lib/nokogiri/html/document_fragment.rb:16:in `initialize'
/Library/Ruby/Gems/1.8/gems/nokogiri-1.4.2/lib/nokogiri/xml/node.rb:424:in `new'
/Library/Ruby/Gems/1.8/gems/nokogiri-1.4.2/lib/nokogiri/xml/node.rb:424:in `fragment'
/Library/Ruby/Gems/1.8/gems/nokogiri-1.4.2/lib/nokogiri/xml/node.rb:776:in `coerce'
/Library/Ruby/Gems/1.8/gems/nokogiri-1.4.2/lib/nokogiri/xml/node.rb:331:in `replace'
Replacing with a div works:
doc.css("div.replace-me").each do |div|
div.replace "<div>Test</div>"
end
Is this a Nokogiri bug, or did I do something wrong?
The same issue occurs with add_child, inner_html and other setters for this purpose.
I will quote my comment from your previous question:
This happens because of HTML strictness (HTML has a predefined set of elements). Replace Nokogiri::HTML( self.content ) with Nokogiri::XML( self.content ) and do not forget to add a DOCTYPE declaration manually later.
If you look into the log, the part you chose with Nokogiri turns nil.
Try it this way:
doc.css(".replace-me").each do |div|
div.replace "<video src='my_video.mov'></video>"
end
Or you may need to specify which element you want to replace.
I can't duplicate the problem. Granted, the question is old, but this works:
require 'nokogiri'
doc = Nokogiri::HTML(<<EOT)
<div class="replace-me">
</div>
EOT
It could have been a Ruby 1.8 issue, an issue with that version of Nokogiri, or something was wrong in your libXML... it's hard to say given the information in the question.
doc.at('div.replace-me').replace("<video src='my_video.mov'></video>")
doc.to_html
# => "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n" +
# "<html><body>\n" +
# "<video src=\"my_video.mov\"></video>\n" +
# "</body></html>\n"

Resources