fix(notification): text emails not encoded properly (#3854)

This fixes an issue where the plain text portion of emails is not encoded with quoted printable encoding.
pull/3912/head
James Elliott 2022-08-27 07:39:20 +10:00 committed by GitHub
parent 4d3ac31051
commit 319a8cf9d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 469 additions and 241 deletions

31
.gitattributes vendored 100644
View File

@ -0,0 +1,31 @@
* text=auto
*.go text
*.sh text
*.html text
*.js text
*.jsx text
*.ts text
*.tsx text
*.md text
*.json text
*.svg text
*.css text
*.yaml text
*.yml text
*.txt text
*.tmpl text
*.service text
Dockerfile text
Dockerfile.* text
LICENSE text
*.jpg binary
*.png binary
*.gif binary
*.ico binary
# RFC2822 requires us to save the email templates with CRLF line endings.
internal/templates/src/notification/* text eol=crlf
examples/templates/notifications/*.tmpl text eol=crlf
examples/templates/notifications/*.html text eol=crlf

View File

@ -18,6 +18,17 @@ two extensions; `.html` for HTML templates, and `.txt` for plaintext templates.
This guide effectively documents the usage of the This guide effectively documents the usage of the
[template_path](../../configuration/notifications/introduction.md#template_path) notification configuration option. [template_path](../../configuration/notifications/introduction.md#template_path) notification configuration option.
## Important Notes
1. The templates are not covered by our stability guarantees. While we aim to avoid changes to the templates which
would cause users to have to manually change them changes may be necessary in order to facilitate bug fixes or
generally improve the templates.
1. This is especially important for the [Envelope Template](#envelope-template).
2. It is your responsibility to ensure your templates are up to date. We make no efforts in facilitating this.
2. We may not be able to offer any direct support in debugging these templates. We only offer support and fixes to
the official templates.
3. All templates __*MUST*__ be encoded in UTF-8 with CRLF line endings. The line endings __*MUST NOT*__ be a simple LF.
## Template Names ## Template Names
| Template | Description | | Template | Description |
@ -60,13 +71,14 @@ Some Additional examples for specific purposes can be found in the
## Envelope Template ## Envelope Template
*__Important Note:__ This template must end with a CRLF newline. Failure to include this newline will result in
malformed emails.*
There is also a special envelope template. This is the email envelope which contains the content of the other templates There is also a special envelope template. This is the email envelope which contains the content of the other templates
when sent via the SMTP notifier. It's *__strongly recommended__* that you do not modify this template unless you know when sent via the SMTP notifier. It's *__strongly recommended__* that you do not modify this template unless you know
what you're doing. If you really want to modify it the name of the file must be `Envelope.tmpl`. what you're doing. If you really want to modify it the name of the file must be `Envelope.tmpl`.
This template contains the following placeholders: This template contains the following placeholders which are automatically injected into the template:
In template files, you can use the following placeholders which are automatically injected into the templates:
| Placeholder | Description | | Placeholder | Description |
|:-----------------------:|:---------------------------------------------------------------------------:| |:-----------------------:|:---------------------------------------------------------------------------:|
@ -80,9 +92,6 @@ In template files, you can use the following placeholders which are automaticall
| `{{ .To }}` | The string representation of the recipients email address. | | `{{ .To }}` | The string representation of the recipients email address. |
| `{{ .Subject }}` | The email subject. | | `{{ .Subject }}` | The email subject. |
| `{{ .Date }}` | The time.Time of the email envelope being rendered. | | `{{ .Date }}` | The time.Time of the email envelope being rendered. |
| `{{ .Boundary }}` | The random alphanumeric 20 character multi-part email boundary. |
| `{{ .Body.PlainText }}` | The plain text version of the email. |
| `{{ .Body.HTML }}` | The HTML version of the email. |
## Original Templates ## Original Templates

View File

@ -4,18 +4,3 @@ Subject: {{ .Subject }}
Date: {{ .Date.Format "Mon, 2 Jan 2006 15:04:05 -0700" }} Date: {{ .Date.Format "Mon, 2 Jan 2006 15:04:05 -0700" }}
Message-ID: <{{ .UUID }}@{{ .Identifier }}> Message-ID: <{{ .UUID }}@{{ .Identifier }}>
MIME-Version: 1.0 MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="{{ .Boundary }}"
--{{ .Boundary }}
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline
{{ .Body.PlainText }}
{{- if not (eq .Body.HTML "") }}
--{{ .Boundary }}
Content-Type: text/html; charset="utf-8"
{{ .Body.HTML }}
{{- end }}
--{{ .Boundary }}--

View File

@ -11,10 +11,9 @@ import (
// NewDuoAPI create duo API instance. // NewDuoAPI create duo API instance.
func NewDuoAPI(duoAPI *duoapi.DuoApi) *APIImpl { func NewDuoAPI(duoAPI *duoapi.DuoApi) *APIImpl {
api := new(APIImpl) return &APIImpl{
api.DuoApi = duoAPI DuoApi: duoAPI,
}
return api
} }
// Call call to the DuoAPI. // Call call to the DuoAPI.

View File

@ -74,8 +74,6 @@ func ResetPasswordPOST(ctx *middlewares.AutheliaCtx) {
return return
} }
bufHTML := new(bytes.Buffer)
disableHTML := false disableHTML := false
if ctx.Configuration.Notifier.SMTP != nil { if ctx.Configuration.Notifier.SMTP != nil {
disableHTML = ctx.Configuration.Notifier.SMTP.DisableHTMLEmails disableHTML = ctx.Configuration.Notifier.SMTP.DisableHTMLEmails
@ -87,6 +85,8 @@ func ResetPasswordPOST(ctx *middlewares.AutheliaCtx) {
RemoteIP: ctx.RemoteIP().String(), RemoteIP: ctx.RemoteIP().String(),
} }
bufHTML, bufText := &bytes.Buffer{}, &bytes.Buffer{}
if !disableHTML { if !disableHTML {
if err = ctx.Providers.Templates.ExecuteEmailPasswordResetTemplate(bufHTML, values, templates.HTMLFormat); err != nil { if err = ctx.Providers.Templates.ExecuteEmailPasswordResetTemplate(bufHTML, values, templates.HTMLFormat); err != nil {
ctx.Logger.Error(err) ctx.Logger.Error(err)
@ -96,8 +96,6 @@ func ResetPasswordPOST(ctx *middlewares.AutheliaCtx) {
} }
} }
bufText := new(bytes.Buffer)
if err = ctx.Providers.Templates.ExecuteEmailPasswordResetTemplate(bufText, values, templates.PlainTextFormat); err != nil { if err = ctx.Providers.Templates.ExecuteEmailPasswordResetTemplate(bufText, values, templates.PlainTextFormat); err != nil {
ctx.Logger.Error(err) ctx.Logger.Error(err)
ctx.ReplyOK() ctx.ReplyOK()
@ -110,7 +108,7 @@ func ResetPasswordPOST(ctx *middlewares.AutheliaCtx) {
ctx.Logger.Debugf("Sending an email to user %s (%s) to inform that the password has changed.", ctx.Logger.Debugf("Sending an email to user %s (%s) to inform that the password has changed.",
username, addresses[0]) username, addresses[0])
if err = ctx.Providers.Notifier.Send(addresses[0], "Password changed successfully", bufText.String(), bufHTML.String()); err != nil { if err = ctx.Providers.Notifier.Send(addresses[0], "Password changed successfully", bufText.Bytes(), bufHTML.Bytes()); err != nil {
ctx.Logger.Error(err) ctx.Logger.Error(err)
ctx.ReplyOK() ctx.ReplyOK()

View File

@ -62,16 +62,15 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs, delayFunc Tim
return return
} }
uri, err := ctx.ExternalRootURL() var (
if err != nil { uri string
)
if uri, err = ctx.ExternalRootURL(); err != nil {
ctx.Error(err, messageOperationFailed) ctx.Error(err, messageOperationFailed)
return return
} }
link := fmt.Sprintf("%s%s?token=%s", uri, args.TargetEndpoint, ss)
bufHTML := new(bytes.Buffer)
disableHTML := false disableHTML := false
if ctx.Configuration.Notifier.SMTP != nil { if ctx.Configuration.Notifier.SMTP != nil {
disableHTML = ctx.Configuration.Notifier.SMTP.DisableHTMLEmails disableHTML = ctx.Configuration.Notifier.SMTP.DisableHTMLEmails
@ -79,12 +78,14 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs, delayFunc Tim
values := templates.EmailIdentityVerificationValues{ values := templates.EmailIdentityVerificationValues{
Title: args.MailTitle, Title: args.MailTitle,
LinkURL: link, LinkURL: fmt.Sprintf("%s%s?token=%s", uri, args.TargetEndpoint, ss),
LinkText: args.MailButtonContent, LinkText: args.MailButtonContent,
DisplayName: identity.DisplayName, DisplayName: identity.DisplayName,
RemoteIP: ctx.RemoteIP().String(), RemoteIP: ctx.RemoteIP().String(),
} }
bufHTML, bufText := &bytes.Buffer{}, &bytes.Buffer{}
if !disableHTML { if !disableHTML {
if err = ctx.Providers.Templates.ExecuteEmailIdentityVerificationTemplate(bufHTML, values, templates.HTMLFormat); err != nil { if err = ctx.Providers.Templates.ExecuteEmailIdentityVerificationTemplate(bufHTML, values, templates.HTMLFormat); err != nil {
ctx.Error(err, messageOperationFailed) ctx.Error(err, messageOperationFailed)
@ -92,8 +93,6 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs, delayFunc Tim
} }
} }
bufText := new(bytes.Buffer)
if err = ctx.Providers.Templates.ExecuteEmailIdentityVerificationTemplate(bufText, values, templates.PlainTextFormat); err != nil { if err = ctx.Providers.Templates.ExecuteEmailIdentityVerificationTemplate(bufText, values, templates.PlainTextFormat); err != nil {
ctx.Error(err, messageOperationFailed) ctx.Error(err, messageOperationFailed)
return return
@ -102,7 +101,7 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs, delayFunc Tim
ctx.Logger.Debugf("Sending an email to user %s (%s) to confirm identity for registering a device.", ctx.Logger.Debugf("Sending an email to user %s (%s) to confirm identity for registering a device.",
identity.Username, identity.Email) identity.Username, identity.Email)
if err = ctx.Providers.Notifier.Send(mail.Address{Name: identity.DisplayName, Address: identity.Email}, args.MailTitle, bufText.String(), bufHTML.String()); err != nil { if err = ctx.Providers.Notifier.Send(mail.Address{Name: identity.DisplayName, Address: identity.Email}, args.MailTitle, bufText.Bytes(), bufHTML.Bytes()); err != nil {
ctx.Error(err, messageOperationFailed) ctx.Error(err, messageOperationFailed)
return return
} }

View File

@ -35,7 +35,7 @@ func (m *MockNotifier) EXPECT() *MockNotifierMockRecorder {
} }
// Send mocks base method. // Send mocks base method.
func (m *MockNotifier) Send(arg0 mail.Address, arg1, arg2, arg3 string) error { func (m *MockNotifier) Send(arg0 mail.Address, arg1 string, arg2, arg3 []byte) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Send", arg0, arg1, arg2, arg3) ret := m.ctrl.Call(m, "Send", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)

View File

@ -16,9 +16,30 @@ const (
smtpCommandAUTH = "AUTH" smtpCommandAUTH = "AUTH"
smtpCommandMAIL = "MAIL" smtpCommandMAIL = "MAIL"
smtpCommandRCPT = "RCPT" smtpCommandRCPT = "RCPT"
smtpEncodingQuotedPrintable = "quoted-printable"
smtpEncoding8bit = "8bit"
smtpContentTypeTextPlain = "text/plain"
smtpContentTypeTextHTML = "text/html"
smtpFmtContentType = `%s; charset="UTF-8"`
smtpFmtContentDispositionInline = "inline"
smtpExtSTARTTLS = smtpCommandSTARTTLS
smtpExt8BITMIME = "8BITMIME"
)
const (
headerContentType = "Content-Type"
headerContentDisposition = "Content-Disposition"
headerContentTransferEncoding = "Content-Transfer-Encoding"
) )
const ( const (
fmtSMTPGenericError = "error performing %s with the SMTP server: %w" fmtSMTPGenericError = "error performing %s with the SMTP server: %w"
fmtSMTPDialError = "error dialing the SMTP server: %w" fmtSMTPDialError = "error dialing the SMTP server: %w"
) )
var (
rfc2822DoubleNewLine = []byte("\r\n\r\n")
)

View File

@ -43,8 +43,8 @@ func (n *FileNotifier) StartupCheck() (err error) {
} }
// Send send a identity verification link to a user. // Send send a identity verification link to a user.
func (n *FileNotifier) Send(recipient mail.Address, subject, body, _ string) error { func (n *FileNotifier) Send(recipient mail.Address, subject string, bodyText, _ []byte) error {
content := fmt.Sprintf("Date: %s\nRecipient: %s\nSubject: %s\nBody: %s", time.Now(), recipient, subject, body) content := fmt.Sprintf("Date: %s\nRecipient: %s\nSubject: %s\nBody: %s", time.Now(), recipient, subject, bodyText)
return os.WriteFile(n.path, []byte(content), fileNotifierMode) return os.WriteFile(n.path, []byte(content), fileNotifierMode)
} }

View File

@ -10,5 +10,5 @@ import (
type Notifier interface { type Notifier interface {
model.StartupCheck model.StartupCheck
Send(recipient mail.Address, subject, body, htmlBody string) (err error) Send(recipient mail.Address, subject string, bodyText, bodyHTML []byte) (err error)
} }

View File

@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"mime/multipart"
"net" "net"
"net/mail" "net/mail"
"net/smtp" "net/smtp"
@ -52,7 +53,7 @@ type SMTPNotifier struct {
} }
// Send is used to email a recipient. // Send is used to email a recipient.
func (n *SMTPNotifier) Send(recipient mail.Address, title, body, htmlBody string) (err error) { func (n *SMTPNotifier) Send(recipient mail.Address, subject string, bodyText, bodyHTML []byte) (err error) {
if err = n.dial(); err != nil { if err = n.dial(); err != nil {
return fmt.Errorf(fmtSMTPDialError, err) return fmt.Errorf(fmtSMTPDialError, err)
} }
@ -65,7 +66,7 @@ func (n *SMTPNotifier) Send(recipient mail.Address, title, body, htmlBody string
} }
// Compose and send the email body to the server. // Compose and send the email body to the server.
if err = n.compose(recipient, title, body, htmlBody); err != nil { if err = n.compose(recipient, subject, bodyText, bodyHTML); err != nil {
return fmt.Errorf(fmtSMTPGenericError, smtpCommandDATA, err) return fmt.Errorf(fmtSMTPGenericError, smtpCommandDATA, err)
} }
@ -161,7 +162,7 @@ func (n *SMTPNotifier) startTLS() error {
return nil return nil
} }
switch ok, _ := n.client.Extension("STARTTLS"); ok { switch ok, _ := n.client.Extension(smtpExtSTARTTLS); ok {
case true: case true:
n.log.Debugf("Notifier SMTP server supports STARTTLS (disableVerifyCert: %t, ServerName: %s), attempting", n.tlsConfig.InsecureSkipVerify, n.tlsConfig.ServerName) n.log.Debugf("Notifier SMTP server supports STARTTLS (disableVerifyCert: %t, ServerName: %s), attempting", n.tlsConfig.InsecureSkipVerify, n.tlsConfig.ServerName)
@ -237,7 +238,7 @@ func (n *SMTPNotifier) auth() (err error) {
return nil return nil
} }
func (n *SMTPNotifier) compose(recipient mail.Address, title, body, htmlBody string) (err error) { func (n *SMTPNotifier) compose(recipient mail.Address, subject string, bodyText, bodyHTML []byte) (err error) {
n.log.Debugf("Notifier SMTP client attempting to send email body to %s", recipient.String()) n.log.Debugf("Notifier SMTP client attempting to send email body to %s", recipient.String())
if !n.config.DisableRequireTLS { if !n.config.DisableRequireTLS {
@ -261,7 +262,7 @@ func (n *SMTPNotifier) compose(recipient mail.Address, title, body, htmlBody str
return err return err
} }
values := templates.EmailEnvelopeValues{ data := templates.EmailEnvelopeValues{
ProcessID: os.Getpid(), ProcessID: os.Getpid(),
UUID: muuid.String(), UUID: muuid.String(),
Host: n.config.Host, Host: n.config.Host,
@ -270,21 +271,43 @@ func (n *SMTPNotifier) compose(recipient mail.Address, title, body, htmlBody str
Identifier: n.config.Identifier, Identifier: n.config.Identifier,
From: n.config.Sender.String(), From: n.config.Sender.String(),
To: recipient.String(), To: recipient.String(),
Subject: strings.ReplaceAll(n.config.Subject, "{title}", title), Subject: strings.ReplaceAll(n.config.Subject, "{title}", subject),
Date: time.Now(), Date: time.Now(),
Boundary: utils.RandomString(30, utils.AlphaNumericCharacters, true),
Body: templates.EmailEnvelopeBodyValues{
PlainText: body,
HTML: htmlBody,
},
} }
if err = n.templates.ExecuteEmailEnvelope(wc, values); err != nil { if err = n.templates.ExecuteEmailEnvelope(wc, data); err != nil {
n.log.Debugf("Notifier SMTP client error while sending email body over WriteCloser: %v", err) n.log.Debugf("Notifier SMTP client error while sending email body over WriteCloser: %v", err)
return err return err
} }
mwr := multipart.NewWriter(wc)
if _, err = wc.Write([]byte(fmt.Sprintf(`Content-Type: multipart/alternative; boundary="%s"`, mwr.Boundary()))); err != nil {
return err
}
if _, err = wc.Write(rfc2822DoubleNewLine); err != nil {
return err
}
ext8BITMIME, _ := n.client.Extension(smtpExt8BITMIME)
if err = multipartWrite(mwr, smtpMIMEHeaders(ext8BITMIME, smtpContentTypeTextPlain, bodyText), bodyText); err != nil {
return fmt.Errorf("failed to write text/plain part: %w", err)
}
if len(bodyHTML) != 0 {
if err = multipartWrite(mwr,
smtpMIMEHeaders(ext8BITMIME, smtpContentTypeTextHTML, bodyText), bodyHTML); err != nil {
return fmt.Errorf("failed to write text/html part: %w", err)
}
}
if err = mwr.Close(); err != nil {
return fmt.Errorf("failed to finalize the multipart content: %w", err)
}
if err = wc.Close(); err != nil { if err = wc.Close(); err != nil {
n.log.Debugf("Notifier SMTP client error while closing the WriteCloser: %v", err) n.log.Debugf("Notifier SMTP client error while closing the WriteCloser: %v", err)
return err return err

View File

@ -0,0 +1,96 @@
package notification
import (
"fmt"
"io"
"mime/multipart"
"mime/quotedprintable"
"net/textproto"
"github.com/authelia/authelia/v4/internal/utils"
)
func smtpMIMEHeaders(ext8BITMIME bool, contentType string, data []byte) textproto.MIMEHeader {
headers := textproto.MIMEHeader{
headerContentType: []string{fmt.Sprintf(smtpFmtContentType, contentType)},
headerContentDisposition: []string{smtpFmtContentDispositionInline},
}
characteristics := NewMIMECharacteristics(data)
if !ext8BITMIME || characteristics.LongLines || characteristics.Characters8BIT {
headers.Set(headerContentTransferEncoding, smtpEncodingQuotedPrintable)
} else {
headers.Set(headerContentTransferEncoding, smtpEncoding8bit)
}
return headers
}
func multipartWrite(mwr *multipart.Writer, header textproto.MIMEHeader, data []byte) (err error) {
var (
wc io.WriteCloser
wr io.Writer
)
if wr, err = mwr.CreatePart(header); err != nil {
return err
}
switch header.Get(headerContentTransferEncoding) {
case smtpEncodingQuotedPrintable:
wc = quotedprintable.NewWriter(wr)
case smtpEncoding8bit, "":
wc = utils.NewWriteCloser(wr)
default:
return fmt.Errorf("unknown encoding: %s", header.Get(headerContentTransferEncoding))
}
if _, err = wc.Write(data); err != nil {
_ = wc.Close()
return err
}
_ = wc.Close()
return nil
}
// NewMIMECharacteristics detects the SMTP MIMECharacteristics for the given data bytes.
func NewMIMECharacteristics(data []byte) MIMECharacteristics {
characteristics := MIMECharacteristics{}
cl := 0
n := len(data)
for i := 0; i < n; i++ {
cl++
if cl > 1000 {
characteristics.LongLines = true
}
if data[i] == 10 {
cl = 0
if i == 0 || data[i-1] != 13 {
characteristics.LineFeeds = true
}
}
if data[i] >= 128 {
characteristics.Characters8BIT = true
}
}
return characteristics
}
// MIMECharacteristics represents specific MIME related characteristics.
type MIMECharacteristics struct {
LongLines bool
LineFeeds bool
Characters8BIT bool
}

View File

@ -0,0 +1,16 @@
package templates
import (
"path"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestShouldNotHaveDeprecatedPlaceholders(t *testing.T) {
data, err := embedFS.ReadFile(path.Join("src", TemplateCategoryNotifications, TemplateNameEmailEnvelope))
require.NoError(t, err)
assert.False(t, tmplEnvelopeHasDeprecatedPlaceholders(data))
}

View File

@ -25,23 +25,33 @@ type Provider struct {
} }
// ExecuteEmailEnvelope writes the envelope template to the given io.Writer. // ExecuteEmailEnvelope writes the envelope template to the given io.Writer.
func (p Provider) ExecuteEmailEnvelope(wr io.Writer, data EmailEnvelopeValues) (err error) { func (p *Provider) ExecuteEmailEnvelope(wr io.Writer, data EmailEnvelopeValues) (err error) {
return p.templates.notification.envelope.Execute(wr, data) return p.templates.notification.envelope.Execute(wr, data)
} }
// ExecuteEmailPasswordResetTemplate writes the password reset template to the given io.Writer. // ExecuteEmailPasswordResetTemplate writes the password reset template to the given io.Writer.
func (p Provider) ExecuteEmailPasswordResetTemplate(wr io.Writer, data EmailPasswordResetValues, format Format) (err error) { func (p *Provider) ExecuteEmailPasswordResetTemplate(wr io.Writer, data EmailPasswordResetValues, format Format) (err error) {
return p.templates.notification.passwordReset.Get(format).Execute(wr, data) return p.templates.notification.passwordReset.Get(format).Execute(wr, data)
} }
// ExecuteEmailIdentityVerificationTemplate writes the identity verification template to the given io.Writer. // ExecuteEmailIdentityVerificationTemplate writes the identity verification template to the given io.Writer.
func (p Provider) ExecuteEmailIdentityVerificationTemplate(wr io.Writer, data EmailIdentityVerificationValues, format Format) (err error) { func (p *Provider) ExecuteEmailIdentityVerificationTemplate(wr io.Writer, data EmailIdentityVerificationValues, format Format) (err error) {
return p.templates.notification.identityVerification.Get(format).Execute(wr, data) return p.templates.notification.identityVerification.Get(format).Execute(wr, data)
} }
func (p *Provider) load() (err error) { func (p *Provider) load() (err error) {
var errs []error var errs []error
if tPath, embed, data, err := readTemplate(TemplateNameEmailEnvelope, TemplateCategoryNotifications, p.config.EmailTemplatesPath); err != nil {
errs = append(errs, err)
} else {
if !embed && tmplEnvelopeHasDeprecatedPlaceholders(data) {
errs = append(errs, fmt.Errorf("the evelope template override appears to contain removed placeholders"))
} else if p.templates.notification.envelope, err = parseTemplate(TemplateNameEmailEnvelope, tPath, embed, data); err != nil {
errs = append(errs, err)
}
}
if p.templates.notification.envelope, err = loadTemplate(TemplateNameEmailEnvelope, TemplateCategoryNotifications, p.config.EmailTemplatesPath); err != nil { if p.templates.notification.envelope, err = loadTemplate(TemplateNameEmailEnvelope, TemplateCategoryNotifications, p.config.EmailTemplatesPath); err != nil {
errs = append(errs, err) errs = append(errs, err)
} }

View File

@ -3,18 +3,3 @@ To: {{ .To }}
Subject: {{ .Subject }} Subject: {{ .Subject }}
Date: {{ .Date.Format "Mon, 2 Jan 2006 15:04:05 -0700" }} Date: {{ .Date.Format "Mon, 2 Jan 2006 15:04:05 -0700" }}
MIME-Version: 1.0 MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="{{ .Boundary }}"
--{{ .Boundary }}
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline
{{ .Body.PlainText }}
{{- if not (eq .Body.HTML "") }}
--{{ .Boundary }}
Content-Type: text/html; charset="utf-8"
{{ .Body.HTML }}
{{- end }}
--{{ .Boundary }}--

View File

@ -62,12 +62,4 @@ type EmailEnvelopeValues struct {
To string To string
Subject string Subject string
Date time.Time Date time.Time
Boundary string
Body EmailEnvelopeBodyValues
}
// EmailEnvelopeBodyValues are the values used for the email envelopes bodies.
type EmailEnvelopeBodyValues struct {
PlainText string
HTML string
} }

View File

@ -1,6 +1,7 @@
package templates package templates
import ( import (
"bytes"
"fmt" "fmt"
"os" "os"
"path" "path"
@ -8,6 +9,12 @@ import (
"text/template" "text/template"
) )
func tmplEnvelopeHasDeprecatedPlaceholders(data []byte) bool {
return bytes.Contains(data, []byte("{{ .Boundary }}")) ||
bytes.Contains(data, []byte("{{ .Body.PlainText }}")) ||
bytes.Contains(data, []byte("{{ .Body.HTML }}"))
}
func templateExists(path string) (exists bool) { func templateExists(path string) (exists bool) {
info, err := os.Stat(path) info, err := os.Stat(path)
if err != nil { if err != nil {
@ -21,28 +28,51 @@ func templateExists(path string) (exists bool) {
return true return true
} }
//nolint:unparam func readTemplate(name, category, overridePath string) (tPath string, embed bool, data []byte, err error) {
func loadTemplate(name, category, overridePath string) (t *template.Template, err error) {
if overridePath != "" { if overridePath != "" {
tPath := filepath.Join(overridePath, name) tPath = filepath.Join(overridePath, name)
if templateExists(tPath) { if templateExists(tPath) {
if t, err = template.ParseFiles(tPath); err != nil { if data, err = os.ReadFile(tPath); err != nil {
return nil, fmt.Errorf("could not parse template at path '%s': %w", tPath, err) return tPath, false, nil, fmt.Errorf("failed to read template override at path '%s': %w", tPath, err)
} }
return t, nil return tPath, false, data, nil
} }
} }
data, err := embedFS.ReadFile(path.Join("src", category, name)) tPath = path.Join("src", category, name)
if err != nil {
return nil, err if data, err = embedFS.ReadFile(tPath); err != nil {
return tPath, true, nil, fmt.Errorf("failed to read embedded template '%s': %w", tPath, err)
} }
return tPath, true, data, nil
}
func parseTemplate(name, tPath string, embed bool, data []byte) (t *template.Template, err error) {
if t, err = template.New(name).Parse(string(data)); err != nil { if t, err = template.New(name).Parse(string(data)); err != nil {
panic(fmt.Errorf("failed to parse internal template: %w", err)) if embed {
return nil, fmt.Errorf("failed to parse embedded template '%s': %w", tPath, err)
}
return nil, fmt.Errorf("failed to parse template override at path '%s': %w", tPath, err)
} }
return t, nil return t, nil
} }
//nolint:unparam
func loadTemplate(name, category, overridePath string) (t *template.Template, err error) {
var (
embed bool
tPath string
data []byte
)
if tPath, embed, data, err = readTemplate(name, category, overridePath); err != nil {
return nil, err
}
return parseTemplate(name, tPath, embed, data)
}

View File

@ -0,0 +1,34 @@
package utils
import (
"errors"
"io"
)
// NewWriteCloser creates a new io.WriteCloser from an io.Writer.
func NewWriteCloser(wr io.Writer) io.WriteCloser {
return &WriteCloser{wr: wr}
}
// WriteCloser is a io.Writer with an io.Closer.
type WriteCloser struct {
wr io.Writer
closed bool
}
// Write to the io.Writer.
func (w *WriteCloser) Write(p []byte) (n int, err error) {
if w.closed {
return -1, errors.New("already closed")
}
return w.wr.Write(p)
}
// Close the io.Closer.
func (w *WriteCloser) Close() error {
w.closed = true
return nil
}

View File

@ -1,129 +1,129 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="US_UK_Download_on_the" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" <svg version="1.1" id="US_UK_Download_on_the" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px" width="135px" height="40px" viewBox="0 0 135 40" enable-background="new 0 0 135 40" xml:space="preserve"> x="0px" y="0px" width="135px" height="40px" viewBox="0 0 135 40" enable-background="new 0 0 135 40" xml:space="preserve">
<g> <g>
<path fill="#A6A6A6" d="M130.197,40H4.729C2.122,40,0,37.872,0,35.267V4.726C0,2.12,2.122,0,4.729,0h125.468 <path fill="#A6A6A6" d="M130.197,40H4.729C2.122,40,0,37.872,0,35.267V4.726C0,2.12,2.122,0,4.729,0h125.468
C132.803,0,135,2.12,135,4.726v30.541C135,37.872,132.803,40,130.197,40L130.197,40z"/> C132.803,0,135,2.12,135,4.726v30.541C135,37.872,132.803,40,130.197,40L130.197,40z"/>
<path d="M134.032,35.268c0,2.116-1.714,3.83-3.834,3.83H4.729c-2.119,0-3.839-1.714-3.839-3.83V4.725 <path d="M134.032,35.268c0,2.116-1.714,3.83-3.834,3.83H4.729c-2.119,0-3.839-1.714-3.839-3.83V4.725
c0-2.115,1.72-3.835,3.839-3.835h125.468c2.121,0,3.834,1.72,3.834,3.835L134.032,35.268L134.032,35.268z"/> c0-2.115,1.72-3.835,3.839-3.835h125.468c2.121,0,3.834,1.72,3.834,3.835L134.032,35.268L134.032,35.268z"/>
<g> <g>
<g> <g>
<path fill="#FFFFFF" d="M30.128,19.784c-0.029-3.223,2.639-4.791,2.761-4.864c-1.511-2.203-3.853-2.504-4.676-2.528 <path fill="#FFFFFF" d="M30.128,19.784c-0.029-3.223,2.639-4.791,2.761-4.864c-1.511-2.203-3.853-2.504-4.676-2.528
c-1.967-0.207-3.875,1.177-4.877,1.177c-1.022,0-2.565-1.157-4.228-1.123c-2.14,0.033-4.142,1.272-5.24,3.196 c-1.967-0.207-3.875,1.177-4.877,1.177c-1.022,0-2.565-1.157-4.228-1.123c-2.14,0.033-4.142,1.272-5.24,3.196
c-2.266,3.923-0.576,9.688,1.595,12.859c1.086,1.553,2.355,3.287,4.016,3.226c1.625-0.067,2.232-1.036,4.193-1.036 c-2.266,3.923-0.576,9.688,1.595,12.859c1.086,1.553,2.355,3.287,4.016,3.226c1.625-0.067,2.232-1.036,4.193-1.036
c1.943,0,2.513,1.036,4.207,0.997c1.744-0.028,2.842-1.56,3.89-3.127c1.255-1.78,1.759-3.533,1.779-3.623 c1.943,0,2.513,1.036,4.207,0.997c1.744-0.028,2.842-1.56,3.89-3.127c1.255-1.78,1.759-3.533,1.779-3.623
C33.507,24.924,30.161,23.647,30.128,19.784z"/> C33.507,24.924,30.161,23.647,30.128,19.784z"/>
<path fill="#FFFFFF" d="M26.928,10.306c0.874-1.093,1.472-2.58,1.306-4.089c-1.265,0.056-2.847,0.875-3.758,1.944 <path fill="#FFFFFF" d="M26.928,10.306c0.874-1.093,1.472-2.58,1.306-4.089c-1.265,0.056-2.847,0.875-3.758,1.944
c-0.806,0.942-1.526,2.486-1.34,3.938C24.557,12.205,26.016,11.382,26.928,10.306z"/> c-0.806,0.942-1.526,2.486-1.34,3.938C24.557,12.205,26.016,11.382,26.928,10.306z"/>
</g> </g>
</g> </g>
<g> <g>
<path fill="#FFFFFF" d="M53.645,31.504h-2.271l-1.244-3.909h-4.324l-1.185,3.909h-2.211l4.284-13.308h2.646L53.645,31.504z <path fill="#FFFFFF" d="M53.645,31.504h-2.271l-1.244-3.909h-4.324l-1.185,3.909h-2.211l4.284-13.308h2.646L53.645,31.504z
M49.755,25.955L48.63,22.48c-0.119-0.355-0.342-1.191-0.671-2.507h-0.04c-0.131,0.566-0.342,1.402-0.632,2.507l-1.105,3.475 M49.755,25.955L48.63,22.48c-0.119-0.355-0.342-1.191-0.671-2.507h-0.04c-0.131,0.566-0.342,1.402-0.632,2.507l-1.105,3.475
H49.755z"/> H49.755z"/>
<path fill="#FFFFFF" d="M64.662,26.588c0,1.632-0.441,2.922-1.323,3.869c-0.79,0.843-1.771,1.264-2.942,1.264 <path fill="#FFFFFF" d="M64.662,26.588c0,1.632-0.441,2.922-1.323,3.869c-0.79,0.843-1.771,1.264-2.942,1.264
c-1.264,0-2.172-0.454-2.725-1.362h-0.04v5.055h-2.132V25.067c0-1.026-0.027-2.079-0.079-3.159h1.875l0.119,1.521h0.04 c-1.264,0-2.172-0.454-2.725-1.362h-0.04v5.055h-2.132V25.067c0-1.026-0.027-2.079-0.079-3.159h1.875l0.119,1.521h0.04
c0.711-1.146,1.79-1.718,3.238-1.718c1.132,0,2.077,0.447,2.833,1.342C64.284,23.949,64.662,25.127,64.662,26.588z M62.49,26.666 c0.711-1.146,1.79-1.718,3.238-1.718c1.132,0,2.077,0.447,2.833,1.342C64.284,23.949,64.662,25.127,64.662,26.588z M62.49,26.666
c0-0.934-0.21-1.704-0.632-2.31c-0.461-0.632-1.08-0.948-1.856-0.948c-0.526,0-1.004,0.176-1.431,0.523 c0-0.934-0.21-1.704-0.632-2.31c-0.461-0.632-1.08-0.948-1.856-0.948c-0.526,0-1.004,0.176-1.431,0.523
c-0.428,0.35-0.708,0.807-0.839,1.373c-0.066,0.264-0.099,0.48-0.099,0.65v1.6c0,0.698,0.214,1.287,0.642,1.768 c-0.428,0.35-0.708,0.807-0.839,1.373c-0.066,0.264-0.099,0.48-0.099,0.65v1.6c0,0.698,0.214,1.287,0.642,1.768
s0.984,0.721,1.668,0.721c0.803,0,1.428-0.31,1.875-0.928C62.266,28.496,62.49,27.68,62.49,26.666z"/> s0.984,0.721,1.668,0.721c0.803,0,1.428-0.31,1.875-0.928C62.266,28.496,62.49,27.68,62.49,26.666z"/>
<path fill="#FFFFFF" d="M75.699,26.588c0,1.632-0.441,2.922-1.324,3.869c-0.789,0.843-1.77,1.264-2.941,1.264 <path fill="#FFFFFF" d="M75.699,26.588c0,1.632-0.441,2.922-1.324,3.869c-0.789,0.843-1.77,1.264-2.941,1.264
c-1.264,0-2.172-0.454-2.724-1.362H68.67v5.055h-2.132V25.067c0-1.026-0.027-2.079-0.079-3.159h1.875l0.119,1.521h0.04 c-1.264,0-2.172-0.454-2.724-1.362H68.67v5.055h-2.132V25.067c0-1.026-0.027-2.079-0.079-3.159h1.875l0.119,1.521h0.04
c0.71-1.146,1.789-1.718,3.238-1.718c1.131,0,2.076,0.447,2.834,1.342C75.32,23.949,75.699,25.127,75.699,26.588z M73.527,26.666 c0.71-1.146,1.789-1.718,3.238-1.718c1.131,0,2.076,0.447,2.834,1.342C75.32,23.949,75.699,25.127,75.699,26.588z M73.527,26.666
c0-0.934-0.211-1.704-0.633-2.31c-0.461-0.632-1.078-0.948-1.855-0.948c-0.527,0-1.004,0.176-1.432,0.523 c0-0.934-0.211-1.704-0.633-2.31c-0.461-0.632-1.078-0.948-1.855-0.948c-0.527,0-1.004,0.176-1.432,0.523
c-0.428,0.35-0.707,0.807-0.838,1.373c-0.065,0.264-0.099,0.48-0.099,0.65v1.6c0,0.698,0.214,1.287,0.64,1.768 c-0.428,0.35-0.707,0.807-0.838,1.373c-0.065,0.264-0.099,0.48-0.099,0.65v1.6c0,0.698,0.214,1.287,0.64,1.768
c0.428,0.48,0.984,0.721,1.67,0.721c0.803,0,1.428-0.31,1.875-0.928C73.303,28.496,73.527,27.68,73.527,26.666z"/> c0.428,0.48,0.984,0.721,1.67,0.721c0.803,0,1.428-0.31,1.875-0.928C73.303,28.496,73.527,27.68,73.527,26.666z"/>
<path fill="#FFFFFF" d="M88.039,27.772c0,1.132-0.393,2.053-1.182,2.764c-0.867,0.777-2.074,1.165-3.625,1.165 <path fill="#FFFFFF" d="M88.039,27.772c0,1.132-0.393,2.053-1.182,2.764c-0.867,0.777-2.074,1.165-3.625,1.165
c-1.432,0-2.58-0.276-3.449-0.829l0.494-1.777c0.936,0.566,1.963,0.85,3.082,0.85c0.803,0,1.428-0.182,1.877-0.544 c-1.432,0-2.58-0.276-3.449-0.829l0.494-1.777c0.936,0.566,1.963,0.85,3.082,0.85c0.803,0,1.428-0.182,1.877-0.544
c0.447-0.362,0.67-0.848,0.67-1.454c0-0.54-0.184-0.995-0.553-1.364c-0.367-0.369-0.98-0.712-1.836-1.029 c0.447-0.362,0.67-0.848,0.67-1.454c0-0.54-0.184-0.995-0.553-1.364c-0.367-0.369-0.98-0.712-1.836-1.029
c-2.33-0.869-3.494-2.142-3.494-3.816c0-1.094,0.408-1.991,1.225-2.689c0.814-0.699,1.9-1.048,3.258-1.048 c-2.33-0.869-3.494-2.142-3.494-3.816c0-1.094,0.408-1.991,1.225-2.689c0.814-0.699,1.9-1.048,3.258-1.048
c1.211,0,2.217,0.211,3.02,0.632l-0.533,1.738c-0.75-0.408-1.598-0.612-2.547-0.612c-0.75,0-1.336,0.185-1.756,0.553 c1.211,0,2.217,0.211,3.02,0.632l-0.533,1.738c-0.75-0.408-1.598-0.612-2.547-0.612c-0.75,0-1.336,0.185-1.756,0.553
c-0.355,0.329-0.533,0.73-0.533,1.205c0,0.526,0.203,0.961,0.611,1.303c0.355,0.316,1,0.658,1.936,1.027 c-0.355,0.329-0.533,0.73-0.533,1.205c0,0.526,0.203,0.961,0.611,1.303c0.355,0.316,1,0.658,1.936,1.027
c1.145,0.461,1.986,1,2.527,1.618C87.77,26.081,88.039,26.852,88.039,27.772z"/> c1.145,0.461,1.986,1,2.527,1.618C87.77,26.081,88.039,26.852,88.039,27.772z"/>
<path fill="#FFFFFF" d="M95.088,23.508h-2.35v4.659c0,1.185,0.414,1.777,1.244,1.777c0.381,0,0.697-0.033,0.947-0.099l0.059,1.619 <path fill="#FFFFFF" d="M95.088,23.508h-2.35v4.659c0,1.185,0.414,1.777,1.244,1.777c0.381,0,0.697-0.033,0.947-0.099l0.059,1.619
c-0.42,0.157-0.973,0.236-1.658,0.236c-0.842,0-1.5-0.257-1.975-0.77c-0.473-0.514-0.711-1.376-0.711-2.587v-4.837h-1.4v-1.6h1.4 c-0.42,0.157-0.973,0.236-1.658,0.236c-0.842,0-1.5-0.257-1.975-0.77c-0.473-0.514-0.711-1.376-0.711-2.587v-4.837h-1.4v-1.6h1.4
v-1.757l2.094-0.632v2.389h2.35V23.508z"/> v-1.757l2.094-0.632v2.389h2.35V23.508z"/>
<path fill="#FFFFFF" d="M105.691,26.627c0,1.475-0.422,2.686-1.264,3.633c-0.883,0.975-2.055,1.461-3.516,1.461 <path fill="#FFFFFF" d="M105.691,26.627c0,1.475-0.422,2.686-1.264,3.633c-0.883,0.975-2.055,1.461-3.516,1.461
c-1.408,0-2.529-0.467-3.365-1.401s-1.254-2.113-1.254-3.534c0-1.487,0.43-2.705,1.293-3.652c0.861-0.948,2.023-1.422,3.484-1.422 c-1.408,0-2.529-0.467-3.365-1.401s-1.254-2.113-1.254-3.534c0-1.487,0.43-2.705,1.293-3.652c0.861-0.948,2.023-1.422,3.484-1.422
c1.408,0,2.541,0.467,3.396,1.402C105.283,24.021,105.691,25.192,105.691,26.627z M103.479,26.696 c1.408,0,2.541,0.467,3.396,1.402C105.283,24.021,105.691,25.192,105.691,26.627z M103.479,26.696
c0-0.885-0.189-1.644-0.572-2.277c-0.447-0.766-1.086-1.148-1.914-1.148c-0.857,0-1.508,0.383-1.955,1.148 c0-0.885-0.189-1.644-0.572-2.277c-0.447-0.766-1.086-1.148-1.914-1.148c-0.857,0-1.508,0.383-1.955,1.148
c-0.383,0.634-0.572,1.405-0.572,2.317c0,0.885,0.189,1.644,0.572,2.276c0.461,0.766,1.105,1.148,1.936,1.148 c-0.383,0.634-0.572,1.405-0.572,2.317c0,0.885,0.189,1.644,0.572,2.276c0.461,0.766,1.105,1.148,1.936,1.148
c0.814,0,1.453-0.39,1.914-1.168C103.281,28.347,103.479,27.58,103.479,26.696z"/> c0.814,0,1.453-0.39,1.914-1.168C103.281,28.347,103.479,27.58,103.479,26.696z"/>
<path fill="#FFFFFF" d="M112.621,23.783c-0.211-0.039-0.436-0.059-0.672-0.059c-0.75,0-1.33,0.283-1.738,0.85 <path fill="#FFFFFF" d="M112.621,23.783c-0.211-0.039-0.436-0.059-0.672-0.059c-0.75,0-1.33,0.283-1.738,0.85
c-0.355,0.5-0.533,1.132-0.533,1.895v5.035h-2.131l0.02-6.574c0-1.106-0.027-2.113-0.08-3.021h1.857l0.078,1.836h0.059 c-0.355,0.5-0.533,1.132-0.533,1.895v5.035h-2.131l0.02-6.574c0-1.106-0.027-2.113-0.08-3.021h1.857l0.078,1.836h0.059
c0.225-0.631,0.58-1.139,1.066-1.52c0.475-0.343,0.988-0.514,1.541-0.514c0.197,0,0.375,0.014,0.533,0.039V23.783z"/> c0.225-0.631,0.58-1.139,1.066-1.52c0.475-0.343,0.988-0.514,1.541-0.514c0.197,0,0.375,0.014,0.533,0.039V23.783z"/>
<path fill="#FFFFFF" d="M122.156,26.252c0,0.382-0.025,0.704-0.078,0.967h-6.396c0.025,0.948,0.334,1.673,0.928,2.173 <path fill="#FFFFFF" d="M122.156,26.252c0,0.382-0.025,0.704-0.078,0.967h-6.396c0.025,0.948,0.334,1.673,0.928,2.173
c0.539,0.447,1.236,0.671,2.092,0.671c0.947,0,1.811-0.151,2.588-0.454l0.334,1.48c-0.908,0.396-1.98,0.593-3.217,0.593 c0.539,0.447,1.236,0.671,2.092,0.671c0.947,0,1.811-0.151,2.588-0.454l0.334,1.48c-0.908,0.396-1.98,0.593-3.217,0.593
c-1.488,0-2.656-0.438-3.506-1.313c-0.848-0.875-1.273-2.05-1.273-3.524c0-1.447,0.395-2.652,1.186-3.613 c-1.488,0-2.656-0.438-3.506-1.313c-0.848-0.875-1.273-2.05-1.273-3.524c0-1.447,0.395-2.652,1.186-3.613
c0.828-1.026,1.947-1.539,3.355-1.539c1.383,0,2.43,0.513,3.141,1.539C121.873,24.047,122.156,25.055,122.156,26.252z c0.828-1.026,1.947-1.539,3.355-1.539c1.383,0,2.43,0.513,3.141,1.539C121.873,24.047,122.156,25.055,122.156,26.252z
M120.123,25.699c0.014-0.632-0.125-1.178-0.414-1.639c-0.369-0.593-0.936-0.889-1.699-0.889c-0.697,0-1.264,0.289-1.697,0.869 M120.123,25.699c0.014-0.632-0.125-1.178-0.414-1.639c-0.369-0.593-0.936-0.889-1.699-0.889c-0.697,0-1.264,0.289-1.697,0.869
c-0.355,0.461-0.566,1.014-0.631,1.658H120.123z"/> c-0.355,0.461-0.566,1.014-0.631,1.658H120.123z"/>
</g> </g>
<g> <g>
<g> <g>
<path fill="#FFFFFF" d="M49.05,10.009c0,1.177-0.353,2.063-1.058,2.658c-0.653,0.549-1.581,0.824-2.783,0.824 <path fill="#FFFFFF" d="M49.05,10.009c0,1.177-0.353,2.063-1.058,2.658c-0.653,0.549-1.581,0.824-2.783,0.824
c-0.596,0-1.106-0.026-1.533-0.078V6.982c0.557-0.09,1.157-0.136,1.805-0.136c1.145,0,2.008,0.249,2.59,0.747 c-0.596,0-1.106-0.026-1.533-0.078V6.982c0.557-0.09,1.157-0.136,1.805-0.136c1.145,0,2.008,0.249,2.59,0.747
C48.723,8.156,49.05,8.961,49.05,10.009z M47.945,10.038c0-0.763-0.202-1.348-0.606-1.756c-0.404-0.407-0.994-0.611-1.771-0.611 C48.723,8.156,49.05,8.961,49.05,10.009z M47.945,10.038c0-0.763-0.202-1.348-0.606-1.756c-0.404-0.407-0.994-0.611-1.771-0.611
c-0.33,0-0.611,0.022-0.844,0.068v4.889c0.129,0.02,0.365,0.029,0.708,0.029c0.802,0,1.421-0.223,1.857-0.669 c-0.33,0-0.611,0.022-0.844,0.068v4.889c0.129,0.02,0.365,0.029,0.708,0.029c0.802,0,1.421-0.223,1.857-0.669
S47.945,10.892,47.945,10.038z"/> S47.945,10.892,47.945,10.038z"/>
<path fill="#FFFFFF" d="M54.909,11.037c0,0.725-0.207,1.319-0.621,1.785c-0.434,0.479-1.009,0.718-1.727,0.718 <path fill="#FFFFFF" d="M54.909,11.037c0,0.725-0.207,1.319-0.621,1.785c-0.434,0.479-1.009,0.718-1.727,0.718
c-0.692,0-1.243-0.229-1.654-0.689c-0.41-0.459-0.615-1.038-0.615-1.736c0-0.73,0.211-1.329,0.635-1.794s0.994-0.698,1.712-0.698 c-0.692,0-1.243-0.229-1.654-0.689c-0.41-0.459-0.615-1.038-0.615-1.736c0-0.73,0.211-1.329,0.635-1.794s0.994-0.698,1.712-0.698
c0.692,0,1.248,0.229,1.669,0.688C54.708,9.757,54.909,10.333,54.909,11.037z M53.822,11.071c0-0.435-0.094-0.808-0.281-1.119 c0.692,0,1.248,0.229,1.669,0.688C54.708,9.757,54.909,10.333,54.909,11.037z M53.822,11.071c0-0.435-0.094-0.808-0.281-1.119
c-0.22-0.376-0.533-0.564-0.94-0.564c-0.421,0-0.741,0.188-0.961,0.564c-0.188,0.311-0.281,0.69-0.281,1.138 c-0.22-0.376-0.533-0.564-0.94-0.564c-0.421,0-0.741,0.188-0.961,0.564c-0.188,0.311-0.281,0.69-0.281,1.138
c0,0.435,0.094,0.808,0.281,1.119c0.227,0.376,0.543,0.564,0.951,0.564c0.4,0,0.714-0.191,0.94-0.574 c0,0.435,0.094,0.808,0.281,1.119c0.227,0.376,0.543,0.564,0.951,0.564c0.4,0,0.714-0.191,0.94-0.574
C53.725,11.882,53.822,11.506,53.822,11.071z"/> C53.725,11.882,53.822,11.506,53.822,11.071z"/>
<path fill="#FFFFFF" d="M62.765,8.719l-1.475,4.714h-0.96l-0.611-2.047c-0.155-0.511-0.281-1.019-0.379-1.523h-0.019 <path fill="#FFFFFF" d="M62.765,8.719l-1.475,4.714h-0.96l-0.611-2.047c-0.155-0.511-0.281-1.019-0.379-1.523h-0.019
c-0.091,0.518-0.217,1.025-0.379,1.523l-0.649,2.047h-0.971l-1.387-4.714h1.077l0.533,2.241c0.129,0.53,0.235,1.035,0.32,1.513 c-0.091,0.518-0.217,1.025-0.379,1.523l-0.649,2.047h-0.971l-1.387-4.714h1.077l0.533,2.241c0.129,0.53,0.235,1.035,0.32,1.513
h0.019c0.078-0.394,0.207-0.896,0.389-1.503l0.669-2.25h0.854l0.641,2.202c0.155,0.537,0.281,1.054,0.378,1.552h0.029 h0.019c0.078-0.394,0.207-0.896,0.389-1.503l0.669-2.25h0.854l0.641,2.202c0.155,0.537,0.281,1.054,0.378,1.552h0.029
c0.071-0.485,0.178-1.002,0.32-1.552l0.572-2.202H62.765z"/> c0.071-0.485,0.178-1.002,0.32-1.552l0.572-2.202H62.765z"/>
<path fill="#FFFFFF" d="M68.198,13.433H67.15v-2.7c0-0.832-0.316-1.248-0.95-1.248c-0.311,0-0.562,0.114-0.757,0.343 <path fill="#FFFFFF" d="M68.198,13.433H67.15v-2.7c0-0.832-0.316-1.248-0.95-1.248c-0.311,0-0.562,0.114-0.757,0.343
c-0.193,0.229-0.291,0.499-0.291,0.808v2.796h-1.048v-3.366c0-0.414-0.013-0.863-0.038-1.349h0.921l0.049,0.737h0.029 c-0.193,0.229-0.291,0.499-0.291,0.808v2.796h-1.048v-3.366c0-0.414-0.013-0.863-0.038-1.349h0.921l0.049,0.737h0.029
c0.122-0.229,0.304-0.418,0.543-0.569c0.284-0.176,0.602-0.265,0.95-0.265c0.44,0,0.806,0.142,1.097,0.427 c0.122-0.229,0.304-0.418,0.543-0.569c0.284-0.176,0.602-0.265,0.95-0.265c0.44,0,0.806,0.142,1.097,0.427
c0.362,0.349,0.543,0.87,0.543,1.562V13.433z"/> c0.362,0.349,0.543,0.87,0.543,1.562V13.433z"/>
<path fill="#FFFFFF" d="M71.088,13.433h-1.047V6.556h1.047V13.433z"/> <path fill="#FFFFFF" d="M71.088,13.433h-1.047V6.556h1.047V13.433z"/>
<path fill="#FFFFFF" d="M77.258,11.037c0,0.725-0.207,1.319-0.621,1.785c-0.434,0.479-1.01,0.718-1.727,0.718 <path fill="#FFFFFF" d="M77.258,11.037c0,0.725-0.207,1.319-0.621,1.785c-0.434,0.479-1.01,0.718-1.727,0.718
c-0.693,0-1.244-0.229-1.654-0.689c-0.41-0.459-0.615-1.038-0.615-1.736c0-0.73,0.211-1.329,0.635-1.794s0.994-0.698,1.711-0.698 c-0.693,0-1.244-0.229-1.654-0.689c-0.41-0.459-0.615-1.038-0.615-1.736c0-0.73,0.211-1.329,0.635-1.794s0.994-0.698,1.711-0.698
c0.693,0,1.248,0.229,1.67,0.688C77.057,9.757,77.258,10.333,77.258,11.037z M76.17,11.071c0-0.435-0.094-0.808-0.281-1.119 c0.693,0,1.248,0.229,1.67,0.688C77.057,9.757,77.258,10.333,77.258,11.037z M76.17,11.071c0-0.435-0.094-0.808-0.281-1.119
c-0.219-0.376-0.533-0.564-0.939-0.564c-0.422,0-0.742,0.188-0.961,0.564c-0.188,0.311-0.281,0.69-0.281,1.138 c-0.219-0.376-0.533-0.564-0.939-0.564c-0.422,0-0.742,0.188-0.961,0.564c-0.188,0.311-0.281,0.69-0.281,1.138
c0,0.435,0.094,0.808,0.281,1.119c0.227,0.376,0.543,0.564,0.951,0.564c0.4,0,0.713-0.191,0.939-0.574 c0,0.435,0.094,0.808,0.281,1.119c0.227,0.376,0.543,0.564,0.951,0.564c0.4,0,0.713-0.191,0.939-0.574
C76.074,11.882,76.17,11.506,76.17,11.071z"/> C76.074,11.882,76.17,11.506,76.17,11.071z"/>
<path fill="#FFFFFF" d="M82.33,13.433h-0.941l-0.078-0.543h-0.029c-0.322,0.433-0.781,0.65-1.377,0.65 <path fill="#FFFFFF" d="M82.33,13.433h-0.941l-0.078-0.543h-0.029c-0.322,0.433-0.781,0.65-1.377,0.65
c-0.445,0-0.805-0.143-1.076-0.427c-0.246-0.258-0.369-0.579-0.369-0.96c0-0.576,0.24-1.015,0.723-1.319 c-0.445,0-0.805-0.143-1.076-0.427c-0.246-0.258-0.369-0.579-0.369-0.96c0-0.576,0.24-1.015,0.723-1.319
c0.482-0.304,1.16-0.453,2.033-0.446V10.3c0-0.621-0.326-0.931-0.979-0.931c-0.465,0-0.875,0.117-1.229,0.349l-0.213-0.688 c0.482-0.304,1.16-0.453,2.033-0.446V10.3c0-0.621-0.326-0.931-0.979-0.931c-0.465,0-0.875,0.117-1.229,0.349l-0.213-0.688
c0.438-0.271,0.979-0.407,1.617-0.407c1.232,0,1.85,0.65,1.85,1.95v1.736C82.262,12.78,82.285,13.155,82.33,13.433z c0.438-0.271,0.979-0.407,1.617-0.407c1.232,0,1.85,0.65,1.85,1.95v1.736C82.262,12.78,82.285,13.155,82.33,13.433z
M81.242,11.813v-0.727c-1.156-0.02-1.734,0.297-1.734,0.95c0,0.246,0.066,0.43,0.201,0.553c0.135,0.123,0.307,0.184,0.512,0.184 M81.242,11.813v-0.727c-1.156-0.02-1.734,0.297-1.734,0.95c0,0.246,0.066,0.43,0.201,0.553c0.135,0.123,0.307,0.184,0.512,0.184
c0.23,0,0.445-0.073,0.641-0.218c0.197-0.146,0.318-0.331,0.363-0.558C81.236,11.946,81.242,11.884,81.242,11.813z"/> c0.23,0,0.445-0.073,0.641-0.218c0.197-0.146,0.318-0.331,0.363-0.558C81.236,11.946,81.242,11.884,81.242,11.813z"/>
<path fill="#FFFFFF" d="M88.285,13.433h-0.93l-0.049-0.757h-0.029c-0.297,0.576-0.803,0.864-1.514,0.864 <path fill="#FFFFFF" d="M88.285,13.433h-0.93l-0.049-0.757h-0.029c-0.297,0.576-0.803,0.864-1.514,0.864
c-0.568,0-1.041-0.223-1.416-0.669s-0.562-1.025-0.562-1.736c0-0.763,0.203-1.381,0.611-1.853c0.395-0.44,0.879-0.66,1.455-0.66 c-0.568,0-1.041-0.223-1.416-0.669s-0.562-1.025-0.562-1.736c0-0.763,0.203-1.381,0.611-1.853c0.395-0.44,0.879-0.66,1.455-0.66
c0.633,0,1.076,0.213,1.328,0.64h0.02V6.556h1.049v5.607C88.248,12.622,88.26,13.045,88.285,13.433z M87.199,11.445v-0.786 c0.633,0,1.076,0.213,1.328,0.64h0.02V6.556h1.049v5.607C88.248,12.622,88.26,13.045,88.285,13.433z M87.199,11.445v-0.786
c0-0.136-0.01-0.246-0.029-0.33c-0.059-0.252-0.186-0.464-0.379-0.635c-0.195-0.171-0.43-0.257-0.701-0.257 c0-0.136-0.01-0.246-0.029-0.33c-0.059-0.252-0.186-0.464-0.379-0.635c-0.195-0.171-0.43-0.257-0.701-0.257
c-0.391,0-0.697,0.155-0.922,0.466c-0.223,0.311-0.336,0.708-0.336,1.193c0,0.466,0.107,0.844,0.322,1.135 c-0.391,0-0.697,0.155-0.922,0.466c-0.223,0.311-0.336,0.708-0.336,1.193c0,0.466,0.107,0.844,0.322,1.135
c0.227,0.31,0.533,0.465,0.916,0.465c0.344,0,0.619-0.129,0.828-0.388C87.1,12.069,87.199,11.781,87.199,11.445z"/> c0.227,0.31,0.533,0.465,0.916,0.465c0.344,0,0.619-0.129,0.828-0.388C87.1,12.069,87.199,11.781,87.199,11.445z"/>
<path fill="#FFFFFF" d="M97.248,11.037c0,0.725-0.207,1.319-0.621,1.785c-0.434,0.479-1.008,0.718-1.727,0.718 <path fill="#FFFFFF" d="M97.248,11.037c0,0.725-0.207,1.319-0.621,1.785c-0.434,0.479-1.008,0.718-1.727,0.718
c-0.691,0-1.242-0.229-1.654-0.689c-0.41-0.459-0.615-1.038-0.615-1.736c0-0.73,0.211-1.329,0.635-1.794s0.994-0.698,1.713-0.698 c-0.691,0-1.242-0.229-1.654-0.689c-0.41-0.459-0.615-1.038-0.615-1.736c0-0.73,0.211-1.329,0.635-1.794s0.994-0.698,1.713-0.698
c0.691,0,1.248,0.229,1.668,0.688C97.047,9.757,97.248,10.333,97.248,11.037z M96.162,11.071c0-0.435-0.094-0.808-0.281-1.119 c0.691,0,1.248,0.229,1.668,0.688C97.047,9.757,97.248,10.333,97.248,11.037z M96.162,11.071c0-0.435-0.094-0.808-0.281-1.119
c-0.221-0.376-0.533-0.564-0.941-0.564c-0.42,0-0.74,0.188-0.961,0.564c-0.188,0.311-0.281,0.69-0.281,1.138 c-0.221-0.376-0.533-0.564-0.941-0.564c-0.42,0-0.74,0.188-0.961,0.564c-0.188,0.311-0.281,0.69-0.281,1.138
c0,0.435,0.094,0.808,0.281,1.119c0.227,0.376,0.543,0.564,0.951,0.564c0.4,0,0.715-0.191,0.941-0.574 c0,0.435,0.094,0.808,0.281,1.119c0.227,0.376,0.543,0.564,0.951,0.564c0.4,0,0.715-0.191,0.941-0.574
C96.064,11.882,96.162,11.506,96.162,11.071z"/> C96.064,11.882,96.162,11.506,96.162,11.071z"/>
<path fill="#FFFFFF" d="M102.883,13.433h-1.047v-2.7c0-0.832-0.316-1.248-0.951-1.248c-0.311,0-0.562,0.114-0.756,0.343 <path fill="#FFFFFF" d="M102.883,13.433h-1.047v-2.7c0-0.832-0.316-1.248-0.951-1.248c-0.311,0-0.562,0.114-0.756,0.343
s-0.291,0.499-0.291,0.808v2.796h-1.049v-3.366c0-0.414-0.012-0.863-0.037-1.349h0.92l0.049,0.737h0.029 s-0.291,0.499-0.291,0.808v2.796h-1.049v-3.366c0-0.414-0.012-0.863-0.037-1.349h0.92l0.049,0.737h0.029
c0.123-0.229,0.305-0.418,0.543-0.569c0.285-0.176,0.602-0.265,0.951-0.265c0.439,0,0.805,0.142,1.096,0.427 c0.123-0.229,0.305-0.418,0.543-0.569c0.285-0.176,0.602-0.265,0.951-0.265c0.439,0,0.805,0.142,1.096,0.427
c0.363,0.349,0.543,0.87,0.543,1.562V13.433z"/> c0.363,0.349,0.543,0.87,0.543,1.562V13.433z"/>
<path fill="#FFFFFF" d="M109.936,9.504h-1.154v2.29c0,0.582,0.205,0.873,0.611,0.873c0.188,0,0.344-0.016,0.467-0.049 <path fill="#FFFFFF" d="M109.936,9.504h-1.154v2.29c0,0.582,0.205,0.873,0.611,0.873c0.188,0,0.344-0.016,0.467-0.049
l0.027,0.795c-0.207,0.078-0.479,0.117-0.814,0.117c-0.414,0-0.736-0.126-0.969-0.378c-0.234-0.252-0.35-0.676-0.35-1.271V9.504 l0.027,0.795c-0.207,0.078-0.479,0.117-0.814,0.117c-0.414,0-0.736-0.126-0.969-0.378c-0.234-0.252-0.35-0.676-0.35-1.271V9.504
h-0.689V8.719h0.689V7.855l1.027-0.31v1.173h1.154V9.504z"/> h-0.689V8.719h0.689V7.855l1.027-0.31v1.173h1.154V9.504z"/>
<path fill="#FFFFFF" d="M115.484,13.433h-1.049v-2.68c0-0.845-0.316-1.268-0.949-1.268c-0.486,0-0.818,0.245-1,0.735 <path fill="#FFFFFF" d="M115.484,13.433h-1.049v-2.68c0-0.845-0.316-1.268-0.949-1.268c-0.486,0-0.818,0.245-1,0.735
c-0.031,0.103-0.049,0.229-0.049,0.377v2.835h-1.047V6.556h1.047v2.841h0.02c0.33-0.517,0.803-0.775,1.416-0.775 c-0.031,0.103-0.049,0.229-0.049,0.377v2.835h-1.047V6.556h1.047v2.841h0.02c0.33-0.517,0.803-0.775,1.416-0.775
c0.434,0,0.793,0.142,1.078,0.427c0.355,0.355,0.533,0.883,0.533,1.581V13.433z"/> c0.434,0,0.793,0.142,1.078,0.427c0.355,0.355,0.533,0.883,0.533,1.581V13.433z"/>
<path fill="#FFFFFF" d="M121.207,10.853c0,0.188-0.014,0.346-0.039,0.475h-3.143c0.014,0.466,0.164,0.821,0.455,1.067 <path fill="#FFFFFF" d="M121.207,10.853c0,0.188-0.014,0.346-0.039,0.475h-3.143c0.014,0.466,0.164,0.821,0.455,1.067
c0.266,0.22,0.609,0.33,1.029,0.33c0.465,0,0.889-0.074,1.271-0.223l0.164,0.728c-0.447,0.194-0.973,0.291-1.582,0.291 c0.266,0.22,0.609,0.33,1.029,0.33c0.465,0,0.889-0.074,1.271-0.223l0.164,0.728c-0.447,0.194-0.973,0.291-1.582,0.291
c-0.73,0-1.305-0.215-1.721-0.645c-0.418-0.43-0.625-1.007-0.625-1.731c0-0.711,0.193-1.303,0.582-1.775 c-0.73,0-1.305-0.215-1.721-0.645c-0.418-0.43-0.625-1.007-0.625-1.731c0-0.711,0.193-1.303,0.582-1.775
c0.406-0.504,0.955-0.756,1.648-0.756c0.678,0,1.193,0.252,1.541,0.756C121.068,9.77,121.207,10.265,121.207,10.853z c0.406-0.504,0.955-0.756,1.648-0.756c0.678,0,1.193,0.252,1.541,0.756C121.068,9.77,121.207,10.265,121.207,10.853z
M120.207,10.582c0.008-0.311-0.061-0.579-0.203-0.805c-0.182-0.291-0.459-0.437-0.834-0.437c-0.342,0-0.621,0.142-0.834,0.427 M120.207,10.582c0.008-0.311-0.061-0.579-0.203-0.805c-0.182-0.291-0.459-0.437-0.834-0.437c-0.342,0-0.621,0.142-0.834,0.427
c-0.174,0.227-0.277,0.498-0.311,0.815H120.207z"/> c-0.174,0.227-0.277,0.498-0.311,0.815H120.207z"/>
</g> </g>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,21 +1,21 @@
<?xml version="1.0" encoding="iso-8859-1"?> <?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="UserSvg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" <svg version="1.1" id="UserSvg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 55 55" style="enable-background:new 0 0 55 55;" xml:space="preserve"> viewBox="0 0 55 55" style="enable-background:new 0 0 55 55;" xml:space="preserve">
<path d="M55,27.5C55,12.337,42.663,0,27.5,0S0,12.337,0,27.5c0,8.009,3.444,15.228,8.926,20.258l-0.026,0.023l0.892,0.752 <path d="M55,27.5C55,12.337,42.663,0,27.5,0S0,12.337,0,27.5c0,8.009,3.444,15.228,8.926,20.258l-0.026,0.023l0.892,0.752
c0.058,0.049,0.121,0.089,0.179,0.137c0.474,0.393,0.965,0.766,1.465,1.127c0.162,0.117,0.324,0.234,0.489,0.348 c0.058,0.049,0.121,0.089,0.179,0.137c0.474,0.393,0.965,0.766,1.465,1.127c0.162,0.117,0.324,0.234,0.489,0.348
c0.534,0.368,1.082,0.717,1.642,1.048c0.122,0.072,0.245,0.142,0.368,0.212c0.613,0.349,1.239,0.678,1.88,0.98 c0.534,0.368,1.082,0.717,1.642,1.048c0.122,0.072,0.245,0.142,0.368,0.212c0.613,0.349,1.239,0.678,1.88,0.98
c0.047,0.022,0.095,0.042,0.142,0.064c2.089,0.971,4.319,1.684,6.651,2.105c0.061,0.011,0.122,0.022,0.184,0.033 c0.047,0.022,0.095,0.042,0.142,0.064c2.089,0.971,4.319,1.684,6.651,2.105c0.061,0.011,0.122,0.022,0.184,0.033
c0.724,0.125,1.456,0.225,2.197,0.292c0.09,0.008,0.18,0.013,0.271,0.021C25.998,54.961,26.744,55,27.5,55 c0.724,0.125,1.456,0.225,2.197,0.292c0.09,0.008,0.18,0.013,0.271,0.021C25.998,54.961,26.744,55,27.5,55
c0.749,0,1.488-0.039,2.222-0.098c0.093-0.008,0.186-0.013,0.279-0.021c0.735-0.067,1.461-0.164,2.178-0.287 c0.749,0,1.488-0.039,2.222-0.098c0.093-0.008,0.186-0.013,0.279-0.021c0.735-0.067,1.461-0.164,2.178-0.287
c0.062-0.011,0.125-0.022,0.187-0.034c2.297-0.412,4.495-1.109,6.557-2.055c0.076-0.035,0.153-0.068,0.229-0.104 c0.062-0.011,0.125-0.022,0.187-0.034c2.297-0.412,4.495-1.109,6.557-2.055c0.076-0.035,0.153-0.068,0.229-0.104
c0.617-0.29,1.22-0.603,1.811-0.936c0.147-0.083,0.293-0.167,0.439-0.253c0.538-0.317,1.067-0.648,1.581-1 c0.617-0.29,1.22-0.603,1.811-0.936c0.147-0.083,0.293-0.167,0.439-0.253c0.538-0.317,1.067-0.648,1.581-1
c0.185-0.126,0.366-0.259,0.549-0.391c0.439-0.316,0.87-0.642,1.289-0.983c0.093-0.075,0.193-0.14,0.284-0.217l0.915-0.764 c0.185-0.126,0.366-0.259,0.549-0.391c0.439-0.316,0.87-0.642,1.289-0.983c0.093-0.075,0.193-0.14,0.284-0.217l0.915-0.764
l-0.027-0.023C51.523,42.802,55,35.55,55,27.5z M2,27.5C2,13.439,13.439,2,27.5,2S53,13.439,53,27.5 l-0.027-0.023C51.523,42.802,55,35.55,55,27.5z M2,27.5C2,13.439,13.439,2,27.5,2S53,13.439,53,27.5
c0,7.577-3.325,14.389-8.589,19.063c-0.294-0.203-0.59-0.385-0.893-0.537l-8.467-4.233c-0.76-0.38-1.232-1.144-1.232-1.993v-2.957 c0,7.577-3.325,14.389-8.589,19.063c-0.294-0.203-0.59-0.385-0.893-0.537l-8.467-4.233c-0.76-0.38-1.232-1.144-1.232-1.993v-2.957
c0.196-0.242,0.403-0.516,0.617-0.817c1.096-1.548,1.975-3.27,2.616-5.123c1.267-0.602,2.085-1.864,2.085-3.289v-3.545 c0.196-0.242,0.403-0.516,0.617-0.817c1.096-1.548,1.975-3.27,2.616-5.123c1.267-0.602,2.085-1.864,2.085-3.289v-3.545
c0-0.867-0.318-1.708-0.887-2.369v-4.667c0.052-0.52,0.236-3.448-1.883-5.864C34.524,9.065,31.541,8,27.5,8 c0-0.867-0.318-1.708-0.887-2.369v-4.667c0.052-0.52,0.236-3.448-1.883-5.864C34.524,9.065,31.541,8,27.5,8
s-7.024,1.065-8.867,3.168c-2.119,2.416-1.935,5.346-1.883,5.864v4.667c-0.568,0.661-0.887,1.502-0.887,2.369v3.545 s-7.024,1.065-8.867,3.168c-2.119,2.416-1.935,5.346-1.883,5.864v4.667c-0.568,0.661-0.887,1.502-0.887,2.369v3.545
c0,1.101,0.494,2.128,1.34,2.821c0.81,3.173,2.477,5.575,3.093,6.389v2.894c0,0.816-0.445,1.566-1.162,1.958l-7.907,4.313 c0,1.101,0.494,2.128,1.34,2.821c0.81,3.173,2.477,5.575,3.093,6.389v2.894c0,0.816-0.445,1.566-1.162,1.958l-7.907,4.313
c-0.252,0.137-0.502,0.297-0.752,0.476C5.276,41.792,2,35.022,2,27.5z"/> c-0.252,0.137-0.502,0.297-0.752,0.476C5.276,41.792,2,35.022,2,27.5z"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB