Mail gem determine whether plaintext or html - ruby

I'm using the mail gem from https://github.com/mikel/mail
I use it to parse raw mail data: e.g
require 'mail'
maildata = Mail.new(body) #where body is the raw text of an email message
#from there I can see info such as
p maildata.body.decoded #displays the decoded email body
p maildata.from #shows who the email is from
How would I figure out whether the email is plaintext or html is there a built in way to do this?

You could look at maildata.content_type:
maildata.content_type
#=> "text/plain; charset=us-ascii"
If it's a multipart e-mail, you could have both plain text and HTML. You could then look at the parts array to see which content types it includes:
maildata.content_type
#=> "multipart/alternative; boundary=\"--==_mimepart_4f848491e618f_7e4b6c1f3849940\"; charset=utf-8"
maildata.parts.collect { |part| part.content_type }
#=> ["text/plain; charset=utf-8", "text/html; charset=utf-8"]

mail = Mail.new do
text_part do
body 'test'
end
html_part do
content_type 'text/html; charset=utf-8'
body '<b>test</b>'
end
end
mail.multipart? # true
puts mail.html_part.decoded # "<b>test</b>"
puts mail.text_part # "test"

Related

Send accentuated characters inside an email body, how to fix encoding issues

In a script which sends emails through Net::SMTP, I've to figure out how to properly encode the email body in order to support accentuated characters. I've separated the email into 3 parts: headers, body and attachment, as from this tutorial https://www.tutorialspoint.com/ruby/ruby_sending_email.htm
Fixing this issue for the Subject header field wasn't a big deal:
require 'base64'
MARKER = 'FOOBAR'
def self.headers
<<~EOF
From: someemail#domain.tld
To: anotheremail#domaim.tld
# Base64 encoded UTF-8
Subject: =?UTF-8?B?#{Base64.strict_encode64('Accentuated characters supportés')}?=
Date: #{Time.now.to_s}
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=#{MARKER}
--#{MARKER}
EOF
end
I was tempted to reproduce the same logic for the email body, without any success. I've try several headers, such as Language, Content-Language, Content-Tranfer-Encoding, again, without any success. Using Ruby's .encode!('utf-8') was also ineffective.
The only working solution I can think of would be to send HTML encoded characters: using &eacute instead of é inside a HTML block. Though, I'd like to avoid this solution as I've to improve my comprehension of encoding issues.
Does anyone has a suggestion about this issue ?
Here's my code so far, if it can help anyone:
module Reports
module SMTP
MARKER = 'FOOBAR'
def self.send_report(file_path)
file_content = File.binread(file_path)
encoded_content = [file_content].pack('m') # Base64
mail_content = headers + body + attachment(file_path, encoded_content)
begin
Net::SMTP.start('my.smtp.srv', 25, 'HELO_fqdn', 'username', 'p455w0rD', :plain) do |smtp|
smtp.send_message(mail_content, 'from#domain.tld', ['to1#domain.tld', 'to2#domain.tld'])
end
rescue => e
puts e.inspect, e.backtrace
end
end
def self.headers
# see above
end
def self.body
<<~EOF
Content-Type: text/html
Content-Transfer-Encoding:utf8
Here's a franglish text with some characters accentués
--#{MARKER}
EOF
end
def self.attachment(file_path, encoded_content)
<<~EOF
Content-Type: multipart/mixed; name = #{file_path}
Content-Transfer-Encoding:base64
Content-Disposition: attachment; filename = #{file_path}
#{encoded_content}
--#{MARKER}--
EOF
end
end
end
Note: these emails are correctly decoded by ProtonMail webclients, but our company's webclient (OBM) doesn't display accentuated character nor attachment properly.
Adding ;charset="utf-8" to the Content-Type header from the body part fixed my problem.
Content-Type: text/html;charset="utf-8"

how i can decode or parser "multipart/form-data" in ruby 2.5 and get hash with params

