pax_global_header 0000666 0000000 0000000 00000000064 14477124731 0014524 g ustar 00root root 0000000 0000000 52 comment=e4becab738bdf51e32a18cd6c8843f1d59c96e34
golang-github-xhit-go-simple-mail-2.16.0/ 0000775 0000000 0000000 00000000000 14477124731 0020125 5 ustar 00root root 0000000 0000000 golang-github-xhit-go-simple-mail-2.16.0/.gitattributes 0000664 0000000 0000000 00000000566 14477124731 0023027 0 ustar 00root root 0000000 0000000 # 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
golang-github-xhit-go-simple-mail-2.16.0/.github/ 0000775 0000000 0000000 00000000000 14477124731 0021465 5 ustar 00root root 0000000 0000000 golang-github-xhit-go-simple-mail-2.16.0/.github/FUNDING.yml 0000664 0000000 0000000 00000001122 14477124731 0023276 0 ustar 00root root 0000000 0000000 # These are supported funding model platforms
github: [xhit]
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']
golang-github-xhit-go-simple-mail-2.16.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14477124731 0023522 5 ustar 00root root 0000000 0000000 golang-github-xhit-go-simple-mail-2.16.0/.github/workflows/codeql-analysis.yml 0000664 0000000 0000000 00000001233 14477124731 0027334 0 ustar 00root root 0000000 0000000 name: "CodeQL"
on:
push:
branches: [master]
pull_request:
branches: [master]
schedule:
- cron: '0 6 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: ['go']
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
golang-github-xhit-go-simple-mail-2.16.0/.github/workflows/go.yml 0000664 0000000 0000000 00000001556 14477124731 0024661 0 ustar 00root root 0000000 0000000 on: [push, pull_request]
name: build
jobs:
test:
strategy:
matrix:
go-version: [1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x]
os: [ubuntu-latest, macos-latest, windows-latest]
targetplatform: [x86, x64]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v3
- name: Get dependencies
run: |
env GO111MODULE=on go vet ./...
- name: Build
run: go build -v .
- name: Test
run: env GO111MODULE=on go test -v -timeout 30m -race -coverprofile='coverage.txt' -covermode=atomic
- name: Codecov
uses: codecov/codecov-action@v3
with:
file: coverage.txt
flags: unittests
name: codecov-umbrella
golang-github-xhit-go-simple-mail-2.16.0/.gitignore 0000664 0000000 0000000 00000000526 14477124731 0022120 0 ustar 00root root 0000000 0000000 # 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
# Go coverage file
coverage.txt golang-github-xhit-go-simple-mail-2.16.0/.golangci.yml 0000664 0000000 0000000 00000002604 14477124731 0022513 0 ustar 00root root 0000000 0000000 # 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
golang-github-xhit-go-simple-mail-2.16.0/LICENSE 0000664 0000000 0000000 00000002075 14477124731 0021136 0 ustar 00root root 0000000 0000000 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. golang-github-xhit-go-simple-mail-2.16.0/README.md 0000664 0000000 0000000 00000024467 14477124731 0021421 0 ustar 00root root 0000000 0000000 # Go Simple Mail
The best way to send emails in Go with SMTP Keep Alive and Timeout for Connect and Send.
# 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)
- AUTO Authentication (since v2.14.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)
- Support to send using custom connection, ideal for proxy (since v2.15.0)
- Support Delivery Status Notification (DSN) (since v2.16.0)
- Support text/x-amp-html content type body (since v2.16.0)
## Documentation
https://pkg.go.dev/github.com/xhit/go-simple-mail/v2?tab=doc
Note 1: by default duplicated recipients throws an error, from `v2.13.0` you can use `email.AllowDuplicateAddress = true` to avoid the check.
Note 2: by default Bcc header is not set in email. From `v2.14.0` you can use `email.AddBccToHeader = true` to add this.
## Download
This package uses go modules.
```console
$ go get github.com/xhit/go-simple-mail/v2
```
# Usage
```go
package main
import (
"log"
mail "github.com/xhit/go-simple-mail/v2"
"github.com/toorop/go-dkim"
)
const htmlBody = `
Hello Gophers!
This is the Go gopher.

Image created by Renee French
`
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
// You can specified authentication type:
// - AUTO (default)
// - PLAIN
// - LOGIN
// - CRAM-MD5
// - None
// server.Authentication = mail.AuthAuto
// 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 ").
AddTo("xhit@example.com").
AddCc("otherto@example.com").
SetSubject("New Go Email").
SetListUnsubscribe("")
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})
// also you can set Delivery Status Notification (DSN) (only is set when server supports DSN)
email.SetDSN([]mail.DSN{mail.SUCCESS, mail.FAILURE}, false)
// 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 ").
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")
}
}
```
# Send with custom connection
It's possible to use a custom connection with custom dieler, like a dialer that uses a proxy server, etc...
With this, these servers params are ignored: `Host`, `Port`. You have total control of the connection.
Example using a conn for proxy:
```go
package main
import (
"crypto/tls"
"fmt"
"log"
"net"
mail "github.com/xhit/go-simple-mail/v2"
"golang.org/x/net/proxy"
)
func main() {
server := mail.NewSMTPClient()
host := "smtp.example.com"
port := 587
proxyAddr := "proxy.server"
conn, err := getCustomConnWithProxy(proxyAddr, host, port)
if err != nil {
log.Fatal(err)
}
server.Username = "test@example.com"
server.Password = "examplepass"
server.Encryption = mail.EncryptionSTARTTLS
server.CustomConn = conn
smtpClient, err := server.Connect()
if err != nil {
log.Fatal(err)
}
email := mail.NewMSG()
err = email.SetFrom("From Example ").AddTo("xhit@example.com").Send(smtpClient)
if err != nil {
log.Fatal(err)
}
}
func getCustomConnWithProxy(proxyAddr, host string, port int) (net.Conn, error) {
dial, err := proxy.SOCKS5("tcp", proxyAddr, nil, proxy.Direct)
if err != nil {
return nil, err
}
dialer, err := dial.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil {
return nil, err
}
conf := &tls.Config{ServerName: host}
conn := tls.Client(dialer, conf)
return conn, nil
}
```
## More examples
See [example/example_test.go](example/example_test.go).
golang-github-xhit-go-simple-mail-2.16.0/attach.go 0000664 0000000 0000000 00000007325 14477124731 0021727 0 ustar 00root root 0000000 0000000 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)
}
}
golang-github-xhit-go-simple-mail-2.16.0/attach_old.go 0000664 0000000 0000000 00000004706 14477124731 0022565 0 ustar 00root root 0000000 0000000 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})
}
golang-github-xhit-go-simple-mail-2.16.0/attach_test.go 0000664 0000000 0000000 00000006515 14477124731 0022766 0 ustar 00root root 0000000 0000000 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)
})
}
golang-github-xhit-go-simple-mail-2.16.0/auth.go 0000664 0000000 0000000 00000011401 14477124731 0021412 0 ustar 00root root 0000000 0000000 // 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
}
golang-github-xhit-go-simple-mail-2.16.0/email.go 0000664 0000000 0000000 00000064215 14477124731 0021553 0 ustar 00root root 0000000 0000000 package mail
import (
"bytes"
"crypto/tls"
"errors"
"fmt"
"net"
"net/mail"
"net/textproto"
"strconv"
"strings"
"sync"
"time"
"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
AllowDuplicateAddress bool
AddBccToHeader bool
preserveOriginalRecipient bool
dsn []DSN
}
/*
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
// use custom dialer
CustomConn net.Conn
}
// SMTPClient represents a SMTP Client for send email
type SMTPClient struct {
mu sync.Mutex
Client *smtpClient
SendTimeout time.Duration
KeepAlive bool
hasDSNExt bool
preserveOriginalRecipient bool
dsn []DSN
}
// 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
// TextAMP sets body type to text/x-amp-html in message body
TextAMP
)
var contentTypes = [...]string{"text/plain", "text/html", "text/calendar", "text/x-amp-html"}
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
// AuthAuto (default) use the first AuthType of the list of returned types supported by SMTP
AuthAuto
)
func (at AuthType) String() string {
switch at {
case AuthPlain:
return "PLAIN"
case AuthLogin:
return "LOGIN"
case AuthCRAMMD5:
return "CRAM-MD5"
default:
return ""
}
}
/*
DSN notifications
- 'NEVER' under no circumstances a DSN must be returned to the sender. If you use NEVER all other notifications will be ignored.
- 'SUCCESS' will notify you when your mail has arrived at its destination.
- 'FAILURE' will arrive if an error occurred during delivery.
- 'DELAY' will notify you if there is an unusual delay in delivery, but the actual delivery's outcome (success or failure) is not yet decided.
see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY
*/
type DSN int
const (
NEVER DSN = iota
FAILURE
DELAY
SUCCESS
)
var dsnTypes = [...]string{"NEVER", "FAILURE", "DELAY", "SUCCESS"}
func (dsn DSN) String() string {
return dsnTypes[dsn]
}
// 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: AuthAuto,
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, email.AllowDuplicateAddress)
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 Bcc only if AddBccToHeader is true
if header == "Bcc" && email.AddBccToHeader {
email.headers.Add(header, address.String())
}
// 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, allowDuplicateAddress bool) ([]string, error) {
if !allowDuplicateAddress {
// 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
}
// SetDSN sets the delivery status notification list, only is set when SMTP server supports DSN extension
//
// To preserve the original recipient of an email message, for example, if it is forwarded to another address, set preserveOriginalRecipient to true
func (email *Email) SetDSN(dsn []DSN, preserveOriginalRecipient bool) *Email {
if email.Error != nil {
return email
}
email.dsn = dsn
email.preserveOriginalRecipient = preserveOriginalRecipient
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()
}
client.dsn = email.dsn
client.preserveOriginalRecipient = email.preserveOriginalRecipient
return send(from, email.recipients, msg, client)
}
// dial connects to the smtp server with the request encryption type
func dial(customConn net.Conn, host string, port string, encryption Encryption, config *tls.Config) (*smtpClient, error) {
var conn net.Conn
var err error
var c *smtpClient
if customConn != nil {
conn = customConn
} else {
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(customConn net.Conn, host, port, helo string, encryption Encryption, config *tls.Config) (*smtpClient, error) {
// connect to the mail server
c, err := dial(customConn, 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)
}
}
}
return c, nil
}
func (server *SMTPServer) getAuth(a string) (auth, error) {
var afn auth
switch {
case strings.Contains(a, AuthPlain.String()):
if server.Username != "" || server.Password != "" {
afn = plainAuthfn("", server.Username, server.Password, server.Host)
}
case strings.Contains(a, AuthLogin.String()):
if server.Username != "" || server.Password != "" {
afn = loginAuthfn("", server.Username, server.Password, server.Host)
}
case strings.Contains(a, AuthCRAMMD5.String()):
if server.Username != "" || server.Password != "" {
afn = cramMD5Authfn(server.Username, server.Password)
}
default:
return nil, fmt.Errorf("Mail Error on determining auth type, %s is not supported", a)
}
return afn, nil
}
func (server *SMTPServer) validateAuth(c *smtpClient) error {
var err error
var afn auth
switch {
case server.Authentication == AuthNone || server.Username == "":
return nil
case server.Authentication != AuthAuto:
afn, err = server.getAuth(server.Authentication.String())
if err != nil {
return err
}
}
if ok, a := c.extension("AUTH"); ok {
// Determine Auth type automatically from extension
if afn == nil {
afn, err = server.getAuth(a)
if err != nil {
return err
}
}
if err = c.authenticate(afn); err != nil {
c.close()
return fmt.Errorf("Mail Error on Auth: %w", err)
}
}
return nil
}
// Connect returns the smtp client
func (server *SMTPServer) Connect() (*SMTPClient, error) {
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.CustomConn, server.Host, fmt.Sprintf("%d", server.Port), server.Helo, 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.CustomConn, server.Host, fmt.Sprintf("%d", server.Port), server.Helo, server.Encryption, tlsConfig)
if err != nil {
return nil, err
}
}
_, hasDSN := c.ext["DSN"]
return &SMTPClient{
Client: c,
KeepAlive: server.KeepAlive,
SendTimeout: server.SendTimeout,
hasDSNExt: hasDSN,
}, server.validateAuth(c)
}
// Reset send RSET command to smtp client
func (smtpClient *SMTPClient) Reset() error {
smtpClient.mu.Lock()
defer smtpClient.mu.Unlock()
return smtpClient.Client.reset()
}
// Noop send NOOP command to smtp client
func (smtpClient *SMTPClient) Noop() error {
smtpClient.mu.Lock()
defer smtpClient.mu.Unlock()
return smtpClient.Client.noop()
}
// Quit send QUIT command to smtp client
func (smtpClient *SMTPClient) Quit() error {
smtpClient.mu.Lock()
defer smtpClient.mu.Unlock()
return smtpClient.Client.quit()
}
// Close closes the connection
func (smtpClient *SMTPClient) Close() error {
smtpClient.mu.Lock()
defer smtpClient.mu.Unlock()
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, client *SMTPClient) {
smtpSendChannel <- sendMailProcess(from, to, msg, client)
}(from, to, msg, client)
}
if client.SendTimeout == 0 {
// no SendTimeout, just fire the sendMailProcess
return sendMailProcess(from, to, msg, 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 {
c.mu.Lock()
defer c.mu.Unlock()
cmdArgs := make(map[string]string)
if _, ok := c.Client.ext["SIZE"]; ok {
cmdArgs["SIZE"] = strconv.Itoa(len(msg))
}
// Set the sender
if err := c.Client.mail(from, cmdArgs); err != nil {
return err
}
var dsn string
var dsnSet bool
if c.hasDSNExt && len(c.dsn) > 0 {
dsn = " NOTIFY="
if hasNeverDSN(c.dsn) {
dsn += NEVER.String()
} else {
dsn += strings.Join(dsnToString(c.dsn), ",")
}
if c.preserveOriginalRecipient {
dsn += " ORCPT=rfc822;"
}
dsnSet = true
}
// Set the recipients
for _, address := range to {
if dsnSet && c.preserveOriginalRecipient {
dsn += address
}
if err := c.Client.rcpt(address, dsn); err != nil {
return err
}
}
// Send the data command
w, err := c.Client.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.Reset()
} else {
client.Quit()
client.Close()
}
}
func hasNeverDSN(dsnList []DSN) bool {
for i := range dsnList {
if dsnList[i] == NEVER {
return true
}
}
return false
}
func dsnToString(dsnList []DSN) []string {
dsnString := make([]string, len(dsnList))
for i := range dsnList {
dsnString[i] = dsnList[i].String()
}
return dsnString
}
golang-github-xhit-go-simple-mail-2.16.0/email_test.go 0000664 0000000 0000000 00000004705 14477124731 0022610 0 ustar 00root root 0000000 0000000 package mail
import (
"fmt"
"log"
"net"
"testing"
"time"
)
func TestSendRace(t *testing.T) {
port := 56666
port2 := 56667
timeout := 1 * time.Second
responses := []string{
`220 test connected`,
`250 after helo`,
`250 after mail from`,
`250 after rcpt to`,
`354 after data`,
}
startService(port, responses, 5*time.Second)
startService(port2, responses, 0)
server := NewSMTPClient()
server.ConnectTimeout = timeout
server.SendTimeout = timeout
server.KeepAlive = false
server.Host = `127.0.0.1`
server.Port = port
smtpClient, err := server.Connect()
if err != nil {
log.Fatalf("couldn't connect: %s", err.Error())
}
defer smtpClient.Close()
// create another server in other port to test timeouts
server.Port = port2
smtpClient2, err := server.Connect()
if err != nil {
log.Fatalf("couldn't connect: %s", err.Error())
}
defer smtpClient2.Close()
msg := NewMSG().
SetFrom(`foo@bar`).
AddTo(`rcpt@bar`).
SetSubject("subject").
SetBody(TextPlain, "body")
// the smtpClient2 has not timeout
err = msg.Send(smtpClient2)
if err != nil {
log.Fatalf("couldn't send: %s", err.Error())
}
// the smtpClient send to listener with the last response is after SendTimeout, so when this error is returned the test succeed.
err = msg.Send(smtpClient)
if err != nil && err.Error() != "Mail Error: SMTP Send timed out" {
log.Fatalf("couldn't send: %s", err.Error())
}
}
func startService(port int, responses []string, timeout time.Duration) {
log.Printf("starting service at %d...\n", port)
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
log.Fatalf("couldn't listen to port %d: %s", port, err)
}
go func() {
for {
conn, err := listener.Accept()
if err != nil {
log.Fatalf("couldn't listen accept the request in port %d", port)
}
go respond(conn, responses, timeout)
}
}()
}
func respond(conn net.Conn, responses []string, timeout time.Duration) {
buf := make([]byte, 1024)
for _, resp := range responses {
write(conn, resp)
n, err := conn.Read(buf)
if err != nil {
log.Println("couldn't read data")
return
}
readStr := string(buf[:n])
log.Printf("READ:%s", string(readStr))
}
// if timeout, sleep for that time, otherwise sent a 250 OK
if timeout > 0 {
time.Sleep(timeout)
} else {
write(conn, "250 OK")
}
conn.Close()
fmt.Print("\n\n")
}
func write(conn net.Conn, command string) {
log.Printf("WRITE:%s", command)
conn.Write([]byte(command + "\n"))
}
golang-github-xhit-go-simple-mail-2.16.0/example/ 0000775 0000000 0000000 00000000000 14477124731 0021560 5 ustar 00root root 0000000 0000000 golang-github-xhit-go-simple-mail-2.16.0/example/example_test.go 0000664 0000000 0000000 00000013222 14477124731 0024601 0 ustar 00root root 0000000 0000000 package example
import (
"testing"
"time"
mail "github.com/xhit/go-simple-mail/v2"
)
// Some variables to connect and the body.
var (
htmlBody = `
Hello Gophers!
This is the Go gopher.

Image created by Renee French
`
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 ").
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 ").
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")
}
}
golang-github-xhit-go-simple-mail-2.16.0/go.mod 0000664 0000000 0000000 00000000174 14477124731 0021235 0 ustar 00root root 0000000 0000000 module github.com/xhit/go-simple-mail/v2
require (
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208
)
go 1.13
golang-github-xhit-go-simple-mail-2.16.0/go.sum 0000664 0000000 0000000 00000000656 14477124731 0021267 0 ustar 00root root 0000000 0000000 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.2.2+incompatible h1:Hm2VGfLqiQJ/NnC8SYsrPOPyVYIlvP2kmnotP4RIV74=
github.com/xhit/go-simple-mail v2.2.2+incompatible/go.mod h1:I8Ctg6vIJZ+Sv7k/22M6oeu/tbFumDY0uxBuuLbtU7Y=
golang-github-xhit-go-simple-mail-2.16.0/header.go 0000664 0000000 0000000 00000011251 14477124731 0021704 0 ustar 00root root 0000000 0000000 // 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'
}
golang-github-xhit-go-simple-mail-2.16.0/header_test.go 0000664 0000000 0000000 00000166637 14477124731 0022766 0 ustar 00root root 0000000 0000000 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)
}
}
}
golang-github-xhit-go-simple-mail-2.16.0/message.go 0000664 0000000 0000000 00000014364 14477124731 0022110 0 ustar 00root root 0000000 0000000 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)
}
}
golang-github-xhit-go-simple-mail-2.16.0/smtp.go 0000664 0000000 0000000 00000021603 14477124731 0021441 0 ustar 00root root 0000000 0000000 // 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, dsn string) error {
if err := validateLine(to); err != nil {
return err
}
_, _, err := c.cmd(25, "RCPT TO:<%s>%s", to, dsn)
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
}
golang-github-xhit-go-simple-mail-2.16.0/smtp_test.go 0000664 0000000 0000000 00000065416 14477124731 0022512 0 ustar 00root root 0000000 0000000 // 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 the LICENSE file.
package mail
import (
"bufio"
"bytes"
"crypto/tls"
"crypto/x509"
"io"
"net"
"net/textproto"
"strings"
"testing"
"time"
)
type authTest struct {
auth auth
challenges []string
name string
responses []string
}
var authTests = []authTest{
{plainAuthfn("", "user", "pass", "testserver"), []string{}, "PLAIN", []string{"\x00user\x00pass"}},
{plainAuthfn("foo", "bar", "baz", "testserver"), []string{}, "PLAIN", []string{"foo\x00bar\x00baz"}},
{loginAuthfn("", "bar", "baz", "testserver"), []string{}, "LOGIN", []string{"bar"}},
{loginAuthfn("foo", "bar", "baz", "testserver"), []string{}, "LOGIN", []string{"bar"}},
{cramMD5Authfn("user", "pass"), []string{"<123456.1322876914@testserver>"}, "CRAM-MD5", []string{"", "user 287eb355114cf5c471c26a875f1ca4ae"}},
}
func TestAuth(t *testing.T) {
testLoop:
for i, test := range authTests {
name, resp, err := test.auth.start(&serverInfo{"testserver", true, nil})
if name != test.name {
t.Errorf("#%d got name %s, expected %s", i, name, test.name)
}
if !bytes.Equal(resp, []byte(test.responses[0])) {
t.Errorf("#%d got response %s, expected %s", i, resp, test.responses[0])
}
if err != nil {
t.Errorf("#%d error: %s", i, err)
}
for j := range test.challenges {
challenge := []byte(test.challenges[j])
expected := []byte(test.responses[j+1])
resp, err := test.auth.next(challenge, true)
if err != nil {
t.Errorf("#%d error: %s", i, err)
continue testLoop
}
if !bytes.Equal(resp, expected) {
t.Errorf("#%d got %s, expected %s", i, resp, expected)
continue testLoop
}
}
}
}
func TestAuthPlain(t *testing.T) {
tests := []struct {
authName string
server *serverInfo
err string
}{
{
authName: "servername",
server: &serverInfo{name: "servername", tls: true},
},
{
// OK to use plainAuthfn on localhost without TLS
authName: "localhost",
server: &serverInfo{name: "localhost", tls: false},
},
{
authName: "servername",
server: &serverInfo{name: "attacker", tls: true},
err: "wrong host name",
},
}
for i, tt := range tests {
auth := plainAuthfn("foo", "bar", "baz", tt.authName)
_, _, err := auth.start(tt.server)
got := ""
if err != nil {
got = err.Error()
}
if got != tt.err {
t.Errorf("%d. got error = %q; want %q", i, got, tt.err)
}
}
}
func TestAuthLogin(t *testing.T) {
tests := []struct {
authName string
server *serverInfo
err string
}{
{
authName: "servername",
server: &serverInfo{name: "servername", tls: true},
},
{
// OK to use loginAuthfn on localhost without TLS
authName: "localhost",
server: &serverInfo{name: "localhost", tls: false},
},
{
authName: "servername",
server: &serverInfo{name: "attacker", tls: true},
err: "wrong host name",
},
}
for i, tt := range tests {
auth := loginAuthfn("foo", "bar", "baz", tt.authName)
_, _, err := auth.start(tt.server)
got := ""
if err != nil {
got = err.Error()
}
if got != tt.err {
t.Errorf("%d. got error = %q; want %q", i, got, tt.err)
}
}
}
// Issue https://github.com/golang/go/issues/17794: don't send a trailing space on AUTH command when there's no password.
func TestClientAuthTrimSpace(t *testing.T) {
server := "220 hello world\r\n" +
"200 some more"
var wrote bytes.Buffer
var fake faker
fake.ReadWriter = struct {
io.Reader
io.Writer
}{
strings.NewReader(server),
&wrote,
}
c, err := newClient(fake, "fake.host")
if err != nil {
t.Fatalf("NewClient: %v", err)
}
c.tls = true
c.didHello = true
c.authenticate(toServerEmptyAuth{})
c.close()
if got, want := wrote.String(), "AUTH FOOAUTH\r\n*\r\nQUIT\r\n"; got != want {
t.Errorf("wrote %q; want %q", got, want)
}
}
// toServerEmptyAuth is an implementation of Auth that only implements
// the Start method, and returns "FOOAUTH", nil, nil. Notably, it returns
// zero bytes for "toServer" so we can test that we don't send spaces at
// the end of the line. See TestClientAuthTrimSpace.
type toServerEmptyAuth struct{}
func (toServerEmptyAuth) start(server *serverInfo) (proto string, toServer []byte, err error) {
return "FOOAUTH", nil, nil
}
func (toServerEmptyAuth) next(fromServer []byte, more bool) (toServer []byte, err error) {
panic("unexpected call")
}
type faker struct {
io.ReadWriter
}
func (f faker) Close() error { return nil }
func (f faker) LocalAddr() net.Addr { return nil }
func (f faker) RemoteAddr() net.Addr { return nil }
func (f faker) SetDeadline(time.Time) error { return nil }
func (f faker) SetReadDeadline(time.Time) error { return nil }
func (f faker) SetWriteDeadline(time.Time) error { return nil }
func TestBasic(t *testing.T) {
server := strings.Join(strings.Split(basicServer, "\n"), "\r\n")
client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
var cmdbuf bytes.Buffer
bcmdbuf := bufio.NewWriter(&cmdbuf)
var fake faker
fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
c := &smtpClient{text: textproto.NewConn(fake), localName: "localhost"}
if err := c.helo(); err != nil {
t.Fatalf("HELO failed: %s", err)
}
if err := c.ehlo(); err == nil {
t.Fatalf("Expected first EHLO to fail")
}
if err := c.ehlo(); err != nil {
t.Fatalf("Second EHLO failed: %s", err)
}
c.didHello = true
if ok, args := c.extension("aUtH"); !ok || args != "LOGIN PLAIN" {
t.Fatalf("Expected AUTH supported")
}
if ok, _ := c.extension("DSN"); ok {
t.Fatalf("Shouldn't support DSN")
}
if err := c.mail("user@gmail.com"); err == nil {
t.Fatalf("MAIL should require authentication")
}
if err := c.verify("user1@gmail.com"); err == nil {
t.Fatalf("First VRFY: expected no verification")
}
if err := c.verify("user2@gmail.com>\r\nDATA\r\nAnother injected message body\r\n.\r\nQUIT\r\n"); err == nil {
t.Fatalf("VRFY should have failed due to a message injection attempt")
}
if err := c.verify("user2@gmail.com"); err != nil {
t.Fatalf("Second VRFY: expected verification, got %s", err)
}
// fake TLS so authentication won't complain
c.tls = true
c.serverName = "smtp.google.com"
if err := c.authenticate(plainAuthfn("", "user", "pass", "smtp.google.com")); err != nil {
t.Fatalf("AUTH failed: %s", err)
}
if err := c.rcpt("golang-nuts@googlegroups.com>\r\nDATA\r\nInjected message body\r\n.\r\nQUIT\r\n", ""); err == nil {
t.Fatalf("RCPT should have failed due to a message injection attempt")
}
if err := c.mail("user@gmail.com>\r\nDATA\r\nAnother injected message body\r\n.\r\nQUIT\r\n"); err == nil {
t.Fatalf("MAIL should have failed due to a message injection attempt")
}
if err := c.mail("user@gmail.com"); err != nil {
t.Fatalf("MAIL failed: %s", err)
}
if err := c.rcpt("golang-nuts@googlegroups.com", ""); err != nil {
t.Fatalf("RCPT failed: %s", err)
}
msg := `From: user@gmail.com
To: golang-nuts@googlegroups.com
Subject: Hooray for Go
Line 1
.Leading dot line .
Goodbye.`
w, err := c.data()
if err != nil {
t.Fatalf("DATA failed: %s", err)
}
if _, err := w.Write([]byte(msg)); err != nil {
t.Fatalf("Data write failed: %s", err)
}
if err := w.Close(); err != nil {
t.Fatalf("Bad data response: %s", err)
}
if err := c.quit(); err != nil {
t.Fatalf("QUIT failed: %s", err)
}
bcmdbuf.Flush()
actualcmds := cmdbuf.String()
if client != actualcmds {
t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
}
}
var basicServer = `250 mx.google.com at your service
502 Unrecognized command.
250-mx.google.com at your service
250-SIZE 35651584
250-AUTH LOGIN PLAIN
250 8BITMIME
530 Authentication required
252 Send some mail, I'll try my best
250 User is valid
235 Accepted
250 Sender OK
250 Receiver OK
354 Go ahead
250 Data OK
221 OK
`
var basicClient = `HELO localhost
EHLO localhost
EHLO localhost
MAIL FROM: BODY=8BITMIME
VRFY user1@gmail.com
VRFY user2@gmail.com
AUTH PLAIN AHVzZXIAcGFzcw==
MAIL FROM: BODY=8BITMIME
RCPT TO:
DATA
From: user@gmail.com
To: golang-nuts@googlegroups.com
Subject: Hooray for Go
Line 1
..Leading dot line .
Goodbye.
.
QUIT
`
func TestExtensions(t *testing.T) {
faker := func(server string) (c *smtpClient, bcmdbuf *bufio.Writer, cmdbuf *bytes.Buffer) {
server = strings.Join(strings.Split(server, "\n"), "\r\n")
cmdbuf = &bytes.Buffer{}
bcmdbuf = bufio.NewWriter(cmdbuf)
var fake faker
fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
c = &smtpClient{text: textproto.NewConn(fake), localName: "localhost"}
return c, bcmdbuf, cmdbuf
}
t.Run("helo", func(t *testing.T) {
const (
basicServer = `250 mx.google.com at your service
250 Sender OK
221 Goodbye
`
basicClient = `HELO localhost
MAIL FROM:
QUIT
`
)
c, bcmdbuf, cmdbuf := faker(basicServer)
if err := c.helo(); err != nil {
t.Fatalf("HELO failed: %s", err)
}
c.didHello = true
if err := c.mail("user@gmail.com"); err != nil {
t.Fatalf("MAIL FROM failed: %s", err)
}
if err := c.quit(); err != nil {
t.Fatalf("QUIT failed: %s", err)
}
bcmdbuf.Flush()
actualcmds := cmdbuf.String()
client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
if client != actualcmds {
t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
}
})
t.Run("ehlo", func(t *testing.T) {
const (
basicServer = `250-mx.google.com at your service
250 SIZE 35651584
250 Sender OK
221 Goodbye
`
basicClient = `EHLO localhost
MAIL FROM:
QUIT
`
)
c, bcmdbuf, cmdbuf := faker(basicServer)
if err := c.hi("localhost"); err != nil {
t.Fatalf("EHLO failed: %s", err)
}
if ok, _ := c.extension("8BITMIME"); ok {
t.Fatalf("Shouldn't support 8BITMIME")
}
if ok, _ := c.extension("SMTPUTF8"); ok {
t.Fatalf("Shouldn't support SMTPUTF8")
}
if err := c.mail("user@gmail.com"); err != nil {
t.Fatalf("MAIL FROM failed: %s", err)
}
if err := c.quit(); err != nil {
t.Fatalf("QUIT failed: %s", err)
}
bcmdbuf.Flush()
actualcmds := cmdbuf.String()
client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
if client != actualcmds {
t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
}
})
t.Run("ehlo size", func(t *testing.T) {
const (
basicServer = `250-mx.google.com at your service
250-SIZE 35651584
250 user@gmail.com OK; can accommodate 500000 byte message
250 Sender OK
221 Goodbye
`
basicClient = `EHLO localhost
MAIL FROM: SIZE=50000
QUIT
`
)
c, bcmdbuf, cmdbuf := faker(basicServer)
cmdArgs := make(map[string]string)
if err := c.hi("localhost"); err != nil {
t.Fatalf("EHLO failed: %s", err)
}
if ok, _ := c.extension("SIZE"); !ok {
t.Fatalf("Should support SIZE")
}
if ok, _ := c.extension("SMTPUTF8"); ok {
t.Fatalf("Shouldn't support SMTPUTF8")
}
if ok, _ := c.extension("SIZE"); ok {
cmdArgs["SIZE"] = "50000"
}
if err := c.mail("user@gmail.com", cmdArgs); err != nil {
t.Fatalf("MAIL FROM failed: %s", err)
}
if err := c.quit(); err != nil {
t.Fatalf("QUIT failed: %s", err)
}
bcmdbuf.Flush()
actualcmds := cmdbuf.String()
client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
if client != actualcmds {
t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
}
})
t.Run("ehlo 8bitmime", func(t *testing.T) {
const (
basicServer = `250-mx.google.com at your service
250-SIZE 35651584
250 8BITMIME
250 Sender OK
221 Goodbye
`
basicClient = `EHLO localhost
MAIL FROM: BODY=8BITMIME
QUIT
`
)
c, bcmdbuf, cmdbuf := faker(basicServer)
if err := c.hi("localhost"); err != nil {
t.Fatalf("EHLO failed: %s", err)
}
if ok, _ := c.extension("8BITMIME"); !ok {
t.Fatalf("Should support 8BITMIME")
}
if ok, _ := c.extension("SMTPUTF8"); ok {
t.Fatalf("Shouldn't support SMTPUTF8")
}
if err := c.mail("user@gmail.com"); err != nil {
t.Fatalf("MAIL FROM failed: %s", err)
}
if err := c.quit(); err != nil {
t.Fatalf("QUIT failed: %s", err)
}
bcmdbuf.Flush()
actualcmds := cmdbuf.String()
client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
if client != actualcmds {
t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
}
})
t.Run("ehlo smtputf8", func(t *testing.T) {
const (
basicServer = `250-mx.google.com at your service
250-SIZE 35651584
250 SMTPUTF8
250 Sender OK
221 Goodbye
`
basicClient = `EHLO localhost
MAIL FROM: SMTPUTF8
QUIT
`
)
c, bcmdbuf, cmdbuf := faker(basicServer)
if err := c.hi("localhost"); err != nil {
t.Fatalf("EHLO failed: %s", err)
}
if ok, _ := c.extension("8BITMIME"); ok {
t.Fatalf("Shouldn't support 8BITMIME")
}
if ok, _ := c.extension("SMTPUTF8"); !ok {
t.Fatalf("Should support SMTPUTF8")
}
if err := c.mail("user+📧@gmail.com"); err != nil {
t.Fatalf("MAIL FROM failed: %s", err)
}
if err := c.quit(); err != nil {
t.Fatalf("QUIT failed: %s", err)
}
bcmdbuf.Flush()
actualcmds := cmdbuf.String()
client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
if client != actualcmds {
t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
}
})
t.Run("ehlo 8bitmime smtputf8", func(t *testing.T) {
const (
basicServer = `250-mx.google.com at your service
250-SIZE 35651584
250-8BITMIME
250 SMTPUTF8
250 Sender OK
221 Goodbye
`
basicClient = `EHLO localhost
MAIL FROM: BODY=8BITMIME SMTPUTF8
QUIT
`
)
c, bcmdbuf, cmdbuf := faker(basicServer)
if err := c.hi("localhost"); err != nil {
t.Fatalf("EHLO failed: %s", err)
}
c.didHello = true
if ok, _ := c.extension("8BITMIME"); !ok {
t.Fatalf("Should support 8BITMIME")
}
if ok, _ := c.extension("SMTPUTF8"); !ok {
t.Fatalf("Should support SMTPUTF8")
}
if err := c.mail("user+📧@gmail.com"); err != nil {
t.Fatalf("MAIL FROM failed: %s", err)
}
if err := c.quit(); err != nil {
t.Fatalf("QUIT failed: %s", err)
}
bcmdbuf.Flush()
actualcmds := cmdbuf.String()
client := strings.Join(strings.Split(basicClient, "\n"), "\r\n")
if client != actualcmds {
t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
}
})
}
func TestNewClient(t *testing.T) {
server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n")
client := strings.Join(strings.Split(newClientClient, "\n"), "\r\n")
var cmdbuf bytes.Buffer
bcmdbuf := bufio.NewWriter(&cmdbuf)
out := func() string {
bcmdbuf.Flush()
return cmdbuf.String()
}
var fake faker
fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
c, err := newClient(fake, "fake.host")
if err != nil {
t.Fatalf("NewClient: %v\n(after %v)", err, out())
}
defer c.close()
if ok, args := c.extension("aUtH"); !ok || args != "LOGIN PLAIN" {
t.Fatalf("Expected AUTH supported")
}
if ok, _ := c.extension("DSN"); ok {
t.Fatalf("Shouldn't support DSN")
}
if err := c.quit(); err != nil {
t.Fatalf("QUIT failed: %s", err)
}
actualcmds := out()
if client != actualcmds {
t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
}
}
var newClientServer = `220 hello world
250-mx.google.com at your service
250-SIZE 35651584
250-AUTH LOGIN PLAIN
250 8BITMIME
221 OK
`
var newClientClient = `EHLO localhost
QUIT
`
func TestNewClient2(t *testing.T) {
server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n")
client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n")
var cmdbuf bytes.Buffer
bcmdbuf := bufio.NewWriter(&cmdbuf)
var fake faker
fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
c, err := newClient(fake, "fake.host")
if err != nil {
t.Fatalf("NewClient: %v", err)
}
defer c.close()
if ok, _ := c.extension("DSN"); ok {
t.Fatalf("Shouldn't support DSN")
}
if err := c.quit(); err != nil {
t.Fatalf("QUIT failed: %s", err)
}
bcmdbuf.Flush()
actualcmds := cmdbuf.String()
if client != actualcmds {
t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client)
}
}
var newClient2Server = `220 hello world
502 EH?
250-mx.google.com at your service
250-SIZE 35651584
250-AUTH LOGIN PLAIN
250 8BITMIME
221 OK
`
var newClient2Client = `EHLO localhost
HELO localhost
QUIT
`
func TestNewClientWithTLS(t *testing.T) {
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
if err != nil {
t.Fatalf("loadcert: %v", err)
}
config := tls.Config{Certificates: []tls.Certificate{cert}}
ln, err := tls.Listen("tcp", "127.0.0.1:0", &config)
if err != nil {
ln, err = tls.Listen("tcp", "[::1]:0", &config)
if err != nil {
t.Fatalf("server: listen: %v", err)
}
}
go func() {
conn, errAccept := ln.Accept()
if errAccept != nil {
t.Errorf("server: accept: %v", errAccept)
return
}
defer conn.Close()
_, errWrite := conn.Write([]byte("220 SIGNS\r\n"))
if errWrite != nil {
t.Errorf("server: write: %v", errWrite)
return
}
}()
config.InsecureSkipVerify = true
conn, err := tls.Dial("tcp", ln.Addr().String(), &config)
if err != nil {
t.Fatalf("client: dial: %v", err)
}
defer conn.Close()
client, err := newClient(conn, ln.Addr().String())
if err != nil {
t.Fatalf("smtp: newclient: %v", err)
}
if !client.tls {
t.Errorf("client.tls Got: %t Expected: %t", client.tls, true)
}
}
func TestHello(t *testing.T) {
if len(helloServer) != len(helloClient) {
t.Fatalf("Hello server and client size mismatch")
}
for i := 0; i < len(helloServer); i++ {
server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n")
client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n")
var cmdbuf bytes.Buffer
bcmdbuf := bufio.NewWriter(&cmdbuf)
var fake faker
fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
c, err := newClient(fake, "fake.host")
if err != nil {
t.Fatalf("NewClient: %v", err)
}
defer c.close()
c.localName = "customhost"
err = nil
switch i {
case 0:
err = c.hi("hostinjection>\n\rDATA\r\nInjected message body\r\n.\r\nQUIT\r\n")
if err == nil {
t.Errorf("Expected Hello to be rejected due to a message injection attempt")
}
err = c.hi("customhost")
case 1:
err = c.startTLS(nil)
if err.Error() == "502 Not implemented" {
err = nil
}
case 2:
err = c.verify("test@example.com")
case 3:
c.tls = true
c.serverName = "smtp.google.com"
err = c.authenticate(plainAuthfn("", "user", "pass", "smtp.google.com"))
case 4:
err = c.mail("test@example.com")
case 5:
ok, _ := c.extension("feature")
if ok {
t.Errorf("Expected FEATURE not to be supported")
}
case 6:
err = c.reset()
case 7:
err = c.quit()
case 8:
err = c.verify("test@example.com")
if err != nil {
err = c.hi("customhost")
if err != nil {
t.Errorf("Want error, got none")
}
}
case 9:
err = c.noop()
default:
t.Fatalf("Unhandled command")
}
if err != nil {
t.Errorf("Command %d failed: %v", i, err)
}
bcmdbuf.Flush()
actualcmds := cmdbuf.String()
if client != actualcmds {
t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
}
}
}
// verify checks the validity of an email address on the server.
// If verify returns nil, the address is valid. A non-nil return
// does not necessarily indicate an invalid address. Many servers
// will not verify addresses for security reasons.
func (c *smtpClient) verify(addr string) error {
if err := validateLine(addr); err != nil {
return err
}
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(250, "VRFY %s", addr)
return err
}
var baseHelloServer = `220 hello world
502 EH?
250-mx.google.com at your service
250 FEATURE
`
var helloServer = []string{
"",
"502 Not implemented\n",
"250 User is valid\n",
"235 Accepted\n",
"250 Sender ok\n",
"",
"250 Reset ok\n",
"221 Goodbye\n",
"250 Sender ok\n",
"250 ok\n",
}
var baseHelloClient = `EHLO customhost
HELO customhost
`
var helloClient = []string{
"",
"STARTTLS\n",
"VRFY test@example.com\n",
"AUTH PLAIN AHVzZXIAcGFzcw==\n",
"MAIL FROM:\n",
"",
"RSET\n",
"QUIT\n",
"VRFY test@example.com\n",
"NOOP\n",
}
func TestAuthFailed(t *testing.T) {
server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n")
client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n")
var cmdbuf bytes.Buffer
bcmdbuf := bufio.NewWriter(&cmdbuf)
var fake faker
fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
c, err := newClient(fake, "fake.host")
if err != nil {
t.Fatalf("NewClient: %v", err)
}
defer c.close()
c.tls = true
c.serverName = "smtp.google.com"
err = c.authenticate(plainAuthfn("", "user", "pass", "smtp.google.com"))
if err == nil {
t.Error("Auth: expected error; got none")
} else if err.Error() != "535 Invalid credentials\nplease see www.example.com" {
t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com")
}
bcmdbuf.Flush()
actualcmds := cmdbuf.String()
if client != actualcmds {
t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client)
}
}
var authFailedServer = `220 hello world
250-mx.google.com at your service
250 AUTH LOGIN PLAIN
535-Invalid credentials
535 please see www.example.com
221 Goodbye
`
var authFailedClient = `EHLO localhost
AUTH PLAIN AHVzZXIAcGFzcw==
*
QUIT
`
func TestTLSConnState(t *testing.T) {
ln := newLocalListener(t)
defer ln.Close()
clientDone := make(chan bool)
serverDone := make(chan bool)
go func() {
defer close(serverDone)
c, err := ln.Accept()
if err != nil {
t.Errorf("Server accept: %v", err)
return
}
defer c.Close()
if err := serverHandle(c, t); err != nil {
t.Errorf("server error: %v", err)
}
}()
go func() {
defer close(clientDone)
c, err := dialer(ln.Addr().String())
if err != nil {
t.Errorf("Client dial: %v", err)
return
}
defer c.quit()
cfg := &tls.Config{ServerName: "example.com"}
testHookStartTLS(cfg) // set the RootCAs
if err := c.startTLS(cfg); err != nil {
t.Errorf("StartTLS: %v", err)
return
}
cs, ok := c.tlsConnectionState()
if !ok {
t.Errorf("TLSConnectionState returned ok == false; want true")
return
}
if cs.Version == 0 || !cs.HandshakeComplete {
t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs)
}
}()
<-clientDone
<-serverDone
}
var testHookStartTLS func(*tls.Config)
// dialer returns a new Client connected to an SMTP server at addr.
// The addr must include a port, as in "mail.example.com:smtp".
func dialer(addr string) (*smtpClient, error) {
conn, err := net.Dial("tcp", addr)
if err != nil {
return nil, err
}
host, _, _ := net.SplitHostPort(addr)
return newClient(conn, host)
}
// TLSConnectionState returns the client's TLS connection state.
// The return values are their zero values if StartTLS did
// not succeed.
func (c *smtpClient) tlsConnectionState() (state tls.ConnectionState, ok bool) {
tc, ok := c.conn.(*tls.Conn)
if !ok {
return
}
return tc.ConnectionState(), true
}
func newLocalListener(t *testing.T) net.Listener {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
ln, err = net.Listen("tcp6", "[::1]:0")
}
if err != nil {
t.Fatal(err)
}
return ln
}
type smtpSender struct {
w io.Writer
}
func (s smtpSender) send(f string) {
s.w.Write([]byte(f + "\r\n"))
}
// smtp server, finely tailored to deal with our own client only!
func serverHandle(c net.Conn, t *testing.T) error {
send := smtpSender{c}.send
send("220 127.0.0.1 ESMTP service ready")
s := bufio.NewScanner(c)
for s.Scan() {
switch s.Text() {
case "EHLO localhost":
send("250-127.0.0.1 ESMTP offers a warm hug of welcome")
send("250-STARTTLS")
send("250 Ok")
case "STARTTLS":
send("220 Go ahead")
keypair, err := tls.X509KeyPair(localhostCert, localhostKey)
if err != nil {
return err
}
config := &tls.Config{Certificates: []tls.Certificate{keypair}}
c = tls.Server(c, config)
defer c.Close()
return serverHandleTLS(c, t)
default:
t.Fatalf("unrecognized command: %q", s.Text())
}
}
return s.Err()
}
func serverHandleTLS(c net.Conn, t *testing.T) error {
send := smtpSender{c}.send
s := bufio.NewScanner(c)
for s.Scan() {
switch s.Text() {
case "EHLO localhost":
send("250 Ok")
case "MAIL FROM:":
send("250 Ok")
case "RCPT TO:":
send("250 Ok")
case "DATA":
send("354 send the mail data, end with .")
send("250 Ok")
case "Subject: test":
case "":
case "howdy!":
case ".":
case "QUIT":
send("221 127.0.0.1 Service closing transmission channel")
return nil
default:
t.Fatalf("unrecognized command during TLS: %q", s.Text())
}
}
return s.Err()
}
func init() {
testRootCAs := x509.NewCertPool()
testRootCAs.AppendCertsFromPEM(localhostCert)
testHookStartTLS = func(config *tls.Config) {
config.RootCAs = testRootCAs
}
}
// localhostCert is a PEM-encoded TLS cert generated from src/crypto/tls:
//
// go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com \
// --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
var localhostCert = []byte(`
-----BEGIN CERTIFICATE-----
MIICFDCCAX2gAwIBAgIRAK0xjnaPuNDSreeXb+z+0u4wDQYJKoZIhvcNAQELBQAw
EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
gYkCgYEA0nFbQQuOWsjbGtejcpWz153OlziZM4bVjJ9jYruNw5n2Ry6uYQAffhqa
JOInCmmcVe2siJglsyH9aRh6vKiobBbIUXXUU1ABd56ebAzlt0LobLlx7pZEMy30
LqIi9E6zmL3YvdGzpYlkFRnRrqwEtWYbGBf3znO250S56CCWH2UCAwEAAaNoMGYw
DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF
MAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAA
AAAAAAEwDQYJKoZIhvcNAQELBQADgYEAbZtDS2dVuBYvb+MnolWnCNqvw1w5Gtgi
NmvQQPOMgM3m+oQSCPRTNGSg25e1Qbo7bgQDv8ZTnq8FgOJ/rbkyERw2JckkHpD4
n4qcK27WkEDBtQFlPihIM8hLIuzWoi/9wygiElTy/tVL3y7fGCvY2/k1KBthtZGF
tN8URjVmyEo=
-----END CERTIFICATE-----`)
// localhostKey is the private key for localhostCert.
var localhostKey = []byte(testingKey(`
-----BEGIN RSA TESTING KEY-----
MIICXgIBAAKBgQDScVtBC45ayNsa16NylbPXnc6XOJkzhtWMn2Niu43DmfZHLq5h
AB9+Gpok4icKaZxV7ayImCWzIf1pGHq8qKhsFshRddRTUAF3np5sDOW3QuhsuXHu
lkQzLfQuoiL0TrOYvdi90bOliWQVGdGurAS1ZhsYF/fOc7bnRLnoIJYfZQIDAQAB
AoGBAMst7OgpKyFV6c3JwyI/jWqxDySL3caU+RuTTBaodKAUx2ZEmNJIlx9eudLA
kucHvoxsM/eRxlxkhdFxdBcwU6J+zqooTnhu/FE3jhrT1lPrbhfGhyKnUrB0KKMM
VY3IQZyiehpxaeXAwoAou6TbWoTpl9t8ImAqAMY8hlULCUqlAkEA+9+Ry5FSYK/m
542LujIcCaIGoG1/Te6Sxr3hsPagKC2rH20rDLqXwEedSFOpSS0vpzlPAzy/6Rbb
PHTJUhNdwwJBANXkA+TkMdbJI5do9/mn//U0LfrCR9NkcoYohxfKz8JuhgRQxzF2
6jpo3q7CdTuuRixLWVfeJzcrAyNrVcBq87cCQFkTCtOMNC7fZnCTPUv+9q1tcJyB
vNjJu3yvoEZeIeuzouX9TJE21/33FaeDdsXbRhQEj23cqR38qFHsF1qAYNMCQQDP
QXLEiJoClkR2orAmqjPLVhR3t2oB3INcnEjLNSq8LHyQEfXyaFfu4U9l5+fRPL2i
jiC0k/9L5dHUsF0XZothAkEA23ddgRs+Id/HxtojqqUT27B8MT/IGNrYsp4DvS/c
qgkeluku4GjxRlDMBuXk94xOBEinUs+p/hwP1Alll80Tpg==
-----END RSA TESTING KEY-----`))
func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }
golang-github-xhit-go-simple-mail-2.16.0/testdata/ 0000775 0000000 0000000 00000000000 14477124731 0021736 5 ustar 00root root 0000000 0000000 golang-github-xhit-go-simple-mail-2.16.0/testdata/foo.txt 0000664 0000000 0000000 00000000003 14477124731 0023253 0 ustar 00root root 0000000 0000000 foo