<commit_before>package router

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"log"
	"sync"
	"time"
)

const GossipInterval = 30 * time.Second

type GossipData interface {
	Encode() []byte
	Merge(GossipData)
}

type Gossip interface {
	// specific message from one peer to another
	// intermediate peers relay it using unicast topology.
	GossipUnicast(dstPeerName PeerName, msg []byte) error
	// send gossip to every peer, relayed using broadcast topology.
	GossipBroadcast(update GossipData) error
}

type Gossiper interface {
	OnGossipUnicast(sender PeerName, msg []byte) error
	// merge received data into state and return a representation of
	// the received data, for further propagation
	OnGossipBroadcast(update []byte) (GossipData, error)
	// return state of everything we know; gets called periodically
	Gossip() GossipData
	// merge received data into state and return "everything new I've
	// just learnt", or nil if nothing in the received data was new
	OnGossip(update []byte) (GossipData, error)
}

// Accumulates GossipData that needs to be sent to one destination,
// and sends it when possible.
type GossipSender struct {
	send    func(GossipData)
	cell    chan GossipData
	flushch chan chan struct{}
}

func NewGossipSender(send func(GossipData)) *GossipSender {
	return &GossipSender{send: send}
}

func (sender *GossipSender) Start() {
	sender.cell = make(chan GossipData, 1)
	sender.flushch = make(chan chan struct{})
	go sender.run()
}

func (sender *GossipSender) run() {
	for {
		select {
		case pending := <-sender.cell:
			if pending == nil { // receive zero value when chan is closed
				return
			}
			sender.send(pending)
		case ch := <-sender.flushch:
			// ensure anything pending is sent, then close the channel we were given
			select {
			case pending := <-sender.cell:
				if pending != nil {
					sender.send(pending)
				}
			default:
			}
			close(ch)
		}
	}
}

func (sender *GossipSender) Send(data GossipData) {
	// NB: this must not be invoked concurrently
	select {
	case pending := <-sender.cell:
		pending.Merge(data)
		sender.cell <- pending
	default:
		sender.cell <- data
	}
}

func (sender *GossipSender) Stop() {
	close(sender.cell)
}

type connectionSenders map[Connection]*GossipSender
type peerSenders map[PeerName]*GossipSender

type GossipChannel struct {
	sync.Mutex
	ourself      *LocalPeer
	routes       *Routes
	name         string
	gossiper     Gossiper
	senders      connectionSenders
	broadcasters peerSenders
}

type GossipChannels map[string]*GossipChannel

func (router *Router) NewGossip(channelName string, g Gossiper) Gossip {
	channel := &GossipChannel{
		ourself:      router.Ourself,
		routes:       router.Routes,
		name:         channelName,
		gossiper:     g,
		senders:      make(connectionSenders),
		broadcasters: make(peerSenders)}
	router.GossipChannels[channelName] = channel
	return channel
}

func (router *Router) SendAllGossip() {
	for _, channel := range router.GossipChannels {
		if gossip := channel.gossiper.Gossip(); gossip != nil {
			channel.Send(router.Ourself.Name, gossip)
		}
	}
}

func (router *Router) SendAllGossipDown(conn Connection) {
	for _, channel := range router.GossipChannels {
		if gossip := channel.gossiper.Gossip(); gossip != nil {
			channel.SendDown(conn, channel.gossiper.Gossip())
		}
	}
}

func (router *Router) handleGossip(tag ProtocolTag, payload []byte) error {
	decoder := gob.NewDecoder(bytes.NewReader(payload))
	var channelName string
	if err := decoder.Decode(&channelName); err != nil {
		return err
	}
	channel, found := router.GossipChannels[channelName]
	if !found {
		return fmt.Errorf("[gossip] received unknown channel with name %s", channelName)
	}
	var srcName PeerName
	if err := decoder.Decode(&srcName); err != nil {
		return err
	}
	switch tag {
	case ProtocolGossipUnicast:
		return channel.deliverUnicast(srcName, payload, decoder)
	case ProtocolGossipBroadcast:
		return channel.deliverBroadcast(srcName, payload, decoder)
	case ProtocolGossip:
		return channel.deliver(srcName, payload, decoder)
	}
	return nil
}

func (c *GossipChannel) deliverUnicast(srcName PeerName, origPayload []byte, dec *gob.Decoder) error {
	var destName PeerName
	if err := dec.Decode(&destName); err != nil {
		return err
	}
	if c.ourself.Name != destName {
		return c.relayUnicast(destName, origPayload)
	}
	var payload []byte
	if err := dec.Decode(&payload); err != nil {
		return err
	}
	return c.gossiper.OnGossipUnicast(srcName, payload)
}

func (c *GossipChannel) deliverBroadcast(srcName PeerName, _ []byte, dec *gob.Decoder) error {
	var payload []byte
	if err := dec.Decode(&payload); err != nil {
		return err
	}
	data, err := c.gossiper.OnGossipBroadcast(payload)
	if err != nil || data == nil {
		return err
	}
	return c.relayBroadcast(srcName, data)
}

func (c *GossipChannel) deliver(srcName PeerName, _ []byte, dec *gob.Decoder) error {
	var payload []byte
	if err := dec.Decode(&payload); err != nil {
		return err
	}
	if data, err := c.gossiper.OnGossip(payload); err != nil {
		return err
	} else if data != nil {
		c.Send(srcName, data)
	}
	return nil
}

func (c *GossipChannel) Send(srcName PeerName, data GossipData) {
	// do this outside the lock below so we avoid lock nesting
	c.routes.EnsureRecalculated()
	selectedConnections := make(ConnectionSet)
	for name := range c.routes.RandomNeighbours(srcName) {
		if conn, found := c.ourself.ConnectionTo(name); found {
			selectedConnections[conn] = void
		}
	}
	if len(selectedConnections) == 0 {
		return
	}
	connections := c.ourself.Connections()
	c.Lock()
	defer c.Unlock()
	// GC - randomly (courtesy of go's map iterator) pick some
	// existing entries and stop&remove them if the associated
	// connection is no longer active.  We stop as soon as we
	// encounter a valid entry; the idea being that when there is
	// little or no garbage then this executes close to O(1)[1],
	// whereas when there is lots of garbage we remove it quickly.
	//
	// [1] TODO Unfortunately, due to the desire to avoid nested
	// locks, instead of simply invoking Peer.ConnectionTo(name)
	// below, we have that Peer.Connections() invocation above. That
	// is O(n_our_connections) at best.
	for conn, sender := range c.senders {
		if _, found := connections[conn]; !found {
			delete(c.senders, conn)
			sender.Stop()
		} else {
			break
		}
	}
	for conn := range selectedConnections {
		c.sendDown(conn, data)
	}
}

func (c *GossipChannel) SendDown(conn Connection, data GossipData) {
	c.Lock()
	c.sendDown(conn, data)
	c.Unlock()
}

func (c *GossipChannel) sendDown(conn Connection, data GossipData) {
	sender, found := c.senders[conn]
	if !found {
		sender = NewGossipSender(func(pending GossipData) {
			protocolMsg := ProtocolMsg{ProtocolGossip, GobEncode(c.name, c.ourself.Name, pending.Encode())}
			conn.(ProtocolSender).SendProtocolMsg(protocolMsg)
		})
		c.senders[conn] = sender
		sender.Start()
	}
	sender.Send(data)
}

func (c *GossipChannel) GossipUnicast(dstPeerName PeerName, msg []byte) error {
	return c.relayUnicast(dstPeerName, GobEncode(c.name, c.ourself.Name, dstPeerName, msg))
}

func (c *GossipChannel) GossipBroadcast(update GossipData) error {
	return c.relayBroadcast(c.ourself.Name, update)
}

func (c *GossipChannel) relayUnicast(dstPeerName PeerName, buf []byte) error {
	if relayPeerName, found := c.routes.UnicastAll(dstPeerName); !found {
		c.log("unknown relay destination:", dstPeerName)
	} else if conn, found := c.ourself.ConnectionTo(relayPeerName); !found {
		c.log("unable to find connection to relay peer", relayPeerName)
	} else {
		conn.(ProtocolSender).SendProtocolMsg(ProtocolMsg{ProtocolGossipUnicast, buf})
	}
	return nil
}

func (c *GossipChannel) relayBroadcast(srcName PeerName, update GossipData) error {
	names := c.routes.PeerNames() // do this outside the lock so they don't nest
	c.Lock()
	defer c.Unlock()
	// GC - randomly (courtesy of go's map iterator) pick some
	// existing broadcasters and stop&remove them if their source peer
	// is unknown. We stop as soon as we encounter a valid entry; the
	// idea being that when there is little or no garbage then this
	// executes close to O(1)[1], whereas when there is lots of
	// garbage we remove it quickly.
	//
	// [1] TODO Unfortunately, due to the desire to avoid nested
	// locks, instead of simply invoking Peers.Fetch(name) below, we
	// have that Peers.Names() invocation above. That is O(n_peers) at
	// best.
	for name, broadcaster := range c.broadcasters {
		if _, found := names[name]; !found {
			delete(c.broadcasters, name)
			broadcaster.Stop()
		} else {
			break
		}
	}
	broadcaster, found := c.broadcasters[srcName]
	if !found {
		broadcaster = NewGossipSender(func(pending GossipData) { c.sendBroadcast(srcName, pending) })
		c.broadcasters[srcName] = broadcaster
		broadcaster.Start()
	}
	broadcaster.Send(update)
	return nil
}

func (c *GossipChannel) sendBroadcast(srcName PeerName, update GossipData) {
	c.routes.EnsureRecalculated()
	nextHops := c.routes.BroadcastAll(srcName)
	if len(nextHops) == 0 {
		return
	}
	protocolMsg := ProtocolMsg{ProtocolGossipBroadcast, GobEncode(c.name, srcName, update.Encode())}
	// FIXME a single blocked connection can stall us
	for _, conn := range c.ourself.ConnectionsTo(nextHops) {
		conn.(ProtocolSender).SendProtocolMsg(protocolMsg)
	}
}

func (c *GossipChannel) log(args ...interface{}) {
	log.Println(append(append([]interface{}{}, "[gossip "+c.name+"]:"), args...)...)
}

// for testing

func (router *Router) sendPendingGossip() {
	for _, channel := range router.GossipChannels {
		channel.Lock()
		for _, sender := range channel.senders {
			ch := make(chan struct{})
			sender.flushch <- ch
			<-ch
		}
		for _, sender := range channel.broadcasters {
			ch := make(chan struct{})
			sender.flushch <- ch
			<-ch
		}
		channel.Unlock()
	}
}
<commit_msg>Factor out GossipSender.flush()<commit_after>package router

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"log"
	"sync"
	"time"
)

const GossipInterval = 30 * time.Second

type GossipData interface {
	Encode() []byte
	Merge(GossipData)
}

type Gossip interface {
	// specific message from one peer to another
	// intermediate peers relay it using unicast topology.
	GossipUnicast(dstPeerName PeerName, msg []byte) error
	// send gossip to every peer, relayed using broadcast topology.
	GossipBroadcast(update GossipData) error
}

type Gossiper interface {
	OnGossipUnicast(sender PeerName, msg []byte) error
	// merge received data into state and return a representation of
	// the received data, for further propagation
	OnGossipBroadcast(update []byte) (GossipData, error)
	// return state of everything we know; gets called periodically
	Gossip() GossipData
	// merge received data into state and return "everything new I've
	// just learnt", or nil if nothing in the received data was new
	OnGossip(update []byte) (GossipData, error)
}

// Accumulates GossipData that needs to be sent to one destination,
// and sends it when possible.
type GossipSender struct {
	send    func(GossipData)
	cell    chan GossipData
	flushch chan chan struct{}
}

func NewGossipSender(send func(GossipData)) *GossipSender {
	return &GossipSender{send: send}
}

func (sender *GossipSender) Start() {
	sender.cell = make(chan GossipData, 1)
	sender.flushch = make(chan chan struct{})
	go sender.run()
}

func (sender *GossipSender) run() {
	for {
		select {
		case pending := <-sender.cell:
			if pending == nil { // receive zero value when chan is closed
				return
			}
			sender.send(pending)
		case ch := <-sender.flushch:
			// ensure anything pending is sent, then close the channel we were given
			select {
			case pending := <-sender.cell:
				if pending != nil {
					sender.send(pending)
				}
			default:
			}
			close(ch)
		}
	}
}

