mirror of
https://github.com/gophish/gophish
synced 2024-11-14 00:07:19 +00:00
Added support for templating attachments (#1936)
The following attachment types support template variables: docx, docm, pptx, xlsx, xlsm, txt, html, ics.
This commit is contained in:
parent
0646f14c99
commit
a6627dfc6b
22 changed files with 441 additions and 11 deletions
|
@ -1,11 +1,156 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Attachment contains the fields and methods for
|
||||
// an email attachment
|
||||
type Attachment struct {
|
||||
Id int64 `json:"-"`
|
||||
TemplateId int64 `json:"-"`
|
||||
Content string `json:"content"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Id int64 `json:"-"`
|
||||
TemplateId int64 `json:"-"`
|
||||
Content string `json:"content"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
vanillaFile bool // Vanilla file has no template variables
|
||||
}
|
||||
|
||||
// Validate ensures that the provided attachment uses the supported template variables correctly.
|
||||
func (a Attachment) Validate() error {
|
||||
vc := ValidationContext{
|
||||
FromAddress: "foo@bar.com",
|
||||
BaseURL: "http://example.com",
|
||||
}
|
||||
td := Result{
|
||||
BaseRecipient: BaseRecipient{
|
||||
Email: "foo@bar.com",
|
||||
FirstName: "Foo",
|
||||
LastName: "Bar",
|
||||
Position: "Test",
|
||||
},
|
||||
RId: "123456",
|
||||
}
|
||||
ptx, err := NewPhishingTemplateContext(vc, td.BaseRecipient, td.RId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = a.ApplyTemplate(ptx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ApplyTemplate parses different attachment files and applies the supplied phishing template.
|
||||
func (a *Attachment) ApplyTemplate(ptx PhishingTemplateContext) (io.Reader, error) {
|
||||
|
||||
decodedAttachment := base64.NewDecoder(base64.StdEncoding, strings.NewReader(a.Content))
|
||||
|
||||
// If we've already determined there are no template variables in this attachment return it immediately
|
||||
if a.vanillaFile == true {
|
||||
return decodedAttachment, nil
|
||||
}
|
||||
|
||||
// Decided to use the file extension rather than the content type, as there seems to be quite
|
||||
// a bit of variability with types. e.g sometimes a Word docx file would have:
|
||||
// "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
fileExtension := filepath.Ext(a.Name)
|
||||
|
||||
switch fileExtension {
|
||||
|
||||
case ".docx", ".docm", ".pptx", ".xlsx", ".xlsm":
|
||||
// Most modern office formats are xml based and can be unarchived.
|
||||
// .docm and .xlsm files are comprised of xml, and a binary blob for the macro code
|
||||
|
||||
// Zip archives require random access for reading, so it's hard to stream bytes. Solution seems to be to use a buffer.
|
||||
// See https://stackoverflow.com/questions/16946978/how-to-unzip-io-readcloser
|
||||
b := new(bytes.Buffer)
|
||||
b.ReadFrom(decodedAttachment)
|
||||
zipReader, err := zip.NewReader(bytes.NewReader(b.Bytes()), int64(b.Len())) // Create a new zip reader from the file
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newZipArchive := new(bytes.Buffer)
|
||||
zipWriter := zip.NewWriter(newZipArchive) // For writing the new archive
|
||||
|
||||
// i. Read each file from the Word document archive
|
||||
// ii. Apply the template to it
|
||||
// iii. Add the templated content to a new zip Word archive
|
||||
a.vanillaFile = true
|
||||
for _, zipFile := range zipReader.File {
|
||||
ff, err := zipFile.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer ff.Close()
|
||||
contents, err := ioutil.ReadAll(ff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subFileExtension := filepath.Ext(zipFile.Name)
|
||||
var tFile string
|
||||
if subFileExtension == ".xml" || subFileExtension == ".rels" { // Ignore other files, e.g binary ones and images
|
||||
// First we look for instances where Word has URL escaped our template variables. This seems to happen when inserting a remote image, converting {{.Foo}} to %7b%7b.foo%7d%7d.
|
||||
// See https://stackoverflow.com/questions/68287630/disable-url-encoding-for-includepicture-in-microsoft-word
|
||||
rx, _ := regexp.Compile("%7b%7b.([a-zA-Z]+)%7d%7d")
|
||||
contents := rx.ReplaceAllFunc(contents, func(m []byte) []byte {
|
||||
d, err := url.QueryUnescape(string(m))
|
||||
if err != nil {
|
||||
return m
|
||||
}
|
||||
return []byte(d)
|
||||
})
|
||||
|
||||
// For each file apply the template.
|
||||
tFile, err = ExecuteTemplate(string(contents), ptx)
|
||||
if err != nil {
|
||||
zipWriter.Close() // Don't use defer when writing files https://www.joeshaw.org/dont-defer-close-on-writable-files/
|
||||
return nil, err
|
||||
}
|
||||
// Check if the subfile changed. We only need this to be set once to know in the future to check the 'parent' file
|
||||
if tFile != string(contents) {
|
||||
a.vanillaFile = false
|
||||
}
|
||||
} else {
|
||||
tFile = string(contents) // Could move this to the declaration of tFile, but might be confusing to read
|
||||
}
|
||||
// Write new Word archive
|
||||
newZipFile, err := zipWriter.Create(zipFile.Name)
|
||||
if err != nil {
|
||||
zipWriter.Close() // Don't use defer when writing files https://www.joeshaw.org/dont-defer-close-on-writable-files/
|
||||
return nil, err
|
||||
}
|
||||
_, err = newZipFile.Write([]byte(tFile))
|
||||
if err != nil {
|
||||
zipWriter.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
zipWriter.Close()
|
||||
return bytes.NewReader(newZipArchive.Bytes()), err
|
||||
|
||||
case ".txt", ".html", ".ics":
|
||||
b, err := ioutil.ReadAll(decodedAttachment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
processedAttachment, err := ExecuteTemplate(string(b), ptx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if processedAttachment == string(b) {
|
||||
a.vanillaFile = true
|
||||
}
|
||||
return strings.NewReader(processedAttachment), nil
|
||||
default:
|
||||
return decodedAttachment, nil // Default is to simply return the file
|
||||
}
|
||||
|
||||
}
|
||||
|
|
82
models/attachment_test.go
Normal file
82
models/attachment_test.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
func (s *ModelsSuite) TestAttachment(c *check.C) {
|
||||
ptx := PhishingTemplateContext{
|
||||
BaseRecipient: BaseRecipient{
|
||||
FirstName: "Foo",
|
||||
LastName: "Bar",
|
||||
Email: "foo@bar.com",
|
||||
Position: "Space Janitor",
|
||||
},
|
||||
BaseURL: "http://testurl.com",
|
||||
URL: "http://testurl.com/?rid=1234567",
|
||||
TrackingURL: "http://testurl.local/track?rid=1234567",
|
||||
Tracker: "<img alt='' style='display: none' src='http://testurl.local/track?rid=1234567'/>",
|
||||
From: "From Address",
|
||||
RId: "1234567",
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir("testdata")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open attachment folder 'testdata': %v\n", err)
|
||||
}
|
||||
for _, ff := range files {
|
||||
if !ff.IsDir() && !strings.Contains(ff.Name(), "templated") {
|
||||
fname := ff.Name()
|
||||
fmt.Printf("Checking attachment file -> %s\n", fname)
|
||||
data := readFile("testdata/" + fname)
|
||||
if filepath.Ext(fname) == ".b64" {
|
||||
fname = fname[:len(fname)-4]
|
||||
}
|
||||
a := Attachment{
|
||||
Content: data,
|
||||
Name: fname,
|
||||
}
|
||||
t, err := a.ApplyTemplate(ptx)
|
||||
c.Assert(err, check.Equals, nil)
|
||||
c.Assert(a.vanillaFile, check.Equals, strings.Contains(fname, "without-vars"))
|
||||
c.Assert(a.vanillaFile, check.Not(check.Equals), strings.Contains(fname, "with-vars"))
|
||||
|
||||
// Verfify template was applied as expected
|
||||
tt, err := ioutil.ReadAll(t)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse templated file '%s': %v\n", fname, err)
|
||||
}
|
||||
templatedFile := base64.StdEncoding.EncodeToString(tt)
|
||||
expectedOutput := readFile("testdata/" + strings.TrimSuffix(ff.Name(), filepath.Ext(ff.Name())) + ".templated" + filepath.Ext(ff.Name())) // e.g text-file-with-vars.templated.txt
|
||||
c.Assert(templatedFile, check.Equals, expectedOutput)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readFile(fname string) string {
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open file '%s': %v\n", fname, err)
|
||||
}
|
||||
reader := bufio.NewReader(f)
|
||||
content, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read file '%s': %v\n", fname, err)
|
||||
}
|
||||
data := ""
|
||||
if filepath.Ext(fname) == ".b64" {
|
||||
data = string(content)
|
||||
} else {
|
||||
data = base64.StdEncoding.EncodeToString(content)
|
||||
}
|
||||
return data
|
||||
}
|
|
@ -2,7 +2,6 @@ package models
|
|||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -10,7 +9,6 @@ import (
|
|||
"math/big"
|
||||
"net/mail"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gophish/gomail"
|
||||
|
@ -211,6 +209,7 @@ func (m *MailLog) Generate(msg *gomail.Message) error {
|
|||
|
||||
// Parse remaining templates
|
||||
subject, err := ExecuteTemplate(c.Template.Subject, ptx)
|
||||
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
|
@ -239,12 +238,16 @@ func (m *MailLog) Generate(msg *gomail.Message) error {
|
|||
}
|
||||
}
|
||||
// Attach the files
|
||||
for _, a := range c.Template.Attachments {
|
||||
msg.Attach(func(a Attachment) (string, gomail.FileSetting, gomail.FileSetting) {
|
||||
for i, _ := range c.Template.Attachments {
|
||||
a := &c.Template.Attachments[i]
|
||||
msg.Attach(func(a *Attachment) (string, gomail.FileSetting, gomail.FileSetting) {
|
||||
h := map[string][]string{"Content-ID": {fmt.Sprintf("<%s>", a.Name)}}
|
||||
return a.Name, gomail.SetCopyFunc(func(w io.Writer) error {
|
||||
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(a.Content))
|
||||
_, err = io.Copy(w, decoder)
|
||||
content, err := a.ApplyTemplate(ptx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(w, content)
|
||||
return err
|
||||
}), gomail.SetHeader(h)
|
||||
}(a))
|
||||
|
|
|
@ -40,6 +40,12 @@ func (t *Template) Validate() error {
|
|||
if err := ValidateTemplate(t.Text); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, a := range t.Attachments {
|
||||
if err := a.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
51
models/testdata/calendar-file-with-vars.ics
vendored
Normal file
51
models/testdata/calendar-file-with-vars.ics
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
BEGIN:VCALENDAR
|
||||
PRODID:-//zoom.us//iCalendar Event//EN
|
||||
VERSION:2.0
|
||||
CALSCALE:GREGORIAN
|
||||
METHOD:PUBLISH
|
||||
CLASS:PUBLIC
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:Europe/London
|
||||
TZURL:http://tzurl.org/zoneinfo-outlook/Europe/London
|
||||
X-LIC-LOCATION:Europe/London
|
||||
BEGIN:DAYLIGHT
|
||||
TZOFFSETFROM:+0000
|
||||
TZOFFSETTO:+0100
|
||||
TZNAME:BST
|
||||
DTSTART:19700329T010000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
TZOFFSETFROM:+0100
|
||||
TZOFFSETTO:+0000
|
||||
TZNAME:GMT
|
||||
DTSTART:19701025T020000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20210306T182251Z
|
||||
DTSTART;TZID=Europe/London:20210306T183000
|
||||
DTEND;TZID=Europe/London:20210306T190000
|
||||
SUMMARY:Gophish Test Calendar
|
||||
UID:20210306T182251Z-89336450000@fe80:0:0:0:31:49ff:fec9:f252ens5
|
||||
TZID:Europe/London
|
||||
DESCRIPTION:Glenn Wilkinson is inviting you to a scheduled Zoom meeting.\
|
||||
n\nJoin Zoom Meeting\n{{.URL}}\n\nMeeting ID: 893 3645 9466\nPasscode: 31337\
|
||||
nOne tap mobile\n+442039017895\,\,89336450000#\,\,\,\,*509879# United Ki
|
||||
ngdom\n+441314601196\,\,89336450000#\,\,\,\,*509879# United Kingdom\n\nD
|
||||
ial by your location\n +44 203 901 7895 United Kingdom\n +
|
||||
44 131 460 1196 United Kingdom\n +44 203 051 2874 United Kingdom\
|
||||
n +44 203 481 5237 United Kingdom\n +44 203 481 5240 Unite
|
||||
d Kingdom\n +1 253 215 8782 US (Tacoma)\n +1 301 715 8592
|
||||
US (Washington DC)\n +1 312 626 6799 US (Chicago)\n +1 346
|
||||
248 7799 US (Houston)\n +1 646 558 8656 US (New York)\n +
|
||||
1 669 900 9128 US https://us02web.zoom.us/u/kpXDbMrN\n\n
|
||||
LOCATION:{{.URL}}
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT10M
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Reminder
|
||||
END:VALARM
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
51
models/testdata/calendar-file-with-vars.templated.ics
vendored
Normal file
51
models/testdata/calendar-file-with-vars.templated.ics
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
BEGIN:VCALENDAR
|
||||
PRODID:-//zoom.us//iCalendar Event//EN
|
||||
VERSION:2.0
|
||||
CALSCALE:GREGORIAN
|
||||
METHOD:PUBLISH
|
||||
CLASS:PUBLIC
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:Europe/London
|
||||
TZURL:http://tzurl.org/zoneinfo-outlook/Europe/London
|
||||
X-LIC-LOCATION:Europe/London
|
||||
BEGIN:DAYLIGHT
|
||||
TZOFFSETFROM:+0000
|
||||
TZOFFSETTO:+0100
|
||||
TZNAME:BST
|
||||
DTSTART:19700329T010000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
TZOFFSETFROM:+0100
|
||||
TZOFFSETTO:+0000
|
||||
TZNAME:GMT
|
||||
DTSTART:19701025T020000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20210306T182251Z
|
||||
DTSTART;TZID=Europe/London:20210306T183000
|
||||
DTEND;TZID=Europe/London:20210306T190000
|
||||
SUMMARY:Gophish Test Calendar
|
||||
UID:20210306T182251Z-89336450000@fe80:0:0:0:31:49ff:fec9:f252ens5
|
||||
TZID:Europe/London
|
||||
DESCRIPTION:Glenn Wilkinson is inviting you to a scheduled Zoom meeting.\
|
||||
n\nJoin Zoom Meeting\nhttp://testurl.com/?rid=1234567\n\nMeeting ID: 893 3645 9466\nPasscode: 31337\
|
||||
nOne tap mobile\n+442039017895\,\,89336450000#\,\,\,\,*509879# United Ki
|
||||
ngdom\n+441314601196\,\,89336450000#\,\,\,\,*509879# United Kingdom\n\nD
|
||||
ial by your location\n +44 203 901 7895 United Kingdom\n +
|
||||
44 131 460 1196 United Kingdom\n +44 203 051 2874 United Kingdom\
|
||||
n +44 203 481 5237 United Kingdom\n +44 203 481 5240 Unite
|
||||
d Kingdom\n +1 253 215 8782 US (Tacoma)\n +1 301 715 8592
|
||||
US (Washington DC)\n +1 312 626 6799 US (Chicago)\n +1 346
|
||||
248 7799 US (Houston)\n +1 646 558 8656 US (New York)\n +
|
||||
1 669 900 9128 US https://us02web.zoom.us/u/kpXDbMrN\n\n
|
||||
LOCATION:http://testurl.com/?rid=1234567
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT10M
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Reminder
|
||||
END:VALARM
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
1
models/testdata/excel-with-vars.xlsx.b64
vendored
Normal file
1
models/testdata/excel-with-vars.xlsx.b64
vendored
Normal file
File diff suppressed because one or more lines are too long
1
models/testdata/excel-with-vars.xlsx.templated.b64
vendored
Normal file
1
models/testdata/excel-with-vars.xlsx.templated.b64
vendored
Normal file
File diff suppressed because one or more lines are too long
13
models/testdata/html-file-with-vars.html
vendored
Normal file
13
models/testdata/html-file-with-vars.html
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
<html>
|
||||
<head><title>Page for {{.FirstName}}</title></head>
|
||||
|
||||
<body>
|
||||
|
||||
Hello {{.FirstName}} {{.LastName}} <p>
|
||||
|
||||
|
||||
Click <a href="{{.URL}}">here</a> for a legit link.
|
||||
|
||||
</body>
|
||||
{{.Tracker}}
|
||||
</html>
|
13
models/testdata/html-file-with-vars.templated.html
vendored
Normal file
13
models/testdata/html-file-with-vars.templated.html
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
<html>
|
||||
<head><title>Page for Foo</title></head>
|
||||
|
||||
<body>
|
||||
|
||||
Hello Foo Bar <p>
|
||||
|
||||
|
||||
Click <a href="http://testurl.com/?rid=1234567">here</a> for a legit link.
|
||||
|
||||
</body>
|
||||
<img alt='' style='display: none' src='http://testurl.local/track?rid=1234567'/>
|
||||
</html>
|
9
models/testdata/html-file-without-vars.html
vendored
Normal file
9
models/testdata/html-file-without-vars.html
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
<html>
|
||||
<head><title>There are no variables here.</title></head>
|
||||
|
||||
<body>
|
||||
|
||||
There are no vars in this file.
|
||||
|
||||
</body>
|
||||
</html>
|
9
models/testdata/html-file-without-vars.templated.html
vendored
Normal file
9
models/testdata/html-file-without-vars.templated.html
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
<html>
|
||||
<head><title>There are no variables here.</title></head>
|
||||
|
||||
<body>
|
||||
|
||||
There are no vars in this file.
|
||||
|
||||
</body>
|
||||
</html>
|
1
models/testdata/powerpoint-with-vars.pptx.b64
vendored
Normal file
1
models/testdata/powerpoint-with-vars.pptx.b64
vendored
Normal file
File diff suppressed because one or more lines are too long
1
models/testdata/powerpoint-with-vars.pptx.templated.b64
vendored
Normal file
1
models/testdata/powerpoint-with-vars.pptx.templated.b64
vendored
Normal file
File diff suppressed because one or more lines are too long
19
models/testdata/text-file-with-vars.templated.txt
vendored
Normal file
19
models/testdata/text-file-with-vars.templated.txt
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
The target's unique ID: 1234567
|
||||
|
||||
The target's first name: Foo
|
||||
|
||||
The target's last name: Bar
|
||||
|
||||
The target's position: Space Janitor
|
||||
|
||||
The target's email address: foo@bar.com
|
||||
|
||||
The spoofed sender: From Address
|
||||
|
||||
The URL to the tracking handler: http://testurl.local/track?rid=1234567
|
||||
|
||||
An alias for tracker image: <img alt='' style='display: none' src='http://testurl.local/track?rid=1234567'/>
|
||||
|
||||
The phishing URL: http://testurl.com/?rid=1234567
|
||||
|
||||
The base URL with the path and rid parameter stripped. Useful for making links to static files: http://testurl.com
|
19
models/testdata/text-file-with-vars.txt
vendored
Normal file
19
models/testdata/text-file-with-vars.txt
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
The target's unique ID: {{.RId}}
|
||||
|
||||
The target's first name: {{.FirstName}}
|
||||
|
||||
The target's last name: {{.LastName}}
|
||||
|
||||
The target's position: {{.Position}}
|
||||
|
||||
The target's email address: {{.Email}}
|
||||
|
||||
The spoofed sender: {{.From}}
|
||||
|
||||
The URL to the tracking handler: {{.TrackingURL}}
|
||||
|
||||
An alias for tracker image: {{.Tracker}}
|
||||
|
||||
The phishing URL: {{.URL}}
|
||||
|
||||
The base URL with the path and rid parameter stripped. Useful for making links to static files: {{.BaseURL}}
|
1
models/testdata/text-file-without-vars.templated.txt
vendored
Normal file
1
models/testdata/text-file-without-vars.templated.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
There are no variables in this file.
|
1
models/testdata/text-file-without-vars.txt
vendored
Normal file
1
models/testdata/text-file-without-vars.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
There are no variables in this file.
|
1
models/testdata/word-file-with-vars.docm.b64
vendored
Normal file
1
models/testdata/word-file-with-vars.docm.b64
vendored
Normal file
File diff suppressed because one or more lines are too long
1
models/testdata/word-file-with-vars.docm.templated.b64
vendored
Normal file
1
models/testdata/word-file-with-vars.docm.templated.b64
vendored
Normal file
File diff suppressed because one or more lines are too long
1
models/testdata/word-file-without-vars.docx.b64
vendored
Normal file
1
models/testdata/word-file-without-vars.docx.b64
vendored
Normal file
File diff suppressed because one or more lines are too long
1
models/testdata/word-file-without-vars.docx.templated.b64
vendored
Normal file
1
models/testdata/word-file-without-vars.docx.templated.b64
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue