Unicode Characters in Attachment name - go

I'm trying to write a program that sends PDFs to my Kindle using SMTP.
When I send an attachment to the Kindle using a regular client (i.e. Outlook), I get the correct file name even if it's non-ASCII. However, when I send it using the code, the Unicode characters are not shown correctly. I tried sending the attachment to my personal email and there was nothing wrong in it, only Kindle does not recognize the characters.
This is my attachment headers:
Content-Disposition: attachment; filename="اÙضح٠ÙاÙÙسÙاÙ.pdf"
Content-Transfer-Encoding: base64
Content-Type: application/pdf; name="اÙضح٠ÙاÙÙسÙاÙ.pdf"
And this is my code:
package main
import (
"log"
"gopkg.in/gomail.v2"
)
func main() {
m := gomail.NewMessage()
m.SetHeader("To", "MY-KINDLE-EMAIL#kindle.com")
m.SetHeader("From", "MY-EMAIL#hotmail.com")
m.SetBody("text/plain", "")
path := "C:\\Users\\al111\\Downloads\\Telegram Desktop\\كيف تعمل الماركسية.pdf"
m.Attach(path)
d := gomail.NewDialer("smtp.live.com", 587, "MY-EMAIL#hotmail.com", "MY-PASSWORD")
err := d.DialAndSend(m)
if err != nil {
log.Fatal(err)
}
}

RFC 2822 style email headers do not allow unescaped Unicode characters. You need to use an ASCII-compatible encoding, such as RFC20471 or RFC 2231, eg:
Content-Disposition: attachment;
filename="=?UTF-8?Q?=D9=83=D9=8A=D9=81=20=D8=AA=D8=B9=D9=85=D9=84=20=D8=A7=D9=84=D9=85=D8=A7=D8=B1=D9=83=D8=B3=D9=8A=D8=A9=2E=70=64=66.pdf?="
Content-Type: application/pdf;
name="=?UTF-8?Q?=D9=83=D9=8A=D9=81=20=D8=AA=D8=B9=D9=85=D9=84=20=D8=A7=D9=84=D9=85=D8=A7=D8=B1=D9=83=D8=B3=D9=8A=D8=A9=2E=70=64=66.pdf?="
Content-Disposition: attachment;
filename*=UTF-8''%D9%83%D9%8A%D9%81%20%D8%AA%D8%B9%D9%85%D9%84%20%D8%A7%D9%84%D9%85%D8%A7%D8%B1%D9%83%D8%B3%D9%8A%D8%A9%2E%70%64%66.pdf
Content-Type: application/pdf;
name*=UTF-8''%D9%83%D9%8A%D9%81%20%D8%AA%D8%B9%D9%85%D9%84%20%D8%A7%D9%84%D9%85%D8%A7%D8%B1%D9%83%D8%B3%D9%8A%D8%A9%2E%70%64%66.pdf
1: yes, I'm aware that RFC 2047 technically does not allow encoded-words inside quoted-strings, such as for parameter values. But many servers do allow this.
See:
How to encode the filename parameter value of the Content-Disposition header in MIME message?
The mess that is attachment filenames
It is likely that Outlook is doing exactly this when it sends emails your emails. Which you can verify by looking at the raw data of the emails that it actually sends.
In Go, the m.Attach() function has an optional settings parameter that can be used to pass in additional parameters for an attachment, such as custom filenames and even custom headers, eg:
baseName := mime.QEncoding.Encode("utf-8", filepath.Base(path))
m.Attach(path, gomail.Rename(baseName))
baseName := url.PathEscape(filepath.Base(path))
m.Attach(path, gomail.SetHeader(map[string][]string{
"Content-Disposition": {"attachment; filename*=UTF-8''" + baseName},
}))

Related

SMTP return different value message

My purpose is to send filtered values using SMTP Gmail. But I got different values from the message. Here I got character count from the message that I need.
func main() {
links := []string{"http://grafana.url.io/api/datasources/proxy/2/query?db=telegraf&q=SELECT%20last(%22used_percent%22)%20AS%20used%20FROM%20%22disk%22%20WHERE%20(%22path%22%20%3D%20'%2F')%20AND%20time%20%3E%3D%20now()%20-%2030s%20GROUP%20BY%20time(10s)%2C%20host%20ORDER%20BY%20desc&epoch=ms"}
fmt.Println("CPU with the condition over than 80%: ")
if err := checkURLS(links); err != nil {
fmt.Println("err:", err)
return
}
checklink, _ := fmt.Println(links)
subject := "TESTING SOME subject"
message := []byte("Subject:" + subject + "\n\n" +
"CPU with the condition over than 80%: \n" +
fmt.Sprint(checklink))
fmt.Println(string(message))
fmt.Println("Email Sent!")
}
Result:
Subject: TESTING SOME subject
CPU with the condition over than 25%:
28
Expected:
Subject: TESTING SOME subject
CPU with the condition over than 25%:
[{"ha-external-01":45.804}]
any suggestion? My plain full code on this https://play.golang.org/p/_fwy38hQwVv
To cite the docs:
The msg parameter should be an RFC 822-style email with headers first, a blank line, and then the message body. The lines of msg should be CRLF terminated. The msg headers should usually include fields such as "From", "To", "Subject", and "Cc". Sending "Bcc" messages is accomplished by including an email address in the to parameter but not including it in the msg headers.
The SendMail function and the net/smtp package are low-level mechanisms and provide no support for DKIM signing, MIME attachments (see the mime/multipart package), or other mail functionality. Higher-level packages exist outside of the standard library.
This part itself already mentions CRLF (\r\n) used for line termination in the messages transmitted using SMTP (and you're using sole LFs).
You could read RFC 822 (or more up-to-date versions of this original spec) to learn more on how SMTP messages are formatted, but I admit it's hard to grok.
Still, if your messages are guaranteed to be all-ASCII and have sensible line lengths, you could get away with just using proper line termination. Otherwise I'd look at github.com/go-gomail/gomail: you can copy its approach to formatting SMTP messages or use its Message type which is able to properly serialize itself to any io.Writer (which can be a *bytes.Buffer, for example).

Adding newline to base64url string

I have a program that is sending a base64url encoded string, but I read in places that the '\' character isn't supported by base64. My purpose is to send emails with the Gmail API in Go. The body part consists of the following:
"Name: \n\nThis is the body of the email\n\nSincerely,\nSenderName"
When I send emails through the Gmail API, I need to pass it a base64url string.
I have the following function to handle that:
func encodeWeb64String(b []byte) string {
s := base64.URLEncoding.EncodeToString(b)
var i = len(s) - 1
for s[i] == '=' {
i--
}
return s[0 : i+1]
}
I have already added the header information to msg following this post, and have set the content type to text/html; charset=\"utf-8\". I then create the Gmail message using this:
gmsg := gmail.Message{
Raw: encodeWeb64String([]byte(msg)),
}
When the email comes through, it looks like this:
Name: This is the body of the email Sincerely, SenderName
But I want each '\n' to put in actual newline. Thanks for any help, I am new with the Gmail API for Go.
I finally fixed it. I had to change the content type from text/html to text/plain, and now the newlines show properly on the email client

Email message with no body part when attaching a file and sending via SMTP in Golang

I am trying to send an email message with both an email body and a file attachment (a CSV file) in Go (Golang).
I am following the mime standard of a multi-part message, however I am not very familiar with the structure of the messages following that standard. I am vaguely following a Python code snippet from a colleague as a guide which is using the Python library email (I think this is from the standard library) e.g. MIMEText and MIMEMultipart.
The email message body is not showing up when executing the following Go code:
What is wrong with it?
How can I send an email message with both that file attachment and an email message body?
This function should return a byte slice to be used as a parameter for the call to smtp.SendMail from the Go standard library. See the comments below explaining what's happening to the received email message (the THIS DOES NOT SHOW UP [...] and the THIS ALSO DOES NOT SHOW UP [...]).
func msgWithAttachment(subject, filePath string) ([]byte, error) {
// this is the separator used for the various parts of the MIME message structure
// identified as "boundary"
bPlaceholder := "our-custom-separator"
// the message setup of the common/standard initial part
mime := bytes.NewBuffer(nil)
mime.WriteString(fmt.Sprintf("Subject: %s\r\nMIME-Version: 1.0\r\n", subject))
mime.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=%s\r\n", bPlaceholder))
// THIS DOES NOT SHOW UP AS THE BODY OF THE EMAIL...
// mime.WriteString("\r\n")
// mime.WriteString(fmt.Sprintf("--%s\r\n", bPlaceholder))
// mime.WriteString("This should be the email message body (v1)...")
// mime.WriteString("\r\n")
// THIS ALSO DOES NOT SHOW UP AS THE BODY OF THE EMAIL...
// BUT IS NEEDED OTHERWISE THE EMAIL MESSAGE SEEMS TO CONTAIN AS ATTACHMENT THE EMAIL MESSAGE ITSELF
// (CONTAINING ITSELF THE REAL ATTACHMENT)
mime.WriteString(fmt.Sprintf("--%s\r\n", bPlaceholder))
mime.WriteString("Content-Type: text/plain; charset=utf-8\r\n")
mime.WriteString("This should be the email message body (v2)...")
// attach a file from the filesystem
_, msgFilename := filepath.Split(filePath)
mime.WriteString(fmt.Sprintf("\n--%s\r\n", bPlaceholder))
mime.WriteString("Content-Type: application/octet-stream\r\n")
mime.WriteString("Content-Description: " + msgFilename + "\r\n")
mime.WriteString("Content-Transfer-Encoding: base64\r\n")
mime.WriteString("Content-Disposition: attachment; filename=\"" + msgFilename + "\"\r\n\r\n")
fileContent, err := ioutil.ReadFile(filePath) // read and encode the content of the file
if err != nil {
return nil, err
}
b := make([]byte, base64.StdEncoding.EncodedLen(len(fileContent)))
base64.StdEncoding.Encode(b, fileContent)
mime.Write(b)
// footer of the email message
mime.WriteString("\r\n--" + bPlaceholder + "--\r\n\r\n")
return mime.Bytes(), nil
}
Coincidentally I had a similar issue the other day. I needed a blank line between the body content type and the beginning of the body itself. Below is the updated lines for this section of your code:
mime.WriteString("Content-Type: text/plain; charset=utf-8\r\n")
mime.WriteString("\r\nThis should be the email message body (v2)...")
For clarity, this newline (\r\n) doesn't have to be exactly here, it can be appended to the content-type line above. It just needs to see a blank line between the content-type and the beginning of the body.
I'm assuming the attachment is being attached without problems, correct? My assumption is this is because you have the double new-line at the end of the content-disposition line before the attachment data is added.
Reading the RFC spec helped me: https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
Note that the encapsulation boundary must occur at the beginning of a line, i.e., following a CRLF, and that that initial CRLF is considered to be part of the encapsulation boundary rather than part of the preceding part. The boundary must be followed immediately either by another CRLF and the header fields for the next part, or by two CRLFs, in which case there are no header fields for the next part (and it is therefore assumed to be of Content-Type text/plain).

Decoding the message body from the Gmail API using Go

I'm attempting to retrieve the full message body of messages using the Gmail API in Go. Currently when I do so, I only get the first three characters of the message body which are "<ht". I'm pretty sure my issue lies with the decoding of the message body but I can't seem to figure out what I'm doing wrong.
I've looked at examples in several other languages and have tried to translate them to Go with no success. The encoded message body is rather large so I'm fairly certain some data is getting lost somewhere.
Here is an (abridged) code snippet illustrating how I've been attempting to go about this:
req := svc.Users.Messages.List("me").Q("from:someone#somedomain.com,label:inbox")
r, _ := req.Do()
for _, m := range r.Messages {
msg, _ := svc.Users.Messages.Get("me", m.Id).Format("full").Do()
for _, part := range msg.Payload.Parts {
if part.MimeType == "text/html" {
data, _ := base64.StdEncoding.DecodeString(part.Body.Data)
html := string(data)
fmt.Println(html)
}
}
}
Need to use Base64 URL encoding (slightly different alphabet than standard Base64 encoding).
Using the same base64 package, you should use:
base64.URLEncoding.DecodeString instead of
base64.StdEncoding.DecodeString.
To get URL Base64 from standard Base64, replace:
+ to - (char 62, plus to dash)
/ to _ (char 63, slash to underscore)
= to * padding
from body string (source here: Base64 decoding of MIME email not working (GMail API) and here: How to send a message successfully using the new Gmail REST API?).

Receipt address won't show up in the email sent by golang smtp client

Here is the code snippet to send the email via a local postfix server:
from := r.FormValue("from")
to := strings.Split(r.FormValue("to"), ";")
body := r.FormValue("body")
mime := "MIME-version:1.0;\nContent-Type:text/html;charset=\"UTF-8\";\n\n"
subject := fmt.Sprintf("Subject: %s\n", r.FormValue("subject"))
msg := []byte(subject + mime + body)
err := smtp.SendMail("localhost:25", nil, from, to, msg)
The email was sent/received fine. However, it is missing the receipt address in the To field of received email. I also tried it on an exchange server. The receipt addresses are missing as well. Here is what it shows in the email source.
To: Undisclosed recipients:;
Any suggestions to fix it? thanks!
You're setting the values for the mail envelope, but you haven't put any headers in the email itself except for Subject:. You should also be using \r\n as a newline for email.
A minimal example might look like:
headers := make(map[string]string)
headers["Subject"] = "this is a test"
headers["From"] = "me#example.com"
headers["To"] = "you#example.com"
body := "hello,\nthis is a test"
var msg bytes.Buffer
for k, v := range headers {
msg.WriteString(k + ": " + v + "\r\n")
}
msg.WriteString("\r\n")
msg.WriteString(body)
Some other helpful stdlib packages:
net/textproto for MIME header handling
net/mail for address handling (though the package is really only for parsing email)
http://gopkg.in/gomail.v1 for a more complete solution (there are probably many others)

Resources