<commit_before>package apps

import (
	"bytes"
	"html/template"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path"
	"strings"

	"github.com/cozy/cozy-stack/model/app"
	"github.com/cozy/cozy-stack/model/instance"
	"github.com/cozy/cozy-stack/model/intent"
	"github.com/cozy/cozy-stack/model/permission"
	"github.com/cozy/cozy-stack/model/session"
	"github.com/cozy/cozy-stack/pkg/appfs"
	"github.com/cozy/cozy-stack/pkg/assets"
	"github.com/cozy/cozy-stack/pkg/config/config"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/utils"
	"github.com/cozy/cozy-stack/web/middlewares"
	"github.com/cozy/cozy-stack/web/statik"
	"github.com/labstack/echo/v4"
)

// Serve is an handler for serving files from the VFS for a client-side app
func Serve(c echo.Context) error {
	method := c.Request().Method
	if method != "GET" && method != "HEAD" {
		return echo.NewHTTPError(http.StatusMethodNotAllowed, "Method "+method+" not allowed")
	}

	i := middlewares.GetInstance(c)
	slug := c.Get("slug").(string)

	if !i.OnboardingFinished {
		return c.Redirect(http.StatusFound, i.PageURL("/", nil))
	}

	if config.GetConfig().Subdomains == config.FlatSubdomains {
		if code := c.QueryParam("code"); code != "" {
			return tryAuthWithSessionCode(c, i, code, slug)
		}
		if disconnect := c.QueryParam("disconnect"); disconnect == "true" || disconnect == "1" {
			return deleteAppCookie(c, i, slug)
		}
	}

	webapp, err := app.GetWebappBySlug(i, slug)
	if err != nil {
		// Used for the "collect" => "home" renaming
		if err == app.ErrNotFound && slug == "collect" {
			return c.Redirect(http.StatusMovedPermanently, i.DefaultRedirection().String())
		}
		// Used for the deprecated "onboarding" app
		if err == app.ErrNotFound && slug == "onboarding" {
			return c.Redirect(http.StatusMovedPermanently, i.DefaultRedirection().String())
		}
		return err
	}

	route, file := webapp.FindRoute(path.Clean(c.Request().URL.Path))

	if webapp.FromAppsDir {
		// Save permissions in couchdb before loading an index page
		if file == "" && webapp.Permissions() != nil {
			err := permission.ForceWebapp(i, webapp.Slug(), webapp.Permissions())
			if err != nil {
				return err
			}
		}

		fs := app.FSForAppDir(slug)
		f := appfs.NewAferoFileServer(fs, func(_, _, _, file string) string {
			return path.Join("/", file)
		})
		return ServeAppFile(c, i, f, webapp)
	}

	if file == "" || file == route.Index {
		webapp = app.DoLazyUpdate(i, webapp, app.Copier(consts.WebappType, i), i.Registries()).(*app.WebappManifest)
	}

	switch webapp.State() {
	case app.Installed:
		// This legacy "installed" state is not used anymore with the addition
		// of the registry. Change the webapp state to "ready" and serve the app
		// file.
		webapp.SetState(app.Ready)
		if err := webapp.Update(i, nil); err != nil {
			return err
		}
		fallthrough
	case app.Ready:
		return ServeAppFile(c, i, app.AppsFileServer(i), webapp)
	default:
		return echo.NewHTTPError(http.StatusServiceUnavailable, "Application is not ready")
	}
}

// handleIntent will allow iframes from another app if the current app is
// opened as an intent
func handleIntent(c echo.Context, i *instance.Instance, slug, intentID string) {
	intent := &intent.Intent{}
	if err := couchdb.GetDoc(i, consts.Intents, intentID, intent); err != nil {
		return
	}
	allowed := false
	for _, service := range intent.Services {
		if slug == service.Slug {
			allowed = true
		}
	}
	if !allowed {
		return
	}
	parts := strings.SplitN(intent.Client, "/", 2)
	if len(parts) < 2 || parts[0] != consts.Apps {
		return
	}
	from := i.SubDomain(parts[1]).String()
	middlewares.AppendCSPRule(c, "frame-ancestors", from)
}

// ServeAppFile will serve the requested file using the specified application
// manifest and appfs.FileServer context.
//
// It can be used to serve file application in another context than the VFS,
// for instance for tests or development purposes where we want to serve an
// application that is not installed on the user's instance. However this
// procedure should not be used for standard applications, use the Serve method
// for that.
func ServeAppFile(c echo.Context, i *instance.Instance, fs appfs.FileServer, webapp *app.WebappManifest) error {
	slug := webapp.Slug()
	route, file := webapp.FindRoute(path.Clean(c.Request().URL.Path))
	if route.NotFound() {
		return echo.NewHTTPError(http.StatusNotFound, "Page not found")
	}
	if file == "" {
		file = route.Index
	}

	session, isLoggedIn := middlewares.GetSession(c)
	filepath := path.Join("/", route.Folder, file)
	isRobotsTxt := filepath == "/robots.txt"

	if !route.Public && !isLoggedIn && !isRobotsTxt {
		if file != route.Index {
			return echo.NewHTTPError(http.StatusUnauthorized, "You must be authenticated")
		}
		reqURL := c.Request().URL
		subdomain := i.SubDomain(slug)
		subdomain.Path = reqURL.Path
		subdomain.RawQuery = reqURL.RawQuery
		subdomain.Fragment = reqURL.Fragment
		params := url.Values{
			"redirect": {subdomain.String()},
		}
		if jwt := c.QueryParam("jwt"); jwt != "" {
			params.Add("jwt", jwt)
		}
		return c.Redirect(http.StatusFound, i.PageURL("/auth/login", params))
	}

	version := webapp.Version()
	shasum := webapp.Checksum()

	if file != route.Index {
		// If file is not the index, it is considered an asset of the application
		// (JS, image, ...). For theses assets we check if it contains an unique
		// identifier to help caching. In such case, a long cache (1 year) is set.
		//
		// A unique identifier is matched when the file base contains a "long"
		// hexadecimal subpart between '.', of at least 10 characters: for instance
		// "app.badf00dbadf00d.js".
		if _, id := statik.ExtractAssetID(file); id != "" {
			c.Response().Header().Set("Cache-Control", "max-age=31536000, immutable")
		}

		err := fs.ServeFileContent(c.Response(), c.Request(), slug, version, shasum, filepath)
		if os.IsNotExist(err) {
			if isRobotsTxt {
				if f, ok := assets.Get("/robots.txt", i.ContextName); ok {
					_, err = io.Copy(c.Response(), f.Reader())
					return err
				}
			}
			return echo.NewHTTPError(http.StatusNotFound, "Asset not found")
		}
		if err != nil {
			return echo.NewHTTPError(http.StatusInternalServerError, err)
		}

		return nil
	}

	if intentID := c.QueryParam("intent"); intentID != "" {
		handleIntent(c, i, slug, intentID)
	}

	// For index file, we inject the locale, the stack domain, and a token if the
	// user is connected
	content, err := fs.Open(slug, version, shasum, filepath)
	if err != nil {
		return err
	}
	defer content.Close()

	buf, err := ioutil.ReadAll(content)
	if err != nil {
		return err
	}

	tmpl, err := template.New(file).Parse(string(buf))
	if err != nil {
		i.Logger().WithField("nspace", "apps").Warnf("%s cannot be parsed as a template: %s", file, err)
		return fs.ServeFileContent(c.Response(), c.Request(), slug, version, shasum, filepath)
	}

	var token string
	if isLoggedIn {
		token = i.BuildAppToken(webapp.Slug(), session.ID())
	} else {
		token = c.QueryParam("sharecode")
	}

	tracking := "false"
	settings, err := i.SettingsDocument()
	if err == nil {
		if t, ok := settings.M["tracking"].(string); ok {
			tracking = t
		}
	}

	res := c.Response()
	res.Header().Set("Content-Type", "text/html; charset=utf-8")
	res.Header().Set("Cache-Control", "private, no-store, must-revalidate")
	res.WriteHeader(http.StatusOK)
	subdomainsType := config.GetConfig().Subdomains
	return tmpl.Execute(res, echo.Map{
		"Token":         token,
		"Domain":        i.ContextualDomain(),
		"ContextName":   i.ContextName,
		"Locale":        i.Locale,
		"AppSlug":       webapp.Slug(),
		"AppName":       webapp.NameLocalized(i.Locale),
		"AppEditor":     webapp.Editor,
		"AppNamePrefix": webapp.NamePrefix,
		"IconPath":      webapp.Icon,
		"CozyBar":       cozybar(i, isLoggedIn),
		"CozyClientJS":  cozyclientjs(i),
		"ThemeCSS":      middlewares.ThemeCSS(i),
		"Tracking":      tracking,
		"Favicon":       middlewares.Favicon(i),
		"SubDomain":     subdomainsType,
	})
}

func tryAuthWithSessionCode(c echo.Context, i *instance.Instance, value, slug string) error {
	u := *(c.Request().URL)
	u.Scheme = i.Scheme()
	u.Host = c.Request().Host
	if code := session.FindCode(value, u.Host); code != nil {
		sess, err := session.Get(i, code.SessionID)
		if err == nil {
			cookie, err := sess.ToAppCookie(u.Host, slug)
			if err == nil {
				c.SetCookie(cookie)
			}
		}
	}
	q := u.Query()
	q.Del("code")
	u.RawQuery = q.Encode()
	return c.Redirect(http.StatusFound, u.String())
}

func deleteAppCookie(c echo.Context, i *instance.Instance, slug string) error {
	c.SetCookie(&http.Cookie{
		Name:   session.SessionCookieName,
		Value:  "",
		MaxAge: -1,
		Path:   "/",
		Domain: utils.StripPort(i.ContextualDomain()),
	})

	redirect := *(c.Request().URL)
	redirect.Scheme = i.Scheme()
	redirect.Host = c.Request().Host

	queries := make(url.Values)
	for k, v := range redirect.Query() {
		if k != "disconnect" {
			queries[k] = v
		}
	}
	redirect.RawQuery = queries.Encode()

	u := i.PageURL("/auth/login", url.Values{
		"redirect": {redirect.String()},
	})
	return c.Redirect(http.StatusFound, u)
}

var clientTemplate *template.Template
var barTemplate *template.Template

// BuildTemplates ensure that cozy-client-js and the bar can be injected in templates
func BuildTemplates() {
	clientTemplate = template.Must(template.New("cozy-client-js").Funcs(middlewares.FuncsMap).Parse(`` +
		`<script src="{{asset .Domain "/js/cozy-client.min.js" .ContextName}}"></script>`,
	))

	barTemplate = template.Must(template.New("cozy-bar").Funcs(middlewares.FuncsMap).Parse(`
<link rel="stylesheet" type="text/css" href="{{asset .Domain "/fonts/fonts.css" .ContextName}}">
<link rel="stylesheet" type="text/css" href="{{asset .Domain "/css/cozy-bar.min.css" .ContextName}}">
{{if .LoggedIn}}
{{range .Warnings}}
<meta name="user-action-required" data-title="{{ .Title }}" data-code="{{ .Code }}" data-detail="{{ .Detail }}" data-links="{{ .Links.Self }}" />
{{end}}
{{end}}
<script src="{{asset .Domain "/js/cozy-bar.min.js" .ContextName}}"></script>`,
	))
}

func cozyclientjs(i *instance.Instance) template.HTML {
	buf := new(bytes.Buffer)
	err := clientTemplate.Execute(buf, echo.Map{
		"Domain":      i.ContextualDomain(),
		"ContextName": i.ContextName,
	})
	if err != nil {
		panic(err)
	}
	return template.HTML(buf.String())
}

