<commit_before>// Copyright 2017-present The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package releaser implements a set of utilities and a wrapper around Goreleaser
// to help automate the Hugo release process.
package releaser

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"text/template"
	"time"
)

const (
	issueLinkTemplate            = "[#%d](https://github.com/gohugoio/hugo/issues/%d)"
	linkTemplate                 = "[%s](%s)"
	releaseNotesMarkdownTemplate = `
{{- $patchRelease := isPatch . -}}
{{- $contribsPerAuthor := .All.ContribCountPerAuthor -}}
{{- $docsContribsPerAuthor := .Docs.ContribCountPerAuthor -}}
{{- if $patchRelease }}
{{ if eq (len .All) 1 }}
This is a bug-fix release with one important fix.
{{ else }}
This is a bug-fix release with a couple of important fixes.
{{ end }}
{{ else }}
This release represents **{{ len .All }} contributions by {{ len $contribsPerAuthor }} contributors** to the main Hugo code base.
{{ end -}}

{{- if  gt (len $contribsPerAuthor) 3 -}}
{{- $u1 := index $contribsPerAuthor 0 -}}
{{- $u2 := index $contribsPerAuthor 1 -}}
{{- $u3 := index $contribsPerAuthor 2 -}}
{{- $u4 := index $contribsPerAuthor 3 -}}
{{- $u1.AuthorLink }} leads the Hugo development with a significant amount of contributions, but also a big shoutout to {{ $u2.AuthorLink }}, {{ $u3.AuthorLink }}, and {{ $u4.AuthorLink }} for their ongoing contributions.
And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
{{ end }}
{{- if not $patchRelease }}
Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
which has received **{{ len .Docs }} contributions by {{ len $docsContribsPerAuthor }} contributors**.
{{- if  gt (len $docsContribsPerAuthor) 3 -}}
{{- $u1 := index $docsContribsPerAuthor 0 -}}
{{- $u2 := index $docsContribsPerAuthor 1 -}}
{{- $u3 := index $docsContribsPerAuthor 2 -}}
{{- $u4 := index $docsContribsPerAuthor 3 }} A special thanks to {{ $u1.AuthorLink }}, {{ $u2.AuthorLink }}, {{ $u3.AuthorLink }}, and {{ $u4.AuthorLink }} for their work on the documentation site.
{{ end }}
{{ end }}
Hugo now has:

{{ with .Repo -}}
* {{ .Stars }}+ [stars](https://github.com/gohugoio/hugo/stargazers)
* {{ len .Contributors }}+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
{{- end -}}
{{ with .ThemeCount }}
* {{ . }}+ [themes](http://themes.gohugo.io/)
{{ end }}
{{ with .Notes }}
## Notes
{{ template "change-section" . }}
{{- end -}}
## Enhancements
{{ template "change-headers"  .Enhancements -}}
## Fixes
{{ template "change-headers"  .Fixes -}}

{{ define "change-headers" }}
{{ $tmplChanges := index . "templateChanges" -}}
{{- $outChanges := index . "outChanges" -}}
{{- $coreChanges := index . "coreChanges" -}}
{{- $otherChanges := index . "otherChanges" -}}
{{- with $tmplChanges -}}
### Templates
{{ template "change-section" . }}
{{- end -}}
{{- with $outChanges -}}
### Output
{{ template "change-section"  . }}
{{- end -}}
{{- with $coreChanges -}}
### Core
{{ template "change-section" . }}
{{- end -}}
{{- with $otherChanges -}}
### Other
{{ template "change-section"  . }}
{{- end -}}
{{ end }}

{{ define "change-section" }}
{{ range . }}
{{- if .GitHubCommit -}}
* {{ .Subject }} {{ . | commitURL }} {{ . | authorURL }} {{ range .Issues }}{{ . | issue }}{{ end }}
{{ else -}}
* {{ .Subject }} {{ range .Issues }}{{ . | issue }}{{ end }}
{{ end -}}
{{- end }}
{{ end }}
`
)

var templateFuncs = template.FuncMap{
	"isPatch": func(c changeLog) bool {
		return strings.Count(c.Version, ".") > 1
	},
	"issue": func(id int) string {
		return fmt.Sprintf(issueLinkTemplate, id, id)
	},
	"commitURL": func(info gitInfo) string {
		if info.GitHubCommit.HTMLURL == "" {
			return ""
		}
		return fmt.Sprintf(linkTemplate, info.Hash, info.GitHubCommit.HTMLURL)
	},
	"authorURL": func(info gitInfo) string {
		if info.GitHubCommit.Author.Login == "" {
			return ""
		}
		return fmt.Sprintf(linkTemplate, "@"+info.GitHubCommit.Author.Login, info.GitHubCommit.Author.HTMLURL)
	},
}

func writeReleaseNotes(version string, infosMain, infosDocs gitInfos, to io.Writer) error {
	client := newGitHubAPI("hugo")
	changes := gitInfosToChangeLog(infosMain, infosDocs)
	changes.Version = version
	repo, err := client.fetchRepo()
	if err == nil {
		changes.Repo = &repo
	}
	themeCount, err := fetchThemeCount()
	if err == nil {
		changes.ThemeCount = themeCount
	}

	tmpl, err := template.New("").Funcs(templateFuncs).Parse(releaseNotesMarkdownTemplate)
	if err != nil {
		return err
	}

	err = tmpl.Execute(to, changes)
	if err != nil {
		return err
	}

	return nil

}

func fetchThemeCount() (int, error) {
	resp, err := http.Get("https://raw.githubusercontent.com/gohugoio/hugoThemes/master/.gitmodules")
	if err != nil {
		return 0, err
	}
	defer resp.Body.Close()

	b, _ := ioutil.ReadAll(resp.Body)
	return bytes.Count(b, []byte("submodule")), nil
}

func writeReleaseNotesToTmpFile(version string, infosMain, infosDocs gitInfos) (string, error) {
	f, err := ioutil.TempFile("", "hugorelease")
	if err != nil {
		return "", err
	}

	defer f.Close()

	if err := writeReleaseNotes(version, infosMain, infosDocs, f); err != nil {
		return "", err
	}

	return f.Name(), nil
}

func getReleaseNotesDocsTempDirAndName(version string, final bool) (string, string) {
	if final {
		return hugoFilepath("temp"), fmt.Sprintf("%s-relnotes-ready.md", version)
	}
	return hugoFilepath("temp"), fmt.Sprintf("%s-relnotes.md", version)
}

func getReleaseNotesDocsTempFilename(version string, final bool) string {
	return filepath.Join(getReleaseNotesDocsTempDirAndName(version, final))
}

func (r *ReleaseHandler) releaseNotesState(version string) (releaseNotesState, error) {
	docsTempPath, name := getReleaseNotesDocsTempDirAndName(version, false)
	_, err := os.Stat(filepath.Join(docsTempPath, name))

	if err == nil {
		return releaseNotesCreated, nil
	}

	docsTempPath, name = getReleaseNotesDocsTempDirAndName(version, true)
	_, err = os.Stat(filepath.Join(docsTempPath, name))

	if err == nil {
		return releaseNotesReady, nil
	}

	if !os.IsNotExist(err) {
		return releaseNotesNone, err
	}

	return releaseNotesNone, nil

}

func (r *ReleaseHandler) writeReleaseNotesToTemp(version string, infosMain, infosDocs gitInfos) (string, error) {

	docsTempPath, name := getReleaseNotesDocsTempDirAndName(version, false)

	var (
		w io.WriteCloser
	)

	if !r.try {
		os.Mkdir(docsTempPath, os.ModePerm)

		f, err := os.Create(filepath.Join(docsTempPath, name))
		if err != nil {
			return "", err
		}

		name = f.Name()

		defer f.Close()

		w = f

	} else {
		w = os.Stdout
	}

	if err := writeReleaseNotes(version, infosMain, infosDocs, w); err != nil {
		return "", err
	}

	return name, nil

}

func (r *ReleaseHandler) writeReleaseNotesToDocs(title, sourceFilename string) (string, error) {
	targetFilename := "index.md"
	bundleDir := strings.TrimSuffix(filepath.Base(sourceFilename), "-ready.md")
	contentDir := hugoFilepath("docs/content/en/news/" + bundleDir)
	targetFullFilename := filepath.Join(contentDir, targetFilename)

	if r.try {
		fmt.Printf("Write release notes to /docs: Bundle %q Dir: %q\n", bundleDir, contentDir)
		return targetFullFilename, nil
	}

	if err := os.MkdirAll(contentDir, os.ModePerm); err != nil {
		return "", nil
	}

	b, err := ioutil.ReadFile(sourceFilename)
	if err != nil {
		return "", err
	}

	f, err := os.Create(targetFullFilename)
	if err != nil {
		return "", err
	}
	defer f.Close()

	fmTail := ""
	if strings.Count(title, ".") > 1 {
		// Bug fix release
		fmTail = `
images:
- images/blog/hugo-bug-poster.png
`
	}

	if _, err := f.WriteString(fmt.Sprintf(`
---
date: %s
title: %q
description: %q
categories: ["Releases"]%s
---

	`, time.Now().Format("2006-01-02"), title, title, fmTail)); err != nil {
		return "", err
	}

	if _, err := f.Write(b); err != nil {
		return "", err
	}

	return targetFullFilename, nil

}
<commit_msg>Update releasenotes_writer.go<commit_after>// Copyright 2017-present The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package releaser implements a set of utilities and a wrapper around Goreleaser
// to help automate the Hugo release process.
package releaser

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"text/template"
	"time"
)

const (
	issueLinkTemplate            = "[#%d](https://github.com/gohugoio/hugo/issues/%d)"
	linkTemplate                 = "[%s](%s)"
	releaseNotesMarkdownTemplate = `
{{- $patchRelease := isPatch . -}}
{{- $contribsPerAuthor := .All.ContribCountPerAuthor -}}
{{- $docsContribsPerAuthor := .Docs.ContribCountPerAuthor -}}
{{- if $patchRelease }}
{{ if eq (len .All) 1 }}
This is a bug-fix release with one important fix.
{{ else }}
This is a bug-fix release with a couple of important fixes.
{{ end }}
{{ else }}
This release represents **{{ len .All }} contributions by {{ len $contribsPerAuthor }} contributors** to the main Hugo code base.
{{ end -}}

{{- if  gt (len $contribsPerAuthor) 3 -}}
{{- $u1 := index $contribsPerAuthor 0 -}}
{{- $u2 := index $contribsPerAuthor 1 -}}
{{- $u3 := index $contribsPerAuthor 2 -}}
{{- $u4 := index $contribsPerAuthor 3 -}}
{{- $u1.AuthorLink }} leads the Hugo development with a significant amount of contributions, but also a big shoutout to {{ $u2.AuthorLink }}, {{ $u3.AuthorLink }}, and {{ $u4.AuthorLink }} for their ongoing contributions.
And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
{{ end }}
{{- if not $patchRelease }}
Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
which has received **{{ len .Docs }} contributions by {{ len $docsContribsPerAuthor }} contributors**.
{{- if  gt (len $docsContribsPerAuthor) 3 -}}
{{- $u1 := index $docsContribsPerAuthor 0 -}}
{{- $u2 := index $docsContribsPerAuthor 1 -}}
{{- $u3 := index $docsContribsPerAuthor 2 -}}
{{- $u4 := index $docsContribsPerAuthor 3 }} A special thanks to {{ $u1.AuthorLink }}, {{ $u2.AuthorLink }}, {{ $u3.AuthorLink }}, and {{ $u4.AuthorLink }} for their work on the documentation site.
{{ end }}
{{ end }}
Hugo now has:

{{ with .Repo -}}
* {{ .Stars }}+ [stars](https://github.com/gohugoio/hugo/stargazers)
* {{ len .Contributors }}+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
{{- end -}}
{{ with .ThemeCount }}
* {{ . }}+ [themes](http://themes.gohugo.io/)
{{ end }}
{{ with .Notes }}
## Notes
{{ template "change-section" . }}
{{- end -}}
## Enhancements
{{ template "change-headers"  .Enhancements -}}
## Fixes
{{ template "change-headers"  .Fixes -}}

{{ define "change-headers" }}
{{ $tmplChanges := index . "templateChanges" -}}
{{- $outChanges := index . "outChanges" -}}
{{- $coreChanges := index . "coreChanges" -}}
{{- $otherChanges := index . "otherChanges" -}}
{{- with $tmplChanges -}}
### Templates
{{ template "change-section" . }}
{{- end -}}
{{- with $outChanges -}}
### Output
{{ template "change-section"  . }}
{{- end -}}
{{- with $coreChanges -}}
### Core
{{ template "change-section" . }}
{{- end -}}
{{- with $otherChanges -}}
### Other
{{ template "change-section"  . }}
{{- end -}}
{{ end }}

{{ define "change-section" }}
{{ range . }}
{{- if .GitHubCommit -}}
* {{ .Subject }} {{ . | commitURL }} {{ . | authorURL }} {{ range .Issues }}{{ . | issue }}{{ end }}
{{ else -}}
* {{ .Subject }} {{ range .Issues }}{{ . | issue }}{{ end }}
{{ end -}}
{{- end }}
{{ end }}
`
)

var templateFuncs = template.FuncMap{
	"isPatch": func(c changeLog) bool {
		return strings.Count(c.Version, ".") > 1
	},
	"issue": func(id int) string {
		return fmt.Sprintf(issueLinkTemplate, id, id)
	},
	"commitURL": func(info gitInfo) string {
		if info.GitHubCommit.HTMLURL == "" {
			return ""
		}
		return fmt.Sprintf(linkTemplate, info.Hash, info.GitHubCommit.HTMLURL)
	},
	"authorURL": func(info gitInfo) string {
		if info.GitHubCommit.Author.Login == "" {
			return ""
		}
		return fmt.Sprintf(linkTemplate, "@"+info.GitHubCommit.Author.Login, info.GitHubCommit.Author.HTMLURL)
	},
}

func writeReleaseNotes(version string, infosMain, infosDocs gitInfos, to io.Writer) error {
	client := newGitHubAPI("hugo")
	changes := gitInfosToChangeLog(infosMain, infosDocs)
	changes.Version = version
	repo, err := client.fetchRepo()
	if err == nil {
		changes.Repo = &repo
	}
	themeCount, err := fetchThemeCount()
	if err == nil {
		changes.ThemeCount = themeCount
	}

	tmpl, err := template.New("").Funcs(templateFuncs).Parse(releaseNotesMarkdownTemplate)
	if err != nil {
		return err
	}

	err = tmpl.Execute(to, changes)
	if err != nil {
		return err
	}

	return nil

}

func fetchThemeCount() (int, error) {
	resp, err := http.Get("https://raw.githubusercontent.com/gohugoio/hugoThemes/master/.gitmodules")
	if err != nil {
		return 0, err
	}
	defer resp.Body.Close()

	b, _ := ioutil.ReadAll(resp.Body)
	return bytes.Count(b, []byte("submodule")), nil
}

func writeReleaseNotesToTmpFile(version string, infosMain, infosDocs gitInfos) (string, error) {
	f, err := ioutil.TempFile("", "hugorelease")
	if err != nil {
		return "", err
	}

	defer f.Close()

	if err := writeReleaseNotes(version, infosMain, infosDocs, f); err != nil {
		return "", err
	}

	return f.Name(), nil
}

func getReleaseNotesDocsTempDirAndName(version string, final bool) (string, string) {
	if final {
		return hugoFilepath("temp"), fmt.Sprintf("%s-relnotes-ready.md", version)
	}
	return hugoFilepath("temp"), fmt.Sprintf("%s-relnotes.md", version)
}

func getReleaseNotesDocsTempFilename(version string, final bool) string {
	return filepath.Join(getReleaseNotesDocsTempDirAndName(version, final))
}

func (r *ReleaseHandler) releaseNotesState(version string) (releaseNotesState, error) {
	docsTempPath, name := getReleaseNotesDocsTempDirAndName(version, false)
	_, err := os.Stat(filepath.Join(docsTempPath, name))

	if err == nil {
		return releaseNotesCreated, nil
	}

	docsTempPath, name = getReleaseNotesDocsTempDirAndName(version, true)
	_, err = os.Stat(filepath.Join(docsTempPath, name))

	if err == nil {
		return releaseNotesReady, nil
	}

	if !os.IsNotExist(err) {
		return releaseNotesNone, err
	}

	return releaseNotesNone, nil

}

func (r *ReleaseHandler) writeReleaseNotesToTemp(version string, infosMain, infosDocs gitInfos) (string, error) {

	docsTempPath, name := getReleaseNotesDocsTempDirAndName(version, false)

	var (
		w io.WriteCloser
	)

	if !r.try {
		os.Mkdir(docsTempPath, os.ModePerm)

		f, err := os.Create(filepath.Join(docsTempPath, name))
		if err != nil {
			return "", err
		}

		name = f.Name()

		defer f.Close()

		w = f

	} else {
		w = os.Stdout
	}

	if err := writeReleaseNotes(version, infosMain, infosDocs, w); err != nil {
		return "", err
	}

	return name, nil

}

func (r *ReleaseHandler) writeReleaseNotesToDocs(title, sourceFilename string) (string, error) {
	targetFilename := "index.md"
	bundleDir := strings.TrimSuffix(filepath.Base(sourceFilename), "-ready.md")
	contentDir := hugoFilepath("docs/content/en/news/" + bundleDir)
	targetFullFilename := filepath.Join(contentDir, targetFilename)

	if r.try {
		fmt.Printf("Write release notes to /docs: Bundle %q Dir: %q\n", bundleDir, contentDir)
		return targetFullFilename, nil
	}

	if err := os.MkdirAll(contentDir, os.ModePerm); err != nil {
		return "", nil
	}

	b, err := ioutil.ReadFile(sourceFilename)
	if err != nil {
		return "", err
	}

	f, err := os.Create(targetFullFilename)
	if err != nil {
		return "", err
	}
	defer f.Close()

	fmTail := ""
	if strings.Count(title, ".") > 1 {
		// Bug fix release
		fmTail = `
images:
- images/blog/hugo-bug-poster.png
`
	}

	if _, err := f.WriteString(fmt.Sprintf(`
---
date: %s
title: %q
description: %q
categories: ["Releases"]%s
---

	`, time.Now().Format("2006-01-02"), title, title, fmTail)); err != nil {
		return "", err
	}

	if _, err := f.Write(b); err != nil {
		return "", err
	}

	return targetFullFilename, nil

}
<|endoftext|>


<commit_before>package validator

import (
	"reflect"

	validation "github.com/go-ozzo/ozzo-validation"
	"github.com/kataras/iris"
	"github.com/mlanin/go-apierr"
)

// HTTPRequest interface.
type HTTPRequest interface {
	// Type of the request.
	Type() string
	// Validate request.
	Validate(ctx *iris.Context) error
}

// Config for the middleware.
type Config struct {
	WebHandler func(err error, ctx *iris.Context)
	APIHandler func(err error, ctx *iris.Context)
}

// RequestValidator for http requests.
type RequestValidator struct {
	Request HTTPRequest
	Config
}

// New middleware.
func New(config Config) *RequestValidator {
	validator := &RequestValidator{
		Config: config,
	}

	if validator.APIHandler == nil {
		validator.APIHandler = validator.sendAPIError
	}

	if validator.WebHandler == nil {
		validator.WebHandler = validator.sendWebError
	}

	return validator
}

// ValidateRequest helper function to make validator.
func (rv *RequestValidator) ValidateRequest(request HTTPRequest) iris.HandlerFunc {
	rv.Request = request

	return rv.Validate
}

// Serve the middleware.
func (rv *RequestValidator) Serve(ctx *iris.Context) {
	ctx.Next()

	// Save current url.
	rv.storeCurrentURL(ctx)
}

// Validate request.
func (rv *RequestValidator) Validate(ctx *iris.Context) {
	err := rv.Request.Validate(ctx)

	if err == nil {
		// Save request to use it futher in controller.
		ctx.Set("request", rv.Request)

		// Switch to next handler.
		ctx.Next()
		return
	}

	// Convert errors to validation fails and send them to the user.
	if rv.wantsJSON(ctx) {
		rv.APIHandler(err, ctx)
	} else {
		rv.WebHandler(err, ctx)
	}
}

// If user want's only JSON.
func (rv *RequestValidator) wantsJSON(ctx *iris.Context) bool {
	return ctx.RequestHeader("accept") == "application/json"
}

// Store current url to reuse it for
func (rv *RequestValidator) storeCurrentURL(ctx *iris.Context) {
	if ctx.Method() == "GET" && !ctx.IsAjax() && !rv.wantsJSON(ctx) {
		ctx.Session().Set("_previous_url", ctx.Request.URL.String())
	}
}

// Add validation errors to flash and send back 302 redirect.
func (rv *RequestValidator) sendWebError(err error, ctx *iris.Context) {
	errors := rv.convertErrors(err)

	ctx.Session().SetFlash("_errors", errors)
	ctx.Session().SetFlash("_old_input", rv.Request)

	rv.redirectBack(ctx)
}

// Redirect user back to show page with errors.
func (rv *RequestValidator) redirectBack(ctx *iris.Context) {
	referer := ctx.RequestHeader("referer")
	if referer != "" {
		ctx.Redirect(referer, 302)
		return
	}

	previousURL := ctx.Session().GetFlash("_previous_url")
	if previousURL != nil {
		ctx.Redirect(previousURL.(string), 302)
		return
	}

	ctx.Redirect("/", 302)
}

// Send API validation error.
func (rv *RequestValidator) sendAPIError(err error, ctx *iris.Context) {
	errors := rv.convertErrors(err)

	// Create new api error and attach meta with errors.
	fail := *apierr.ValiationFailed
	fail.AddMeta(&apierr.ValidationErrors{
		Errors: errors,
	})

	panic(&fail)
}

// Convert errors to field - message format.
func (rv *RequestValidator) convertErrors(err error) []apierr.ValidationError {
	fails := err.(validation.Errors)
	errors := []apierr.ValidationError{}

	reflection := reflect.ValueOf(rv.Request).Elem().Type()
	requestType := rv.Request.Type()

	for field, message := range fails {
		errors = append(errors, apierr.ValidationError{
			Field:   rv.normalizeFieldName(reflection, requestType, field),
			Message: rv.normalizeMessage(message),
		})
	}

	return errors
}

// Convert request attribute name to normal.
func (rv *RequestValidator) normalizeFieldName(reflection reflect.Type, requestType string, name string) string {
	field, ok := reflection.FieldByName(name)
	if ok {
		return field.Tag.Get(requestType)
	}

	return name
}

// Upper case first letter of the message.
func (rv *RequestValidator) normalizeMessage(message error) string {
	return UcFirst(message.Error())
}
<commit_msg>Update struct name<commit_after>package validator

import (
	"reflect"

	validation "github.com/go-ozzo/ozzo-validation"
	"github.com/kataras/iris"
	"github.com/mlanin/go-apierr"
)

// HTTPRequest interface.
type HTTPRequest interface {
	// Type of the request.
	Type() string
	// Validate request.
	Validate(ctx *iris.Context) error
}

// Config for the middleware.
type Config struct {
	WebHandler func(err error, ctx *iris.Context)
	APIHandler func(err error, ctx *iris.Context)
}

// RequestsValidator for http requests.
type RequestsValidator struct {
	Request HTTPRequest
	Config
}

// New middleware.
func New(config Config) *RequestsValidator {
	validator := &RequestsValidator{
		Config: config,
	}

	if validator.APIHandler == nil {
		validator.APIHandler = validator.sendAPIError
	}

	if validator.WebHandler == nil {
		validator.WebHandler = validator.sendWebError
	}

	return validator
}

// ValidateRequest helper function to make validator.
func (rv *RequestsValidator) ValidateRequest(request HTTPRequest) iris.HandlerFunc {
	rv.Request = request

	return rv.Validate
}

// Serve the middleware.
func (rv *RequestsValidator) Serve(ctx *iris.Context) {
	ctx.Next()

	// Save current url.
	rv.storeCurrentURL(ctx)
}

// Validate request.
func (rv *RequestsValidator) Validate(ctx *iris.Context) {
	err := rv.Request.Validate(ctx)

	if err == nil {
		// Save request to use it futher in controller.
		ctx.Set("request", rv.Request)

		// Switch to next handler.
		ctx.Next()
		return
	}

	// Convert errors to validation fails and send them to the user.
	if rv.wantsJSON(ctx) {
		rv.APIHandler(err, ctx)
	} else {
		rv.WebHandler(err, ctx)
	}
}

// If user want's only JSON.
func (rv *RequestsValidator) wantsJSON(ctx *iris.Context) bool {
	return ctx.RequestHeader("accept") == "application/json"
}

// Store current url to reuse it for
func (rv *RequestsValidator) storeCurrentURL(ctx *iris.Context) {
	if ctx.Method() == "GET" && !ctx.IsAjax() && !rv.wantsJSON(ctx) {
		ctx.Session().Set("_previous_url", ctx.Request.URL.String())
	}
}

// Add validation errors to flash and send back 302 redirect.
func (rv *RequestsValidator) sendWebError(err error, ctx *iris.Context) {
	errors := rv.convertErrors(err)

	ctx.Session().SetFlash("_errors", errors)
	ctx.Session().SetFlash("_old_input", rv.Request)

	rv.redirectBack(ctx)
}

// Redirect user back to show page with errors.
func (rv *RequestsValidator) redirectBack(ctx *iris.Context) {
	referer := ctx.RequestHeader("referer")
	if referer != "" {
		ctx.Redirect(referer, 302)
		return
	}

	previousURL := ctx.Session().GetFlash("_previous_url")
	if previousURL != nil {
		ctx.Redirect(previousURL.(string), 302)
		return
	}

	ctx.Redirect("/", 302)
}

// Send API validation error.
func (rv *RequestsValidator) sendAPIError(err error, ctx *iris.Context) {
	errors := rv.convertErrors(err)

	// Create new api error and attach meta with errors.
	fail := *apierr.ValiationFailed
	fail.AddMeta(&apierr.ValidationErrors{
		Errors: errors,
	})

	panic(&fail)
}

// Convert errors to field - message format.
func (rv *RequestsValidator) convertErrors(err error) []apierr.ValidationError {
	fails := err.(validation.Errors)
	errors := []apierr.ValidationError{}

	reflection := reflect.ValueOf(rv.Request).Elem().Type()
	requestType := rv.Request.Type()

	for field, message := range fails {
		errors = append(errors, apierr.ValidationError{
			Field:   rv.normalizeFieldName(reflection, requestType, field),
			Message: rv.normalizeMessage(message),
		})
	}

	return errors
}

// Convert request attribute name to normal.
func (rv *RequestsValidator) normalizeFieldName(reflection reflect.Type, requestType string, name string) string {
	field, ok := reflection.FieldByName(name)
	if ok {
		return field.Tag.Get(requestType)
	}

	return name
}

// Upper case first letter of the message.
func (rv *RequestsValidator) normalizeMessage(message error) string {
	return UcFirst(message.Error())
}
<|endoftext|>


<commit_before>package proxy

import (
	"math/rand"

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
	ggio "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/gogoprotobuf/io"
	host "github.com/jbenet/go-ipfs/p2p/host"
	inet "github.com/jbenet/go-ipfs/p2p/net"
	peer "github.com/jbenet/go-ipfs/p2p/peer"
	dhtpb "github.com/jbenet/go-ipfs/routing/dht/pb"
	eventlog "github.com/jbenet/go-ipfs/thirdparty/eventlog"
	errors "github.com/jbenet/go-ipfs/util/debugerror"
)

const ProtocolSNR = "/ipfs/supernoderouting"

var log = eventlog.Logger("supernode/proxy")

type Proxy interface {
	Bootstrap(context.Context) error
	HandleStream(inet.Stream)
	SendMessage(ctx context.Context, m *dhtpb.Message) error
	SendRequest(ctx context.Context, m *dhtpb.Message) (*dhtpb.Message, error)
}

type standard struct {
	Host    host.Host
	Remotes []peer.PeerInfo
}

func Standard(h host.Host, remotes []peer.PeerInfo) Proxy {
	return &standard{h, remotes}
}

func (px *standard) Bootstrap(ctx context.Context) error {
		for _, info := range px.Remotes {
			if err := px.Host.Connect(ctx, info); err != nil {
				return err // TODO
			}
		}
	return nil
}

func (p *standard) HandleStream(s inet.Stream) {
	// TODO(brian): Should clients be able to satisfy requests?
	log.Error("supernode client received (dropped) a routing message from", s.Conn().RemotePeer())
	s.Close()
}

// SendMessage sends message to each remote sequentially (randomized order),
// stopping after the first successful response. If all fail, returns the last
// error.
func (px *standard) SendMessage(ctx context.Context, m *dhtpb.Message) error {
	var err error
	for _, i := range rand.Perm(len(px.Remotes)) {
		remote := px.Remotes[i].ID
		if err = px.sendMessage(ctx, m, remote); err != nil { // careful don't re-declare err!
			continue
		}
		return nil // success
	}
	return err // NB: returns the last error
}

func (px *standard) sendMessage(ctx context.Context, m *dhtpb.Message, remote peer.ID) (err error) {
	e := log.EventBegin(ctx, "sendRoutingMessage", px.Host.ID(), remote, m)
	defer func() {
		if err != nil {
			e.SetError(err)
		}
		e.Done()
	}()
	if err = px.Host.Connect(ctx, peer.PeerInfo{ID: remote}); err != nil {
		return err
	}
	s, err := px.Host.NewStream(ProtocolSNR, remote)
	if err != nil {
		return err
	}
	defer s.Close()
	pbw := ggio.NewDelimitedWriter(s)
	if err := pbw.WriteMsg(m); err != nil {
		return errors.Wrap(err)
	}
	return nil
}

// SendRequest sends the request to each remote sequentially (randomized order),
// stopping after the first successful response. If all fail, returns the last
// error.
func (px *standard) SendRequest(ctx context.Context, m *dhtpb.Message) (*dhtpb.Message, error) {
	var err error
	for _, i := range rand.Perm(len(px.Remotes)) {
		remote := px.Remotes[i].ID
		var reply *dhtpb.Message
		reply, err = px.sendRequest(ctx, m, remote) // careful don't redeclare err!
		if err != nil {
			continue
		}
		return reply, nil // success
	}
	return nil, err // NB: returns the last error
}

func (px *standard) sendRequest(ctx context.Context, m *dhtpb.Message, remote peer.ID) (*dhtpb.Message, error) {
	e := log.EventBegin(ctx, "sendRoutingRequest", px.Host.ID(), remote, eventlog.Pair("request", m))
	defer e.Done()
	if err := px.Host.Connect(ctx, peer.PeerInfo{ID: remote}); err != nil {
		e.SetError(err)
		return nil, err
	}
	s, err := px.Host.NewStream(ProtocolSNR, remote)
	if err != nil {
		e.SetError(err)
		return nil, err
	}
	defer s.Close()
	r := ggio.NewDelimitedReader(s, inet.MessageSizeMax)
	w := ggio.NewDelimitedWriter(s)
	if err = w.WriteMsg(m); err != nil {
		e.SetError(err)
		return nil, err
	}

	response := &dhtpb.Message{}
	if err = r.ReadMsg(response); err != nil {
		e.SetError(err)
		return nil, err
	}
	// need ctx expiration?
	if response == nil {
		err := errors.New("no response to request")
		e.SetError(err)
		return nil, err
	}
	e.Append(eventlog.Pair("response", response))
	e.Append(eventlog.Pair("uuid", eventlog.Uuid("foo")))
	return response, nil
}
<commit_msg>style(routing/supernode/client) fix indent<commit_after>package proxy

import (
	"math/rand"

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
	ggio "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/gogoprotobuf/io"
	host "github.com/jbenet/go-ipfs/p2p/host"
	inet "github.com/jbenet/go-ipfs/p2p/net"
	peer "github.com/jbenet/go-ipfs/p2p/peer"
	dhtpb "github.com/jbenet/go-ipfs/routing/dht/pb"
	eventlog "github.com/jbenet/go-ipfs/thirdparty/eventlog"
	errors "github.com/jbenet/go-ipfs/util/debugerror"
)

const ProtocolSNR = "/ipfs/supernoderouting"

var log = eventlog.Logger("supernode/proxy")

type Proxy interface {
	Bootstrap(context.Context) error
	HandleStream(inet.Stream)
	SendMessage(ctx context.Context, m *dhtpb.Message) error
	SendRequest(ctx context.Context, m *dhtpb.Message) (*dhtpb.Message, error)
}

type standard struct {
	Host    host.Host
	Remotes []peer.PeerInfo
}

func Standard(h host.Host, remotes []peer.PeerInfo) Proxy {
	return &standard{h, remotes}
}

func (px *standard) Bootstrap(ctx context.Context) error {
	for _, info := range px.Remotes {
		if err := px.Host.Connect(ctx, info); err != nil {
			return err // TODO
		}
	}
	return nil
}

func (p *standard) HandleStream(s inet.Stream) {
	// TODO(brian): Should clients be able to satisfy requests?
	log.Error("supernode client received (dropped) a routing message from", s.Conn().RemotePeer())
	s.Close()
}

// SendMessage sends message to each remote sequentially (randomized order),
// stopping after the first successful response. If all fail, returns the last
// error.
func (px *standard) SendMessage(ctx context.Context, m *dhtpb.Message) error {
	var err error
	for _, i := range rand.Perm(len(px.Remotes)) {
		remote := px.Remotes[i].ID
		if err = px.sendMessage(ctx, m, remote); err != nil { // careful don't re-declare err!
			continue
		}
		return nil // success
	}
	return err // NB: returns the last error
}

func (px *standard) sendMessage(ctx context.Context, m *dhtpb.Message, remote peer.ID) (err error) {
	e := log.EventBegin(ctx, "sendRoutingMessage", px.Host.ID(), remote, m)
	defer func() {
		if err != nil {
			e.SetError(err)
		}
		e.Done()
	}()
	if err = px.Host.Connect(ctx, peer.PeerInfo{ID: remote}); err != nil {
		return err
	}
	s, err := px.Host.NewStream(ProtocolSNR, remote)
	if err != nil {
		return err
	}
	defer s.Close()
	pbw := ggio.NewDelimitedWriter(s)
	if err := pbw.WriteMsg(m); err != nil {
		return errors.Wrap(err)
	}
	return nil
}

// SendRequest sends the request to each remote sequentially (randomized order),
// stopping after the first successful response. If all fail, returns the last
// error.
func (px *standard) SendRequest(ctx context.Context, m *dhtpb.Message) (*dhtpb.Message, error) {
	var err error
	for _, i := range rand.Perm(len(px.Remotes)) {
		remote := px.Remotes[i].ID
		var reply *dhtpb.Message
		reply, err = px.sendRequest(ctx, m, remote) // careful don't redeclare err!
		if err != nil {
			continue
		}
		return reply, nil // success
	}
	return nil, err // NB: returns the last error
}

func (px *standard) sendRequest(ctx context.Context, m *dhtpb.Message, remote peer.ID) (*dhtpb.Message, error) {
	e := log.EventBegin(ctx, "sendRoutingRequest", px.Host.ID(), remote, eventlog.Pair("request", m))
	defer e.Done()
	if err := px.Host.Connect(ctx, peer.PeerInfo{ID: remote}); err != nil {
		e.SetError(err)
		return nil, err
	}
	s, err := px.Host.NewStream(ProtocolSNR, remote)
	if err != nil {
		e.SetError(err)
		return nil, err
	}
	defer s.Close()
	r := ggio.NewDelimitedReader(s, inet.MessageSizeMax)
	w := ggio.NewDelimitedWriter(s)
	if err = w.WriteMsg(m); err != nil {
		e.SetError(err)
		return nil, err
	}

	response := &dhtpb.Message{}
	if err = r.ReadMsg(response); err != nil {
		e.SetError(err)
		return nil, err
	}
	// need ctx expiration?
	if response == nil {
		err := errors.New("no response to request")
		e.SetError(err)
		return nil, err
	}
	e.Append(eventlog.Pair("response", response))
	e.Append(eventlog.Pair("uuid", eventlog.Uuid("foo")))
	return response, nil
}
<|endoftext|>


<commit_before>package imageserver

import (
	"time"

	"github.com/Symantec/Dominator/lib/hash"
	"github.com/Symantec/Dominator/lib/image"
)

type AddImageRequest struct {
	ImageName string
	Image     *image.Image
}

type AddImageResponse struct{}

type ChangeOwnerRequest struct {
	DirectoryName string
	OwnerGroup    string
}

type ChangeOwnerResponse struct{}

type CheckImageRequest struct {
	ImageName string
}

type CheckImageResponse struct {
	ImageExists bool
}

type DeleteImageRequest struct {
	ImageName string
}

type DeleteImageResponse struct{}

type DeleteUnreferencedObjectsRequest struct {
	Percentage uint8
	Bytes      uint64
}

type DeleteUnreferencedObjectsResponse struct{}

type GetImageRequest struct {
	ImageName string
	Timeout   time.Duration
}

type GetImageResponse struct {
	Image *image.Image
}

const (
	OperationAddImage = iota
	OperationDeleteImage
	OperationMakeDirectory
)

// The GetImageUpdates() RPC is fully streamed.
// The client sends no information to the server.
// The server sends a stream of ImageUpdate messages.

type ImageUpdate struct {
	Name      string // "" signifies initial list is sent, changes to follow.
	Directory *image.Directory
	Operation uint
}

// The ListDirectories() RPC is fully streamed.
// The client sends no information to the server.
// The server sends a stream of image.Directory values with an empty string
// for the Name field signifying the end of the list.

// The ListImages() RPC is fully streamed.
// The client sends no information to the server.
// The server sends a stream of strings (image names) with an empty string
// signifying the end of the list.

// The ListUnreferencedObjects() RPC is fully streamed.
// The client sends no information to the server.
// The server sends a stream of Object values with a zero Size field signifying
// the end of the stream.

type Object struct {
	Hash hash.Hash
	Size uint64
}

type MakeDirectoryRequest struct {
	DirectoryName string
}

type MakeDirectoryResponse struct{}
<commit_msg>Add proto/imageserver.GetImageRequest.IgnoreFilesystem.<commit_after>package imageserver

import (
	"time"

	"github.com/Symantec/Dominator/lib/hash"
	"github.com/Symantec/Dominator/lib/image"
)

type AddImageRequest struct {
	ImageName string
	Image     *image.Image
}

type AddImageResponse struct{}

type ChangeOwnerRequest struct {
	DirectoryName string
	OwnerGroup    string
}

type ChangeOwnerResponse struct{}

type CheckImageRequest struct {
	ImageName string
}

type CheckImageResponse struct {
	ImageExists bool
}

type DeleteImageRequest struct {
	ImageName string
}

type DeleteImageResponse struct{}

type DeleteUnreferencedObjectsRequest struct {
	Percentage uint8
	Bytes      uint64
}

type DeleteUnreferencedObjectsResponse struct{}

type GetImageRequest struct {
	ImageName        string
	IgnoreFilesystem bool
	Timeout          time.Duration
}

type GetImageResponse struct {
	Image *image.Image
}

const (
	OperationAddImage = iota
	OperationDeleteImage
	OperationMakeDirectory
)

// The GetImageUpdates() RPC is fully streamed.
// The client sends no information to the server.
// The server sends a stream of ImageUpdate messages.

type ImageUpdate struct {
	Name      string // "" signifies initial list is sent, changes to follow.
	Directory *image.Directory
	Operation uint
}

// The ListDirectories() RPC is fully streamed.
// The client sends no information to the server.
// The server sends a stream of image.Directory values with an empty string
// for the Name field signifying the end of the list.

// The ListImages() RPC is fully streamed.
// The client sends no information to the server.
// The server sends a stream of strings (image names) with an empty string
// signifying the end of the list.

// The ListUnreferencedObjects() RPC is fully streamed.
// The client sends no information to the server.
// The server sends a stream of Object values with a zero Size field signifying
// the end of the stream.

type Object struct {
	Hash hash.Hash
	Size uint64
}

type MakeDirectoryRequest struct {
	DirectoryName string
}

type MakeDirectoryResponse struct{}
<|endoftext|>


<commit_before>package protocol

import "time"

// DefaultMaxCongestionWindow is the default for the max congestion window
const DefaultMaxCongestionWindow = 1000

// InitialCongestionWindow is the initial congestion window in QUIC packets
const InitialCongestionWindow = 32

// MaxUndecryptablePackets limits the number of undecryptable packets that a
// session queues for later until it sends a public reset.
const MaxUndecryptablePackets = 10

// AckSendDelay is the maximum delay that can be applied to an ACK for a retransmittable packet
// This is the value Chromium is using
const AckSendDelay = 25 * time.Millisecond

// ReceiveStreamFlowControlWindow is the stream-level flow control window for receiving data
// This is the value that Google servers are using
const ReceiveStreamFlowControlWindow ByteCount = (1 << 10) * 32 // 32 kB

// ReceiveConnectionFlowControlWindow is the connection-level flow control window for receiving data
// This is the value that Google servers are using
const ReceiveConnectionFlowControlWindow ByteCount = (1 << 10) * 48 // 48 kB

// MaxReceiveStreamFlowControlWindow is the maximum stream-level flow control window for receiving data
// This is the value that Google servers are using
const MaxReceiveStreamFlowControlWindow ByteCount = 1 * (1 << 20) // 1 MB

// MaxReceiveConnectionFlowControlWindow is the connection-level flow control window for receiving data
// This is the value that Google servers are using
const MaxReceiveConnectionFlowControlWindow ByteCount = 1.5 * (1 << 20) // 1.5 MB

// MaxStreamsPerConnection is the maximum value accepted for the number of streams per connection
const MaxStreamsPerConnection = 100

// MaxIncomingDynamicStreamsPerConnection is the maximum value accepted for the incoming number of dynamic streams per connection
const MaxIncomingDynamicStreamsPerConnection = 100

// MaxStreamsMultiplier is the slack the client is allowed for the maximum number of streams per connection, needed e.g. when packets are out of order or dropped. The minimum of this procentual increase and the absolute increment specified by MaxStreamsMinimumIncrement is used.
const MaxStreamsMultiplier = 1.1

// MaxStreamsMinimumIncrement is the slack the client is allowed for the maximum number of streams per connection, needed e.g. when packets are out of order or dropped. The minimum of this absolute increment and the procentual increase specified by MaxStreamsMultiplier is used.
const MaxStreamsMinimumIncrement = 10

// MaxNewStreamIDDelta is the maximum difference between and a newly opened Stream and the highest StreamID that a client has ever opened
// note that the number of streams is half this value, since the client can only open streams with open StreamID
const MaxNewStreamIDDelta = 4 * MaxStreamsPerConnection

// MaxSessionUnprocessedPackets is the max number of packets stored in each session that are not yet processed.
const MaxSessionUnprocessedPackets = DefaultMaxCongestionWindow

// RetransmissionThreshold + 1 is the number of times a packet has to be NACKed so that it gets retransmitted
const RetransmissionThreshold = 3

// SkipPacketAveragePeriodLength is the average period length in which one packet number is skipped to prevent an Optimistic ACK attack
const SkipPacketAveragePeriodLength PacketNumber = 500

// MaxTrackedSkippedPackets is the maximum number of skipped packet numbers the SentPacketHandler keep track of for Optimistic ACK attack mitigation
const MaxTrackedSkippedPackets = 10

// STKExpiryTimeSec is the valid time of a source address token in seconds
const STKExpiryTimeSec = 24 * 60 * 60

// MaxTrackedSentPackets is maximum number of sent packets saved for either later retransmission or entropy calculation
const MaxTrackedSentPackets = 2 * DefaultMaxCongestionWindow

// MaxTrackedReceivedPackets is the maximum number of received packets saved for doing the entropy calculations
const MaxTrackedReceivedPackets = 2 * DefaultMaxCongestionWindow

// MaxTrackedReceivedAckRanges is the maximum number of ACK ranges tracked
const MaxTrackedReceivedAckRanges = DefaultMaxCongestionWindow

// MaxPacketsReceivedBeforeAckSend is the number of packets that can be received before an ACK frame is sent
const MaxPacketsReceivedBeforeAckSend = 20

// RetransmittablePacketsBeforeAck is the number of retransmittable that an ACK is sent for
const RetransmittablePacketsBeforeAck = 2

// MaxStreamFrameSorterGaps is the maximum number of gaps between received StreamFrames
// prevents DoS attacks against the streamFrameSorter
const MaxStreamFrameSorterGaps = 1000

// CryptoMaxParams is the upper limit for the number of parameters in a crypto message.
// Value taken from Chrome.
const CryptoMaxParams = 128

// CryptoParameterMaxLength is the upper limit for the length of a parameter in a crypto message.
const CryptoParameterMaxLength = 2000

// EphermalKeyLifetime is the lifetime of the ephermal key during the handshake, see handshake.getEphermalKEX.
const EphermalKeyLifetime = time.Minute

// InitialIdleTimeout is the timeout before the handshake succeeds.
const InitialIdleTimeout = 5 * time.Second

// DefaultIdleTimeout is the default idle timeout.
const DefaultIdleTimeout = 30 * time.Second

// MaxIdleTimeout is the maximum idle timeout that can be negotiated.
const MaxIdleTimeout = 1 * time.Minute

// MaxTimeForCryptoHandshake is the default timeout for a connection until the crypto handshake succeeds.
const MaxTimeForCryptoHandshake = 10 * time.Second

// ClosedSessionDeleteTimeout the server ignores packets arriving on a connection that is already closed
// after this time all information about the old connection will be deleted
const ClosedSessionDeleteTimeout = time.Minute

// NumCachedCertificates is the number of cached compressed certificate chains, each taking ~1K space
const NumCachedCertificates = 128
<commit_msg>increase CryptoParameterMaxLength<commit_after>package protocol

import "time"

// DefaultMaxCongestionWindow is the default for the max congestion window
const DefaultMaxCongestionWindow = 1000

// InitialCongestionWindow is the initial congestion window in QUIC packets
const InitialCongestionWindow = 32

// MaxUndecryptablePackets limits the number of undecryptable packets that a
// session queues for later until it sends a public reset.
const MaxUndecryptablePackets = 10

// AckSendDelay is the maximum delay that can be applied to an ACK for a retransmittable packet
// This is the value Chromium is using
const AckSendDelay = 25 * time.Millisecond

// ReceiveStreamFlowControlWindow is the stream-level flow control window for receiving data
// This is the value that Google servers are using
const ReceiveStreamFlowControlWindow ByteCount = (1 << 10) * 32 // 32 kB

// ReceiveConnectionFlowControlWindow is the connection-level flow control window for receiving data
// This is the value that Google servers are using
const ReceiveConnectionFlowControlWindow ByteCount = (1 << 10) * 48 // 48 kB

// MaxReceiveStreamFlowControlWindow is the maximum stream-level flow control window for receiving data
// This is the value that Google servers are using
const MaxReceiveStreamFlowControlWindow ByteCount = 1 * (1 << 20) // 1 MB

// MaxReceiveConnectionFlowControlWindow is the connection-level flow control window for receiving data
// This is the value that Google servers are using
const MaxReceiveConnectionFlowControlWindow ByteCount = 1.5 * (1 << 20) // 1.5 MB

// MaxStreamsPerConnection is the maximum value accepted for the number of streams per connection
const MaxStreamsPerConnection = 100

// MaxIncomingDynamicStreamsPerConnection is the maximum value accepted for the incoming number of dynamic streams per connection
const MaxIncomingDynamicStreamsPerConnection = 100

// MaxStreamsMultiplier is the slack the client is allowed for the maximum number of streams per connection, needed e.g. when packets are out of order or dropped. The minimum of this procentual increase and the absolute increment specified by MaxStreamsMinimumIncrement is used.
const MaxStreamsMultiplier = 1.1

// MaxStreamsMinimumIncrement is the slack the client is allowed for the maximum number of streams per connection, needed e.g. when packets are out of order or dropped. The minimum of this absolute increment and the procentual increase specified by MaxStreamsMultiplier is used.
const MaxStreamsMinimumIncrement = 10

// MaxNewStreamIDDelta is the maximum difference between and a newly opened Stream and the highest StreamID that a client has ever opened
// note that the number of streams is half this value, since the client can only open streams with open StreamID
const MaxNewStreamIDDelta = 4 * MaxStreamsPerConnection

// MaxSessionUnprocessedPackets is the max number of packets stored in each session that are not yet processed.
const MaxSessionUnprocessedPackets = DefaultMaxCongestionWindow

// RetransmissionThreshold + 1 is the number of times a packet has to be NACKed so that it gets retransmitted
const RetransmissionThreshold = 3

// SkipPacketAveragePeriodLength is the average period length in which one packet number is skipped to prevent an Optimistic ACK attack
const SkipPacketAveragePeriodLength PacketNumber = 500

// MaxTrackedSkippedPackets is the maximum number of skipped packet numbers the SentPacketHandler keep track of for Optimistic ACK attack mitigation
const MaxTrackedSkippedPackets = 10

// STKExpiryTimeSec is the valid time of a source address token in seconds
const STKExpiryTimeSec = 24 * 60 * 60

// MaxTrackedSentPackets is maximum number of sent packets saved for either later retransmission or entropy calculation
const MaxTrackedSentPackets = 2 * DefaultMaxCongestionWindow

// MaxTrackedReceivedPackets is the maximum number of received packets saved for doing the entropy calculations
const MaxTrackedReceivedPackets = 2 * DefaultMaxCongestionWindow

// MaxTrackedReceivedAckRanges is the maximum number of ACK ranges tracked
const MaxTrackedReceivedAckRanges = DefaultMaxCongestionWindow

// MaxPacketsReceivedBeforeAckSend is the number of packets that can be received before an ACK frame is sent
const MaxPacketsReceivedBeforeAckSend = 20

// RetransmittablePacketsBeforeAck is the number of retransmittable that an ACK is sent for
const RetransmittablePacketsBeforeAck = 2

// MaxStreamFrameSorterGaps is the maximum number of gaps between received StreamFrames
// prevents DoS attacks against the streamFrameSorter
const MaxStreamFrameSorterGaps = 1000

// CryptoMaxParams is the upper limit for the number of parameters in a crypto message.
// Value taken from Chrome.
const CryptoMaxParams = 128

// CryptoParameterMaxLength is the upper limit for the length of a parameter in a crypto message.
const CryptoParameterMaxLength = 4000

// EphermalKeyLifetime is the lifetime of the ephermal key during the handshake, see handshake.getEphermalKEX.
const EphermalKeyLifetime = time.Minute

// InitialIdleTimeout is the timeout before the handshake succeeds.
const InitialIdleTimeout = 5 * time.Second

// DefaultIdleTimeout is the default idle timeout.
const DefaultIdleTimeout = 30 * time.Second

// MaxIdleTimeout is the maximum idle timeout that can be negotiated.
const MaxIdleTimeout = 1 * time.Minute

// MaxTimeForCryptoHandshake is the default timeout for a connection until the crypto handshake succeeds.
const MaxTimeForCryptoHandshake = 10 * time.Second

// ClosedSessionDeleteTimeout the server ignores packets arriving on a connection that is already closed
// after this time all information about the old connection will be deleted
const ClosedSessionDeleteTimeout = time.Minute

// NumCachedCertificates is the number of cached compressed certificate chains, each taking ~1K space
const NumCachedCertificates = 128
<|endoftext|>


<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package common_test

import (
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"path/filepath"

	gc "launchpad.net/gocheck"
	"launchpad.net/goyaml"

	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/environs/storage"
	envtesting "launchpad.net/juju-core/environs/testing"
	"launchpad.net/juju-core/instance"
	"launchpad.net/juju-core/provider/common"
	"launchpad.net/juju-core/testing"
	"launchpad.net/juju-core/testing/testbase"
)

type StateSuite struct {
	testbase.LoggingSuite
}

var _ = gc.Suite(&StateSuite{})

type cleaner interface {
	AddCleanup(testbase.CleanupFunc)
}

func newStorageWithDataDir(suite cleaner, c *gc.C) (stor storage.Storage, dataDir string) {
	closer, stor, dataDir := envtesting.CreateLocalTestStorage(c)
	suite.AddCleanup(func(*gc.C) { closer.Close() })
	return
}

func newStorage(suite cleaner, c *gc.C) (stor storage.Storage) {
	stor, _ = newStorageWithDataDir(suite, c)
	return
}

// testingHTTPServer creates a tempdir backed https server with invalid certs
func testingHTTPSServer(suite cleaner, c *gc.C) (baseURL string, dataDir string) {
	dataDir = c.MkDir()
	mux := http.NewServeMux()
	mux.Handle("/", http.FileServer(http.Dir(dataDir)))
	server := httptest.NewTLSServer(mux)
	suite.AddCleanup(func(*gc.C) { server.Close() })
	baseURL = server.URL
	return
}

func (suite *StateSuite) TestCreateStateFileWritesEmptyStateFile(c *gc.C) {
	stor := newStorage(suite, c)

	url, err := common.CreateStateFile(stor)
	c.Assert(err, gc.IsNil)

	reader, err := storage.Get(stor, common.StateFile)
	c.Assert(err, gc.IsNil)
	data, err := ioutil.ReadAll(reader)
	c.Assert(err, gc.IsNil)
	c.Check(string(data), gc.Equals, "")
	c.Assert(url, gc.NotNil)
	expectedURL, err := stor.URL(common.StateFile)
	c.Assert(err, gc.IsNil)
	c.Check(url, gc.Equals, expectedURL)
}

func (suite *StateSuite) TestSaveStateWritesStateFile(c *gc.C) {
	stor := newStorage(suite, c)
	arch := "amd64"
	state := common.BootstrapState{
		StateInstances:  []instance.Id{instance.Id("an-instance-id")},
		Characteristics: []instance.HardwareCharacteristics{{Arch: &arch}}}
	marshaledState, err := goyaml.Marshal(state)
	c.Assert(err, gc.IsNil)

	err = common.SaveState(stor, &state)
	c.Assert(err, gc.IsNil)

	loadedState, err := storage.Get(stor, common.StateFile)
	c.Assert(err, gc.IsNil)
	content, err := ioutil.ReadAll(loadedState)
	c.Assert(err, gc.IsNil)
	c.Check(content, gc.DeepEquals, marshaledState)
}

func (suite *StateSuite) setUpSavedState(c *gc.C, dataDir string) common.BootstrapState {
	arch := "amd64"
	state := common.BootstrapState{
		StateInstances:  []instance.Id{instance.Id("an-instance-id")},
		Characteristics: []instance.HardwareCharacteristics{{Arch: &arch}}}
	content, err := goyaml.Marshal(state)
	c.Assert(err, gc.IsNil)
	err = ioutil.WriteFile(filepath.Join(dataDir, common.StateFile), []byte(content), 0644)
	c.Assert(err, gc.IsNil)
	return state
}

func (suite *StateSuite) TestLoadStateReadsStateFile(c *gc.C) {
	storage, dataDir := newStorageWithDataDir(suite, c)
	state := suite.setUpSavedState(c, dataDir)
	storedState, err := common.LoadState(storage)
	c.Assert(err, gc.IsNil)
	c.Check(*storedState, gc.DeepEquals, state)
}

func (suite *StateSuite) TestLoadStateFromURLReadsStateFile(c *gc.C) {
	stor, dataDir := newStorageWithDataDir(suite, c)
	state := suite.setUpSavedState(c, dataDir)
	url, err := stor.URL(common.StateFile)
	c.Assert(err, gc.IsNil)
	storedState, err := common.LoadStateFromURL(url, false)
	c.Assert(err, gc.IsNil)
	c.Check(*storedState, gc.DeepEquals, state)
}

func (suite *StateSuite) TestLoadStateFromURLBadCert(c *gc.C) {
	baseURL, _ := testingHTTPSServer(suite, c)
	url := baseURL + "/" + common.StateFile
	storedState, err := common.LoadStateFromURL(url, false)
	c.Assert(err, gc.ErrorMatches, ".*/provider-state:.* certificate signed by unknown authority")
	c.Assert(storedState, gc.IsNil)
}

func (suite *StateSuite) TestLoadStateFromURLBadCertPermitted(c *gc.C) {
	baseURL, dataDir := testingHTTPSServer(suite, c)
	state := suite.setUpSavedState(c, dataDir)
	url := baseURL + "/" + common.StateFile
	storedState, err := common.LoadStateFromURL(url, true)
	c.Assert(err, gc.IsNil)
	c.Check(*storedState, gc.DeepEquals, state)
}

func (suite *StateSuite) TestLoadStateMissingFile(c *gc.C) {
	stor := newStorage(suite, c)
	_, err := common.LoadState(stor)
	c.Check(err, gc.Equals, environs.ErrNotBootstrapped)
}

func (suite *StateSuite) TestLoadStateIntegratesWithSaveState(c *gc.C) {
	storage := newStorage(suite, c)
	arch := "amd64"
	state := common.BootstrapState{
		StateInstances:  []instance.Id{instance.Id("an-instance-id")},
		Characteristics: []instance.HardwareCharacteristics{{Arch: &arch}}}
	err := common.SaveState(storage, &state)
	c.Assert(err, gc.IsNil)
	storedState, err := common.LoadState(storage)
	c.Assert(err, gc.IsNil)

	c.Check(*storedState, gc.DeepEquals, state)
}

func (suite *StateSuite) TestGetDNSNamesAcceptsNil(c *gc.C) {
	result := common.GetDNSNames(nil)
	c.Check(result, gc.DeepEquals, []string{})
}

func (suite *StateSuite) TestGetDNSNamesReturnsNames(c *gc.C) {
	instances := []instance.Instance{
		&dnsNameFakeInstance{name: "foo"},
		&dnsNameFakeInstance{name: "bar"},
	}

	c.Check(common.GetDNSNames(instances), gc.DeepEquals, []string{"foo", "bar"})
}

func (suite *StateSuite) TestGetDNSNamesIgnoresNils(c *gc.C) {
	c.Check(common.GetDNSNames([]instance.Instance{nil, nil}), gc.DeepEquals, []string{})
}

func (suite *StateSuite) TestGetDNSNamesIgnoresInstancesWithoutNames(c *gc.C) {
	instances := []instance.Instance{&dnsNameFakeInstance{err: instance.ErrNoDNSName}}
	c.Check(common.GetDNSNames(instances), gc.DeepEquals, []string{})
}

func (suite *StateSuite) TestGetDNSNamesIgnoresInstancesWithBlankNames(c *gc.C) {
	instances := []instance.Instance{&dnsNameFakeInstance{name: ""}}
	c.Check(common.GetDNSNames(instances), gc.DeepEquals, []string{})
}

func (suite *StateSuite) TestComposeAddressesAcceptsNil(c *gc.C) {
	c.Check(common.ComposeAddresses(nil, 1433), gc.DeepEquals, []string{})
}

func (suite *StateSuite) TestComposeAddressesSuffixesAddresses(c *gc.C) {
	c.Check(
		common.ComposeAddresses([]string{"onehost", "otherhost"}, 1957),
		gc.DeepEquals,
		[]string{"onehost:1957", "otherhost:1957"})
}

func (suite *StateSuite) TestGetStateInfo(c *gc.C) {
	cert := testing.CACert
	attrs := testing.FakeConfig().Merge(testing.Attrs{
		"ca-cert":    cert,
		"state-port": 123,
		"api-port":   456,
	})
	cfg, err := config.New(config.NoDefaults, attrs)
	c.Assert(err, gc.IsNil)
	hostnames := []string{"onehost", "otherhost"}

	stateInfo, apiInfo := common.GetStateInfo(cfg, hostnames)

	c.Check(stateInfo.Addrs, gc.DeepEquals, []string{"onehost:123", "otherhost:123"})
	c.Check(string(stateInfo.CACert), gc.Equals, cert)
	c.Check(apiInfo.Addrs, gc.DeepEquals, []string{"onehost:456", "otherhost:456"})
	c.Check(string(apiInfo.CACert), gc.Equals, cert)
}
<commit_msg>Doc comment tweak mentioned by axw in review<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package common_test

import (
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"path/filepath"

	gc "launchpad.net/gocheck"
	"launchpad.net/goyaml"

	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/environs/storage"
	envtesting "launchpad.net/juju-core/environs/testing"
	"launchpad.net/juju-core/instance"
	"launchpad.net/juju-core/provider/common"
	"launchpad.net/juju-core/testing"
	"launchpad.net/juju-core/testing/testbase"
)

type StateSuite struct {
	testbase.LoggingSuite
}

var _ = gc.Suite(&StateSuite{})

type cleaner interface {
	AddCleanup(testbase.CleanupFunc)
}

func newStorageWithDataDir(suite cleaner, c *gc.C) (stor storage.Storage, dataDir string) {
	closer, stor, dataDir := envtesting.CreateLocalTestStorage(c)
	suite.AddCleanup(func(*gc.C) { closer.Close() })
	return
}

func newStorage(suite cleaner, c *gc.C) (stor storage.Storage) {
	stor, _ = newStorageWithDataDir(suite, c)
	return
}

// testingHTTPServer creates a tempdir backed https server without certs
func testingHTTPSServer(suite cleaner, c *gc.C) (baseURL string, dataDir string) {
	dataDir = c.MkDir()
	mux := http.NewServeMux()
	mux.Handle("/", http.FileServer(http.Dir(dataDir)))
	server := httptest.NewTLSServer(mux)
	suite.AddCleanup(func(*gc.C) { server.Close() })
	baseURL = server.URL
	return
}

func (suite *StateSuite) TestCreateStateFileWritesEmptyStateFile(c *gc.C) {
	stor := newStorage(suite, c)

	url, err := common.CreateStateFile(stor)
	c.Assert(err, gc.IsNil)

	reader, err := storage.Get(stor, common.StateFile)
	c.Assert(err, gc.IsNil)
	data, err := ioutil.ReadAll(reader)
	c.Assert(err, gc.IsNil)
	c.Check(string(data), gc.Equals, "")
	c.Assert(url, gc.NotNil)
	expectedURL, err := stor.URL(common.StateFile)
	c.Assert(err, gc.IsNil)
	c.Check(url, gc.Equals, expectedURL)
}

func (suite *StateSuite) TestSaveStateWritesStateFile(c *gc.C) {
	stor := newStorage(suite, c)
	arch := "amd64"
	state := common.BootstrapState{
		StateInstances:  []instance.Id{instance.Id("an-instance-id")},
		Characteristics: []instance.HardwareCharacteristics{{Arch: &arch}}}
	marshaledState, err := goyaml.Marshal(state)
	c.Assert(err, gc.IsNil)

	err = common.SaveState(stor, &state)
	c.Assert(err, gc.IsNil)

	loadedState, err := storage.Get(stor, common.StateFile)
	c.Assert(err, gc.IsNil)
	content, err := ioutil.ReadAll(loadedState)
	c.Assert(err, gc.IsNil)
	c.Check(content, gc.DeepEquals, marshaledState)
}

func (suite *StateSuite) setUpSavedState(c *gc.C, dataDir string) common.BootstrapState {
	arch := "amd64"
	state := common.BootstrapState{
		StateInstances:  []instance.Id{instance.Id("an-instance-id")},
		Characteristics: []instance.HardwareCharacteristics{{Arch: &arch}}}
	content, err := goyaml.Marshal(state)
	c.Assert(err, gc.IsNil)
	err = ioutil.WriteFile(filepath.Join(dataDir, common.StateFile), []byte(content), 0644)
	c.Assert(err, gc.IsNil)
	return state
}

func (suite *StateSuite) TestLoadStateReadsStateFile(c *gc.C) {
	storage, dataDir := newStorageWithDataDir(suite, c)
	state := suite.setUpSavedState(c, dataDir)
	storedState, err := common.LoadState(storage)
	c.Assert(err, gc.IsNil)
	c.Check(*storedState, gc.DeepEquals, state)
}

func (suite *StateSuite) TestLoadStateFromURLReadsStateFile(c *gc.C) {
	stor, dataDir := newStorageWithDataDir(suite, c)
	state := suite.setUpSavedState(c, dataDir)
	url, err := stor.URL(common.StateFile)
	c.Assert(err, gc.IsNil)
	storedState, err := common.LoadStateFromURL(url, false)
	c.Assert(err, gc.IsNil)
	c.Check(*storedState, gc.DeepEquals, state)
}

func (suite *StateSuite) TestLoadStateFromURLBadCert(c *gc.C) {
	baseURL, _ := testingHTTPSServer(suite, c)
	url := baseURL + "/" + common.StateFile
	storedState, err := common.LoadStateFromURL(url, false)
	c.Assert(err, gc.ErrorMatches, ".*/provider-state:.* certificate signed by unknown authority")
	c.Assert(storedState, gc.IsNil)
}

func (suite *StateSuite) TestLoadStateFromURLBadCertPermitted(c *gc.C) {
	baseURL, dataDir := testingHTTPSServer(suite, c)
	state := suite.setUpSavedState(c, dataDir)
	url := baseURL + "/" + common.StateFile
	storedState, err := common.LoadStateFromURL(url, true)
	c.Assert(err, gc.IsNil)
	c.Check(*storedState, gc.DeepEquals, state)
}

func (suite *StateSuite) TestLoadStateMissingFile(c *gc.C) {
	stor := newStorage(suite, c)
	_, err := common.LoadState(stor)
	c.Check(err, gc.Equals, environs.ErrNotBootstrapped)
}

func (suite *StateSuite) TestLoadStateIntegratesWithSaveState(c *gc.C) {
	storage := newStorage(suite, c)
	arch := "amd64"
	state := common.BootstrapState{
		StateInstances:  []instance.Id{instance.Id("an-instance-id")},
		Characteristics: []instance.HardwareCharacteristics{{Arch: &arch}}}
	err := common.SaveState(storage, &state)
	c.Assert(err, gc.IsNil)
	storedState, err := common.LoadState(storage)
	c.Assert(err, gc.IsNil)

	c.Check(*storedState, gc.DeepEquals, state)
}

func (suite *StateSuite) TestGetDNSNamesAcceptsNil(c *gc.C) {
	result := common.GetDNSNames(nil)
	c.Check(result, gc.DeepEquals, []string{})
}

func (suite *StateSuite) TestGetDNSNamesReturnsNames(c *gc.C) {
	instances := []instance.Instance{
		&dnsNameFakeInstance{name: "foo"},
		&dnsNameFakeInstance{name: "bar"},
	}

	c.Check(common.GetDNSNames(instances), gc.DeepEquals, []string{"foo", "bar"})
}

func (suite *StateSuite) TestGetDNSNamesIgnoresNils(c *gc.C) {
	c.Check(common.GetDNSNames([]instance.Instance{nil, nil}), gc.DeepEquals, []string{})
}

func (suite *StateSuite) TestGetDNSNamesIgnoresInstancesWithoutNames(c *gc.C) {
	instances := []instance.Instance{&dnsNameFakeInstance{err: instance.ErrNoDNSName}}
	c.Check(common.GetDNSNames(instances), gc.DeepEquals, []string{})
}

func (suite *StateSuite) TestGetDNSNamesIgnoresInstancesWithBlankNames(c *gc.C) {
	instances := []instance.Instance{&dnsNameFakeInstance{name: ""}}
	c.Check(common.GetDNSNames(instances), gc.DeepEquals, []string{})
}

func (suite *StateSuite) TestComposeAddressesAcceptsNil(c *gc.C) {
	c.Check(common.ComposeAddresses(nil, 1433), gc.DeepEquals, []string{})
}

func (suite *StateSuite) TestComposeAddressesSuffixesAddresses(c *gc.C) {
	c.Check(
		common.ComposeAddresses([]string{"onehost", "otherhost"}, 1957),
		gc.DeepEquals,
		[]string{"onehost:1957", "otherhost:1957"})
}

func (suite *StateSuite) TestGetStateInfo(c *gc.C) {
	cert := testing.CACert
	attrs := testing.FakeConfig().Merge(testing.Attrs{
		"ca-cert":    cert,
		"state-port": 123,
		"api-port":   456,
	})
	cfg, err := config.New(config.NoDefaults, attrs)
	c.Assert(err, gc.IsNil)
	hostnames := []string{"onehost", "otherhost"}

	stateInfo, apiInfo := common.GetStateInfo(cfg, hostnames)

	c.Check(stateInfo.Addrs, gc.DeepEquals, []string{"onehost:123", "otherhost:123"})
	c.Check(string(stateInfo.CACert), gc.Equals, cert)
	c.Check(apiInfo.Addrs, gc.DeepEquals, []string{"onehost:456", "otherhost:456"})
	c.Check(string(apiInfo.CACert), gc.Equals, cert)
}
<|endoftext|>


<commit_before>// Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.

package provider

import (
	"fmt"
	"testing"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

const (
	TagRequiredOnlyResource = TagResourceDependencies + `
resource "oci_identity_tag" "test_tag" {
	#Required
	description = "${var.tag_description}"
	name = "${var.tag_name}"
	tag_namespace_id = "${oci_identity_tag_namespace.test_tag_namespace.id}"
}
`

	TagResourceConfig = TagResourceDependencies + `
resource "oci_identity_tag" "test_tag" {
    depends_on = ["oci_identity_tag_namespace.tag-namespace1", "oci_identity_tag.tag1"]
	#Required
	description = "${var.tag_description}"
	name = "${var.tag_name}"
	tag_namespace_id = "${oci_identity_tag_namespace.test_tag_namespace.id}"

	#Optional
	defined_tags = "${map("${oci_identity_tag_namespace.tag-namespace1.name}.${oci_identity_tag.tag1.name}", "${var.tag_defined_tags_value}")}"
	freeform_tags = "${var.tag_freeform_tags}"
}
`
	TagPropertyVariables = `
variable "tag_defined_tags_value" { default = "value" }
variable "tag_description" { default = "This tag will show the cost center that will be used for billing of associated resources." }
variable "tag_freeform_tags" { default = {"Department"= "Finance"} }
variable "tag_name" { default = "CostCenter" }

`
	TagResourceDependencies = TagNamespacePropertyVariables + TagNamespaceRequiredOnlyResource

	DefinedTagsDependencies = `
variable defined_tag_namespace_name { default = "" }
resource "oci_identity_tag_namespace" "tag-namespace1" {
  		#Required
		compartment_id = "${var.tenancy_ocid}"
  		description = "example tag namespace"
  		name = "${var.defined_tag_namespace_name != "" ? var.defined_tag_namespace_name : "example-tag-namespace-${var.region}"}"

		is_retired = false
}

resource "oci_identity_tag" "tag1" {
  		#Required
  		description = "example tag"
  		name = "example-tag"
        tag_namespace_id = "${oci_identity_tag_namespace.tag-namespace1.id}"

		is_retired = false
}
`
)

func TestIdentityTagResource_basic(t *testing.T) {
	provider := testAccProvider
	config := testProviderConfig()

	compartmentId := getRequiredEnvSetting("compartment_ocid")
	compartmentIdVariableStr := fmt.Sprintf("variable \"compartment_id\" { default = \"%s\" }\n", compartmentId)

	resourceName := "oci_identity_tag.test_tag"
	datasourceName := "data.oci_identity_tags.test_tags"

	var resId, resId2 string

	resource.Test(t, resource.TestCase{
		Providers: map[string]terraform.ResourceProvider{
			"oci": provider,
		},
		Steps: []resource.TestStep{
			// verify create
			{
				ImportState:       true,
				ImportStateVerify: true,
				Config:            config + TagPropertyVariables + compartmentIdVariableStr + TagRequiredOnlyResource,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttr(resourceName, "description", "This tag will show the cost center that will be used for billing of associated resources."),
					resource.TestCheckResourceAttr(resourceName, "name", "CostCenter"),
					resource.TestCheckResourceAttrSet(resourceName, "tag_namespace_id"),

					func(s *terraform.State) (err error) {
						resId, err = fromInstanceState(s, resourceName, "id")
						return err
					},
				),
			},

			// delete before next create
			{
				Config: config + compartmentIdVariableStr + TagResourceDependencies,
			},
			// verify create with optionals
			{
				Config: config + TagPropertyVariables + compartmentIdVariableStr + TagResourceConfig,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttr(resourceName, "defined_tags.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "description", "This tag will show the cost center that will be used for billing of associated resources."),
					resource.TestCheckResourceAttr(resourceName, "freeform_tags.%", "1"),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttrSet(resourceName, "is_retired"),
					resource.TestCheckResourceAttr(resourceName, "name", "CostCenter"),
					resource.TestCheckResourceAttrSet(resourceName, "tag_namespace_id"),
					resource.TestCheckResourceAttrSet(resourceName, "time_created"),

					func(s *terraform.State) (err error) {
						resId, err = fromInstanceState(s, resourceName, "id")
						return err
					},
				),
			},

			// verify updates to updatable parameters
			{
				Config: config + `
variable "tag_defined_tags_value" { default = "updatedValue" }
variable "tag_description" { default = "description2" }
variable "tag_freeform_tags" { default = {"Department"= "Accounting"} }
variable "tag_name" { default = "CostCenter" }

                ` + compartmentIdVariableStr + TagResourceConfig,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttr(resourceName, "defined_tags.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "description", "description2"),
					resource.TestCheckResourceAttr(resourceName, "freeform_tags.%", "1"),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttrSet(resourceName, "is_retired"),
					resource.TestCheckResourceAttr(resourceName, "name", "CostCenter"),
					resource.TestCheckResourceAttrSet(resourceName, "tag_namespace_id"),
					resource.TestCheckResourceAttrSet(resourceName, "time_created"),

					func(s *terraform.State) (err error) {
						resId2, err = fromInstanceState(s, resourceName, "id")
						if resId != resId2 {
							return fmt.Errorf("Resource recreated when it was supposed to be updated.")
						}
						return err
					},
				),
			},
			// verify datasource
			{
				Config: config + `
variable "tag_defined_tags_value" { default = "updatedValue" }
variable "tag_description" { default = "description2" }
variable "tag_freeform_tags" { default = {"Department"= "Accounting"} }
variable "tag_name" { default = "CostCenter" }

data "oci_identity_tags" "test_tags" {
	#Required
	tag_namespace_id = "${oci_identity_tag_namespace.test_tag_namespace.id}"

    filter {
    	name = "id"
    	values = ["${oci_identity_tag.test_tag.id}"]
    }
}
                ` + compartmentIdVariableStr + TagResourceConfig,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrSet(datasourceName, "tag_namespace_id"),

					resource.TestCheckResourceAttr(datasourceName, "tags.#", "1"),
					resource.TestCheckResourceAttr(datasourceName, "tags.0.defined_tags.%", "1"),
					resource.TestCheckResourceAttr(datasourceName, "tags.0.description", "description2"),
					resource.TestCheckResourceAttr(datasourceName, "tags.0.freeform_tags.%", "1"),
					resource.TestCheckResourceAttrSet(datasourceName, "tags.0.id"),
					resource.TestCheckResourceAttrSet(datasourceName, "tags.0.is_retired"),
					resource.TestCheckResourceAttr(datasourceName, "tags.0.name", "CostCenter"),
					resource.TestCheckResourceAttrSet(datasourceName, "tags.0.time_created"),
				),
			},
		},
	})
}
<commit_msg>tag namespace test fix<commit_after>// Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.

package provider

import (
	"fmt"
	"testing"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

const (
	TagRequiredOnlyResource = TagResourceDependencies + `
resource "oci_identity_tag" "test_tag" {
	#Required
	description = "${var.tag_description}"
	name = "${var.tag_name}"
	tag_namespace_id = "${oci_identity_tag_namespace.test_tag_namespace.id}"
}
`

	TagResourceConfig = TagResourceDependencies + `
resource "oci_identity_tag" "test_tag" {
    depends_on = ["oci_identity_tag_namespace.tag-namespace1", "oci_identity_tag.tag1"]
	#Required
	description = "${var.tag_description}"
	name = "${var.tag_name}"
	tag_namespace_id = "${oci_identity_tag_namespace.test_tag_namespace.id}"

	#Optional
	defined_tags = "${map("${oci_identity_tag_namespace.tag-namespace1.name}.${oci_identity_tag.tag1.name}", "${var.tag_defined_tags_value}")}"
	freeform_tags = "${var.tag_freeform_tags}"
}
`
	TagPropertyVariables = `
variable "tag_defined_tags_value" { default = "value" }
variable "tag_description" { default = "This tag will show the cost center that will be used for billing of associated resources." }
variable "tag_freeform_tags" { default = {"Department"= "Finance"} }
variable "tag_name" { default = "CostCenter" }

`
	TagResourceDependencies = TagNamespacePropertyVariables + TagNamespaceRequiredOnlyResource

	DefinedTagsDependencies = `
variable defined_tag_namespace_name { default = "" }
resource "oci_identity_tag_namespace" "tag-namespace1" {
  		#Required
		compartment_id = "${var.tenancy_ocid}"
  		description = "example tag namespace"
  		name = "${var.defined_tag_namespace_name != "" ? var.defined_tag_namespace_name : "example-tag-namespace-all"}"

		is_retired = false
}

resource "oci_identity_tag" "tag1" {
  		#Required
  		description = "example tag"
  		name = "example-tag"
        tag_namespace_id = "${oci_identity_tag_namespace.tag-namespace1.id}"

		is_retired = false
}
`
)

func TestIdentityTagResource_basic(t *testing.T) {
	provider := testAccProvider
	config := testProviderConfig()

	compartmentId := getRequiredEnvSetting("compartment_ocid")
	compartmentIdVariableStr := fmt.Sprintf("variable \"compartment_id\" { default = \"%s\" }\n", compartmentId)

	resourceName := "oci_identity_tag.test_tag"
	datasourceName := "data.oci_identity_tags.test_tags"

	var resId, resId2 string

	resource.Test(t, resource.TestCase{
		Providers: map[string]terraform.ResourceProvider{
			"oci": provider,
		},
		Steps: []resource.TestStep{
			// verify create
			{
				ImportState:       true,
				ImportStateVerify: true,
				Config:            config + TagPropertyVariables + compartmentIdVariableStr + TagRequiredOnlyResource,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttr(resourceName, "description", "This tag will show the cost center that will be used for billing of associated resources."),
					resource.TestCheckResourceAttr(resourceName, "name", "CostCenter"),
					resource.TestCheckResourceAttrSet(resourceName, "tag_namespace_id"),

					func(s *terraform.State) (err error) {
						resId, err = fromInstanceState(s, resourceName, "id")
						return err
					},
				),
			},

			// delete before next create
			{
				Config: config + compartmentIdVariableStr + TagResourceDependencies,
			},
			// verify create with optionals
			{
				Config: config + TagPropertyVariables + compartmentIdVariableStr + TagResourceConfig,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttr(resourceName, "defined_tags.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "description", "This tag will show the cost center that will be used for billing of associated resources."),
					resource.TestCheckResourceAttr(resourceName, "freeform_tags.%", "1"),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttrSet(resourceName, "is_retired"),
					resource.TestCheckResourceAttr(resourceName, "name", "CostCenter"),
					resource.TestCheckResourceAttrSet(resourceName, "tag_namespace_id"),
					resource.TestCheckResourceAttrSet(resourceName, "time_created"),

					func(s *terraform.State) (err error) {
						resId, err = fromInstanceState(s, resourceName, "id")
						return err
					},
				),
			},

			// verify updates to updatable parameters
			{
				Config: config + `
variable "tag_defined_tags_value" { default = "updatedValue" }
variable "tag_description" { default = "description2" }
variable "tag_freeform_tags" { default = {"Department"= "Accounting"} }
variable "tag_name" { default = "CostCenter" }

                ` + compartmentIdVariableStr + TagResourceConfig,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttr(resourceName, "defined_tags.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "description", "description2"),
					resource.TestCheckResourceAttr(resourceName, "freeform_tags.%", "1"),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttrSet(resourceName, "is_retired"),
					resource.TestCheckResourceAttr(resourceName, "name", "CostCenter"),
					resource.TestCheckResourceAttrSet(resourceName, "tag_namespace_id"),
					resource.TestCheckResourceAttrSet(resourceName, "time_created"),

					func(s *terraform.State) (err error) {
						resId2, err = fromInstanceState(s, resourceName, "id")
						if resId != resId2 {
							return fmt.Errorf("Resource recreated when it was supposed to be updated.")
						}
						return err
					},
				),
			},
			// verify datasource
			{
				Config: config + `
variable "tag_defined_tags_value" { default = "updatedValue" }
variable "tag_description" { default = "description2" }
variable "tag_freeform_tags" { default = {"Department"= "Accounting"} }
variable "tag_name" { default = "CostCenter" }

data "oci_identity_tags" "test_tags" {
	#Required
	tag_namespace_id = "${oci_identity_tag_namespace.test_tag_namespace.id}"

    filter {
    	name = "id"
    	values = ["${oci_identity_tag.test_tag.id}"]
    }
}
                ` + compartmentIdVariableStr + TagResourceConfig,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrSet(datasourceName, "tag_namespace_id"),

					resource.TestCheckResourceAttr(datasourceName, "tags.#", "1"),
					resource.TestCheckResourceAttr(datasourceName, "tags.0.defined_tags.%", "1"),
					resource.TestCheckResourceAttr(datasourceName, "tags.0.description", "description2"),
					resource.TestCheckResourceAttr(datasourceName, "tags.0.freeform_tags.%", "1"),
					resource.TestCheckResourceAttrSet(datasourceName, "tags.0.id"),
					resource.TestCheckResourceAttrSet(datasourceName, "tags.0.is_retired"),
					resource.TestCheckResourceAttr(datasourceName, "tags.0.name", "CostCenter"),
					resource.TestCheckResourceAttrSet(datasourceName, "tags.0.time_created"),
				),
			},
		},
	})
}
<|endoftext|>


<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package null

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	gc "launchpad.net/gocheck"

	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/manual"
	"launchpad.net/juju-core/environs/sshstorage"
	"launchpad.net/juju-core/environs/tools"
	"launchpad.net/juju-core/instance"
	jc "launchpad.net/juju-core/testing/checkers"
	"launchpad.net/juju-core/testing/testbase"
)

type environSuite struct {
	testbase.LoggingSuite
	env *nullEnviron
}

var _ = gc.Suite(&environSuite{})

func (s *environSuite) SetUpTest(c *gc.C) {
	envConfig := getEnvironConfig(c, minimalConfigValues())
	s.env = &nullEnviron{cfg: envConfig}
}

func (s *environSuite) TestSetConfig(c *gc.C) {
	err := s.env.SetConfig(minimalConfig(c))
	c.Assert(err, gc.IsNil)

	testConfig := minimalConfig(c)
	testConfig, err = testConfig.Apply(map[string]interface{}{"bootstrap-host": ""})
	c.Assert(err, gc.IsNil)
	err = s.env.SetConfig(testConfig)
	c.Assert(err, gc.ErrorMatches, "bootstrap-host must be specified")
}

func (s *environSuite) TestInstances(c *gc.C) {
	var ids []instance.Id

	instances, err := s.env.Instances(ids)
	c.Assert(err, gc.Equals, environs.ErrNoInstances)
	c.Assert(instances, gc.HasLen, 0)

	ids = append(ids, manual.BootstrapInstanceId)
	instances, err = s.env.Instances(ids)
	c.Assert(err, gc.IsNil)
	c.Assert(instances, gc.HasLen, 1)
	c.Assert(instances[0], gc.NotNil)

	ids = append(ids, manual.BootstrapInstanceId)
	instances, err = s.env.Instances(ids)
	c.Assert(err, gc.IsNil)
	c.Assert(instances, gc.HasLen, 2)
	c.Assert(instances[0], gc.NotNil)
	c.Assert(instances[1], gc.NotNil)

	ids = append(ids, instance.Id("invalid"))
	instances, err = s.env.Instances(ids)
	c.Assert(err, gc.Equals, environs.ErrPartialInstances)
	c.Assert(instances, gc.HasLen, 3)
	c.Assert(instances[0], gc.NotNil)
	c.Assert(instances[1], gc.NotNil)
	c.Assert(instances[2], gc.IsNil)

	ids = []instance.Id{instance.Id("invalid")}
	instances, err = s.env.Instances(ids)
	c.Assert(err, gc.Equals, environs.ErrNoInstances)
	c.Assert(instances, gc.HasLen, 1)
	c.Assert(instances[0], gc.IsNil)
}

func (s *environSuite) TestDestroy(c *gc.C) {
	c.Assert(s.env.Destroy(), gc.ErrorMatches, "null provider destruction is not implemented yet")
}

func (s *environSuite) TestLocalStorageConfig(c *gc.C) {
	c.Assert(s.env.StorageDir(), gc.Equals, "/var/lib/juju/storage")
	c.Assert(s.env.cfg.storageListenAddr(), gc.Equals, ":8040")
	c.Assert(s.env.StorageAddr(), gc.Equals, s.env.cfg.storageListenAddr())
	c.Assert(s.env.SharedStorageAddr(), gc.Equals, "")
	c.Assert(s.env.SharedStorageDir(), gc.Equals, "")
}

func (s *environSuite) TestEnvironSupportsCustomSources(c *gc.C) {
	sources, err := tools.GetMetadataSources(s.env)
	c.Assert(err, gc.IsNil)
	c.Assert(len(sources), gc.Equals, 2)
	url, err := sources[0].URL("")
	c.Assert(err, gc.IsNil)
	c.Assert(strings.Contains(url, "/tools"), jc.IsTrue)
}

func (s *environSuite) TestEnvironBootstrapStorager(c *gc.C) {
	var sshScript = `
#!/bin/sh
# Wait for input to be written before exiting.
( echo $* | grep -q touch ) && head -n 1 > /dev/null
echo JUJU-RC: $RC
`[1:]
	bin := c.MkDir()
	ssh := filepath.Join(bin, "ssh")
	err := ioutil.WriteFile(ssh, []byte(sshScript), 0755)
	c.Assert(err, gc.IsNil)
	s.PatchEnvironment("PATH", bin+":"+os.Getenv("PATH"))

	s.PatchEnvironment("RC", "99") // simulate ssh failure
	err = s.env.EnableBootstrapStorage()
	c.Assert(err, gc.ErrorMatches, "exit code 99")
	c.Assert(s.env.Storage(), gc.Not(gc.FitsTypeOf), new(sshstorage.SSHStorage))

	s.PatchEnvironment("RC", "0")
	err = s.env.EnableBootstrapStorage()
	c.Assert(err, gc.IsNil)
	c.Assert(s.env.Storage(), gc.FitsTypeOf, new(sshstorage.SSHStorage))

	// Check idempotency
	err = s.env.EnableBootstrapStorage()
	c.Assert(err, gc.IsNil)
	c.Assert(s.env.Storage(), gc.FitsTypeOf, new(sshstorage.SSHStorage))
}
<commit_msg>Fix expected arg for stdin check<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package null

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	gc "launchpad.net/gocheck"

	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/manual"
	"launchpad.net/juju-core/environs/sshstorage"
	"launchpad.net/juju-core/environs/tools"
	"launchpad.net/juju-core/instance"
	jc "launchpad.net/juju-core/testing/checkers"
	"launchpad.net/juju-core/testing/testbase"
)

type environSuite struct {
	testbase.LoggingSuite
	env *nullEnviron
}

var _ = gc.Suite(&environSuite{})

func (s *environSuite) SetUpTest(c *gc.C) {
	envConfig := getEnvironConfig(c, minimalConfigValues())
	s.env = &nullEnviron{cfg: envConfig}
}

func (s *environSuite) TestSetConfig(c *gc.C) {
	err := s.env.SetConfig(minimalConfig(c))
	c.Assert(err, gc.IsNil)

	testConfig := minimalConfig(c)
	testConfig, err = testConfig.Apply(map[string]interface{}{"bootstrap-host": ""})
	c.Assert(err, gc.IsNil)
	err = s.env.SetConfig(testConfig)
	c.Assert(err, gc.ErrorMatches, "bootstrap-host must be specified")
}

func (s *environSuite) TestInstances(c *gc.C) {
	var ids []instance.Id

	instances, err := s.env.Instances(ids)
	c.Assert(err, gc.Equals, environs.ErrNoInstances)
	c.Assert(instances, gc.HasLen, 0)

	ids = append(ids, manual.BootstrapInstanceId)
	instances, err = s.env.Instances(ids)
	c.Assert(err, gc.IsNil)
	c.Assert(instances, gc.HasLen, 1)
	c.Assert(instances[0], gc.NotNil)

	ids = append(ids, manual.BootstrapInstanceId)
	instances, err = s.env.Instances(ids)
	c.Assert(err, gc.IsNil)
	c.Assert(instances, gc.HasLen, 2)
	c.Assert(instances[0], gc.NotNil)
	c.Assert(instances[1], gc.NotNil)

	ids = append(ids, instance.Id("invalid"))
	instances, err = s.env.Instances(ids)
	c.Assert(err, gc.Equals, environs.ErrPartialInstances)
	c.Assert(instances, gc.HasLen, 3)
	c.Assert(instances[0], gc.NotNil)
	c.Assert(instances[1], gc.NotNil)
	c.Assert(instances[2], gc.IsNil)

	ids = []instance.Id{instance.Id("invalid")}
	instances, err = s.env.Instances(ids)
	c.Assert(err, gc.Equals, environs.ErrNoInstances)
	c.Assert(instances, gc.HasLen, 1)
	c.Assert(instances[0], gc.IsNil)
}

func (s *environSuite) TestDestroy(c *gc.C) {
	c.Assert(s.env.Destroy(), gc.ErrorMatches, "null provider destruction is not implemented yet")
}

func (s *environSuite) TestLocalStorageConfig(c *gc.C) {
	c.Assert(s.env.StorageDir(), gc.Equals, "/var/lib/juju/storage")
	c.Assert(s.env.cfg.storageListenAddr(), gc.Equals, ":8040")
	c.Assert(s.env.StorageAddr(), gc.Equals, s.env.cfg.storageListenAddr())
	c.Assert(s.env.SharedStorageAddr(), gc.Equals, "")
	c.Assert(s.env.SharedStorageDir(), gc.Equals, "")
}

func (s *environSuite) TestEnvironSupportsCustomSources(c *gc.C) {
	sources, err := tools.GetMetadataSources(s.env)
	c.Assert(err, gc.IsNil)
	c.Assert(len(sources), gc.Equals, 2)
	url, err := sources[0].URL("")
	c.Assert(err, gc.IsNil)
	c.Assert(strings.Contains(url, "/tools"), jc.IsTrue)
}

func (s *environSuite) TestEnvironBootstrapStorager(c *gc.C) {
	var sshScript = `
#!/bin/bash
if [ "$*" = "hostname -- bash" ]; then
    # We're executing bash inside ssh. Wait
    # for input to be written before exiting.
    head -n 1 > /dev/null
fi
exec 0<&- # close stdin
echo JUJU-RC: $RC
`[1:]
	bin := c.MkDir()
	ssh := filepath.Join(bin, "ssh")
	err := ioutil.WriteFile(ssh, []byte(sshScript), 0755)
	c.Assert(err, gc.IsNil)
	s.PatchEnvironment("PATH", bin+":"+os.Getenv("PATH"))

	s.PatchEnvironment("RC", "99") // simulate ssh failure
	err = s.env.EnableBootstrapStorage()
	c.Assert(err, gc.ErrorMatches, "exit code 99")
	c.Assert(s.env.Storage(), gc.Not(gc.FitsTypeOf), new(sshstorage.SSHStorage))

	s.PatchEnvironment("RC", "0")
	err = s.env.EnableBootstrapStorage()
	c.Assert(err, gc.IsNil)
	c.Assert(s.env.Storage(), gc.FitsTypeOf, new(sshstorage.SSHStorage))

	// Check idempotency
	err = s.env.EnableBootstrapStorage()
	c.Assert(err, gc.IsNil)
	c.Assert(s.env.Storage(), gc.FitsTypeOf, new(sshstorage.SSHStorage))
}
<|endoftext|>


<commit_before>// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see <http://www.gnu.org/licenses/>.

package url

import (
	"errors"
	"fmt"
	"github.com/nmeum/marvin/irc"
	"github.com/nmeum/marvin/modules"
	"golang.org/x/net/html"
	"mime"
	"net/http"
	"regexp"
	"strings"
)

type Module struct {
	regex    *regexp.Regexp
	RegexStr string `json:"regex"`
}

func Init(moduleSet *modules.ModuleSet) {
	moduleSet.Register(new(Module))
}

func (m *Module) Name() string {
	return "url"
}

func (m *Module) Help() string {
	return "Displays information about posted URLs."
}

func (m *Module) Defaults() {
	m.RegexStr = `(?i)\b((http|https)\://(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s` + "`" + `!()\[\]{};:'".,<>?«»“”‘’]))`
}

func (m *Module) Load(client *irc.Client) error {
	regex, err := regexp.Compile(m.RegexStr)
	if err != nil {
		return err
	}

	m.regex = regex
	client.CmdHook("privmsg", m.urlCmd)

	return nil
}

func (m *Module) urlCmd(client *irc.Client, msg irc.Message) error {
	url := m.regex.FindString(msg.Data)
	if len(url) <= 0 {
		return nil
	}

	resp, err := http.Head(url)
	if err != nil {
		return err
	}
	resp.Body.Close() // HEAD response doesn't have a body

	info := m.infoString(resp)
	if len(info) <= 0 {
		return nil
	}

	return client.Write("NOTICE %s :%s", msg.Receiver, info)
}

func (m *Module) infoString(resp *http.Response) string {
	var mtype string
	var infos []string

	ctype := resp.Header.Get("Content-Type")
	if len(ctype) > 0 {
		m, _, err := mime.ParseMediaType(ctype)
		if err == nil {
			mtype = m
			infos = append(infos, fmt.Sprintf("Type: %s", mtype))
		}
	}

	csize := resp.ContentLength
	if csize >= 0 {
		infos = append(infos, fmt.Sprintf("Size: %s", m.humanize(csize)))
	}

	if mtype == "text/html" {
		title, err := m.extractTitle(resp.Request.URL.String())
		if err == nil {
			infos = append(infos, fmt.Sprintf("Title: %s", title))
		}
	}

	info := strings.Join(infos, " | ")
	if len(info) > 0 {
		info = fmt.Sprintf("%s -- %s", strings.ToUpper(m.Name()), info)
	}

	return info
}

func (m *Module) extractTitle(url string) (title string, err error) {
	resp, err := http.Get(url)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	doc, err := html.Parse(resp.Body)
	if err != nil {
		return
	}

	var parseFunc func(n *html.Node)
	parseFunc = func(n *html.Node) {
		if n.Type == html.ElementNode && n.Data == "title" {
			child := n.FirstChild
			if child != nil {
				title = child.Data
			} else {
				return
			}
		}

		for c := n.FirstChild; c != nil; c = c.NextSibling {
			parseFunc(c)
		}
	}

	parseFunc(doc)
	if len(title) <= 0 {
		err = errors.New("couldn't extract title")
		return
	}

	return
}

func (m *Module) humanize(count int64) string {
	switch {
	case count > (1 << 40):
		return fmt.Sprintf("%v TiB", count/(1<<40))
	case count > (1 << 30):
		return fmt.Sprintf("%v GiB", count/(1<<30))
	case count > (1 << 20):
		return fmt.Sprintf("%v MiB", count/(1<<20))
	case count > (1 << 10):
		return fmt.Sprintf("%v KiB", count/(1<<10))
	default:
		return fmt.Sprintf("%v B", count)
	}
}
<commit_msg>Implemented support for HTTP deflate decompression<commit_after>// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see <http://www.gnu.org/licenses/>.

package url

import (
	"errors"
	"fmt"
	"github.com/nmeum/marvin/irc"
	"github.com/nmeum/marvin/modules"
	"golang.org/x/net/html"
	"mime"
	"net/http"
	"regexp"
	"strings"
	"compress/zlib"
)

type Module struct {
	regex    *regexp.Regexp
	RegexStr string `json:"regex"`
}

func Init(moduleSet *modules.ModuleSet) {
	moduleSet.Register(new(Module))
}

func (m *Module) Name() string {
	return "url"
}

func (m *Module) Help() string {
	return "Displays information about posted URLs."
}

func (m *Module) Defaults() {
	m.RegexStr = `(?i)\b((http|https)\://(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s` + "`" + `!()\[\]{};:'".,<>?«»“”‘’]))`
}

func (m *Module) Load(client *irc.Client) error {
	regex, err := regexp.Compile(m.RegexStr)
	if err != nil {
		return err
	}

	m.regex = regex
	client.CmdHook("privmsg", m.urlCmd)

	return nil
}

func (m *Module) urlCmd(client *irc.Client, msg irc.Message) error {
	url := m.regex.FindString(msg.Data)
	if len(url) <= 0 {
		return nil
	}

	resp, err := http.Head(url)
	if err != nil {
		return err
	}
	resp.Body.Close() // HEAD response doesn't have a body

	info := m.infoString(resp)
	if len(info) <= 0 {
		return nil
	}

	return client.Write("NOTICE %s :%s", msg.Receiver, info)
}

func (m *Module) infoString(resp *http.Response) string {
	var mtype string
	var infos []string

	ctype := resp.Header.Get("Content-Type")
	if len(ctype) > 0 {
		m, _, err := mime.ParseMediaType(ctype)
		if err == nil {
			mtype = m
			infos = append(infos, fmt.Sprintf("Type: %s", mtype))
		}
	}

	csize := resp.ContentLength
	if csize >= 0 {
		infos = append(infos, fmt.Sprintf("Size: %s", m.humanize(csize)))
	}

	if mtype == "text/html" {
		title, err := m.extractTitle(resp.Request.URL.String())
		if err == nil {
			infos = append(infos, fmt.Sprintf("Title: %s", title))
		}
	}

	info := strings.Join(infos, " | ")
	if len(info) > 0 {
		info = fmt.Sprintf("%s -- %s", strings.ToUpper(m.Name()), info)
	}

	return info
}

func (m *Module) extractTitle(url string) (title string, err error) {
	resp, err := http.Get(url)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	var reader = resp.Body
	if resp.Header.Get("Content-Encoding") == "deflate" {
		readerZ, errZ := zlib.NewReader(resp.Body)
		defer readerZ.Close()
		if errZ == nil {
			reader = readerZ
		}
	}

	doc, err := html.Parse(reader)
	if err != nil {
		return
	}

	var parseFunc func(n *html.Node)
	parseFunc = func(n *html.Node) {
		if n.Type == html.ElementNode && n.Data == "title" {
			child := n.FirstChild
			if child != nil {
				title = child.Data
			} else {
				return
			}
		}

		for c := n.FirstChild; c != nil; c = c.NextSibling {
			parseFunc(c)
		}
	}

	parseFunc(doc)
	if len(title) <= 0 {
		err = errors.New("couldn't extract title")
		return
	}

	return
}

func (m *Module) humanize(count int64) string {
	switch {
	case count > (1 << 40):
		return fmt.Sprintf("%v TiB", count/(1<<40))
	case count > (1 << 30):
		return fmt.Sprintf("%v GiB", count/(1<<30))
	case count > (1 << 20):
		return fmt.Sprintf("%v MiB", count/(1<<20))
	case count > (1 << 10):
		return fmt.Sprintf("%v KiB", count/(1<<10))
	default:
		return fmt.Sprintf("%v B", count)
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package testutils

import (
	"flag"
	"fmt"
	"runtime"
	"strings"
	"testing"
	"time"
)

var timeoutMultiplier = flag.Float64("timeoutMultiplier", 1, "All calls to {Get,Set}Timeout have their timeout multiplied by this value")

// Timeout returns the timeout multiplied by any set multiplier.
func Timeout(timeout time.Duration) time.Duration {
	return time.Duration(*timeoutMultiplier * float64(timeout))
}

// getCallerName returns the test name that called this function.
// It traverses the stack to find the function name directly after a testing.* call.
func getCallerName() string {
	pc := make([]uintptr, 10)
	n := runtime.Callers(2, pc)
	for i := n; i > 0; i-- {
		fname := runtime.FuncForPC(pc[i-1]).Name()
		if strings.HasPrefix(fname, "testing.") {
			return runtime.FuncForPC(pc[i-2]).Name()
		}
	}
	return "unknown"
}

// SetTimeout is used to fail tests after a timeout. It returns a function that should be
// run once the test is complete. The standard way is to use defer, e.g.
// defer SetTimeout(t, time.Second)()
func SetTimeout(t *testing.T, timeout time.Duration) func() {
	timeout = Timeout(timeout)

	caller := getCallerName()
	c := make(chan struct{})

	go func() {
		select {
		case <-c:
			// Test is complete, don't need to do anything.
		case <-time.After(timeout):
			t.Logf("Test %s timed out after %v", caller, timeout)
			// Unfortunately, tests cannot be failed from new goroutines, so use a panic.
			panic(fmt.Errorf("Test %s timed out after %v", caller, timeout))
		}
	}()

	return func() {
		c <- struct{}{}
	}
}
<commit_msg>Simplify testutils.SetTimeout to not require goroutine<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package testutils

import (
	"flag"
	"fmt"
	"runtime"
	"strings"
	"testing"
	"time"
)

var timeoutMultiplier = flag.Float64("timeoutMultiplier", 1, "All calls to {Get,Set}Timeout have their timeout multiplied by this value")

// Timeout returns the timeout multiplied by any set multiplier.
func Timeout(timeout time.Duration) time.Duration {
	return time.Duration(*timeoutMultiplier * float64(timeout))
}

// getCallerName returns the test name that called this function.
// It traverses the stack to find the function name directly after a testing.* call.
func getCallerName() string {
	pc := make([]uintptr, 10)
	n := runtime.Callers(2, pc)
	for i := n; i > 0; i-- {
		fname := runtime.FuncForPC(pc[i-1]).Name()
		if strings.HasPrefix(fname, "testing.") {
			return runtime.FuncForPC(pc[i-2]).Name()
		}
	}
	return "unknown"
}

// SetTimeout is used to fail tests after a timeout. It returns a function that should be
// run once the test is complete. The standard way is to use defer, e.g.
// defer SetTimeout(t, time.Second)()
func SetTimeout(t *testing.T, timeout time.Duration) func() {
	timeout = Timeout(timeout)

	caller := getCallerName()

	timer := time.AfterFunc(timeout, func() {
		t.Logf("Test %s timed out after %v", caller, timeout)
		// Unfortunately, tests cannot be failed from new goroutines, so use a panic.
		panic(fmt.Errorf("Test %s timed out after %v", caller, timeout))
	})

	return func() {
		timer.Stop()
	}
}
<|endoftext|>