<commit_before>/*
Copyright 2018 The Kubernetes 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 apiresources

import (
	"fmt"
	"io"
	"sort"
	"strings"

	"github.com/spf13/cobra"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/printers"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
	"k8s.io/kubectl/pkg/util/i18n"
	"k8s.io/kubectl/pkg/util/templates"
)

var (
	apiresourcesExample = templates.Examples(`
		# Print the supported API resources
		kubectl api-resources

		# Print the supported API resources with more information
		kubectl api-resources -o wide

		# Print the supported API resources sorted by a column
		kubectl api-resources --sort-by=name

		# Print the supported namespaced resources
		kubectl api-resources --namespaced=true

		# Print the supported non-namespaced resources
		kubectl api-resources --namespaced=false

		# Print the supported API resources with a specific APIGroup
		kubectl api-resources --api-group=extensions`)
)

// APIResourceOptions is the start of the data required to perform the operation.
// As new fields are added, add them here instead of referencing the cmd.Flags()
type APIResourceOptions struct {
	Output     string
	SortBy     string
	APIGroup   string
	Namespaced bool
	Verbs      []string
	NoHeaders  bool
	Cached     bool

	genericclioptions.IOStreams
}

// groupResource contains the APIGroup and APIResource
type groupResource struct {
	APIGroup        string
	APIGroupVersion string
	APIResource     metav1.APIResource
}

// NewAPIResourceOptions creates the options for APIResource
func NewAPIResourceOptions(ioStreams genericclioptions.IOStreams) *APIResourceOptions {
	return &APIResourceOptions{
		IOStreams:  ioStreams,
		Namespaced: true,
	}
}

// NewCmdAPIResources creates the `api-resources` command
func NewCmdAPIResources(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	o := NewAPIResourceOptions(ioStreams)

	cmd := &cobra.Command{
		Use:     "api-resources",
		Short:   i18n.T("Print the supported API resources on the server"),
		Long:    i18n.T("Print the supported API resources on the server."),
		Example: apiresourcesExample,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(cmd, args))
			cmdutil.CheckErr(o.Validate())
			cmdutil.CheckErr(o.RunAPIResources(cmd, f))
		},
	}

	cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "When using the default or custom-column output format, don't print headers (default print headers).")
	cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, `Output format. One of: (wide, name).`)

	cmd.Flags().StringVar(&o.APIGroup, "api-group", o.APIGroup, "Limit to resources in the specified API group.")
	cmd.Flags().BoolVar(&o.Namespaced, "namespaced", o.Namespaced, "If false, non-namespaced resources will be returned, otherwise returning namespaced resources by default.")
	cmd.Flags().StringSliceVar(&o.Verbs, "verbs", o.Verbs, "Limit to resources that support the specified verbs.")
	cmd.Flags().StringVar(&o.SortBy, "sort-by", o.SortBy, "If non-empty, sort list of resources using specified field. The field can be either 'name' or 'kind'.")
	cmd.Flags().BoolVar(&o.Cached, "cached", o.Cached, "Use the cached list of resources if available.")
	return cmd
}

// Validate checks to the APIResourceOptions to see if there is sufficient information run the command
func (o *APIResourceOptions) Validate() error {
	supportedOutputTypes := sets.NewString("", "wide", "name")
	if !supportedOutputTypes.Has(o.Output) {
		return fmt.Errorf("--output %v is not available", o.Output)
	}
	supportedSortTypes := sets.NewString("", "name", "kind")
	if len(o.SortBy) > 0 {
		if !supportedSortTypes.Has(o.SortBy) {
			return fmt.Errorf("--sort-by accepts only name or kind")
		}
	}
	return nil
}

// Complete adapts from the command line args and validates them
func (o *APIResourceOptions) Complete(cmd *cobra.Command, args []string) error {
	if len(args) != 0 {
		return cmdutil.UsageErrorf(cmd, "unexpected arguments: %v", args)
	}
	return nil
}

// RunAPIResources does the work
func (o *APIResourceOptions) RunAPIResources(cmd *cobra.Command, f cmdutil.Factory) error {
	w := printers.GetNewTabWriter(o.Out)
	defer w.Flush()

	discoveryclient, err := f.ToDiscoveryClient()
	if err != nil {
		return err
	}

	if !o.Cached {
		// Always request fresh data from the server
		discoveryclient.Invalidate()
	}

	errs := []error{}
	lists, err := discoveryclient.ServerPreferredResources()
	if err != nil {
		errs = append(errs, err)
	}

	resources := []groupResource{}

	groupChanged := cmd.Flags().Changed("api-group")
	nsChanged := cmd.Flags().Changed("namespaced")

	for _, list := range lists {
		if len(list.APIResources) == 0 {
			continue
		}
		gv, err := schema.ParseGroupVersion(list.GroupVersion)
		if err != nil {
			continue
		}
		for _, resource := range list.APIResources {
			if len(resource.Verbs) == 0 {
				continue
			}
			// filter apiGroup
			if groupChanged && o.APIGroup != gv.Group {
				continue
			}
			// filter namespaced
			if nsChanged && o.Namespaced != resource.Namespaced {
				continue
			}
			// filter to resources that support the specified verbs
			if len(o.Verbs) > 0 && !sets.NewString(resource.Verbs...).HasAll(o.Verbs...) {
				continue
			}
			resources = append(resources, groupResource{
				APIGroup:        gv.Group,
				APIGroupVersion: gv.String(),
				APIResource:     resource,
			})
		}
	}

	if o.NoHeaders == false && o.Output != "name" {
		if err = printContextHeaders(w, o.Output); err != nil {
			return err
		}
	}

	sort.Stable(sortableResource{resources, o.SortBy})
	for _, r := range resources {
		switch o.Output {
		case "name":
			name := r.APIResource.Name
			if len(r.APIGroup) > 0 {
				name += "." + r.APIGroup
			}
			if _, err := fmt.Fprintf(w, "%s\n", name); err != nil {
				errs = append(errs, err)
			}
		case "wide":
			if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\t%v\n",
				r.APIResource.Name,
				strings.Join(r.APIResource.ShortNames, ","),
				r.APIGroupVersion,
				r.APIResource.Namespaced,
				r.APIResource.Kind,
				r.APIResource.Verbs); err != nil {
				errs = append(errs, err)
			}
		case "":
			if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\n",
				r.APIResource.Name,
				strings.Join(r.APIResource.ShortNames, ","),
				r.APIGroupVersion,
				r.APIResource.Namespaced,
				r.APIResource.Kind); err != nil {
				errs = append(errs, err)
			}
		}
	}

	if len(errs) > 0 {
		return errors.NewAggregate(errs)
	}
	return nil
}

func printContextHeaders(out io.Writer, output string) error {
	columnNames := []string{"NAME", "SHORTNAMES", "APIVERSION", "NAMESPACED", "KIND"}
	if output == "wide" {
		columnNames = append(columnNames, "VERBS")
	}
	_, err := fmt.Fprintf(out, "%s\n", strings.Join(columnNames, "\t"))
	return err
}

type sortableResource struct {
	resources []groupResource
	sortBy    string
}

func (s sortableResource) Len() int { return len(s.resources) }
func (s sortableResource) Swap(i, j int) {
	s.resources[i], s.resources[j] = s.resources[j], s.resources[i]
}
func (s sortableResource) Less(i, j int) bool {
	ret := strings.Compare(s.compareValues(i, j))
	if ret > 0 {
		return false
	} else if ret == 0 {
		return strings.Compare(s.resources[i].APIResource.Name, s.resources[j].APIResource.Name) < 0
	}
	return true
}

func (s sortableResource) compareValues(i, j int) (string, string) {
	switch s.sortBy {
	case "name":
		return s.resources[i].APIResource.Name, s.resources[j].APIResource.Name
	case "kind":
		return s.resources[i].APIResource.Kind, s.resources[j].APIResource.Kind
	}
	return s.resources[i].APIGroup, s.resources[j].APIGroup
}
<commit_msg>update kubectl api-resources help info<commit_after>/*
Copyright 2018 The Kubernetes 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 apiresources

import (
	"fmt"
	"io"
	"sort"
	"strings"

	"github.com/spf13/cobra"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/printers"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
	"k8s.io/kubectl/pkg/util/i18n"
	"k8s.io/kubectl/pkg/util/templates"
)

var (
	apiresourcesExample = templates.Examples(`
		# Print the supported API resources
		kubectl api-resources

		# Print the supported API resources with more information
		kubectl api-resources -o wide

		# Print the supported API resources sorted by a column
		kubectl api-resources --sort-by=name

		# Print the supported namespaced resources
		kubectl api-resources --namespaced=true

		# Print the supported non-namespaced resources
		kubectl api-resources --namespaced=false

		# Print the supported API resources with a specific APIGroup
		kubectl api-resources --api-group=rbac.authorization.k8s.io`)
)

// APIResourceOptions is the start of the data required to perform the operation.
// As new fields are added, add them here instead of referencing the cmd.Flags()
type APIResourceOptions struct {
	Output     string
	SortBy     string
	APIGroup   string
	Namespaced bool
	Verbs      []string
	NoHeaders  bool
	Cached     bool

	genericclioptions.IOStreams
}

// groupResource contains the APIGroup and APIResource
type groupResource struct {
	APIGroup        string
	APIGroupVersion string
	APIResource     metav1.APIResource
}

// NewAPIResourceOptions creates the options for APIResource
func NewAPIResourceOptions(ioStreams genericclioptions.IOStreams) *APIResourceOptions {
	return &APIResourceOptions{
		IOStreams:  ioStreams,
		Namespaced: true,
	}
}

// NewCmdAPIResources creates the `api-resources` command
func NewCmdAPIResources(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	o := NewAPIResourceOptions(ioStreams)

	cmd := &cobra.Command{
		Use:     "api-resources",
		Short:   i18n.T("Print the supported API resources on the server"),
		Long:    i18n.T("Print the supported API resources on the server."),
		Example: apiresourcesExample,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(cmd, args))
			cmdutil.CheckErr(o.Validate())
			cmdutil.CheckErr(o.RunAPIResources(cmd, f))
		},
	}

	cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "When using the default or custom-column output format, don't print headers (default print headers).")
	cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, `Output format. One of: (wide, name).`)

	cmd.Flags().StringVar(&o.APIGroup, "api-group", o.APIGroup, "Limit to resources in the specified API group.")
	cmd.Flags().BoolVar(&o.Namespaced, "namespaced", o.Namespaced, "If false, non-namespaced resources will be returned, otherwise returning namespaced resources by default.")
	cmd.Flags().StringSliceVar(&o.Verbs, "verbs", o.Verbs, "Limit to resources that support the specified verbs.")
	cmd.Flags().StringVar(&o.SortBy, "sort-by", o.SortBy, "If non-empty, sort list of resources using specified field. The field can be either 'name' or 'kind'.")
	cmd.Flags().BoolVar(&o.Cached, "cached", o.Cached, "Use the cached list of resources if available.")
	return cmd
}

// Validate checks to the APIResourceOptions to see if there is sufficient information run the command
func (o *APIResourceOptions) Validate() error {
	supportedOutputTypes := sets.NewString("", "wide", "name")
	if !supportedOutputTypes.Has(o.Output) {
		return fmt.Errorf("--output %v is not available", o.Output)
	}
	supportedSortTypes := sets.NewString("", "name", "kind")
	if len(o.SortBy) > 0 {
		if !supportedSortTypes.Has(o.SortBy) {
			return fmt.Errorf("--sort-by accepts only name or kind")
		}
	}
	return nil
}

// Complete adapts from the command line args and validates them
func (o *APIResourceOptions) Complete(cmd *cobra.Command, args []string) error {
	if len(args) != 0 {
		return cmdutil.UsageErrorf(cmd, "unexpected arguments: %v", args)
	}
	return nil
}

// RunAPIResources does the work
func (o *APIResourceOptions) RunAPIResources(cmd *cobra.Command, f cmdutil.Factory) error {
	w := printers.GetNewTabWriter(o.Out)
	defer w.Flush()

	discoveryclient, err := f.ToDiscoveryClient()
	if err != nil {
		return err
	}

	if !o.Cached {
		// Always request fresh data from the server
		discoveryclient.Invalidate()
	}

	errs := []error{}
	lists, err := discoveryclient.ServerPreferredResources()
	if err != nil {
		errs = append(errs, err)
	}

	resources := []groupResource{}

	groupChanged := cmd.Flags().Changed("api-group")
	nsChanged := cmd.Flags().Changed("namespaced")

	for _, list := range lists {
		if len(list.APIResources) == 0 {
			continue
		}
		gv, err := schema.ParseGroupVersion(list.GroupVersion)
		if err != nil {
			continue
		}
		for _, resource := range list.APIResources {
			if len(resource.Verbs) == 0 {
				continue
			}
			// filter apiGroup
			if groupChanged && o.APIGroup != gv.Group {
				continue
			}
			// filter namespaced
			if nsChanged && o.Namespaced != resource.Namespaced {
				continue
			}
			// filter to resources that support the specified verbs
			if len(o.Verbs) > 0 && !sets.NewString(resource.Verbs...).HasAll(o.Verbs...) {
				continue
			}
			resources = append(resources, groupResource{
				APIGroup:        gv.Group,
				APIGroupVersion: gv.String(),
				APIResource:     resource,
			})
		}
	}

	if o.NoHeaders == false && o.Output != "name" {
		if err = printContextHeaders(w, o.Output); err != nil {
			return err
		}
	}

	sort.Stable(sortableResource{resources, o.SortBy})
	for _, r := range resources {
		switch o.Output {
		case "name":
			name := r.APIResource.Name
			if len(r.APIGroup) > 0 {
				name += "." + r.APIGroup
			}
			if _, err := fmt.Fprintf(w, "%s\n", name); err != nil {
				errs = append(errs, err)
			}
		case "wide":
			if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\t%v\n",
				r.APIResource.Name,
				strings.Join(r.APIResource.ShortNames, ","),
				r.APIGroupVersion,
				r.APIResource.Namespaced,
				r.APIResource.Kind,
				r.APIResource.Verbs); err != nil {
				errs = append(errs, err)
			}
		case "":
			if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\n",
				r.APIResource.Name,
				strings.Join(r.APIResource.ShortNames, ","),
				r.APIGroupVersion,
				r.APIResource.Namespaced,
				r.APIResource.Kind); err != nil {
				errs = append(errs, err)
			}
		}
	}

	if len(errs) > 0 {
		return errors.NewAggregate(errs)
	}
	return nil
}

func printContextHeaders(out io.Writer, output string) error {
	columnNames := []string{"NAME", "SHORTNAMES", "APIVERSION", "NAMESPACED", "KIND"}
	if output == "wide" {
		columnNames = append(columnNames, "VERBS")
	}
	_, err := fmt.Fprintf(out, "%s\n", strings.Join(columnNames, "\t"))
	return err
}

type sortableResource struct {
	resources []groupResource
	sortBy    string
}

func (s sortableResource) Len() int { return len(s.resources) }
func (s sortableResource) Swap(i, j int) {
	s.resources[i], s.resources[j] = s.resources[j], s.resources[i]
}
func (s sortableResource) Less(i, j int) bool {
	ret := strings.Compare(s.compareValues(i, j))
	if ret > 0 {
		return false
	} else if ret == 0 {
		return strings.Compare(s.resources[i].APIResource.Name, s.resources[j].APIResource.Name) < 0
	}
	return true
}

func (s sortableResource) compareValues(i, j int) (string, string) {
	switch s.sortBy {
	case "name":
		return s.resources[i].APIResource.Name, s.resources[j].APIResource.Name
	case "kind":
		return s.resources[i].APIResource.Kind, s.resources[j].APIResource.Kind
	}
	return s.resources[i].APIGroup, s.resources[j].APIGroup
}
<|endoftext|>


<commit_before><commit_msg>cmd/coordinator/buildongce: fix tests<commit_after><|endoftext|>


<commit_before>package punkt

// golang implementation of a set, probably not the best way to do this
// but oh well
type SetString map[string]int

func (ss SetString) Add(str string) {
	ss[str] = 1
}

func (ss SetString) Remove(str string) {
	delete(ss, str)
}

func (ss SetString) Has(str string) bool {
	if ss[str] == 0 {
		return false
	} else {
		return true
	}
}

func (ss SetString) Array() []string {
	arr := make([]string, 0, len(ss))

	for key := range ss {
		arr = append(arr, key)
	}

	return arr
}

// Stores data used to perform sentence boundary detection with punkt
// This is where all the training data gets stored for future use
type Storage struct {
	AbbrevTypes  SetString `json:"AbbrevTypes"`
	Collocations SetString `json:"Collocations"`
	SentStarters SetString `json:"SentStarters"`
	OrthoContext SetString `json:"OrthoContext"`
}

// Creates the default storage container
func NewStorage() *Storage {
	return &Storage{SetString{}, SetString{}, SetString{}, SetString{}}
}

func (p *Storage) addOrthoContext(typ string, flag int) {
	p.OrthoContext[typ] |= flag
}

// Detemins if any of the tokens are an abbreviation
func (p *Storage) IsAbbr(tokens ...string) bool {
	for _, token := range tokens {
		if p.AbbrevTypes.Has(token) {
			return true
		}
	}
	return false
}
<commit_msg>now thats the good good<commit_after>package punkt

// golang implementation of a set of strings
// probably not the best way to do this but oh well
type SetString map[string]int

func (ss SetString) Add(str string) {
	ss[str] = 1
}

func (ss SetString) Remove(str string) {
	delete(ss, str)
}

func (ss SetString) Has(str string) bool {
	if ss[str] == 0 {
		return false
	} else {
		return true
	}
}

func (ss SetString) Array() []string {
	arr := make([]string, 0, len(ss))

	for key := range ss {
		arr = append(arr, key)
	}

	return arr
}

// Stores data used to perform sentence boundary detection with punkt
// This is where all the training data gets stored for future use
type Storage struct {
	AbbrevTypes  SetString `json:"AbbrevTypes"`
	Collocations SetString `json:"Collocations"`
	SentStarters SetString `json:"SentStarters"`
	OrthoContext SetString `json:"OrthoContext"`
}

// Creates the default storage container
func NewStorage() *Storage {
	return &Storage{SetString{}, SetString{}, SetString{}, SetString{}}
}

func (p *Storage) addOrthoContext(typ string, flag int) {
	p.OrthoContext[typ] |= flag
}

// Detemins if any of the tokens are an abbreviation
func (p *Storage) IsAbbr(tokens ...string) bool {
	for _, token := range tokens {
		if p.AbbrevTypes.Has(token) {
			return true
		}
	}

	return false
}
<|endoftext|>


<commit_before>package middlewares

import (
	"net/http"

	"github.com/getfider/fider/app/models/enum"
	"github.com/getfider/fider/app/models/query"

	"github.com/getfider/fider/app"
	"github.com/getfider/fider/app/pkg/bus"
	"github.com/getfider/fider/app/pkg/env"
	"github.com/getfider/fider/app/pkg/errors"
	"github.com/getfider/fider/app/pkg/web"
)

// Tenant adds either SingleTenant or MultiTenant to the pipeline
func Tenant() web.MiddlewareFunc {
	if env.IsSingleHostMode() {
		return SingleTenant()
	}
	return MultiTenant()
}

// SingleTenant inject default tenant into current context
func SingleTenant() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(c *web.Context) error {
			firstTenant := &query.GetFirstTenant{}
			err := bus.Dispatch(c, firstTenant)
			if err != nil && errors.Cause(err) != app.ErrNotFound {
				return c.Failure(err)
			}

			if firstTenant.Result != nil && !firstTenant.Result.IsDisabled() {
				c.SetTenant(firstTenant.Result)

				if c.Request.URL.Hostname() != env.Config.HostDomain {
					return c.NotFound()
				}
			}

			return next(c)
		}
	}
}

// MultiTenant extract tenant information from hostname and inject it into current context
func MultiTenant() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(c *web.Context) error {
			hostname := c.Request.URL.Hostname()

			// If no tenant is specified, redirect user to getfider.com
			// This is only valid for fider.io hosting
			if (env.IsProduction() && hostname == "fider.io") ||
				(env.IsDevelopment() && hostname == "dev.fider.io") {
				if c.Request.URL.Path == "" || c.Request.URL.Path == "/" {
					return c.Redirect("https://getfider.com")
				}
			}

			byDomain := &query.GetTenantByDomain{Domain: hostname}
			err := bus.Dispatch(c, byDomain)
			if err != nil && errors.Cause(err) != app.ErrNotFound {
				return c.Failure(err)
			}

			if byDomain.Result != nil && !byDomain.Result.IsDisabled() {
				c.SetTenant(byDomain.Result)

				if byDomain.Result.CNAME != "" && !c.IsAjax() {
					baseURL := web.TenantBaseURL(c, byDomain.Result)
					if baseURL != c.BaseURL() {
						link := baseURL + c.Request.URL.RequestURI()
						c.SetCanonicalURL(link)
					}
				}
			}

			return next(c)
		}
	}
}

// RequireTenant returns 404 if tenant is not available
func RequireTenant() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(c *web.Context) error {
			tenant := c.Tenant()
			if tenant == nil {
				if env.IsSingleHostMode() {
					return c.Redirect("/signup")
				}
				return c.NotFound()
			}

			return next(c)
		}
	}
}

// BlockPendingTenants blocks requests for pending tenants
func BlockPendingTenants() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(c *web.Context) error {
			if c.Tenant().Status == enum.TenantPending {
				return c.Render(http.StatusOK, "pending-activation.html", web.Props{
					Title:       "Pending Activation",
					Description: "We sent you a confirmation email with a link to activate your site. Please check your inbox to activate it.",
				})
			}
			return next(c)
		}
	}
}

// CheckTenantPrivacy blocks requests of unauthenticated users for private tenants
func CheckTenantPrivacy() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(c *web.Context) error {
			if c.Tenant().IsPrivate && !c.IsAuthenticated() {
				return c.Redirect("/signin")
			}
			return next(c)
		}
	}
}

// BlockLockedTenants blocks requests on locked tenants as they are in read-only mode
func BlockLockedTenants() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(c *web.Context) error {
			if c.Tenant().Status == enum.TenantLocked {

				// Only API operations are blocked, so it's ok to always return a JSON
				return c.JSON(http.StatusPaymentRequired, web.Map{})
			}
			return next(c)
		}
	}
}
<commit_msg>enhancement: add error log to HOST_DOMAIN validation<commit_after>package middlewares

import (
	"net/http"

	"github.com/getfider/fider/app/models/dto"
	"github.com/getfider/fider/app/models/enum"
	"github.com/getfider/fider/app/models/query"

	"github.com/getfider/fider/app"
	"github.com/getfider/fider/app/pkg/bus"
	"github.com/getfider/fider/app/pkg/env"
	"github.com/getfider/fider/app/pkg/errors"
	"github.com/getfider/fider/app/pkg/log"
	"github.com/getfider/fider/app/pkg/web"
)

// Tenant adds either SingleTenant or MultiTenant to the pipeline
func Tenant() web.MiddlewareFunc {
	if env.IsSingleHostMode() {
		return SingleTenant()
	}
	return MultiTenant()
}

// SingleTenant inject default tenant into current context
func SingleTenant() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(c *web.Context) error {
			firstTenant := &query.GetFirstTenant{}
			err := bus.Dispatch(c, firstTenant)
			if err != nil && errors.Cause(err) != app.ErrNotFound {
				return c.Failure(err)
			}

			if firstTenant.Result != nil && !firstTenant.Result.IsDisabled() {
				c.SetTenant(firstTenant.Result)

				if c.Request.URL.Hostname() != env.Config.HostDomain {
					log.Errorf(c, "Requested hostname '@{URLHostname}' does not match environment HOST_DOMAIN '@{HostDomain}'.", dto.Props{
						"URLHostname": c.Request.URL.Hostname(),
						"HostDomain":  env.Config.HostDomain,
					})
					return c.NotFound()
				}
			}

			return next(c)
		}
	}
}

// MultiTenant extract tenant information from hostname and inject it into current context
func MultiTenant() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(c *web.Context) error {
			hostname := c.Request.URL.Hostname()

			// If no tenant is specified, redirect user to getfider.com
			// This is only valid for fider.io hosting
			if (env.IsProduction() && hostname == "fider.io") ||
				(env.IsDevelopment() && hostname == "dev.fider.io") {
				if c.Request.URL.Path == "" || c.Request.URL.Path == "/" {
					return c.Redirect("https://getfider.com")
				}
			}

			byDomain := &query.GetTenantByDomain{Domain: hostname}
			err := bus.Dispatch(c, byDomain)
			if err != nil && errors.Cause(err) != app.ErrNotFound {
				return c.Failure(err)
			}

			if byDomain.Result != nil && !byDomain.Result.IsDisabled() {
				c.SetTenant(byDomain.Result)

				if byDomain.Result.CNAME != "" && !c.IsAjax() {
					baseURL := web.TenantBaseURL(c, byDomain.Result)
					if baseURL != c.BaseURL() {
						link := baseURL + c.Request.URL.RequestURI()
						c.SetCanonicalURL(link)
					}
				}
			}

			return next(c)
		}
	}
}

// RequireTenant returns 404 if tenant is not available
func RequireTenant() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(c *web.Context) error {
			tenant := c.Tenant()
			if tenant == nil {
				if env.IsSingleHostMode() {
					return c.Redirect("/signup")
				}
				return c.NotFound()
			}

			return next(c)
		}
	}
}

// BlockPendingTenants blocks requests for pending tenants
func BlockPendingTenants() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(c *web.Context) error {
			if c.Tenant().Status == enum.TenantPending {
				return c.Render(http.StatusOK, "pending-activation.html", web.Props{
					Title:       "Pending Activation",
					Description: "We sent you a confirmation email with a link to activate your site. Please check your inbox to activate it.",
				})
			}
			return next(c)
		}
	}
}

// CheckTenantPrivacy blocks requests of unauthenticated users for private tenants
func CheckTenantPrivacy() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(c *web.Context) error {
			if c.Tenant().IsPrivate && !c.IsAuthenticated() {
				return c.Redirect("/signin")
			}
			return next(c)
		}
	}
}

// BlockLockedTenants blocks requests on locked tenants as they are in read-only mode
func BlockLockedTenants() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(c *web.Context) error {
			if c.Tenant().Status == enum.TenantLocked {

				// Only API operations are blocked, so it's ok to always return a JSON
				return c.JSON(http.StatusPaymentRequired, web.Map{})
			}
			return next(c)
		}
	}
}
<|endoftext|>


<commit_before>package raft

import (
	"encoding/json"
	golog "log"
)

type Interface interface {
	Step(m Message) bool
	Msgs() []Message
}

type tick int

type config struct {
	NodeId int
	Addr   string
}

type Node struct {
	sm *stateMachine

	elapsed   tick
	election  tick
	heartbeat tick
}

func New(id int, heartbeat, election tick) *Node {
	if election < heartbeat*3 {
		panic("election is least three times as heartbeat [election: %d, heartbeat: %d]")
	}

	n := &Node{
		heartbeat: heartbeat,
		election:  election,
		sm:        newStateMachine(id, []int{id}),
	}

	return n
}

func (n *Node) Id() int { return n.sm.id }

func (n *Node) HasLeader() bool { return n.sm.lead != none }

// Propose asynchronously proposes data be applied to the underlying state machine.
func (n *Node) Propose(data []byte) { n.propose(Normal, data) }

func (n *Node) propose(t int, data []byte) {
	n.Step(Message{Type: msgProp, Entries: []Entry{{Type: t, Data: data}}})
}

func (n *Node) Add(id int, addr string) { n.updateConf(AddNode, &config{NodeId: id, Addr: addr}) }

func (n *Node) Remove(id int) { n.updateConf(RemoveNode, &config{NodeId: id}) }

func (n *Node) Msgs() []Message { return n.sm.Msgs() }

func (n *Node) Step(m Message) bool {
	l := len(n.sm.msgs)
	if !n.sm.Step(m) {
		return false
	}
	for _, m := range n.sm.msgs[l:] {
		switch m.Type {
		case msgAppResp:
			// We just heard from the leader of the same term.
			n.elapsed = 0
		case msgVoteResp:
			// We just heard from the candidate the node voted for.
			if m.Index >= 0 {
				n.elapsed = 0
			}
		}
	}
	return true
}

// Next returns all the appliable entries
func (n *Node) Next() []Entry {
	ents := n.sm.nextEnts()
	for i := range ents {
		switch ents[i].Type {
		case Normal:
		case AddNode:
			c := new(config)
			if err := json.Unmarshal(ents[i].Data, c); err != nil {
				golog.Println(err)
				continue
			}
			n.sm.addNode(c.NodeId)
		case RemoveNode:
			c := new(config)
			if err := json.Unmarshal(ents[i].Data, c); err != nil {
				golog.Println(err)
				continue
			}
			n.sm.removeNode(c.NodeId)
		default:
			panic("unexpected entry type")
		}
	}
	return ents
}

// Tick triggers the node to do a tick.
// If the current elapsed is greater or equal than the timeout,
// node will send corresponding message to the statemachine.
func (n *Node) Tick() {
	if !n.sm.promotable() {
		return
	}

	timeout, msgType := n.election, msgHup
	if n.sm.state == stateLeader {
		timeout, msgType = n.heartbeat, msgBeat
	}
	if n.elapsed >= timeout {
		n.Step(Message{Type: msgType})
		n.elapsed = 0
	} else {
		n.elapsed++
	}
}

func (n *Node) updateConf(t int, c *config) {
	data, err := json.Marshal(c)
	if err != nil {
		panic(err)
	}
	n.propose(t, data)
}
<commit_msg>etcd: glue<commit_after>package raft

import (
	"encoding/json"
	golog "log"
)

type Interface interface {
	Step(m Message) bool
	Msgs() []Message
}

type tick int

type Config struct {
	NodeId int
	Addr   string
}

type Node struct {
	sm *stateMachine

	elapsed   tick
	election  tick
	heartbeat tick
}

func New(id int, heartbeat, election tick) *Node {
	if election < heartbeat*3 {
		panic("election is least three times as heartbeat [election: %d, heartbeat: %d]")
	}

	n := &Node{
		heartbeat: heartbeat,
		election:  election,
		sm:        newStateMachine(id, []int{id}),
	}

	return n
}

func (n *Node) Id() int { return n.sm.id }

func (n *Node) HasLeader() bool { return n.sm.lead != none }

// Propose asynchronously proposes data be applied to the underlying state machine.
func (n *Node) Propose(data []byte) { n.propose(Normal, data) }

func (n *Node) propose(t int, data []byte) {
	n.Step(Message{Type: msgProp, Entries: []Entry{{Type: t, Data: data}}})
}

func (n *Node) Campaign() { n.Step(Message{Type: msgHup}) }

func (n *Node) Add(id int, addr string) { n.updateConf(AddNode, &Config{NodeId: id, Addr: addr}) }

func (n *Node) Remove(id int) { n.updateConf(RemoveNode, &Config{NodeId: id}) }

func (n *Node) Msgs() []Message { return n.sm.Msgs() }

func (n *Node) Step(m Message) bool {
	l := len(n.sm.msgs)
	if !n.sm.Step(m) {
		return false
	}
	for _, m := range n.sm.msgs[l:] {
		switch m.Type {
		case msgAppResp:
			// We just heard from the leader of the same term.
			n.elapsed = 0
		case msgVoteResp:
			// We just heard from the candidate the node voted for.
			if m.Index >= 0 {
				n.elapsed = 0
			}
		}
	}
	return true
}

// Next returns all the appliable entries
func (n *Node) Next() []Entry {
	ents := n.sm.nextEnts()
	for i := range ents {
		switch ents[i].Type {
		case Normal:
		case AddNode:
			c := new(Config)
			if err := json.Unmarshal(ents[i].Data, c); err != nil {
				golog.Println(err)
				continue
			}
			n.sm.addNode(c.NodeId)
		case RemoveNode:
			c := new(Config)
			if err := json.Unmarshal(ents[i].Data, c); err != nil {
				golog.Println(err)
				continue
			}
			n.sm.removeNode(c.NodeId)
		default:
			panic("unexpected entry type")
		}
	}
	return ents
}

// Tick triggers the node to do a tick.
// If the current elapsed is greater or equal than the timeout,
// node will send corresponding message to the statemachine.
func (n *Node) Tick() {
	if !n.sm.promotable() {
		return
	}

	timeout, msgType := n.election, msgHup
	if n.sm.state == stateLeader {
		timeout, msgType = n.heartbeat, msgBeat
	}
	if n.elapsed >= timeout {
		n.Step(Message{Type: msgType})
		n.elapsed = 0
	} else {
		n.elapsed++
	}
}

func (n *Node) updateConf(t int, c *Config) {
	data, err := json.Marshal(c)
	if err != nil {
		panic(err)
	}
	n.propose(t, data)
}
<|endoftext|>


<commit_before>// Package raft implements raft.
package raft

import (
	"errors"
	"log"

	pb "github.com/coreos/etcd/raft/raftpb"
	"github.com/coreos/etcd/third_party/code.google.com/p/go.net/context"
)

var ErrStopped = errors.New("raft: stopped")

// Ready encapsulates the entries and messages that are ready to be saved to
// stable storage, committed or sent to other peers.
type Ready struct {
	// The current state of a Node
	pb.State

	// Entries specifies entries to be saved to stable storage BEFORE
	// Messages are sent.
	Entries []pb.Entry

	// CommittedEntries specifies entries to be committed to a
	// store/state-machine. These have previously been committed to stable
	// store.
	CommittedEntries []pb.Entry

	// Messages specifies outbound messages to be sent AFTER Entries are
	// committed to stable storage.
	Messages []pb.Message
}

func isStateEqual(a, b pb.State) bool {
	return a.Term == b.Term && a.Vote == b.Vote && a.LastIndex == b.LastIndex
}

func (rd Ready) containsUpdates(prev Ready) bool {
	return !isStateEqual(prev.State, rd.State) || len(rd.Entries) > 0 || len(rd.CommittedEntries) > 0 || len(rd.Messages) > 0
}

type Node struct {
	ctx    context.Context
	propc  chan pb.Message
	recvc  chan pb.Message
	readyc chan Ready
	tickc  chan struct{}
	done   chan struct{}
}

func Start(id int64, peers []int64, election, heartbeat int) Node {
	n := newNode()
	r := newRaft(id, peers, election, heartbeat)
	go n.run(r)
	return n
}

func Restart(id int64, peers []int64, election, heartbeat int, st pb.State, ents []pb.Entry) Node {
	n := newNode()
	r := newRaft(id, peers, election, heartbeat)
	r.loadState(st)
	r.loadEnts(ents)
	go n.run(r)
	return n
}

func newNode() Node {
	return Node{
		propc:  make(chan pb.Message),
		recvc:  make(chan pb.Message),
		readyc: make(chan Ready),
		tickc:  make(chan struct{}),
		done:   make(chan struct{}),
	}
}

func (n *Node) Stop() {
	close(n.done)
}

func (n *Node) run(r *raft) {
	propc := n.propc
	readyc := n.readyc

	var lead int64
	var prev Ready
	prev.State = r.State

	for {
		if lead != r.lead {
			log.Printf("raft: leader changed from %#x to %#x", lead, r.lead)
			lead = r.lead
			if r.hasLeader() {
				propc = n.propc
			} else {
				propc = nil
			}
		}

		rd := Ready{
			r.State,
			r.raftLog.unstableEnts(),
			r.raftLog.nextEnts(),
			r.msgs,
		}

		if rd.containsUpdates(prev) {
			readyc = n.readyc
		} else {
			readyc = nil
		}

		select {
		case m := <-propc:
			m.From = r.id
			r.Step(m)
		case m := <-n.recvc:
			r.Step(m) // raft never returns an error
		case <-n.tickc:
			r.tick()
		case readyc <- rd:
			r.raftLog.resetNextEnts()
			r.raftLog.resetUnstable()
			prev = rd
			r.msgs = nil
		case <-n.done:
			return
		}
	}
}

func (n *Node) Tick() error {
	select {
	case n.tickc <- struct{}{}:
		return nil
	case <-n.done:
		return n.ctx.Err()
	}
}

func (n *Node) Campaign(ctx context.Context) error {
	return n.Step(ctx, pb.Message{Type: msgHup})
}

// Propose proposes data be appended to the log.
func (n *Node) Propose(ctx context.Context, data []byte) error {
	return n.Step(ctx, pb.Message{Type: msgProp, Entries: []pb.Entry{{Data: data}}})
}

// Step advances the state machine using msgs. The ctx.Err() will be returned,
// if any.
func (n *Node) Step(ctx context.Context, m pb.Message) error {
	ch := n.recvc
	if m.Type == msgProp {
		ch = n.propc
	}

	select {
	case ch <- m:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	case <-n.done:
		return ErrStopped
	}
}

// ReadState returns the current point-in-time state.
func (n *Node) Ready() <-chan Ready {
	return n.readyc
}
<commit_msg>raft: record previous state is enough<commit_after>// Package raft implements raft.
package raft

import (
	"errors"
	"log"

	pb "github.com/coreos/etcd/raft/raftpb"
	"github.com/coreos/etcd/third_party/code.google.com/p/go.net/context"
)

var (
	EmptyState = pb.State{}
	ErrStopped = errors.New("raft: stopped")
)

// Ready encapsulates the entries and messages that are ready to be saved to
// stable storage, committed or sent to other peers.
type Ready struct {
	// The current state of a Node
	pb.State

	// Entries specifies entries to be saved to stable storage BEFORE
	// Messages are sent.
	Entries []pb.Entry

	// CommittedEntries specifies entries to be committed to a
	// store/state-machine. These have previously been committed to stable
	// store.
	CommittedEntries []pb.Entry

	// Messages specifies outbound messages to be sent AFTER Entries are
	// committed to stable storage.
	Messages []pb.Message
}

func isStateEqual(a, b pb.State) bool {
	return a.Term == b.Term && a.Vote == b.Vote && a.LastIndex == b.LastIndex
}

func (rd Ready) containsUpdates(prevSt pb.State) bool {
	return !isStateEqual(prevSt, rd.State) || len(rd.Entries) > 0 || len(rd.CommittedEntries) > 0 || len(rd.Messages) > 0
}

type Node struct {
	ctx    context.Context
	propc  chan pb.Message
	recvc  chan pb.Message
	readyc chan Ready
	tickc  chan struct{}
	done   chan struct{}
}

func Start(id int64, peers []int64, election, heartbeat int) Node {
	n := newNode()
	r := newRaft(id, peers, election, heartbeat)
	go n.run(r)
	return n
}

func Restart(id int64, peers []int64, election, heartbeat int, st pb.State, ents []pb.Entry) Node {
	n := newNode()
	r := newRaft(id, peers, election, heartbeat)
	r.loadState(st)
	r.loadEnts(ents)
	go n.run(r)
	return n
}

func newNode() Node {
	return Node{
		propc:  make(chan pb.Message),
		recvc:  make(chan pb.Message),
		readyc: make(chan Ready),
		tickc:  make(chan struct{}),
		done:   make(chan struct{}),
	}
}

func (n *Node) Stop() {
	close(n.done)
}

func (n *Node) run(r *raft) {
	propc := n.propc
	readyc := n.readyc

	var lead int64
	prevSt := r.State

	for {
		if lead != r.lead {
			log.Printf("raft: leader changed from %#x to %#x", lead, r.lead)
			lead = r.lead
			if r.hasLeader() {
				propc = n.propc
			} else {
				propc = nil
			}
		}

		rd := Ready{
			r.State,
			r.raftLog.unstableEnts(),
			r.raftLog.nextEnts(),
			r.msgs,
		}

		if rd.containsUpdates(prevSt) {
			readyc = n.readyc
		} else {
			readyc = nil
		}

		select {
		case m := <-propc:
			m.From = r.id
			r.Step(m)
		case m := <-n.recvc:
			r.Step(m) // raft never returns an error
		case <-n.tickc:
			r.tick()
		case readyc <- rd:
			r.raftLog.resetNextEnts()
			r.raftLog.resetUnstable()
			prevSt = rd.State
			r.msgs = nil
		case <-n.done:
			return
		}
	}
}

func (n *Node) Tick() error {
	select {
	case n.tickc <- struct{}{}:
		return nil
	case <-n.done:
		return n.ctx.Err()
	}
}

func (n *Node) Campaign(ctx context.Context) error {
	return n.Step(ctx, pb.Message{Type: msgHup})
}

// Propose proposes data be appended to the log.
func (n *Node) Propose(ctx context.Context, data []byte) error {
	return n.Step(ctx, pb.Message{Type: msgProp, Entries: []pb.Entry{{Data: data}}})
}

// Step advances the state machine using msgs. The ctx.Err() will be returned,
// if any.
func (n *Node) Step(ctx context.Context, m pb.Message) error {
	ch := n.recvc
	if m.Type == msgProp {
		ch = n.propc
	}

	select {
	case ch <- m:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	case <-n.done:
		return ErrStopped
	}
}

// ReadState returns the current point-in-time state.
func (n *Node) Ready() <-chan Ready {
	return n.readyc
}
<|endoftext|>


<commit_before>// String objects
//
// Note that string objects in Python are arrays of unicode
// characters.  However we are using the native Go string which is
// UTF-8 encoded.  This makes very little difference most of the time,
// but care is needed when indexing, slicing or iterating through
// strings.

package py

import (
	"unicode/utf8"
)

type String string

var StringType = NewType("string",
	`str(object='') -> str
str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or
errors is specified, then the object must expose a data buffer
that will be decoded using the given encoding and error handler.
Otherwise, returns the result of object.__str__() (if defined)
or repr(object).
encoding defaults to sys.getdefaultencoding().
errors defaults to 'strict'.`)

// Type of this object
func (s String) Type() *Type {
	return StringType
}

// Intern s possibly returning a reference to an already interned string
func (s String) Intern() String {
	// fmt.Printf("FIXME interning %q\n", s)
	return s
}

func (s String) M__bool__() Object {
	return NewBool(len(s) > 0)
}

func (s String) M__len__() Object {
	return Int(utf8.RuneCountInString(string(s)))
}

func (a String) M__add__(other Object) Object {
	if b, ok := other.(String); ok {
		return a + b
	}
	return NotImplemented
}

func (a String) M__radd__(other Object) Object {
	if b, ok := other.(String); ok {
		return b + a
	}
	return NotImplemented
}

func (a String) M__iadd__(other Object) Object {
	return a.M__add__(other)
}

func (a String) M__mul__(other Object) Object {
	if b, ok := convertToInt(other); ok {
		newString := String("")
		for i := 0; i < int(b); i++ {
			newString += a
		}
		return newString
	}
	return NotImplemented
}

func (a String) M__rmul__(other Object) Object {
	return a.M__mul__(other)
}

func (a String) M__imul__(other Object) Object {
	return a.M__mul__(other)
}

// Convert an Object to an String
//
// Retrurns ok as to whether the conversion worked or not
func convertToString(other Object) (String, bool) {
	switch b := other.(type) {
	case String:
		return b, true
	}
	return "", false
}

// Rich comparison

func (a String) M__lt__(other Object) Object {
	if b, ok := convertToString(other); ok {
		return NewBool(a < b)
	}
	return NotImplemented
}

func (a String) M__le__(other Object) Object {
	if b, ok := convertToString(other); ok {
		return NewBool(a <= b)
	}
	return NotImplemented
}

func (a String) M__eq__(other Object) Object {
	if b, ok := convertToString(other); ok {
		return NewBool(a == b)
	}
	return NotImplemented
}

func (a String) M__ne__(other Object) Object {
	if b, ok := convertToString(other); ok {
		return NewBool(a != b)
	}
	return NotImplemented
}

func (a String) M__gt__(other Object) Object {
	if b, ok := convertToString(other); ok {
		return NewBool(a > b)
	}
	return NotImplemented
}

func (a String) M__ge__(other Object) Object {
	if b, ok := convertToString(other); ok {
		return NewBool(a >= b)
	}
	return NotImplemented
}

// Check stringerface is satisfied
var _ richComparison = String("")
var _ sequenceArithmetic = String("")
var _ I__len__ = String("")
var _ I__bool__ = String("")
<commit_msg>Rudimentary string % operator<commit_after>// String objects
//
// Note that string objects in Python are arrays of unicode
// characters.  However we are using the native Go string which is
// UTF-8 encoded.  This makes very little difference most of the time,
// but care is needed when indexing, slicing or iterating through
// strings.

package py

import (
	"fmt"
	"unicode/utf8"
)

type String string

var StringType = NewType("string",
	`str(object='') -> str
str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or
errors is specified, then the object must expose a data buffer
that will be decoded using the given encoding and error handler.
Otherwise, returns the result of object.__str__() (if defined)
or repr(object).
encoding defaults to sys.getdefaultencoding().
errors defaults to 'strict'.`)

// Type of this object
func (s String) Type() *Type {
	return StringType
}

// Intern s possibly returning a reference to an already interned string
func (s String) Intern() String {
	// fmt.Printf("FIXME interning %q\n", s)
	return s
}

func (s String) M__bool__() Object {
	return NewBool(len(s) > 0)
}

func (s String) M__len__() Object {
	return Int(utf8.RuneCountInString(string(s)))
}

func (a String) M__add__(other Object) Object {
	if b, ok := other.(String); ok {
		return a + b
	}
	return NotImplemented
}

func (a String) M__radd__(other Object) Object {
	if b, ok := other.(String); ok {
		return b + a
	}
	return NotImplemented
}

func (a String) M__iadd__(other Object) Object {
	return a.M__add__(other)
}

func (a String) M__mul__(other Object) Object {
	if b, ok := convertToInt(other); ok {
		newString := String("")
		for i := 0; i < int(b); i++ {
			newString += a
		}
		return newString
	}
	return NotImplemented
}

func (a String) M__rmul__(other Object) Object {
	return a.M__mul__(other)
}

func (a String) M__imul__(other Object) Object {
	return a.M__mul__(other)
}

// Convert an Object to an String
//
// Retrurns ok as to whether the conversion worked or not
func convertToString(other Object) (String, bool) {
	switch b := other.(type) {
	case String:
		return b, true
	}
	return "", false
}

// Rich comparison

func (a String) M__lt__(other Object) Object {
	if b, ok := convertToString(other); ok {
		return NewBool(a < b)
	}
	return NotImplemented
}

func (a String) M__le__(other Object) Object {
	if b, ok := convertToString(other); ok {
		return NewBool(a <= b)
	}
	return NotImplemented
}

func (a String) M__eq__(other Object) Object {
	if b, ok := convertToString(other); ok {
		return NewBool(a == b)
	}
	return NotImplemented
}

func (a String) M__ne__(other Object) Object {
	if b, ok := convertToString(other); ok {
		return NewBool(a != b)
	}
	return NotImplemented
}

func (a String) M__gt__(other Object) Object {
	if b, ok := convertToString(other); ok {
		return NewBool(a > b)
	}
	return NotImplemented
}

func (a String) M__ge__(other Object) Object {
	if b, ok := convertToString(other); ok {
		return NewBool(a >= b)
	}
	return NotImplemented
}

// % operator

func (a String) M__mod__(other Object) Object {
	var values Tuple
	switch b := other.(type) {
	case Tuple:
		values = b
	default:
		values = Tuple{other}
	}
	// FIXME not a full implementation ;-)
	return String(fmt.Sprintf("%s %#v", a, values))
}

func (a String) M__rmod__(other Object) Object {
	switch b := other.(type) {
	case String:
		return b.M__mod__(a)
	}
	return NotImplemented
}

func (a String) M__imod__(other Object) Object {
	return a.M__mod__(other)
}

// Check stringerface is satisfied
var _ richComparison = String("")
var _ sequenceArithmetic = String("")
var _ I__mod__ = String("")
var _ I__rmod__ = String("")
var _ I__imod__ = String("")
var _ I__len__ = String("")
var _ I__bool__ = String("")
<|endoftext|>


<commit_before>// Discordgo - Discord bindings for Go
// Available at https://github.com/bwmarrin/discordgo

// Copyright 2015 Bruce Marriner <bruce@sqls.net>.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file contains experimental functions for interacting with the Discord
// Voice websocket and UDP connections.
//
// EVERYTHING in this file is very experimental and will change.

package discordgo

import (
	"encoding/binary"
	"encoding/json"
	"fmt"
	"net"
	"strings"
	"time"

	"github.com/gorilla/websocket"
)

// A VEvent is the initial structure for voice websocket events.  I think
// I can reuse the data websocket structure here.
type VEvent struct {
	Type      string          `json:"t"`
	State     int             `json:"s"`
	Operation int             `json:"op"`
	RawData   json.RawMessage `json:"d"`
}

// A VoiceOP2 stores the data for voice operation 2 websocket events
// which is sort of like the voice READY packet
type VoiceOP2 struct {
	SSRC              uint32        `json:"ssrc"`
	Port              int           `json:"port"`
	Modes             []string      `json:"modes"`
	HeartbeatInterval time.Duration `json:"heartbeat_interval"`
}

type voiceHandshakeData struct {
	ServerID  string `json:"server_id"`
	UserID    string `json:"user_id"`
	SessionID string `json:"session_id"`
	Token     string `json:"token"`
}

type voiceHandshakeOp struct {
	Op   int                `json:"op"` // Always 0
	Data voiceHandshakeData `json:"d"`
}

// VoiceOpenWS opens a voice websocket connection.  This should be called
// after VoiceChannelJoin is used and the data VOICE websocket events
// are captured.
func (s *Session) VoiceOpenWS() {

	// Don't open a socket if one is already open
	if s.VwsConn != nil {
		return
	}

	var self User
	var err error

	self, err = s.User("@me") // TODO: Move to @ login and store in session

	// Connect to Voice Websocket
	vg := fmt.Sprintf("wss://%s", strings.TrimSuffix(s.VEndpoint, ":80"))
	s.VwsConn, _, err = websocket.DefaultDialer.Dial(vg, nil)
	if err != nil {
		fmt.Println("VOICE cannot open websocket:", err)
	}

	json := voiceHandshakeOp{0, voiceHandshakeData{s.VGuildID, self.ID, s.VSessionID, s.VToken}}

	err = s.VwsConn.WriteJSON(json)
	if err != nil {
		fmt.Println("VOICE ERROR sending init packet:", err)
	}

	// Start a listening for voice websocket events
	go s.VoiceListen()
}

// Close closes the connection to the voice websocket.
func (s *Session) VoiceCloseWS() {
	s.VwsConn.Close()
}

// VoiceListen listens on the voice websocket for messages and passes them
// to the voice event handler.
func (s *Session) VoiceListen() (err error) {

	for {
		messageType, message, err := s.VwsConn.ReadMessage()
		if err != nil {
			fmt.Println("Voice Listen Error:", err)
			break
		}

		// Pass received message to voice event handler
		go s.VoiceEvent(messageType, message)
	}

	return
}

// VoiceEvent handles any messages received on the voice websocket
func (s *Session) VoiceEvent(messageType int, message []byte) (err error) {

	if s.Debug {
		fmt.Println("VOICE EVENT:", messageType)
		printJSON(message)
	}

	var e VEvent
	if err := json.Unmarshal(message, &e); err != nil {
		return err
	}

	switch e.Operation {

	case 2: // READY packet
		var st VoiceOP2
		if err := json.Unmarshal(e.RawData, &st); err != nil {
			fmt.Println(e.Type, err)
			printJSON(e.RawData) // TODO: Better error logginEventg
			return err
		}

		// Start the voice websocket heartbeat to keep the connection alive
		go s.VoiceHeartbeat(st.HeartbeatInterval)

		// Store all event data into the session
		s.Vop2 = st

		// We now have enough data to start the UDP connection
		s.VoiceOpenUDP()

		return
	case 3: // HEARTBEAT response
		// add code to use this to track latency?
		return
	case 4:
		// TODO
	default:
		fmt.Println("UNKNOWN VOICE OP: ", e.Operation)
		printJSON(e.RawData)
	}

	return
}

type voiceUDPData struct {
	Address string `json:"address"` // Public IP of machine running this code
	Port    uint16 `json:"port"`    // UDP Port of machine running this code
	Mode    string `json:"mode"`    // plain or ?  (plain or encrypted)
}

type voiceUDPD struct {
	Protocol string       `json:"protocol"` // Always "udp" ?
	Data     voiceUDPData `json:"data"`
}

type voiceUDPOp struct {
	Op   int       `json:"op"` // Always 1
	Data voiceUDPD `json:"d"`
}

// VoiceOpenUDP opens a UDP connect to the voice server and completes the
// initial required handshake.  This connect is left open in the session
// and can be used to send or receive audio.
func (s *Session) VoiceOpenUDP() {

	// TODO: add code to convert hostname into an IP address to avoid problems
	// with frequent DNS lookups. ??

	udpHost := fmt.Sprintf("%s:%d", strings.TrimSuffix(s.VEndpoint, ":80"), s.Vop2.Port)
	serverAddr, err := net.ResolveUDPAddr("udp", udpHost)
	if err != nil {
		fmt.Println(err)
	}

	s.UDPConn, err = net.DialUDP("udp", nil, serverAddr)
	if err != nil {
		fmt.Println(err)
	}

	// Create a 70 byte array and put the SSRC code from the Op 2 Voice event
	// into it.  Then send that over the UDP connection to Discord
	sb := make([]byte, 70)
	binary.BigEndian.PutUint32(sb, s.Vop2.SSRC)
	s.UDPConn.Write(sb)

	// Create a 70 byte array and listen for the initial handshake response
	// from Discord.  Once we get it parse the IP and PORT information out
	// of the response.  This should be our public IP and PORT as Discord
	// saw us.
	rb := make([]byte, 70)
	rlen, _, err := s.UDPConn.ReadFromUDP(rb)
	if rlen < 70 {
		fmt.Println("Voice RLEN should be 70 but isn't")
	}

	// TODO need serious changes, this will likely not work on all IPs!
	ip := string(rb[4:16]) // must be a better way.  TODO: NEEDS TESTING
	port := make([]byte, 2)
	port[0] = rb[68]
	port[1] = rb[69]
	p := binary.LittleEndian.Uint16(port)

	// Take the parsed data from above and send it back to Discord
	// to finalize the UDP handshake.
	jsondata := voiceUDPOp{1, voiceUDPD{"udp", voiceUDPData{ip, p, "plain"}}}

	err = s.VwsConn.WriteJSON(jsondata)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	s.UDPReady = true

	// continue to listen for future packets
	// go s.VoiceListenUDP()
}

// VoiceCloseUDP closes the voice UDP connection.
func (s *Session) VoiceCloseUDP() {
	s.UDPConn.Close()
}

type voiceSpeakingData struct {
	Speaking bool `json:"speaking"`
	Delay    int  `json:"delay"`
}

type voiceSpeakingOp struct {
	Op   int               `json:"op"` // Always 5
	Data voiceSpeakingData `json:"d"`
}

func (s *Session) VoiceSpeaking() {

	if s.VwsConn == nil {
		// TODO return an error
		fmt.Println("No Voice websocket.")
		return
	}

	json := voiceSpeakingOp{5, voiceSpeakingData{true, 0}}
	err := s.VwsConn.WriteJSON(json)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
}

// VoiceListenUDP is test code to listen for UDP packets
func (s *Session) VoiceListenUDP() {

	// start the udp keep alive too.  Otherwise listening doesn't get much.
	// THIS DOES NOT WORK YET
	// go s.VoiceUDPKeepalive(s.Vop2.HeartbeatInterval) // lets try the ws timer

	for {
		b := make([]byte, 1024)
		rlen, _, err := s.UDPConn.ReadFromUDP(b)
		if err != nil {
			fmt.Println("Error reading from UDP:", err)
			//			return
		}

		if rlen < 1 {
			fmt.Println("Empty UDP packet received")
			continue
			// empty packet?
		}
		fmt.Println("READ FROM UDP: ", b)
	}

}

// VoiceUDPKeepalive sends a packet to keep the UDP connection forwarding
// alive for NATed clients.  Without this no audio can be received
// after short periods of silence.
// Not sure how often this is supposed to be sent or even what payload
// I am suppose to be sending.  So this is very.. unfinished :)
func (s *Session) VoiceUDPKeepalive(i time.Duration) {

	// NONE OF THIS WORKS. SO DON'T USE IT.
	//
	// testing with the above 70 byte SSRC packet.
	//
	// Create a 70 byte array and put the SSRC code from the Op 2 Voice event
	// into it.  Then send that over the UDP connection to Discord

	ticker := time.NewTicker(i * time.Millisecond)
	for range ticker.C {
		sb := make([]byte, 8)
		sb[0] = 0x80
		sb[1] = 0xc9
		sb[2] = 0x00
		sb[3] = 0x01

		ssrcBE := make([]byte, 4)
		binary.BigEndian.PutUint32(ssrcBE, s.Vop2.SSRC)

		sb[4] = ssrcBE[0]
		sb[5] = ssrcBE[1]
		sb[6] = ssrcBE[2]
		sb[7] = ssrcBE[3]

		s.UDPConn.Write(ssrcBE)
	}
}

type voiceHeartbeatOp struct {
	Op   int `json:"op"` // Always 3
	Data int `json:"d"`
}

// VoiceHeartbeat sends regular heartbeats to voice Discord so it knows the client
// is still connected.  If you do not send these heartbeats Discord will
// disconnect the websocket connection after a few seconds.
func (s *Session) VoiceHeartbeat(i time.Duration) {

	ticker := time.NewTicker(i * time.Millisecond)
	for {
		timestamp := int(time.Now().Unix())
		json := voiceHeartbeatOp{3, timestamp}

		err := s.VwsConn.WriteJSON(json)
		if err != nil {
			s.VoiceReady = false
			fmt.Println(err)
			return // log error?
		}

		s.VoiceReady = true
		<-ticker.C
	}
}
<commit_msg>Clean up voice.go, should work better on other systems now, closes #31<commit_after>// Discordgo - Discord bindings for Go
// Available at https://github.com/bwmarrin/discordgo

// Copyright 2015 Bruce Marriner <bruce@sqls.net>.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file contains experimental functions for interacting with the Discord
// Voice websocket and UDP connections.
//
// EVERYTHING in this file is very experimental and will change.

package discordgo

import (
	"encoding/binary"
	"encoding/json"
	"fmt"
	"net"
	"strings"
	"time"

	"github.com/gorilla/websocket"
)

// A VEvent is the initial structure for voice websocket events.  I think
// I can reuse the data websocket structure here.
type VEvent struct {
	Type      string          `json:"t"`
	State     int             `json:"s"`
	Operation int             `json:"op"`
	RawData   json.RawMessage `json:"d"`
}

// A VoiceOP2 stores the data for voice operation 2 websocket events
// which is sort of like the voice READY packet
type VoiceOP2 struct {
	SSRC              uint32        `json:"ssrc"`
	Port              int           `json:"port"`
	Modes             []string      `json:"modes"`
	HeartbeatInterval time.Duration `json:"heartbeat_interval"`
}

type voiceHandshakeData struct {
	ServerID  string `json:"server_id"`
	UserID    string `json:"user_id"`
	SessionID string `json:"session_id"`
	Token     string `json:"token"`
}

type voiceHandshakeOp struct {
	Op   int                `json:"op"` // Always 0
	Data voiceHandshakeData `json:"d"`
}

// VoiceOpenWS opens a voice websocket connection.  This should be called
// after VoiceChannelJoin is used and the data VOICE websocket events
// are captured.
func (s *Session) VoiceOpenWS() {

	// Don't open a socket if one is already open
	if s.VwsConn != nil {
		return
	}

	var self User
	var err error

	self, err = s.User("@me") // TODO: Move to @ login and store in session

	// Connect to Voice Websocket
	vg := fmt.Sprintf("wss://%s", strings.TrimSuffix(s.VEndpoint, ":80"))
	s.VwsConn, _, err = websocket.DefaultDialer.Dial(vg, nil)
	if err != nil {
		fmt.Println("VOICE cannot open websocket:", err)
	}

	json := voiceHandshakeOp{0, voiceHandshakeData{s.VGuildID, self.ID, s.VSessionID, s.VToken}}

	err = s.VwsConn.WriteJSON(json)
	if err != nil {
		fmt.Println("VOICE ERROR sending init packet:", err)
	}

	// Start a listening for voice websocket events
	go s.VoiceListen()
}

// Close closes the connection to the voice websocket.
func (s *Session) VoiceCloseWS() {
	s.VwsConn.Close()
}

// VoiceListen listens on the voice websocket for messages and passes them
// to the voice event handler.
func (s *Session) VoiceListen() (err error) {

	for {
		messageType, message, err := s.VwsConn.ReadMessage()
		if err != nil {
			fmt.Println("Voice Listen Error:", err)
			break
		}

		// Pass received message to voice event handler
		go s.VoiceEvent(messageType, message)
	}

	return
}

// VoiceEvent handles any messages received on the voice websocket
func (s *Session) VoiceEvent(messageType int, message []byte) (err error) {

	if s.Debug {
		fmt.Println("VOICE EVENT:", messageType)
		printJSON(message)
	}

	var e VEvent
	if err := json.Unmarshal(message, &e); err != nil {
		return err
	}

	switch e.Operation {

	case 2: // READY packet
		var st VoiceOP2
		if err := json.Unmarshal(e.RawData, &st); err != nil {
			fmt.Println(e.Type, err)
			printJSON(e.RawData) // TODO: Better error logginEventg
			return err
		}

		// Start the voice websocket heartbeat to keep the connection alive
		go s.VoiceHeartbeat(st.HeartbeatInterval)

		// Store all event data into the session
		s.Vop2 = st

		// We now have enough data to start the UDP connection
		s.VoiceOpenUDP()

		return
	case 3: // HEARTBEAT response
		// add code to use this to track latency?
		return
	case 4:
		// TODO
	default:
		fmt.Println("UNKNOWN VOICE OP: ", e.Operation)
		printJSON(e.RawData)
	}

	return
}

type voiceUDPData struct {
	Address string `json:"address"` // Public IP of machine running this code
	Port    uint16 `json:"port"`    // UDP Port of machine running this code
	Mode    string `json:"mode"`    // plain or ?  (plain or encrypted)
}

type voiceUDPD struct {
	Protocol string       `json:"protocol"` // Always "udp" ?
	Data     voiceUDPData `json:"data"`
}

type voiceUDPOp struct {
	Op   int       `json:"op"` // Always 1
	Data voiceUDPD `json:"d"`
}

// VoiceOpenUDP opens a UDP connect to the voice server and completes the
// initial required handshake.  This connect is left open in the session
// and can be used to send or receive audio.
func (s *Session) VoiceOpenUDP() {

	udpHost := fmt.Sprintf("%s:%d", strings.TrimSuffix(s.VEndpoint, ":80"), s.Vop2.Port)
	serverAddr, err := net.ResolveUDPAddr("udp", udpHost)
	if err != nil {
		fmt.Println(err)
	}

	s.UDPConn, err = net.DialUDP("udp", nil, serverAddr)
	if err != nil {
		fmt.Println(err)
	}

	// Create a 70 byte array and put the SSRC code from the Op 2 Voice event
	// into it.  Then send that over the UDP connection to Discord
	sb := make([]byte, 70)
	binary.BigEndian.PutUint32(sb, s.Vop2.SSRC)
	s.UDPConn.Write(sb)

	// Create a 70 byte array and listen for the initial handshake response
	// from Discord.  Once we get it parse the IP and PORT information out
	// of the response.  This should be our public IP and PORT as Discord
	// saw us.
	rb := make([]byte, 70)
	rlen, _, err := s.UDPConn.ReadFromUDP(rb)
	if rlen < 70 {
		fmt.Println("Voice RLEN should be 70 but isn't")
	}

	// Loop over position 4 though 20 to grab the IP address
	// Should never be beyond position 20.
	var ip string
	for i := 4; i < 20; i++ {
		if rb[i] == 0 {
			break
		}
		ip += string(rb[i])
	}

	// Grab port from postion 68 and 69
	port := binary.LittleEndian.Uint16(rb[68:70])

	// Take the parsed data from above and send it back to Discord
	// to finalize the UDP handshake.
	jsondata := voiceUDPOp{1, voiceUDPD{"udp", voiceUDPData{ip, port, "plain"}}}

	err = s.VwsConn.WriteJSON(jsondata)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	s.UDPReady = true
}

// VoiceCloseUDP closes the voice UDP connection.
func (s *Session) VoiceCloseUDP() {
	s.UDPConn.Close()
}

type voiceSpeakingData struct {
	Speaking bool `json:"speaking"`
	Delay    int  `json:"delay"`
}

type voiceSpeakingOp struct {
	Op   int               `json:"op"` // Always 5
	Data voiceSpeakingData `json:"d"`
}

func (s *Session) VoiceSpeaking() {

	if s.VwsConn == nil {
		// TODO return an error
		fmt.Println("No Voice websocket.")
		return
	}

	json := voiceSpeakingOp{5, voiceSpeakingData{true, 0}}
	err := s.VwsConn.WriteJSON(json)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
}

// VoiceListenUDP is test code to listen for UDP packets
func (s *Session) VoiceListenUDP() {

	// start the udp keep alive too.  Otherwise listening doesn't get much.
	// THIS DOES NOT WORK YET
	// go s.VoiceUDPKeepalive(s.Vop2.HeartbeatInterval) // lets try the ws timer

	for {
		b := make([]byte, 1024) //TODO DO NOT PUT MAKE INSIDE LOOP
		rlen, _, err := s.UDPConn.ReadFromUDP(b)
		if err != nil {
			fmt.Println("Error reading from UDP:", err)
			//			return
		}

		if rlen < 1 {
			fmt.Println("Empty UDP packet received")
			continue
			// empty packet?
		}
		fmt.Println("READ FROM UDP: ", b)
	}

}

// VoiceUDPKeepalive sends a packet to keep the UDP connection forwarding
// alive for NATed clients.  Without this no audio can be received
// after short periods of silence.
// Not sure how often this is supposed to be sent or even what payload
// I am suppose to be sending.  So this is very.. unfinished :)
func (s *Session) VoiceUDPKeepalive(i time.Duration) {

	// NONE OF THIS WORKS. SO DON'T USE IT.
	//
	// testing with the above 70 byte SSRC packet.
	//
	// Create a 70 byte array and put the SSRC code from the Op 2 Voice event
	// into it.  Then send that over the UDP connection to Discord

	ticker := time.NewTicker(i * time.Millisecond)
	for range ticker.C {
		sb := make([]byte, 8)
		sb[0] = 0x80
		sb[1] = 0xc9
		sb[2] = 0x00
		sb[3] = 0x01

		ssrcBE := make([]byte, 4)
		binary.BigEndian.PutUint32(ssrcBE, s.Vop2.SSRC)

		sb[4] = ssrcBE[0]
		sb[5] = ssrcBE[1]
		sb[6] = ssrcBE[2]
		sb[7] = ssrcBE[3]

		s.UDPConn.Write(ssrcBE)
	}
}

type voiceHeartbeatOp struct {
	Op   int `json:"op"` // Always 3
	Data int `json:"d"`
}

// VoiceHeartbeat sends regular heartbeats to voice Discord so it knows the client
// is still connected.  If you do not send these heartbeats Discord will
// disconnect the websocket connection after a few seconds.
func (s *Session) VoiceHeartbeat(i time.Duration) {

	ticker := time.NewTicker(i * time.Millisecond)
	for {
		err := s.VwsConn.WriteJSON(voiceHeartbeatOp{3, int(time.Now().Unix())})
		if err != nil {
			s.VoiceReady = false
			fmt.Println(err)
			return // TODO LOG ERROR
		}

		s.VoiceReady = true
		<-ticker.C
	}
}
<|endoftext|>


<commit_before>package cli

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	fp "path/filepath"
	"strings"

	cmds "github.com/jbenet/go-ipfs/commands"
	u "github.com/jbenet/go-ipfs/util"
)

// ErrInvalidSubcmd signals when the parse error is not found
var ErrInvalidSubcmd = errors.New("subcommand not found")

// Parse parses the input commandline string (cmd, flags, and args).
// returns the corresponding command Request object.
func Parse(input []string, stdin *os.File, root *cmds.Command) (cmds.Request, *cmds.Command, []string, error) {
	path, input, cmd := parsePath(input, root)
	if len(path) == 0 {
		return nil, nil, path, ErrInvalidSubcmd
	}

	opts, stringVals, err := parseOptions(input)
	if err != nil {
		return nil, cmd, path, err
	}

	optDefs, err := root.GetOptions(path)
	if err != nil {
		return nil, cmd, path, err
	}

	// check to make sure there aren't any undefined options
	for k := range opts {
		if _, found := optDefs[k]; !found {
			err = fmt.Errorf("Unrecognized option: -%s", k)
			return nil, cmd, path, err
		}
	}

	req, err := cmds.NewRequest(path, opts, nil, nil, cmd, optDefs)
	if err != nil {
		return nil, cmd, path, err
	}

	// if -r is provided, and it is associated with the package builtin
	// recursive path option, allow recursive file paths
	recursiveOpt := req.Option(cmds.RecShort)
	recursive := false
	if recursiveOpt != nil && recursiveOpt.Definition() == cmds.OptionRecursivePath {
		recursive, _, err = recursiveOpt.Bool()
		if err != nil {
			return nil, nil, nil, u.ErrCast()
		}
	}

	stringArgs, fileArgs, err := parseArgs(stringVals, stdin, cmd.Arguments, recursive)
	if err != nil {
		return nil, cmd, path, err
	}
	req.SetArguments(stringArgs)

	file := &cmds.SliceFile{"", fileArgs}
	req.SetFiles(file)

	err = cmd.CheckArguments(req)
	if err != nil {
		return req, cmd, path, err
	}

	return req, cmd, path, nil
}

// parsePath separates the command path and the opts and args from a command string
// returns command path slice, rest slice, and the corresponding *cmd.Command
func parsePath(input []string, root *cmds.Command) ([]string, []string, *cmds.Command) {
	cmd := root
	path := make([]string, 0, len(input))
	input2 := make([]string, 0, len(input))

	for i, blob := range input {
		if strings.HasPrefix(blob, "-") {
			input2 = append(input2, blob)
			continue
		}

		sub := cmd.Subcommand(blob)
		if sub == nil {
			input2 = append(input2, input[i:]...)
			break
		}
		cmd = sub
		path = append(path, blob)
	}

	return path, input2, cmd
}

// parseOptions parses the raw string values of the given options
// returns the parsed options as strings, along with the CLI args
func parseOptions(input []string) (map[string]interface{}, []string, error) {
	opts := make(map[string]interface{})
	args := []string{}

	for i := 0; i < len(input); i++ {
		blob := input[i]

		if strings.HasPrefix(blob, "-") {
			name := blob[1:]
			value := ""

			// support single and double dash
			if strings.HasPrefix(name, "-") {
				name = name[1:]
			}

			if strings.Contains(name, "=") {
				split := strings.SplitN(name, "=", 2)
				name = split[0]
				value = split[1]
			}

			if _, ok := opts[name]; ok {
				return nil, nil, fmt.Errorf("Duplicate values for option '%s'", name)
			}

			opts[name] = value

		} else {
			args = append(args, blob)
		}
	}

	return opts, args, nil
}

func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursive bool) ([]string, []cmds.File, error) {
	// check if stdin is coming from terminal or is being piped in
	if stdin != nil {
		if term, err := isTerminal(stdin); err != nil {
			return nil, nil, err
		} else if term {
			stdin = nil // set to nil so we ignore it
		}
	}

	// count required argument definitions
	numRequired := 0
	for _, argDef := range argDefs {
		if argDef.Required {
			numRequired++
		}
	}

	// count number of values provided by user
	numInputs := len(inputs)
	if stdin != nil {
		numInputs += 1
	}

	// if we have more arg values provided than argument definitions,
	// and the last arg definition is not variadic (or there are no definitions), return an error
	notVariadic := len(argDefs) == 0 || !argDefs[len(argDefs)-1].Variadic
	if notVariadic && numInputs > len(argDefs) {
		return nil, nil, fmt.Errorf("Expected %v arguments, got %v", len(argDefs), numInputs)
	}

	stringArgs := make([]string, 0, numInputs)
	fileArgs := make([]cmds.File, 0, numInputs)

	argDefIndex := 0 // the index of the current argument definition
	for i := 0; i < numInputs; i++ {
		argDef := getArgDef(argDefIndex, argDefs)

		// skip optional argument definitions if there aren't sufficient remaining inputs
		if numInputs-i <= numRequired && !argDef.Required {
			continue
		} else if argDef.Required {
			numRequired--
		}

		var err error
		if argDef.Type == cmds.ArgString {
			if stdin == nil {
				// add string values
				stringArgs, inputs = appendString(stringArgs, inputs)

			} else if argDef.SupportsStdin {
				// if we have a stdin, read it in and use the data as a string value
				stringArgs, stdin, err = appendStdinAsString(stringArgs, stdin)
				if err != nil {
					return nil, nil, err
				}
			}

		} else if argDef.Type == cmds.ArgFile {
			if stdin == nil {
				// treat stringArg values as file paths
				fileArgs, inputs, err = appendFile(fileArgs, inputs, argDef, recursive)
				if err != nil {
					return nil, nil, err
				}

			} else if argDef.SupportsStdin {
				// if we have a stdin, create a file from it
				fileArgs, stdin = appendStdinAsFile(fileArgs, stdin)
			}
		}

		argDefIndex++
	}

	// check to make sure we didn't miss any required arguments
	if len(argDefs) > argDefIndex {
		for _, argDef := range argDefs[argDefIndex:] {
			if argDef.Required {
				return nil, nil, fmt.Errorf("Argument '%s' is required", argDef.Name)
			}
		}
	}

	return stringArgs, fileArgs, nil
}

func getArgDef(i int, argDefs []cmds.Argument) *cmds.Argument {
	if i < len(argDefs) {
		// get the argument definition (usually just argDefs[i])
		return &argDefs[i]

	} else if len(argDefs) > 0 {
		// but if i > len(argDefs) we use the last argument definition)
		return &argDefs[len(argDefs)-1]
	}

	// only happens if there aren't any definitions
	return nil
}

func appendString(args, inputs []string) ([]string, []string) {
	return append(args, inputs[0]), inputs[1:]
}

func appendStdinAsString(args []string, stdin *os.File) ([]string, *os.File, error) {
	var buf bytes.Buffer

	_, err := buf.ReadFrom(stdin)
	if err != nil {
		return nil, nil, err
	}

	return append(args, buf.String()), nil, nil
}

func appendFile(args []cmds.File, inputs []string, argDef *cmds.Argument, recursive bool) ([]cmds.File, []string, error) {
	path := inputs[0]

	file, err := os.Open(path)
	if err != nil {
		return nil, nil, err
	}

	stat, err := file.Stat()
	if err != nil {
		return nil, nil, err
	}

	if stat.IsDir() {
		if !argDef.Recursive {
			err = fmt.Errorf("Invalid path '%s', argument '%s' does not support directories",
				path, argDef.Name)
			return nil, nil, err
		}
		if !recursive {
			err = fmt.Errorf("'%s' is a directory, use the '-%s' flag to specify directories",
				path, cmds.RecShort)
			return nil, nil, err
		}
	}

	arg, err := openPath(file, path)
	if err != nil {
		return nil, nil, err
	}

	return append(args, arg), inputs[1:], nil
}

func appendStdinAsFile(args []cmds.File, stdin *os.File) ([]cmds.File, *os.File) {
	arg := &cmds.ReaderFile{"", stdin}
	return append(args, arg), nil
}

// recursively get file or directory contents as a cmds.File
func openPath(file *os.File, path string) (cmds.File, error) {
	stat, err := file.Stat()
	if err != nil {
		return nil, err
	}

	// for non-directories, return a ReaderFile
	if !stat.IsDir() {
		return &cmds.ReaderFile{path, file}, nil
	}

	// for directories, recursively iterate though children then return as a SliceFile
	contents, err := file.Readdir(0)
	if err != nil {
		return nil, err
	}

	files := make([]cmds.File, 0, len(contents))

	for _, child := range contents {
		childPath := fp.Join(path, child.Name())
		childFile, err := os.Open(childPath)
		if err != nil {
			return nil, err
		}

		f, err := openPath(childFile, childPath)
		if err != nil {
			return nil, err
		}

		files = append(files, f)
	}

	return &cmds.SliceFile{path, files}, nil
}

// isTerminal returns true if stdin is a Stdin pipe (e.g. `cat file | ipfs`),
// and false otherwise (e.g. nothing is being piped in, so stdin is
// coming from the terminal)
func isTerminal(stdin *os.File) (bool, error) {
	stat, err := stdin.Stat()
	if err != nil {
		return false, err
	}

	// if stdin is a CharDevice, return true
	return ((stat.Mode() & os.ModeCharDevice) != 0), nil
}
<commit_msg>commands/cli: Ignore stdin on Windows<commit_after>package cli

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	fp "path/filepath"
	"runtime"
	"strings"

	cmds "github.com/jbenet/go-ipfs/commands"
	u "github.com/jbenet/go-ipfs/util"
)

// ErrInvalidSubcmd signals when the parse error is not found
var ErrInvalidSubcmd = errors.New("subcommand not found")

// Parse parses the input commandline string (cmd, flags, and args).
// returns the corresponding command Request object.
func Parse(input []string, stdin *os.File, root *cmds.Command) (cmds.Request, *cmds.Command, []string, error) {
	path, input, cmd := parsePath(input, root)
	if len(path) == 0 {
		return nil, nil, path, ErrInvalidSubcmd
	}

	opts, stringVals, err := parseOptions(input)
	if err != nil {
		return nil, cmd, path, err
	}

	optDefs, err := root.GetOptions(path)
	if err != nil {
		return nil, cmd, path, err
	}

	// check to make sure there aren't any undefined options
	for k := range opts {
		if _, found := optDefs[k]; !found {
			err = fmt.Errorf("Unrecognized option: -%s", k)
			return nil, cmd, path, err
		}
	}

	req, err := cmds.NewRequest(path, opts, nil, nil, cmd, optDefs)
	if err != nil {
		return nil, cmd, path, err
	}

	// if -r is provided, and it is associated with the package builtin
	// recursive path option, allow recursive file paths
	recursiveOpt := req.Option(cmds.RecShort)
	recursive := false
	if recursiveOpt != nil && recursiveOpt.Definition() == cmds.OptionRecursivePath {
		recursive, _, err = recursiveOpt.Bool()
		if err != nil {
			return nil, nil, nil, u.ErrCast()
		}
	}

	stringArgs, fileArgs, err := parseArgs(stringVals, stdin, cmd.Arguments, recursive)
	if err != nil {
		return nil, cmd, path, err
	}
	req.SetArguments(stringArgs)

	file := &cmds.SliceFile{"", fileArgs}
	req.SetFiles(file)

	err = cmd.CheckArguments(req)
	if err != nil {
		return req, cmd, path, err
	}

	return req, cmd, path, nil
}

// parsePath separates the command path and the opts and args from a command string
// returns command path slice, rest slice, and the corresponding *cmd.Command
func parsePath(input []string, root *cmds.Command) ([]string, []string, *cmds.Command) {
	cmd := root
	path := make([]string, 0, len(input))
	input2 := make([]string, 0, len(input))

	for i, blob := range input {
		if strings.HasPrefix(blob, "-") {
			input2 = append(input2, blob)
			continue
		}

		sub := cmd.Subcommand(blob)
		if sub == nil {
			input2 = append(input2, input[i:]...)
			break
		}
		cmd = sub
		path = append(path, blob)
	}

	return path, input2, cmd
}

// parseOptions parses the raw string values of the given options
// returns the parsed options as strings, along with the CLI args
func parseOptions(input []string) (map[string]interface{}, []string, error) {
	opts := make(map[string]interface{})
	args := []string{}

	for i := 0; i < len(input); i++ {
		blob := input[i]

		if strings.HasPrefix(blob, "-") {
			name := blob[1:]
			value := ""

			// support single and double dash
			if strings.HasPrefix(name, "-") {
				name = name[1:]
			}

			if strings.Contains(name, "=") {
				split := strings.SplitN(name, "=", 2)
				name = split[0]
				value = split[1]
			}

			if _, ok := opts[name]; ok {
				return nil, nil, fmt.Errorf("Duplicate values for option '%s'", name)
			}

			opts[name] = value

		} else {
			args = append(args, blob)
		}
	}

	return opts, args, nil
}

func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursive bool) ([]string, []cmds.File, error) {
	// ignore stdin on Windows
	if runtime.GOOS == "windows" {
		stdin = nil
	}

	// check if stdin is coming from terminal or is being piped in
	if stdin != nil {
		if term, err := isTerminal(stdin); err != nil {
			return nil, nil, err
		} else if term {
			stdin = nil // set to nil so we ignore it
		}
	}

	// count required argument definitions
	numRequired := 0
	for _, argDef := range argDefs {
		if argDef.Required {
			numRequired++
		}
	}

	// count number of values provided by user
	numInputs := len(inputs)
	if stdin != nil {
		numInputs += 1
	}

	// if we have more arg values provided than argument definitions,
	// and the last arg definition is not variadic (or there are no definitions), return an error
	notVariadic := len(argDefs) == 0 || !argDefs[len(argDefs)-1].Variadic
	if notVariadic && numInputs > len(argDefs) {
		return nil, nil, fmt.Errorf("Expected %v arguments, got %v", len(argDefs), numInputs)
	}

	stringArgs := make([]string, 0, numInputs)
	fileArgs := make([]cmds.File, 0, numInputs)

	argDefIndex := 0 // the index of the current argument definition
	for i := 0; i < numInputs; i++ {
		argDef := getArgDef(argDefIndex, argDefs)

		// skip optional argument definitions if there aren't sufficient remaining inputs
		if numInputs-i <= numRequired && !argDef.Required {
			continue
		} else if argDef.Required {
			numRequired--
		}

		var err error
		if argDef.Type == cmds.ArgString {
			if stdin == nil {
				// add string values
				stringArgs, inputs = appendString(stringArgs, inputs)

			} else if argDef.SupportsStdin {
				// if we have a stdin, read it in and use the data as a string value
				stringArgs, stdin, err = appendStdinAsString(stringArgs, stdin)
				if err != nil {
					return nil, nil, err
				}
			}

		} else if argDef.Type == cmds.ArgFile {
			if stdin == nil {
				// treat stringArg values as file paths
				fileArgs, inputs, err = appendFile(fileArgs, inputs, argDef, recursive)
				if err != nil {
					return nil, nil, err
				}

			} else if argDef.SupportsStdin {
				// if we have a stdin, create a file from it
				fileArgs, stdin = appendStdinAsFile(fileArgs, stdin)
			}
		}

		argDefIndex++
	}

	// check to make sure we didn't miss any required arguments
	if len(argDefs) > argDefIndex {
		for _, argDef := range argDefs[argDefIndex:] {
			if argDef.Required {
				return nil, nil, fmt.Errorf("Argument '%s' is required", argDef.Name)
			}
		}
	}

	return stringArgs, fileArgs, nil
}

func getArgDef(i int, argDefs []cmds.Argument) *cmds.Argument {
	if i < len(argDefs) {
		// get the argument definition (usually just argDefs[i])
		return &argDefs[i]

	} else if len(argDefs) > 0 {
		// but if i > len(argDefs) we use the last argument definition)
		return &argDefs[len(argDefs)-1]
	}

	// only happens if there aren't any definitions
	return nil
}

func appendString(args, inputs []string) ([]string, []string) {
	return append(args, inputs[0]), inputs[1:]
}

func appendStdinAsString(args []string, stdin *os.File) ([]string, *os.File, error) {
	var buf bytes.Buffer

	_, err := buf.ReadFrom(stdin)
	if err != nil {
		return nil, nil, err
	}

	return append(args, buf.String()), nil, nil
}

func appendFile(args []cmds.File, inputs []string, argDef *cmds.Argument, recursive bool) ([]cmds.File, []string, error) {
	path := inputs[0]

	file, err := os.Open(path)
	if err != nil {
		return nil, nil, err
	}

	stat, err := file.Stat()
	if err != nil {
		return nil, nil, err
	}

	if stat.IsDir() {
		if !argDef.Recursive {
			err = fmt.Errorf("Invalid path '%s', argument '%s' does not support directories",
				path, argDef.Name)
			return nil, nil, err
		}
		if !recursive {
			err = fmt.Errorf("'%s' is a directory, use the '-%s' flag to specify directories",
				path, cmds.RecShort)
			return nil, nil, err
		}
	}

	arg, err := openPath(file, path)
	if err != nil {
		return nil, nil, err
	}

	return append(args, arg), inputs[1:], nil
}

func appendStdinAsFile(args []cmds.File, stdin *os.File) ([]cmds.File, *os.File) {
	arg := &cmds.ReaderFile{"", stdin}
	return append(args, arg), nil
}

// recursively get file or directory contents as a cmds.File
func openPath(file *os.File, path string) (cmds.File, error) {
	stat, err := file.Stat()
	if err != nil {
		return nil, err
	}

	// for non-directories, return a ReaderFile
	if !stat.IsDir() {
		return &cmds.ReaderFile{path, file}, nil
	}

	// for directories, recursively iterate though children then return as a SliceFile
	contents, err := file.Readdir(0)
	if err != nil {
		return nil, err
	}

	files := make([]cmds.File, 0, len(contents))

	for _, child := range contents {
		childPath := fp.Join(path, child.Name())
		childFile, err := os.Open(childPath)
		if err != nil {
			return nil, err
		}

		f, err := openPath(childFile, childPath)
		if err != nil {
			return nil, err
		}

		files = append(files, f)
	}

	return &cmds.SliceFile{path, files}, nil
}

// isTerminal returns true if stdin is a Stdin pipe (e.g. `cat file | ipfs`),
// and false otherwise (e.g. nothing is being piped in, so stdin is
// coming from the terminal)
func isTerminal(stdin *os.File) (bool, error) {
	stat, err := stdin.Stat()
	if err != nil {
		return false, err
	}

	// if stdin is a CharDevice, return true
	return ((stat.Mode() & os.ModeCharDevice) != 0), nil
}
<|endoftext|>


<commit_before><commit_msg>gofmt<commit_after><|endoftext|>


<commit_before>/*
Copyright 2022 The Kubernetes 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 app

import (
	"net/http"
	"regexp"
	"strings"

	"k8s.io/klog/v2"

	"sigs.k8s.io/oci-proxy/pkg/net/cidrs/aws"
)

const (
	// hard coded for now
	infoURL    = "https://github.com/kubernetes/k8s.io/tree/main/registry.k8s.io"
	privacyURL = "https://www.linuxfoundation.org/privacy-policy/"
)

// MakeHandler returns the root archeio HTTP handler
//
// upstream registry should be the url to the primary registry
// archeio is fronting.
//
// Exact behavior should be documented in docs/request-handling.md
func MakeHandler(upstreamRegistry string) http.Handler {
	blobs := newCachedBlobChecker()
	doV2 := makeV2Handler(upstreamRegistry, blobs)
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// all valid registry requests should be at /v2/
		// v1 API is super old and not supported by GCR anymore.
		path := r.URL.Path
		switch {
		case strings.HasPrefix(path, "/v2"):
			doV2(w, r)
		case path == "/":
			http.Redirect(w, r, infoURL, http.StatusTemporaryRedirect)
		case strings.HasPrefix(path, "/privacy"):
			http.Redirect(w, r, privacyURL, http.StatusTemporaryRedirect)
		default:
			klog.V(2).InfoS("unknown request", "path", path)
			http.NotFound(w, r)
		}
	})
}

func makeV2Handler(upstreamRegistry string, blobs blobChecker) func(w http.ResponseWriter, r *http.Request) {
	// matches blob requests, captures the requested blob hash
	reBlob := regexp.MustCompile("^/v2/.*/blobs/sha256:([0-9a-f]{64})$")
	// initialize map of clientIP to AWS region
	regionMapper := aws.NewAWSRegionMapper()
	// capture these in a http handler lambda
	return func(w http.ResponseWriter, r *http.Request) {
		path := r.URL.Path

		// we only care about publicly readable GCR as the backing registry
		// or publicly readable blob storage
		//
		// when the client attempts to probe the API for auth, we always return
		// 200 OK so it will not attempt to request an auth token
		//
		// this makes it easier to redirect to backends with different
		// repo namespacing without worrying about incorrect token scope
		//
		// it turns out publicly readable GCR repos do not actually care about
		// the presence of a token for any API calls, despite the /v2/ API call
		// returning 401, prompting token auth
		if path == "/v2/" || path == "/v2" {
			klog.V(2).InfoS("serving 200 OK for /v2/ check", "path", path)
			// NOTE: OCI does not require this, but the docker v2 spec include it, and GCR sets this
			// Docker distribution v2 clients may fallback to an older version if this is not set.
			w.Header().Set("Docker-Distribution-Api-Version", "registry/2.0")
			w.WriteHeader(http.StatusOK)
			return
		}

		// check if blob request
		matches := reBlob.FindStringSubmatch(path)
		if len(matches) != 2 {
			// not a blob request so forward it to the main upstream registry
			klog.V(2).InfoS("redirecting non-blob request to upstream registry", "path", path)
			http.Redirect(w, r, upstreamRegistry+path, http.StatusTemporaryRedirect)
			return
		}

		// for blob requests, check the client IP and determine the best backend
		clientIP, err := getClientIP(r)
		if err != nil {
			// this should not happen
			klog.ErrorS(err, "failed to get client IP")
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		// check if client is known to be coming from an AWS region
		awsRegion, ipIsKnown := regionMapper.GetIP(clientIP)
		if !ipIsKnown {
			// no region match, redirect to main upstream registry
			klog.V(2).InfoS("redirecting blob request to upstream registry", "path", path)
			http.Redirect(w, r, upstreamRegistry+path, http.StatusTemporaryRedirect)
			return
		}

		// check if blob is available in our S3 bucket for the region
		bucketURL := awsRegionToS3URL(awsRegion)
		hash := matches[1]
		// this matches GCR's GCS layout, which we will use for other buckets
		blobURL := bucketURL + "/containers/images/sha256%3A" + hash
		if blobs.BlobExists(blobURL, bucketURL, hash) {
			// blob known to be available in S3, redirect client there
			klog.V(2).InfoS("redirecting blob request to S3", "path", path)
			http.Redirect(w, r, blobURL, http.StatusTemporaryRedirect)
			return
		}

		// fall back to redirect to upstream
		klog.V(2).InfoS("redirecting blob request to upstream registry", "path", path)
		http.Redirect(w, r, upstreamRegistry+path, http.StatusTemporaryRedirect)
	}
}
<commit_msg>update the / redirection<commit_after>/*
Copyright 2022 The Kubernetes 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 app

import (
	"net/http"
	"regexp"
	"strings"

	"k8s.io/klog/v2"

	"sigs.k8s.io/oci-proxy/pkg/net/cidrs/aws"
)

const (
	// hard coded for now
	infoURL = "https://github.com/kubernetes/k8s.io/wiki/New-Registry-url-for-Kubernetes-(registry.k8s.io)"
	privacyURL = "https://www.linuxfoundation.org/privacy-policy/"
)

// MakeHandler returns the root archeio HTTP handler
//
// upstream registry should be the url to the primary registry
// archeio is fronting.
//
// Exact behavior should be documented in docs/request-handling.md
func MakeHandler(upstreamRegistry string) http.Handler {
	blobs := newCachedBlobChecker()
	doV2 := makeV2Handler(upstreamRegistry, blobs)
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// all valid registry requests should be at /v2/
		// v1 API is super old and not supported by GCR anymore.
		path := r.URL.Path
		switch {
		case strings.HasPrefix(path, "/v2"):
			doV2(w, r)
		case path == "/":
			http.Redirect(w, r, infoURL, http.StatusTemporaryRedirect)
		case strings.HasPrefix(path, "/privacy"):
			http.Redirect(w, r, privacyURL, http.StatusTemporaryRedirect)
		default:
			klog.V(2).InfoS("unknown request", "path", path)
			http.NotFound(w, r)
		}
	})
}

func makeV2Handler(upstreamRegistry string, blobs blobChecker) func(w http.ResponseWriter, r *http.Request) {
	// matches blob requests, captures the requested blob hash
	reBlob := regexp.MustCompile("^/v2/.*/blobs/sha256:([0-9a-f]{64})$")
	// initialize map of clientIP to AWS region
	regionMapper := aws.NewAWSRegionMapper()
	// capture these in a http handler lambda
	return func(w http.ResponseWriter, r *http.Request) {
		path := r.URL.Path

		// we only care about publicly readable GCR as the backing registry
		// or publicly readable blob storage
		//
		// when the client attempts to probe the API for auth, we always return
		// 200 OK so it will not attempt to request an auth token
		//
		// this makes it easier to redirect to backends with different
		// repo namespacing without worrying about incorrect token scope
		//
		// it turns out publicly readable GCR repos do not actually care about
		// the presence of a token for any API calls, despite the /v2/ API call
		// returning 401, prompting token auth
		if path == "/v2/" || path == "/v2" {
			klog.V(2).InfoS("serving 200 OK for /v2/ check", "path", path)
			// NOTE: OCI does not require this, but the docker v2 spec include it, and GCR sets this
			// Docker distribution v2 clients may fallback to an older version if this is not set.
			w.Header().Set("Docker-Distribution-Api-Version", "registry/2.0")
			w.WriteHeader(http.StatusOK)
			return
		}

		// check if blob request
		matches := reBlob.FindStringSubmatch(path)
		if len(matches) != 2 {
			// not a blob request so forward it to the main upstream registry
			klog.V(2).InfoS("redirecting non-blob request to upstream registry", "path", path)
			http.Redirect(w, r, upstreamRegistry+path, http.StatusTemporaryRedirect)
			return
		}

		// for blob requests, check the client IP and determine the best backend
		clientIP, err := getClientIP(r)
		if err != nil {
			// this should not happen
			klog.ErrorS(err, "failed to get client IP")
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		// check if client is known to be coming from an AWS region
		awsRegion, ipIsKnown := regionMapper.GetIP(clientIP)
		if !ipIsKnown {
			// no region match, redirect to main upstream registry
			klog.V(2).InfoS("redirecting blob request to upstream registry", "path", path)
			http.Redirect(w, r, upstreamRegistry+path, http.StatusTemporaryRedirect)
			return
		}

		// check if blob is available in our S3 bucket for the region
		bucketURL := awsRegionToS3URL(awsRegion)
		hash := matches[1]
		// this matches GCR's GCS layout, which we will use for other buckets
		blobURL := bucketURL + "/containers/images/sha256%3A" + hash
		if blobs.BlobExists(blobURL, bucketURL, hash) {
			// blob known to be available in S3, redirect client there
			klog.V(2).InfoS("redirecting blob request to S3", "path", path)
			http.Redirect(w, r, blobURL, http.StatusTemporaryRedirect)
			return
		}

		// fall back to redirect to upstream
		klog.V(2).InfoS("redirecting blob request to upstream registry", "path", path)
		http.Redirect(w, r, upstreamRegistry+path, http.StatusTemporaryRedirect)
	}
}
<|endoftext|>