func (sender *GossipSender) Send(data GossipData) {
	// NB: this must not be invoked concurrently
	select {
	case pending := <-sender.cell:
		pending.Merge(data)
		sender.cell <- pending
	default:
		sender.cell <- data
	}
}

func (sender *GossipSender) Stop() {
	close(sender.cell)
}

type connectionSenders map[Connection]*GossipSender
type peerSenders map[PeerName]*GossipSender

type GossipChannel struct {
	sync.Mutex
	ourself      *LocalPeer
	routes       *Routes
	name         string
	gossiper     Gossiper
	senders      connectionSenders
	broadcasters peerSenders
}

type GossipChannels map[string]*GossipChannel

func (router *Router) NewGossip(channelName string, g Gossiper) Gossip {
	channel := &GossipChannel{
		ourself:      router.Ourself,
		routes:       router.Routes,
		name:         channelName,
		gossiper:     g,
		senders:      make(connectionSenders),
		broadcasters: make(peerSenders)}
	router.GossipChannels[channelName] = channel
	return channel
}

func (router *Router) SendAllGossip() {
	for _, channel := range router.GossipChannels {
		if gossip := channel.gossiper.Gossip(); gossip != nil {
			channel.Send(router.Ourself.Name, gossip)
		}
	}
}

func (router *Router) SendAllGossipDown(conn Connection) {
	for _, channel := range router.GossipChannels {
		if gossip := channel.gossiper.Gossip(); gossip != nil {
			channel.SendDown(conn, channel.gossiper.Gossip())
		}
	}
}

func (router *Router) handleGossip(tag ProtocolTag, payload []byte) error {
	decoder := gob.NewDecoder(bytes.NewReader(payload))
	var channelName string
	if err := decoder.Decode(&channelName); err != nil {
		return err
	}
	channel, found := router.GossipChannels[channelName]
	if !found {
		return fmt.Errorf("[gossip] received unknown channel with name %s", channelName)
	}
	var srcName PeerName
	if err := decoder.Decode(&srcName); err != nil {
		return err
	}
	switch tag {
	case ProtocolGossipUnicast:
		return channel.deliverUnicast(srcName, payload, decoder)
	case ProtocolGossipBroadcast:
		return channel.deliverBroadcast(srcName, payload, decoder)
	case ProtocolGossip:
		return channel.deliver(srcName, payload, decoder)
	}
	return nil
}

func (c *GossipChannel) deliverUnicast(srcName PeerName, origPayload []byte, dec *gob.Decoder) error {
	var destName PeerName
	if err := dec.Decode(&destName); err != nil {
		return err
	}
	if c.ourself.Name != destName {
		return c.relayUnicast(destName, origPayload)
	}
	var payload []byte
	if err := dec.Decode(&payload); err != nil {
		return err
	}
	return c.gossiper.OnGossipUnicast(srcName, payload)
}

func (c *GossipChannel) deliverBroadcast(srcName PeerName, _ []byte, dec *gob.Decoder) error {
	var payload []byte
	if err := dec.Decode(&payload); err != nil {
		return err
	}
	data, err := c.gossiper.OnGossipBroadcast(payload)
	if err != nil || data == nil {
		return err
	}
	return c.relayBroadcast(srcName, data)
}

func (c *GossipChannel) deliver(srcName PeerName, _ []byte, dec *gob.Decoder) error {
	var payload []byte
	if err := dec.Decode(&payload); err != nil {
		return err
	}
	if data, err := c.gossiper.OnGossip(payload); err != nil {
		return err
	} else if data != nil {
		c.Send(srcName, data)
	}
	return nil
}

func (c *GossipChannel) Send(srcName PeerName, data GossipData) {
	// do this outside the lock below so we avoid lock nesting
	c.routes.EnsureRecalculated()
	selectedConnections := make(ConnectionSet)
	for name := range c.routes.RandomNeighbours(srcName) {
		if conn, found := c.ourself.ConnectionTo(name); found {
			selectedConnections[conn] = void
		}
	}
	if len(selectedConnections) == 0 {
		return
	}
	connections := c.ourself.Connections()
	c.Lock()
	defer c.Unlock()
	// GC - randomly (courtesy of go's map iterator) pick some
	// existing entries and stop&remove them if the associated
	// connection is no longer active.  We stop as soon as we
	// encounter a valid entry; the idea being that when there is
	// little or no garbage then this executes close to O(1)[1],
	// whereas when there is lots of garbage we remove it quickly.
	//
	// [1] TODO Unfortunately, due to the desire to avoid nested
	// locks, instead of simply invoking Peer.ConnectionTo(name)
	// below, we have that Peer.Connections() invocation above. That
	// is O(n_our_connections) at best.
	for conn, sender := range c.senders {
		if _, found := connections[conn]; !found {
			delete(c.senders, conn)
			sender.Stop()
		} else {
			break
		}
	}
	for conn := range selectedConnections {
		c.sendDown(conn, data)
	}
}

func (c *GossipChannel) SendDown(conn Connection, data GossipData) {
	c.Lock()
	c.sendDown(conn, data)
	c.Unlock()
}

func (c *GossipChannel) sendDown(conn Connection, data GossipData) {
	sender, found := c.senders[conn]
	if !found {
		sender = NewGossipSender(func(pending GossipData) {
			protocolMsg := ProtocolMsg{ProtocolGossip, GobEncode(c.name, c.ourself.Name, pending.Encode())}
			conn.(ProtocolSender).SendProtocolMsg(protocolMsg)
		})
		c.senders[conn] = sender
		sender.Start()
	}
	sender.Send(data)
}

func (c *GossipChannel) GossipUnicast(dstPeerName PeerName, msg []byte) error {
	return c.relayUnicast(dstPeerName, GobEncode(c.name, c.ourself.Name, dstPeerName, msg))
}

func (c *GossipChannel) GossipBroadcast(update GossipData) error {
	return c.relayBroadcast(c.ourself.Name, update)
}

func (c *GossipChannel) relayUnicast(dstPeerName PeerName, buf []byte) error {
	if relayPeerName, found := c.routes.UnicastAll(dstPeerName); !found {
		c.log("unknown relay destination:", dstPeerName)
	} else if conn, found := c.ourself.ConnectionTo(relayPeerName); !found {
		c.log("unable to find connection to relay peer", relayPeerName)
	} else {
		conn.(ProtocolSender).SendProtocolMsg(ProtocolMsg{ProtocolGossipUnicast, buf})
	}
	return nil
}

func (c *GossipChannel) relayBroadcast(srcName PeerName, update GossipData) error {
	names := c.routes.PeerNames() // do this outside the lock so they don't nest
	c.Lock()
	defer c.Unlock()
	// GC - randomly (courtesy of go's map iterator) pick some
	// existing broadcasters and stop&remove them if their source peer
	// is unknown. We stop as soon as we encounter a valid entry; the
	// idea being that when there is little or no garbage then this
	// executes close to O(1)[1], whereas when there is lots of
	// garbage we remove it quickly.
	//
	// [1] TODO Unfortunately, due to the desire to avoid nested
	// locks, instead of simply invoking Peers.Fetch(name) below, we
	// have that Peers.Names() invocation above. That is O(n_peers) at
	// best.
	for name, broadcaster := range c.broadcasters {
		if _, found := names[name]; !found {
			delete(c.broadcasters, name)
			broadcaster.Stop()
		} else {
			break
		}
	}
	broadcaster, found := c.broadcasters[srcName]
	if !found {
		broadcaster = NewGossipSender(func(pending GossipData) { c.sendBroadcast(srcName, pending) })
		c.broadcasters[srcName] = broadcaster
		broadcaster.Start()
	}
	broadcaster.Send(update)
	return nil
}

func (c *GossipChannel) sendBroadcast(srcName PeerName, update GossipData) {
	c.routes.EnsureRecalculated()
	nextHops := c.routes.BroadcastAll(srcName)
	if len(nextHops) == 0 {
		return
	}
	protocolMsg := ProtocolMsg{ProtocolGossipBroadcast, GobEncode(c.name, srcName, update.Encode())}
	// FIXME a single blocked connection can stall us
	for _, conn := range c.ourself.ConnectionsTo(nextHops) {
		conn.(ProtocolSender).SendProtocolMsg(protocolMsg)
	}
}

func (c *GossipChannel) log(args ...interface{}) {
	log.Println(append(append([]interface{}{}, "[gossip "+c.name+"]:"), args...)...)
}

// for testing

func (router *Router) sendPendingGossip() {
	for _, channel := range router.GossipChannels {
		channel.Lock()
		for _, sender := range channel.senders {
			sender.flush()
		}
		for _, sender := range channel.broadcasters {
			sender.flush()
		}
		channel.Unlock()
	}
}

func (sender *GossipSender) flush() {
	ch := make(chan struct{})
	sender.flushch <- ch
	<-ch
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The ContainerOps Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package router

import (
	"gopkg.in/macaron.v1"

	"github.com/containerops/dockyard/handler"
)

// Dockyard Router Definition
func SetRouters(m *macaron.Macaron) {
	// Web API
	m.Get("/", handler.IndexV1Handler)

	// Docker Registry V1
	m.Group("/v1", func() {
		m.Get("/_ping", handler.GetPingV1Handler)

		m.Get("/users", handler.GetUsersV1Handler)
		m.Post("/users", handler.PostUsersV1Handler)

		m.Group("/repositories", func() {
			m.Put("/:namespace/:repository/tags/:tag", handler.PutTagV1Handler)
			m.Put("/:namespace/:repository/images", handler.PutRepositoryImagesV1Handler)
			m.Get("/:namespace/:repository/images", handler.GetRepositoryImagesV1Handler)
			m.Get("/:namespace/:repository/tags", handler.GetTagV1Handler)
			m.Put("/:namespace/:repository", handler.PutRepositoryV1Handler)
		})

		m.Group("/images", func() {
			m.Get("/:image/ancestry", handler.GetImageAncestryV1Handler)
			m.Get("/:image/json", handler.GetImageJSONV1Handler)
			m.Get("/:image/layer", handler.GetImageLayerV1Handler)
			m.Put("/:image/json", handler.PutImageJSONV1Handler)
			m.Put("/:image/layer", handler.PutImageLayerV1Handler)
			m.Put("/:image/checksum", handler.PutImageChecksumV1Handler)
		})
	})

	// Docker Registry V2
	m.Group("/v2", func() {
		m.Get("/", handler.GetPingV2Handler)
		m.Get("/_catalog", handler.GetCatalogV2Handler)

		// user mode: /namespace/repository:tag
		m.Head("/:namespace/:repository/blobs/:digest", handler.HeadBlobsV2Handler)
		m.Post("/:namespace/:repository/blobs/uploads", handler.PostBlobsV2Handler)
		m.Patch("/:namespace/:repository/blobs/uploads/:uuid", handler.PatchBlobsV2Handler)
		m.Put("/:namespace/:repository/blobs/uploads/:uuid", handler.PutBlobsV2Handler)
		m.Get("/:namespace/:repository/blobs/:digest", handler.GetBlobsV2Handler)
		m.Put("/:namespace/:repository/manifests/:tag", handler.PutManifestsV2Handler)
		m.Get("/:namespace/:repository/tags/list", handler.GetTagsListV2Handler)
		m.Get("/:namespace/:repository/manifests/:tag", handler.GetManifestsV2Handler)
		m.Delete("/:namespace/:repository/blobs/:digest", handler.DeleteBlobsV2Handler)
		m.Delete("/:namespace/:repository/manifests/:reference", handler.DeleteManifestsV2Handler)

		// library mode: /repository:tag
		m.Head("/:repository/blobs/:digest", handler.HeadBlobsV2LibraryHandler)
		m.Post("/:repository/blobs/uploads", handler.PostBlobsV2LibraryHandler)
		m.Patch("/:repository/blobs/uploads/:uuid", handler.PatchBlobsV2LibraryHandler)
		m.Put("/:repository/blobs/uploads/:uuid", handler.PutBlobsV2LibraryHandler)
		m.Get("/:repository/blobs/:digest", handler.GetBlobsV2LibraryHandler)
		m.Put("/:repository/manifests/:tag", handler.PutManifestsV2LibraryHandler)
		m.Get("/:repository/tags/list", handler.GetTagsListV2LibraryHandler)
		m.Get("/:repository/manifests/:tag", handler.GetManifestsV2LibraryHandler)
		m.Delete("/:repository/blobs/:digest", handler.DeleteBlobsV2LibraryHandler)
		m.Delete("/:repository/manifests/:reference", handler.DeleteManifestsV2LibraryHandler)
	})

	// App Discovery
	m.Group("/app", func() {
		m.Group("/v1", func() {
			// Global Search
			m.Get("/search", handler.AppGlobalSearchV1Handler)

			m.Group("/:namespace/:repository", func() {
				// Discovery
				m.Get("/?app-discovery=1", handler.AppDiscoveryV1Handler)

				// Scoped Search
				m.Get("/search", handler.AppScopedSearchV1Handler)
				m.Get("/list", handler.AppGetListAppV1Handler)

				// Pull
				m.Get("/:os/:arch/:app", handler.AppGetFileV1Handler)

				// Push
				m.Post("/", handler.AppPostV1Handler)
				m.Put("/:os/:arch/:app/:tag", handler.AppPutFileV1Handler)
				m.Put("/:os/:arch/:app/:tag/manifests", handler.AppPutManifestV1Handler)
				m.Patch("/:os/:arch/:app/:tag/:status", handler.AppPatchFileV1Handler)
				m.Delete("/:os/:arch/:app/:tag", handler.AppDeleteFileByTagV1Handler)
				m.Delete("/:os/:arch/:app", handler.AppDeleteFileV1Handler)
			})
		})
	})

	// Appc Discovery
	m.Group("/appc", func() {
		m.Group("/v1", func() {
			m.Group("/:namespace/:repository", func() {
				// Discovery
				m.Get("/?ac-discovery=1", handler.AppcDiscoveryV1Handler)

				// Pull
				m.Get("/pubkeys", handler.AppcGetPubkeysV1Handler)
				m.Get("/:os/:arch/:aci", handler.AppcGetACIV1Handler)
			})

		})
	})

	// VM Image Discovery
	m.Group("/image", func() {
		m.Group("/v1", func() {
			// Global Search
			m.Get("/search", handler.ImageGlobalSearchV1Handler)

			m.Group("/:namespace/:repository", func() {
				// Discovery
				m.Get("/?image-discovery=1", handler.ImageDiscoveryV1Handler)

				// Scoped Search
				m.Get("/search", handler.ImageScopedSearchV1Handler)
				m.Get("/list", handler.ImageGetListV1Handler)

				// Pull
				m.Get("/:os/:arch/:image", handler.ImageGetFileV1Handler)

				// Push
				m.Post("/", handler.ImagePostV1Handler)
				m.Put("/:os/:arch/:image/:tag", handler.ImagePutFileV1Handler)
				m.Put("/:os/:arch/:image/:tag/manifests", handler.ImagePutManifestV1Handler)
				m.Patch("/:os/:arch/:image/:tag/:status", handler.ImagePatchFileV1Handler)
				m.Delete("/:os/:arch/:image/:tag", handler.ImageDeleteFileByTagV1Handler)
				m.Delete("/:os/:arch/:image", handler.ImageDeleteFileV1Handler)
			})
		})
	})

	// Sync APIS
	m.Group("/sync", func() {
		m.Group("/v1", func() {
			// Server Ping
			m.Get("/ping", handler.SyncGetPingV1Handler)

			m.Group("/master", func() {
				// Server Sync Of Master
				m.Post("/registry", handler.SyncMasterPostRegistryV1Handler)
				m.Delete("/registry", handler.SyncMasterDeleteRegistryV1Handler)

				m.Put("/mode", handler.SyncMasterPutModeRegistryV1Handler)
			})

			m.Group("/slave", func() {
				// Server Sync Of Slaver
				m.Post("/registry", handler.SyncSlavePostRegistryV1Handler)
				m.Put("/registry", handler.SyncSlavePutRegistryV1Handler)
				m.Delete("/registry", handler.SyncSlaveDeleteRegistryV1Handler)

				m.Put("/mode", handler.SyncSlavePutModeRegistryV1Handler)

				// Data Sync
				m.Get("/list", handler.SyncSlaveListDataV1Handler)

				// File Sync
				m.Put("/:namespace/:repository/manifests", handler.SyncSlavePutManifestsV1Handler)
				m.Put("/:namespace/:repository/file", handler.SyncSlavePutFileV1Handler)
				m.Put("/:namespace/:repository/:status", handler.SyncSlavePutStatusV1Handler)
			})
		})
	})

	// Admin APIs
	m.Group("/admin", func() {
		m.Group("/v1", func() {
			// Server Status
			m.Get("/stats/:type", handler.AdminGetStatusV1Handler)

			// Server Config
			m.Get("/config", handler.AdminGetConfigV1Handler)
			m.Put("/config", handler.AdminSetConfigV1Handler)

			// Maintenance
			m.Post("/maintenance", handler.AdminPostMaintenance)
		})
	})
}
<commit_msg>Change tag param to optional match in router.<commit_after>/*
Copyright 2015 The ContainerOps Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package router

import (
	"gopkg.in/macaron.v1"

	"github.com/containerops/dockyard/handler"
)

// Dockyard Router Definition
func SetRouters(m *macaron.Macaron) {
	// Web API
	m.Get("/", handler.IndexV1Handler)

	// Docker Registry V1
	m.Group("/v1", func() {
		m.Get("/_ping", handler.GetPingV1Handler)

		m.Get("/users", handler.GetUsersV1Handler)
		m.Post("/users", handler.PostUsersV1Handler)

		m.Group("/repositories", func() {
			m.Put("/:namespace/:repository/tags/:tag", handler.PutTagV1Handler)
			m.Put("/:namespace/:repository/images", handler.PutRepositoryImagesV1Handler)
			m.Get("/:namespace/:repository/images", handler.GetRepositoryImagesV1Handler)
			m.Get("/:namespace/:repository/tags", handler.GetTagV1Handler)
			m.Put("/:namespace/:repository", handler.PutRepositoryV1Handler)
		})

		m.Group("/images", func() {
			m.Get("/:image/ancestry", handler.GetImageAncestryV1Handler)
			m.Get("/:image/json", handler.GetImageJSONV1Handler)
			m.Get("/:image/layer", handler.GetImageLayerV1Handler)
			m.Put("/:image/json", handler.PutImageJSONV1Handler)
			m.Put("/:image/layer", handler.PutImageLayerV1Handler)
			m.Put("/:image/checksum", handler.PutImageChecksumV1Handler)
		})
	})

	// Docker Registry V2
	m.Group("/v2", func() {
		m.Get("/", handler.GetPingV2Handler)
		m.Get("/_catalog", handler.GetCatalogV2Handler)

		// user mode: /namespace/repository:tag
		m.Head("/:namespace/:repository/blobs/:digest", handler.HeadBlobsV2Handler)
		m.Post("/:namespace/:repository/blobs/uploads", handler.PostBlobsV2Handler)
		m.Patch("/:namespace/:repository/blobs/uploads/:uuid", handler.PatchBlobsV2Handler)
		m.Put("/:namespace/:repository/blobs/uploads/:uuid", handler.PutBlobsV2Handler)
		m.Get("/:namespace/:repository/blobs/:digest", handler.GetBlobsV2Handler)
		m.Put("/:namespace/:repository/manifests/:tag", handler.PutManifestsV2Handler)
		m.Get("/:namespace/:repository/tags/list", handler.GetTagsListV2Handler)
		m.Get("/:namespace/:repository/manifests/:tag", handler.GetManifestsV2Handler)
		m.Delete("/:namespace/:repository/blobs/:digest", handler.DeleteBlobsV2Handler)
		m.Delete("/:namespace/:repository/manifests/:reference", handler.DeleteManifestsV2Handler)

		// library mode: /repository:tag
		m.Head("/:repository/blobs/:digest", handler.HeadBlobsV2LibraryHandler)
		m.Post("/:repository/blobs/uploads", handler.PostBlobsV2LibraryHandler)
		m.Patch("/:repository/blobs/uploads/:uuid", handler.PatchBlobsV2LibraryHandler)
		m.Put("/:repository/blobs/uploads/:uuid", handler.PutBlobsV2LibraryHandler)
		m.Get("/:repository/blobs/:digest", handler.GetBlobsV2LibraryHandler)
		m.Put("/:repository/manifests/:tag", handler.PutManifestsV2LibraryHandler)
		m.Get("/:repository/tags/list", handler.GetTagsListV2LibraryHandler)
		m.Get("/:repository/manifests/:tag", handler.GetManifestsV2LibraryHandler)
		m.Delete("/:repository/blobs/:digest", handler.DeleteBlobsV2LibraryHandler)
		m.Delete("/:repository/manifests/:reference", handler.DeleteManifestsV2LibraryHandler)
	})

	// App Discovery
	m.Group("/app", func() {
		m.Group("/v1", func() {
			// Global Search
			m.Get("/search", handler.AppGlobalSearchV1Handler)

			m.Group("/:namespace/:repository", func() {
				// Discovery
				m.Get("/?app-discovery=1", handler.AppDiscoveryV1Handler)

				// Scoped Search
				m.Get("/search/:?tag", handler.AppScopedSearchV1Handler)
				m.Get("/list", handler.AppGetListAppV1Handler)

				// Pull
				m.Get("/:os/:arch/:app/?:tag", handler.AppGetFileV1Handler)

				// Push
				m.Post("/", handler.AppPostV1Handler)
				m.Put("/:os/:arch/:app/?:tag", handler.AppPutFileV1Handler)
				m.Put("/:os/:arch/:app/manifests/?:tag", handler.AppPutManifestV1Handler)
				m.Patch("/:os/:arch/:app/:status/?:tag", handler.AppPatchFileV1Handler)
				m.Delete("/:os/:arch/:app/?:tag", handler.AppDeleteFileByTagV1Handler)
				m.Delete("/:os/:arch/:app/?:tag", handler.AppDeleteFileV1Handler)
			})
		})
	})

	// Appc Discovery
	m.Group("/appc", func() {
		m.Group("/v1", func() {
			m.Group("/:namespace/:repository", func() {
				// Discovery
				m.Get("/?ac-discovery=1", handler.AppcDiscoveryV1Handler)

				// Pull
				m.Get("/pubkeys", handler.AppcGetPubkeysV1Handler)
				m.Get("/:os/:arch/:aci", handler.AppcGetACIV1Handler)
			})

		})
	})

	// VM Image Discovery
	m.Group("/image", func() {
		m.Group("/v1", func() {
			// Global Search
			m.Get("/search", handler.ImageGlobalSearchV1Handler)

			m.Group("/:namespace/:repository", func() {
				// Discovery
				m.Get("/?image-discovery=1", handler.ImageDiscoveryV1Handler)

				// Scoped Search
				m.Get("/search/:?tag", handler.ImageScopedSearchV1Handler)
				m.Get("/list", handler.ImageGetListV1Handler)

				// Pull
				m.Get("/:os/:arch/:image/:?tag", handler.ImageGetFileV1Handler)

				// Push
				m.Post("/", handler.ImagePostV1Handler)
				m.Put("/:os/:arch/:image/?:tag", handler.ImagePutFileV1Handler)
				m.Put("/:os/:arch/:image/manifests/?:tag", handler.ImagePutManifestV1Handler)
				m.Patch("/:os/:arch/:image/:status/?:tag", handler.ImagePatchFileV1Handler)
				m.Delete("/:os/:arch/:image/?:tag", handler.ImageDeleteFileByTagV1Handler)
				m.Delete("/:os/:arch/:image", handler.ImageDeleteFileV1Handler)
			})
		})
	})

	// Sync APIS
	m.Group("/sync", func() {
		m.Group("/v1", func() {
			// Server Ping
			m.Get("/ping", handler.SyncGetPingV1Handler)

			m.Group("/master", func() {
				// Server Sync Of Master
				m.Post("/registry", handler.SyncMasterPostRegistryV1Handler)
				m.Delete("/registry", handler.SyncMasterDeleteRegistryV1Handler)

				m.Put("/mode", handler.SyncMasterPutModeRegistryV1Handler)
			})

			m.Group("/slave", func() {
				// Server Sync Of Slaver
				m.Post("/registry", handler.SyncSlavePostRegistryV1Handler)
				m.Put("/registry", handler.SyncSlavePutRegistryV1Handler)
				m.Delete("/registry", handler.SyncSlaveDeleteRegistryV1Handler)

				m.Put("/mode", handler.SyncSlavePutModeRegistryV1Handler)

				// Data Sync
				m.Get("/list", handler.SyncSlaveListDataV1Handler)

				// File Sync
				m.Put("/:namespace/:repository/manifests", handler.SyncSlavePutManifestsV1Handler)
				m.Put("/:namespace/:repository/file", handler.SyncSlavePutFileV1Handler)
				m.Put("/:namespace/:repository/:status", handler.SyncSlavePutStatusV1Handler)
			})
		})
	})

	// Admin APIs
	m.Group("/admin", func() {
		m.Group("/v1", func() {
			// Server Status
			m.Get("/stats/:type", handler.AdminGetStatusV1Handler)

			// Server Config
			m.Get("/config", handler.AdminGetConfigV1Handler)
			m.Put("/config", handler.AdminSetConfigV1Handler)

			// Maintenance
			m.Post("/maintenance", handler.AdminPostMaintenance)
		})
	})
}
<|endoftext|>


<commit_before>package router

import (
	"encoding/base32"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"

	"github.com/NyaaPantsu/nyaa/cache"
	"github.com/NyaaPantsu/nyaa/config"
	"github.com/NyaaPantsu/nyaa/service/upload"
	"github.com/NyaaPantsu/nyaa/util"
	"github.com/NyaaPantsu/nyaa/util/categories"
	"github.com/NyaaPantsu/nyaa/util/metainfo"
	"github.com/microcosm-cc/bluemonday"
	"github.com/zeebo/bencode"
)

// Use this, because we seem to avoid using models, and we would need
// the torrent ID to create the File in the DB
type UploadedFile struct {
	Path     []string
	Filesize int64
}

// UploadForm serializing HTTP form for torrent upload
type UploadForm struct {
	Name        string
	Magnet      string
	Category    string
	Remake      bool
	Description string
	Status      int
	CaptchaID   string
	WebsiteLink string

	Infohash      string
	CategoryID    int
	SubCategoryID int
	Filesize      int64
	Filepath      string
	FileList      []UploadedFile
}

// TODO: these should be in another package (?)

// form names
const UploadFormName = "name"
const UploadFormTorrent = "torrent"
const UploadFormMagnet = "magnet"
const UploadFormCategory = "c"
const UploadFormRemake = "remake"
const UploadFormDescription = "desc"
const UploadFormWebsiteLink = "website_link"
const UploadFormStatus = "status"

// error indicating that you can't send both a magnet link and torrent
var ErrTorrentPlusMagnet = errors.New("Upload either a torrent file or magnet link, not both")

// error indicating a torrent is private
var ErrPrivateTorrent = errors.New("Torrent is private")

// error indicating a problem with its trackers
var ErrTrackerProblem = errors.New("Torrent does not have any (working) trackers: https://" + config.WebAddress + "/faq#trackers")

// error indicating a torrent's name is invalid
var ErrInvalidTorrentName = errors.New("Torrent name is invalid")

// error indicating a torrent's description is invalid
var ErrInvalidTorrentDescription = errors.New("Torrent description is invalid")

// error indicating a torrent's description is invalid
var ErrInvalidWebsiteLink = errors.New("Website url or IRC link is invalid")

// error indicating a torrent's category is invalid
var ErrInvalidTorrentCategory = errors.New("Torrent category is invalid")

var p = bluemonday.UGCPolicy()

/**
UploadForm.ExtractInfo takes an http request and computes all fields for this form
*/
func (f *UploadForm) ExtractInfo(r *http.Request) error {

	f.Name = r.FormValue(UploadFormName)
	f.Category = r.FormValue(UploadFormCategory)
	f.Description = r.FormValue(UploadFormDescription)
	f.WebsiteLink = r.FormValue(UploadFormWebsiteLink)
	f.Status, _ = strconv.Atoi(r.FormValue(UploadFormStatus))
	f.Magnet = r.FormValue(UploadFormMagnet)
	f.Remake = r.FormValue(UploadFormRemake) == "on"

	// trim whitespace
	f.Name = util.TrimWhitespaces(f.Name)
	f.Description = p.Sanitize(util.TrimWhitespaces(f.Description))
	f.WebsiteLink = util.TrimWhitespaces(f.WebsiteLink)
	f.Magnet = util.TrimWhitespaces(f.Magnet)
	cache.Impl.ClearAll()

	catsSplit := strings.Split(f.Category, "_")
	// need this to prevent out of index panics
	if len(catsSplit) == 2 {
		CatID, err := strconv.Atoi(catsSplit[0])
		if err != nil {
			return ErrInvalidTorrentCategory
		}
		SubCatID, err := strconv.Atoi(catsSplit[1])
		if err != nil {
			return ErrInvalidTorrentCategory
		}

		if !categories.CategoryExists(f.Category) {
			return ErrInvalidTorrentCategory
		}

		f.CategoryID = CatID
		f.SubCategoryID = SubCatID
	} else {
		return ErrInvalidTorrentCategory
	}
	if f.WebsiteLink != "" {
		// WebsiteLink
		urlRegexp, _ := regexp.Compile(`^(https?:\/\/|ircs?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$`)
		if !urlRegexp.MatchString(f.WebsiteLink) {
			return ErrInvalidWebsiteLink
		}
	}

	// first: parse torrent file (if any) to fill missing information
	tfile, _, err := r.FormFile(UploadFormTorrent)
	if err == nil {
		var torrent metainfo.TorrentFile

		// decode torrent
		_, seekErr := tfile.Seek(0, io.SeekStart)
		if seekErr != nil {
			return seekErr
		}
		err = bencode.NewDecoder(tfile).Decode(&torrent)
		if err != nil {
			return metainfo.ErrInvalidTorrentFile
		}

		// check a few things
		if torrent.IsPrivate() {
			return ErrPrivateTorrent
		}
		trackers := torrent.GetAllAnnounceURLS()
		if !uploadService.CheckTrackers(trackers) {
			return ErrTrackerProblem
		}

		// Name
		if len(f.Name) == 0 {
			f.Name = torrent.TorrentName()
		}

		// Magnet link: if a file is provided it should be empty
		if len(f.Magnet) != 0 {
			return ErrTorrentPlusMagnet
		}
		binInfohash, err := torrent.Infohash()
		if err != nil {
			return err
		}
		f.Infohash = strings.ToUpper(hex.EncodeToString(binInfohash[:]))
		f.Magnet = util.InfoHashToMagnet(f.Infohash, f.Name, trackers...)

		// extract filesize
		f.Filesize = int64(torrent.TotalSize())

		// extract filelist
		fileInfos := torrent.Info.GetFiles()
		for _, fileInfo := range fileInfos {
			f.FileList = append(f.FileList, UploadedFile{
				Path:     fileInfo.Path,
				Filesize: int64(fileInfo.Length),
			})
		}
	} else {
		// No torrent file provided
		magnetUrl, err := url.Parse(string(f.Magnet)) //?
		if err != nil {
			return err
		}
		xt := magnetUrl.Query().Get("xt")
		if !strings.HasPrefix(xt, "urn:btih:") {
			return errors.New("Incorrect magnet")
		}
		xt = strings.SplitAfter(xt, ":")[2]
		f.Infohash = strings.ToUpper(strings.Split(xt, "&")[0])
		isBase32, err := regexp.MatchString("^[2-7A-Z]{32}$", f.Infohash)
		if err != nil {
			return err
		}
		if !isBase32 {
			isBase16, err := regexp.MatchString("^[0-9A-F]{40}$", f.Infohash)
			if err != nil {
				return err
			}
			if !isBase16 {
				return errors.New("Incorrect hash")
			}
		}
		f.Filesize = 0
		f.Filepath = ""
	}

	// then actually check that we have everything we need
	if len(f.Name) == 0 {
		return ErrInvalidTorrentName
	}

	// after data has been checked & extracted, write it to disk
	if len(config.TorrentFileStorage) > 0 {
		err := WriteTorrentToDisk(tfile, f.Infohash+".torrent", &f.Filepath)
		if err != nil {
			return err
		}
	} else {
		f.Filepath = ""
	}

	return nil
}

func (f *UploadForm) ExtractEditInfo(r *http.Request) error {
	f.Name = r.FormValue(UploadFormName)
	f.Category = r.FormValue(UploadFormCategory)
	f.WebsiteLink = r.FormValue(UploadFormWebsiteLink)
	f.Description = r.FormValue(UploadFormDescription)
	f.Status, _ = strconv.Atoi(r.FormValue(UploadFormStatus))

	// trim whitespace
	f.Name = util.TrimWhitespaces(f.Name)
	f.Description = p.Sanitize(util.TrimWhitespaces(f.Description))

	catsSplit := strings.Split(f.Category, "_")
	// need this to prevent out of index panics
	if len(catsSplit) == 2 {
		CatID, err := strconv.Atoi(catsSplit[0])
		if err != nil {
			return ErrInvalidTorrentCategory
		}
		SubCatID, err := strconv.Atoi(catsSplit[1])
		if err != nil {
			return ErrInvalidTorrentCategory
		}

		f.CategoryID = CatID
		f.SubCategoryID = SubCatID
	} else {
		return ErrInvalidTorrentCategory
	}
	return nil
}

func WriteTorrentToDisk(file multipart.File, name string, fullpath *string) error {
	_, seekErr := file.Seek(0, io.SeekStart)
	if seekErr != nil {
		return seekErr
	}
	b, err := ioutil.ReadAll(file)
	if err != nil {
		return err
	}
	*fullpath = fmt.Sprintf("%s%c%s", config.TorrentFileStorage, os.PathSeparator, name)
	return ioutil.WriteFile(*fullpath, b, 0644)
}

// NewUploadForm creates a new upload form given parameters as list
func NewUploadForm(params ...string) (uploadForm UploadForm) {
	if len(params) > 1 {
		uploadForm.Category = params[0]
	} else {
		uploadForm.Category = "3_12"
	}
	if len(params) > 2 {
		uploadForm.Description = params[1]
	} else {
		uploadForm.Description = "Description"
	}
	return
}
<commit_msg>Remove unused package<commit_after>package router

import (
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"

	"github.com/NyaaPantsu/nyaa/cache"
	"github.com/NyaaPantsu/nyaa/config"
	"github.com/NyaaPantsu/nyaa/service/upload"
	"github.com/NyaaPantsu/nyaa/util"
	"github.com/NyaaPantsu/nyaa/util/categories"
	"github.com/NyaaPantsu/nyaa/util/metainfo"
	"github.com/microcosm-cc/bluemonday"
	"github.com/zeebo/bencode"
)

// Use this, because we seem to avoid using models, and we would need
// the torrent ID to create the File in the DB
type UploadedFile struct {
	Path     []string
	Filesize int64
}

// UploadForm serializing HTTP form for torrent upload
type UploadForm struct {
	Name        string
	Magnet      string
	Category    string
	Remake      bool
	Description string
	Status      int
	CaptchaID   string
	WebsiteLink string

	Infohash      string
	CategoryID    int
	SubCategoryID int
	Filesize      int64
	Filepath      string
	FileList      []UploadedFile
}

// TODO: these should be in another package (?)

// form names
const UploadFormName = "name"
const UploadFormTorrent = "torrent"
const UploadFormMagnet = "magnet"
const UploadFormCategory = "c"
const UploadFormRemake = "remake"
const UploadFormDescription = "desc"
const UploadFormWebsiteLink = "website_link"
const UploadFormStatus = "status"

// error indicating that you can't send both a magnet link and torrent
var ErrTorrentPlusMagnet = errors.New("Upload either a torrent file or magnet link, not both")

// error indicating a torrent is private
var ErrPrivateTorrent = errors.New("Torrent is private")

// error indicating a problem with its trackers
var ErrTrackerProblem = errors.New("Torrent does not have any (working) trackers: https://" + config.WebAddress + "/faq#trackers")

// error indicating a torrent's name is invalid
var ErrInvalidTorrentName = errors.New("Torrent name is invalid")

// error indicating a torrent's description is invalid
var ErrInvalidTorrentDescription = errors.New("Torrent description is invalid")

// error indicating a torrent's description is invalid
var ErrInvalidWebsiteLink = errors.New("Website url or IRC link is invalid")

// error indicating a torrent's category is invalid
var ErrInvalidTorrentCategory = errors.New("Torrent category is invalid")

var p = bluemonday.UGCPolicy()

/**
UploadForm.ExtractInfo takes an http request and computes all fields for this form
*/
func (f *UploadForm) ExtractInfo(r *http.Request) error {

	f.Name = r.FormValue(UploadFormName)
	f.Category = r.FormValue(UploadFormCategory)
	f.Description = r.FormValue(UploadFormDescription)
	f.WebsiteLink = r.FormValue(UploadFormWebsiteLink)
	f.Status, _ = strconv.Atoi(r.FormValue(UploadFormStatus))
	f.Magnet = r.FormValue(UploadFormMagnet)
	f.Remake = r.FormValue(UploadFormRemake) == "on"

	// trim whitespace
	f.Name = util.TrimWhitespaces(f.Name)
	f.Description = p.Sanitize(util.TrimWhitespaces(f.Description))
	f.WebsiteLink = util.TrimWhitespaces(f.WebsiteLink)
	f.Magnet = util.TrimWhitespaces(f.Magnet)
	cache.Impl.ClearAll()

	catsSplit := strings.Split(f.Category, "_")
	// need this to prevent out of index panics
	if len(catsSplit) == 2 {
		CatID, err := strconv.Atoi(catsSplit[0])
		if err != nil {
			return ErrInvalidTorrentCategory
		}
		SubCatID, err := strconv.Atoi(catsSplit[1])
		if err != nil {
			return ErrInvalidTorrentCategory
		}

		if !categories.CategoryExists(f.Category) {
			return ErrInvalidTorrentCategory
		}

		f.CategoryID = CatID
		f.SubCategoryID = SubCatID
	} else {
		return ErrInvalidTorrentCategory
	}
	if f.WebsiteLink != "" {
		// WebsiteLink
		urlRegexp, _ := regexp.Compile(`^(https?:\/\/|ircs?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$`)
		if !urlRegexp.MatchString(f.WebsiteLink) {
			return ErrInvalidWebsiteLink
		}
	}

	// first: parse torrent file (if any) to fill missing information
	tfile, _, err := r.FormFile(UploadFormTorrent)
	if err == nil {
		var torrent metainfo.TorrentFile

		// decode torrent
		_, seekErr := tfile.Seek(0, io.SeekStart)
		if seekErr != nil {
			return seekErr
		}
		err = bencode.NewDecoder(tfile).Decode(&torrent)
		if err != nil {
			return metainfo.ErrInvalidTorrentFile
		}

		// check a few things
		if torrent.IsPrivate() {
			return ErrPrivateTorrent
		}
		trackers := torrent.GetAllAnnounceURLS()
		if !uploadService.CheckTrackers(trackers) {
			return ErrTrackerProblem
		}

		// Name
		if len(f.Name) == 0 {
			f.Name = torrent.TorrentName()
		}

		// Magnet link: if a file is provided it should be empty
		if len(f.Magnet) != 0 {
			return ErrTorrentPlusMagnet
		}
		binInfohash, err := torrent.Infohash()
		if err != nil {
			return err
		}
		f.Infohash = strings.ToUpper(hex.EncodeToString(binInfohash[:]))
		f.Magnet = util.InfoHashToMagnet(f.Infohash, f.Name, trackers...)

		// extract filesize
		f.Filesize = int64(torrent.TotalSize())

		// extract filelist
		fileInfos := torrent.Info.GetFiles()
		for _, fileInfo := range fileInfos {
			f.FileList = append(f.FileList, UploadedFile{
				Path:     fileInfo.Path,
				Filesize: int64(fileInfo.Length),
			})
		}
	} else {
		// No torrent file provided
		magnetUrl, err := url.Parse(string(f.Magnet)) //?
		if err != nil {
			return err
		}
		xt := magnetUrl.Query().Get("xt")
		if !strings.HasPrefix(xt, "urn:btih:") {
			return errors.New("Incorrect magnet")
		}
		xt = strings.SplitAfter(xt, ":")[2]
		f.Infohash = strings.ToUpper(strings.Split(xt, "&")[0])
		isBase32, err := regexp.MatchString("^[2-7A-Z]{32}$", f.Infohash)
		if err != nil {
			return err
		}
		if !isBase32 {
			isBase16, err := regexp.MatchString("^[0-9A-F]{40}$", f.Infohash)
			if err != nil {
				return err
			}
			if !isBase16 {
				return errors.New("Incorrect hash")
			}
		}
		f.Filesize = 0
		f.Filepath = ""
	}

	// then actually check that we have everything we need
	if len(f.Name) == 0 {
		return ErrInvalidTorrentName
	}

	// after data has been checked & extracted, write it to disk
	if len(config.TorrentFileStorage) > 0 {
		err := WriteTorrentToDisk(tfile, f.Infohash+".torrent", &f.Filepath)
		if err != nil {
			return err
		}
	} else {
		f.Filepath = ""
	}

	return nil
}

func (f *UploadForm) ExtractEditInfo(r *http.Request) error {
	f.Name = r.FormValue(UploadFormName)
	f.Category = r.FormValue(UploadFormCategory)
	f.WebsiteLink = r.FormValue(UploadFormWebsiteLink)
	f.Description = r.FormValue(UploadFormDescription)
	f.Status, _ = strconv.Atoi(r.FormValue(UploadFormStatus))

	// trim whitespace
	f.Name = util.TrimWhitespaces(f.Name)
	f.Description = p.Sanitize(util.TrimWhitespaces(f.Description))

	catsSplit := strings.Split(f.Category, "_")
	// need this to prevent out of index panics
	if len(catsSplit) == 2 {
		CatID, err := strconv.Atoi(catsSplit[0])
		if err != nil {
			return ErrInvalidTorrentCategory
		}
		SubCatID, err := strconv.Atoi(catsSplit[1])
		if err != nil {
			return ErrInvalidTorrentCategory
		}

		f.CategoryID = CatID
		f.SubCategoryID = SubCatID
	} else {
		return ErrInvalidTorrentCategory
	}
	return nil
}

func WriteTorrentToDisk(file multipart.File, name string, fullpath *string) error {
	_, seekErr := file.Seek(0, io.SeekStart)
	if seekErr != nil {
		return seekErr
	}
	b, err := ioutil.ReadAll(file)
	if err != nil {
		return err
	}
	*fullpath = fmt.Sprintf("%s%c%s", config.TorrentFileStorage, os.PathSeparator, name)
	return ioutil.WriteFile(*fullpath, b, 0644)
}

// NewUploadForm creates a new upload form given parameters as list
func NewUploadForm(params ...string) (uploadForm UploadForm) {
	if len(params) > 1 {
		uploadForm.Category = params[0]
	} else {
		uploadForm.Category = "3_12"
	}
	if len(params) > 2 {
		uploadForm.Description = params[1]
	} else {
		uploadForm.Description = "Description"
	}
	return
}
<|endoftext|>


<commit_before>package main

func main() {
}
<commit_msg>remove main.go<commit_after><|endoftext|>


<commit_before>package collection

import (
  "github.com/kuzzleio/sdk-go/internal"
  "errors"
  "encoding/json"
  "github.com/kuzzleio/sdk-go/types"
)

/*
  Create a new empty data collection, with no associated mapping.
*/
func (dc *Collection) Create(options *types.Options) (*types.AckResponse, error) {
  ch := make(chan types.KuzzleResponse)

  go dc.kuzzle.Query(internal.BuildQuery(dc.collection, dc.index, "collection", "create", nil), options, ch)

  res := <-ch

  if res.Error.Message != "" {
    return nil, errors.New(res.Error.Message)
  }

  ack := &types.AckResponse{}
  json.Unmarshal(res.Result, &ack)

  return ack, nil
}<commit_msg>Update create.go<commit_after>package collection

import (
  "github.com/kuzzleio/sdk-go/internal"
  "errors"
  "encoding/json"
  "github.com/kuzzleio/sdk-go/types"
)

/*
  Create a new empty data collection, with no associated mapping.
*/
func (dc Collection) Create(options *types.Options) (*types.AckResponse, error) {
  ch := make(chan types.KuzzleResponse)

  go dc.kuzzle.Query(internal.BuildQuery(dc.collection, dc.index, "collection", "create", nil), options, ch)

  res := <-ch

  if res.Error.Message != "" {
    return nil, errors.New(res.Error.Message)
  }

  ack := &types.AckResponse{}
  json.Unmarshal(res.Result, &ack)

  return ack, nil
}
<|endoftext|>


<commit_before>package log

import (
	"log"
	"os"
)

// AccessStatus is the status of an access request from clients.
type AccessStatus string

const (
	AccessAccepted = AccessStatus("accepted")
	AccessRejected = AccessStatus("rejected")
)

type accessLogger interface {
	Log(from, to string, status AccessStatus, reason string)
}

type noOpAccessLogger struct {
}

func (logger *noOpAccessLogger) Log(from, to string, status AccessStatus, reason string) {
	// Swallow
}

type accessLog struct {
	From   string
	To     string
	Status AccessStatus
	Reason string
}

type fileAccessLogger struct {
	queue  chan *accessLog
	logger *log.Logger
	file   *os.File
}

func (logger *fileAccessLogger) close() {
	logger.file.Close()
}

func (logger *fileAccessLogger) Log(from, to string, status AccessStatus, reason string) {
	logger.queue <- &accessLog{
		From:   from,
		To:     to,
		Status: status,
		Reason: reason,
	}
}

func (logger *fileAccessLogger) Run() {
	for entry := range logger.queue {
		logger.logger.Println(entry.From + " " + string(entry.Status) + " " + entry.To + " " + entry.Reason)
	}
}

func newFileAccessLogger(path string) accessLogger {
	file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		log.Printf("Unable to create or open file (%s): %v\n", path, err)
		return nil
	}
	return &fileAccessLogger{
		queue:  make(chan *accessLog, 16),
		logger: log.New(file, "", log.Ldate|log.Ltime),
		file:   file,
	}
}

var accessLoggerInstance accessLogger = &noOpAccessLogger{}

// InitAccessLogger initializes the access logger to write into the give file.
func InitAccessLogger(file string) {
	logger := newFileAccessLogger(file)
	if logger != nil {
		go logger.(*fileAccessLogger).Run()
		accessLoggerInstance = logger
	}
}

// Access writes an access log.
func Access(from, to string, status AccessStatus, reason string) {
	accessLoggerInstance.Log(from, to, status, reason)
}
<commit_msg>Don't block main thread when writing access log.<commit_after>package log

import (
	"log"
	"os"
)

// AccessStatus is the status of an access request from clients.
type AccessStatus string

const (
	AccessAccepted = AccessStatus("accepted")
	AccessRejected = AccessStatus("rejected")
)

type accessLogger interface {
	Log(from, to string, status AccessStatus, reason string)
}

type noOpAccessLogger struct {
}

func (logger *noOpAccessLogger) Log(from, to string, status AccessStatus, reason string) {
	// Swallow
}

type accessLog struct {
	From   string
	To     string
	Status AccessStatus
	Reason string
}

type fileAccessLogger struct {
	queue  chan *accessLog
	logger *log.Logger
	file   *os.File
}

func (logger *fileAccessLogger) close() {
	logger.file.Close()
}

func (logger *fileAccessLogger) Log(from, to string, status AccessStatus, reason string) {
	select {
	case logger.queue <- &accessLog{
		From:   from,
		To:     to,
		Status: status,
		Reason: reason,
	}:
	default:
		// We don't expect this to happen, but don't want to block main thread as well.
	}
}

func (logger *fileAccessLogger) Run() {
	for entry := range logger.queue {
		logger.logger.Println(entry.From + " " + string(entry.Status) + " " + entry.To + " " + entry.Reason)
	}
}

func newFileAccessLogger(path string) accessLogger {
	file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		log.Printf("Unable to create or open file (%s): %v\n", path, err)
		return nil
	}
	return &fileAccessLogger{
		queue:  make(chan *accessLog, 16),
		logger: log.New(file, "", log.Ldate|log.Ltime),
		file:   file,
	}
}

var accessLoggerInstance accessLogger = &noOpAccessLogger{}

// InitAccessLogger initializes the access logger to write into the give file.
func InitAccessLogger(file string) {
	logger := newFileAccessLogger(file)
	if logger != nil {
		go logger.(*fileAccessLogger).Run()
		accessLoggerInstance = logger
	}
}

// Access writes an access log.
func Access(from, to string, status AccessStatus, reason string) {
	accessLoggerInstance.Log(from, to, status, reason)
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/tls"
	"flag"
	"log"
	"net/http"
	"time"
)

var (
	edgeHost      = flag.String("edgeHost", "www.gov.uk", "Hostname of edge")
	originPort    = flag.Int("originPort", 8080, "Origin port to listen on for requests")
	skipVerifyTLS = flag.Bool("skipVerifyTLS", false, "Skip TLS cert verification if set")
)

// These consts and vars are available to all tests.
const requestTimeout = time.Second * 5

var (
	client       *http.Transport
	originServer *CDNServeMux
)

// Setup clients and servers.
func init() {

	flag.Parse()

	tlsOptions := &tls.Config{}
	if *skipVerifyTLS {
		tlsOptions.InsecureSkipVerify = true
	}

	client = &http.Transport{
		ResponseHeaderTimeout: requestTimeout,
		TLSClientConfig:       tlsOptions,
	}
	originServer = StartServer(*originPort)

	log.Println("Confirming that CDN is healthy")
	err := confirmEdgeIsHealthy(originServer, *edgeHost)
	if err != nil {
		log.Fatal(err)
	}
}
<commit_msg>Make -edgeHost option mandatory<commit_after>package main

import (
	"crypto/tls"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"
)

var (
	edgeHost      = flag.String("edgeHost", "", "Hostname of edge")
	originPort    = flag.Int("originPort", 8080, "Origin port to listen on for requests")
	skipVerifyTLS = flag.Bool("skipVerifyTLS", false, "Skip TLS cert verification if set")
)

// These consts and vars are available to all tests.
const requestTimeout = time.Second * 5

var (
	client       *http.Transport
	originServer *CDNServeMux
)

// Setup clients and servers.
func init() {

	flag.Parse()

	if *edgeHost == "" {
		fmt.Println("ERROR: -edgeHost must be set to the CDN edge hostname we wish to test against\n")
		fmt.Println("Usage:")
		flag.PrintDefaults()
		os.Exit(1)
	}

	tlsOptions := &tls.Config{}
	if *skipVerifyTLS {
		tlsOptions.InsecureSkipVerify = true
	}

	client = &http.Transport{
		ResponseHeaderTimeout: requestTimeout,
		TLSClientConfig:       tlsOptions,
	}
	originServer = StartServer(*originPort)

	log.Println("Confirming that CDN is healthy")
	err := confirmEdgeIsHealthy(originServer, *edgeHost)
	if err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package config

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"os"
	"strings"

	"github.com/cloudcredo/cloudfocker/utils"
	"github.com/cloudfoundry-incubator/candiedyaml"
)

type RunConfig struct {
	ContainerName  string
	Daemon         bool
	Mounts         map[string]string
	PublishedPorts map[int]int
	EnvVars        map[string]string
	ImageTag       string
	Command        []string
}

func NewStageRunConfig(cloudfoundryAppDir string, directories *Directories) (runConfig *RunConfig) {
	runConfig = &RunConfig{
		ContainerName: "cloudfocker-staging",
		Mounts: map[string]string{
			cloudfoundryAppDir:                  "/app",
			directories.Droplet():               "/tmp/droplet",
			utils.CloudfockerHome() + "/result": "/tmp/result",
			directories.Buildpacks():            "/tmp/cloudfockerbuildpacks",
			utils.CloudfockerHome() + "/cache":  "/tmp/cache",
			utils.CloudfockerHome() + "/focker": "/focker",
		},
		ImageTag: "cloudfocker-base:latest",
		Command:  []string{"/focker/fock", "stage", "internal"},
	}
	return
}

func NewRuntimeRunConfig(cloudfoundryDropletDir string) (runConfig *RunConfig) {
	runConfig = &RunConfig{
		ContainerName: "cloudfocker-runtime",
		Daemon:        true,
		Mounts: map[string]string{
			cloudfoundryDropletDir + "/app": "/app",
		},
		PublishedPorts: map[int]int{8080: 8080},
		EnvVars: map[string]string{
			"HOME":          "/app",
			"TMPDIR":        "/app/tmp",
			"PORT":          "8080",
			"VCAP_SERVICES": vcapServices(cloudfoundryDropletDir),
			"DATABASE_URL":  databaseURL(cloudfoundryDropletDir),
		},
		ImageTag: "cloudfocker-base:latest",
		Command: append([]string{"/bin/bash", "/app/cloudfocker-start-1c4352a23e52040ddb1857d7675fe3cc.sh", "/app"},
			parseStartCommand(cloudfoundryDropletDir)...),
	}
	return
}

func vcapServices(cloudfoundryDropletDir string) (services string) {
	servicesBytes, err := ioutil.ReadFile(cloudfoundryDropletDir + "/app/vcap_services.json")
	if err != nil {
		return
	}
	services = string(servicesBytes)
	return
}

type database struct {
	Credentials struct {
		URI string
	}
}

func databaseURL(cloudfoundryDropletDir string) (databaseURL string) {
	servicesBytes, err := ioutil.ReadFile(cloudfoundryDropletDir + "/app/vcap_services.json")
	if err != nil {
		return
	}

	var services map[string][]database

	json.Unmarshal(servicesBytes, &services)

	for _, serviceDatabase := range services {
		if len(serviceDatabase) > 0 && serviceDatabase[0].Credentials.URI != "" {
			databaseURL = serviceDatabase[0].Credentials.URI
		}
	}

	return
}

type StagingInfoYml struct {
	DetectedBuildpack string `yaml:"detected_buildpack"`
	StartCommand      string `yaml:"start_command"`
}

type ProcfileYml struct {
	Web string `yaml:"web"`
}

func parseStartCommand(cloudfoundryDropletDir string) (startCommand []string) {
	stagingInfoFile, err := os.Open(cloudfoundryDropletDir + "/staging_info.yml")
	if err == nil {
		stagingInfo := new(StagingInfoYml)
		decoder := candiedyaml.NewDecoder(stagingInfoFile)
		err = decoder.Decode(stagingInfo)
		if err != nil {
			log.Fatalf("Failed to decode document: %s", err)
		}
		startCommand = strings.Split(stagingInfo.StartCommand, " ")
		if startCommand[0] != "" {
			return
		}
		procfileFile, err := os.Open(cloudfoundryDropletDir + "/app/Procfile")
		if err == nil {
			procfileInfo := new(ProcfileYml)
			decoder := candiedyaml.NewDecoder(procfileFile)
			err = decoder.Decode(procfileInfo)
			if err != nil {
				log.Fatalf("Failed to decode document: %s", err)
			}
			startCommand = strings.Split(procfileInfo.Web, " ")
			return
		}
	}
	log.Fatal("Unable to find staging_info.yml")
	return
}
<commit_msg>added description of container volume mappings<commit_after>package config

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"os"
	"strings"

	"github.com/cloudcredo/cloudfocker/utils"
	"github.com/cloudfoundry-incubator/candiedyaml"
)

type RunConfig struct {
	ContainerName  string
	Daemon         bool
	Mounts         map[string]string
	PublishedPorts map[int]int
	EnvVars        map[string]string
	ImageTag       string
	Command        []string
}

func NewStageRunConfig(cloudfoundryAppDir string, directories *Directories) (runConfig *RunConfig) {
	runConfig = &RunConfig{
		ContainerName: "cloudfocker-staging",
		Mounts: map[string]string{ // host dir: container dir
			cloudfoundryAppDir:                  "/app",
			directories.Droplet():               "/tmp/droplet",
			utils.CloudfockerHome() + "/result": "/tmp/result",
			directories.Buildpacks():            "/tmp/cloudfockerbuildpacks",
			utils.CloudfockerHome() + "/cache":  "/tmp/cache",
			utils.CloudfockerHome() + "/focker": "/focker",
		},
		ImageTag: "cloudfocker-base:latest",
		Command:  []string{"/focker/fock", "stage", "internal"},
	}
	return
}

func NewRuntimeRunConfig(cloudfoundryDropletDir string) (runConfig *RunConfig) {
	runConfig = &RunConfig{
		ContainerName: "cloudfocker-runtime",
		Daemon:        true,
		Mounts: map[string]string{
			cloudfoundryDropletDir + "/app": "/app",
		},
		PublishedPorts: map[int]int{8080: 8080},
		EnvVars: map[string]string{
			"HOME":          "/app",
			"TMPDIR":        "/app/tmp",
			"PORT":          "8080",
			"VCAP_SERVICES": vcapServices(cloudfoundryDropletDir),
			"DATABASE_URL":  databaseURL(cloudfoundryDropletDir),
		},
		ImageTag: "cloudfocker-base:latest",
		Command: append([]string{"/bin/bash", "/app/cloudfocker-start-1c4352a23e52040ddb1857d7675fe3cc.sh", "/app"},
			parseStartCommand(cloudfoundryDropletDir)...),
	}
	return
}

func vcapServices(cloudfoundryDropletDir string) (services string) {
	servicesBytes, err := ioutil.ReadFile(cloudfoundryDropletDir + "/app/vcap_services.json")
	if err != nil {
		return
	}
	services = string(servicesBytes)
	return
}

type database struct {
	Credentials struct {
		URI string
	}
}

func databaseURL(cloudfoundryDropletDir string) (databaseURL string) {
	servicesBytes, err := ioutil.ReadFile(cloudfoundryDropletDir + "/app/vcap_services.json")
	if err != nil {
		return
	}

	var services map[string][]database

	json.Unmarshal(servicesBytes, &services)

	for _, serviceDatabase := range services {
		if len(serviceDatabase) > 0 && serviceDatabase[0].Credentials.URI != "" {
			databaseURL = serviceDatabase[0].Credentials.URI
		}
	}

	return
}

type StagingInfoYml struct {
	DetectedBuildpack string `yaml:"detected_buildpack"`
	StartCommand      string `yaml:"start_command"`
}

type ProcfileYml struct {
	Web string `yaml:"web"`
}

func parseStartCommand(cloudfoundryDropletDir string) (startCommand []string) {
	stagingInfoFile, err := os.Open(cloudfoundryDropletDir + "/staging_info.yml")
	if err == nil {
		stagingInfo := new(StagingInfoYml)
		decoder := candiedyaml.NewDecoder(stagingInfoFile)
		err = decoder.Decode(stagingInfo)
		if err != nil {
			log.Fatalf("Failed to decode document: %s", err)
		}
		startCommand = strings.Split(stagingInfo.StartCommand, " ")
		if startCommand[0] != "" {
			return
		}
		procfileFile, err := os.Open(cloudfoundryDropletDir + "/app/Procfile")
		if err == nil {
			procfileInfo := new(ProcfileYml)
			decoder := candiedyaml.NewDecoder(procfileFile)
			err = decoder.Decode(procfileInfo)
			if err != nil {
				log.Fatalf("Failed to decode document: %s", err)
			}
			startCommand = strings.Split(procfileInfo.Web, " ")
			return
		}
	}
	log.Fatal("Unable to find staging_info.yml")
	return
}
<|endoftext|>


<commit_before>package ehttp

import (
	"bufio"
	"fmt"
	"net"
	"net/http"
	"sync/atomic"
)

// ResponseWriter wraps http.ResponseWriter and holds
// a flag to know if the headers have been sent as well has
// the http code sent.
type ResponseWriter struct {
	http.ResponseWriter
	code *int32
}

// NewResponseWriter instantiates a new ehttp ResponseWriter.
func NewResponseWriter(w http.ResponseWriter) *ResponseWriter {
	return &ResponseWriter{
		ResponseWriter: w,
		code:           new(int32),
	}
}

// Code return the http code stored in the response writer.
func (w ResponseWriter) Code() int {
	return int(atomic.LoadInt32(w.code))
}

// WriteHeader wraps underlying WriteHeader
// - flag that the headers have been sent
// - store the sent code
func (w *ResponseWriter) WriteHeader(code int) {
	atomic.CompareAndSwapInt32(w.code, 0, int32(code))
	w.ResponseWriter.WriteHeader(code)
}

// Write wraps the underlying Write and flag that the headers have been sent.
func (w *ResponseWriter) Write(buf []byte) (int, error) {
	atomic.CompareAndSwapInt32(w.code, 0, int32(http.StatusOK))
	return w.ResponseWriter.Write(buf)
}

// Hijack wraps the underlying Hijack if available.
func (w *ResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	hijacker, ok := w.ResponseWriter.(http.Hijacker)
	if !ok {
		return nil, nil, fmt.Errorf("not a hijacker")
	}
	return hijacker.Hijack()
}

// Flush wraps the underlying Flush. Panic if not a Flusher.
func (w *ResponseWriter) Flush() {
	w.ResponseWriter.(http.Flusher).Flush()
}
<commit_msg>Minor bugfix: don't send headers if already sent<commit_after>package ehttp

import (
	"bufio"
	"fmt"
	"net"
	"net/http"
	"sync/atomic"
)

// ResponseWriter wraps http.ResponseWriter and holds
// a flag to know if the headers have been sent as well has
// the http code sent.
type ResponseWriter struct {
	http.ResponseWriter
	code *int32
}

// NewResponseWriter instantiates a new ehttp ResponseWriter.
func NewResponseWriter(w http.ResponseWriter) *ResponseWriter {
	return &ResponseWriter{
		ResponseWriter: w,
		code:           new(int32),
	}
}

// Code return the http code stored in the response writer.
func (w ResponseWriter) Code() int {
	return int(atomic.LoadInt32(w.code))
}

// WriteHeader wraps underlying WriteHeader.
// - sends the header only if not already sent.
// - flag that the headers have been sent
// - store the sent code
func (w *ResponseWriter) WriteHeader(code int) {
	if atomic.CompareAndSwapInt32(w.code, 0, int32(code)) {
		w.ResponseWriter.WriteHeader(code)
	}
}

// Write wraps the underlying Write and flag that the headers have been sent.
// Following the net/http behavior, if no http status code has been set, assume http.StatusOK.
func (w *ResponseWriter) Write(buf []byte) (int, error) {
	atomic.CompareAndSwapInt32(w.code, 0, int32(http.StatusOK))
	return w.ResponseWriter.Write(buf)
}

// Hijack wraps the underlying Hijack if available.
func (w *ResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	hijacker, ok := w.ResponseWriter.(http.Hijacker)
	if !ok {
		return nil, nil, fmt.Errorf("not a hijacker")
	}
	return hijacker.Hijack()
}

// Flush wraps the underlying Flush. Panic if not a Flusher.
func (w *ResponseWriter) Flush() {
	w.ResponseWriter.(http.Flusher).Flush()
}
<|endoftext|>


<commit_before><commit_msg>Fix package comment<commit_after><|endoftext|>


<commit_before>package gorethink

import (
	"bufio"
	"encoding/binary"
	"fmt"
	"io"

	p "github.com/dancannon/gorethink/ql2"
)

// Write 'data' to conn
func (c *Connection) writeData(data []byte) error {
	_, err := c.conn.Write(data[:])
	if err != nil {
		return RqlConnectionError{err.Error()}
	}

	return nil
}

func (c *Connection) writeHandshakeReq() error {
	pos := 0
	dataLen := 4 + 4 + len(c.opts.AuthKey) + 4

	data := c.buf.takeSmallBuffer(dataLen)
	if data == nil {
		return RqlDriverError{ErrBusyBuffer.Error()}
	}

	// Send the protocol version to the server as a 4-byte little-endian-encoded integer
	binary.LittleEndian.PutUint32(data[pos:], uint32(p.VersionDummy_V0_3))
	pos += 4

	// Send the length of the auth key to the server as a 4-byte little-endian-encoded integer
	binary.LittleEndian.PutUint32(data[pos:], uint32(len(c.opts.AuthKey)))
	pos += 4

	// Send the auth key as an ASCII string
	if len(c.opts.AuthKey) > 0 {
		pos += copy(data[pos:], c.opts.AuthKey)
	}

	// Send the protocol type as a 4-byte little-endian-encoded integer
	binary.LittleEndian.PutUint32(data[pos:], uint32(p.VersionDummy_JSON))
	pos += 4

	return c.writeData(data)
}

func (c *Connection) readHandshakeSuccess() error {
	reader := bufio.NewReader(c.conn)
	line, err := reader.ReadBytes('\x00')
	if err != nil {
		if err == io.EOF {
			return fmt.Errorf("Unexpected EOF: %s", string(line))
		}
		return RqlConnectionError{err.Error()}
	}
	// convert to string and remove trailing NUL byte
	response := string(line[:len(line)-1])
	if response != "SUCCESS" {
		// we failed authorization or something else terrible happened
		return RqlDriverError{fmt.Sprintf("Server dropped connection with message: \"%s\"", response)}
	}

	return nil
}

func (c *Connection) writeQuery(token int64, q []byte) error {
	pos := 0
	dataLen := 8 + 4 + len(q)

	data := c.buf.takeSmallBuffer(dataLen)
	if data == nil {
		return RqlDriverError{ErrBusyBuffer.Error()}
	}

	// Send the protocol version to the server as a 4-byte little-endian-encoded integer
	binary.LittleEndian.PutUint64(data[pos:], uint64(token))
	pos += 8

	// Send the length of the auth key to the server as a 4-byte little-endian-encoded integer
	binary.LittleEndian.PutUint32(data[pos:], uint32(len(q)))
	pos += 4

	// Send the auth key as an ASCII string
	pos += copy(data[pos:], q)

	return c.writeData(data)
}
<commit_msg>Fixed writeQuery being too small when sending large queries<commit_after>package gorethink

import (
	"bufio"
	"encoding/binary"
	"fmt"
	"io"

	p "github.com/dancannon/gorethink/ql2"
)

// Write 'data' to conn
func (c *Connection) writeData(data []byte) error {
	_, err := c.conn.Write(data[:])
	if err != nil {
		return RqlConnectionError{err.Error()}
	}

	return nil
}

func (c *Connection) writeHandshakeReq() error {
	pos := 0
	dataLen := 4 + 4 + len(c.opts.AuthKey) + 4

	data := c.buf.takeSmallBuffer(dataLen)
	if data == nil {
		return RqlDriverError{ErrBusyBuffer.Error()}
	}

	// Send the protocol version to the server as a 4-byte little-endian-encoded integer
	binary.LittleEndian.PutUint32(data[pos:], uint32(p.VersionDummy_V0_3))
	pos += 4

	// Send the length of the auth key to the server as a 4-byte little-endian-encoded integer
	binary.LittleEndian.PutUint32(data[pos:], uint32(len(c.opts.AuthKey)))
	pos += 4

	// Send the auth key as an ASCII string
	if len(c.opts.AuthKey) > 0 {
		pos += copy(data[pos:], c.opts.AuthKey)
	}

	// Send the protocol type as a 4-byte little-endian-encoded integer
	binary.LittleEndian.PutUint32(data[pos:], uint32(p.VersionDummy_JSON))
	pos += 4

	return c.writeData(data)
}

func (c *Connection) readHandshakeSuccess() error {
	reader := bufio.NewReader(c.conn)
	line, err := reader.ReadBytes('\x00')
	if err != nil {
		if err == io.EOF {
			return fmt.Errorf("Unexpected EOF: %s", string(line))
		}
		return RqlConnectionError{err.Error()}
	}
	// convert to string and remove trailing NUL byte
	response := string(line[:len(line)-1])
	if response != "SUCCESS" {
		// we failed authorization or something else terrible happened
		return RqlDriverError{fmt.Sprintf("Server dropped connection with message: \"%s\"", response)}
	}

	return nil
}

func (c *Connection)(token int64, q []byte) error {
	pos := 0
	dataLen := 8 + 4 + len(q)

	data := c.buf.takeBuffer(dataLen)
	if data == nil {
		return RqlDriverError{ErrBusyBuffer.Error()}
	}

	// Send the protocol version to the server as a 4-byte little-endian-encoded integer
	binary.LittleEndian.PutUint64(data[pos:], uint64(token))
	pos += 8

	// Send the length of the auth key to the server as a 4-byte little-endian-encoded integer
	binary.LittleEndian.PutUint32(data[pos:], uint32(len(q)))
	pos += 4

	// Send the auth key as an ASCII string
	pos += copy(data[pos:], q)

	return c.writeData(data)
}
<|endoftext|>


<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 testing

import (
	"math/rand"
	"testing"

	admissionv1beta1 "k8s.io/api/admission/v1beta1"
	admissionregv1beta1 "k8s.io/api/admissionregistration/v1beta1"
	appsv1 "k8s.io/api/apps/v1"
	appsv1beta1 "k8s.io/api/apps/v1beta1"
	appsv1beta2 "k8s.io/api/apps/v1beta2"
	authenticationv1 "k8s.io/api/authentication/v1"
	authenticationv1beta1 "k8s.io/api/authentication/v1beta1"
	authorizationv1 "k8s.io/api/authorization/v1"
	authorizationv1beta1 "k8s.io/api/authorization/v1beta1"
	autoscalingv1 "k8s.io/api/autoscaling/v1"
	autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
	batchv1 "k8s.io/api/batch/v1"
	batchv1beta1 "k8s.io/api/batch/v1beta1"
	batchv2alpha1 "k8s.io/api/batch/v2alpha1"
	certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
	corev1 "k8s.io/api/core/v1"
	eventsv1beta1 "k8s.io/api/events/v1beta1"
	extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
	imagepolicyv1alpha1 "k8s.io/api/imagepolicy/v1alpha1"
	networkingv1 "k8s.io/api/networking/v1"
	policyv1beta1 "k8s.io/api/policy/v1beta1"
	rbacv1 "k8s.io/api/rbac/v1"
	rbacv1alpha1 "k8s.io/api/rbac/v1alpha1"
	rbacv1beta1 "k8s.io/api/rbac/v1beta1"
	schedulingv1 "k8s.io/api/scheduling/v1"
	schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1"
	schedulingv1beta1 "k8s.io/api/scheduling/v1beta1"
	settingsv1alpha1 "k8s.io/api/settings/v1alpha1"
	storagev1 "k8s.io/api/storage/v1"
	storagev1alpha1 "k8s.io/api/storage/v1alpha1"
	storagev1beta1 "k8s.io/api/storage/v1beta1"

	"github.com/stretchr/testify/require"
	"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
	"k8s.io/apimachinery/pkg/api/apitesting/roundtrip"
	genericfuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/serializer"
)

var groups = []runtime.SchemeBuilder{
	admissionv1beta1.SchemeBuilder,
	admissionregv1beta1.SchemeBuilder,
	appsv1beta1.SchemeBuilder,
	appsv1beta2.SchemeBuilder,
	appsv1.SchemeBuilder,
	authenticationv1beta1.SchemeBuilder,
	authenticationv1.SchemeBuilder,
	authorizationv1beta1.SchemeBuilder,
	authorizationv1.SchemeBuilder,
	autoscalingv1.SchemeBuilder,
	autoscalingv2beta1.SchemeBuilder,
	batchv2alpha1.SchemeBuilder,
	batchv1beta1.SchemeBuilder,
	batchv1.SchemeBuilder,
	certificatesv1beta1.SchemeBuilder,
	corev1.SchemeBuilder,
	eventsv1beta1.SchemeBuilder,
	extensionsv1beta1.SchemeBuilder,
	imagepolicyv1alpha1.SchemeBuilder,
	networkingv1.SchemeBuilder,
	policyv1beta1.SchemeBuilder,
	rbacv1alpha1.SchemeBuilder,
	rbacv1beta1.SchemeBuilder,
	rbacv1.SchemeBuilder,
	schedulingv1alpha1.SchemeBuilder,
	schedulingv1beta1.SchemeBuilder,
	schedulingv1.SchemeBuilder,
	settingsv1alpha1.SchemeBuilder,
	storagev1alpha1.SchemeBuilder,
	storagev1beta1.SchemeBuilder,
	storagev1.SchemeBuilder,
}

