Add go-fuzz to all exportable Funcs (#31)

* Initial Fuzzing Logic
* Add more fuzzing, add gitignore, add cleanup, gomod updates
* Exclude fuzz directories from commits
* Update docs on Fuzzing
* Update docs with Examples and Offline Mode
* Remove duplicate line in Makefile
This commit is contained in:
Eric Duncan 2020-02-13 13:52:13 -05:00 committed by GitHub
parent 8139fb1eaf
commit 975adb8ec2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 965 additions and 27 deletions

View File

@ -3,7 +3,7 @@ name: go-cicd
jobs: jobs:
golangci-lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Install Go - name: Install Go
@ -16,20 +16,21 @@ jobs:
echo "::set-env name=GOPATH::${{ github.workspace }}/go" echo "::set-env name=GOPATH::${{ github.workspace }}/go"
echo "::add-path::${{ github.workspace }}/go/bin" echo "::add-path::${{ github.workspace }}/go/bin"
- name: checkout - name: checkout
uses: actions/checkout@v1 uses: actions/checkout@v2
with: with:
fetch-depth: 1 fetch-depth: 1
path: podcast/go/src/github.com/${{ github.repository }} path: go/src/github.com/${{ github.repository }}
- name: Install golangci-lint - name: Install golangci-lint
shell: bash shell: bash
run: | run: |
go get github.com/golangci/golangci-lint/cmd/golangci-lint go get github.com/golangci/golangci-lint/cmd/golangci-lint
- name: Run linters - name: Run linters
shell: bash shell: bash
run: | run: |
cd $GOPATH/src/github.com/${{ github.repository }}
golangci-lint -E bodyclose,misspell,gocyclo,dupl,gofmt,golint,unconvert,depguard,interfacer run golangci-lint -E bodyclose,misspell,gocyclo,dupl,gofmt,golint,unconvert,depguard,interfacer run
coveralls: coverage:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Install Go - name: Install Go
@ -62,7 +63,9 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: coverage.lcov path-to-lcov: coverage.lcov
go-bench: benchmark:
# TODO: actually compare to previous runs
# maybe this setup: https://github.com/knqyf263/cob
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Install Go - name: Install Go
@ -75,13 +78,14 @@ jobs:
echo "::set-env name=GOPATH::${{ github.workspace }}/go" echo "::set-env name=GOPATH::${{ github.workspace }}/go"
echo "::add-path::${{ github.workspace }}/go/bin" echo "::add-path::${{ github.workspace }}/go/bin"
- name: checkout - name: checkout
uses: actions/checkout@v1 uses: actions/checkout@v2
with: with:
fetch-depth: 1 fetch-depth: 1
path: podcast/go/src/github.com/${{ github.repository }} path: go/src/github.com/${{ github.repository }}
- name: Run Benchmark - name: Run Benchmark
shell: bash shell: bash
run: | run: |
cd $GOPATH/src/github.com/${{ github.repository }}
go test -test.run Benchmark -cpu 1 -bench . go test -test.run Benchmark -cpu 1 -bench .
go-test: go-test:
@ -102,10 +106,12 @@ jobs:
with: with:
go-version: ${{ matrix.go-version }} go-version: ${{ matrix.go-version }}
- name: checkout - name: checkout
uses: actions/checkout@v1 uses: actions/checkout@v2
with: with:
fetch-depth: 1 fetch-depth: 1
path: podcast/go/src/github.com/${{ github.repository }} path: go/src/github.com/${{ github.repository }}
- name: Run tests - name: Run tests
shell: bash
run: | run: |
cd $GOPATH/src/github.com/${{ github.repository }}
go test -v -covermode=count go test -v -covermode=count

6
.gitignore vendored
View File

@ -1,3 +1,7 @@
profile.out profile.out
README.md.tmp README.md.tmp
corpus
crashers
suppressions
workdir
podcast-fuzz.zip

View File

@ -3,13 +3,15 @@ SHELL = /bin/bash
GITHUB_REPO:=eduncan911/podcast GITHUB_REPO:=eduncan911/podcast
README: README:
godoc2ghmd github.com/$(GITHUB_REPO) > README.md.tmp godoc2ghmd -play -ex -verify_import_links=0 github.com/$(GITHUB_REPO) > README.md.tmp
echo "[![GoDoc](https://godoc.org/github.com/$(GITHUB_REPO)?status.svg)](https://godoc.org/github.com/$(GITHUB_REPO))" > README.md echo "[![GoDoc](https://godoc.org/github.com/$(GITHUB_REPO)?status.svg)](https://godoc.org/github.com/$(GITHUB_REPO))" > README.md
echo "[![Build Status](https://github.com/$(GITHUB_REPO)/workflows/go-cicd/badge.svg)](https://github.com/$(GITHUB_REPO)/actions?workflow=go-cicd)" >> README.md echo "[![Build Status](https://github.com/$(GITHUB_REPO)/workflows/go-cicd/badge.svg)](https://github.com/$(GITHUB_REPO)/actions?workflow=go-cicd)" >> README.md
echo "[![Coverage Status](https://coveralls.io/repos/github/$(GITHUB_REPO)/badge.svg?branch=master)](https://coveralls.io/github/$(GITHUB_REPO)?branch=master)" >> README.md echo "[![Coverage Status](https://coveralls.io/repos/github/$(GITHUB_REPO)/badge.svg?branch=master)](https://coveralls.io/github/$(GITHUB_REPO)?branch=master)" >> README.md
echo "[![Go Report Card](https://goreportcard.com/badge/github.com/$(GITHUB_REPO))](https://goreportcard.com/report/github.com/$(GITHUB_REPO))" >> README.md echo "[![Go Report Card](https://goreportcard.com/badge/github.com/$(GITHUB_REPO))](https://goreportcard.com/report/github.com/$(GITHUB_REPO))" >> README.md
echo "[![GoDoc](https://godoc.org/github.com/$(GITHUB_REPO)?status.svg)](https://godoc.org/github.com/$(GITHUB_REPO))"
echo "[![MIT License](https://img.shields.io/npm/l/mediaelement.svg)](https://eduncan911.mit-license.org/)" >> README.md echo "[![MIT License](https://img.shields.io/npm/l/mediaelement.svg)](https://eduncan911.mit-license.org/)" >> README.md
echo >>README.md echo >>README.md
cat README.md.tmp >> README.md cat README.md.tmp >> README.md
rm README.md.tmp rm README.md.tmp
clean:
rm -rf corpus crashers suppressions workdir podcast-fuzz.zip

602
README.md
View File

@ -37,16 +37,42 @@ new installs, tested with Go 1.13. To keep 1.7 compatibility, we use
If either runtime has an issue, please create an Issue and I will address. If either runtime has an issue, please create an Issue and I will address.
### Extensibility ### Extensibility
In no way are you restricted in having full control over your feeds. You may For version 1.x, you are not restricted in having full control over your feeds.
choose to skip the API methods and instead use the structs directly. The You may choose to skip the API methods and instead use the structs directly. The
fields have been grouped by RSS 2.0 and iTunes fields. fields have been grouped by RSS 2.0 and iTunes fields with iTunes specific fields
all prefixed with the letter `I`.
iTunes specific fields are all prefixed with the letter `I`. However, do note that the 2.x version currently in progress will break this
extensibility and enforce API methods going forward. This is to ensure that the feed
can both be marshalled, and unmarshalled back and forth (current 1.x branch can only
be unmarshalled - hence the work for 2.x).
### References ### Fuzzing Inputs
RSS 2.0: <a href="https://cyber.harvard.edu/rss/rss.html">https://cyber.harvard.edu/rss/rss.html</a> `go-fuzz` has been added in 1.4.1, covering all exported API methods. They have been
ran extensively and no issues have come out of them yet (most tests were ran overnight,
over about 11 hours with zero crashes).
Podcasts: <a href="https://help.apple.com/itc/podcasts_connect/#/itca5b22233">https://help.apple.com/itc/podcasts_connect/#/itca5b22233</a> If you wish to help fuzz the inputs, with Go 1.13 or later you can run `go-fuzz` on any
of the inputs.
go get -u github.com/dvyukov/go-fuzz/go-fuzz
go get -u github.com/dvyukov/go-fuzz/go-fuzz-build
go get -u github.com/eduncan911/podcast
cd $GOPATH/src/github.com/eduncan911/podcast
go-fuzz-build
go-fuzz -func FuzzPodcastAddItem
To obtain a list of available funcs to pass, just run `go-fuzz` without any parameters:
$ go-fuzz
2020/02/13 07:27:32 -func flag not provided, but multiple fuzz functions available:
FuzzItemAddDuration, FuzzItemAddEnclosure, FuzzItemAddImage, FuzzItemAddPubDate,
FuzzItemAddSummary, FuzzPodcastAddAtomLink, FuzzPodcastAddAuthor, FuzzPodcastAddCategory,
FuzzPodcastAddImage, FuzzPodcastAddItem, FuzzPodcastAddLastBuildDate, FuzzPodcastAddPubDate,
FuzzPodcastAddSubTitle, FuzzPodcastAddSummary, FuzzPodcastBytes, FuzzPodcastEncode,
FuzzPodcastNew
If you do find an issue, please raise an issue immediately and I will quickly address.
### Roadmap ### Roadmap
The 1.x branch is now mostly in maintenance mode, open to PRs. This means no The 1.x branch is now mostly in maintenance mode, open to PRs. This means no
@ -70,6 +96,13 @@ However, the new 2.x branch, while keeping the same API, is expected break those
bypass the API methods and use the underlying public properties instead. bypass the API methods and use the underlying public properties instead.
### Release Notes ### Release Notes
1.4.1
* Implement fuzz logic testing of exported funcs (#31)
* Upgrade CICD Pipeline Tooling (#31)
* Update documentation for 1.x and 2.3 (#31)
* Allow godoc2ghmd to run without network (#31)
1.4.0 1.4.0
* Add Go Modules, Update vendor folder (#26, #25) * Add Go Modules, Update vendor folder (#26, #25)
@ -121,6 +154,226 @@ bypass the API methods and use the underlying public properties instead.
* Initial release. * Initial release.
* Full documentation, full examples and complete code coverage. * Full documentation, full examples and complete code coverage.
### References
RSS 2.0: <a href="https://cyber.harvard.edu/rss/rss.html">https://cyber.harvard.edu/rss/rss.html</a>
Podcasts: <a href="https://help.apple.com/itc/podcasts_connect/#/itca5b22233">https://help.apple.com/itc/podcasts_connect/#/itca5b22233</a>
#### Example:
<details>
<summary>Click to expand code.</summary>
```go
// ResponseWriter example using Podcast.Encode(w io.Writer).
//
httpHandler := func(w http.ResponseWriter, r *http.Request) {
// instantiate a new Podcast
p := podcast.New(
"eduncan911 Podcasts",
"http://eduncan911.com/",
"An example Podcast",
&pubDate, &updatedDate,
)
// add some channel properties
p.AddAuthor("Jane Doe", "me@janedoe.com")
p.AddAtomLink("http://eduncan911.com/feed.rss")
p.AddImage("http://janedoe.com/i.jpg")
p.AddSummary(`link <a href="http://example.com">example.com</a>`)
p.IExplicit = "no"
for i := int64(1); i < 3; i++ {
n := strconv.FormatInt(i, 10)
d := pubDate.AddDate(0, 0, int(i))
// create an Item
item := podcast.Item{
Title: "Episode " + n,
Link: "http://example.com/" + n + ".mp3",
Description: "Description for Episode " + n,
PubDate: &d,
}
item.AddImage("http://example.com/episode-" + n + ".png")
item.AddSummary(`item <a href="http://example.com">example.com</a>`)
// add a Download to the Item
item.AddEnclosure("http://e.com/"+n+".mp3", podcast.MP3, 55*(i+1))
// add the Item and check for validation errors
if _, err := p.AddItem(item); err != nil {
fmt.Println(item.Title, ": error", err.Error())
return
}
}
// set the Content Type to that of XML
w.Header().Set("Content-Type", "application/xml")
// finally, Encode and write the Podcast to the ResponseWriter.
//
// a simple pattern is to handle any errors within this check.
// alternatively if using middleware, you can just return
// the Podcast entity as it also implements the io.Writer interface
// that complies with several middleware packages.
if err := p.Encode(w); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
rr := httptest.NewRecorder()
httpHandler(rr, nil)
os.Stdout.Write(rr.Body.Bytes())
// Output:
// <?xml version="1.0" encoding="UTF-8"?>
// <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
// <channel>
// <title>eduncan911 Podcasts</title>
// <link>http://eduncan911.com/</link>
// <description>An example Podcast</description>
// <generator>go podcast v1.3.1 (github.com/eduncan911/podcast)</generator>
// <language>en-us</language>
// <lastBuildDate>Mon, 06 Feb 2017 08:21:52 +0000</lastBuildDate>
// <managingEditor>me@janedoe.com (Jane Doe)</managingEditor>
// <pubDate>Sat, 04 Feb 2017 08:21:52 +0000</pubDate>
// <image>
// <url>http://janedoe.com/i.jpg</url>
// <title>eduncan911 Podcasts</title>
// <link>http://eduncan911.com/</link>
// </image>
// <atom:link href="http://eduncan911.com/feed.rss" rel="self" type="application/rss+xml"></atom:link>
// <itunes:author>me@janedoe.com (Jane Doe)</itunes:author>
// <itunes:summary><![CDATA[link <a href="http://example.com">example.com</a>]]></itunes:summary>
// <itunes:image href="http://janedoe.com/i.jpg"></itunes:image>
// <itunes:explicit>no</itunes:explicit>
// <item>
// <guid>http://e.com/1.mp3</guid>
// <title>Episode 1</title>
// <link>http://example.com/1.mp3</link>
// <description>Description for Episode 1</description>
// <pubDate>Sun, 05 Feb 2017 08:21:52 +0000</pubDate>
// <enclosure url="http://e.com/1.mp3" length="110" type="audio/mpeg"></enclosure>
// <itunes:author>me@janedoe.com (Jane Doe)</itunes:author>
// <itunes:summary><![CDATA[item <a href="http://example.com">example.com</a>]]></itunes:summary>
// <itunes:image href="http://example.com/episode-1.png"></itunes:image>
// </item>
// <item>
// <guid>http://e.com/2.mp3</guid>
// <title>Episode 2</title>
// <link>http://example.com/2.mp3</link>
// <description>Description for Episode 2</description>
// <pubDate>Mon, 06 Feb 2017 08:21:52 +0000</pubDate>
// <enclosure url="http://e.com/2.mp3" length="165" type="audio/mpeg"></enclosure>
// <itunes:author>me@janedoe.com (Jane Doe)</itunes:author>
// <itunes:summary><![CDATA[item <a href="http://example.com">example.com</a>]]></itunes:summary>
// <itunes:image href="http://example.com/episode-2.png"></itunes:image>
// </item>
// </channel>
// </rss>
```
</details>
#### Example:
<details>
<summary>Click to expand code.</summary>
```go
// instantiate a new Podcast
p := podcast.New(
"Sample Podcasts",
"http://example.com/",
"An example Podcast",
&createdDate, &updatedDate,
)
// add some channel properties
p.ISubtitle = "A simple Podcast"
p.AddSummary(`link <a href="http://example.com">example.com</a>`)
p.AddImage("http://example.com/podcast.jpg")
p.AddAuthor("Jane Doe", "jane.doe@example.com")
p.AddAtomLink("http://example.com/atom.rss")
for i := int64(9); i < 11; i++ {
n := strconv.FormatInt(i, 10)
d := pubDate.AddDate(0, 0, int(i))
// create an Item
item := podcast.Item{
Title: "Episode " + n,
Description: "Description for Episode " + n,
ISubtitle: "A simple episode " + n,
PubDate: &d,
}
item.AddImage("http://example.com/episode-" + n + ".png")
item.AddSummary(`item k <a href="http://example.com">example.com</a>`)
// add a Download to the Item
item.AddEnclosure("http://example.com/"+n+".mp3", podcast.MP3, 55*(i+1))
// add the Item and check for validation errors
if _, err := p.AddItem(item); err != nil {
os.Stderr.WriteString("item validation error: " + err.Error())
}
}
// Podcast.Encode writes to an io.Writer
if err := p.Encode(os.Stdout); err != nil {
fmt.Println("error writing to stdout:", err.Error())
}
// Output:
// <?xml version="1.0" encoding="UTF-8"?>
// <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
// <channel>
// <title>Sample Podcasts</title>
// <link>http://example.com/</link>
// <description>An example Podcast</description>
// <generator>go podcast v1.3.1 (github.com/eduncan911/podcast)</generator>
// <language>en-us</language>
// <lastBuildDate>Mon, 06 Feb 2017 08:21:52 +0000</lastBuildDate>
// <managingEditor>jane.doe@example.com (Jane Doe)</managingEditor>
// <pubDate>Wed, 01 Feb 2017 08:21:52 +0000</pubDate>
// <image>
// <url>http://example.com/podcast.jpg</url>
// <title>Sample Podcasts</title>
// <link>http://example.com/</link>
// </image>
// <atom:link href="http://example.com/atom.rss" rel="self" type="application/rss+xml"></atom:link>
// <itunes:author>jane.doe@example.com (Jane Doe)</itunes:author>
// <itunes:subtitle>A simple Podcast</itunes:subtitle>
// <itunes:summary><![CDATA[link <a href="http://example.com">example.com</a>]]></itunes:summary>
// <itunes:image href="http://example.com/podcast.jpg"></itunes:image>
// <item>
// <guid>http://example.com/9.mp3</guid>
// <title>Episode 9</title>
// <link>http://example.com/9.mp3</link>
// <description>Description for Episode 9</description>
// <pubDate>Mon, 13 Feb 2017 08:21:52 +0000</pubDate>
// <enclosure url="http://example.com/9.mp3" length="550" type="audio/mpeg"></enclosure>
// <itunes:author>jane.doe@example.com (Jane Doe)</itunes:author>
// <itunes:subtitle>A simple episode 9</itunes:subtitle>
// <itunes:summary><![CDATA[item k <a href="http://example.com">example.com</a>]]></itunes:summary>
// <itunes:image href="http://example.com/episode-9.png"></itunes:image>
// </item>
// <item>
// <guid>http://example.com/10.mp3</guid>
// <title>Episode 10</title>
// <link>http://example.com/10.mp3</link>
// <description>Description for Episode 10</description>
// <pubDate>Tue, 14 Feb 2017 08:21:52 +0000</pubDate>
// <enclosure url="http://example.com/10.mp3" length="605" type="audio/mpeg"></enclosure>
// <itunes:author>jane.doe@example.com (Jane Doe)</itunes:author>
// <itunes:subtitle>A simple episode 10</itunes:subtitle>
// <itunes:summary><![CDATA[item k <a href="http://example.com">example.com</a>]]></itunes:summary>
// <itunes:image href="http://example.com/episode-10.png"></itunes:image>
// </item>
// </channel>
// </rss>
```
</details>
## Table of Contents ## Table of Contents
* [Imported Packages](#pkg-imports) * [Imported Packages](#pkg-imports)
@ -364,6 +617,29 @@ func (i *Item) AddDuration(durationInSeconds int64)
``` ```
AddDuration adds the duration to the iTunes duration field. AddDuration adds the duration to the iTunes duration field.
#### Example:
<details>
<summary>Click to expand code.</summary>
```go
i := podcast.Item{
Title: "item title",
Description: "item desc",
Link: "item link",
}
d := int64(533)
// add the Duration in Seconds
i.AddDuration(d)
fmt.Println(i.IDuration)
// Output:
// 8:53
```
</details>
### <a name="Item.AddEnclosure">func</a> (\*Item) [AddEnclosure](./item.go#L54-L55) ### <a name="Item.AddEnclosure">func</a> (\*Item) [AddEnclosure](./item.go#L54-L55)
``` go ``` go
func (i *Item) AddEnclosure( func (i *Item) AddEnclosure(
@ -393,6 +669,42 @@ AddPubDate adds the datetime as a parsed PubDate.
UTC time is used by default. UTC time is used by default.
#### Example:
<details>
<summary>Click to expand code.</summary>
```go
p := podcast.New("title", "link", "description", nil, nil)
i := podcast.Item{
Title: "item title",
Description: "item desc",
Link: "item link",
}
d := pubDate.AddDate(0, 0, -11)
// add the pub date
i.AddPubDate(&d)
// before adding
if i.PubDate != nil {
fmt.Println(i.PubDateFormatted, *i.PubDate)
}
// this should not override with Podcast.PubDate
if _, err := p.AddItem(i); err != nil {
fmt.Println(err)
}
// after adding item
fmt.Println(i.PubDateFormatted, *i.PubDate)
// Output:
// Tue, 24 Jan 2017 08:21:52 +0000 2017-01-24 08:21:52 +0000 UTC
// Tue, 24 Jan 2017 08:21:52 +0000 2017-01-24 08:21:52 +0000 UTC
```
</details>
### <a name="Item.AddSummary">func</a> (\*Item) [AddSummary](./item.go#L92) ### <a name="Item.AddSummary">func</a> (\*Item) [AddSummary](./item.go#L92)
``` go ``` go
func (i *Item) AddSummary(summary string) func (i *Item) AddSummary(summary string)
@ -458,6 +770,26 @@ New instantiates a Podcast with required parameters.
Nil-able fields are optional but recommended as they are formatted Nil-able fields are optional but recommended as they are formatted
to the expected proper formats. to the expected proper formats.
#### Example:
<details>
<summary>Click to expand code.</summary>
```go
ti, l, d := "title", "link", "description"
// instantiate a new Podcast
p := podcast.New(ti, l, d, &pubDate, &updatedDate)
fmt.Println(p.Title, p.Link, p.Description, p.Language)
fmt.Println(p.PubDate, p.LastBuildDate)
// Output:
// title link description en-us
// Sat, 04 Feb 2017 08:21:52 +0000 Mon, 06 Feb 2017 08:21:52 +0000
```
</details>
### <a name="Podcast.AddAtomLink">func</a> (\*Podcast) [AddAtomLink](./podcast.go#L94) ### <a name="Podcast.AddAtomLink">func</a> (\*Podcast) [AddAtomLink](./podcast.go#L94)
``` go ``` go
func (p *Podcast) AddAtomLink(href string) func (p *Podcast) AddAtomLink(href string)
@ -470,6 +802,26 @@ func (p *Podcast) AddAuthor(name, email string)
``` ```
AddAuthor adds the specified Author to the podcast. AddAuthor adds the specified Author to the podcast.
#### Example:
<details>
<summary>Click to expand code.</summary>
```go
p := podcast.New("title", "link", "description", nil, nil)
// add the Author
p.AddAuthor("the name", "me@test.com")
fmt.Println(p.ManagingEditor)
fmt.Println(p.IAuthor)
// Output:
// me@test.com (the name)
// me@test.com (the name)
```
</details>
### <a name="Podcast.AddCategory">func</a> (\*Podcast) [AddCategory](./podcast.go#L183) ### <a name="Podcast.AddCategory">func</a> (\*Podcast) [AddCategory](./podcast.go#L183)
``` go ``` go
func (p *Podcast) AddCategory(category string, subCategories []string) func (p *Podcast) AddCategory(category string, subCategories []string)
@ -553,6 +905,28 @@ as follows.
* Tech News * Tech News
* TV & Film * TV & Film
#### Example:
<details>
<summary>Click to expand code.</summary>
```go
p := podcast.New("title", "link", "description", nil, nil)
// add the Category
p.AddCategory("Bombay", nil)
p.AddCategory("American", []string{"Longhair", "Shorthair"})
p.AddCategory("Siamese", nil)
fmt.Println(len(p.ICategories), len(p.ICategories[1].ICategories))
fmt.Println(p.Category)
// Output:
// 3 2
// Bombay,American,Siamese
```
</details>
### <a name="Podcast.AddImage">func</a> (\*Podcast) [AddImage](./podcast.go#L214) ### <a name="Podcast.AddImage">func</a> (\*Podcast) [AddImage](./podcast.go#L214)
``` go ``` go
func (p *Podcast) AddImage(url string) func (p *Podcast) AddImage(url string)
@ -566,6 +940,28 @@ extensions (.jpg, .png), and in the RGB colorspace. To optimize
images for mobile devices, Apple recommends compressing your images for mobile devices, Apple recommends compressing your
image files. image files.
#### Example:
<details>
<summary>Click to expand code.</summary>
```go
p := podcast.New("title", "link", "description", nil, nil)
// add the Image
p.AddImage("http://example.com/image.jpg")
if p.Image != nil && p.IImage != nil {
fmt.Println(p.Image.URL)
fmt.Println(p.IImage.HREF)
}
// Output:
// http://example.com/image.jpg
// http://example.com/image.jpg
```
</details>
### <a name="Podcast.AddItem">func</a> (\*Podcast) [AddItem](./podcast.go#L267) ### <a name="Podcast.AddItem">func</a> (\*Podcast) [AddItem](./podcast.go#L267)
``` go ``` go
func (p *Podcast) AddItem(i Item) (int, error) func (p *Podcast) AddItem(i Item) (int, error)
@ -611,6 +1007,52 @@ Recommendations:
* For specifications of itunes tags, see: * For specifications of itunes tags, see:
<a href="https://help.apple.com/itc/podcasts_connect/#/itcb54353390">https://help.apple.com/itc/podcasts_connect/#/itcb54353390</a> <a href="https://help.apple.com/itc/podcasts_connect/#/itcb54353390">https://help.apple.com/itc/podcasts_connect/#/itcb54353390</a>
#### Example:
<details>
<summary>Click to expand code.</summary>
```go
p := podcast.New("title", "link", "description", &pubDate, &updatedDate)
p.AddAuthor("the name", "me@test.com")
p.AddImage("http://example.com/image.jpg")
// create an Item
date := pubDate.AddDate(0, 0, 77)
item := podcast.Item{
Title: "Episode 1",
Description: "Description for Episode 1",
ISubtitle: "A simple episode 1",
PubDate: &date,
}
item.AddEnclosure(
"http://example.com/1.mp3",
podcast.MP3,
183,
)
item.AddSummary("See more at <a href=\"http://example.com\">Here</a>")
// add the Item
if _, err := p.AddItem(item); err != nil {
fmt.Println("item validation error: " + err.Error())
}
if len(p.Items) != 1 {
fmt.Println("expected 1 item in the collection")
}
pp := p.Items[0]
fmt.Println(
pp.GUID, pp.Title, pp.Link, pp.Description, pp.Author,
pp.AuthorFormatted, pp.Category, pp.Comments, pp.Source,
pp.PubDate, pp.PubDateFormatted, *pp.Enclosure,
pp.IAuthor, pp.IDuration, pp.IExplicit, pp.IIsClosedCaptioned,
pp.IOrder, pp.ISubtitle, pp.ISummary)
// Output:
// http://example.com/1.mp3 Episode 1 http://example.com/1.mp3 Description for Episode 1 &{{ } me@test.com (the name)} 2017-04-22 08:21:52 +0000 UTC Sat, 22 Apr 2017 08:21:52 +0000 {{ } http://example.com/1.mp3 183 183 audio/mpeg audio/mpeg} me@test.com (the name) A simple episode 1 &{{ } See more at <a href="http://example.com">Here</a>}
```
</details>
### <a name="Podcast.AddLastBuildDate">func</a> (\*Podcast) [AddLastBuildDate](./podcast.go#L344) ### <a name="Podcast.AddLastBuildDate">func</a> (\*Podcast) [AddLastBuildDate](./podcast.go#L344)
``` go ``` go
func (p *Podcast) AddLastBuildDate(datetime *time.Time) func (p *Podcast) AddLastBuildDate(datetime *time.Time)
@ -619,6 +1061,24 @@ AddLastBuildDate adds the datetime as a parsed PubDate.
UTC time is used by default. UTC time is used by default.
#### Example:
<details>
<summary>Click to expand code.</summary>
```go
p := podcast.New("title", "link", "description", nil, nil)
d := pubDate.AddDate(0, 0, -7)
p.AddLastBuildDate(&d)
fmt.Println(p.LastBuildDate)
// Output:
// Sat, 28 Jan 2017 08:21:52 +0000
```
</details>
### <a name="Podcast.AddPubDate">func</a> (\*Podcast) [AddPubDate](./podcast.go#L337) ### <a name="Podcast.AddPubDate">func</a> (\*Podcast) [AddPubDate](./podcast.go#L337)
``` go ``` go
func (p *Podcast) AddPubDate(datetime *time.Time) func (p *Podcast) AddPubDate(datetime *time.Time)
@ -627,6 +1087,24 @@ AddPubDate adds the datetime as a parsed PubDate.
UTC time is used by default. UTC time is used by default.
#### Example:
<details>
<summary>Click to expand code.</summary>
```go
p := podcast.New("title", "link", "description", nil, nil)
d := pubDate.AddDate(0, 0, -5)
p.AddPubDate(&d)
fmt.Println(p.PubDate)
// Output:
// Mon, 30 Jan 2017 08:21:52 +0000
```
</details>
### <a name="Podcast.AddSubTitle">func</a> (\*Podcast) [AddSubTitle](./podcast.go#L353) ### <a name="Podcast.AddSubTitle">func</a> (\*Podcast) [AddSubTitle](./podcast.go#L353)
``` go ``` go
func (p *Podcast) AddSubTitle(subTitle string) func (p *Podcast) AddSubTitle(subTitle string)
@ -648,12 +1126,122 @@ Limit: 4000 characters
Note that this field is a CDATA encoded field which allows for rich text Note that this field is a CDATA encoded field which allows for rich text
such as html links: `<a href="<a href="http://www.apple.com">http://www.apple.com</a>">Apple</a>`. such as html links: `<a href="<a href="http://www.apple.com">http://www.apple.com</a>">Apple</a>`.
#### Example:
<details>
<summary>Click to expand code.</summary>
```go
p := podcast.New("title", "link", "description", nil, nil)
// add a summary
p.AddSummary(`A very cool podcast with a long summary!
See more at our website: <a href="http://example.com">example.com</a>
`)
if p.ISummary != nil {
fmt.Println(p.ISummary.Text)
}
// Output:
// A very cool podcast with a long summary!
//
// See more at our website: <a href="http://example.com">example.com</a>
```
</details>
### <a name="Podcast.Bytes">func</a> (\*Podcast) [Bytes](./podcast.go#L386) ### <a name="Podcast.Bytes">func</a> (\*Podcast) [Bytes](./podcast.go#L386)
``` go ``` go
func (p *Podcast) Bytes() []byte func (p *Podcast) Bytes() []byte
``` ```
Bytes returns an encoded []byte slice. Bytes returns an encoded []byte slice.
#### Example:
<details>
<summary>Click to expand code.</summary>
```go
p := podcast.New(
"eduncan911 Podcasts",
"http://eduncan911.com/",
"An example Podcast",
&pubDate, &updatedDate,
)
p.AddAuthor("Jane Doe", "me@janedoe.com")
p.AddImage("http://janedoe.com/i.jpg")
p.AddSummary(`A very cool podcast with a long summary using Bytes()!
See more at our website: <a href="http://example.com">example.com</a>
`)
for i := int64(5); i < 7; i++ {
n := strconv.FormatInt(i, 10)
d := pubDate.AddDate(0, 0, int(i+3))
item := podcast.Item{
Title: "Episode " + n,
Link: "http://example.com/" + n + ".mp3",
Description: "Description for Episode " + n,
PubDate: &d,
}
if _, err := p.AddItem(item); err != nil {
fmt.Println(item.Title, ": error", err.Error())
break
}
}
// call Podcast.Bytes() to return a byte array
os.Stdout.Write(p.Bytes())
// Output:
// <?xml version="1.0" encoding="UTF-8"?>
// <rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
// <channel>
// <title>eduncan911 Podcasts</title>
// <link>http://eduncan911.com/</link>
// <description>An example Podcast</description>
// <generator>go podcast v1.3.1 (github.com/eduncan911/podcast)</generator>
// <language>en-us</language>
// <lastBuildDate>Mon, 06 Feb 2017 08:21:52 +0000</lastBuildDate>
// <managingEditor>me@janedoe.com (Jane Doe)</managingEditor>
// <pubDate>Sat, 04 Feb 2017 08:21:52 +0000</pubDate>
// <image>
// <url>http://janedoe.com/i.jpg</url>
// <title>eduncan911 Podcasts</title>
// <link>http://eduncan911.com/</link>
// </image>
// <itunes:author>me@janedoe.com (Jane Doe)</itunes:author>
// <itunes:summary><![CDATA[A very cool podcast with a long summary using Bytes()!
//
// See more at our website: <a href="http://example.com">example.com</a>
// ]]></itunes:summary>
// <itunes:image href="http://janedoe.com/i.jpg"></itunes:image>
// <item>
// <guid>http://example.com/5.mp3</guid>
// <title>Episode 5</title>
// <link>http://example.com/5.mp3</link>
// <description>Description for Episode 5</description>
// <pubDate>Sun, 12 Feb 2017 08:21:52 +0000</pubDate>
// <itunes:author>me@janedoe.com (Jane Doe)</itunes:author>
// <itunes:image href="http://janedoe.com/i.jpg"></itunes:image>
// </item>
// <item>
// <guid>http://example.com/6.mp3</guid>
// <title>Episode 6</title>
// <link>http://example.com/6.mp3</link>
// <description>Description for Episode 6</description>
// <pubDate>Mon, 13 Feb 2017 08:21:52 +0000</pubDate>
// <itunes:author>me@janedoe.com (Jane Doe)</itunes:author>
// <itunes:image href="http://janedoe.com/i.jpg"></itunes:image>
// </item>
// </channel>
// </rss>
```
</details>
### <a name="Podcast.Encode">func</a> (\*Podcast) [Encode](./podcast.go#L391) ### <a name="Podcast.Encode">func</a> (\*Podcast) [Encode](./podcast.go#L391)
``` go ``` go
func (p *Podcast) Encode(w io.Writer) error func (p *Podcast) Encode(w io.Writer) error

52
doc.go
View File

@ -33,17 +33,43 @@
// //
// Extensibility // Extensibility
// //
// In no way are you restricted in having full control over your feeds. You may // For version 1.x, you are not restricted in having full control over your feeds.
// choose to skip the API methods and instead use the structs directly. The // You may choose to skip the API methods and instead use the structs directly. The
// fields have been grouped by RSS 2.0 and iTunes fields. // fields have been grouped by RSS 2.0 and iTunes fields with iTunes specific fields
// all prefixed with the letter `I`.
// //
// iTunes specific fields are all prefixed with the letter `I`. // However, do note that the 2.x version currently in progress will break this
// extensibility and enforce API methods going forward. This is to ensure that the feed
// can both be marshalled, and unmarshalled back and forth (current 1.x branch can only
// be unmarshalled - hence the work for 2.x).
// //
// References // Fuzzing Inputs
// //
// RSS 2.0: https://cyber.harvard.edu/rss/rss.html // `go-fuzz` has been added in 1.4.1, covering all exported API methods. They have been
// ran extensively and no issues have come out of them yet (most tests were ran overnight,
// over about 11 hours with zero crashes).
// //
// Podcasts: https://help.apple.com/itc/podcasts_connect/#/itca5b22233 // If you wish to help fuzz the inputs, with Go 1.13 or later you can run `go-fuzz` on any
// of the inputs.
//
// go get -u github.com/dvyukov/go-fuzz/go-fuzz
// go get -u github.com/dvyukov/go-fuzz/go-fuzz-build
// go get -u github.com/eduncan911/podcast
// cd $GOPATH/src/github.com/eduncan911/podcast
// go-fuzz-build
// go-fuzz -func FuzzPodcastAddItem
//
// To obtain a list of available funcs to pass, just run `go-fuzz` without any parameters:
//
// $ go-fuzz
// 2020/02/13 07:27:32 -func flag not provided, but multiple fuzz functions available:
// FuzzItemAddDuration, FuzzItemAddEnclosure, FuzzItemAddImage, FuzzItemAddPubDate,
// FuzzItemAddSummary, FuzzPodcastAddAtomLink, FuzzPodcastAddAuthor, FuzzPodcastAddCategory,
// FuzzPodcastAddImage, FuzzPodcastAddItem, FuzzPodcastAddLastBuildDate, FuzzPodcastAddPubDate,
// FuzzPodcastAddSubTitle, FuzzPodcastAddSummary, FuzzPodcastBytes, FuzzPodcastEncode,
// FuzzPodcastNew
//
// If you do find an issue, please raise an issue immediately and I will quickly address.
// //
// Roadmap // Roadmap
// //
@ -70,6 +96,12 @@
// //
// Release Notes // Release Notes
// //
// 1.4.1
// * Implement fuzz logic testing of exported funcs (#31)
// * Upgrade CICD Pipeline Tooling (#31)
// * Update documentation for 1.x and 2.3 (#31)
// * Allow godoc2ghmd to run without network (#31)
//
// 1.4.0 // 1.4.0
// * Add Go Modules, Update vendor folder (#26, #25) // * Add Go Modules, Update vendor folder (#26, #25)
// * Add C.I. GitHub Actions (#25) // * Add C.I. GitHub Actions (#25)
@ -113,4 +145,10 @@
// * Initial release. // * Initial release.
// * Full documentation, full examples and complete code coverage. // * Full documentation, full examples and complete code coverage.
// //
// References
//
// RSS 2.0: https://cyber.harvard.edu/rss/rss.html
//
// Podcasts: https://help.apple.com/itc/podcasts_connect/#/itca5b22233
//
package podcast package podcast

297
fuzz.go Normal file
View File

@ -0,0 +1,297 @@
// +build gofuzz
package podcast
import (
"bytes"
"encoding/binary"
"time"
)
func FuzzItemAddDuration(data []byte) int {
input, read := binary.Varint(data)
if input <= 0 && read == 0 {
// error converting []byte into int64
return 0
}
i := newItem(data)
i.AddDuration(input)
p := newPodcast(data)
if _, err := p.AddItem(i); err != nil {
return 0
}
var buf bytes.Buffer
if err := p.Encode(&buf); err != nil {
return 0
}
return 1
}
func FuzzItemAddEnclosure(data []byte) int {
url := string(data)
length, read := binary.Varint(data)
if length <= 0 && read == 0 {
// error converting []byte into int64
return 0
}
i := newItem(data)
i.AddEnclosure(url, MP3, length)
p := newPodcast(data)
if _, err := p.AddItem(i); err != nil {
return 0
}
var buf bytes.Buffer
if err := p.Encode(&buf); err != nil {
return 0
}
return 1
}
func FuzzItemAddImage(data []byte) int {
i := newItem(data)
i.AddImage(string(data))
p := newPodcast(data)
if _, err := p.AddItem(i); err != nil {
return 0
}
var buf bytes.Buffer
if err := p.Encode(&buf); err != nil {
return 0
}
return 1
}
func FuzzItemAddPubDate(data []byte) int {
t := time.Time{}
if err := t.GobDecode(data); err != nil {
return 0
}
i := newItem(data)
i.AddPubDate(&t)
p := newPodcast(data)
if _, err := p.AddItem(i); err != nil {
return 0
}
var buf bytes.Buffer
if err := p.Encode(&buf); err != nil {
return 0
}
return 1
}
func FuzzItemAddSummary(data []byte) int {
i := newItem(data)
i.AddSummary(string(data))
p := newPodcast(data)
if _, err := p.AddItem(i); err != nil {
return 0
}
var buf bytes.Buffer
if err := p.Encode(&buf); err != nil {
return 0
}
return 1
}
func FuzzPodcastNew(data []byte) int {
p := newPodcast(data)
var buf bytes.Buffer
if err := p.Encode(&buf); err != nil {
return 0
}
return 1
}
func FuzzPodcastAddAtomLink(data []byte) int {
p := newPodcast(data)
p.AddAtomLink(string(data))
var buf bytes.Buffer
if err := p.Encode(&buf); err != nil {
return 0
}
return 1
}
func FuzzPodcastAddAuthor(data []byte) int {
p := newPodcast(data)
p.AddAuthor(string(data), string(data))
var buf bytes.Buffer
if err := p.Encode(&buf); err != nil {
return 0
}
return 1
}
func FuzzPodcastAddCategory(data []byte) int {
p := newPodcast(data)
subs := make([]string, 3)
subs[0] = string(data)
subs[1] = string(data)
subs[2] = string(data)
p.AddCategory(string(data), subs)
var buf bytes.Buffer
if err := p.Encode(&buf); err != nil {
return 0
}
return 1
}
func FuzzPodcastAddImage(data []byte) int {
p := newPodcast(data)
p.AddImage(string(data))
var buf bytes.Buffer
if err := p.Encode(&buf); err != nil {
return 0
}
return 1
}
func FuzzPodcastAddItem(data []byte) int {
p := newPodcast(data)
i := newItem(data)
if _, err := p.AddItem(i); err != nil {
return 0
}
var buf bytes.Buffer
if err := p.Encode(&buf); err != nil {
return 0
}
return 1
}
func FuzzPodcastAddLastBuildDate(data []byte) int {
p := newPodcast(data)
t := time.Time{}
if err := t.GobDecode(data); err != nil {
return 0
}
p.AddLastBuildDate(&t)
var buf bytes.Buffer
if err := p.Encode(&buf); err != nil {
return 0
}
return 1
}
func FuzzPodcastAddPubDate(data []byte) int {
p := newPodcast(data)
t := time.Time{}
if err := t.GobDecode(data); err != nil {
return 0
}
p.AddPubDate(&t)
var buf bytes.Buffer
if err := p.Encode(&buf); err != nil {
return 0
}
return 1
}
func FuzzPodcastAddSubTitle(data []byte) int {
p := newPodcast(data)
p.AddSubTitle(string(data))
var buf bytes.Buffer
if err := p.Encode(&buf); err != nil {
return 0
}
return 1
}
func FuzzPodcastAddSummary(data []byte) int {
p := newPodcast(data)
p.AddSummary(string(data))
var buf bytes.Buffer
if err := p.Encode(&buf); err != nil {
return 0
}
return 1
}
func FuzzPodcastBytes(data []byte) int {
p := newPodcast(data)
p.Bytes()
return 1
}
func FuzzPodcastEncode(data []byte) int {
p := newPodcast(data)
var buf bytes.Buffer
if err := p.Encode(&buf); err != nil {
return 0
}
return 1
}
func newPodcast(data []byte) Podcast {
return New(
string(data),
string(data),
string(data),
nil, nil)
}
func newItem(data []byte) Item {
// Article minimal requirements are:
// - Title
// - Description
// - Link
//
// Audio minimal requirements are:
// - Title
// - Description
// - Enclosure (HREF, Type and Length all required)
//
return Item{
Title: string(data),
Description: string(data),
Link: string(data),
}
}

1
go.mod
View File

@ -4,6 +4,7 @@ go 1.13
require ( require (
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/dvyukov/go-fuzz v0.0.0-20191206100749-a378175e205c // indirect
github.com/kr/pretty v0.1.0 // indirect github.com/kr/pretty v0.1.0 // indirect
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib v1.0.0

2
go.sum
View File

@ -2,6 +2,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dvyukov/go-fuzz v0.0.0-20191206100749-a378175e205c h1:/bXaeEuNG6V0HeyEGw11DYLW5BGsOPlcVRIXbHNUWSo=
github.com/dvyukov/go-fuzz v0.0.0-20191206100749-a378175e205c/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=