commit
This commit is contained in:
commit
331bbdc451
17
.gitattributes
vendored
Normal file
17
.gitattributes
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* -text
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
|
||||
# Standard to msysgit
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: ['https://paypal.me/hit']
|
32
.github/workflows/go.yml
vendored
Normal file
32
.github/workflows/go.yml
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.14
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.14
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# - name: Get dependencies
|
||||
# run: go get -v -t -d ./...
|
||||
|
||||
# - name: Build
|
||||
# run: go build -v .
|
||||
|
||||
- name: Test
|
||||
run: go test -v .
|
20
.github/workflows/golangci-lint.yml
vendored
Normal file
20
.github/workflows/golangci-lint.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
name: golangci-lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
with:
|
||||
version: v1.35.2
|
28
.gitignore
vendored
Normal file
28
.gitignore
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
# Eclipse Project files
|
||||
.project
|
||||
.settings
|
59
.golangci.yml
Normal file
59
.golangci.yml
Normal file
@ -0,0 +1,59 @@
|
||||
# This file contains all available configuration options
|
||||
# with their default values.
|
||||
|
||||
# options for analysis running
|
||||
run:
|
||||
concurrency: 4
|
||||
timeout: 10m
|
||||
issues-exit-code: 1
|
||||
tests: true
|
||||
|
||||
# output configuration options
|
||||
output:
|
||||
format: line-number
|
||||
|
||||
# all available settings of specific linters
|
||||
linters-settings:
|
||||
govet:
|
||||
# report about shadowed variables
|
||||
check-shadowing: true
|
||||
misspell:
|
||||
locale: US
|
||||
unused:
|
||||
# treat code as a program (not a library) and report unused exported identifiers; default is false.
|
||||
# XXX: if you enable this setting, unused will report a lot of false-positives in text editors:
|
||||
# if it's called for subdir of a project it can't find funcs usages. All text editor integrations
|
||||
# with golangci-lint call it on a directory with the changed file.
|
||||
check-exported: false
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- asciicheck
|
||||
- deadcode
|
||||
- dogsled
|
||||
- exportloopref
|
||||
- golint
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- megacheck
|
||||
- misspell
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unused
|
||||
- varcheck
|
||||
disable:
|
||||
- errcheck
|
||||
disable-all: false
|
||||
fast: false
|
||||
|
||||
issues:
|
||||
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
|
||||
max-issues-per-linter: 0
|
||||
|
||||
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
|
||||
max-same-issues: 0
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 Santiago De la Cruz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
268
README.md
Normal file
268
README.md
Normal file
@ -0,0 +1,268 @@
|
||||
# Go Simple Mail
|
||||
|
||||
The best way to send emails in Go with SMTP Keep Alive and Timeout for Connect and Send.
|
||||
|
||||
<a href="https://goreportcard.com/report/github.com/xhit/go-simple-mail/v2"><img src="https://goreportcard.com/badge/github.com/xhit/go-simple-mail" alt="Go Report Card"></a>
|
||||
<a href="https://pkg.go.dev/github.com/xhit/go-simple-mail/v2?tab=doc"><img src="https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white" alt="go.dev"></a>
|
||||
|
||||
|
||||
# IMPORTANT
|
||||
|
||||
Examples in this README are for v2.2.0 and above. Examples for older versions
|
||||
can be found [here](https://gist.github.com/xhit/54516917473420a8db1b6fff68a21c99).
|
||||
|
||||
Go 1.13+ is required.
|
||||
|
||||
Breaking change in 2.2.0: The signature of `SetBody` and `AddAlternative` used
|
||||
to accept strings ("text/html" and "text/plain") and not require on of the
|
||||
`contentType` constants (`TextHTML` or `TextPlain`). Upgrading, while not
|
||||
quite following semantic versioning, is quite simple:
|
||||
|
||||
```diff
|
||||
email := mail.NewMSG()
|
||||
- email.SetBody("text/html", htmlBody)
|
||||
- email.AddAlternative("text/plain", plainBody)
|
||||
+ email.SetBody(mail.TextHTML, htmlBody)
|
||||
+ email.AddAlternative(mail.TextPlain, plainBody)
|
||||
```
|
||||
|
||||
# Introduction
|
||||
|
||||
Go Simple Mail is a simple and efficient package to send emails. It is well tested and
|
||||
documented.
|
||||
|
||||
Go Simple Mail can only send emails using an SMTP server. But the API is flexible and it
|
||||
is easy to implement other methods for sending emails using a local Postfix, an API, etc.
|
||||
|
||||
This package contains (and is based on) two packages by **Joe Grasse**:
|
||||
|
||||
- https://github.com/joegrasse/mail (unmaintained since Jun 29, 2018), and
|
||||
- https://github.com/joegrasse/mime (unmaintained since Oct 1, 2015).
|
||||
|
||||
A lot of changes in Go Simple Mail were sent with not response.
|
||||
|
||||
## Features
|
||||
|
||||
Go Simple Mail supports:
|
||||
|
||||
- Multiple Attachments with path
|
||||
- Multiple Attachments in base64
|
||||
- Multiple Attachments from bytes (since v2.6.0)
|
||||
- Inline attachments from file, base64 and bytes (bytes since v2.6.0)
|
||||
- Multiple Recipients
|
||||
- Priority
|
||||
- Reply to
|
||||
- Set sender
|
||||
- Set from
|
||||
- Allow sending mail with different envelope from (since v2.7.0)
|
||||
- Embedded images
|
||||
- HTML and text templates
|
||||
- Automatic encoding of special characters
|
||||
- SSL/TLS and STARTTLS
|
||||
- Unencrypted connection (not recommended)
|
||||
- Sending multiple emails with the same SMTP connection (Keep Alive or Persistent Connection)
|
||||
- Timeout for connect to a SMTP Server
|
||||
- Timeout for send an email
|
||||
- Return Path
|
||||
- Alternative Email Body
|
||||
- CC and BCC
|
||||
- Add Custom Headers in Message
|
||||
- Send NOOP, RESET, QUIT and CLOSE to SMTP client
|
||||
- PLAIN, LOGIN and CRAM-MD5 Authentication (since v2.3.0)
|
||||
- Allow connect to SMTP without authentication (since v2.10.0)
|
||||
- Custom TLS Configuration (since v2.5.0)
|
||||
- Send a RFC822 formatted message (since v2.8.0)
|
||||
- Send from localhost (yes, Go standard SMTP package cannot do that because... WTF Google!)
|
||||
- Support text/calendar content type body (since v2.11.0)
|
||||
- Support add a List-Unsubscribe header (since v2.11.0)
|
||||
- Support to add a DKIM signarure (since v2.11.0)
|
||||
|
||||
## Documentation
|
||||
|
||||
https://pkg.go.dev/github.com/xhit/go-simple-mail/v2?tab=doc
|
||||
|
||||
## Download
|
||||
|
||||
This package uses go modules.
|
||||
|
||||
```console
|
||||
$ go get github.com/xhit/go-simple-mail/v2
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/xhit/go-simple-mail/v2"
|
||||
"github.com/toorop/go-dkim"
|
||||
)
|
||||
|
||||
const htmlBody = `<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>Hello Gophers!</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>This is the <b>Go gopher</b>.</p>
|
||||
<p><img src="cid:Gopher.png" alt="Go gopher" /></p>
|
||||
<p>Image created by Renee French</p>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
const privateKey = `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAwGrscUxi9zEa9oMOJbS0kLVHZXNIW+EBjY7KFWIZSxuGAils
|
||||
wBVl+s5mMRrR5VlkyLQNulAdNemg6OSeB0R+2+8/lkHiMrimqQckZ5ig8slBoZhZ
|
||||
wUoL/ZkeQa1bacbdww5TuWkiVPD9kooT/+TZW1P/ugd6oYjpOI56ZjsXzJw5pz7r
|
||||
DiwcIJJaaDIqvvc5C4iW94GZjwtmP5pxhvBZ5D6Uzmh7Okvi6z4QCKzdJQLdVmC0
|
||||
CMiFeh2FwqMkVpjZhNt3vtCo7Z51kwHVscel6vl51iQFq/laEzgzAWOUQ+ZEoQpL
|
||||
uTaUiYzzNyEdGEzZ2CjMMoO8RgtXnUo2qX2FDQIDAQABAoIBAHWKW3kycloSMyhX
|
||||
EnNSGeMz+bMtYwxNPMeebC/3xv+shoYXjAkiiTNWlfJ1MbbqjrhT1Pb1LYLbfqIF
|
||||
1csWum/bjHpbMLRPO++RH1nxUJA/BMqT6HA8rWpy+JqiLW9GPf2DaP2gDYrZ0+yK
|
||||
UIFG6MfzXgnju7OlkOItlvOQMY+Y501u/h6xnN2yTeRqXXJ1YlWFPRIeFdS6UOtL
|
||||
J2wSxRVdymHbGwf+D7zet7ngMPwFBsbEN/83KGLRjkt8+dMQeUeob+nslsQofCZx
|
||||
iokIAvByTugmqrB4JqhNkAlZhC0mqkRQh7zUFrxSj5UppMWlxLH+gPFZHKAsUJE5
|
||||
mqmylcECgYEA8I/f90cpF10uH4NPBCR4+eXq1PzYoD+NdXykN65bJTEDZVEy8rBO
|
||||
phXRNfw030sc3R0waQaZVhFuSgshhRuryfG9c1FP6tQhqi/jiEj9IfCW7zN9V/P2
|
||||
r16pGjLuCK4SyxUC8H58Q9I0X2CQqFamtkLXC6Ogy86rZfIc8GcvZ9UCgYEAzMQZ
|
||||
WAiLhRF2MEmMhKL+G3jm20r+dOzPYkfGxhIryluOXhuUhnxZWL8UZfiEqP5zH7Li
|
||||
NeJvLz4pOL45rLw44qiNu6sHN0JNaKYvwNch1wPT/3/eDNZKKePqbAG4iamhjLy5
|
||||
gjO1KgA5FBbcNN3R6fuJAg1e4QJCOuo55eW6vFkCgYEA7UBIV72D5joM8iFzvZcn
|
||||
BPdfqh2QnELxhaye3ReFZuG3AqaZg8akWqLryb1qe8q9tclC5GIQulTInBfsQDXx
|
||||
MGLNQL0x/1ylsw417kRl+qIoidMTTLocUgse5erS3haoDEg1tPBaKB1Zb7NyF8QV
|
||||
+W1kX2NKg5bZbdrh9asekt0CgYA6tUam7NxDrLv8IDo/lRPSAJn/6cKG95aGERo2
|
||||
k+MmQ5XP+Yxd+q0LOs24ZsZyRXHwdrNQy7khDGt5L2EN23Fb2wO3+NM6zrGu/WbX
|
||||
nVbAdQKFUL3zZEUjOYtuqBemsJH27e0qHXUls6ap0dwU9DxJH6sqgXbggGtIxPsQ
|
||||
pQsjEQKBgQC9gAqAj+ZtMXNG9exVPT8I15reox9kwxGuvJrRu/5eSi6jLR9z3x9P
|
||||
2FrgxQ+GCB2ypoOUcliXrKesdSbolUilA8XQn/M113Lg8oA3gJXbAKqbTR/EgfUU
|
||||
kvYaR/rTFnivF4SL/P4k/gABQoJuFUtSKdouELqefXB+e94g/G++Bg==
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
|
||||
func main() {
|
||||
server := mail.NewSMTPClient()
|
||||
|
||||
// SMTP Server
|
||||
server.Host = "smtp.example.com"
|
||||
server.Port = 587
|
||||
server.Username = "test@example.com"
|
||||
server.Password = "examplepass"
|
||||
server.Encryption = mail.EncryptionSTARTTLS
|
||||
|
||||
// Since v2.3.0 you can specified authentication type:
|
||||
// - PLAIN (default)
|
||||
// - LOGIN
|
||||
// - CRAM-MD5
|
||||
// - None
|
||||
// server.Authentication = mail.AuthPlain
|
||||
|
||||
// Variable to keep alive connection
|
||||
server.KeepAlive = false
|
||||
|
||||
// Timeout for connect to SMTP Server
|
||||
server.ConnectTimeout = 10 * time.Second
|
||||
|
||||
// Timeout for send the data and wait respond
|
||||
server.SendTimeout = 10 * time.Second
|
||||
|
||||
// Set TLSConfig to provide custom TLS configuration. For example,
|
||||
// to skip TLS verification (useful for testing):
|
||||
server.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
|
||||
// SMTP client
|
||||
smtpClient,err := server.Connect()
|
||||
|
||||
if err != nil{
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// New email simple html with inline and CC
|
||||
email := mail.NewMSG()
|
||||
email.SetFrom("From Example <nube@example.com>").
|
||||
AddTo("xhit@example.com").
|
||||
AddCc("otherto@example.com").
|
||||
SetSubject("New Go Email").
|
||||
SetListUnsubscribe("<mailto:unsubscribe@example.com?subject=https://example.com/unsubscribe>")
|
||||
|
||||
email.SetBody(mail.TextHTML, htmlBody)
|
||||
|
||||
// also you can add body from []byte with SetBodyData, example:
|
||||
// email.SetBodyData(mail.TextHTML, []byte(htmlBody))
|
||||
// or alternative part
|
||||
// email.AddAlternativeData(mail.TextHTML, []byte(htmlBody))
|
||||
|
||||
// add inline
|
||||
email.Attach(&mail.File{FilePath: "/path/to/image.png", Name:"Gopher.png", Inline: true})
|
||||
|
||||
// you can add dkim signature to the email.
|
||||
// to add dkim, you need a private key already created one.
|
||||
if privateKey != "" {
|
||||
options := dkim.NewSigOptions()
|
||||
options.PrivateKey = []byte(privateKey)
|
||||
options.Domain = "example.com"
|
||||
options.Selector = "default"
|
||||
options.SignatureExpireIn = 3600
|
||||
options.Headers = []string{"from", "date", "mime-version", "received", "received"}
|
||||
options.AddSignatureTimestamp = true
|
||||
options.Canonicalization = "relaxed/relaxed"
|
||||
|
||||
email.SetDkim(options)
|
||||
}
|
||||
|
||||
// always check error after send
|
||||
if email.Error != nil{
|
||||
log.Fatal(email.Error)
|
||||
}
|
||||
|
||||
// Call Send and pass the client
|
||||
err = email.Send(smtpClient)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
log.Println("Email Sent")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Send multiple emails in same connection
|
||||
|
||||
```go
|
||||
//Set your smtpClient struct to keep alive connection
|
||||
server.KeepAlive = true
|
||||
|
||||
for _, to := range []string{
|
||||
"to1@example1.com",
|
||||
"to3@example2.com",
|
||||
"to4@example3.com",
|
||||
} {
|
||||
// New email simple html with inline and CC
|
||||
email := mail.NewMSG()
|
||||
email.SetFrom("From Example <nube@example.com>").
|
||||
AddTo(to).
|
||||
SetSubject("New Go Email")
|
||||
|
||||
email.SetBody(mail.TextHTML, htmlBody)
|
||||
|
||||
// add inline
|
||||
email.Attach(&mail.File{FilePath: "/path/to/image.png", Name:"Gopher.png", Inline: true})
|
||||
|
||||
// always check error after send
|
||||
if email.Error != nil{
|
||||
log.Fatal(email.Error)
|
||||
}
|
||||
|
||||
// Call Send and pass the client
|
||||
err = email.Send(smtpClient)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
log.Println("Email Sent")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## More examples
|
||||
|
||||
See [example/example_test.go](example/example_test.go).
|
157
attach.go
Normal file
157
attach.go
Normal file
@ -0,0 +1,157 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// File represents the file that can be added to the email message.
|
||||
// You can add attachment from file in path, from base64 string or from []byte.
|
||||
// You can define if attachment is inline or not.
|
||||
// Only one, Data, B64Data or FilePath is supported. If multiple are set, then
|
||||
// the first in that order is used.
|
||||
type File struct {
|
||||
// FilePath is the path of the file to attach.
|
||||
FilePath string
|
||||
// Name is the name of file in attachment. Required for Data and B64Data. Optional for FilePath.
|
||||
Name string
|
||||
// MimeType of attachment. If empty then is obtained from Name (if not empty) or FilePath. If cannot obtained, application/octet-stream is set.
|
||||
MimeType string
|
||||
// B64Data is the base64 string to attach.
|
||||
B64Data string
|
||||
// Data is the []byte of file to attach.
|
||||
Data []byte
|
||||
// Inline defines if attachment is inline or not.
|
||||
Inline bool
|
||||
}
|
||||
|
||||
type attachType int
|
||||
|
||||
const (
|
||||
attachData attachType = iota
|
||||
attachB64
|
||||
attachFile
|
||||
)
|
||||
|
||||
// Attach allows you to add an attachment to the email message.
|
||||
// The attachment can be inlined
|
||||
func (email *Email) Attach(file *File) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
var name = file.Name
|
||||
var mimeType = file.MimeType
|
||||
|
||||
// if no alternative name was provided, get the filename
|
||||
if len(name) == 0 && len(file.FilePath) > 0 {
|
||||
_, name = filepath.Split(file.FilePath)
|
||||
}
|
||||
|
||||
// get the mimetype
|
||||
if mimeType == "" {
|
||||
mimeType = mime.TypeByExtension(filepath.Ext(name))
|
||||
if mimeType == "" {
|
||||
mimeType = "application/octet-stream"
|
||||
}
|
||||
}
|
||||
|
||||
attachTy, err := getAttachmentType(file)
|
||||
if err != nil {
|
||||
email.Error = errors.New("Mail Error: Failed to add attachment with following error: " + err.Error())
|
||||
return email
|
||||
}
|
||||
|
||||
file.Name = name
|
||||
file.MimeType = mimeType
|
||||
|
||||
switch attachTy {
|
||||
case attachData:
|
||||
email.attachData(file)
|
||||
case attachB64:
|
||||
email.Error = email.attachB64(file)
|
||||
case attachFile:
|
||||
email.Error = email.attachFile(file)
|
||||
}
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
func getAttachmentType(file *File) (attachType, error) {
|
||||
// 1- data
|
||||
// 2- base64
|
||||
// 3- file
|
||||
|
||||
// first check if Data
|
||||
if len(file.Data) > 0 {
|
||||
// data requires a name
|
||||
if len(file.Name) == 0 {
|
||||
return 0, errors.New("attach from bytes requires a name")
|
||||
}
|
||||
return attachData, nil
|
||||
}
|
||||
|
||||
// check if base64
|
||||
if len(file.B64Data) > 0 {
|
||||
// B64Data requires a name
|
||||
if len(file.Name) == 0 {
|
||||
return 0, errors.New("attach from base64 string requires a name")
|
||||
}
|
||||
return attachB64, nil
|
||||
}
|
||||
|
||||
// check if file
|
||||
if len(file.FilePath) > 0 {
|
||||
return attachFile, nil
|
||||
}
|
||||
|
||||
return 0, errors.New("empty attachment")
|
||||
}
|
||||
|
||||
// attachB64 does the low level attaching of the files but decoding base64
|
||||
func (email *Email) attachB64(file *File) error {
|
||||
|
||||
// decode the string
|
||||
dec, err := base64.StdEncoding.DecodeString(file.B64Data)
|
||||
if err != nil {
|
||||
return errors.New("Mail Error: Failed to decode base64 attachment with following error: " + err.Error())
|
||||
}
|
||||
|
||||
email.attachData(&File{
|
||||
Name: file.Name,
|
||||
MimeType: file.MimeType,
|
||||
Data: dec,
|
||||
Inline: file.Inline,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (email *Email) attachFile(file *File) error {
|
||||
data, err := ioutil.ReadFile(file.FilePath)
|
||||
if err != nil {
|
||||
return errors.New("Mail Error: Failed to add file with following error: " + err.Error())
|
||||
}
|
||||
|
||||
email.attachData(&File{
|
||||
Name: file.Name,
|
||||
MimeType: file.MimeType,
|
||||
Data: data,
|
||||
Inline: file.Inline,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// attachData does the low level attaching of the in-memory data
|
||||
func (email *Email) attachData(file *File) {
|
||||
// use inlines and attachments because is necessary to know if message has related parts and mixed parts
|
||||
if file.Inline {
|
||||
email.inlines = append(email.inlines, file)
|
||||
} else {
|
||||
email.attachments = append(email.attachments, file)
|
||||
}
|
||||
}
|
69
attach_old.go
Normal file
69
attach_old.go
Normal file
@ -0,0 +1,69 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// TODO: Remove this file before launch v3
|
||||
|
||||
// AddAttachment. DEPRECATED. Use Attach method. Allows you to add an attachment to the email message.
|
||||
// You can optionally provide a different name for the file.
|
||||
func (email *Email) AddAttachment(file string, name ...string) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
if len(name) > 1 {
|
||||
email.Error = errors.New("Mail Error: Attach can only have a file and an optional name")
|
||||
return email
|
||||
}
|
||||
|
||||
var nm string
|
||||
if len(name) == 1 {
|
||||
nm = name[0]
|
||||
}
|
||||
return email.Attach(&File{Name: nm, FilePath: file})
|
||||
}
|
||||
|
||||
// AddAttachmentData. DEPRECATED. Use Attach method. Allows you to add an in-memory attachment to the email message.
|
||||
func (email *Email) AddAttachmentData(data []byte, filename, mimeType string) *Email {
|
||||
return email.Attach(&File{Data: data, Name: filename, MimeType: mimeType})
|
||||
}
|
||||
|
||||
// AddAttachmentBase64. DEPRECATED. Use Attach method. Allows you to add an attachment in base64 to the email message.
|
||||
// You need provide a name for the file.
|
||||
func (email *Email) AddAttachmentBase64(b64File, name string) *Email {
|
||||
return email.Attach(&File{B64Data: b64File, Name: name})
|
||||
}
|
||||
|
||||
// AddInline. DEPRECATED. Use Attach method. Allows you to add an inline attachment to the email message.
|
||||
// You can optionally provide a different name for the file.
|
||||
func (email *Email) AddInline(file string, name ...string) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
if len(name) > 1 {
|
||||
email.Error = errors.New("Mail Error: Inline can only have a file and an optional name")
|
||||
return email
|
||||
}
|
||||
|
||||
var nm string
|
||||
if len(name) == 1 {
|
||||
nm = name[0]
|
||||
}
|
||||
|
||||
return email.Attach(&File{Name: nm, FilePath: file, Inline: true})
|
||||
}
|
||||
|
||||
// AddInlineData. DEPRECATED. Use Attach method. Allows you to add an inline in-memory attachment to the email message.
|
||||
func (email *Email) AddInlineData(data []byte, filename, mimeType string) *Email {
|
||||
return email.Attach(&File{Data: data, Name: filename, MimeType: mimeType, Inline: true})
|
||||
}
|
||||
|
||||
// AddInlineBase64. DEPRECATED. Use Attach method. Allows you to add an inline in-memory base64 encoded attachment to the email message.
|
||||
// You need provide a name for the file. If mimeType is an empty string, attachment mime type will be deduced
|
||||
// from the file name extension and defaults to application/octet-stream.
|
||||
func (email *Email) AddInlineBase64(b64File, name, mimeType string) *Email {
|
||||
return email.Attach(&File{B64Data: b64File, Name: name, MimeType: mimeType, Inline: true})
|
||||
}
|
122
attach_test.go
Normal file
122
attach_test.go
Normal file
@ -0,0 +1,122 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func checkError(t *testing.T, err error) {
|
||||
if err != nil {
|
||||
t.Errorf("got error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func checkByteSlice(t *testing.T, got, want []byte) {
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachments(t *testing.T) {
|
||||
want := []byte("foo")
|
||||
t.Run("Inline File", func(t *testing.T) {
|
||||
msg := NewMSG()
|
||||
msg.Attach(&File{FilePath: "testdata/foo.txt", Name: "foo", Inline: true})
|
||||
checkError(t, msg.Error)
|
||||
got := msg.inlines[0].Data
|
||||
checkByteSlice(t, got, want)
|
||||
})
|
||||
t.Run("Inline Base64", func(t *testing.T) {
|
||||
msg := NewMSG()
|
||||
msg.Attach(&File{B64Data: "Zm9v", Name: "foo", Inline: true})
|
||||
checkError(t, msg.Error)
|
||||
got := msg.inlines[0].Data
|
||||
checkByteSlice(t, got, want)
|
||||
})
|
||||
t.Run("Inline Data", func(t *testing.T) {
|
||||
msg := NewMSG()
|
||||
msg.Attach(&File{Data: []byte("foo"), Name: "foo", Inline: true})
|
||||
checkError(t, msg.Error)
|
||||
got := msg.inlines[0].Data
|
||||
checkByteSlice(t, got, want)
|
||||
})
|
||||
t.Run("Attachment File", func(t *testing.T) {
|
||||
msg := NewMSG()
|
||||
msg.Attach(&File{FilePath: "testdata/foo.txt", Name: "foo"})
|
||||
checkError(t, msg.Error)
|
||||
got := msg.attachments[0].Data
|
||||
checkByteSlice(t, got, want)
|
||||
})
|
||||
t.Run("Attachment Base64", func(t *testing.T) {
|
||||
msg := NewMSG()
|
||||
msg.Attach(&File{B64Data: "Zm9v", Name: "foo"})
|
||||
checkError(t, msg.Error)
|
||||
got := msg.attachments[0].Data
|
||||
checkByteSlice(t, got, want)
|
||||
})
|
||||
t.Run("Attachment Data", func(t *testing.T) {
|
||||
msg := NewMSG()
|
||||
msg.Attach(&File{Data: []byte("foo"), Name: "foo"})
|
||||
checkError(t, msg.Error)
|
||||
got := msg.attachments[0].Data
|
||||
checkByteSlice(t, got, want)
|
||||
})
|
||||
|
||||
// DEPRECATED. TODO: Remove before launch v3
|
||||
t.Run("Inline File Deprecated", func(t *testing.T) {
|
||||
msg := NewMSG()
|
||||
msg.AddInline("testdata/foo.txt", "foo")
|
||||
checkError(t, msg.Error)
|
||||
got := msg.inlines[0].Data
|
||||
checkByteSlice(t, got, want)
|
||||
})
|
||||
t.Run("Inline Base64 Deprecated", func(t *testing.T) {
|
||||
msg := NewMSG()
|
||||
msg.AddInlineBase64("Zm9v", "foo", "")
|
||||
checkError(t, msg.Error)
|
||||
got := msg.inlines[0].Data
|
||||
checkByteSlice(t, got, want)
|
||||
})
|
||||
t.Run("Inline Data Deprecated", func(t *testing.T) {
|
||||
msg := NewMSG()
|
||||
msg.AddInlineData([]byte("foo"), "foo", "")
|
||||
checkError(t, msg.Error)
|
||||
got := msg.inlines[0].Data
|
||||
checkByteSlice(t, got, want)
|
||||
})
|
||||
t.Run("Attachment File Deprecated", func(t *testing.T) {
|
||||
msg := NewMSG()
|
||||
msg.AddAttachment("testdata/foo.txt", "foo")
|
||||
checkError(t, msg.Error)
|
||||
got := msg.attachments[0].Data
|
||||
checkByteSlice(t, got, want)
|
||||
})
|
||||
t.Run("Attachment Base64 Deprecated", func(t *testing.T) {
|
||||
msg := NewMSG()
|
||||
msg.AddAttachmentBase64("Zm9v", "foo")
|
||||
checkError(t, msg.Error)
|
||||
got := msg.attachments[0].Data
|
||||
checkByteSlice(t, got, want)
|
||||
})
|
||||
t.Run("Attachment Data Deprecated", func(t *testing.T) {
|
||||
msg := NewMSG()
|
||||
msg.AddAttachmentData([]byte("foo"), "foo", "")
|
||||
checkError(t, msg.Error)
|
||||
got := msg.attachments[0].Data
|
||||
checkByteSlice(t, got, want)
|
||||
})
|
||||
t.Run("Inline File not name Deprecated", func(t *testing.T) {
|
||||
msg := NewMSG()
|
||||
msg.AddInline("testdata/foo.txt")
|
||||
checkError(t, msg.Error)
|
||||
got := msg.inlines[0].Data
|
||||
checkByteSlice(t, got, want)
|
||||
})
|
||||
t.Run("Attachment File not name Deprecated", func(t *testing.T) {
|
||||
msg := NewMSG()
|
||||
msg.AddAttachment("testdata/foo.txt")
|
||||
checkError(t, msg.Error)
|
||||
got := msg.attachments[0].Data
|
||||
checkByteSlice(t, got, want)
|
||||
})
|
||||
}
|
145
auth.go
Normal file
145
auth.go
Normal file
@ -0,0 +1,145 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in https://raw.githubusercontent.com/golang/go/master/LICENSE
|
||||
// auth.go file is a modification of smtp golang package what is frozen and is not accepting new features.
|
||||
|
||||
package mail
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// auth is implemented by an SMTP authentication mechanism.
|
||||
type auth interface {
|
||||
// start begins an authentication with a server.
|
||||
// It returns the name of the authentication protocol
|
||||
// and optionally data to include in the initial AUTH message
|
||||
// sent to the server. It can return proto == "" to indicate
|
||||
// that the authentication should be skipped.
|
||||
// If it returns a non-nil error, the SMTP client aborts
|
||||
// the authentication attempt and closes the connection.
|
||||
start(server *serverInfo) (proto string, toServer []byte, err error)
|
||||
|
||||
// next continues the authentication. The server has just sent
|
||||
// the fromServer data. If more is true, the server expects a
|
||||
// response, which next should return as toServer; otherwise
|
||||
// next should return toServer == nil.
|
||||
// If next returns a non-nil error, the SMTP client aborts
|
||||
// the authentication attempt and closes the connection.
|
||||
next(fromServer []byte, more bool) (toServer []byte, err error)
|
||||
}
|
||||
|
||||
// serverInfo records information about an SMTP server.
|
||||
type serverInfo struct {
|
||||
name string // SMTP server name
|
||||
tls bool // using TLS, with valid certificate for Name
|
||||
auth []string // advertised authentication mechanisms
|
||||
}
|
||||
|
||||
type plainAuth struct {
|
||||
identity, username, password string
|
||||
host string
|
||||
}
|
||||
|
||||
// plainAuthfn returns an auth that implements the PLAIN authentication
|
||||
// mechanism as defined in RFC 4616. The returned Auth uses the given
|
||||
// username and password to authenticate to host and act as identity.
|
||||
// Usually identity should be the empty string, to act as username.
|
||||
//
|
||||
// plainAuthfn will only send the credentials if the connection is using TLS
|
||||
// or is connected to localhost. Otherwise authentication will fail with an
|
||||
// error, without sending the credentials.
|
||||
func plainAuthfn(identity, username, password, host string) auth {
|
||||
return &plainAuth{identity, username, password, host}
|
||||
}
|
||||
|
||||
func (a *plainAuth) start(server *serverInfo) (string, []byte, error) {
|
||||
// Must have TLS, or else localhost server. Unencrypted connection is permitted here too but is not recommended
|
||||
// Note: If TLS is not true, then we can't trust ANYTHING in serverInfo.
|
||||
// In particular, it doesn't matter if the server advertises PLAIN auth.
|
||||
// That might just be the attacker saying
|
||||
// "it's ok, you can trust me with your password."
|
||||
if server.name != a.host {
|
||||
return "", nil, errors.New("wrong host name")
|
||||
}
|
||||
resp := []byte(a.identity + "\x00" + a.username + "\x00" + a.password)
|
||||
return "PLAIN", resp, nil
|
||||
}
|
||||
|
||||
func (a *plainAuth) next(fromServer []byte, more bool) ([]byte, error) {
|
||||
if more {
|
||||
// We've already sent everything.
|
||||
return nil, errors.New("unexpected server challenge")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
/*
|
||||
loginAuthfn authentication implements LOGIN Authentication, is the same PLAIN
|
||||
but username and password are sent in different commands
|
||||
*/
|
||||
|
||||
type loginAuth struct {
|
||||
identity, username, password string
|
||||
host string
|
||||
}
|
||||
|
||||
func loginAuthfn(identity, username, password, host string) auth {
|
||||
return &loginAuth{identity, username, password, host}
|
||||
}
|
||||
|
||||
func (a *loginAuth) start(server *serverInfo) (string, []byte, error) {
|
||||
if server.name != a.host {
|
||||
return "", nil, errors.New("wrong host name")
|
||||
}
|
||||
resp := []byte(a.username)
|
||||
return "LOGIN", resp, nil
|
||||
}
|
||||
|
||||
func (a *loginAuth) next(fromServer []byte, more bool) ([]byte, error) {
|
||||
if more {
|
||||
if strings.Contains(string(fromServer), "Username") {
|
||||
resp := []byte(a.username)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
if strings.Contains(string(fromServer), "Password") {
|
||||
resp := []byte(a.password)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// We've already sent everything.
|
||||
return nil, errors.New("unexpected server challenge")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type cramMD5Auth struct {
|
||||
username, secret string
|
||||
}
|
||||
|
||||
// cramMD5Authfn returns an Auth that implements the CRAM-MD5 authentication
|
||||
// mechanism as defined in RFC 2195.
|
||||
// The returned Auth uses the given username and secret to authenticate
|
||||
// to the server using the challenge-response mechanism.
|
||||
func cramMD5Authfn(username, secret string) auth {
|
||||
return &cramMD5Auth{username, secret}
|
||||
}
|
||||
|
||||
func (a *cramMD5Auth) start(server *serverInfo) (string, []byte, error) {
|
||||
return "CRAM-MD5", nil, nil
|
||||
}
|
||||
|
||||
func (a *cramMD5Auth) next(fromServer []byte, more bool) ([]byte, error) {
|
||||
if more {
|
||||
d := hmac.New(md5.New, []byte(a.secret))
|
||||
d.Write(fromServer)
|
||||
s := make([]byte, 0, d.Size())
|
||||
return []byte(fmt.Sprintf("%s %x", a.username, d.Sum(s))), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
971
email.go
Normal file
971
email.go
Normal file
@ -0,0 +1,971 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/mail"
|
||||
"net/textproto"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/m4x1202/go-smime/smime"
|
||||
"github.com/toorop/go-dkim"
|
||||
)
|
||||
|
||||
// Email represents an email message.
|
||||
type Email struct {
|
||||
from string
|
||||
sender string
|
||||
replyTo string
|
||||
returnPath string
|
||||
recipients []string
|
||||
headers textproto.MIMEHeader
|
||||
parts []part
|
||||
attachments []*File
|
||||
inlines []*File
|
||||
Charset string
|
||||
Encoding encoding
|
||||
Error error
|
||||
SMTPServer *smtpClient
|
||||
DkimMsg string
|
||||
SmimeCertificate *tls.Certificate
|
||||
}
|
||||
|
||||
/*
|
||||
SMTPServer represents a SMTP Server
|
||||
If authentication is CRAM-MD5 then the Password is the Secret
|
||||
*/
|
||||
type SMTPServer struct {
|
||||
Authentication AuthType
|
||||
Encryption Encryption
|
||||
Username string
|
||||
Password string
|
||||
Helo string
|
||||
ConnectTimeout time.Duration
|
||||
SendTimeout time.Duration
|
||||
Host string
|
||||
Port int
|
||||
KeepAlive bool
|
||||
TLSConfig *tls.Config
|
||||
}
|
||||
|
||||
// SMTPClient represents a SMTP Client for send email
|
||||
type SMTPClient struct {
|
||||
Client *smtpClient
|
||||
KeepAlive bool
|
||||
SendTimeout time.Duration
|
||||
}
|
||||
|
||||
// part represents the different content parts of an email body.
|
||||
type part struct {
|
||||
contentType string
|
||||
body *bytes.Buffer
|
||||
}
|
||||
|
||||
// Encryption type to enum encryption types (None, SSL/TLS, STARTTLS)
|
||||
type Encryption int
|
||||
|
||||
// TODO: Remove EncryptionSSL and EncryptionTLS before launch v3
|
||||
|
||||
const (
|
||||
// EncryptionNone uses no encryption when sending email
|
||||
EncryptionNone Encryption = iota
|
||||
// EncryptionSSL: DEPRECATED. Use EncryptionSSLTLS. Sets encryption type to SSL/TLS when sending email
|
||||
EncryptionSSL
|
||||
// EncryptionTLS: DEPRECATED. Use EncryptionSTARTTLS. sets encryption type to STARTTLS when sending email
|
||||
EncryptionTLS
|
||||
// EncryptionSSLTLS sets encryption type to SSL/TLS when sending email
|
||||
EncryptionSSLTLS
|
||||
// EncryptionSTARTTLS sets encryption type to STARTTLS when sending email
|
||||
EncryptionSTARTTLS
|
||||
)
|
||||
|
||||
// TODO: Remove last two indexes
|
||||
var encryptionTypes = [...]string{"None", "SSL/TLS", "STARTTLS", "SSL/TLS", "STARTTLS"}
|
||||
|
||||
func (encryption Encryption) String() string {
|
||||
return encryptionTypes[encryption]
|
||||
}
|
||||
|
||||
type encoding int
|
||||
|
||||
const (
|
||||
// EncodingNone turns off encoding on the message body
|
||||
EncodingNone encoding = iota
|
||||
// EncodingBase64 sets the message body encoding to base64
|
||||
EncodingBase64
|
||||
// EncodingQuotedPrintable sets the message body encoding to quoted-printable
|
||||
EncodingQuotedPrintable
|
||||
)
|
||||
|
||||
var encodingTypes = [...]string{"binary", "base64", "quoted-printable"}
|
||||
|
||||
func (encoding encoding) string() string {
|
||||
return encodingTypes[encoding]
|
||||
}
|
||||
|
||||
type ContentType int
|
||||
|
||||
const (
|
||||
// TextPlain sets body type to text/plain in message body
|
||||
TextPlain ContentType = iota
|
||||
// TextHTML sets body type to text/html in message body
|
||||
TextHTML
|
||||
// TextCalendar sets body type to text/calendar in message body
|
||||
TextCalendar
|
||||
)
|
||||
|
||||
var contentTypes = [...]string{"text/plain", "text/html", "text/calendar"}
|
||||
|
||||
func (contentType ContentType) string() string {
|
||||
return contentTypes[contentType]
|
||||
}
|
||||
|
||||
type AuthType int
|
||||
|
||||
const (
|
||||
// AuthPlain implements the PLAIN authentication
|
||||
AuthPlain AuthType = iota
|
||||
// AuthLogin implements the LOGIN authentication
|
||||
AuthLogin
|
||||
// AuthCRAMMD5 implements the CRAM-MD5 authentication
|
||||
AuthCRAMMD5
|
||||
// AuthNone for SMTP servers without authentication
|
||||
AuthNone
|
||||
)
|
||||
|
||||
// NewMSG creates a new email. It uses UTF-8 by default. All charsets: http://webcheatsheet.com/HTML/character_sets_list.php
|
||||
func NewMSG() *Email {
|
||||
email := &Email{
|
||||
headers: make(textproto.MIMEHeader),
|
||||
Charset: "UTF-8",
|
||||
Encoding: EncodingQuotedPrintable,
|
||||
}
|
||||
|
||||
email.AddHeader("MIME-Version", "1.0")
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// NewSMTPClient returns the client for send email
|
||||
func NewSMTPClient() *SMTPServer {
|
||||
server := &SMTPServer{
|
||||
Authentication: AuthPlain,
|
||||
Encryption: EncryptionNone,
|
||||
ConnectTimeout: 10 * time.Second,
|
||||
SendTimeout: 10 * time.Second,
|
||||
Helo: "localhost",
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
// GetEncryptionType returns the encryption type used to connect to SMTP server
|
||||
func (server *SMTPServer) GetEncryptionType() Encryption {
|
||||
return server.Encryption
|
||||
}
|
||||
|
||||
// GetError returns the first email error encountered
|
||||
func (email *Email) GetError() error {
|
||||
return email.Error
|
||||
}
|
||||
|
||||
// SetFrom sets the From address.
|
||||
func (email *Email) SetFrom(address string) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
email.AddAddresses("From", address)
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// SetSender sets the Sender address.
|
||||
func (email *Email) SetSender(address string) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
email.AddAddresses("Sender", address)
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// SetReplyTo sets the Reply-To address.
|
||||
func (email *Email) SetReplyTo(address string) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
email.AddAddresses("Reply-To", address)
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// SetReturnPath sets the Return-Path address. This is most often used
|
||||
// to send bounced emails to a different email address.
|
||||
func (email *Email) SetReturnPath(address string) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
email.AddAddresses("Return-Path", address)
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// AddTo adds a To address. You can provide multiple
|
||||
// addresses at the same time.
|
||||
func (email *Email) AddTo(addresses ...string) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
email.AddAddresses("To", addresses...)
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// AddCc adds a Cc address. You can provide multiple
|
||||
// addresses at the same time.
|
||||
func (email *Email) AddCc(addresses ...string) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
email.AddAddresses("Cc", addresses...)
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// AddBcc adds a Bcc address. You can provide multiple
|
||||
// addresses at the same time.
|
||||
func (email *Email) AddBcc(addresses ...string) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
email.AddAddresses("Bcc", addresses...)
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// AddAddresses allows you to add addresses to the specified address header.
|
||||
func (email *Email) AddAddresses(header string, addresses ...string) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
found := false
|
||||
|
||||
// check for a valid address header
|
||||
for _, h := range []string{"To", "Cc", "Bcc", "From", "Sender", "Reply-To", "Return-Path"} {
|
||||
if header == h {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
email.Error = errors.New("Mail Error: Invalid address header; Header: [" + header + "]")
|
||||
return email
|
||||
}
|
||||
|
||||
// check to see if the addresses are valid
|
||||
for i := range addresses {
|
||||
var address = new(mail.Address)
|
||||
var err error
|
||||
|
||||
// ignore parse the address if empty
|
||||
if len(addresses[i]) > 0 {
|
||||
address, err = mail.ParseAddress(addresses[i])
|
||||
if err != nil {
|
||||
email.Error = errors.New("Mail Error: " + err.Error() + "; Header: [" + header + "] Address: [" + addresses[i] + "]")
|
||||
return email
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
// check for more than one address
|
||||
switch {
|
||||
case header == "Sender" && len(email.sender) > 0:
|
||||
fallthrough
|
||||
case header == "Reply-To" && len(email.replyTo) > 0:
|
||||
fallthrough
|
||||
case header == "Return-Path" && len(email.returnPath) > 0:
|
||||
email.Error = errors.New("Mail Error: There can only be one \"" + header + "\" address; Header: [" + header + "] Address: [" + addresses[i] + "]")
|
||||
return email
|
||||
default:
|
||||
// other address types can have more than one address
|
||||
}
|
||||
|
||||
// save the address
|
||||
switch header {
|
||||
case "From":
|
||||
// delete the current "From" to set the new
|
||||
// when "From" need to be changed in the message
|
||||
if len(email.from) > 0 && header == "From" {
|
||||
email.headers.Del("From")
|
||||
}
|
||||
email.from = address.Address
|
||||
case "Sender":
|
||||
email.sender = address.Address
|
||||
case "Reply-To":
|
||||
email.replyTo = address.Address
|
||||
case "Return-Path":
|
||||
email.returnPath = address.Address
|
||||
default:
|
||||
// check that the address was added to the recipients list
|
||||
email.recipients, err = addAddress(email.recipients, address.Address)
|
||||
if err != nil {
|
||||
email.Error = errors.New("Mail Error: " + err.Error() + "; Header: [" + header + "] Address: [" + addresses[i] + "]")
|
||||
return email
|
||||
}
|
||||
}
|
||||
|
||||
// make sure the from and sender addresses are different
|
||||
if email.from != "" && email.sender != "" && email.from == email.sender {
|
||||
email.sender = ""
|
||||
email.headers.Del("Sender")
|
||||
email.Error = errors.New("Mail Error: From and Sender should not be set to the same address")
|
||||
return email
|
||||
}
|
||||
|
||||
// add all addresses to the headers except for Bcc and Return-Path
|
||||
if header != "Bcc" && header != "Return-Path" {
|
||||
// add the address to the headers
|
||||
email.headers.Add(header, address.String())
|
||||
}
|
||||
}
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// addAddress adds an address to the address list if it hasn't already been added
|
||||
func addAddress(addressList []string, address string) ([]string, error) {
|
||||
// loop through the address list to check for dups
|
||||
for _, a := range addressList {
|
||||
if address == a {
|
||||
return addressList, errors.New("Mail Error: Address: [" + address + "] has already been added")
|
||||
}
|
||||
}
|
||||
|
||||
return append(addressList, address), nil
|
||||
}
|
||||
|
||||
type priority int
|
||||
|
||||
const (
|
||||
// PriorityLow sets the email priority to Low
|
||||
PriorityLow priority = iota
|
||||
// PriorityHigh sets the email priority to High
|
||||
PriorityHigh
|
||||
)
|
||||
|
||||
// SetPriority sets the email message priority. Use with
|
||||
// either "High" or "Low".
|
||||
func (email *Email) SetPriority(priority priority) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
switch priority {
|
||||
case PriorityLow:
|
||||
email.AddHeaders(textproto.MIMEHeader{
|
||||
"X-Priority": {"5 (Lowest)"},
|
||||
"X-MSMail-Priority": {"Low"},
|
||||
"Importance": {"Low"},
|
||||
})
|
||||
case PriorityHigh:
|
||||
email.AddHeaders(textproto.MIMEHeader{
|
||||
"X-Priority": {"1 (Highest)"},
|
||||
"X-MSMail-Priority": {"High"},
|
||||
"Importance": {"High"},
|
||||
})
|
||||
default:
|
||||
}
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// SetDate sets the date header to the provided date/time.
|
||||
// The format of the string should be YYYY-MM-DD HH:MM:SS Time Zone.
|
||||
//
|
||||
// Example: SetDate("2015-04-28 10:32:00 CDT")
|
||||
func (email *Email) SetDate(dateTime string) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
const dateFormat = "2006-01-02 15:04:05 MST"
|
||||
|
||||
// Try to parse the provided date/time
|
||||
dt, err := time.Parse(dateFormat, dateTime)
|
||||
if err != nil {
|
||||
email.Error = errors.New("Mail Error: Setting date failed with: " + err.Error())
|
||||
return email
|
||||
}
|
||||
|
||||
email.headers.Set("Date", dt.Format(time.RFC1123Z))
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// SetSubject sets the subject of the email message.
|
||||
func (email *Email) SetSubject(subject string) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
email.AddHeader("Subject", subject)
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// SetListUnsubscribe sets the Unsubscribe address.
|
||||
func (email *Email) SetListUnsubscribe(address string) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
email.AddHeader("List-Unsubscribe", address)
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// SetDkim adds DomainKey signature to the email message (header+body)
|
||||
func (email *Email) SetDkim(options dkim.SigOptions) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
msg := []byte(email.GetMessage())
|
||||
err := dkim.Sign(&msg, options)
|
||||
|
||||
if err != nil {
|
||||
email.Error = errors.New("Mail Error: cannot dkim sign message due: %s" + err.Error())
|
||||
return email
|
||||
}
|
||||
|
||||
email.DkimMsg = string(msg)
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// SetBody sets the body of the email message.
|
||||
func (email *Email) SetBody(contentType ContentType, body string) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
email.parts = []part{
|
||||
{
|
||||
contentType: contentType.string(),
|
||||
body: bytes.NewBufferString(body),
|
||||
},
|
||||
}
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// SetBodyData sets the body of the email message from []byte
|
||||
func (email *Email) SetBodyData(contentType ContentType, body []byte) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
email.parts = []part{
|
||||
{
|
||||
contentType: contentType.string(),
|
||||
body: bytes.NewBuffer(body),
|
||||
},
|
||||
}
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// AddHeader adds the given "header" with the passed "value".
|
||||
func (email *Email) AddHeader(header string, values ...string) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
// check that there is actually a value
|
||||
if len(values) < 1 {
|
||||
email.Error = errors.New("Mail Error: no value provided; Header: [" + header + "]")
|
||||
return email
|
||||
}
|
||||
|
||||
if header != "MIME-Version" {
|
||||
// Set header to correct canonical Mime
|
||||
header = textproto.CanonicalMIMEHeaderKey(header)
|
||||
}
|
||||
|
||||
switch header {
|
||||
case "Sender":
|
||||
fallthrough
|
||||
case "From":
|
||||
fallthrough
|
||||
case "To":
|
||||
fallthrough
|
||||
case "Bcc":
|
||||
fallthrough
|
||||
case "Cc":
|
||||
fallthrough
|
||||
case "Reply-To":
|
||||
fallthrough
|
||||
case "Return-Path":
|
||||
email.AddAddresses(header, values...)
|
||||
case "Date":
|
||||
if len(values) > 1 {
|
||||
email.Error = errors.New("Mail Error: To many dates provided")
|
||||
return email
|
||||
}
|
||||
email.SetDate(values[0])
|
||||
case "List-Unsubscribe":
|
||||
fallthrough
|
||||
default:
|
||||
email.headers[header] = values
|
||||
}
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// AddHeaders is used to add multiple headers at once
|
||||
func (email *Email) AddHeaders(headers textproto.MIMEHeader) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
for header, values := range headers {
|
||||
email.AddHeader(header, values...)
|
||||
}
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// AddAlternative allows you to add alternative parts to the body
|
||||
// of the email message. This is most commonly used to add an
|
||||
// html version in addition to a plain text version that was
|
||||
// already added with SetBody.
|
||||
func (email *Email) AddAlternative(contentType ContentType, body string) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
email.parts = append(email.parts,
|
||||
part{
|
||||
contentType: contentType.string(),
|
||||
body: bytes.NewBufferString(body),
|
||||
},
|
||||
)
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// AddAlternativeData allows you to add alternative parts to the body
|
||||
// of the email message. This is most commonly used to add an
|
||||
// html version in addition to a plain text version that was
|
||||
// already added with SetBody.
|
||||
func (email *Email) AddAlternativeData(contentType ContentType, body []byte) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
|
||||
email.parts = append(email.parts,
|
||||
part{
|
||||
contentType: contentType.string(),
|
||||
body: bytes.NewBuffer(body),
|
||||
},
|
||||
)
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// SetSmimeCertificate gives possibility to sign email message with SMIME
|
||||
// certificate
|
||||
func (email *Email) SetSmimeCertificate(certificate tls.Certificate) *Email {
|
||||
if email.Error != nil {
|
||||
return email
|
||||
}
|
||||
email.SmimeCertificate = &certificate
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// GetFrom returns the sender of the email, if any
|
||||
func (email *Email) GetFrom() string {
|
||||
from := email.returnPath
|
||||
if from == "" {
|
||||
from = email.sender
|
||||
if from == "" {
|
||||
from = email.from
|
||||
if from == "" {
|
||||
from = email.replyTo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return from
|
||||
}
|
||||
|
||||
// GetRecipients returns a slice of recipients emails
|
||||
func (email *Email) GetRecipients() []string {
|
||||
return email.recipients
|
||||
}
|
||||
|
||||
func (email *Email) hasMixedPart() bool {
|
||||
return (len(email.parts) > 0 && len(email.attachments) > 0) || len(email.attachments) > 1
|
||||
}
|
||||
|
||||
func (email *Email) hasRelatedPart() bool {
|
||||
return (len(email.parts) > 0 && len(email.inlines) > 0) || len(email.inlines) > 1
|
||||
}
|
||||
|
||||
func (email *Email) hasAlternativePart() bool {
|
||||
return len(email.parts) > 1
|
||||
}
|
||||
|
||||
// GetMessage builds and returns the email message (RFC822 formatted message)
|
||||
func (email *Email) GetMessage() string {
|
||||
msg := newMessage(email)
|
||||
|
||||
if email.hasMixedPart() {
|
||||
msg.openMultipart("mixed")
|
||||
}
|
||||
|
||||
if email.hasRelatedPart() {
|
||||
msg.openMultipart("related")
|
||||
}
|
||||
|
||||
if email.hasAlternativePart() {
|
||||
msg.openMultipart("alternative")
|
||||
}
|
||||
|
||||
for _, part := range email.parts {
|
||||
msg.addBody(part.contentType, part.body.Bytes())
|
||||
}
|
||||
|
||||
if email.hasAlternativePart() {
|
||||
msg.closeMultipart()
|
||||
}
|
||||
|
||||
msg.addFiles(email.inlines, true)
|
||||
if email.hasRelatedPart() {
|
||||
msg.closeMultipart()
|
||||
}
|
||||
|
||||
msg.addFiles(email.attachments, false)
|
||||
if email.hasMixedPart() {
|
||||
msg.closeMultipart()
|
||||
}
|
||||
|
||||
return msg.getHeaders() + msg.body.String()
|
||||
}
|
||||
|
||||
// Send sends the composed email
|
||||
func (email *Email) Send(client *SMTPClient) error {
|
||||
return email.SendEnvelopeFrom(email.from, client)
|
||||
}
|
||||
|
||||
// SendEnvelopeFrom sends the composed email with envelope
|
||||
// sender. 'from' must be an email address.
|
||||
func (email *Email) SendEnvelopeFrom(from string, client *SMTPClient) error {
|
||||
if email.Error != nil {
|
||||
return email.Error
|
||||
}
|
||||
|
||||
if from == "" {
|
||||
from = email.from
|
||||
}
|
||||
|
||||
if len(email.recipients) < 1 {
|
||||
return errors.New("Mail Error: No recipient specified")
|
||||
}
|
||||
|
||||
var msg string
|
||||
if email.DkimMsg != "" {
|
||||
msg = email.DkimMsg
|
||||
} else {
|
||||
msg = email.GetMessage()
|
||||
}
|
||||
|
||||
if email.SmimeCertificate != nil {
|
||||
SMIME, err := smime.New(*email.SmimeCertificate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signedMsg, err := SMIME.Sign([]byte(msg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg = string(signedMsg)
|
||||
}
|
||||
|
||||
return send(from, email.recipients, msg, client)
|
||||
}
|
||||
|
||||
// dial connects to the smtp server with the request encryption type
|
||||
func dial(host string, port string, encryption Encryption, config *tls.Config) (*smtpClient, error) {
|
||||
var conn net.Conn
|
||||
var err error
|
||||
|
||||
address := host + ":" + port
|
||||
|
||||
// do the actual dial
|
||||
switch encryption {
|
||||
// TODO: Remove EncryptionSSL check before launch v3
|
||||
case EncryptionSSL, EncryptionSSLTLS:
|
||||
conn, err = tls.Dial("tcp", address, config)
|
||||
default:
|
||||
conn, err = net.Dial("tcp", address)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.New("Mail Error on dialing with encryption type " + encryption.String() + ": " + err.Error())
|
||||
}
|
||||
|
||||
c, err := newClient(conn, host)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Mail Error on smtp dial: %w", err)
|
||||
}
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
// smtpConnect connects to the smtp server and starts TLS and passes auth
|
||||
// if necessary
|
||||
func smtpConnect(host, port, helo string, a auth, at AuthType, encryption Encryption, config *tls.Config) (*smtpClient, error) {
|
||||
// connect to the mail server
|
||||
c, err := dial(host, port, encryption, config)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if helo == "" {
|
||||
helo = "localhost"
|
||||
}
|
||||
|
||||
// send Helo
|
||||
if err = c.hi(helo); err != nil {
|
||||
c.close()
|
||||
return nil, fmt.Errorf("Mail Error on Hello: %w", err)
|
||||
}
|
||||
|
||||
// STARTTLS if necessary
|
||||
// TODO: Remove EncryptionTLS check before launch v3
|
||||
if encryption == EncryptionTLS || encryption == EncryptionSTARTTLS {
|
||||
if ok, _ := c.extension("STARTTLS"); ok {
|
||||
if err = c.startTLS(config); err != nil {
|
||||
c.close()
|
||||
return nil, fmt.Errorf("Mail Error on STARTTLS: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only pass authentication if defined
|
||||
if at != AuthNone {
|
||||
// pass the authentication if necessary
|
||||
if a != nil {
|
||||
if ok, _ := c.extension("AUTH"); ok {
|
||||
if err = c.authenticate(a); err != nil {
|
||||
c.close()
|
||||
return nil, fmt.Errorf("Mail Error on Auth: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Connect returns the smtp client
|
||||
func (server *SMTPServer) Connect() (*SMTPClient, error) {
|
||||
|
||||
var a auth
|
||||
|
||||
switch server.Authentication {
|
||||
case AuthPlain:
|
||||
if server.Username != "" || server.Password != "" {
|
||||
a = plainAuthfn("", server.Username, server.Password, server.Host)
|
||||
}
|
||||
case AuthLogin:
|
||||
if server.Username != "" || server.Password != "" {
|
||||
a = loginAuthfn("", server.Username, server.Password, server.Host)
|
||||
}
|
||||
case AuthCRAMMD5:
|
||||
if server.Username != "" || server.Password != "" {
|
||||
a = cramMD5Authfn(server.Username, server.Password)
|
||||
}
|
||||
}
|
||||
|
||||
var smtpConnectChannel chan error
|
||||
var c *smtpClient
|
||||
var err error
|
||||
|
||||
tlsConfig := server.TLSConfig
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = &tls.Config{ServerName: server.Host}
|
||||
}
|
||||
|
||||
// if there is a ConnectTimeout, setup the channel and do the connect under a goroutine
|
||||
if server.ConnectTimeout != 0 {
|
||||
smtpConnectChannel = make(chan error, 2)
|
||||
go func() {
|
||||
c, err = smtpConnect(server.Host, fmt.Sprintf("%d", server.Port), server.Helo, a, server.Authentication, server.Encryption, tlsConfig)
|
||||
// send the result
|
||||
smtpConnectChannel <- err
|
||||
}()
|
||||
// get the connect result or timeout result, which ever happens first
|
||||
select {
|
||||
case err = <-smtpConnectChannel:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case <-time.After(server.ConnectTimeout):
|
||||
return nil, errors.New("Mail Error: SMTP Connection timed out")
|
||||
}
|
||||
} else {
|
||||
// no ConnectTimeout, just fire the connect
|
||||
c, err = smtpConnect(server.Host, fmt.Sprintf("%d", server.Port), server.Helo, a, server.Authentication, server.Encryption, tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &SMTPClient{
|
||||
Client: c,
|
||||
KeepAlive: server.KeepAlive,
|
||||
SendTimeout: server.SendTimeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Reset send RSET command to smtp client
|
||||
func (smtpClient *SMTPClient) Reset() error {
|
||||
return smtpClient.Client.reset()
|
||||
}
|
||||
|
||||
// Noop send NOOP command to smtp client
|
||||
func (smtpClient *SMTPClient) Noop() error {
|
||||
return smtpClient.Client.noop()
|
||||
}
|
||||
|
||||
// Quit send QUIT command to smtp client
|
||||
func (smtpClient *SMTPClient) Quit() error {
|
||||
return smtpClient.Client.quit()
|
||||
}
|
||||
|
||||
// Close closes the connection
|
||||
func (smtpClient *SMTPClient) Close() error {
|
||||
return smtpClient.Client.close()
|
||||
}
|
||||
|
||||
// SendMessage sends a message (a RFC822 formatted message)
|
||||
// 'from' must be an email address, recipients must be a slice of email address
|
||||
func SendMessage(from string, recipients []string, msg string, client *SMTPClient) error {
|
||||
if from == "" {
|
||||
return errors.New("Mail Error: No From email specifier")
|
||||
}
|
||||
if len(recipients) < 1 {
|
||||
return errors.New("Mail Error: No recipient specified")
|
||||
}
|
||||
|
||||
return send(from, recipients, msg, client)
|
||||
}
|
||||
|
||||
// send does the low level sending of the email
|
||||
func send(from string, to []string, msg string, client *SMTPClient) error {
|
||||
|
||||
//Check if client struct is not nil
|
||||
if client != nil {
|
||||
|
||||
//Check if client is not nil
|
||||
if client.Client != nil {
|
||||
var smtpSendChannel chan error
|
||||
|
||||
// if there is a SendTimeout, setup the channel and do the send under a goroutine
|
||||
if client.SendTimeout != 0 {
|
||||
smtpSendChannel = make(chan error, 1)
|
||||
|
||||
go func(from string, to []string, msg string, c *smtpClient) {
|
||||
smtpSendChannel <- sendMailProcess(from, to, msg, c)
|
||||
}(from, to, msg, client.Client)
|
||||
}
|
||||
|
||||
if client.SendTimeout == 0 {
|
||||
// no SendTimeout, just fire the sendMailProcess
|
||||
return sendMailProcess(from, to, msg, client.Client)
|
||||
}
|
||||
|
||||
// get the send result or timeout result, which ever happens first
|
||||
select {
|
||||
case sendError := <-smtpSendChannel:
|
||||
checkKeepAlive(client)
|
||||
return sendError
|
||||
case <-time.After(client.SendTimeout):
|
||||
checkKeepAlive(client)
|
||||
return errors.New("Mail Error: SMTP Send timed out")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New("Mail Error: No SMTP Client Provided")
|
||||
}
|
||||
|
||||
func sendMailProcess(from string, to []string, msg string, c *smtpClient) error {
|
||||
|
||||
cmdArgs := make(map[string]string)
|
||||
|
||||
if _, ok := c.ext["SIZE"]; ok {
|
||||
cmdArgs["SIZE"] = strconv.Itoa(len(msg))
|
||||
}
|
||||
|
||||
// Set the sender
|
||||
if err := c.mail(from, cmdArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the recipients
|
||||
for _, address := range to {
|
||||
if err := c.rcpt(address); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Send the data command
|
||||
w, err := c.data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// write the message
|
||||
_, err = fmt.Fprint(w, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if keepAlive for close or reset
|
||||
func checkKeepAlive(client *SMTPClient) {
|
||||
if client.KeepAlive {
|
||||
client.Client.reset()
|
||||
} else {
|
||||
client.Client.quit()
|
||||
client.Client.close()
|
||||
}
|
||||
}
|
226
example/example_test.go
Normal file
226
example/example_test.go
Normal file
@ -0,0 +1,226 @@
|
||||
package example
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
mail "github.com/xhit/go-simple-mail/v2"
|
||||
)
|
||||
|
||||
// Some variables to connect and the body.
|
||||
var (
|
||||
htmlBody = `<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>Hello Gophers!</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>This is the <b>Go gopher</b>.</p>
|
||||
<p><img src="cid:Gopher.png" alt="Go gopher" /></p>
|
||||
<p>Image created by Renee French</p>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
host = "localhost"
|
||||
port = 25
|
||||
username = "test@example.com"
|
||||
password = "santiago"
|
||||
encryptionType = mail.EncryptionNone
|
||||
connectTimeout = 10 * time.Second
|
||||
sendTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
// TestSendMailWithAttachment send a simple html email.
|
||||
func TestSendMail(t *testing.T) {
|
||||
client := mail.NewSMTPClient()
|
||||
|
||||
//SMTP Client
|
||||
client.Host = host
|
||||
client.Port = port
|
||||
client.Username = username
|
||||
client.Password = password
|
||||
client.Encryption = encryptionType
|
||||
client.ConnectTimeout = connectTimeout
|
||||
client.SendTimeout = sendTimeout
|
||||
client.KeepAlive = false
|
||||
|
||||
//Connect to client
|
||||
smtpClient, err := client.Connect()
|
||||
|
||||
if err != nil {
|
||||
t.Error("Expected nil, got", err, "connecting to client")
|
||||
}
|
||||
|
||||
//NOOP command, optional, used for avoid timeout when KeepAlive is true and you aren't sending mails.
|
||||
//Execute this command each 30 seconds is ideal for persistent connection
|
||||
err = smtpClient.Noop()
|
||||
|
||||
if err != nil {
|
||||
t.Error("Expected nil, got", err, "noop to client")
|
||||
}
|
||||
|
||||
//Create the email message
|
||||
email := mail.NewMSG()
|
||||
|
||||
email.SetFrom("From Example <test@example.com>").
|
||||
AddTo("admin@example.com").
|
||||
SetSubject("New Go Email")
|
||||
|
||||
email.SetBody(mail.TextHTML, htmlBody)
|
||||
email.AddAlternative(mail.TextPlain, "Hello Gophers!")
|
||||
|
||||
//Some additional options to send
|
||||
email.SetSender("xhit@test.com")
|
||||
email.SetReplyTo("replyto@reply.com")
|
||||
email.SetReturnPath("test@example.com")
|
||||
email.AddCc("cc@example1.com")
|
||||
email.AddBcc("bcccc@example2.com")
|
||||
|
||||
//Add inline too!
|
||||
email.Attach(&mail.File{FilePath: "C:/Users/sdelacruz/Pictures/Gopher.png", Inline: true})
|
||||
|
||||
//Attach a file with path
|
||||
email.Attach(&mail.File{FilePath: "C:/Users/sdelacruz/Pictures/Gopher.png"})
|
||||
|
||||
//Attach the file with a base64
|
||||
email.Attach(&mail.File{B64Data: "Zm9v", Name: "filename"})
|
||||
|
||||
//Set a different date in header email
|
||||
email.SetDate("2015-04-28 10:32:00 CDT")
|
||||
|
||||
//Send with low priority
|
||||
email.SetPriority(mail.PriorityLow)
|
||||
|
||||
// always check error after send
|
||||
if email.Error != nil {
|
||||
t.Error("Expected nil, got", email.Error, "generating email")
|
||||
}
|
||||
|
||||
//Pass the client to the email message to send it
|
||||
err = email.Send(smtpClient)
|
||||
|
||||
//Get first error
|
||||
email.GetError()
|
||||
|
||||
if err != nil {
|
||||
t.Error("Expected nil, got", err, "sending email")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSendMultipleEmails send multiple emails in same connection.
|
||||
func TestSendMultipleEmails(t *testing.T) {
|
||||
client := mail.NewSMTPClient()
|
||||
|
||||
//SMTP Client
|
||||
client.Host = host
|
||||
client.Port = port
|
||||
client.Username = username
|
||||
client.Password = password
|
||||
client.Encryption = encryptionType
|
||||
client.ConnectTimeout = connectTimeout
|
||||
client.SendTimeout = sendTimeout
|
||||
|
||||
//For authentication you can use AuthPlain, AuthLogin or AuthCRAMMD5
|
||||
client.Authentication = mail.AuthPlain
|
||||
|
||||
//KeepAlive true because the connection need to be open for multiple emails
|
||||
//For avoid inactivity timeout, every 30 second you can send a NO OPERATION command to smtp client
|
||||
//use smtpClient.Client.Noop() after 30 second of inactivity in this example
|
||||
client.KeepAlive = true
|
||||
|
||||
//Connect to client
|
||||
smtpClient, err := client.Connect()
|
||||
|
||||
if err != nil {
|
||||
t.Error("Expected nil, got", err, "connecting to client")
|
||||
}
|
||||
|
||||
toList := [3]string{"to1@example1.com", "to3@example2.com", "to4@example3.com"}
|
||||
|
||||
for _, to := range toList {
|
||||
err = sendEmail(htmlBody, to, smtpClient)
|
||||
if err != nil {
|
||||
t.Error("Expected nil, got", err, "sending email")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendEmail(htmlBody string, to string, smtpClient *mail.SMTPClient) error {
|
||||
//Create the email message
|
||||
email := mail.NewMSG()
|
||||
|
||||
email.SetFrom("From Example <from.email@example.com>").
|
||||
AddTo(to).
|
||||
SetSubject("New Go Email")
|
||||
|
||||
//Get from each mail
|
||||
email.GetFrom()
|
||||
email.SetBody(mail.TextHTML, htmlBody)
|
||||
|
||||
//Send with high priority
|
||||
email.SetPriority(mail.PriorityHigh)
|
||||
|
||||
// always check error after send
|
||||
if email.Error != nil {
|
||||
return email.Error
|
||||
}
|
||||
|
||||
//Pass the client to the email message to send it
|
||||
return email.Send(smtpClient)
|
||||
}
|
||||
|
||||
// TestWithTLS using gmail port 587.
|
||||
func TestWithTLS(t *testing.T) {
|
||||
client := mail.NewSMTPClient()
|
||||
|
||||
//SMTP Client
|
||||
client.Host = "smtp.gmail.com"
|
||||
client.Port = 587
|
||||
client.Username = "aaa@gmail.com"
|
||||
client.Password = "asdfghh"
|
||||
client.Encryption = mail.EncryptionSTARTTLS
|
||||
client.ConnectTimeout = 10 * time.Second
|
||||
client.SendTimeout = 10 * time.Second
|
||||
|
||||
//KeepAlive is not settted because by default is false
|
||||
|
||||
//Connect to client
|
||||
smtpClient, err := client.Connect()
|
||||
|
||||
if err != nil {
|
||||
t.Error("Expected nil, got", err, "connecting to client")
|
||||
}
|
||||
|
||||
err = sendEmail(htmlBody, "bbb@gmail.com", smtpClient)
|
||||
if err != nil {
|
||||
t.Error("Expected nil, got", err, "sending email")
|
||||
}
|
||||
}
|
||||
|
||||
// TestWithTLS using gmail port 465.
|
||||
func TestWithSSL(t *testing.T) {
|
||||
client := mail.NewSMTPClient()
|
||||
|
||||
//SMTP Client
|
||||
client.Host = "smtp.gmail.com"
|
||||
client.Port = 465
|
||||
client.Username = "aaa@gmail.com"
|
||||
client.Password = "asdfghh"
|
||||
client.Encryption = mail.EncryptionSSLTLS
|
||||
client.ConnectTimeout = 10 * time.Second
|
||||
client.SendTimeout = 10 * time.Second
|
||||
|
||||
//KeepAlive is not settted because by default is false
|
||||
|
||||
//Connect to client
|
||||
smtpClient, err := client.Connect()
|
||||
|
||||
if err != nil {
|
||||
t.Error("Expected nil, got", err, "connecting to client")
|
||||
}
|
||||
|
||||
err = sendEmail(htmlBody, "bbb@gmail.com", smtpClient)
|
||||
if err != nil {
|
||||
t.Error("Expected nil, got", err, "sending email")
|
||||
}
|
||||
}
|
16
go.mod
Normal file
16
go.mod
Normal file
@ -0,0 +1,16 @@
|
||||
module git.ma-al.com/gora_filip/go-simple-mail
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/m4x1202/go-smime v0.0.0-20220116002628-81b2598d61e9
|
||||
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208
|
||||
github.com/xhit/go-simple-mail/v2 v2.12.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-test/deep v1.0.8 // indirect
|
||||
github.com/stretchr/testify v1.8.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
|
||||
)
|
28
go.sum
Normal file
28
go.sum
Normal file
@ -0,0 +1,28 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/m4x1202/go-smime v0.0.0-20220116002628-81b2598d61e9 h1:utZv/cn554kAsaxARzYuX4AxhJYVVXplxsQUUyyXzLw=
|
||||
github.com/m4x1202/go-smime v0.0.0-20220116002628-81b2598d61e9/go.mod h1:J8KbgcmlC9pJd6BqeJ+O3vHtjpxqCKUP06VvTnwzeBc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjMdEfbuOBNXSVF2cMFGgQTPdKCbwM=
|
||||
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
|
||||
github.com/xhit/go-simple-mail/v2 v2.12.0 h1:KweA6NO8Z6fZyeckMPNpvElU6QDIyBShlpce1sYUZgg=
|
||||
github.com/xhit/go-simple-mail/v2 v2.12.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI=
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
197
header.go
Normal file
197
header.go
Normal file
@ -0,0 +1,197 @@
|
||||
// headers.go implements "Q" encoding as specified by RFC 2047.
|
||||
//Modified from https://github.com/joegrasse/mime to use with Go Simple Mail
|
||||
|
||||
package mail
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type encoder struct {
|
||||
w *bufio.Writer
|
||||
charset string
|
||||
usedChars int
|
||||
}
|
||||
|
||||
// newEncoder returns a new mime header encoder that writes to w. The c
|
||||
// parameter specifies the name of the character set of the text that will be
|
||||
// encoded. The u parameter indicates how many characters have been used
|
||||
// already.
|
||||
func newEncoder(w io.Writer, c string, u int) *encoder {
|
||||
return &encoder{bufio.NewWriter(w), strings.ToUpper(c), u}
|
||||
}
|
||||
|
||||
// encode encodes p using the "Q" encoding and writes it to the underlying
|
||||
// io.Writer. It limits line length to 75 characters.
|
||||
func (e *encoder) encode(p []byte) (n int, err error) {
|
||||
var output bytes.Buffer
|
||||
allPrintable := true
|
||||
|
||||
// some lines we encode end in "
|
||||
//maxLineLength := 75 - 1
|
||||
maxLineLength := 76
|
||||
|
||||
// prevent header injection
|
||||
p = secureHeader(p)
|
||||
|
||||
// check to see if we have all printable characters
|
||||
for _, c := range p {
|
||||
if !isVchar(c) && !isWSP(c) {
|
||||
allPrintable = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// all characters are printable. just do line folding
|
||||
if allPrintable {
|
||||
text := string(p)
|
||||
words := strings.Split(text, " ")
|
||||
|
||||
lineBuffer := ""
|
||||
firstWord := true
|
||||
|
||||
// split the line where necessary
|
||||
for _, word := range words {
|
||||
/*fmt.Println("Current Line:",lineBuffer)
|
||||
fmt.Println("Here: Max:", maxLineLength ,"Buffer Length:", len(lineBuffer), "Used Chars:", e.usedChars, "Length Encoded Char:",len(word))
|
||||
fmt.Println("----------")*/
|
||||
|
||||
newWord := ""
|
||||
if !firstWord {
|
||||
newWord += " "
|
||||
}
|
||||
newWord += word
|
||||
|
||||
// check line length
|
||||
if (e.usedChars+len(lineBuffer)+len(newWord) /*+len(" ")+len(word)*/) > maxLineLength && (lineBuffer != "" || e.usedChars != 0) {
|
||||
output.WriteString(lineBuffer + "\r\n")
|
||||
|
||||
// first word on newline needs a space in front
|
||||
if !firstWord {
|
||||
lineBuffer = ""
|
||||
} else {
|
||||
lineBuffer = " "
|
||||
}
|
||||
|
||||
//firstLine = false
|
||||
//firstWord = true
|
||||
// reset since not on the first line anymore
|
||||
e.usedChars = 0
|
||||
}
|
||||
|
||||
/*if !firstWord {
|
||||
lineBuffer += " "
|
||||
}*/
|
||||
|
||||
lineBuffer += newWord /*word*/
|
||||
|
||||
firstWord = false
|
||||
|
||||
// reset since not on the first line anymore
|
||||
/*if !firstLine {
|
||||
e.usedChars = 0
|
||||
}*/
|
||||
}
|
||||
|
||||
output.WriteString(lineBuffer)
|
||||
|
||||
} else {
|
||||
firstLine := true
|
||||
|
||||
// A single encoded word can not be longer than 75 characters
|
||||
if e.usedChars == 0 {
|
||||
maxLineLength = 75
|
||||
}
|
||||
|
||||
wordBegin := "=?" + e.charset + "?Q?"
|
||||
wordEnd := "?="
|
||||
|
||||
lineBuffer := wordBegin
|
||||
|
||||
for i := 0; i < len(p); {
|
||||
// encode the character
|
||||
encodedChar, runeLength := encode(p, i)
|
||||
|
||||
/*fmt.Println("Current Line:",lineBuffer)
|
||||
fmt.Println("Here: Max:", maxLineLength ,"Buffer Length:", len(lineBuffer), "Used Chars:", e.usedChars, "Length Encoded Char:",len(encodedChar))
|
||||
fmt.Println("----------")*/
|
||||
|
||||
// Check line length
|
||||
if len(lineBuffer)+e.usedChars+len(encodedChar) > (maxLineLength - len(wordEnd)) {
|
||||
output.WriteString(lineBuffer + wordEnd + "\r\n")
|
||||
lineBuffer = " " + wordBegin
|
||||
firstLine = false
|
||||
}
|
||||
|
||||
lineBuffer += encodedChar
|
||||
|
||||
i = i + runeLength
|
||||
|
||||
// reset since not on the first line anymore
|
||||
if !firstLine {
|
||||
e.usedChars = 0
|
||||
maxLineLength = 76
|
||||
}
|
||||
}
|
||||
|
||||
output.WriteString(lineBuffer + wordEnd)
|
||||
}
|
||||
|
||||
e.w.Write(output.Bytes())
|
||||
e.w.Flush()
|
||||
n = output.Len()
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// encode takes a string and position in that string and encodes one utf-8
|
||||
// character. It then returns the encoded string and number of runes in the
|
||||
// character.
|
||||
func encode(text []byte, i int) (encodedString string, runeLength int) {
|
||||
started := false
|
||||
|
||||
for ; i < len(text) && (!utf8.RuneStart(text[i]) || !started); i++ {
|
||||
switch c := text[i]; {
|
||||
case c == ' ':
|
||||
encodedString += "_"
|
||||
case isVchar(c) && c != '=' && c != '?' && c != '_':
|
||||
encodedString += string(c)
|
||||
default:
|
||||
encodedString += fmt.Sprintf("=%02X", c)
|
||||
}
|
||||
|
||||
runeLength++
|
||||
|
||||
started = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// secureHeader removes all unnecessary values to prevent
|
||||
// header injection
|
||||
func secureHeader(text []byte) []byte {
|
||||
secureValue := strings.TrimSpace(string(text))
|
||||
secureValue = strings.Replace(secureValue, "\r", "", -1)
|
||||
secureValue = strings.Replace(secureValue, "\n", "", -1)
|
||||
secureValue = strings.Replace(secureValue, "\t", "", -1)
|
||||
|
||||
return []byte(secureValue)
|
||||
}
|
||||
|
||||
// isVchar returns true if c is an RFC 5322 VCHAR character.
|
||||
func isVchar(c byte) bool {
|
||||
// Visible (printing) characters.
|
||||
return '!' <= c && c <= '~'
|
||||
}
|
||||
|
||||
// isWSP returns true if c is a WSP (white space).
|
||||
// WSP is a space or horizontal tab (RFC5234 Appendix B).
|
||||
func isWSP(c byte) bool {
|
||||
return c == ' ' || c == '\t'
|
||||
}
|
119
header_test.go
Normal file
119
header_test.go
Normal file
@ -0,0 +1,119 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
//"fmt"
|
||||
"bytes"
|
||||
//"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWriter(t *testing.T) {
|
||||
testWriter(t, false)
|
||||
}
|
||||
|
||||
func testWriter(t *testing.T, binary bool) {
|
||||
utf8 := "utf-8"
|
||||
tests := []struct {
|
||||
charset string
|
||||
usedChars int
|
||||
in, want string
|
||||
}{
|
||||
{utf8, 0, "", ""},
|
||||
{utf8, 0, " ", ""},
|
||||
{utf8, 8, "", ""},
|
||||
{utf8, 0, "This is an English string. 0123456789", "This is an English string. 0123456789"},
|
||||
{utf8, 0, "日本語テキストです。", "=?UTF-8?Q?=E6=97=A5=E6=9C=AC=E8=AA=9E=E3=83=86=E3=82=AD=E3=82=B9=E3=83=88?=\r\n =?UTF-8?Q?=E3=81=A7=E3=81=99=E3=80=82?="},
|
||||
{utf8, 8, "日本語テキストです。", "=?UTF-8?Q?=E6=97=A5=E6=9C=AC=E8=AA=9E=E3=83=86=E3=82=AD=E3=82=B9?=\r\n =?UTF-8?Q?=E3=83=88=E3=81=A7=E3=81=99=E3=80=82?="},
|
||||
{utf8, 0, "漢字、カタカナ、ひらがなを含む、非常に長いタイトル行が一体全体どのようにしてEncodeされるのか?", "=?UTF-8?Q?=E6=BC=A2=E5=AD=97=E3=80=81=E3=82=AB=E3=82=BF=E3=82=AB=E3=83=8A?=\r\n =?UTF-8?Q?=E3=80=81=E3=81=B2=E3=82=89=E3=81=8C=E3=81=AA=E3=82=92=E5=90=AB?=\r\n =?UTF-8?Q?=E3=82=80=E3=80=81=E9=9D=9E=E5=B8=B8=E3=81=AB=E9=95=B7=E3=81=84?=\r\n =?UTF-8?Q?=E3=82=BF=E3=82=A4=E3=83=88=E3=83=AB=E8=A1=8C=E3=81=8C=E4=B8=80?=\r\n =?UTF-8?Q?=E4=BD=93=E5=85=A8=E4=BD=93=E3=81=A9=E3=81=AE=E3=82=88=E3=81=86?=\r\n =?UTF-8?Q?=E3=81=AB=E3=81=97=E3=81=A6Encode=E3=81=95=E3=82=8C=E3=82=8B?=\r\n =?UTF-8?Q?=E3=81=AE=E3=81=8B=EF=BC=9F?="},
|
||||
{utf8, 0, "dankogai@dan.co.jp (小飼=Kogai, 弾=Dan)", "=?UTF-8?Q?dankogai@dan.co.jp_(=E5=B0=8F=E9=A3=BC=3DKogai,_=E5=BC=BE=3DDan?=\r\n =?UTF-8?Q?)?="},
|
||||
{utf8, 0, "Αυτό είναι ελληνικό κείμενο. 0123456789.", "=?UTF-8?Q?=CE=91=CF=85=CF=84=CF=8C_=CE=B5=CE=AF=CE=BD=CE=B1=CE=B9_=CE=B5?=\r\n =?UTF-8?Q?=CE=BB=CE=BB=CE=B7=CE=BD=CE=B9=CE=BA=CF=8C_=CE=BA=CE=B5=CE=AF?=\r\n =?UTF-8?Q?=CE=BC=CE=B5=CE=BD=CE=BF._0123456789.?="},
|
||||
{utf8, 0, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis.\r\n Parturient tincidunt nibh tellus metus pellentesque semper laoreet In\r\n tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit\r\n accumsan gravida. Et fringilla convallis quis tincidunt convallis urna\r\n adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 1, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis.\r\n Parturient tincidunt nibh tellus metus pellentesque semper laoreet In\r\n tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit\r\n accumsan gravida. Et fringilla convallis quis tincidunt convallis urna\r\n adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 2, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis.\r\n Parturient tincidunt nibh tellus metus pellentesque semper laoreet In\r\n tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit\r\n accumsan gravida. Et fringilla convallis quis tincidunt convallis urna\r\n adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 3, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis.\r\n Parturient tincidunt nibh tellus metus pellentesque semper laoreet In\r\n tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit\r\n accumsan gravida. Et fringilla convallis quis tincidunt convallis urna\r\n adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 4, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis.\r\n Parturient tincidunt nibh tellus metus pellentesque semper laoreet In\r\n tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit\r\n accumsan gravida. Et fringilla convallis quis tincidunt convallis urna\r\n adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 5, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis.\r\n Parturient tincidunt nibh tellus metus pellentesque semper laoreet In\r\n tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit\r\n accumsan gravida. Et fringilla convallis quis tincidunt convallis urna\r\n adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 6, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis.\r\n Parturient tincidunt nibh tellus metus pellentesque semper laoreet In\r\n tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit\r\n accumsan gravida. Et fringilla convallis quis tincidunt convallis urna\r\n adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 7, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis.\r\n Parturient tincidunt nibh tellus metus pellentesque semper laoreet In\r\n tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit\r\n accumsan gravida. Et fringilla convallis quis tincidunt convallis urna\r\n adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 8, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis.\r\n Parturient tincidunt nibh tellus metus pellentesque semper laoreet In\r\n tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit\r\n accumsan gravida. Et fringilla convallis quis tincidunt convallis urna\r\n adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 9, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis.\r\n Parturient tincidunt nibh tellus metus pellentesque semper laoreet In\r\n tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit\r\n accumsan gravida. Et fringilla convallis quis tincidunt convallis urna\r\n adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 10, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra\r\n quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In\r\n tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit\r\n accumsan gravida. Et fringilla convallis quis tincidunt convallis urna\r\n adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 11, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra\r\n quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In\r\n tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit\r\n accumsan gravida. Et fringilla convallis quis tincidunt convallis urna\r\n adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 12, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra\r\n quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In\r\n tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit\r\n accumsan gravida. Et fringilla convallis quis tincidunt convallis urna\r\n adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 13, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra\r\n quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In\r\n tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit\r\n accumsan gravida. Et fringilla convallis quis tincidunt convallis urna\r\n adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 14, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra\r\n quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In\r\n tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit\r\n accumsan gravida. Et fringilla convallis quis tincidunt convallis urna\r\n adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 15, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra\r\n quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In\r\n tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit\r\n accumsan gravida. Et fringilla convallis quis tincidunt convallis urna\r\n adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 16, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut\r\n pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper\r\n laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac\r\n suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis\r\n urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 17, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut\r\n pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper\r\n laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac\r\n suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis\r\n urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 18, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut\r\n pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper\r\n laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac\r\n suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis\r\n urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 19, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut\r\n pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper\r\n laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac\r\n suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis\r\n urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 20, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut\r\n pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper\r\n laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac\r\n suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis\r\n urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 21, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut\r\n pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper\r\n laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac\r\n suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis\r\n urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 22, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut\r\n pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper\r\n laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac\r\n suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis\r\n urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 23, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut\r\n pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper\r\n laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac\r\n suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis\r\n urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 24, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi ut\r\n pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper\r\n laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac\r\n suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis\r\n urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
{utf8, 25, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi\r\n ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque\r\n semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus\r\n faucibus ac suscipit accumsan gravida. Et fringilla convallis quis\r\n tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo\r\n Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 26, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi\r\n ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque\r\n semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus\r\n faucibus ac suscipit accumsan gravida. Et fringilla convallis quis\r\n tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo\r\n Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 27, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed morbi\r\n ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque\r\n semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus\r\n faucibus ac suscipit accumsan gravida. Et fringilla convallis quis\r\n tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo\r\n Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 28, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed\r\n morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque\r\n semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus\r\n faucibus ac suscipit accumsan gravida. Et fringilla convallis quis\r\n tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo\r\n Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 29, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed\r\n morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque\r\n semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus\r\n faucibus ac suscipit accumsan gravida. Et fringilla convallis quis\r\n tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo\r\n Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 30, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed\r\n morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque\r\n semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus\r\n faucibus ac suscipit accumsan gravida. Et fringilla convallis quis\r\n tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo\r\n Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 31, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed\r\n morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque\r\n semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus\r\n faucibus ac suscipit accumsan gravida. Et fringilla convallis quis\r\n tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo\r\n Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 32, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed\r\n morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque\r\n semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus\r\n faucibus ac suscipit accumsan gravida. Et fringilla convallis quis\r\n tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo\r\n Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 33, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer Sed\r\n morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque\r\n semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus\r\n faucibus ac suscipit accumsan gravida. Et fringilla convallis quis\r\n tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo\r\n Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 34, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer\r\n Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus\r\n pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae\r\n Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis\r\n tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo\r\n Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 35, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer\r\n Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus\r\n pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae\r\n Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis\r\n tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo\r\n Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 36, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer\r\n Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus\r\n pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae\r\n Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis\r\n tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo\r\n Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 37, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet consectetuer\r\n Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus\r\n pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae\r\n Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis\r\n tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo\r\n Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 38, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet\r\n consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus\r\n metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue\r\n vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis\r\n quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl\r\n leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 39, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet\r\n consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus\r\n metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue\r\n vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis\r\n quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl\r\n leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 40, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet\r\n consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus\r\n metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue\r\n vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis\r\n quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl\r\n leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 41, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet\r\n consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus\r\n metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue\r\n vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis\r\n quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl\r\n leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 42, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet\r\n consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus\r\n metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue\r\n vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis\r\n quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl\r\n leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 43, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet\r\n consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus\r\n metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue\r\n vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis\r\n quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl\r\n leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 44, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet\r\n consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus\r\n metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue\r\n vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis\r\n quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl\r\n leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 45, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet\r\n consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus\r\n metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue\r\n vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis\r\n quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl\r\n leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 46, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet\r\n consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus\r\n metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue\r\n vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis\r\n quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl\r\n leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 47, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet\r\n consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus\r\n metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue\r\n vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis\r\n quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl\r\n leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 48, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet\r\n consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus\r\n metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue\r\n vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis\r\n quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl\r\n leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 49, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet\r\n consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus\r\n metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue\r\n vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis\r\n quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl\r\n leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 50, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit amet\r\n consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus\r\n metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue\r\n vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis\r\n quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl\r\n leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 51, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit\r\n amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh\r\n tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum\r\n augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla\r\n convallis quis tincidunt convallis urna adipiscing eros nunc porttitor.\r\n Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 52, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit\r\n amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh\r\n tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum\r\n augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla\r\n convallis quis tincidunt convallis urna adipiscing eros nunc porttitor.\r\n Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 53, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit\r\n amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh\r\n tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum\r\n augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla\r\n convallis quis tincidunt convallis urna adipiscing eros nunc porttitor.\r\n Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 54, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit\r\n amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh\r\n tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum\r\n augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla\r\n convallis quis tincidunt convallis urna adipiscing eros nunc porttitor.\r\n Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 55, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor sit\r\n amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh\r\n tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum\r\n augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla\r\n convallis quis tincidunt convallis urna adipiscing eros nunc porttitor.\r\n Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 56, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor\r\n sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh\r\n tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum\r\n augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla\r\n convallis quis tincidunt convallis urna adipiscing eros nunc porttitor.\r\n Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 57, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor\r\n sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh\r\n tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum\r\n augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla\r\n convallis quis tincidunt convallis urna adipiscing eros nunc porttitor.\r\n Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 58, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor\r\n sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh\r\n tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum\r\n augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla\r\n convallis quis tincidunt convallis urna adipiscing eros nunc porttitor.\r\n Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 59, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum dolor\r\n sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh\r\n tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum\r\n augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla\r\n convallis quis tincidunt convallis urna adipiscing eros nunc porttitor.\r\n Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor."},
|
||||
{utf8, 60, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum\r\n dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient\r\n tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed\r\n in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et\r\n fringilla convallis quis tincidunt convallis urna adipiscing eros nunc\r\n porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus\r\n tortor."},
|
||||
{utf8, 61, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum\r\n dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient\r\n tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed\r\n in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et\r\n fringilla convallis quis tincidunt convallis urna adipiscing eros nunc\r\n porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus\r\n tortor."},
|
||||
{utf8, 62, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum\r\n dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient\r\n tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed\r\n in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et\r\n fringilla convallis quis tincidunt convallis urna adipiscing eros nunc\r\n porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus\r\n tortor."},
|
||||
{utf8, 63, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum\r\n dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient\r\n tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed\r\n in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et\r\n fringilla convallis quis tincidunt convallis urna adipiscing eros nunc\r\n porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus\r\n tortor."},
|
||||
{utf8, 64, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum\r\n dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient\r\n tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed\r\n in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et\r\n fringilla convallis quis tincidunt convallis urna adipiscing eros nunc\r\n porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus\r\n tortor."},
|
||||
{utf8, 65, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem ipsum\r\n dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient\r\n tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed\r\n in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et\r\n fringilla convallis quis tincidunt convallis urna adipiscing eros nunc\r\n porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus\r\n tortor."},
|
||||
{utf8, 66, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem\r\n ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient\r\n tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed\r\n in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et\r\n fringilla convallis quis tincidunt convallis urna adipiscing eros nunc\r\n porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus\r\n tortor."},
|
||||
{utf8, 67, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem\r\n ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient\r\n tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed\r\n in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et\r\n fringilla convallis quis tincidunt convallis urna adipiscing eros nunc\r\n porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus\r\n tortor."},
|
||||
{utf8, 68, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem\r\n ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient\r\n tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed\r\n in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et\r\n fringilla convallis quis tincidunt convallis urna adipiscing eros nunc\r\n porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus\r\n tortor."},
|
||||
{utf8, 69, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem\r\n ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient\r\n tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed\r\n in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et\r\n fringilla convallis quis tincidunt convallis urna adipiscing eros nunc\r\n porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus\r\n tortor."},
|
||||
{utf8, 70, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem\r\n ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient\r\n tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed\r\n in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et\r\n fringilla convallis quis tincidunt convallis urna adipiscing eros nunc\r\n porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus\r\n tortor."},
|
||||
{utf8, 71, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "Lorem\r\n ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient\r\n tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed\r\n in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et\r\n fringilla convallis quis tincidunt convallis urna adipiscing eros nunc\r\n porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus\r\n tortor."},
|
||||
{utf8, 72, "Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis. Parturient tincidunt nibh tellus metus pellentesque semper laoreet In tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit accumsan gravida. Et fringilla convallis quis tincidunt convallis urna adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum Vestibulum nunc purus tortor.", "\r\n Lorem ipsum dolor sit amet consectetuer Sed morbi ut pharetra quis.\r\n Parturient tincidunt nibh tellus metus pellentesque semper laoreet In\r\n tempor sed. Sed in Vestibulum augue vitae Vivamus faucibus ac suscipit\r\n accumsan gravida. Et fringilla convallis quis tincidunt convallis urna\r\n adipiscing eros nunc porttitor. Ipsum id nisl leo Curabitur purus Cum\r\n Vestibulum nunc purus tortor."},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
//fmt.Println("Here! <---------------------------------------------------------------")
|
||||
buf := new(bytes.Buffer)
|
||||
w := newEncoder(buf, test.charset, test.usedChars)
|
||||
|
||||
if _, err := w.encode([]byte(test.in)); err != nil {
|
||||
t.Errorf("Write(%q): %v", test.in, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if buf.String() != test.want {
|
||||
t.Errorf("Charset: %s Used Chars: %d Write(%q), \r\ngot:\r\n%q\r\nwant:\r\n%q", test.charset, test.usedChars, test.in, buf, test.want)
|
||||
}
|
||||
}
|
||||
}
|
248
message.go
Normal file
248
message.go
Normal file
@ -0,0 +1,248 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"mime/quotedprintable"
|
||||
"net/textproto"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type message struct {
|
||||
headers textproto.MIMEHeader
|
||||
body *bytes.Buffer
|
||||
writers []*multipart.Writer
|
||||
parts uint8
|
||||
cids map[string]string
|
||||
charset string
|
||||
encoding encoding
|
||||
}
|
||||
|
||||
func newMessage(email *Email) *message {
|
||||
return &message{
|
||||
headers: email.headers,
|
||||
body: new(bytes.Buffer),
|
||||
cids: make(map[string]string),
|
||||
charset: email.Charset,
|
||||
encoding: email.Encoding}
|
||||
}
|
||||
|
||||
func encodeHeader(text string, charset string, usedChars int) string {
|
||||
// create buffer
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// encode
|
||||
encoder := newEncoder(buf, charset, usedChars)
|
||||
encoder.encode([]byte(text))
|
||||
|
||||
return buf.String()
|
||||
|
||||
/*
|
||||
switch encoding {
|
||||
case EncodingBase64:
|
||||
return mime.BEncoding.Encode(charset, text)
|
||||
default:
|
||||
return mime.QEncoding.Encode(charset, text)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// getHeaders returns the message headers
|
||||
func (msg *message) getHeaders() (headers string) {
|
||||
// if the date header isn't set, set it
|
||||
if date := msg.headers.Get("Date"); date == "" {
|
||||
msg.headers.Set("Date", time.Now().Format(time.RFC1123Z))
|
||||
}
|
||||
|
||||
// encode and combine the headers
|
||||
for header, values := range msg.headers {
|
||||
headers += header + ": " + encodeHeader(strings.Join(values, ", "), msg.charset, len(header)+2) + "\r\n"
|
||||
}
|
||||
|
||||
headers = headers + "\r\n"
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// getCID gets the generated CID for the provided text
|
||||
func (msg *message) getCID(text string) (cid string) {
|
||||
// set the date format to use
|
||||
const dateFormat = "20060102.150405"
|
||||
|
||||
// get the cid if we have one
|
||||
cid, exists := msg.cids[text]
|
||||
if !exists {
|
||||
// generate a new cid
|
||||
cid = time.Now().Format(dateFormat) + "." + strconv.Itoa(len(msg.cids)+1) + "@mail.0"
|
||||
// save it
|
||||
msg.cids[text] = cid
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// replaceCIDs replaces the CIDs found in a text string
|
||||
// with generated ones
|
||||
func (msg *message) replaceCIDs(text string) string {
|
||||
// regular expression to find cids
|
||||
re := regexp.MustCompile(`(src|href)="cid:(.*?)"`)
|
||||
// replace all of the found cids with generated ones
|
||||
for _, matches := range re.FindAllStringSubmatch(text, -1) {
|
||||
cid := msg.getCID(matches[2])
|
||||
text = strings.Replace(text, "cid:"+matches[2], "cid:"+cid, -1)
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
// openMultipart creates a new part of a multipart message
|
||||
func (msg *message) openMultipart(multipartType string) {
|
||||
// create a new multipart writer
|
||||
msg.writers = append(msg.writers, multipart.NewWriter(msg.body))
|
||||
// create the boundary
|
||||
contentType := "multipart/" + multipartType + ";\n \tboundary=" + msg.writers[msg.parts].Boundary()
|
||||
|
||||
// if no existing parts, add header to main header group
|
||||
if msg.parts == 0 {
|
||||
msg.headers.Set("Content-Type", contentType)
|
||||
} else { // add header to multipart section
|
||||
header := make(textproto.MIMEHeader)
|
||||
header.Set("Content-Type", contentType)
|
||||
msg.writers[msg.parts-1].CreatePart(header)
|
||||
}
|
||||
|
||||
msg.parts++
|
||||
}
|
||||
|
||||
// closeMultipart closes a part of a multipart message
|
||||
func (msg *message) closeMultipart() {
|
||||
if msg.parts > 0 {
|
||||
msg.writers[msg.parts-1].Close()
|
||||
msg.parts--
|
||||
}
|
||||
}
|
||||
|
||||
// base64Encode base64 encodes the provided text with line wrapping
|
||||
func base64Encode(text []byte) []byte {
|
||||
// create buffer
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// create base64 encoder that linewraps
|
||||
encoder := base64.NewEncoder(base64.StdEncoding, &base64LineWrap{writer: buf})
|
||||
|
||||
// write the encoded text to buf
|
||||
encoder.Write(text)
|
||||
encoder.Close()
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// qpEncode uses the quoted-printable encoding to encode the provided text
|
||||
func qpEncode(text []byte) []byte {
|
||||
// create buffer
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
encoder := quotedprintable.NewWriter(buf)
|
||||
|
||||
encoder.Write(text)
|
||||
encoder.Close()
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
const maxLineChars = 76
|
||||
|
||||
type base64LineWrap struct {
|
||||
writer io.Writer
|
||||
numLineChars int
|
||||
}
|
||||
|
||||
func (e *base64LineWrap) Write(p []byte) (n int, err error) {
|
||||
n = 0
|
||||
// while we have more chars than are allowed
|
||||
for len(p)+e.numLineChars > maxLineChars {
|
||||
numCharsToWrite := maxLineChars - e.numLineChars
|
||||
// write the chars we can
|
||||
e.writer.Write(p[:numCharsToWrite])
|
||||
// write a line break
|
||||
e.writer.Write([]byte("\r\n"))
|
||||
// reset the line count
|
||||
e.numLineChars = 0
|
||||
// remove the chars that have been written
|
||||
p = p[numCharsToWrite:]
|
||||
// set the num of chars written
|
||||
n += numCharsToWrite
|
||||
}
|
||||
|
||||
// write what is left
|
||||
e.writer.Write(p)
|
||||
e.numLineChars += len(p)
|
||||
n += len(p)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (msg *message) write(header textproto.MIMEHeader, body []byte, encoding encoding) {
|
||||
msg.writeHeader(header)
|
||||
msg.writeBody(body, encoding)
|
||||
}
|
||||
|
||||
func (msg *message) writeHeader(headers textproto.MIMEHeader) {
|
||||
// if there are no parts add header to main headers
|
||||
if msg.parts == 0 {
|
||||
for header, value := range headers {
|
||||
msg.headers[header] = value
|
||||
}
|
||||
} else { // add header to multipart section
|
||||
msg.writers[msg.parts-1].CreatePart(headers)
|
||||
}
|
||||
}
|
||||
|
||||
func (msg *message) writeBody(body []byte, encoding encoding) {
|
||||
// encode and write the body
|
||||
switch encoding {
|
||||
case EncodingQuotedPrintable:
|
||||
msg.body.Write(qpEncode(body))
|
||||
case EncodingBase64:
|
||||
msg.body.Write(base64Encode(body))
|
||||
default:
|
||||
msg.body.Write(body)
|
||||
}
|
||||
}
|
||||
|
||||
func (msg *message) addBody(contentType string, body []byte) {
|
||||
body = []byte(msg.replaceCIDs(string(body)))
|
||||
|
||||
header := make(textproto.MIMEHeader)
|
||||
header.Set("Content-Type", contentType+"; charset="+msg.charset)
|
||||
header.Set("Content-Transfer-Encoding", msg.encoding.string())
|
||||
msg.write(header, body, msg.encoding)
|
||||
}
|
||||
|
||||
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
|
||||
|
||||
func escapeQuotes(s string) string {
|
||||
return quoteEscaper.Replace(s)
|
||||
}
|
||||
|
||||
func (msg *message) addFiles(files []*File, inline bool) {
|
||||
encoding := EncodingBase64
|
||||
for _, file := range files {
|
||||
header := make(textproto.MIMEHeader)
|
||||
header.Set("Content-Type", file.MimeType+";\n \tname=\""+encodeHeader(escapeQuotes(file.Name), msg.charset, 6)+`"`)
|
||||
header.Set("Content-Transfer-Encoding", encoding.string())
|
||||
if inline {
|
||||
header.Set("Content-Disposition", "inline;\n \tfilename=\""+encodeHeader(escapeQuotes(file.Name), msg.charset, 10)+`"`)
|
||||
header.Set("Content-ID", "<"+msg.getCID(file.Name)+">")
|
||||
} else {
|
||||
header.Set("Content-Disposition", "attachment;\n \tfilename=\""+encodeHeader(escapeQuotes(file.Name), msg.charset, 10)+`"`)
|
||||
}
|
||||
|
||||
msg.write(header, file.Data, encoding)
|
||||
}
|
||||
}
|
332
smtp.go
Normal file
332
smtp.go
Normal file
@ -0,0 +1,332 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in https://raw.githubusercontent.com/golang/go/master/LICENSE
|
||||
|
||||
// Package mail implements the Simple Mail Transfer Protocol as defined in RFC 5321.
|
||||
// It also implements the following extensions:
|
||||
// 8BITMIME RFC 1652
|
||||
// SMTPUTF8 RFC 6531
|
||||
// AUTH RFC 2554
|
||||
// STARTTLS RFC 3207
|
||||
// SIZE RFC 1870
|
||||
// Additional extensions may be handled by clients using smtp.go in golang source code or pull request Go Simple Mail
|
||||
|
||||
// smtp.go file is a modification of smtp golang package what is frozen and is not accepting new features.
|
||||
|
||||
package mail
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A Client represents a client connection to an SMTP server.
|
||||
type smtpClient struct {
|
||||
// Text is the textproto.Conn used by the Client.
|
||||
text *textproto.Conn
|
||||
// keep a reference to the connection so it can be used to create a TLS
|
||||
// connection later
|
||||
conn net.Conn
|
||||
// whether the Client is using TLS
|
||||
tls bool
|
||||
serverName string
|
||||
// map of supported extensions
|
||||
ext map[string]string
|
||||
// supported auth mechanisms
|
||||
a []string
|
||||
localName string // the name to use in HELO/EHLO
|
||||
didHello bool // whether we've said HELO/EHLO
|
||||
helloError error // the error from the hello
|
||||
}
|
||||
|
||||
// newClient returns a new smtpClient using an existing connection and host as a
|
||||
// server name to be used when authenticating.
|
||||
func newClient(conn net.Conn, host string) (*smtpClient, error) {
|
||||
text := textproto.NewConn(conn)
|
||||
_, _, err := text.ReadResponse(220)
|
||||
if err != nil {
|
||||
text.Close()
|
||||
return nil, err
|
||||
}
|
||||
c := &smtpClient{text: text, conn: conn, serverName: host, localName: "localhost"}
|
||||
_, c.tls = conn.(*tls.Conn)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
func (c *smtpClient) close() error {
|
||||
return c.text.Close()
|
||||
}
|
||||
|
||||
// hello runs a hello exchange if needed.
|
||||
func (c *smtpClient) hello() error {
|
||||
if !c.didHello {
|
||||
c.didHello = true
|
||||
err := c.ehlo()
|
||||
if err != nil {
|
||||
c.helloError = c.helo()
|
||||
}
|
||||
}
|
||||
return c.helloError
|
||||
}
|
||||
|
||||
// hi sends a HELO or EHLO to the server as the given host name.
|
||||
// Calling this method is only necessary if the client needs control
|
||||
// over the host name used. The client will introduce itself as "localhost"
|
||||
// automatically otherwise. If Hello is called, it must be called before
|
||||
// any of the other methods.
|
||||
func (c *smtpClient) hi(localName string) error {
|
||||
if err := validateLine(localName); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.didHello {
|
||||
return errors.New("smtp: Hello called after other methods")
|
||||
}
|
||||
c.localName = localName
|
||||
return c.hello()
|
||||
}
|
||||
|
||||
// cmd is a convenience function that sends a command and returns the response
|
||||
func (c *smtpClient) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
|
||||
id, err := c.text.Cmd(format, args...)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
c.text.StartResponse(id)
|
||||
defer c.text.EndResponse(id)
|
||||
code, msg, err := c.text.ReadResponse(expectCode)
|
||||
return code, msg, err
|
||||
}
|
||||
|
||||
// helo sends the HELO greeting to the server. It should be used only when the
|
||||
// server does not support ehlo.
|
||||
func (c *smtpClient) helo() error {
|
||||
c.ext = nil
|
||||
_, _, err := c.cmd(250, "HELO %s", c.localName)
|
||||
return err
|
||||
}
|
||||
|
||||
// ehlo sends the EHLO (extended hello) greeting to the server. It
|
||||
// should be the preferred greeting for servers that support it.
|
||||
func (c *smtpClient) ehlo() error {
|
||||
_, msg, err := c.cmd(250, "EHLO %s", c.localName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ext := make(map[string]string)
|
||||
extList := strings.Split(msg, "\n")
|
||||
if len(extList) > 1 {
|
||||
extList = extList[1:]
|
||||
for _, line := range extList {
|
||||
args := strings.SplitN(line, " ", 2)
|
||||
if len(args) > 1 {
|
||||
ext[args[0]] = args[1]
|
||||
} else {
|
||||
ext[args[0]] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
if mechs, ok := ext["AUTH"]; ok {
|
||||
c.a = strings.Split(mechs, " ")
|
||||
}
|
||||
c.ext = ext
|
||||
return err
|
||||
}
|
||||
|
||||
// startTLS sends the STARTTLS command and encrypts all further communication.
|
||||
// Only servers that advertise the STARTTLS extension support this function.
|
||||
func (c *smtpClient) startTLS(config *tls.Config) error {
|
||||
if err := c.hello(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, err := c.cmd(220, "STARTTLS")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.conn = tls.Client(c.conn, config)
|
||||
c.text = textproto.NewConn(c.conn)
|
||||
c.tls = true
|
||||
return c.ehlo()
|
||||
}
|
||||
|
||||
// authenticate authenticates a client using the provided authentication mechanism.
|
||||
// A failed authentication closes the connection.
|
||||
// Only servers that advertise the AUTH extension support this function.
|
||||
func (c *smtpClient) authenticate(a auth) error {
|
||||
if err := c.hello(); err != nil {
|
||||
return err
|
||||
}
|
||||
encoding := base64.StdEncoding
|
||||
mech, resp, err := a.start(&serverInfo{c.serverName, c.tls, c.a})
|
||||
if err != nil {
|
||||
c.quit()
|
||||
return err
|
||||
}
|
||||
resp64 := make([]byte, encoding.EncodedLen(len(resp)))
|
||||
encoding.Encode(resp64, resp)
|
||||
code, msg64, err := c.cmd(0, strings.TrimSpace(fmt.Sprintf("AUTH %s %s", mech, resp64)))
|
||||
for err == nil {
|
||||
var msg []byte
|
||||
switch code {
|
||||
case 334:
|
||||
msg, err = encoding.DecodeString(msg64)
|
||||
case 235:
|
||||
// the last message isn't base64 because it isn't a challenge
|
||||
msg = []byte(msg64)
|
||||
default:
|
||||
err = &textproto.Error{Code: code, Msg: msg64}
|
||||
}
|
||||
if err == nil {
|
||||
resp, err = a.next(msg, code == 334)
|
||||
}
|
||||
if err != nil {
|
||||
// abort the AUTH
|
||||
c.cmd(501, "*")
|
||||
c.quit()
|
||||
break
|
||||
}
|
||||
if resp == nil {
|
||||
break
|
||||
}
|
||||
resp64 = make([]byte, encoding.EncodedLen(len(resp)))
|
||||
encoding.Encode(resp64, resp)
|
||||
code, msg64, err = c.cmd(0, string(resp64))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// mail issues a MAIL command to the server using the provided email address.
|
||||
// If the server supports the 8BITMIME extension, Mail adds the BODY=8BITMIME
|
||||
// parameter.
|
||||
// If the server supports the SMTPUTF8 extension, Mail adds the
|
||||
// SMTPUTF8 parameter.
|
||||
// This initiates a mail transaction and is followed by one or more Rcpt calls.
|
||||
func (c *smtpClient) mail(from string, extArgs ...map[string]string) error {
|
||||
var args []interface{}
|
||||
var extMap map[string]string
|
||||
|
||||
if len(extArgs) > 0 {
|
||||
extMap = extArgs[0]
|
||||
}
|
||||
|
||||
if err := validateLine(from); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.hello(); err != nil {
|
||||
return err
|
||||
}
|
||||
cmdStr := "MAIL FROM:<%s>"
|
||||
if c.ext != nil {
|
||||
if _, ok := c.ext["8BITMIME"]; ok {
|
||||
cmdStr += " BODY=8BITMIME"
|
||||
}
|
||||
if _, ok := c.ext["SMTPUTF8"]; ok {
|
||||
cmdStr += " SMTPUTF8"
|
||||
}
|
||||
if _, ok := c.ext["SIZE"]; ok {
|
||||
if extMap["SIZE"] != "" {
|
||||
cmdStr += " SIZE=%s"
|
||||
args = append(args, extMap["SIZE"])
|
||||
}
|
||||
}
|
||||
}
|
||||
args = append([]interface{}{from}, args...)
|
||||
_, _, err := c.cmd(250, cmdStr, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
// rcpt issues a RCPT command to the server using the provided email address.
|
||||
// A call to Rcpt must be preceded by a call to Mail and may be followed by
|
||||
// a Data call or another Rcpt call.
|
||||
func (c *smtpClient) rcpt(to string) error {
|
||||
if err := validateLine(to); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, err := c.cmd(25, "RCPT TO:<%s>", to)
|
||||
return err
|
||||
}
|
||||
|
||||
type dataCloser struct {
|
||||
c *smtpClient
|
||||
io.WriteCloser
|
||||
}
|
||||
|
||||
func (d *dataCloser) Close() error {
|
||||
d.WriteCloser.Close()
|
||||
_, _, err := d.c.text.ReadResponse(250)
|
||||
return err
|
||||
}
|
||||
|
||||
// data issues a DATA command to the server and returns a writer that
|
||||
// can be used to write the mail headers and body. The caller should
|
||||
// close the writer before calling any more methods on c. A call to
|
||||
// Data must be preceded by one or more calls to Rcpt.
|
||||
func (c *smtpClient) data() (io.WriteCloser, error) {
|
||||
_, _, err := c.cmd(354, "DATA")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dataCloser{c, c.text.DotWriter()}, nil
|
||||
}
|
||||
|
||||
// extension reports whether an extension is support by the server.
|
||||
// The extension name is case-insensitive. If the extension is supported,
|
||||
// extension also returns a string that contains any parameters the
|
||||
// server specifies for the extension.
|
||||
func (c *smtpClient) extension(ext string) (bool, string) {
|
||||
if err := c.hello(); err != nil {
|
||||
return false, ""
|
||||
}
|
||||
if c.ext == nil {
|
||||
return false, ""
|
||||
}
|
||||
ext = strings.ToUpper(ext)
|
||||
param, ok := c.ext[ext]
|
||||
return ok, param
|
||||
}
|
||||
|
||||
// reset sends the RSET command to the server, aborting the current mail
|
||||
// transaction.
|
||||
func (c *smtpClient) reset() error {
|
||||
if err := c.hello(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, err := c.cmd(250, "RSET")
|
||||
return err
|
||||
}
|
||||
|
||||
// noop sends the NOOP command to the server. It does nothing but check
|
||||
// that the connection to the server is okay.
|
||||
func (c *smtpClient) noop() error {
|
||||
if err := c.hello(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, err := c.cmd(250, "NOOP")
|
||||
return err
|
||||
}
|
||||
|
||||
// quit sends the QUIT command and closes the connection to the server.
|
||||
func (c *smtpClient) quit() error {
|
||||
if err := c.hello(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, err := c.cmd(221, "QUIT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.text.Close()
|
||||
}
|
||||
|
||||
// validateLine checks to see if a line has CR or LF as per RFC 5321
|
||||
func validateLine(line string) error {
|
||||
if strings.ContainsAny(line, "\n\r") {
|
||||
return errors.New("smtp: A line must not contain CR or LF")
|
||||
}
|
||||
return nil
|
||||
}
|
1064
smtp_test.go
Normal file
1064
smtp_test.go
Normal file
File diff suppressed because it is too large
Load Diff
1
testdata/foo.txt
vendored
Normal file
1
testdata/foo.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
foo
|
Loading…
Reference in New Issue
Block a user