Rails - Mail, getting the body as Plain Text - ruby-on-rails

Given: message = Mail.new(params[:message])
as seen here: http://docs.heroku.com/cloudmailin
It shows how to get the message.body as HTML, how to do you get the plain/text version?
Thanks

The code above:
message = Mail.new(params[:message])
will create a new instance of the mail gem from the full message. You can then use any of the methods on that message to get the content. You can therefore get the plain content using:
message.text_part
or the HTML with
message.html_part
These methods will just guess and find the first part in a multipart message of either text/plain or text/html content type. CloudMailin also provides these as convenience methods however via params[:plain] and params[:html]. It's worth remembering that the message is never guaranteed to have a plain or html part. It may be worth using something like the following to be sure:
plain_part = message.multipart? ? (message.text_part ? message.text_part.body.decoded : nil) : message.body.decoded
html_part = message.html_part ? message.html_part.body.decoded : nil
As a side note it's also important to extract the content encoding from the message when you use these methods and make sure that the output is encoded into the encoding method you desire (such as UTF-8).

What is Mail?
The message defined in the question appears to be an instance of the same Mail or Mail::Message class, which is also used in ActionMailer::Base, or in the mailman gem.
I'm not sure where this is integrated into rails, but Steve Smith has pointed out that this is defined in the mail gem.
Usage Section of the gem's readme on github.
Documentation of Mail::Message on rubydoc.info.
Extracting a Part From a Multipart Email
In the gem's readme, there is an example section on reading multipart emails.
Besides the methods html_part and text_part, which simply find the first part of the corresponding mime type, one can access and loop through the parts manually and filter by the criteria as needed.
message.parts.each do |part|
if part.content_type == 'text/plain'
# ...
elsif part.content_type == 'text/html'
# ...
end
end
The Mail::Part is documented here.
Encoding Issues
Depending on the source of the received mail, there might be encoding issues. For example, rails could identify the wrong encoding type. If, then, one tries to convert the body to UTF-8 in order to store it in the database (body_string.encode('UTF-8')), there might be encoding errors like
Encoding::UndefinedConversionError - "\xFC" from ASCII-8BIT to UTF-8
(like in this SO question).
In order to circumvent this, one can readout the charset from the message part and tell rails what charset it has been before encoding to UTF-8:
encoding = part_to_use.content_type_parameters['charset']
body = part_to_use.body.decoded.force_encoding(encoding).encode('UTF-8')
Here, the decoded method removes the header lines, as shown in the encoding section of the mail gem's readme.
EDIT: Hard Encoding Issues
If there are really hard encoding issues, the former approach does not solve, have a look at the excellent charlock_holmes gem.
After adding this gem to the Gemfile, there is a more reliable way to convert email encodings, using the detect_encoding method, which is added to Strings by this gem.
I found it helpful to define a body_in_utf8 method for mail messages. (Mail::Part also inherits from Mail::Message.):
module Mail
class Message
def body_in_utf8
require 'charlock_holmes/string'
body = self.body.decoded
if body.present?
encoding = body.detect_encoding[:encoding]
body = body.force_encoding(encoding).encode('UTF-8')
end
return body
end
end
end
Summary
# select the part to use, either like shown above, or as one-liner
part_to_use = message.html_part || message.text_part || message
# readout the encoding (charset) of the part
encoding = part_to_use.content_type_parameters['charset'] if part_to_use.content_type_parameters
# get the message body without the header information
body = part_to_use.body.decoded
# and convert it to UTF-8
body = body.force_encoding(encoding).encode('UTF-8') if encoding
EDIT: Or, after defining a body_in_utf8 method, as shown above, the same as one-liner:
(message.html_part || message.text_part || message).body_in_utf8

email = Mail.new(params[:message])
text_body = (email.text_part || email.html_part || email).body.decoded
I'm using this solution on RedmineCRM Helpdesk plugin

I believe if you call message.text_part.body.decoded you will get it converted to UTF-8 for you by the Mail gem, the documentation isn't 100% clear on this though.

Save HTML Body Format in Rails
USE <%= #email.body.html_safe%>
This will send text written in email text editor as it is to email.

Related

How do I get ActionMailer to log sent messages but NOT include the attachments?

I like that Rails automatically logs all messages that are sent out from the app. What I don't like is how it fills up my log file with huge blocks of useless Base64-encoded text. This makes looking through the log file a pain because I have to skip past these megabytes-long blocks of unreadable noise. It also causes the log file to grow too quickly and fill up the disk.
How can I get it to still log all messages that are sent but NOT include any of the attachments? Is there a way to tell it to strip out the attachments before logging or something?
Usually all I'm interested in seeing are the headers (subject, who it was sent to) and (at least some of the time) the message body text.
It wouldn't hurt to also have a list of the attachments (file name and type) too — but it certainly does me no good to see the full Base64 dump of all the attachments! (If I want to check and see if the attachments are coming through okay, I already know how to add a mail interceptor that bcc's all outgoing mail to my inbox.)
I ended up having it log the following details about each mail that gets sent out:
The headers
The structure of the email (which parts are within which other parts), including a list of attachments, before stripping out all
attachments
Decoded, human-readable, searchable message bodies (the Rails default is to log the encoded, which is hard for a human to read
and breaks words in random places, making it hard to search)
To accomplish this, I overrode ActionMailer::Base.set_payload_for_mail in my app and replaced this line:
payload[:mail] = mail.encoded
with a version that:
Creates a copy of the Mail object
Calls mail.without_attachments!, and
Only logs:
mail.header.encoded
the output from mail.inspect_structure (from my fork)
the result of calling part.decoded for each (non-attachment) part.
Check out this gist for the whole thing.
Logging of the message body is handled here:
https://github.com/rails/rails/blob/master/actionmailer/lib/action_mailer/log_subscriber.rb#L14
module ActionMailer
class LogSubscriber < ActiveSupport::LogSubscriber
def deliver(event)
info do
recipients = Array(event.payload[:to]).join(', ')
"\nSent mail to #{recipients} (#{event.duration.round(1)}ms)"
end
debug { event.payload[:mail] }
end
# ... more methods ...
end
end
If you want to exclude the mail body entirely you can change the log level to exclude 'debug'. Otherwise you're only option is to override this method and do your own thing.
Unfortunately event.payload[:mail] is a String. Removing attachments could be messy. Showing just the headers wouldn't be that hard with a regex. Otherwise you'd need to recreate the Mail object from that string and extract what you want.
Another option would be to change the logger used by ActionMailer so that it logs to it's own file. That would at least keep your standard Rails log clean.
https://github.com/rails/rails/blob/master/actionmailer/lib/action_mailer/railtie.rb#L12

Is there any gem to send request to some service in xml format

I want to send request to some third party service in xml and also expecting response in xml. I'm searching for some gem or any idea how to do this.
Thing which is in my mind is to
make some partail _example.xml.builder
onclick from my view to some button send ajax request to controller action and use render_to_string to render that xml doc and then
Save it in some variable
and then call to that service method in same action
But it is not proper thing as I expect there should be some thing more efficient than my suggested thing
RoR doesn't natively use XML so some degree of conversion is required.
Having said that, XML generation is very simple in RoR applications. There are several ways of doing this, my favourite being constructing the required data as a Hash (which is native to Ruby) then the_hash.to_xml.
The XML conversion can also be defined in a model Class if you wish a consistent result:
class Example < ActiveRecord::Base
# ensure that only column1, column2, etc are output as XML
def to_xml(options = {})
super( options.merge( select(:column1, :column2, etc) ) )
end
end
Then in your controller:
poster = Example.find(123)
request = Net::HTTP.new('www.example.com', 80)
request.post('/path', poster.to_xml)
Hopefully the above demonstrates a simple example of posting XML data to a remote host. As you mentioned, a more complicated XML can be constructed using xml.builder
HTH and good luck.

There is a common practice to reject "malicious" user-input to a rails application

I am using Ruby on Rails 3.1.0 and I would like to know what is a common practice to prevent to store "malicious" values in the database.
For example, I have a database table column means to store URLs. A user, passing the validation (just a length check), can submit a URL like http://<script>alert('hello!');</script>. I would like to do not permit to store links like the above... how can I make that?
The proper thing to do is use URI to parse the supposed URL and then check each component:
validate :url_check
def url_check
u = URI.parse(self.url)
# check u.scheme, u.userinfo, etc. and call errors.add(:url, '...')
# if something is invalid.
rescue URI::InvalidURIError
errors.add(:url, 'You are being naughty.')
end
While those links are in the database, they do no harm. Problems might occur when you try to render them. Rails does a good job in escaping most things that you output (I didn't dare to say "everything", 'cause I don't know for sure).
You can be extra sure and escape a string yourself:
CGI.escape your_link_url
Link: CGI.escape
You can use regex to validate it's a valid url without the '<', '>' url. And HTML encode it where it applies.

Parsing emails with ActionMailer::Base receive method

I want to know if there is a way to parse a received email.
For example, someone sends me the following email:
{
product: "x_product",
quantity: "1",
price: "15",
}
What I want is a way to get this information and insert it in the database
I know there is a method in ActionMailer::Base called receive.
Is this the correct approach? How to parse this?
Yes this is the correct approach, just google "receive mails with ruby on rails" - there are plenty of tutorials to guide you.
The parsing depends on the kind of data you are about to receive. This looks like JSON, so you'd simply let a JSON parser do the work and it will give you a proper Ruby data structure. The rest (putting it into a DB) can be handled by a model.
It would look a bit like this:
class MailReceiver < ActionMailer::Base
def self.receive(message)
# depending on your Rails version you can use either TMail or Mail to parse the raw mail
mail = TMail::Mail.parse(message)
# parse the JSON
my_data = ActiveSupport::JSON.decode(mail.body)
# create something with the data
MyModel.create(my_data)
end
end
I did not cover the actual fetching of the mails from a mailbox. Again: google it, there are tons of tutorials out there. Have a look at Fetcher, which has always served me well.

How do I parse text from email body using a Rails3 app?

I need to get the name and email address from a forwarded email where there is a line like this in the body:
From: john smith <sales#domain.com>
What is the best way to do this? Is there a good library/gem I should use?
Also this line will vary from which email client it's sent from and also what language, so I need a more robust way than just matching just this string.
Also some clients do not include the name but just the email address on this line so I need to be able to handle that.
require 'mail' # only needed for non-Rails
m = Mail.new( "any RFC-conforming email including headers" )
puts m[:from].display_names.first
puts m[:from].addresses.first
The Mail object is automatically included in Rails projects that use ActionMailer - but you'll need to add it to your Gemfile if you're not using ActionMailer
Update
I didn't read the OP's question properly - they want to get an email address from the body of a forwarded mail. I'd just use some regex:
name, email = m.body.match( /^\s*From:\s*(.*)\s+<(.*)>$/)[1,2]
NB: this assumes a fair amount about the structure of the email - an HTML-only email can easily thwart this regex.

Resources