I want decode body what do i get of apigateway request to a lambda in ruby 2.5 and get hash with params.
This one comes with 'Content-type: "form-data"'
----------------------------106174163609970174188191\r\nContent-Disposition: form-data;
name=\"packageName\"\r\n\r\ncom.orbemnetworks.americatv\r\n----------------------------106174163609970174188191\r\nContent-Disposition: form-data;
name=\"subscriptionId\"\r\n\r\nsuscripcion_mensual\r\n----------------------------106174163609970174188191\r\nContent-Disposition: form-data;
name=\"token\"\r\n\r\ndaomloifokamoigolhhapeap.AO-J1OxQBm7mW7z6pPPfwE9bJ23n5oeQwOBf46gKFSjjdHmkRHJDIR-RCbXtuDD6L_C_KfYEVETbvpu0M72t9-FXTNdHbB67dfJqzGMpx197Pb_IN8kLyU6ng-b8Yvo-1r-xJzcC04Rg\r\n----------------------------106174163609970174188191\r\nContent-Disposition: form-data;
name=\"idusuario\"\r\n\r\n1577633\r\n----------------------------106174163609970174188191\r\nContent-Disposition: form-data;
name=\"os\"\r\n\r\nA\r\n----------------------------106174163609970174188191--\r\n
My code was inspired (read: mostly copied from) Faraday Gem at: Faraday Gem Github
You will need to use gem 'multipart-parser', '~> 0.1.1' and
create a util.rb file:
require 'multipart_parser/reader'
module Util
extend self
# parse a multipart MIME message, returning a hash of any multipart errors
def parse_multipart(event)
body = event["body"]
boundary = MultipartParser::Reader::extract_boundary_value(event.dig("headers","Content-Type"))
reader = MultipartParser::Reader.new(boundary)
result = { errors: [], parts: [] }
def result.part(name)
hash = self[:parts].detect { |h| h[:part].name == name }
[hash[:part], hash[:body].join]
end
reader.on_part do |part|
result[:parts] << thispart = {
part: part,
body: []
}
part.on_data do |chunk|
thispart[:body] << chunk
end
end
reader.on_error do |msg|
result[:errors] << msg
end
reader.write(body)
result
end
def parse_multipart_hash(event)
event_parts = parse_multipart(event)[:parts]
event_parts.inject({}) do |hash, part|
if part[:part].filename.nil?
hash.merge({part[:part].name => part[:body]})
else
hash.merge({part[:part].name => {mime: part[:part].mime, filename: part[:part].filename, data: part[:body].join}})
end
end
end
end
Then you can use it like so:
require_relative './util'
def lambda_handler(event:, context:)
parsed_event_body = Util.parse_multipart_hash(event)
end
Not sure how this will handle images, but it works fine for CSV. You can modify parse_multipart to just return the hash, but I left it because it seem to provide a more generic structure to the data.
You can use the first method like this:
parts = Util.parse_multipart_hash(event)
meta, fieldname = parts.part("fieldname")
Rack's multipart parser can parse multipart form data
require "rack"
content_type = "multipart/form-data; boundary=xYzZY"
sanitized_multipart_form_data = multipart_form_data.gsub(/\r?\n/, "\r\n")
io = StringIO.new(sanitized_multipart_form_data)
tempfile = Rack::Multipart::Parser::TEMPFILE_FACTORY
bufsize = Rack::Multipart::Parser::BUFSIZE
query_parser = Rack::Utils.default_query_parser
result = Rack::Multipart::Parser.parse(io, sanitized_multipart_form_data.length, content_type, tempfile, bufsize, query_parser)
params = result.params
In case it helps, I've published the snippet above as a ruby gem
https://rubygems.org/gems/multipart_form_data_parser
https://github.com/nisanth074/multipart_form_data_parser

Hash/string gets escaped