func cozybar(i *instance.Instance, loggedIn bool) template.HTML {
	buf := new(bytes.Buffer)
	err := barTemplate.Execute(buf, echo.Map{
		"Domain":      i.ContextualDomain(),
		"Warnings":    i.Warnings(),
		"ContextName": i.ContextName,
		"LoggedIn":    loggedIn,
	})
	if err != nil {
		panic(err)
	}
	return template.HTML(buf.String())
}
<commit_msg>Be lazy when rendering the index.html of apps (#2180)<commit_after>package apps

import (
	"bytes"
	"html/template"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path"
	"strings"

	"github.com/cozy/cozy-stack/model/app"
	"github.com/cozy/cozy-stack/model/instance"
	"github.com/cozy/cozy-stack/model/intent"
	"github.com/cozy/cozy-stack/model/permission"
	"github.com/cozy/cozy-stack/model/session"
	"github.com/cozy/cozy-stack/pkg/appfs"
	"github.com/cozy/cozy-stack/pkg/assets"
	"github.com/cozy/cozy-stack/pkg/config/config"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/utils"
	"github.com/cozy/cozy-stack/web/middlewares"
	"github.com/cozy/cozy-stack/web/statik"
	"github.com/labstack/echo/v4"
)

// Serve is an handler for serving files from the VFS for a client-side app
func Serve(c echo.Context) error {
	method := c.Request().Method
	if method != "GET" && method != "HEAD" {
		return echo.NewHTTPError(http.StatusMethodNotAllowed, "Method "+method+" not allowed")
	}

	i := middlewares.GetInstance(c)
	slug := c.Get("slug").(string)

	if !i.OnboardingFinished {
		return c.Redirect(http.StatusFound, i.PageURL("/", nil))
	}

	if config.GetConfig().Subdomains == config.FlatSubdomains {
		if code := c.QueryParam("code"); code != "" {
			return tryAuthWithSessionCode(c, i, code, slug)
		}
		if disconnect := c.QueryParam("disconnect"); disconnect == "true" || disconnect == "1" {
			return deleteAppCookie(c, i, slug)
		}
	}

	webapp, err := app.GetWebappBySlug(i, slug)
	if err != nil {
		// Used for the "collect" => "home" renaming
		if err == app.ErrNotFound && slug == "collect" {
			return c.Redirect(http.StatusMovedPermanently, i.DefaultRedirection().String())
		}
		// Used for the deprecated "onboarding" app
		if err == app.ErrNotFound && slug == "onboarding" {
			return c.Redirect(http.StatusMovedPermanently, i.DefaultRedirection().String())
		}
		return err
	}

	route, file := webapp.FindRoute(path.Clean(c.Request().URL.Path))

	if webapp.FromAppsDir {
		// Save permissions in couchdb before loading an index page
		if file == "" && webapp.Permissions() != nil {
			err := permission.ForceWebapp(i, webapp.Slug(), webapp.Permissions())
			if err != nil {
				return err
			}
		}

		fs := app.FSForAppDir(slug)
		f := appfs.NewAferoFileServer(fs, func(_, _, _, file string) string {
			return path.Join("/", file)
		})
		return ServeAppFile(c, i, f, webapp)
	}

	if file == "" || file == route.Index {
		webapp = app.DoLazyUpdate(i, webapp, app.Copier(consts.WebappType, i), i.Registries()).(*app.WebappManifest)
	}

	switch webapp.State() {
	case app.Installed:
		// This legacy "installed" state is not used anymore with the addition
		// of the registry. Change the webapp state to "ready" and serve the app
		// file.
		webapp.SetState(app.Ready)
		if err := webapp.Update(i, nil); err != nil {
			return err
		}
		fallthrough
	case app.Ready:
		return ServeAppFile(c, i, app.AppsFileServer(i), webapp)
	default:
		return echo.NewHTTPError(http.StatusServiceUnavailable, "Application is not ready")
	}
}

// handleIntent will allow iframes from another app if the current app is
// opened as an intent
func handleIntent(c echo.Context, i *instance.Instance, slug, intentID string) {
	intent := &intent.Intent{}
	if err := couchdb.GetDoc(i, consts.Intents, intentID, intent); err != nil {
		return
	}
	allowed := false
	for _, service := range intent.Services {
		if slug == service.Slug {
			allowed = true
		}
	}
	if !allowed {
		return
	}
	parts := strings.SplitN(intent.Client, "/", 2)
	if len(parts) < 2 || parts[0] != consts.Apps {
		return
	}
	from := i.SubDomain(parts[1]).String()
	middlewares.AppendCSPRule(c, "frame-ancestors", from)
}

// ServeAppFile will serve the requested file using the specified application
// manifest and appfs.FileServer context.
//
// It can be used to serve file application in another context than the VFS,
// for instance for tests or development purposes where we want to serve an
// application that is not installed on the user's instance. However this
// procedure should not be used for standard applications, use the Serve method
// for that.
func ServeAppFile(c echo.Context, i *instance.Instance, fs appfs.FileServer, webapp *app.WebappManifest) error {
	slug := webapp.Slug()
	route, file := webapp.FindRoute(path.Clean(c.Request().URL.Path))
	if route.NotFound() {
		return echo.NewHTTPError(http.StatusNotFound, "Page not found")
	}
	if file == "" {
		file = route.Index
	}

	session, isLoggedIn := middlewares.GetSession(c)
	filepath := path.Join("/", route.Folder, file)
	isRobotsTxt := filepath == "/robots.txt"

	if !route.Public && !isLoggedIn && !isRobotsTxt {
		if file != route.Index {
			return echo.NewHTTPError(http.StatusUnauthorized, "You must be authenticated")
		}
		reqURL := c.Request().URL
		subdomain := i.SubDomain(slug)
		subdomain.Path = reqURL.Path
		subdomain.RawQuery = reqURL.RawQuery
		subdomain.Fragment = reqURL.Fragment
		params := url.Values{
			"redirect": {subdomain.String()},
		}
		if jwt := c.QueryParam("jwt"); jwt != "" {
			params.Add("jwt", jwt)
		}
		return c.Redirect(http.StatusFound, i.PageURL("/auth/login", params))
	}

	version := webapp.Version()
	shasum := webapp.Checksum()

	if file != route.Index {
		// If file is not the index, it is considered an asset of the application
		// (JS, image, ...). For theses assets we check if it contains an unique
		// identifier to help caching. In such case, a long cache (1 year) is set.
		//
		// A unique identifier is matched when the file base contains a "long"
		// hexadecimal subpart between '.', of at least 10 characters: for instance
		// "app.badf00dbadf00d.js".
		if _, id := statik.ExtractAssetID(file); id != "" {
			c.Response().Header().Set("Cache-Control", "max-age=31536000, immutable")
		}

		err := fs.ServeFileContent(c.Response(), c.Request(), slug, version, shasum, filepath)
		if os.IsNotExist(err) {
			if isRobotsTxt {
				if f, ok := assets.Get("/robots.txt", i.ContextName); ok {
					_, err = io.Copy(c.Response(), f.Reader())
					return err
				}
			}
			return echo.NewHTTPError(http.StatusNotFound, "Asset not found")
		}
		if err != nil {
			return echo.NewHTTPError(http.StatusInternalServerError, err)
		}

		return nil
	}

	if intentID := c.QueryParam("intent"); intentID != "" {
		handleIntent(c, i, slug, intentID)
	}

	// For index file, we inject the locale, the stack domain, and a token if the
	// user is connected
	content, err := fs.Open(slug, version, shasum, filepath)
	if err != nil {
		return err
	}
	defer content.Close()

	buf, err := ioutil.ReadAll(content)
	if err != nil {
		return err
	}

	tmpl, err := template.New(file).Parse(string(buf))
	if err != nil {
		i.Logger().WithField("nspace", "apps").Warnf("%s cannot be parsed as a template: %s", file, err)
		return fs.ServeFileContent(c.Response(), c.Request(), slug, version, shasum, filepath)
	}

	var token string
	if isLoggedIn {
		token = i.BuildAppToken(webapp.Slug(), session.ID())
	} else {
		token = c.QueryParam("sharecode")
	}

	tracking := "false"
	settings, err := i.SettingsDocument()
	if err == nil {
		if t, ok := settings.M["tracking"].(string); ok {
			tracking = t
		}
	}
	var subdomainsType string
	switch config.GetConfig().Subdomains {
	case config.FlatSubdomains:
		subdomainsType = "flat"
	case config.NestedSubdomains:
		subdomainsType = "nested"
	}

	res := c.Response()
	res.Header().Set("Content-Type", "text/html; charset=utf-8")
	res.Header().Set("Cache-Control", "private, no-store, must-revalidate")
	res.WriteHeader(http.StatusOK)
	return tmpl.Execute(res, serveParams{
		Token:      token,
		SubDomain:  subdomainsType,
		Tracking:   tracking,
		webapp:     webapp,
		instance:   i,
		isLoggedIn: isLoggedIn,
	})
}

// serveParams is a struct used for rendering the index.html of webapps. A
// struct is used, and not a map, to have some methods declared on it. It
// allows to be lazy when constructing the paths of the assets: if an asset is
// not used in the template, the method won't be called and the stack can avoid
// checking if this asset is dynamically overridden in this instance context.
type serveParams struct {
	Token      string
	SubDomain  string
	Tracking   string
	webapp     *app.WebappManifest
	instance   *instance.Instance
	isLoggedIn bool
}

func (s serveParams) Domain() string {
	return s.instance.ContextualDomain()
}

func (s serveParams) ContextName() string {
	return s.instance.ContextName
}

func (s serveParams) Locale() string {
	return s.instance.Locale
}

func (s serveParams) AppSlug() string {
	return s.webapp.Slug()
}

func (s serveParams) AppName() string {
	return s.webapp.NameLocalized(s.instance.Locale)
}

func (s serveParams) AppEditor() string {
	return s.webapp.Editor
}

func (s serveParams) AppNamePrefix() string {
	return s.webapp.NamePrefix
}

func (s serveParams) IconPath() string {
	return s.webapp.Icon
}

func (s serveParams) CozyBar() template.HTML {
	return cozybar(s.instance, s.isLoggedIn)
}

func (s serveParams) CozyClientJS() template.HTML {
	return cozyclientjs(s.instance)
}

func (s serveParams) ThemeCSS() template.HTML {
	return middlewares.ThemeCSS(s.instance)
}

func (s serveParams) Favicon() template.HTML {
	return middlewares.Favicon(s.instance)
}

func tryAuthWithSessionCode(c echo.Context, i *instance.Instance, value, slug string) error {
	u := *(c.Request().URL)
	u.Scheme = i.Scheme()
	u.Host = c.Request().Host
	if code := session.FindCode(value, u.Host); code != nil {
		sess, err := session.Get(i, code.SessionID)
		if err == nil {
			cookie, err := sess.ToAppCookie(u.Host, slug)
			if err == nil {
				c.SetCookie(cookie)
			}
		}
	}
	q := u.Query()
	q.Del("code")
	u.RawQuery = q.Encode()
	return c.Redirect(http.StatusFound, u.String())
}

func deleteAppCookie(c echo.Context, i *instance.Instance, slug string) error {
	c.SetCookie(&http.Cookie{
		Name:   session.SessionCookieName,
		Value:  "",
		MaxAge: -1,
		Path:   "/",
		Domain: utils.StripPort(i.ContextualDomain()),
	})

	redirect := *(c.Request().URL)
	redirect.Scheme = i.Scheme()
	redirect.Host = c.Request().Host

	queries := make(url.Values)
	for k, v := range redirect.Query() {
		if k != "disconnect" {
			queries[k] = v
		}
	}
	redirect.RawQuery = queries.Encode()

	u := i.PageURL("/auth/login", url.Values{
		"redirect": {redirect.String()},
	})
	return c.Redirect(http.StatusFound, u)
}

var clientTemplate *template.Template
var barTemplate *template.Template

// BuildTemplates ensure that cozy-client-js and the bar can be injected in templates
func BuildTemplates() {
	clientTemplate = template.Must(template.New("cozy-client-js").Funcs(middlewares.FuncsMap).Parse(`` +
		`<script src="{{asset .Domain "/js/cozy-client.min.js" .ContextName}}"></script>`,
	))

	barTemplate = template.Must(template.New("cozy-bar").Funcs(middlewares.FuncsMap).Parse(`
<link rel="stylesheet" type="text/css" href="{{asset .Domain "/fonts/fonts.css" .ContextName}}">
<link rel="stylesheet" type="text/css" href="{{asset .Domain "/css/cozy-bar.min.css" .ContextName}}">
{{if .LoggedIn}}
{{range .Warnings}}
<meta name="user-action-required" data-title="{{ .Title }}" data-code="{{ .Code }}" data-detail="{{ .Detail }}" data-links="{{ .Links.Self }}" />
{{end}}
{{end}}
<script src="{{asset .Domain "/js/cozy-bar.min.js" .ContextName}}"></script>`,
	))
}

func cozyclientjs(i *instance.Instance) template.HTML {
	buf := new(bytes.Buffer)
	err := clientTemplate.Execute(buf, echo.Map{
		"Domain":      i.ContextualDomain(),
		"ContextName": i.ContextName,
	})
	if err != nil {
		panic(err)
	}
	return template.HTML(buf.String())
}

func cozybar(i *instance.Instance, loggedIn bool) template.HTML {
	buf := new(bytes.Buffer)
	err := barTemplate.Execute(buf, echo.Map{
		"Domain":      i.ContextualDomain(),
		"Warnings":    i.Warnings(),
		"ContextName": i.ContextName,
		"LoggedIn":    loggedIn,
	})
	if err != nil {
		panic(err)
	}
	return template.HTML(buf.String())
}
<|endoftext|>


<commit_before>// Copyright 2014 The Serviced Authors.
// 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 web

//#include <stdlib.h>
//#include <string.h>
//extern int isGroupMember(const char *username, const char *group);
import "C"
import (
	"github.com/msteinert/pam"
	"github.com/zenoss/glog"

	"errors"
	"fmt"
	"os/user"
	"unsafe"
)

// currently logged in user
var currentUser *user.User

func init() {
	var err error
	currentUser, err = user.Current()
	if err != nil {
		panic(fmt.Errorf("could not get current user: %s", err))
	}
}

func isGroupMember(username, group string) bool {
	var cuser = C.CString(username)
	defer C.free(unsafe.Pointer(cuser))
	var cgroup = C.CString(group)
	defer C.free(unsafe.Pointer(cgroup))
	result := C.isGroupMember(cuser, cgroup)
	glog.V(2).Infof("C.isGroupMember(%s, %s) returned %d.\n", username, group, result)
	return (result != 0)
}

func pamValidateLogin(creds *login, group string) bool {
	return pamValidateLoginOnly(creds, group) && isGroupMember(creds.Username, group)
}

func makePamConvHandler(creds *login) func(pam.Style, string) (string, error) {
	return func(s pam.Style, msg string) (string, error) {
		switch s {
		case pam.PromptEchoOff:
			return creds.Password, nil
		case pam.PromptEchoOn:
			glog.V(1).Infof("PAM Prompt: %s\n", msg)
			return creds.Username, nil
		case pam.ErrorMsg:
			glog.Errorf("PAM ERROR: %s\n", msg)
			return "", nil
		case pam.TextInfo:
			glog.V(1).Infof("PAM MESSAGE: %s\n", msg)
			return "", nil
		}
		return "", errors.New("Unrecognized message style")
	}
}

func pamValidateLoginOnly(creds *login, group string) bool {
	t, err := pam.StartFunc("sudo", "", makePamConvHandler(creds))
	if err != nil {
		glog.Errorf("Start: %s", err.Error())
		return false
	}
	err = t.Authenticate(0)
	if err != nil {
		glog.Errorf("Authentication failed for user %s: Authenticate error: %s", creds.Username, err.Error())
		return false
	}
	err = t.AcctMgmt(pam.Silent)
	if err != nil {
		glog.Errorf("Authentication failed for user %s: AcctMgmt error: %s", creds.Username, err.Error())
		return false
	}

	return true
}
<commit_msg>Pass in the username string for sudo PAM plugin (#3403)<commit_after>// Copyright 2014 The Serviced Authors.
// 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 web

//#include <stdlib.h>
//#include <string.h>
//extern int isGroupMember(const char *username, const char *group);
import "C"
import (
	"github.com/msteinert/pam"
	"github.com/zenoss/glog"

	"errors"
	"fmt"
	"os/user"
	"unsafe"
)

// currently logged in user
var currentUser *user.User

func init() {
	var err error
	currentUser, err = user.Current()
	if err != nil {
		panic(fmt.Errorf("could not get current user: %s", err))
	}
}

func isGroupMember(username, group string) bool {
	var cuser = C.CString(username)
	defer C.free(unsafe.Pointer(cuser))
	var cgroup = C.CString(group)
	defer C.free(unsafe.Pointer(cgroup))
	result := C.isGroupMember(cuser, cgroup)
	glog.V(2).Infof("C.isGroupMember(%s, %s) returned %d.\n", username, group, result)
	return (result != 0)
}

func pamValidateLogin(creds *login, group string) bool {
	return pamValidateLoginOnly(creds, group) && isGroupMember(creds.Username, group)
}

func makePamConvHandler(creds *login) func(pam.Style, string) (string, error) {
	return func(s pam.Style, msg string) (string, error) {
		switch s {
		case pam.PromptEchoOff:
			return creds.Password, nil
		case pam.PromptEchoOn:
			glog.V(1).Infof("PAM Prompt: %s\n", msg)
			return creds.Username, nil
		case pam.ErrorMsg:
			glog.Errorf("PAM ERROR: %s\n", msg)
			return "", nil
		case pam.TextInfo:
			glog.V(1).Infof("PAM MESSAGE: %s\n", msg)
			return "", nil
		}
		return "", errors.New("Unrecognized message style")
	}
}

func pamValidateLoginOnly(creds *login, group string) bool {
	if len(creds.Username) == 0 {
		glog.Errorf("Authentication failed: User not given")
		return false
	}

	t, err := pam.StartFunc("sudo", creds.Username, makePamConvHandler(creds))
	if err != nil {
		glog.Errorf("Start: %s", err.Error())
		return false
	}
	err = t.Authenticate(0)
	if err != nil {
		glog.Errorf("Authentication failed for user %s: Authenticate error: %s", creds.Username, err.Error())
		return false
	}
	err = t.AcctMgmt(pam.Silent)
	if err != nil {
		glog.Errorf("Authentication failed for user %s: AcctMgmt error: %s", creds.Username, err.Error())
		return false
	}

	return true
}
<|endoftext|>


<commit_before>package controllers

import (
	"net/http"
	"net/http/httptest"
	//"net/url"
	"path/filepath"
	"runtime"
	"testing"

	"fmt"
	"strings"

	"github.com/astaxie/beego"
	//"github.com/dghubble/sling"
	"github.com/stretchr/testify/assert"
)

//const (
//	adminName = "admin"
//	adminPwd  = "Harbor12345"
//)

//type usrInfo struct {
//	Name   string
//	Passwd string
//}

//var admin *usrInfo

func init() {

	_, file, _, _ := runtime.Caller(1)
	apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, ".."+string(filepath.Separator))))
	beego.BConfig.WebConfig.Session.SessionOn = true
	beego.TestBeegoInit(apppath)
	beego.AddTemplateExt("htm")

	beego.Router("/", &IndexController{})
	beego.Router("/dashboard", &DashboardController{})
	beego.Router("/project", &ProjectController{})
	beego.Router("/repository", &RepositoryController{})
	beego.Router("/sign_up", &SignUpController{})
	beego.Router("/add_new", &AddNewController{})
	beego.Router("/account_setting", &AccountSettingController{})
	beego.Router("/change_password", &ChangePasswordController{})
	beego.Router("/admin_option", &AdminOptionController{})
	beego.Router("/forgot_password", &ForgotPasswordController{})
	beego.Router("/reset_password", &ResetPasswordController{})
	beego.Router("/search", &SearchController{})

	beego.Router("/login", &CommonController{}, "post:Login")
	beego.Router("/log_out", &CommonController{}, "get:LogOut")
	beego.Router("/reset", &CommonController{}, "post:ResetPassword")
	beego.Router("/userExists", &CommonController{}, "post:UserExists")
	beego.Router("/sendEmail", &CommonController{}, "get:SendEmail")
	beego.Router("/language", &CommonController{}, "get:SwitchLanguage")

	beego.Router("/optional_menu", &OptionalMenuController{})
	beego.Router("/navigation_header", &NavigationHeaderController{})
	beego.Router("/navigation_detail", &NavigationDetailController{})
	beego.Router("/sign_in", &SignInController{})

	//Init user Info
	//admin = &usrInfo{adminName, adminPwd}

}

