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

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"

Related

how to send all files in a folder as email attachments in ruby

i am trying to send all files in a particular folder as email attachments using ruby .
How to read all files in a folder and send them as email attachment in ruby?
As of now i am able to send one file as email attachment using the below code.
require 'net/smtp'
filename = "/tmp/test.txt"
# Read a file and encode it into base64 format
filecontent = File.read(filename)
encodedcontent = [filecontent].pack("m") # base64
marker = "AUNIQUEMARKER"
body =<<EOF
This is a test email to send an attachement.
EOF
# Define the main headers.
part1 =<<EOF
From: Private Person <me#fromdomain.net>
To: A Test User <test#todmain.com>
Subject: Sending Attachement
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=#{marker}
--#{marker}
EOF
# Define the message action
part2 =<<EOF
Content-Type: text/plain
Content-Transfer-Encoding:8bit
#{body}
--#{marker}
EOF
# Define the attachment section
part3 =<<EOF
Content-Type: multipart/mixed; name=\"#{filename}\"
Content-Transfer-Encoding:base64
Content-Disposition: attachment; filename="#{filename}"
#{encodedcontent}
--#{marker}--
EOF
mailtext = part1 + part2 + part3
# Let's put our code in safe area
begin
Net::SMTP.start('localhost') do |smtp|
smtp.sendmail(mailtext, 'me#fromdomain.net',
['test#todmain.com'])
end
rescue Exception => e
print "Exception occured: " + e
end
The important part here is to loop over all files in a directory, and add each of them as an attachment.
I could modify your original code to do this, but there are much easier solutions.
For example, using a ruby gem such as mail (https://github.com/mikel/mail), you can simplify the code a great deal:
Mail.deliver do
from 'me#fromdomain.net'
to 'mytest#todmain.com'
subject 'Sending Attachement'
body 'This is a test email to send an attachement.'
Dir.glob('/tmp/files_to_attach/*.txt') do |file|
add_file file
end
end

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"

Can't read HTTP Request Header correctly with Ruby 1.9.3

I'm writing a small webserver. I want to read the HTTP Request. It works when there is no body involved. But when a body is sent then I can't read the content of the body in a satisfying manner.
I read the data coming from the client via TCPSocket. The TCPSocket::gets method reads until the data for the body is received. There is no delimiter or EOF send to signal for the end of the HTTP Request body. The HTTP/1.1 Specification - Section 4.4 lists five cases to get the message length. Point 1) works. Points 2) and 4) are not relevant for my application. Point 5) is not an option because I need to send an response.
I can read the value of the Content-Length field. But when I try to "persuade" the TCPSocket to read the last part of the HTTP Request via read(contentlength) or rcv(contentlength), I have no success. Reading line-by-line until the \r\n which separates Header and Body works, but after that I'm stuck - at least in the way I want to do it.
So my questions are:
Is there a possibility to do is like I intended in the code?
Are there better ways to achieve my goal of reading the HTTP Request correctly (which I really hope for)?
Here is runnable code. The parts that I want to work is in comments.
#!/usr/bin/ruby
require 'socket'
server = TCPServer.new 2000
loop do
Thread.start(server.accept) do |client|
hascontent = false
contentlength = 0
content = ""
request = ""
#This seems to work, but I'm not really happy with it, too much is happening in
#the loop
while(buf = client.readpartial(4096))
request = request + buf
split = request.split("\r\n")
puts request
puts request.dump
puts split.length
puts split.inspect
if(request.index("\r\n\r\n")>0)
break
end
end
#This part is commented out because it doesn't work
=begin
while(line = client.gets)
puts ":" + line
request = request + line
if(line.start_with?("Content-Length"))
hascontent = true
split = line.split(' ')
contentlength = split[1]
end
if(line == "\r\n" and !hascontent)
break
end
if(line == "\r\n" and hascontent)
puts "Trying to get content :P"
puts contentlength
puts content.length
puts client.inspect
#tried read, with and without parameter, rcv, also with and
#without param and their nonblocking couterparts
#where does my thought process go in the wrong direction
while(readin = client.readpartial(contentlength))
puts readin
content = content + readin
end
break
end
end
=end
puts request
client.close
end
So... I have just had this issue for the past 2 hours also, and so I did some digging into the Socket API. Turns out Socket extends BasicSocket which has a method recvmsg. When I tried calling it I got the following:
["GET / HTTP/1.1\r\nHost: localhost:12357\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9\r\n\r\n", #<Addrinfo: empty-sockaddr SOCK_STREAM>, 0]
I.E. My the complete HTTP request, the sender's address information and any other ruby flags raised.
You can use recvmsg to read the entire HTTP request:
raw_request = client.recvmsg()
request = /(?<METHOD>\w+) \/(?<RESOURCE>[^ ]*) HTTP\/1.\d\r\n(?<HEADERS>(.+\r\n)*)(?:\r\n)?(?<BODY>(.|\s)*)/i.match(raw_request)
p request["BODY"]
I have no idea how to do it without recvmsg but I am glad the functionality exists.

Ruby heredoc makes entire document a string literal in eclipse ide

I am trying to use heredoc to send an email in Ruby, but my entire code seems to become a string literal. I am using Eclipse with the Ruby plugin, and I can't figure out what the problem is. The code here is taken directly from a tutorial, so I don't understand why it isn't working. Can anyone shed some light on this?
Here is my code:
require 'net/smtp'
filename = "/tmp/test.txt"
# Read a file and encode it into base64 format
filecontent = File.read(filename)
encodedcontent = [filecontent].pack("m") # base64
marker = "AUNIQUEMARKER"
body = <<-EOF
This is a test email to send an attachement.
EOF
# Define the main headers.
part1 = <<-EOF
From: Private Person <me#fromdomain.net>
To: A Test User <test#todmain.com>
Subject: Sending Attachement
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=#{marker}
--#{marker}
EOF
# Define the message action
part2 = <<-EOF
Content-Type: text/plain
Content-Transfer-Encoding:8bit
#{body}
--#{marker}
EOF
# Define the attachment section
part3 = <<-EOF
Content-Type: multipart/mixed; name=\"#{filename}\"
Content-Transfer-Encoding:base64
Content-Disposition: attachment; filename="#{filename}"
#{encodedcontent}
--#{marker}--
EOF
mailtext = part1 + part2 + part3
# Let's put our code in safe area
begin
Net::SMTP.start('localhost') do |smtp|
smtp.sendmail(mailtext, 'me#fromdomain.net',
['test#todmain.com'])
end
rescue Exception => e
print "Exception occured: " + e
end
This is what it looks like in Eclipse:
I know this question is a little old, but I think that if you use tab before the closing "EOF" it should work. I think that when you add the "-" after the "<<" it looks for a tabulation before the end word, so an alternative could be deleting the "-".
I'm not super sure about it, I don't really use the heredocs because it has problems with the coding of the strings (like á é í...), but if someone is having this problem they should check that out.
I hope this helps someone.

Mail gem determine whether plaintext or html

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"

Resources