diff --git a/.github/workflows/go-cicd.yaml b/.github/workflows/go-cicd.yaml index 129709a..abb1da2 100644 --- a/.github/workflows/go-cicd.yaml +++ b/.github/workflows/go-cicd.yaml @@ -3,7 +3,7 @@ name: go-cicd jobs: - golangci-lint: + lint: runs-on: ubuntu-latest steps: - name: Install Go @@ -16,20 +16,21 @@ jobs: echo "::set-env name=GOPATH::${{ github.workspace }}/go" echo "::add-path::${{ github.workspace }}/go/bin" - name: checkout - uses: actions/checkout@v1 + uses: actions/checkout@v2 with: fetch-depth: 1 - path: podcast/go/src/github.com/${{ github.repository }} + path: go/src/github.com/${{ github.repository }} - name: Install golangci-lint shell: bash run: | go get github.com/golangci/golangci-lint/cmd/golangci-lint - name: Run linters shell: bash - run: | + run: | + cd $GOPATH/src/github.com/${{ github.repository }} golangci-lint -E bodyclose,misspell,gocyclo,dupl,gofmt,golint,unconvert,depguard,interfacer run - coveralls: + coverage: runs-on: ubuntu-latest steps: - name: Install Go @@ -62,7 +63,9 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} 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 steps: - name: Install Go @@ -75,13 +78,14 @@ jobs: echo "::set-env name=GOPATH::${{ github.workspace }}/go" echo "::add-path::${{ github.workspace }}/go/bin" - name: checkout - uses: actions/checkout@v1 + uses: actions/checkout@v2 with: fetch-depth: 1 - path: podcast/go/src/github.com/${{ github.repository }} + path: go/src/github.com/${{ github.repository }} - name: Run Benchmark shell: bash run: | + cd $GOPATH/src/github.com/${{ github.repository }} go test -test.run Benchmark -cpu 1 -bench . go-test: @@ -102,10 +106,12 @@ jobs: with: go-version: ${{ matrix.go-version }} - name: checkout - uses: actions/checkout@v1 + uses: actions/checkout@v2 with: fetch-depth: 1 - path: podcast/go/src/github.com/${{ github.repository }} + path: go/src/github.com/${{ github.repository }} - name: Run tests + shell: bash run: | + cd $GOPATH/src/github.com/${{ github.repository }} go test -v -covermode=count diff --git a/.gitignore b/.gitignore index 2c44570..1b1053c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ profile.out README.md.tmp - +corpus +crashers +suppressions +workdir +podcast-fuzz.zip diff --git a/Makefile b/Makefile index 0dd4e38..1b8e97f 100644 --- a/Makefile +++ b/Makefile @@ -3,13 +3,15 @@ SHELL = /bin/bash GITHUB_REPO:=eduncan911/podcast 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 "[![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 "[![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 >>README.md cat README.md.tmp >> README.md rm README.md.tmp + +clean: + rm -rf corpus crashers suppressions workdir podcast-fuzz.zip diff --git a/README.md b/README.md index e491b29..a407119 100644 --- a/README.md +++ b/README.md @@ -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. ### Extensibility -In no way are you restricted in having full control over your feeds. 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. - -iTunes specific fields are all prefixed with the letter `I`. - -### References -RSS 2.0: https://cyber.harvard.edu/rss/rss.html - -Podcasts: https://help.apple.com/itc/podcasts_connect/#/itca5b22233 +For version 1.x, you are not restricted in having full control over your feeds. +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 with iTunes specific fields +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). + +### Fuzzing Inputs +`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). + +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 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. ### 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 * 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. * 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 + +#### Example: + +
+Click to expand code. + +```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 example.com`) + 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 example.com`) + // 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: + // + // + // + // eduncan911 Podcasts + // http://eduncan911.com/ + // An example Podcast + // go podcast v1.3.1 (github.com/eduncan911/podcast) + // en-us + // Mon, 06 Feb 2017 08:21:52 +0000 + // me@janedoe.com (Jane Doe) + // Sat, 04 Feb 2017 08:21:52 +0000 + // + // http://janedoe.com/i.jpg + // eduncan911 Podcasts + // http://eduncan911.com/ + // + // + // me@janedoe.com (Jane Doe) + // example.com]]> + // + // no + // + // http://e.com/1.mp3 + // Episode 1 + // http://example.com/1.mp3 + // Description for Episode 1 + // Sun, 05 Feb 2017 08:21:52 +0000 + // + // me@janedoe.com (Jane Doe) + // example.com]]> + // + // + // + // http://e.com/2.mp3 + // Episode 2 + // http://example.com/2.mp3 + // Description for Episode 2 + // Mon, 06 Feb 2017 08:21:52 +0000 + // + // me@janedoe.com (Jane Doe) + // example.com]]> + // + // + // + // +``` + +
+ +#### Example: + +
+Click to expand code. + +```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 example.com`) + 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 example.com`) + // 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: + // + // + // + // Sample Podcasts + // http://example.com/ + // An example Podcast + // go podcast v1.3.1 (github.com/eduncan911/podcast) + // en-us + // Mon, 06 Feb 2017 08:21:52 +0000 + // jane.doe@example.com (Jane Doe) + // Wed, 01 Feb 2017 08:21:52 +0000 + // + // http://example.com/podcast.jpg + // Sample Podcasts + // http://example.com/ + // + // + // jane.doe@example.com (Jane Doe) + // A simple Podcast + // example.com]]> + // + // + // http://example.com/9.mp3 + // Episode 9 + // http://example.com/9.mp3 + // Description for Episode 9 + // Mon, 13 Feb 2017 08:21:52 +0000 + // + // jane.doe@example.com (Jane Doe) + // A simple episode 9 + // example.com]]> + // + // + // + // http://example.com/10.mp3 + // Episode 10 + // http://example.com/10.mp3 + // Description for Episode 10 + // Tue, 14 Feb 2017 08:21:52 +0000 + // + // jane.doe@example.com (Jane Doe) + // A simple episode 10 + // example.com]]> + // + // + // + // +``` + +
+ ## Table of Contents * [Imported Packages](#pkg-imports) @@ -364,6 +617,29 @@ func (i *Item) AddDuration(durationInSeconds int64) ``` AddDuration adds the duration to the iTunes duration field. +#### Example: + +
+Click to expand code. + +```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 +``` + +
+ ### func (\*Item) [AddEnclosure](./item.go#L54-L55) ``` go func (i *Item) AddEnclosure( @@ -393,6 +669,42 @@ AddPubDate adds the datetime as a parsed PubDate. UTC time is used by default. +#### Example: + +
+Click to expand code. + +```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 +``` + +
+ ### func (\*Item) [AddSummary](./item.go#L92) ``` go 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 to the expected proper formats. +#### Example: + +
+Click to expand code. + +```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 +``` + +
+ ### func (\*Podcast) [AddAtomLink](./podcast.go#L94) ``` go 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. +#### Example: + +
+Click to expand code. + +```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) +``` + +
+ ### func (\*Podcast) [AddCategory](./podcast.go#L183) ``` go func (p *Podcast) AddCategory(category string, subCategories []string) @@ -553,6 +905,28 @@ as follows. * Tech News * TV & Film +#### Example: + +
+Click to expand code. + +```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 +``` + +
+ ### func (\*Podcast) [AddImage](./podcast.go#L214) ``` go 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 image files. +#### Example: + +
+Click to expand code. + +```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 +``` + +
+ ### func (\*Podcast) [AddItem](./podcast.go#L267) ``` go func (p *Podcast) AddItem(i Item) (int, error) @@ -611,6 +1007,52 @@ Recommendations: * For specifications of itunes tags, see: https://help.apple.com/itc/podcasts_connect/#/itcb54353390 +#### Example: + +
+Click to expand code. + +```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 Here") + + // 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 Here} +``` + +
+ ### func (\*Podcast) [AddLastBuildDate](./podcast.go#L344) ``` go 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. +#### Example: + +
+Click to expand code. + +```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 +``` + +
+ ### func (\*Podcast) [AddPubDate](./podcast.go#L337) ``` go 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. +#### Example: + +
+Click to expand code. + +```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 +``` + +
+ ### func (\*Podcast) [AddSubTitle](./podcast.go#L353) ``` go 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 such as html links: `http://www.apple.com">Apple`. +#### Example: + +
+Click to expand code. + +```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: example.com + `) + + if p.ISummary != nil { + fmt.Println(p.ISummary.Text) + } + // Output: + // A very cool podcast with a long summary! + // + // See more at our website: example.com +``` + +
+ ### func (\*Podcast) [Bytes](./podcast.go#L386) ``` go func (p *Podcast) Bytes() []byte ``` Bytes returns an encoded []byte slice. +#### Example: + +
+Click to expand code. + +```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: example.com + `) + + 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: + // + // + // + // eduncan911 Podcasts + // http://eduncan911.com/ + // An example Podcast + // go podcast v1.3.1 (github.com/eduncan911/podcast) + // en-us + // Mon, 06 Feb 2017 08:21:52 +0000 + // me@janedoe.com (Jane Doe) + // Sat, 04 Feb 2017 08:21:52 +0000 + // + // http://janedoe.com/i.jpg + // eduncan911 Podcasts + // http://eduncan911.com/ + // + // me@janedoe.com (Jane Doe) + // example.com + // ]]> + // + // + // http://example.com/5.mp3 + // Episode 5 + // http://example.com/5.mp3 + // Description for Episode 5 + // Sun, 12 Feb 2017 08:21:52 +0000 + // me@janedoe.com (Jane Doe) + // + // + // + // http://example.com/6.mp3 + // Episode 6 + // http://example.com/6.mp3 + // Description for Episode 6 + // Mon, 13 Feb 2017 08:21:52 +0000 + // me@janedoe.com (Jane Doe) + // + // + // + // +``` + +
+ ### func (\*Podcast) [Encode](./podcast.go#L391) ``` go func (p *Podcast) Encode(w io.Writer) error diff --git a/doc.go b/doc.go index 8c280fa..d1e10b3 100644 --- a/doc.go +++ b/doc.go @@ -33,17 +33,43 @@ // // Extensibility // -// In no way are you restricted in having full control over your feeds. 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. +// For version 1.x, you are not restricted in having full control over your feeds. +// 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 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 // @@ -70,6 +96,12 @@ // // 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 // * Add Go Modules, Update vendor folder (#26, #25) // * Add C.I. GitHub Actions (#25) @@ -113,4 +145,10 @@ // * Initial release. // * 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 diff --git a/fuzz.go b/fuzz.go new file mode 100644 index 0000000..27e9ece --- /dev/null +++ b/fuzz.go @@ -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), + } +} diff --git a/go.mod b/go.mod index b258075..0c5590f 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.13 require ( 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/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.0 diff --git a/go.sum b/go.sum index f5055af..6cbaf2b 100644 --- a/go.sum +++ b/go.sum @@ -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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=