// TestMain is a sample to run an endpoint test
func TestMain(t *testing.T) {
	assert := assert.New(t)

	//	v := url.Values{}
	//	v.Set("principal", "admin")
	//	v.Add("password", "Harbor12345")

	r, _ := http.NewRequest("GET", "/", nil)
	w := httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(200), w.Code, "'/' httpStatusCode should be 200")
	assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_index</title>"), "http respond should have '<title>page_title_index</title>'")

	r, _ = http.NewRequest("GET", "/dashboard", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(200), w.Code, "'/dashboard' httpStatusCode should be 200")
	assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_dashboard</title>"), "http respond should have '<title>page_title_dashboard</title>'")

	r, _ = http.NewRequest("GET", "/project", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(200), w.Code, "'/project' httpStatusCode should be 200")
	assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_project</title>"), "http respond should have '<title>page_title_project</title>'")

	r, _ = http.NewRequest("GET", "/repository", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(200), w.Code, "'/repository' httpStatusCode should be 200")
	assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_repository</title>"), "http respond should have '<title>page_title_repository</title>'")

	r, _ = http.NewRequest("GET", "/sign_up", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(200), w.Code, "'/sign_up' httpStatusCode should be 200")
	assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_sign_up</title>"), "http respond should have '<title>page_title_sign_up</title>'")

	r, _ = http.NewRequest("GET", "/add_new", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(401), w.Code, "'/add_new' httpStatusCode should be 401")

	r, _ = http.NewRequest("GET", "/account_setting", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(302), w.Code, "'/account_setting' httpStatusCode should be 302")

	r, _ = http.NewRequest("GET", "/change_password", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(302), w.Code, "'/change_password' httpStatusCode should be 302")

	r, _ = http.NewRequest("GET", "/admin_option", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(302), w.Code, "'/admin_option' httpStatusCode should be 302")

	r, _ = http.NewRequest("GET", "/forgot_password", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(200), w.Code, "'/forgot_password' httpStatusCode should be 200")
	assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_forgot_password</title>"), "http respond should have '<title>page_title_forgot_password</title>'")

	r, _ = http.NewRequest("GET", "/reset_password", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(302), w.Code, "'/reset_password' httpStatusCode should be 302")

	r, _ = http.NewRequest("GET", "/search", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(200), w.Code, "'/search' httpStatusCode should be 200")
	assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_search</title>"), "http respond should have '<title>page_title_searc</title>'")

	r, _ = http.NewRequest("POST", "/login", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(401), w.Code, "'/login' httpStatusCode should be 401")

	r, _ = http.NewRequest("GET", "/log_out", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(200), w.Code, "'/log_out' httpStatusCode should be 200")
	assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), ""), "http respond should be empty")

	r, _ = http.NewRequest("POST", "/reset", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(400), w.Code, "'/reset' httpStatusCode should be 400")

	r, _ = http.NewRequest("POST", "/userExists", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(500), w.Code, "'/userExists' httpStatusCode should be 500")

	r, _ = http.NewRequest("GET", "/sendEmail", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(400), w.Code, "'/sendEmail' httpStatusCode should be 400")

	r, _ = http.NewRequest("GET", "/language", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(302), w.Code, "'/language' httpStatusCode should be 302")

	r, _ = http.NewRequest("GET", "/optional_menu", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	//fmt.Printf("/optional_menu: %s\n", w.Body)
	assert.Equal(int(200), w.Code, "'/optional_menu' httpStatusCode should be 200")
	//assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title> </title>"), "http respond should have '<title> </title>'")

	r, _ = http.NewRequest("GET", "/navigation_header", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	//fmt.Printf("/navigation_header: %s\n", w.Body)
	assert.Equal(int(200), w.Code, "'/navigation_header' httpStatusCode should be 200")
	//assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title> </title>"), "http respond should have '<title> </title>'")

	r, _ = http.NewRequest("GET", "/navigation_detail", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	//fmt.Printf("/navigation_detail: %s\n", w.Body)
	assert.Equal(int(200), w.Code, "'/navigation_detail' httpStatusCode should be 200")
	//assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title> </title>"), "http respond should have '<title> </title>'")

	r, _ = http.NewRequest("GET", "/sign_in", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	//fmt.Printf("/sign_in: %s\n", w.Body)
	assert.Equal(int(200), w.Code, "'/sign_in' httpStatusCode should be 200")
	//assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title> </title>"), "http respond should have '<title> </title>'")

}
<commit_msg>Updates for controller tests.<commit_after>package controllers

import (
	"net/http"
	"net/http/httptest"
	//"net/url"
	"path/filepath"
	"runtime"
	"testing"

	"fmt"
	"strings"

	"github.com/astaxie/beego"
	//"github.com/dghubble/sling"
	"github.com/stretchr/testify/assert"
)

//const (
//	adminName = "admin"
//	adminPwd  = "Harbor12345"
//)

//type usrInfo struct {
//	Name   string
//	Passwd string
//}

//var admin *usrInfo

func init() {

	_, file, _, _ := runtime.Caller(1)
	apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, ".."+string(filepath.Separator))))
	beego.BConfig.WebConfig.Session.SessionOn = true
	beego.TestBeegoInit(apppath)
	beego.AddTemplateExt("htm")

	beego.Router("/", &IndexController{})
	beego.Router("/dashboard", &DashboardController{})
	beego.Router("/project", &ProjectController{})
	beego.Router("/repository", &RepositoryController{})
	beego.Router("/sign_up", &SignUpController{})
	beego.Router("/add_new", &AddNewController{})
	beego.Router("/account_setting", &AccountSettingController{})
	beego.Router("/change_password", &ChangePasswordController{})
	beego.Router("/admin_option", &AdminOptionController{})
	beego.Router("/forgot_password", &ForgotPasswordController{})
	beego.Router("/reset_password", &ResetPasswordController{})
	beego.Router("/search", &SearchController{})

	beego.Router("/login", &CommonController{}, "post:Login")
	beego.Router("/log_out", &CommonController{}, "get:LogOut")
	beego.Router("/reset", &CommonController{}, "post:ResetPassword")
	beego.Router("/userExists", &CommonController{}, "post:UserExists")
	beego.Router("/sendEmail", &CommonController{}, "get:SendEmail")
	beego.Router("/language", &CommonController{}, "get:SwitchLanguage")

	beego.Router("/optional_menu", &OptionalMenuController{})
	beego.Router("/navigation_header", &NavigationHeaderController{})
	beego.Router("/navigation_detail", &NavigationDetailController{})
	beego.Router("/sign_in", &SignInController{})

	//Init user Info
	//admin = &usrInfo{adminName, adminPwd}

}

// TestMain is a sample to run an endpoint test
func TestMain(t *testing.T) {
	assert := assert.New(t)

	//	v := url.Values{}
	//	v.Set("principal", "admin")
	//	v.Add("password", "Harbor12345")

	r, _ := http.NewRequest("GET", "/", nil)
	w := httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(200), w.Code, "'/' httpStatusCode should be 200")
	assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_index</title>"), "http respond should have '<title>page_title_index</title>'")

	r, _ = http.NewRequest("GET", "/dashboard", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(200), w.Code, "'/dashboard' httpStatusCode should be 200")
	assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_dashboard</title>"), "http respond should have '<title>page_title_dashboard</title>'")

	r, _ = http.NewRequest("GET", "/project", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(200), w.Code, "'/project' httpStatusCode should be 200")
	assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_project</title>"), "http respond should have '<title>page_title_project</title>'")

	r, _ = http.NewRequest("GET", "/repository", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(200), w.Code, "'/repository' httpStatusCode should be 200")
	assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_repository</title>"), "http respond should have '<title>page_title_repository</title>'")

	r, _ = http.NewRequest("GET", "/sign_up", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(200), w.Code, "'/sign_up' httpStatusCode should be 200")
	assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_sign_up</title>"), "http respond should have '<title>page_title_sign_up</title>'")

	r, _ = http.NewRequest("GET", "/add_new", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(401), w.Code, "'/add_new' httpStatusCode should be 401")

	r, _ = http.NewRequest("GET", "/account_setting", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(302), w.Code, "'/account_setting' httpStatusCode should be 302")

	r, _ = http.NewRequest("GET", "/change_password", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(200), w.Code, "'/change_password' httpStatusCode should be 200")

	r, _ = http.NewRequest("GET", "/admin_option", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(302), w.Code, "'/admin_option' httpStatusCode should be 302")

	r, _ = http.NewRequest("GET", "/forgot_password", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(200), w.Code, "'/forgot_password' httpStatusCode should be 200")
	assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_forgot_password</title>"), "http respond should have '<title>page_title_forgot_password</title>'")

	r, _ = http.NewRequest("GET", "/reset_password", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(302), w.Code, "'/reset_password' httpStatusCode should be 302")

	r, _ = http.NewRequest("GET", "/search", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(200), w.Code, "'/search' httpStatusCode should be 200")
	assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_search</title>"), "http respond should have '<title>page_title_searc</title>'")

	r, _ = http.NewRequest("POST", "/login", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(401), w.Code, "'/login' httpStatusCode should be 401")

	r, _ = http.NewRequest("GET", "/log_out", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(200), w.Code, "'/log_out' httpStatusCode should be 200")
	assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), ""), "http respond should be empty")

	r, _ = http.NewRequest("POST", "/reset", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(400), w.Code, "'/reset' httpStatusCode should be 400")

	r, _ = http.NewRequest("POST", "/userExists", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(500), w.Code, "'/userExists' httpStatusCode should be 500")

	r, _ = http.NewRequest("GET", "/sendEmail", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(400), w.Code, "'/sendEmail' httpStatusCode should be 400")

	r, _ = http.NewRequest("GET", "/language", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	assert.Equal(int(302), w.Code, "'/language' httpStatusCode should be 302")

	r, _ = http.NewRequest("GET", "/optional_menu", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	//fmt.Printf("/optional_menu: %s\n", w.Body)
	assert.Equal(int(200), w.Code, "'/optional_menu' httpStatusCode should be 200")
	//assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title> </title>"), "http respond should have '<title> </title>'")

	r, _ = http.NewRequest("GET", "/navigation_header", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	//fmt.Printf("/navigation_header: %s\n", w.Body)
	assert.Equal(int(200), w.Code, "'/navigation_header' httpStatusCode should be 200")
	//assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title> </title>"), "http respond should have '<title> </title>'")

	r, _ = http.NewRequest("GET", "/navigation_detail", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	//fmt.Printf("/navigation_detail: %s\n", w.Body)
	assert.Equal(int(200), w.Code, "'/navigation_detail' httpStatusCode should be 200")
	//assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title> </title>"), "http respond should have '<title> </title>'")

	r, _ = http.NewRequest("GET", "/sign_in", nil)
	w = httptest.NewRecorder()
	beego.BeeApp.Handlers.ServeHTTP(w, r)
	//fmt.Printf("/sign_in: %s\n", w.Body)
	assert.Equal(int(200), w.Code, "'/sign_in' httpStatusCode should be 200")
	//assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title> </title>"), "http respond should have '<title> </title>'")

}
<|endoftext|>


<commit_before>package slave

import (
	"fmt"
	"github.com/KIT-MAMID/mamid/msp"
	"sync"
	"time"
)

// TODO make all these constants defaults for CLI parameters.
const DataDBDir = "db"

type Controller struct {
	procManager  *ProcessManager
	configurator MongodConfigurator

	busyTable     map[msp.PortNumber]*sync.Mutex
	busyTableLock sync.Mutex

	mongodHardShutdownTimeout time.Duration
}

func NewController(processManager *ProcessManager, configurator MongodConfigurator, mongodHardShutdownTimeout time.Duration) *Controller {
	return &Controller{
		procManager:               processManager,
		configurator:              configurator,
		busyTable:                 make(map[msp.PortNumber]*sync.Mutex),
		busyTableLock:             sync.Mutex{},
		mongodHardShutdownTimeout: mongodHardShutdownTimeout,
	}
}

func (c *Controller) RequestStatus() ([]msp.Mongod, *msp.Error) {
	ports := c.procManager.RunningProcesses()
	mongods := make([]msp.Mongod, len(ports))
	for k, port := range ports {
		var err *msp.Error
		mongods[k], err = c.configurator.MongodConfiguration(port) // TODO parallelize this? => use goroutines that send into a channel whic appends (single-threaded) to the array
		if err != nil {
			log.Errorf("controller: error querying Mongod configuration: %s", err)
			return nil, err
		}
	}
	return mongods, nil
}

func (c *Controller) EstablishMongodState(m msp.Mongod) *msp.Error {
	c.busyTableLock.Lock()
	if _, exists := c.busyTable[m.Port]; exists {
		c.busyTable[m.Port].Lock()
		c.busyTableLock.Unlock()
	} else if m.State != msp.MongodStateDestroyed {
		c.busyTable[m.Port] = &sync.Mutex{}
		c.busyTable[m.Port].Lock()
		c.busyTableLock.Unlock()
		err := c.procManager.SpawnProcess(m)

		if err != nil {
			log.Errorf("controller: error spawning process: %s", err)
			return &msp.Error{
				Identifier:      msp.SlaveSpawnError,
				Description:     fmt.Sprintf("Unable to start a Mongod instance on port %d", m.Port),
				LongDescription: fmt.Sprintf("ProcessManager.spawnProcess() failed to spawn Mongod on port `%d` with name `%s`: %s", m.Port, m.ReplicaSetName, err),
			}
		}
	} else {
		return nil
	}

	if m.State == msp.MongodStateDestroyed {
		go func() {
			time.Sleep(c.mongodHardShutdownTimeout)
			if killProcessError := c.procManager.KillProcess(m.Port); killProcessError != nil {
				log.Error(killProcessError)
			}
			c.busyTable[m.Port].Unlock() // TODO document this line together with the Unlock() in m.State != msp.MongodStateDestroyed
		}()
	}

	err := c.configurator.ApplyMongodConfiguration(m)
	if err != nil {
		log.Errorf("controller: error applying Mongod configuration: %s", err)
	}

	// do wait until the old instance is destroyed. Having a half destroyed unlocked instance flying around should be dangerous
	// TODO better refer to the hard shutdown timeout above. AND: restructure this code because this is really the else part of the `if` above.
	if m.State != msp.MongodStateDestroyed {
		c.busyTable[m.Port].Unlock()
	}
	return err
}
<commit_msg>UPD: parallelize RequestStatus<commit_after>package slave

import (
	"fmt"
	"github.com/KIT-MAMID/mamid/msp"
	"sync"
	"time"
)

// TODO make all these constants defaults for CLI parameters.
const DataDBDir = "db"

type Controller struct {
	procManager  *ProcessManager
	configurator MongodConfigurator

	busyTable     map[msp.PortNumber]*sync.Mutex
	busyTableLock sync.Mutex

	mongodHardShutdownTimeout time.Duration
}

func NewController(processManager *ProcessManager, configurator MongodConfigurator, mongodHardShutdownTimeout time.Duration) *Controller {
	return &Controller{
		procManager:               processManager,
		configurator:              configurator,
		busyTable:                 make(map[msp.PortNumber]*sync.Mutex),
		busyTableLock:             sync.Mutex{},
		mongodHardShutdownTimeout: mongodHardShutdownTimeout,
	}
}

func (c *Controller) RequestStatus() ([]msp.Mongod, *msp.Error) {
	ports := c.procManager.RunningProcesses()
	mongods := make([]msp.Mongod, len(ports))
	channels := make([]chan msp.Mongod, len(ports))
	for k, port := range ports {
		go func(channel chan msp.Mongod, port msp.PortNumber) {
			mongod, err := c.configurator.MongodConfiguration(port)
			if err != nil {
				log.Errorf("controller: error querying Mongod configuration: %s", err)
				channel <- msp.Mongod{
					Port: port,
					StatusError: err,
					State: msp.MongodStateNotRunning,
				}
			} else {
				channel <- mongod
			}
		}(channels[k], port)
	}
	for k := range ports {
		mongods[k] = <- channels[k]
	}
	return mongods, nil
}

func (c *Controller) EstablishMongodState(m msp.Mongod) *msp.Error {
	c.busyTableLock.Lock()
	if _, exists := c.busyTable[m.Port]; exists {
		c.busyTable[m.Port].Lock()
		c.busyTableLock.Unlock()
	} else if m.State != msp.MongodStateDestroyed {
		c.busyTable[m.Port] = &sync.Mutex{}
		c.busyTable[m.Port].Lock()
		c.busyTableLock.Unlock()
		err := c.procManager.SpawnProcess(m)

		if err != nil {
			log.Errorf("controller: error spawning process: %s", err)
			return &msp.Error{
				Identifier:      msp.SlaveSpawnError,
				Description:     fmt.Sprintf("Unable to start a Mongod instance on port %d", m.Port),
				LongDescription: fmt.Sprintf("ProcessManager.spawnProcess() failed to spawn Mongod on port `%d` with name `%s`: %s", m.Port, m.ReplicaSetName, err),
			}
		}
	} else {
		return nil
	}

	if m.State == msp.MongodStateDestroyed {
		go func() {
			time.Sleep(c.mongodHardShutdownTimeout)
			if killProcessError := c.procManager.KillProcess(m.Port); killProcessError != nil {
				log.Error(killProcessError)
			}
			c.busyTable[m.Port].Unlock() // TODO document this line together with the Unlock() in m.State != msp.MongodStateDestroyed
		}()
	}

	err := c.configurator.ApplyMongodConfiguration(m)
	if err != nil {
		log.Errorf("controller: error applying Mongod configuration: %s", err)
	}

	// do wait until the old instance is destroyed. Having a half destroyed unlocked instance flying around should be dangerous
	// TODO better refer to the hard shutdown timeout above. AND: restructure this code because this is really the else part of the `if` above.
	if m.State != msp.MongodStateDestroyed {
		c.busyTable[m.Port].Unlock()
	}
	return err
}
<|endoftext|>


<commit_before>package sourcegraph

import (
	"strconv"
	"strings"

	"sourcegraph.com/sourcegraph/go-sourcegraph/router"
)

// OrgsService communicates with the organizations-related endpoints in the
// Sourcegraph API.
type OrgsService interface {
	// Get fetches an organization.
	Get(org OrgSpec) (*Org, Response, error)

	// ListMembers lists members of an organization.
	ListMembers(org OrgSpec, opt *OrgListMembersOptions) ([]*User, Response, error)

	// GetSettings fetches an org's configuration settings.
	GetSettings(org OrgSpec) (*OrgSettings, Response, error)

	// UpdateSettings updates an org's configuration settings.
	UpdateSettings(org OrgSpec, settings OrgSettings) (Response, error)
}

// orgsService implements OrgsService.
type orgsService struct {
	client *Client
}

var _ OrgsService = &orgsService{}

// OrgSpec specifies an organization. At least one of Email, Login, and UID must be
// nonempty.
type OrgSpec struct {
	Org string // name of organization
	UID int    // user ID of the "user" record for this organization
}

// PathComponent returns the URL path component that specifies the org.
func (s *OrgSpec) PathComponent() string {
	if s.Org != "" {
		return s.Org
	}
	if s.UID > 0 {
		return "$" + strconv.Itoa(s.UID)
	}
	panic("empty OrgSpec")
}

func (s *OrgSpec) RouteVars() map[string]string {
	return map[string]string{"OrgSpec": s.PathComponent()}
}

type Org struct {
	User
}

// OrgSpec returns the OrgSpec that specifies o.
func (o *Org) OrgSpec() OrgSpec { return OrgSpec{Org: o.Login, UID: int(o.UID)} }

// ParseOrgSpec parses a string generated by (*OrgSpec).String() and
// returns the equivalent OrgSpec struct.
func ParseOrgSpec(pathComponent string) (OrgSpec, error) {
	if strings.HasPrefix(pathComponent, "$") {
		uid, err := strconv.Atoi(pathComponent[1:])
		return OrgSpec{UID: uid}, err
	}
	return OrgSpec{Org: pathComponent}, nil
}

func (s *orgsService) Get(org OrgSpec) (*Org, Response, error) {
	url, err := s.client.URL(router.Org, org.RouteVars(), nil)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var org_ *Org
	resp, err := s.client.Do(req, &org_)
	if err != nil {
		return nil, resp, err
	}

	return org_, resp, nil
}

type OrgListMembersOptions struct {
	ListOptions
}

func (s *orgsService) ListMembers(org OrgSpec, opt *OrgListMembersOptions) ([]*User, Response, error) {
	url, err := s.client.URL(router.OrgMembers, org.RouteVars(), nil)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var members []*User
	resp, err := s.client.Do(req, &members)
	if err != nil {
		return nil, resp, err
	}

	return members, resp, nil
}

// OrgSettings describes an org's configuration settings.
type OrgSettings struct {
	PlanSettings `json:",omitempty"`
}

func (s *orgsService) GetSettings(org OrgSpec) (*OrgSettings, Response, error) {
	url, err := s.client.URL(router.OrgSettings, org.RouteVars(), nil)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var settings *OrgSettings
	resp, err := s.client.Do(req, &settings)
	if err != nil {
		return nil, resp, err
	}

	return settings, resp, nil
}

func (s *orgsService) UpdateSettings(org OrgSpec, settings OrgSettings) (Response, error) {
	url, err := s.client.URL(router.OrgSettingsUpdate, org.RouteVars(), nil)
	if err != nil {
		return nil, err
	}

	req, err := s.client.NewRequest("PUT", url.String(), settings)
	if err != nil {
		return nil, err
	}

	resp, err := s.client.Do(req, nil)
	if err != nil {
		return resp, err
	}

	return resp, nil
}

type MockOrgsService struct {
	Get_            func(org OrgSpec) (*Org, Response, error)
	ListMembers_    func(org OrgSpec, opt *OrgListMembersOptions) ([]*User, Response, error)
	GetSettings_    func(org OrgSpec) (*OrgSettings, Response, error)
	UpdateSettings_ func(org OrgSpec, settings OrgSettings) (Response, error)
}

var _ OrgsService = MockOrgsService{}

func (s MockOrgsService) Get(org OrgSpec) (*Org, Response, error) {
	return s.Get_(org)
}

func (s MockOrgsService) ListMembers(org OrgSpec, opt *OrgListMembersOptions) ([]*User, Response, error) {
	return s.ListMembers_(org, opt)
}

func (s MockOrgsService) GetSettings(org OrgSpec) (*OrgSettings, Response, error) {
	return s.GetSettings_(org)
}

func (s MockOrgsService) UpdateSettings(org OrgSpec, settings OrgSettings) (Response, error) {
	return s.UpdateSettings_(org, settings)
}
<commit_msg>send querystring opts in org ListMembers<commit_after>package sourcegraph

import (
	"strconv"
	"strings"

	"sourcegraph.com/sourcegraph/go-sourcegraph/router"
)

// OrgsService communicates with the organizations-related endpoints in the
// Sourcegraph API.
type OrgsService interface {
	// Get fetches an organization.
	Get(org OrgSpec) (*Org, Response, error)

	// ListMembers lists members of an organization.
	ListMembers(org OrgSpec, opt *OrgListMembersOptions) ([]*User, Response, error)

	// GetSettings fetches an org's configuration settings.
	GetSettings(org OrgSpec) (*OrgSettings, Response, error)

	// UpdateSettings updates an org's configuration settings.
	UpdateSettings(org OrgSpec, settings OrgSettings) (Response, error)
}

// orgsService implements OrgsService.
type orgsService struct {
	client *Client
}

var _ OrgsService = &orgsService{}

// OrgSpec specifies an organization. At least one of Email, Login, and UID must be
// nonempty.
type OrgSpec struct {
	Org string // name of organization
	UID int    // user ID of the "user" record for this organization
}

// PathComponent returns the URL path component that specifies the org.
func (s *OrgSpec) PathComponent() string {
	if s.Org != "" {
		return s.Org
	}
	if s.UID > 0 {
		return "$" + strconv.Itoa(s.UID)
	}
	panic("empty OrgSpec")
}

func (s *OrgSpec) RouteVars() map[string]string {
	return map[string]string{"OrgSpec": s.PathComponent()}
}

type Org struct {
	User
}

// OrgSpec returns the OrgSpec that specifies o.
func (o *Org) OrgSpec() OrgSpec { return OrgSpec{Org: o.Login, UID: int(o.UID)} }

// ParseOrgSpec parses a string generated by (*OrgSpec).String() and
// returns the equivalent OrgSpec struct.
func ParseOrgSpec(pathComponent string) (OrgSpec, error) {
	if strings.HasPrefix(pathComponent, "$") {
		uid, err := strconv.Atoi(pathComponent[1:])
		return OrgSpec{UID: uid}, err
	}
	return OrgSpec{Org: pathComponent}, nil
}

func (s *orgsService) Get(org OrgSpec) (*Org, Response, error) {
	url, err := s.client.URL(router.Org, org.RouteVars(), nil)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var org_ *Org
	resp, err := s.client.Do(req, &org_)
	if err != nil {
		return nil, resp, err
	}

	return org_, resp, nil
}

type OrgListMembersOptions struct {
	ListOptions
}

func (s *orgsService) ListMembers(org OrgSpec, opt *OrgListMembersOptions) ([]*User, Response, error) {
	url, err := s.client.URL(router.OrgMembers, org.RouteVars(), opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var members []*User
	resp, err := s.client.Do(req, &members)
	if err != nil {
		return nil, resp, err
	}

	return members, resp, nil
}

// OrgSettings describes an org's configuration settings.
type OrgSettings struct {
	PlanSettings `json:",omitempty"`
}

func (s *orgsService) GetSettings(org OrgSpec) (*OrgSettings, Response, error) {
	url, err := s.client.URL(router.OrgSettings, org.RouteVars(), nil)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var settings *OrgSettings
	resp, err := s.client.Do(req, &settings)
	if err != nil {
		return nil, resp, err
	}

	return settings, resp, nil
}

func (s *orgsService) UpdateSettings(org OrgSpec, settings OrgSettings) (Response, error) {
	url, err := s.client.URL(router.OrgSettingsUpdate, org.RouteVars(), nil)
	if err != nil {
		return nil, err
	}

	req, err := s.client.NewRequest("PUT", url.String(), settings)
	if err != nil {
		return nil, err
	}

	resp, err := s.client.Do(req, nil)
	if err != nil {
		return resp, err
	}

	return resp, nil
}

type MockOrgsService struct {
	Get_            func(org OrgSpec) (*Org, Response, error)
	ListMembers_    func(org OrgSpec, opt *OrgListMembersOptions) ([]*User, Response, error)
	GetSettings_    func(org OrgSpec) (*OrgSettings, Response, error)
	UpdateSettings_ func(org OrgSpec, settings OrgSettings) (Response, error)
}

var _ OrgsService = MockOrgsService{}

func (s MockOrgsService) Get(org OrgSpec) (*Org, Response, error) {
	return s.Get_(org)
}

func (s MockOrgsService) ListMembers(org OrgSpec, opt *OrgListMembersOptions) ([]*User, Response, error) {
	return s.ListMembers_(org, opt)
}

func (s MockOrgsService) GetSettings(org OrgSpec) (*OrgSettings, Response, error) {
	return s.GetSettings_(org)
}

func (s MockOrgsService) UpdateSettings(org OrgSpec, settings OrgSettings) (Response, error) {
	return s.UpdateSettings_(org, settings)
}
<|endoftext|>


<commit_before><commit_msg>move example<commit_after><|endoftext|>


<commit_before>package executors

import (
	"fmt"
	"os"
	"time"

	"bufio"
	log "github.com/Sirupsen/logrus"
	"gitlab.com/gitlab-org/gitlab-ci-multi-runner/common"
	"gitlab.com/gitlab-org/gitlab-ci-multi-runner/helpers"
	"io"
	"path/filepath"
)

type ExecutorOptions struct {
	DefaultBuildsDir string
	SharedBuildsDir  bool
	Shell            common.ShellScriptInfo
	ShowHostname     bool
	SupportedOptions []string
}

type AbstractExecutor struct {
	ExecutorOptions
	Config           *common.RunnerConfig
	Build            *common.Build
	BuildCanceled    chan bool
	FinishLogWatcher chan bool
	BuildFinish      chan error
	BuildLog         *io.PipeWriter
	ShellScript      *common.ShellScript
}

func (e *AbstractExecutor) ReadTrace(pipe *io.PipeReader) {
	defer e.Debugln("ReadTrace finished")

	traceStopped := false
	traceOutputLimit := helpers.NonZeroOrDefault(e.Config.OutputLimit, common.DefaultOutputLimit)
	traceOutputLimit *= 1024

	reader := bufio.NewReader(pipe)
	for {
		r, s, err := reader.ReadRune()
		if s <= 0 {
			break
		} else if traceStopped {
			// ignore symbols if build log exceeded limit
			continue
		} else if err == nil {
			e.Build.WriteRune(r)
		} else {
			// ignore invalid characters
			continue
		}

		if e.Build.BuildLogLen() > traceOutputLimit {
			output := fmt.Sprintf("\n%sBuild log exceeded limit of %v bytes.%s\n",
				helpers.ANSI_BOLD_RED,
				traceOutputLimit,
				helpers.ANSI_RESET,
			)
			e.Build.WriteString(output)
			traceStopped = true
			break
		}
	}

	pipe.Close()
}

func (e *AbstractExecutor) PushTrace(config common.RunnerConfig, canceled chan bool, finished chan bool) {
	defer e.Debugln("PushTrace finished")

	buildLog := e.BuildLog
	if buildLog == nil {
		<-finished
		return
	}

	lastSentTrace := -1
	lastSentTime := time.Now()

	for {
		select {
		case <-time.After(common.UpdateInterval * time.Second):
		// check if build log changed
			buildTraceLen := e.Build.BuildLogLen()
			if buildTraceLen == lastSentTrace && time.Since(lastSentTime) > common.ForceTraceSentInterval {
				e.Debugln("updateBuildLog", "Nothing to send.")
				continue
			}

			buildTrace := e.Build.BuildLog()
			switch common.UpdateBuild(config, e.Build.ID, common.Running, buildTrace) {
			case common.UpdateSucceeded:
				lastSentTrace = buildTraceLen
				lastSentTime = time.Now()

			case common.UpdateAbort:
				e.Debugln("updateBuildLog", "Sending abort request...")
				canceled <- true
				e.Debugln("updateBuildLog", "Waiting for finished flag...")
				<-finished
				e.Debugln("updateBuildLog", "Thread finished.")
				return
			case common.UpdateFailed:
			}

		case <-finished:
			e.Debugln("updateBuildLog", "Received finish.")
			return
		}
	}
}

func (e *AbstractExecutor) Debugln(args ...interface{}) {
	args = append([]interface{}{e.Config.ShortDescription(), e.Build.ID}, args...)
	log.Debugln(args...)
}

func (e *AbstractExecutor) Println(args ...interface{}) {
	if e.Build != nil {
		e.Build.WriteString(fmt.Sprintln(args...))
	}

	if len(args) == 0 {
		return
	}

	args = append([]interface{}{e.Config.ShortDescription(), e.Build.ID}, args...)
	log.Println(args...)
}

func (e *AbstractExecutor) Infoln(args ...interface{}) {
	if e.Build != nil {
		e.Build.WriteString(helpers.ANSI_BOLD_GREEN + fmt.Sprintln(args...) + helpers.ANSI_RESET)
	}

	if len(args) == 0 {
		return
	}

	args = append([]interface{}{e.Config.ShortDescription(), e.Build.ID}, args...)
	log.Println(args...)
}

func (e *AbstractExecutor) Warningln(args ...interface{}) {
	// write to log file
	if e.Build != nil {
		e.Build.WriteString(helpers.ANSI_BOLD_YELLOW + "WARNING:" + fmt.Sprintln(args...) + helpers.ANSI_RESET)
	}

	args = append([]interface{}{e.Config.ShortDescription(), e.Build.ID}, args...)
	log.Warningln(args...)
}

func (e *AbstractExecutor) Errorln(args ...interface{}) {
	// write to log file
	if e.Build != nil {
		e.Build.WriteString(helpers.ANSI_BOLD_RED + "ERROR: " + fmt.Sprintln(args...) + helpers.ANSI_RESET)
	}

	args = append([]interface{}{e.Config.ShortDescription(), e.Build.ID}, args...)
	log.Errorln(args...)
}

func (e *AbstractExecutor) generateShellScript() error {
	e.Shell.Build = e.Build
	e.Shell.Shell = helpers.StringOrDefault(e.Config.Shell, e.Shell.Shell)
	shellScript, err := common.GenerateShellScript(e.Shell)
	if err != nil {
		return err
	}
	e.ShellScript = shellScript
	e.Debugln("Shell script:", shellScript)
	return nil
}

func (e *AbstractExecutor) startBuild() error {
	// Craete pipe where data are read
	reader, writer := io.Pipe()
	go e.ReadTrace(reader)
	e.BuildLog = writer

	// Save hostname
	if e.ShowHostname {
		e.Build.Hostname, _ = os.Hostname()
	}

	// Deduce build directory
	buildsDir := helpers.StringOrDefault(e.Config.BuildsDir, e.DefaultBuildsDir)
	buildsDir = filepath.Join(buildsDir, e.Build.ProjectUniqueDir(e.SharedBuildsDir))

	// Start actual build
	e.Build.StartBuild(buildsDir)
	return nil
}

func (e *AbstractExecutor) verifyOptions() error {
	for key, _ := range e.Build.Options {
		found := false
		for _, option := range e.SupportedOptions {
			if option == key {
				found = true
				break
			}
		}

		if !found {
			e.Warningln("Defined '%s' is not supported for that executor", key)
		}
	}
	return nil
}

func (e *AbstractExecutor) Prepare(globalConfig *common.Config, config *common.RunnerConfig, build *common.Build) error {
	e.Config = config
	e.Build = build
	e.BuildCanceled = make(chan bool, 1)
	e.BuildFinish = make(chan error, 1)
	e.FinishLogWatcher = make(chan bool)

	err := e.startBuild()
	if err != nil {
		return err
	}

	e.Infoln(fmt.Sprintf("%s %s (%s)", common.NAME, common.VERSION, common.REVISION))

	err = e.verifyOptions()
	if err != nil {
		return err
	}

	err = e.generateShellScript()
	if err != nil {
		return err
	}

	go e.PushTrace(*e.Config, e.BuildCanceled, e.FinishLogWatcher)
	return nil
}

func (e *AbstractExecutor) Wait() error {
	e.Build.BuildState = common.Running

	buildTimeout := e.Build.Timeout
	if buildTimeout <= 0 {
		buildTimeout = common.DefaultTimeout
	}

	// Wait for signals: cancel, timeout, abort or finish
	log.Debugln(e.Config.ShortDescription(), e.Build.ID, "Waiting for signals...")
	e.Println()
	select {
	case <-e.BuildCanceled:
		e.Warningln("Build got canceled.")
		e.Build.FinishBuild(common.Failed)

	case <-time.After(time.Duration(buildTimeout) * time.Second):
		e.Errorln("CI Timeout. Execution took longer then", buildTimeout, "seconds.")
		e.Build.FinishBuild(common.Failed)

	case signal := <-e.Build.BuildAbort:
		e.Errorln("Build got aborted:", signal)
		e.Build.FinishBuild(common.Failed)

	case err := <-e.BuildFinish:
		if err != nil {
			return err
		}

		e.Infoln("Build succeeded.")
		e.Build.FinishBuild(common.Success)
	}
	return nil
}

func (e *AbstractExecutor) Finish(err error) {
	if err != nil {
		e.Errorln("Build failed with:", err)
		e.Build.FinishBuild(common.Failed)
	}

	e.Debugln("Build took", e.Build.BuildDuration)

	if e.BuildLog != nil {
		// wait for update log routine to finish
		e.Debugln("Waiting for build log updater to finish")
		e.FinishLogWatcher <- true
		e.Debugln("Build log updater finished.")
	}

	e.Debugln("Build log: ", e.Build.BuildLog())

	// Send final build state to server
	e.Build.SendBuildLog()
	e.Println("Build finished.")
}

func (e *AbstractExecutor) Cleanup() {
	if e.BuildLog != nil {
		e.BuildLog.Close()
	}
}
<commit_msg>Put empty line before footer<commit_after>package executors

import (
	"fmt"
	"os"
	"time"

	"bufio"
	log "github.com/Sirupsen/logrus"
	"gitlab.com/gitlab-org/gitlab-ci-multi-runner/common"
	"gitlab.com/gitlab-org/gitlab-ci-multi-runner/helpers"
	"io"
	"path/filepath"
)

type ExecutorOptions struct {
	DefaultBuildsDir string
	SharedBuildsDir  bool
	Shell            common.ShellScriptInfo
	ShowHostname     bool
	SupportedOptions []string
}

type AbstractExecutor struct {
	ExecutorOptions
	Config           *common.RunnerConfig
	Build            *common.Build
	BuildCanceled    chan bool
	FinishLogWatcher chan bool
	BuildFinish      chan error
	BuildLog         *io.PipeWriter
	ShellScript      *common.ShellScript
}

func (e *AbstractExecutor) ReadTrace(pipe *io.PipeReader) {
	defer e.Debugln("ReadTrace finished")

	traceStopped := false
	traceOutputLimit := helpers.NonZeroOrDefault(e.Config.OutputLimit, common.DefaultOutputLimit)
	traceOutputLimit *= 1024

	reader := bufio.NewReader(pipe)
	for {
		r, s, err := reader.ReadRune()
		if s <= 0 {
			break
		} else if traceStopped {
			// ignore symbols if build log exceeded limit
			continue
		} else if err == nil {
			e.Build.WriteRune(r)
		} else {
			// ignore invalid characters
			continue
		}

		if e.Build.BuildLogLen() > traceOutputLimit {
			output := fmt.Sprintf("\n%sBuild log exceeded limit of %v bytes.%s\n",
				helpers.ANSI_BOLD_RED,
				traceOutputLimit,
				helpers.ANSI_RESET,
			)
			e.Build.WriteString(output)
			traceStopped = true
			break
		}
	}

	pipe.Close()
}

func (e *AbstractExecutor) PushTrace(config common.RunnerConfig, canceled chan bool, finished chan bool) {
	defer e.Debugln("PushTrace finished")

	buildLog := e.BuildLog
	if buildLog == nil {
		<-finished
		return
	}

	lastSentTrace := -1
	lastSentTime := time.Now()

	for {
		select {
		case <-time.After(common.UpdateInterval * time.Second):
		// check if build log changed
			buildTraceLen := e.Build.BuildLogLen()
			if buildTraceLen == lastSentTrace && time.Since(lastSentTime) > common.ForceTraceSentInterval {
				e.Debugln("updateBuildLog", "Nothing to send.")
				continue
			}

			buildTrace := e.Build.BuildLog()
			switch common.UpdateBuild(config, e.Build.ID, common.Running, buildTrace) {
			case common.UpdateSucceeded:
				lastSentTrace = buildTraceLen
				lastSentTime = time.Now()

			case common.UpdateAbort:
				e.Debugln("updateBuildLog", "Sending abort request...")
				canceled <- true
				e.Debugln("updateBuildLog", "Waiting for finished flag...")
				<-finished
				e.Debugln("updateBuildLog", "Thread finished.")
				return
			case common.UpdateFailed:
			}

		case <-finished:
			e.Debugln("updateBuildLog", "Received finish.")
			return
		}
	}
}

func (e *AbstractExecutor) Debugln(args ...interface{}) {
	args = append([]interface{}{e.Config.ShortDescription(), e.Build.ID}, args...)
	log.Debugln(args...)
}

func (e *AbstractExecutor) Println(args ...interface{}) {
	if e.Build != nil {
		e.Build.WriteString(fmt.Sprintln(args...))
	}

	if len(args) == 0 {
		return
	}

	args = append([]interface{}{e.Config.ShortDescription(), e.Build.ID}, args...)
	log.Println(args...)
}

func (e *AbstractExecutor) Infoln(args ...interface{}) {
	if e.Build != nil {
		e.Build.WriteString(helpers.ANSI_BOLD_GREEN + fmt.Sprintln(args...) + helpers.ANSI_RESET)
	}

	if len(args) == 0 {
		return
	}

	args = append([]interface{}{e.Config.ShortDescription(), e.Build.ID}, args...)
	log.Println(args...)
}

func (e *AbstractExecutor) Warningln(args ...interface{}) {
	// write to log file
	if e.Build != nil {
		e.Build.WriteString(helpers.ANSI_BOLD_YELLOW + "WARNING:" + fmt.Sprintln(args...) + helpers.ANSI_RESET)
	}

	args = append([]interface{}{e.Config.ShortDescription(), e.Build.ID}, args...)
	log.Warningln(args...)
}

func (e *AbstractExecutor) Errorln(args ...interface{}) {
	// write to log file
	if e.Build != nil {
		e.Build.WriteString(helpers.ANSI_BOLD_RED + "ERROR: " + fmt.Sprintln(args...) + helpers.ANSI_RESET)
	}

	args = append([]interface{}{e.Config.ShortDescription(), e.Build.ID}, args...)
	log.Errorln(args...)
}

func (e *AbstractExecutor) generateShellScript() error {
	e.Shell.Build = e.Build
	e.Shell.Shell = helpers.StringOrDefault(e.Config.Shell, e.Shell.Shell)
	shellScript, err := common.GenerateShellScript(e.Shell)
	if err != nil {
		return err
	}
	e.ShellScript = shellScript
	e.Debugln("Shell script:", shellScript)
	return nil
}

func (e *AbstractExecutor) startBuild() error {
	// Craete pipe where data are read
	reader, writer := io.Pipe()
	go e.ReadTrace(reader)
	e.BuildLog = writer

	// Save hostname
	if e.ShowHostname {
		e.Build.Hostname, _ = os.Hostname()
	}

	// Deduce build directory
	buildsDir := helpers.StringOrDefault(e.Config.BuildsDir, e.DefaultBuildsDir)
	buildsDir = filepath.Join(buildsDir, e.Build.ProjectUniqueDir(e.SharedBuildsDir))

	// Start actual build
	e.Build.StartBuild(buildsDir)
	return nil
}

func (e *AbstractExecutor) verifyOptions() error {
	for key, _ := range e.Build.Options {
		found := false
		for _, option := range e.SupportedOptions {
			if option == key {
				found = true
				break
			}
		}

		if !found {
			e.Warningln("Defined '%s' is not supported for that executor", key)
		}
	}
	return nil
}

func (e *AbstractExecutor) Prepare(globalConfig *common.Config, config *common.RunnerConfig, build *common.Build) error {
	e.Config = config
	e.Build = build
	e.BuildCanceled = make(chan bool, 1)
	e.BuildFinish = make(chan error, 1)
	e.FinishLogWatcher = make(chan bool)

	err := e.startBuild()
	if err != nil {
		return err
	}

	e.Infoln(fmt.Sprintf("%s %s (%s)", common.NAME, common.VERSION, common.REVISION))

	err = e.verifyOptions()
	if err != nil {
		return err
	}

	err = e.generateShellScript()
	if err != nil {
		return err
	}

	go e.PushTrace(*e.Config, e.BuildCanceled, e.FinishLogWatcher)
	return nil
}

func (e *AbstractExecutor) Wait() error {
	e.Build.BuildState = common.Running

	buildTimeout := e.Build.Timeout
	if buildTimeout <= 0 {
		buildTimeout = common.DefaultTimeout
	}

	// Wait for signals: cancel, timeout, abort or finish
	log.Debugln(e.Config.ShortDescription(), e.Build.ID, "Waiting for signals...")
	select {
	case <-e.BuildCanceled:
		e.Println()
		e.Warningln("Build got canceled.")
		e.Build.FinishBuild(common.Failed)

	case <-time.After(time.Duration(buildTimeout) * time.Second):
		e.Println()
		e.Errorln("CI Timeout. Execution took longer then", buildTimeout, "seconds.")
		e.Build.FinishBuild(common.Failed)

	case signal := <-e.Build.BuildAbort:
		e.Println()
		e.Errorln("Build got aborted:", signal)
		e.Build.FinishBuild(common.Failed)

	case err := <-e.BuildFinish:
		if err != nil {
			return err
		}

		e.Println()
		e.Infoln("Build succeeded.")
		e.Build.FinishBuild(common.Success)
	}
	return nil
}

func (e *AbstractExecutor) Finish(err error) {
	if err != nil {
		e.Println()
		e.Errorln("Build failed with:", err)
		e.Build.FinishBuild(common.Failed)
	}

	e.Debugln("Build took", e.Build.BuildDuration)

	if e.BuildLog != nil {
		// wait for update log routine to finish
		e.Debugln("Waiting for build log updater to finish")
		e.FinishLogWatcher <- true
		e.Debugln("Build log updater finished.")
	}

	e.Debugln("Build log: ", e.Build.BuildLog())

	// Send final build state to server
	e.Build.SendBuildLog()
	e.Println("Build finished.")
}

func (e *AbstractExecutor) Cleanup() {
	if e.BuildLog != nil {
		e.BuildLog.Close()
	}
}
<|endoftext|>


<commit_before>package keeper

import (
	"fmt"

	sdk "github.com/cosmos/cosmos-sdk/types"
	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
	evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types"
	slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
	dclauthTypes "github.com/zigbee-alliance/distributed-compliance-ledger/x/dclauth/types"
	"github.com/zigbee-alliance/distributed-compliance-ledger/x/validator/types"
)

// jail a validator.
func (k Keeper) Jail(ctx sdk.Context, validator types.Validator, reason string) {
	validator.Power = types.ZeroPower
	validator.Jailed = true
	validator.JailedReason = reason

	k.SetValidator(ctx, validator)
}

// unjail a validator.
func (k Keeper) Unjail(ctx sdk.Context, validator types.Validator) {
	validator.Power = types.Power
	validator.Jailed = false
	validator.JailedReason = ""

	k.SetValidator(ctx, validator)
}

// handle a validator signing two blocks at the same height
// Zeros validator power and jail it. So validator will be removed from validator
// set at the end of the block.
func (k Keeper) HandleDoubleSign(ctx sdk.Context, evidence *evidencetypes.Equivocation) {
	logger := k.Logger(ctx)

	consAddr := evidence.GetConsensusAddress()
	validator, found := k.GetValidatorByConsAddr(ctx, consAddr)

	if !found {
		logger.Error(fmt.Sprintf("Validator by validator address %s not found", consAddr))

		return
	}

	// calculate the age of the evidence.
	infractionHeight := evidence.GetHeight()
	infractionTime := evidence.GetTime()
	ageDuration := ctx.BlockHeader().Time.Sub(infractionTime)
	ageBlocks := ctx.BlockHeader().Height - infractionHeight

	// Reject evidence if the double-sign is too old. Evidence is considered stale
	// if the difference in time and number of blocks is greater than the allowed
	// parameters defined.
	cp := ctx.ConsensusParams()
	if cp != nil && cp.Evidence != nil {
		if ageDuration > cp.Evidence.MaxAgeDuration && ageBlocks > cp.Evidence.MaxAgeNumBlocks {
			logger.Info(
				"ignored equivocation; evidence too old",
				"validator", consAddr,
				"infraction_height", infractionHeight,
				"max_age_num_blocks", cp.Evidence.MaxAgeNumBlocks,
				"infraction_time", infractionTime,
				"max_age_duration", cp.Evidence.MaxAgeDuration,
			)

			return
		}
	}

	// double sign confirmed.
	reason := fmt.Sprintf("Confirmed double sign from %s at height %d, age of duration %d, age of blocks %d", consAddr, infractionHeight, ageDuration, ageBlocks)
	logger.Info(reason)

	// Slash validator.
	ctx.EventManager().EmitEvent(
		sdk.NewEvent(
			slashingtypes.EventTypeSlash,
			sdk.NewAttribute(slashingtypes.AttributeKeyAddress, consAddr.String()),
			sdk.NewAttribute(slashingtypes.AttributeKeyPower, fmt.Sprintf("%d", evidence.GetValidatorPower())),
			sdk.NewAttribute(slashingtypes.AttributeKeyReason, slashingtypes.AttributeValueDoubleSign),
			sdk.NewAttribute(slashingtypes.AttributeKeyJailed, consAddr.String()),
		),
	)

	// Jail the validator.
	if !validator.IsJailed() {
		k.Jail(ctx, validator, reason)
	}
	// Revoked Account
	accAddr, err := sdk.AccAddressFromBech32(validator.Owner)
	if err != nil {
		logger.Info("Error:", sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid Address: (%s)", err))
	}

	// Move account to entity revoked account
	revokedAccount, err := k.dclauthKeeper.MoveAccountToRevokeAccount(
		ctx, accAddr, nil, dclauthTypes.RevokedAccount_MaliciousValidator)
	if err != nil {
		logger.Info("Error:", err)
	}

	k.dclauthKeeper.SetRevokedAccount(ctx, *revokedAccount)

	// delete account record
	k.dclauthKeeper.RemoveAccount(ctx, accAddr)
}
<commit_msg>We should be waiting validator address<commit_after>package keeper

import (
	"fmt"

	sdk "github.com/cosmos/cosmos-sdk/types"
	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
	evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types"
	slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
	dclauthTypes "github.com/zigbee-alliance/distributed-compliance-ledger/x/dclauth/types"
	"github.com/zigbee-alliance/distributed-compliance-ledger/x/validator/types"
)

// jail a validator.
func (k Keeper) Jail(ctx sdk.Context, validator types.Validator, reason string) {
	validator.Power = types.ZeroPower
	validator.Jailed = true
	validator.JailedReason = reason

	k.SetValidator(ctx, validator)
}

// unjail a validator.
func (k Keeper) Unjail(ctx sdk.Context, validator types.Validator) {
	validator.Power = types.Power
	validator.Jailed = false
	validator.JailedReason = ""

	k.SetValidator(ctx, validator)
}

// handle a validator signing two blocks at the same height
// Zeros validator power and jail it. So validator will be removed from validator
// set at the end of the block.
func (k Keeper) HandleDoubleSign(ctx sdk.Context, evidence *evidencetypes.Equivocation) {
	logger := k.Logger(ctx)

	consAddr := evidence.GetConsensusAddress()
	validator, found := k.GetValidatorByConsAddr(ctx, consAddr)

	if !found {
		logger.Error(fmt.Sprintf("Validator by validator address %s not found", consAddr))

		return
	}

	// calculate the age of the evidence.
	infractionHeight := evidence.GetHeight()
	infractionTime := evidence.GetTime()
	ageDuration := ctx.BlockHeader().Time.Sub(infractionTime)
	ageBlocks := ctx.BlockHeader().Height - infractionHeight

	// Reject evidence if the double-sign is too old. Evidence is considered stale
	// if the difference in time and number of blocks is greater than the allowed
	// parameters defined.
	cp := ctx.ConsensusParams()
	if cp != nil && cp.Evidence != nil {
		if ageDuration > cp.Evidence.MaxAgeDuration && ageBlocks > cp.Evidence.MaxAgeNumBlocks {
			logger.Info(
				"ignored equivocation; evidence too old",
				"validator", consAddr,
				"infraction_height", infractionHeight,
				"max_age_num_blocks", cp.Evidence.MaxAgeNumBlocks,
				"infraction_time", infractionTime,
				"max_age_duration", cp.Evidence.MaxAgeDuration,
			)

			return
		}
	}

	// double sign confirmed.
	reason := fmt.Sprintf("Confirmed double sign from %s at height %d, age of duration %d, age of blocks %d", consAddr, infractionHeight, ageDuration, ageBlocks)
	logger.Info(reason)

	// Slash validator.
	ctx.EventManager().EmitEvent(
		sdk.NewEvent(
			slashingtypes.EventTypeSlash,
			sdk.NewAttribute(slashingtypes.AttributeKeyAddress, consAddr.String()),
			sdk.NewAttribute(slashingtypes.AttributeKeyPower, fmt.Sprintf("%d", evidence.GetValidatorPower())),
			sdk.NewAttribute(slashingtypes.AttributeKeyReason, slashingtypes.AttributeValueDoubleSign),
			sdk.NewAttribute(slashingtypes.AttributeKeyJailed, consAddr.String()),
		),
	)

	// Jail the validator.
	if !validator.IsJailed() {
		k.Jail(ctx, validator, reason)
	}
	// Revoked Account
	valAddr, err := sdk.ValAddressFromBech32(validator.Owner)
	accAddr := sdk.AccAddress(valAddr)
	if err != nil {
		logger.Info("Error:", sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid Address: (%s)", err))
	}

	// Move account to entity revoked account
	revokedAccount, err := k.dclauthKeeper.MoveAccountToRevokeAccount(
		ctx, accAddr, nil, dclauthTypes.RevokedAccount_MaliciousValidator)
	if err != nil {
		logger.Info("Error:", err)
	} else {
		k.dclauthKeeper.SetRevokedAccount(ctx, *revokedAccount)
		// delete account record
		k.dclauthKeeper.RemoveAccount(ctx, accAddr)
	}

	logger.Info("Error:", err)
}
<|endoftext|>


<commit_before>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 conn_test

import (
	"github.com/jacobsa/aws/exp/sdb/conn"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"
)

func TestHttpConn(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

type localHandler struct {
	// Input seen.
	req *http.Request
	reqBody []byte

	// To be returned.
	statusCode int
	body       []byte
}

func (h *localHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var err error

	// Record the request.
	if h.req != nil {
		panic("Called twice.")
	}

	h.req = r

	// Record the body.
	if h.reqBody, err = ioutil.ReadAll(r.Body); err != nil {
		panic(err)
	}

	// Write out the response.
	w.WriteHeader(h.statusCode)
	if _, err := w.Write(h.body); err != nil {
		panic(err)
	}
}

type HttpConnTest struct {
	handler  localHandler
	server   *httptest.Server
	endpoint *url.URL
}

func init() { RegisterTestSuite(&HttpConnTest{}) }

func (t *HttpConnTest) SetUp(i *TestInfo) {
	t.server = httptest.NewServer(&t.handler)

	var err error
	t.endpoint, err = url.Parse(t.server.URL)
	AssertEq(nil, err)
}

func (t *HttpConnTest) TearDown() {
	t.server.Close()
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *HttpConnTest) InvalidScheme() {
	// Connection
	_, err := conn.NewHttpConn(&url.URL{Scheme: "taco", Host: "localhost"})

	ExpectThat(err, Error(HasSubstr("scheme")))
	ExpectThat(err, Error(HasSubstr("taco")))
}

func (t *HttpConnTest) UnknownHost() {
	// Connection
	c, err := conn.NewHttpConn(&url.URL{Scheme: "http", Host: "foo.sidofhdksjhf"})
	AssertEq(nil, err)

	// Request
	req := conn.Request{}

	// Call
	_, err = c.SendRequest(req)

	ExpectThat(err, Error(HasSubstr("foo.sidofhdksjhf")))
	ExpectThat(err, Error(HasSubstr("no such host")))
}

func (t *HttpConnTest) BasicHttpInfo() {
	// Connection
	c, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)

	// Request
	req := conn.Request{}

	// Call
	_, err = c.SendRequest(req)
	AssertEq(nil, err)

	AssertNe(nil, t.handler.req)
	sysReq := t.handler.req

	ExpectEq("POST", sysReq.Method)
	ExpectEq("/", sysReq.URL.Path)

	ExpectThat(
		sysReq.Header["Content-Type"],
		ElementsAre("application/x-www-form-urlencoded; charset=utf-8"))

	ExpectThat(
		sysReq.Header["Host"],
		ElementsAre(t.endpoint.Host))
}

func (t *HttpConnTest) RequestContainsNoParameters() {
	// Connection
	c, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)

	// Request
	req := conn.Request{}

	// Call
	_, err = c.SendRequest(req)
	AssertEq(nil, err)

	AssertNe(nil, t.handler.reqBody)
	ExpectEq(0, len(t.handler.reqBody))
}

func (t *HttpConnTest) RequestContainsOneParameter() {
	// Connection
	c, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)

	// Request
	req := conn.Request{
		"taco": "burrito",
	}

	// Call
	_, err = c.SendRequest(req)
	AssertEq(nil, err)

	AssertNe(nil, t.handler.reqBody)
	ExpectEq(
		"taco=burrito",
		string(t.handler.reqBody))
}

func (t *HttpConnTest) RequestContainsMultipleParameters() {
	// Connection
	c, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)

	// Request
	req := conn.Request{
		"taco": "burrito",
		"enchilada": "queso",
		"nachos": "carnitas",
	}

	// Call
	_, err = c.SendRequest(req)
	AssertEq(nil, err)

	AssertNe(nil, t.handler.reqBody)
	ExpectEq(
		"taco=burrito" +
		"&enchilada=queso" +
		"&nachos=carnitas",
		string(t.handler.reqBody))
}

func (t *HttpConnTest) ParametersNeedEscaping() {
	// Connection
	c, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)

	// Request
	req := conn.Request{
		"타코": "burrito",
		"b&az=": "qu ?x",
	}

	// Call
	_, err = c.SendRequest(req)
	AssertEq(nil, err)

	AssertNe(nil, t.handler.reqBody)
	ExpectEq(
		"%ED%83%80%EC%BD%94=burrito" +
		"&b%26az%3D=qu%20%3Fx",
		string(t.handler.reqBody))
}

func (t *HttpConnTest) ReturnsStatusCode() {
	// Handler
	t.handler.statusCode = 123

	// Connection
	c, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)

	// Request
	req := conn.Request{}

	// Call
	resp, err := c.SendRequest(req)
	AssertEq(nil, err)

	ExpectEq(123, resp.StatusCode)
}

func (t *HttpConnTest) ReturnsBody() {
	// Handler
	t.handler.body = []byte{0xde, 0xad, 0x00, 0xbe, 0xef}

	// Connection
	c, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)

	// Request
	req := conn.Request{}

	// Call
	resp, err := c.SendRequest(req)
	AssertEq(nil, err)

	ExpectThat(resp.Body, DeepEquals(t.handler.body))
}

func (t *HttpConnTest) ServerReturnsEmptyBody() {
	// Handler
	t.handler.body = []byte{}

	// Connection
	c, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)

	// Request
	req := conn.Request{}

	// Call
	resp, err := c.SendRequest(req)
	AssertEq(nil, err)

	ExpectThat(resp.Body, ElementsAre())
}

func (t *HttpConnTest) HttpsAllowed() {
	t.endpoint.Scheme = "https"

	// Connection
	_, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)
}
<commit_msg>Fixed some test bugs.<commit_after>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 conn_test

import (
	"github.com/jacobsa/aws/exp/sdb/conn"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"
)

func TestHttpConn(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

type localHandler struct {
	// Input seen.
	req *http.Request
	reqBody []byte

	// To be returned.
	statusCode int
	body       []byte
}

func (h *localHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var err error

	// Record the request.
	if h.req != nil {
		panic("Called twice.")
	}

	h.req = r

	// Record the body.
	if h.reqBody, err = ioutil.ReadAll(r.Body); err != nil {
		panic(err)
	}

	// Write out the response.
	w.WriteHeader(h.statusCode)
	if _, err := w.Write(h.body); err != nil {
		panic(err)
	}
}

type HttpConnTest struct {
	handler  localHandler
	server   *httptest.Server
	endpoint *url.URL
}

func init() { RegisterTestSuite(&HttpConnTest{}) }

func (t *HttpConnTest) SetUp(i *TestInfo) {
	t.server = httptest.NewServer(&t.handler)

	var err error
	t.endpoint, err = url.Parse(t.server.URL)
	AssertEq(nil, err)
}

func (t *HttpConnTest) TearDown() {
	t.server.Close()
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *HttpConnTest) InvalidScheme() {
	// Connection
	_, err := conn.NewHttpConn(&url.URL{Scheme: "taco", Host: "localhost"})

	ExpectThat(err, Error(HasSubstr("scheme")))
	ExpectThat(err, Error(HasSubstr("taco")))
}

func (t *HttpConnTest) UnknownHost() {
	// Connection
	c, err := conn.NewHttpConn(&url.URL{Scheme: "http", Host: "foo.sidofhdksjhf"})
	AssertEq(nil, err)

	// Request
	req := conn.Request{}

	// Call
	_, err = c.SendRequest(req)

	ExpectThat(err, Error(HasSubstr("foo.sidofhdksjhf")))
	ExpectThat(err, Error(HasSubstr("no such host")))
}

func (t *HttpConnTest) BasicHttpInfo() {
	// Connection
	c, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)

	// Request
	req := conn.Request{}

	// Call
	_, err = c.SendRequest(req)
	AssertEq(nil, err)

	AssertNe(nil, t.handler.req)
	sysReq := t.handler.req

	ExpectEq("POST", sysReq.Method)
	ExpectEq("/", sysReq.URL.Path)
	ExpectEq(t.endpoint.Host, sysReq.Host)

	ExpectThat(
		sysReq.Header["Content-Type"],
		ElementsAre("application/x-www-form-urlencoded; charset=utf-8"))
}