This is my hyperresource client:
require 'rubygems'
require 'hyperresource'
require 'json'
api = HyperResource.new(root: 'http://127.0.0.1:9393/todos',
headers: {'Accept' => 'application/vnd.127.0.0.1:9393/todos.v1+hal+json'})
string = '{"todo":{"title":"test"}}'
hash = JSON.parse(string)
api.post(hash)
puts hash
The hash output is: {"todo"=>{"title"=>"test"}}
At my Sinatra with Roar API I have this post function:
post "/todos" do
params.to_json
puts params
#todo = Todo.new(params[:todo])
if #todo.save
#todo.extend(TodoRepresenter)
#todo.to_json
else
puts 'FAIL'
end
end
My puts 'params' over here gets: {"{\"todo\":{\"title\":\"test\"}}"=>nil}
I found out, these are 'escaped strings' but I don't know where it goes wrong.
EDIT:
I checked my api with curl and postman google extension, both work fine. It's just hyperresource I guess
You are posting JSON, ergo you either need to register a Sinatra middleware that will automatically parse incoming JSON requests, or you need to do it yourself.
require 'rubygems'
require 'hyperresource'
require 'json'
api = HyperResource.new(root: 'http://127.0.0.1:9393/todos',
headers: {'Accept' => 'application/vnd.127.0.0.1:9393/todos.v1+hal+json'})
string = '{"todo":{"title":"test"}}'
hash = JSON.parse(string)
api.post({:data => hash})
puts hash
---
post "/todos" do
p = JSON.parse(params[:data])
puts p.inspect
#todo = Todo.new(p[:todo])
if #todo.save
#todo.extend(TodoRepresenter)
#todo.to_json
else
puts 'FAIL'
end
end
Should do what you need.

ruby mail gem - set charset in deliver block

I'm using the mail gem to send E-Mail with UTF-8 content using this code
Mail.defaults do
...
end
Mail.deliver do
from "user#example.com"
to "otheruser#example.com"
subject "Mäbülö..."
body "Märchenbücher lösen Leseschwächen."
end
This works, but gives the warning
Non US-ASCII detected and no charset defined.
Defaulting to UTF-8, set your own if this is incorrect.
Now after much trying around, consulting mail gem's generated documentation as well as source code, I'm still unable to set the charset. There is a method charset= in Message.rb, but when I add a call to charset, like so:
Mail.deliver do
from "user#example.com"
to "otheruser#example.com"
charset "UTF-8"
subject "Mäbülö..."
body "Märchenbücher lösen Leseschwächen."
end
I get this ArgumentError:
/usr/local/lib/ruby/gems/1.9.1/gems/mail-2.4.4/lib/mail/message.rb:1423:in `charset': wrong number of arguments (1 for 0) (ArgumentError)
How can I set the charset within the deliver block?
mail.charset() returns the current charset, it does not allow to set one and does not take any argument.
To do so you need to use mail.charset = ...
It's actually possible inside the block with:
Mail.deliver do
from "user#example.com"
to "otheruser#example.com"
subject "Mäbülö..."
body "Märchenbücher lösen Leseschwächen."
charset = "UTF-8"
end
It's also possible without a block:
mail = Mail.new
mail.charset = 'UTF-8'
mail.content_transfer_encoding = '8bit'
mail.from = ...
mail.to = ...
mail.subject = ...
mail.text_part do
body ...
end
mail.html_part do
content_type 'text/html; charset=UTF-8'
body ...
end
mail.deliver!
you need to set the encoding also for the individual parts. Answer by maxdec shows it. Ensure that you do this for the text_part also.
This works for me.
mail = Mail.deliver do
charset='UTF-8'
content_transfer_encoding="8bit"
require 'pry';binding.pry
to 'xxx#xxx.yy'
from 'yyy#yyy.ss'
subject "Tet with äöüß"
text_part do
content_type "text/plain; charset=utf-8"
body <<-EOF
this is a test with äöüß
EOF
end
end
mail.deliver!
I use mail (2.7.1), neither charset nor content_transfer_encoding work for me.
charset='UTF-8'
content_transfer_encoding="8bit"
The following works for me!
content_type "text/plain; charset=utf-8"

How to pass variables into Mail body template?

I am trying to write simple mailer in Sinatra which sends email with params variables.
require 'sinatra'
require 'mail'
class App < Sinatra::Base
post '/test_mailer' do
company = params['Field6']
email = params['Field5']
puts "Company name: #{company}"
puts "Email: #{email}"
mail = Mail.new do
from 'me#mydomain.com'
to 'me#mydomain.com'
subject 'Here is the image you wanted'
text_part do
body "Company Name \n === \n #{company} \n \n Email \n === \n #{email}"
end
end
mail.deliver!
end
end
How to move email template to test_mailer.txt with company and email variables ?
I'm not sure I understand you - you want an separate email template file, right? I'm thinking you could use an erb, or haml template and then do something like the following:
text_part do
body erb(:test_mailer)
end
Your test_mailer.erb file would then contain your email template.
Here shows how something similar is done using pony.

Resources