func TestRoundTripExternalTypes(t *testing.T) {
	scheme := runtime.NewScheme()
	codecs := serializer.NewCodecFactory(scheme)
	for _, builder := range groups {
		require.NoError(t, builder.AddToScheme(scheme))
	}
	seed := rand.Int63()
	// I'm only using the generic fuzzer funcs, but at some point in time we might need to
	// switch to specialized. For now we're happy with the current serialization test.
	fuzzer := fuzzer.FuzzerFor(genericfuzzer.Funcs, rand.NewSource(seed), codecs)

	roundtrip.RoundTripExternalTypes(t, scheme, codecs, fuzzer, nil)
}

func TestCompatibility(t *testing.T) {
	scheme := runtime.NewScheme()
	for _, builder := range groups {
		require.NoError(t, builder.AddToScheme(scheme))
	}
	roundtrip.NewCompatibilityTestOptions(scheme).Complete(t).Run(t)
}
<commit_msg>Add missing API groups to compatibility test<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 testing

import (
	"math/rand"
	"testing"

	admissionv1beta1 "k8s.io/api/admission/v1beta1"
	admissionregv1beta1 "k8s.io/api/admissionregistration/v1beta1"
	appsv1 "k8s.io/api/apps/v1"
	appsv1beta1 "k8s.io/api/apps/v1beta1"
	appsv1beta2 "k8s.io/api/apps/v1beta2"
	authenticationv1 "k8s.io/api/authentication/v1"
	authenticationv1beta1 "k8s.io/api/authentication/v1beta1"
	authorizationv1 "k8s.io/api/authorization/v1"
	authorizationv1beta1 "k8s.io/api/authorization/v1beta1"
	autoscalingv1 "k8s.io/api/autoscaling/v1"
	autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
	autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2"
	batchv1 "k8s.io/api/batch/v1"
	batchv1beta1 "k8s.io/api/batch/v1beta1"
	batchv2alpha1 "k8s.io/api/batch/v2alpha1"
	certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
	coordinationv1 "k8s.io/api/coordination/v1"
	coordinationv1beta1 "k8s.io/api/coordination/v1beta1"
	corev1 "k8s.io/api/core/v1"
	eventsv1beta1 "k8s.io/api/events/v1beta1"
	extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
	imagepolicyv1alpha1 "k8s.io/api/imagepolicy/v1alpha1"
	networkingv1 "k8s.io/api/networking/v1"
	networkingv1beta1 "k8s.io/api/networking/v1beta1"
	nodev1alpha1 "k8s.io/api/node/v1alpha1"
	nodev1beta1 "k8s.io/api/node/v1beta1"
	policyv1beta1 "k8s.io/api/policy/v1beta1"
	rbacv1 "k8s.io/api/rbac/v1"
	rbacv1alpha1 "k8s.io/api/rbac/v1alpha1"
	rbacv1beta1 "k8s.io/api/rbac/v1beta1"
	schedulingv1 "k8s.io/api/scheduling/v1"
	schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1"
	schedulingv1beta1 "k8s.io/api/scheduling/v1beta1"
	settingsv1alpha1 "k8s.io/api/settings/v1alpha1"
	storagev1 "k8s.io/api/storage/v1"
	storagev1alpha1 "k8s.io/api/storage/v1alpha1"
	storagev1beta1 "k8s.io/api/storage/v1beta1"

	"github.com/stretchr/testify/require"
	"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
	"k8s.io/apimachinery/pkg/api/apitesting/roundtrip"
	genericfuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/serializer"
)

var groups = []runtime.SchemeBuilder{
	admissionv1beta1.SchemeBuilder,
	admissionregv1beta1.SchemeBuilder,
	appsv1beta1.SchemeBuilder,
	appsv1beta2.SchemeBuilder,
	appsv1.SchemeBuilder,
	authenticationv1beta1.SchemeBuilder,
	authenticationv1.SchemeBuilder,
	authorizationv1beta1.SchemeBuilder,
	authorizationv1.SchemeBuilder,
	autoscalingv1.SchemeBuilder,
	autoscalingv2beta1.SchemeBuilder,
	autoscalingv2beta2.SchemeBuilder,
	batchv2alpha1.SchemeBuilder,
	batchv1beta1.SchemeBuilder,
	batchv1.SchemeBuilder,
	certificatesv1beta1.SchemeBuilder,
	coordinationv1.SchemeBuilder,
	coordinationv1beta1.SchemeBuilder,
	corev1.SchemeBuilder,
	eventsv1beta1.SchemeBuilder,
	extensionsv1beta1.SchemeBuilder,
	imagepolicyv1alpha1.SchemeBuilder,
	networkingv1.SchemeBuilder,
	networkingv1beta1.SchemeBuilder,
	nodev1alpha1.SchemeBuilder,
	nodev1beta1.SchemeBuilder,
	policyv1beta1.SchemeBuilder,
	rbacv1alpha1.SchemeBuilder,
	rbacv1beta1.SchemeBuilder,
	rbacv1.SchemeBuilder,
	schedulingv1alpha1.SchemeBuilder,
	schedulingv1beta1.SchemeBuilder,
	schedulingv1.SchemeBuilder,
	settingsv1alpha1.SchemeBuilder,
	storagev1alpha1.SchemeBuilder,
	storagev1beta1.SchemeBuilder,
	storagev1.SchemeBuilder,
}

func TestRoundTripExternalTypes(t *testing.T) {
	scheme := runtime.NewScheme()
	codecs := serializer.NewCodecFactory(scheme)
	for _, builder := range groups {
		require.NoError(t, builder.AddToScheme(scheme))
	}
	seed := rand.Int63()
	// I'm only using the generic fuzzer funcs, but at some point in time we might need to
	// switch to specialized. For now we're happy with the current serialization test.
	fuzzer := fuzzer.FuzzerFor(genericfuzzer.Funcs, rand.NewSource(seed), codecs)

	roundtrip.RoundTripExternalTypes(t, scheme, codecs, fuzzer, nil)
}

func TestCompatibility(t *testing.T) {
	scheme := runtime.NewScheme()
	for _, builder := range groups {
		require.NoError(t, builder.AddToScheme(scheme))
	}
	roundtrip.NewCompatibilityTestOptions(scheme).Complete(t).Run(t)
}
<|endoftext|>


<commit_before>package content

import (
	"encoding/json"
	"fmt"
	"log"

	"github.com/bosssauce/ponzu/management/editor"
	"github.com/bosssauce/ponzu/system/api"
)

// Referenceable enures there is a way to reference the implenting type from
// within another type's editor and from type-scoped API calls
type Referenceable interface {
	Referenced() []byte
}

// Select returns the []byte of a <select> HTML element plus internal <options> with a label.
// IMPORTANT:
// The `fieldName` argument will cause a panic if it is not exactly the string
// form of the struct field that this editor input is representing
func Select(fieldName string, p interface{}, attrs map[string]string, contentType string) []byte {
	ct, ok := Types[contentType]
	if !ok {
		log.Println("Cannot reference an invalid content type:", contentType)
		return nil
	}

	// get a handle to the underlying interface type for decoding
	t := ct()

	// decode all content type from db into options map
	// map["?type=<contentType>&id=<id>"]t.String()
	options := make(map[string]string)
	// jj := db.ContentAll(contentType + "__sorted") // make this an API call
	jj := api.ContentAll(contentType)

	for i := range jj {
		err := json.Unmarshal(jj[i], t)
		if err != nil {
			log.Println("Error decoding into reference handle:", contentType, err)
		}

		// make sure it is an Identifiable
		item, ok := t.(Identifiable)
		if !ok {
			log.Println("Cannot use type", contentType, "as a reference since it does not implement Identifiable")
			return nil
		}

		k := fmt.Sprintf("?type=%s&id=%d", contentType, item.ItemID())
		v := item.String()
		options[k] = v
	}

	return editor.Select(fieldName, p, attrs, options)
}
<commit_msg>moving reference into API package and testing API based approach<commit_after><|endoftext|>


<commit_before>package eventsocket

import (
	"encoding/json"
	"net/http"

	"github.com/gorilla/mux"
)

type controllerClient struct {
	C *httpController
}

// handle a request to create a client
func (C *controllerClient) Create(w http.ResponseWriter, r *http.Request) {
	// close the body fd when we exit
	defer r.Body.Close()

	// create the client
	client := newClient()

	json.NewEncoder(w).Encode(client)

	return
}

// handle a request to create a client
func (C *controllerClient) ServeWs(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	client, err := clientById(vars["id"])
	if err != nil {
		http.Error(w, "Client not found", 404)
		return
	}

	client.connectionUpgrade(w, r)

	client.ws.pump()

	// json.NewEncoder(w).Encode(client)

	return
}
<commit_msg>controller.client: add access origin headers<commit_after>package eventsocket

import (
	"encoding/json"
	"net/http"

	"github.com/gorilla/mux"
)

type controllerClient struct {
	C *httpController
}

// handle a request to create a client
func (C *controllerClient) Create(w http.ResponseWriter, r *http.Request) {
	// close the body fd when we exit
	defer r.Body.Close()

	// create the client
	client := newClient()

	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Content-Type", "application/json")

	json.NewEncoder(w).Encode(client)

	return
}

// handle a request to create a client
func (C *controllerClient) ServeWs(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	client, err := clientById(vars["id"])
	if err != nil {
		http.Error(w, "Client not found", 404)
		return
	}

	err = client.connectionUpgrade(w, r)
	if err != nil {
		return
	}

	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Content-Type", "application/json")

	client.ws.pump()

	// json.NewEncoder(w).Encode(client)

	return
}
<|endoftext|>


<commit_before>package controllers

import (
	"github.com/lfq7413/tomato/errs"
	"github.com/lfq7413/tomato/rest"
	"github.com/lfq7413/tomato/types"
)

// ResetController 处理 /requestPasswordReset 接口的请求
type ResetController struct {
	ObjectsController
}

// HandleResetRequest 处理通过 email 重置密码的请求
// @router / [post]
func (r *ResetController) HandleResetRequest() {
	if r.JSONBody == nil && r.JSONBody["email"] == nil {
		r.Data["json"] = errs.ErrorMessageToMap(errs.EmailMissing, "you must provide an email")
		r.ServeJSON()
		return
	}
	email := r.JSONBody["email"].(string)
	err := rest.SendPasswordResetEmail(email)
	if err != nil {
		r.Data["json"] = errs.ErrorToMap(err)
		r.ServeJSON()
		return
	}

	r.Data["json"] = types.M{}
	r.ServeJSON()
}

// Get ...
// @router / [get]
func (r *ResetController) Get() {
	r.ObjectsController.Get()
}

// Delete ...
// @router / [delete]
func (r *ResetController) Delete() {
	r.ObjectsController.Delete()
}

// Put ...
// @router / [put]
func (r *ResetController) Put() {
	r.ObjectsController.Put()
}
<commit_msg>修改错误返回值<commit_after>package controllers

import (
	"strconv"
	"strings"

	"github.com/lfq7413/tomato/errs"
	"github.com/lfq7413/tomato/rest"
	"github.com/lfq7413/tomato/types"
)

// ResetController 处理 /requestPasswordReset 接口的请求
type ResetController struct {
	ObjectsController
}

// HandleResetRequest 处理通过 email 重置密码的请求
// @router / [post]
func (r *ResetController) HandleResetRequest() {
	if r.JSONBody == nil && r.JSONBody["email"] == nil {
		r.Data["json"] = errs.ErrorMessageToMap(errs.EmailMissing, "you must provide an email")
		r.ServeJSON()
		return
	}
	email := r.JSONBody["email"].(string)
	err := rest.SendPasswordResetEmail(email)
	if err != nil {
		msg := err.Error()
		msg = strings.Replace(msg, " ", "", -1)
		if strings.Index(msg, `"code":`+strconv.Itoa(errs.ObjectNotFound)) > -1 {
			err = errs.E(errs.EmailNotFound, "No user found with email "+email)
		}

		r.Data["json"] = errs.ErrorToMap(err)
		r.ServeJSON()
		return
	}

	r.Data["json"] = types.M{}
	r.ServeJSON()
}

// Get ...
// @router / [get]
func (r *ResetController) Get() {
	r.ObjectsController.Get()
}

// Delete ...
// @router / [delete]
func (r *ResetController) Delete() {
	r.ObjectsController.Delete()
}

// Put ...
// @router / [put]
func (r *ResetController) Put() {
	r.ObjectsController.Put()
}
<|endoftext|>