func (t *HttpConnTest) RequestContainsNoParameters() {
	// Connection
	c, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)

	// Request
	req := conn.Request{}

	// Call
	_, err = c.SendRequest(req)
	AssertEq(nil, err)

	AssertNe(nil, t.handler.reqBody)
	ExpectEq(0, len(t.handler.reqBody))
}

func (t *HttpConnTest) RequestContainsOneParameter() {
	// Connection
	c, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)

	// Request
	req := conn.Request{
		"taco": "burrito",
	}

	// Call
	_, err = c.SendRequest(req)
	AssertEq(nil, err)

	AssertNe(nil, t.handler.reqBody)
	ExpectEq(
		"taco=burrito",
		string(t.handler.reqBody))
}

func (t *HttpConnTest) RequestContainsMultipleParameters() {
	// Connection
	c, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)

	// Request
	req := conn.Request{
		"taco": "burrito",
		"enchilada": "queso",
		"nachos": "carnitas",
	}

	// Call
	_, err = c.SendRequest(req)
	AssertEq(nil, err)

	AssertNe(nil, t.handler.reqBody)
	ExpectEq(
		"taco=burrito" +
		"&enchilada=queso" +
		"&nachos=carnitas",
		string(t.handler.reqBody))
}

func (t *HttpConnTest) ParametersNeedEscaping() {
	// Connection
	c, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)

	// Request
	req := conn.Request{
		"타코": "burrito",
		"b&az=": "qu ?x",
	}

	// Call
	_, err = c.SendRequest(req)
	AssertEq(nil, err)

	AssertNe(nil, t.handler.reqBody)
	ExpectEq(
		"%ED%83%80%EC%BD%94=burrito" +
		"&b%26az%3D=qu%20%3Fx",
		string(t.handler.reqBody))
}

func (t *HttpConnTest) ReturnsStatusCode() {
	// Handler
	t.handler.statusCode = 123

	// Connection
	c, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)

	// Request
	req := conn.Request{}

	// Call
	resp, err := c.SendRequest(req)
	AssertEq(nil, err)

	ExpectEq(123, resp.StatusCode)
}

func (t *HttpConnTest) ReturnsBody() {
	// Handler
	t.handler.body = []byte{0xde, 0xad, 0x00, 0xbe, 0xef}

	// Connection
	c, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)

	// Request
	req := conn.Request{}

	// Call
	resp, err := c.SendRequest(req)
	AssertEq(nil, err)

	ExpectThat(resp.Body, DeepEquals(t.handler.body))
}

func (t *HttpConnTest) ServerReturnsEmptyBody() {
	// Handler
	t.handler.body = []byte{}

	// Connection
	c, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)

	// Request
	req := conn.Request{}

	// Call
	resp, err := c.SendRequest(req)
	AssertEq(nil, err)

	ExpectThat(resp.Body, ElementsAre())
}

func (t *HttpConnTest) HttpsAllowed() {
	t.endpoint.Scheme = "https"

	// Connection
	_, err := conn.NewHttpConn(t.endpoint)
	AssertEq(nil, err)
}
<|endoftext|>