<commit_before>// Copyright (c) 2015 The Httpgzip Authors.
// Use of this source code is governed by an Expat-style
// MIT license that can be found in the LICENSE file.

// Package httpgzip implements an http.Handler wrapper adding gzip
// compression for appropriate requests.
//
// It attempts to properly parse the request's Accept-Encoding header
// according to RFC 2616 and does not do a simple string search for
// "gzip" (which will fail to do the correct thing for values such as
// "*" or "identity,gzip;q=0"). It will serve either gzip or identity
// content codings (identity meaning no encoding), or return 406 Not
// Acceptable status if it can do neither.
//
// It works correctly with handlers which honour Range request headers
// (such as http.FileServer) by removing the Range header for requests
// which prefer gzip encoding. This is necessary since Range requests
// apply to the gzipped content but the wrapped handler is not aware
// of the compression when it writes byte ranges. The Accept-Ranges
// header is also stripped from corresponding responses.
//
// For requests which prefer gzip encoding a Content-Type header is
// set using http.DetectContentType if it is not set by the wrapped
// handler.
//
// Gzip implementation
//
// By default, httpgzip uses the standard library gzip
// implementation. To use the optimized gzip implementation from
// https://github.com/klauspost/compress instead, download and install
// httpgzip with the "kpgzip" build tag:
//
//     go get -tags kpgzip github.com/xi2/httpgzip
//
// or simply alter the import line in httpgzip.go.
//
// Thanks
//
// Thanks are due to Klaus Post for his blog post which inspired the
// creation of this package and is recommended reading:
//
//     https://blog.klauspost.com/gzip-performance-for-go-webservers/
package httpgzip

import (
	"bytes"
	"fmt"
	"mime"
	"net/http"
	"strconv"
	"strings"
	"sync"

	"github.com/xi2/httpgzip/internal/gzip"
)

// These constants are copied from the gzip package, so that code that
// imports this package does not also have to import the gzip package.
const (
	NoCompression      = gzip.NoCompression
	BestSpeed          = gzip.BestSpeed
	BestCompression    = gzip.BestCompression
	DefaultCompression = gzip.DefaultCompression
)

// DefaultContentTypes is the default list of content types for which
// a Handler considers gzip compression. This list originates from the
// file compression.conf within the Apache configuration found at
// https://html5boilerplate.com/.
var DefaultContentTypes = []string{
	"application/atom+xml",
	"application/font-sfnt",
	"application/javascript",
	"application/json",
	"application/ld+json",
	"application/manifest+json",
	"application/rdf+xml",
	"application/rss+xml",
	"application/schema+json",
	"application/vnd.geo+json",
	"application/vnd.ms-fontobject",
	"application/x-font-ttf",
	"application/x-javascript",
	"application/x-web-app-manifest+json",
	"application/xhtml+xml",
	"application/xml",
	"font/eot",
	"font/opentype",
	"image/bmp",
	"image/svg+xml",
	"image/vnd.microsoft.icon",
	"image/x-icon",
	"text/cache-manifest",
	"text/css",
	"text/html",
	"text/javascript",
	"text/plain",
	"text/vcard",
	"text/vnd.rim.location.xloc",
	"text/vtt",
	"text/x-component",
	"text/x-cross-domain-policy",
	"text/xml",
}

var gzipWriterPools = map[int]*sync.Pool{}

func init() {
	levels := map[int]struct{}{
		DefaultCompression: struct{}{},
		NoCompression:      struct{}{},
	}
	for i := BestSpeed; i <= BestCompression; i++ {
		levels[i] = struct{}{}
	}
	for k := range levels {
		level := k // create new variable for closure
		gzipWriterPools[level] = &sync.Pool{
			New: func() interface{} {
				w, _ := gzip.NewWriterLevel(nil, level)
				return w
			},
		}
	}
}

var gzipBufPool = sync.Pool{
	New: func() interface{} { return new(bytes.Buffer) },
}

// A gzipResponseWriter is a modified http.ResponseWriter. It adds
// gzip compression to certain responses, and there are two cases
// where this is done. Case 1 is when encs only allows gzip encoding
// and forbids identity. Case 2 is when encs prefers gzip encoding,
// the response is at least 512 bytes and the response's content type
// is in ctMap.
//
// A gzipResponseWriter sets the Content-Encoding and Content-Type
// headers when appropriate. It is important to call the Close method
// when writing is finished in order to flush and close the
// gzipResponseWriter. The slice encs must contain only encodings from
// {encGzip,encIdentity} and contain at least one encoding.
//
// If a gzip.Writer is used in order to write a response it will use a
// compression level of level.
type gzipResponseWriter struct {
	http.ResponseWriter
	httpStatus int
	ctMap      map[string]struct{}
	encs       []encoding
	level      int
	gw         *gzip.Writer
	buf        *bytes.Buffer
}

func newGzipResponseWriter(w http.ResponseWriter, ctMap map[string]struct{}, encs []encoding, level int) *gzipResponseWriter {
	buf := gzipBufPool.Get().(*bytes.Buffer)
	buf.Reset()
	return &gzipResponseWriter{
		ResponseWriter: w,
		httpStatus:     http.StatusOK,
		ctMap:          ctMap,
		encs:           encs,
		level:          level,
		buf:            buf}
}

// init gets called by Write once at least 512 bytes have been written
// to the temporary buffer buf, or by Close if it has not yet been
// called. Firstly it determines the content type, either from the
// Content-Type header, or by calling http.DetectContentType on
// buf. Then, if needed, a gzip.Writer is initialized. Lastly,
// appropriate headers are set and the ResponseWriter's WriteHeader
// method is called.
func (w *gzipResponseWriter) init() {
	cth := w.Header().Get("Content-Type")
	var ct string
	if cth != "" {
		ct = cth
	} else {
		ct = http.DetectContentType(w.buf.Bytes())
	}
	var gzipContentType bool
	if mt, _, err := mime.ParseMediaType(ct); err == nil {
		if _, ok := w.ctMap[mt]; ok {
			gzipContentType = true
		}
	}
	var useGzip bool
	if w.Header().Get("Content-Encoding") == "" && w.encs[0] == encGzip {
		if gzipContentType && w.buf.Len() >= 512 || len(w.encs) == 1 {
			useGzip = true
		}
	}
	if useGzip {
		w.gw = gzipWriterPools[w.level].Get().(*gzip.Writer)
		w.gw.Reset(w.ResponseWriter)
		w.Header().Del("Content-Length")
		w.Header().Set("Content-Encoding", "gzip")
	}
	w.Header().Del("Accept-Ranges")
	if cth == "" {
		w.Header().Set("Content-Type", ct)
	}
	w.ResponseWriter.WriteHeader(w.httpStatus)
}

func (w *gzipResponseWriter) Write(p []byte) (int, error) {
	var n, written int
	var err error
	if w.buf != nil {
		written = w.buf.Len()
		_, _ = w.buf.Write(p)
		if w.buf.Len() < 512 {
			return len(p), nil
		}
		w.init()
		p = w.buf.Bytes()
		defer func() {
			gzipBufPool.Put(w.buf)
			w.buf = nil
		}()
	}
	switch {
	case w.gw != nil:
		n, err = w.gw.Write(p)
	default:
		n, err = w.ResponseWriter.Write(p)
	}
	n -= written
	if n < 0 {
		n = 0
	}
	return n, err
}

func (w *gzipResponseWriter) WriteHeader(httpStatus int) {
	// postpone WriteHeader call until end of init method
	w.httpStatus = httpStatus
}

func (w *gzipResponseWriter) Close() (err error) {
	if w.buf != nil {
		w.init()
		p := w.buf.Bytes()
		defer func() {
			gzipBufPool.Put(w.buf)
			w.buf = nil
		}()
		switch {
		case w.gw != nil:
			_, err = w.gw.Write(p)
		default:
			_, err = w.ResponseWriter.Write(p)
		}
	}
	if w.gw != nil {
		e := w.gw.Close()
		if e != nil && err == nil {
			err = e
		}
		gzipWriterPools[w.level].Put(w.gw)
		w.gw = nil
	}
	return err
}

// An encoding is a supported content coding.
type encoding int

const (
	encIdentity encoding = iota
	encGzip
)

// acceptedEncodings returns the supported content codings that are
// accepted by the request r. It returns a slice of encodings in
// client preference order.
//
// If the Sec-WebSocket-Key header is present then compressed content
// encodings are not considered.
//
// ref: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
func acceptedEncodings(r *http.Request) []encoding {
	h := r.Header.Get("Accept-Encoding")
	swk := r.Header.Get("Sec-WebSocket-Key")
	if h == "" {
		return []encoding{encIdentity}
	}
	gzip := float64(-1)     // q-value: -1 means "not present in header"
	identity := float64(-1) // q-value: -1 means "not present in header"
	any := float64(-1)      // q-value: -1 means "not present in header"
	for _, s := range strings.Split(h, ",") {
		f := strings.Split(s, ";")
		f0 := strings.ToLower(strings.Trim(f[0], " "))
		q := float64(1.0)
		if len(f) > 1 {
			f1 := strings.ToLower(strings.Trim(f[1], " "))
			if strings.HasPrefix(f1, "q=") {
				if flt, err := strconv.ParseFloat(f1[2:], 32); err == nil {
					if flt >= 0 && flt <= 1 {
						q = flt
					}
				}
			}
		}
		if f0 == "gzip" && q > gzip && swk == "" {
			gzip = q
		}
		if f0 == "identity" && q > identity {
			identity = q
		}
		if f0 == "*" && q > any {
			any = q
		}
	}
	if identity == -1 {
		if any >= 0 {
			identity = any
		} else {
			identity = 1
		}
	}
	if gzip == -1 && any >= 0 {
		gzip = any
	}
	switch {
	case gzip <= 0 && identity <= 0:
		return []encoding{}
	case gzip <= 0:
		return []encoding{encIdentity}
	case identity <= 0:
		return []encoding{encGzip}
	case identity > gzip:
		return []encoding{encIdentity, encGzip}
	default:
		return []encoding{encGzip, encIdentity}
	}
}

// NewHandler returns a new http.Handler which wraps a handler h
// adding gzip compression to certain responses. There are two cases
// where gzip compression is done. Case 1 is responses whose requests
// only allow gzip encoding and forbid identity encoding (identity
// encoding meaning no encoding). Case 2 is responses whose requests
// prefer gzip encoding, whose size is at least 512 bytes and whose
// content types are in contentTypes. If contentTypes is nil then
// DefaultContentTypes is considered instead.
//
// The new http.Handler sets the Content-Encoding, Vary and
// Content-Type headers in its responses as appropriate. If a request
// expresses a preference for gzip encoding then any Range headers are
// removed from the request before it is passed through to h and
// Accept-Ranges headers are stripped from corresponding
// responses. This happens regardless of whether gzip encoding is
// eventually used in the response or not.
func NewHandler(h http.Handler, contentTypes []string) http.Handler {
	gzh, _ := NewHandlerLevel(h, contentTypes, DefaultCompression)
	return gzh
}

// NewHandlerLevel is like NewHandler but allows one to specify the
// gzip compression level instead of assuming DefaultCompression.
//
// The compression level can be DefaultCompression, NoCompression, or
// any integer value between BestSpeed and BestCompression
// inclusive. The error returned will be nil if the level is valid.
func NewHandlerLevel(h http.Handler, contentTypes []string, level int) (http.Handler, error) {
	switch {
	case level == DefaultCompression || level == NoCompression:
		// no action needed
	case level < BestSpeed || level > BestCompression:
		return nil, fmt.Errorf(
			"httpgzip: invalid compression level: %d", level)
	}
	if contentTypes == nil {
		contentTypes = DefaultContentTypes
	}
	ctMap := map[string]struct{}{}
	for _, ct := range contentTypes {
		ctMap[ct] = struct{}{}
	}
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// add Vary header
		w.Header().Add("Vary", "Accept-Encoding")
		// check client's accepted encodings
		encs := acceptedEncodings(r)
		// return if no acceptable encodings
		if len(encs) == 0 {
			w.WriteHeader(http.StatusNotAcceptable)
			return
		}
		if encs[0] == encGzip {
			// cannot accept Range requests for possibly gzipped
			// responses
			r.Header.Del("Range")
			// create new ResponseWriter
			w = newGzipResponseWriter(w, ctMap, encs, level)
			defer w.(*gzipResponseWriter).Close()
		}
		// call original handler's ServeHTTP
		h.ServeHTTP(w, r)
	}), nil
}
<commit_msg>Fix for Accept-Encoding and Sec-WebSocket-Key with "*"<commit_after>// Copyright (c) 2015 The Httpgzip Authors.
// Use of this source code is governed by an Expat-style
// MIT license that can be found in the LICENSE file.

// Package httpgzip implements an http.Handler wrapper adding gzip
// compression for appropriate requests.
//
// It attempts to properly parse the request's Accept-Encoding header
// according to RFC 2616 and does not do a simple string search for
// "gzip" (which will fail to do the correct thing for values such as
// "*" or "identity,gzip;q=0"). It will serve either gzip or identity
// content codings (identity meaning no encoding), or return 406 Not
// Acceptable status if it can do neither.
//
// It works correctly with handlers which honour Range request headers
// (such as http.FileServer) by removing the Range header for requests
// which prefer gzip encoding. This is necessary since Range requests
// apply to the gzipped content but the wrapped handler is not aware
// of the compression when it writes byte ranges. The Accept-Ranges
// header is also stripped from corresponding responses.
//
// For requests which prefer gzip encoding a Content-Type header is
// set using http.DetectContentType if it is not set by the wrapped
// handler.
//
// Gzip implementation
//
// By default, httpgzip uses the standard library gzip
// implementation. To use the optimized gzip implementation from
// https://github.com/klauspost/compress instead, download and install
// httpgzip with the "kpgzip" build tag:
//
//     go get -tags kpgzip github.com/xi2/httpgzip
//
// or simply alter the import line in httpgzip.go.
//
// Thanks
//
// Thanks are due to Klaus Post for his blog post which inspired the
// creation of this package and is recommended reading:
//
//     https://blog.klauspost.com/gzip-performance-for-go-webservers/
package httpgzip

import (
	"bytes"
	"fmt"
	"mime"
	"net/http"
	"strconv"
	"strings"
	"sync"

	"github.com/xi2/httpgzip/internal/gzip"
)

// These constants are copied from the gzip package, so that code that
// imports this package does not also have to import the gzip package.
const (
	NoCompression      = gzip.NoCompression
	BestSpeed          = gzip.BestSpeed
	BestCompression    = gzip.BestCompression
	DefaultCompression = gzip.DefaultCompression
)

// DefaultContentTypes is the default list of content types for which
// a Handler considers gzip compression. This list originates from the
// file compression.conf within the Apache configuration found at
// https://html5boilerplate.com/.
var DefaultContentTypes = []string{
	"application/atom+xml",
	"application/font-sfnt",
	"application/javascript",
	"application/json",
	"application/ld+json",
	"application/manifest+json",
	"application/rdf+xml",
	"application/rss+xml",
	"application/schema+json",
	"application/vnd.geo+json",
	"application/vnd.ms-fontobject",
	"application/x-font-ttf",
	"application/x-javascript",
	"application/x-web-app-manifest+json",
	"application/xhtml+xml",
	"application/xml",
	"font/eot",
	"font/opentype",
	"image/bmp",
	"image/svg+xml",
	"image/vnd.microsoft.icon",
	"image/x-icon",
	"text/cache-manifest",
	"text/css",
	"text/html",
	"text/javascript",
	"text/plain",
	"text/vcard",
	"text/vnd.rim.location.xloc",
	"text/vtt",
	"text/x-component",
	"text/x-cross-domain-policy",
	"text/xml",
}

var gzipWriterPools = map[int]*sync.Pool{}

func init() {
	levels := map[int]struct{}{
		DefaultCompression: struct{}{},
		NoCompression:      struct{}{},
	}
	for i := BestSpeed; i <= BestCompression; i++ {
		levels[i] = struct{}{}
	}
	for k := range levels {
		level := k // create new variable for closure
		gzipWriterPools[level] = &sync.Pool{
			New: func() interface{} {
				w, _ := gzip.NewWriterLevel(nil, level)
				return w
			},
		}
	}
}

var gzipBufPool = sync.Pool{
	New: func() interface{} { return new(bytes.Buffer) },
}

// A gzipResponseWriter is a modified http.ResponseWriter. It adds
// gzip compression to certain responses, and there are two cases
// where this is done. Case 1 is when encs only allows gzip encoding
// and forbids identity. Case 2 is when encs prefers gzip encoding,
// the response is at least 512 bytes and the response's content type
// is in ctMap.
//
// A gzipResponseWriter sets the Content-Encoding and Content-Type
// headers when appropriate. It is important to call the Close method
// when writing is finished in order to flush and close the
// gzipResponseWriter. The slice encs must contain only encodings from
// {encGzip,encIdentity} and contain at least one encoding.
//
// If a gzip.Writer is used in order to write a response it will use a
// compression level of level.
type gzipResponseWriter struct {
	http.ResponseWriter
	httpStatus int
	ctMap      map[string]struct{}
	encs       []encoding
	level      int
	gw         *gzip.Writer
	buf        *bytes.Buffer
}

func newGzipResponseWriter(w http.ResponseWriter, ctMap map[string]struct{}, encs []encoding, level int) *gzipResponseWriter {
	buf := gzipBufPool.Get().(*bytes.Buffer)
	buf.Reset()
	return &gzipResponseWriter{
		ResponseWriter: w,
		httpStatus:     http.StatusOK,
		ctMap:          ctMap,
		encs:           encs,
		level:          level,
		buf:            buf}
}

// init gets called by Write once at least 512 bytes have been written
// to the temporary buffer buf, or by Close if it has not yet been
// called. Firstly it determines the content type, either from the
// Content-Type header, or by calling http.DetectContentType on
// buf. Then, if needed, a gzip.Writer is initialized. Lastly,
// appropriate headers are set and the ResponseWriter's WriteHeader
// method is called.
func (w *gzipResponseWriter) init() {
	cth := w.Header().Get("Content-Type")
	var ct string
	if cth != "" {
		ct = cth
	} else {
		ct = http.DetectContentType(w.buf.Bytes())
	}
	var gzipContentType bool
	if mt, _, err := mime.ParseMediaType(ct); err == nil {
		if _, ok := w.ctMap[mt]; ok {
			gzipContentType = true
		}
	}
	var useGzip bool
	if w.Header().Get("Content-Encoding") == "" && w.encs[0] == encGzip {
		if gzipContentType && w.buf.Len() >= 512 || len(w.encs) == 1 {
			useGzip = true
		}
	}
	if useGzip {
		w.gw = gzipWriterPools[w.level].Get().(*gzip.Writer)
		w.gw.Reset(w.ResponseWriter)
		w.Header().Del("Content-Length")
		w.Header().Set("Content-Encoding", "gzip")
	}
	w.Header().Del("Accept-Ranges")
	if cth == "" {
		w.Header().Set("Content-Type", ct)
	}
	w.ResponseWriter.WriteHeader(w.httpStatus)
}

func (w *gzipResponseWriter) Write(p []byte) (int, error) {
	var n, written int
	var err error
	if w.buf != nil {
		written = w.buf.Len()
		_, _ = w.buf.Write(p)
		if w.buf.Len() < 512 {
			return len(p), nil
		}
		w.init()
		p = w.buf.Bytes()
		defer func() {
			gzipBufPool.Put(w.buf)
			w.buf = nil
		}()
	}
	switch {
	case w.gw != nil:
		n, err = w.gw.Write(p)
	default:
		n, err = w.ResponseWriter.Write(p)
	}
	n -= written
	if n < 0 {
		n = 0
	}
	return n, err
}

func (w *gzipResponseWriter) WriteHeader(httpStatus int) {
	// postpone WriteHeader call until end of init method
	w.httpStatus = httpStatus
}

func (w *gzipResponseWriter) Close() (err error) {
	if w.buf != nil {
		w.init()
		p := w.buf.Bytes()
		defer func() {
			gzipBufPool.Put(w.buf)
			w.buf = nil
		}()
		switch {
		case w.gw != nil:
			_, err = w.gw.Write(p)
		default:
			_, err = w.ResponseWriter.Write(p)
		}
	}
	if w.gw != nil {
		e := w.gw.Close()
		if e != nil && err == nil {
			err = e
		}
		gzipWriterPools[w.level].Put(w.gw)
		w.gw = nil
	}
	return err
}

// An encoding is a supported content coding.
type encoding int

const (
	encIdentity encoding = iota
	encGzip
)

// acceptedEncodings returns the supported content codings that are
// accepted by the request r. It returns a slice of encodings in
// client preference order.
//
// If the Sec-WebSocket-Key header is present then compressed content
// encodings are not considered.
//
// ref: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
func acceptedEncodings(r *http.Request) []encoding {
	h := r.Header.Get("Accept-Encoding")
	swk := r.Header.Get("Sec-WebSocket-Key")
	if h == "" {
		return []encoding{encIdentity}
	}
	gzip := float64(-1)     // q-value: -1 means "not present in header"
	identity := float64(-1) // q-value: -1 means "not present in header"
	any := float64(-1)      // q-value: -1 means "not present in header"
	for _, s := range strings.Split(h, ",") {
		f := strings.Split(s, ";")
		f0 := strings.ToLower(strings.Trim(f[0], " "))
		q := float64(1.0)
		if len(f) > 1 {
			f1 := strings.ToLower(strings.Trim(f[1], " "))
			if strings.HasPrefix(f1, "q=") {
				if flt, err := strconv.ParseFloat(f1[2:], 32); err == nil {
					if flt >= 0 && flt <= 1 {
						q = flt
					}
				}
			}
		}
		if f0 == "gzip" && q > gzip && swk == "" {
			gzip = q
		}
		if f0 == "identity" && q > identity {
			identity = q
		}
		if f0 == "*" && q > any {
			any = q
		}
	}
	if identity == -1 {
		if any >= 0 {
			identity = any
		} else {
			identity = 1
		}
	}
	if gzip == -1 && any >= 0 && swk == "" {
		gzip = any
	}
	switch {
	case gzip <= 0 && identity <= 0:
		return []encoding{}
	case gzip <= 0:
		return []encoding{encIdentity}
	case identity <= 0:
		return []encoding{encGzip}
	case identity > gzip:
		return []encoding{encIdentity, encGzip}
	default:
		return []encoding{encGzip, encIdentity}
	}
}

// NewHandler returns a new http.Handler which wraps a handler h
// adding gzip compression to certain responses. There are two cases
// where gzip compression is done. Case 1 is responses whose requests
// only allow gzip encoding and forbid identity encoding (identity
// encoding meaning no encoding). Case 2 is responses whose requests
// prefer gzip encoding, whose size is at least 512 bytes and whose
// content types are in contentTypes. If contentTypes is nil then
// DefaultContentTypes is considered instead.
//
// The new http.Handler sets the Content-Encoding, Vary and
// Content-Type headers in its responses as appropriate. If a request
// expresses a preference for gzip encoding then any Range headers are
// removed from the request before it is passed through to h and
// Accept-Ranges headers are stripped from corresponding
// responses. This happens regardless of whether gzip encoding is
// eventually used in the response or not.
func NewHandler(h http.Handler, contentTypes []string) http.Handler {
	gzh, _ := NewHandlerLevel(h, contentTypes, DefaultCompression)
	return gzh
}

// NewHandlerLevel is like NewHandler but allows one to specify the
// gzip compression level instead of assuming DefaultCompression.
//
// The compression level can be DefaultCompression, NoCompression, or
// any integer value between BestSpeed and BestCompression
// inclusive. The error returned will be nil if the level is valid.
func NewHandlerLevel(h http.Handler, contentTypes []string, level int) (http.Handler, error) {
	switch {
	case level == DefaultCompression || level == NoCompression:
		// no action needed
	case level < BestSpeed || level > BestCompression:
		return nil, fmt.Errorf(
			"httpgzip: invalid compression level: %d", level)
	}
	if contentTypes == nil {
		contentTypes = DefaultContentTypes
	}
	ctMap := map[string]struct{}{}
	for _, ct := range contentTypes {
		ctMap[ct] = struct{}{}
	}
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// add Vary header
		w.Header().Add("Vary", "Accept-Encoding")
		// check client's accepted encodings
		encs := acceptedEncodings(r)
		// return if no acceptable encodings
		if len(encs) == 0 {
			w.WriteHeader(http.StatusNotAcceptable)
			return
		}
		if encs[0] == encGzip {
			// cannot accept Range requests for possibly gzipped
			// responses
			r.Header.Del("Range")
			// create new ResponseWriter
			w = newGzipResponseWriter(w, ctMap, encs, level)
			defer w.(*gzipResponseWriter).Close()
		}
		// call original handler's ServeHTTP
		h.ServeHTTP(w, r)
	}), nil
}
<|endoftext|>


<commit_before>package gapi

import (
	"bytes"
	"encoding/json"
	"fmt"
	"regexp"
	"strconv"
)

var headerNameRegex = regexp.MustCompile(`^httpHeaderName(\d+)$`)

// DataSource represents a Grafana data source.
type DataSource struct {
	ID     int64  `json:"id,omitempty"`
	UID    string `json:"uid,omitempty"`
	Name   string `json:"name"`
	Type   string `json:"type"`
	URL    string `json:"url"`
	Access string `json:"access"`

	Database string `json:"database,omitempty"`
	User     string `json:"user,omitempty"`
	// Deprecated: Use secureJsonData.password instead.
	Password string `json:"password,omitempty"`

	OrgID     int64 `json:"orgId,omitempty"`
	IsDefault bool  `json:"isDefault"`

	BasicAuth     bool   `json:"basicAuth"`
	BasicAuthUser string `json:"basicAuthUser,omitempty"`
	// Deprecated: Use secureJsonData.basicAuthPassword instead.
	BasicAuthPassword string `json:"basicAuthPassword,omitempty"`

	// Helper to read/write http headers
	HTTPHeaders map[string]string `json:"-"`

	JSONData       JSONData       `json:"jsonData,omitempty"`
	SecureJSONData SecureJSONData `json:"secureJsonData,omitempty"`
}

// Required to avoid recursion during (un)marshal
type _DataSource DataSource

// Marshal DataSource
func (ds *DataSource) MarshalJSON() ([]byte, error) {
	dataSource := _DataSource(*ds)
	for name, value := range ds.HTTPHeaders {
		dataSource.JSONData.httpHeaderNames = append(dataSource.JSONData.httpHeaderNames, name)
		dataSource.SecureJSONData.httpHeaderValues = append(dataSource.SecureJSONData.httpHeaderValues, value)
	}

	// Sentry provider expects this value in the JSON data payload,
	// ignoring the url attribute. This hack allows passing the URL as
	// an attribute but then sends it in the payload.
	if ds.Type == "grafana-sentry-datasource" {
		dataSource.JSONData.URL = ds.URL
	}

	return json.Marshal(dataSource)
}

// Unmarshal DataSource
func (ds *DataSource) UnmarshalJSON(b []byte) (err error) {
	dataSource := _DataSource(*ds)
	if err = json.Unmarshal(b, &dataSource); err == nil {
		*ds = DataSource(dataSource)
	}
	ds.HTTPHeaders = make(map[string]string)
	for _, value := range ds.JSONData.httpHeaderNames {
		ds.HTTPHeaders[value] = "true" // HTTP Headers are not returned by the API
	}
	return err
}

// JSONData is a representation of the datasource `jsonData` property
type JSONData struct {
	// Used by all datasources
	TLSAuth           bool `json:"tlsAuth,omitempty"`
	TLSAuthWithCACert bool `json:"tlsAuthWithCACert,omitempty"`
	TLSSkipVerify     bool `json:"tlsSkipVerify,omitempty"`
	httpHeaderNames   []string

	// Used by Athena
	Catalog        string `json:"catalog,omitempty"`
	Database       string `json:"database,omitempty"`
	OutputLocation string `json:"outputLocation,omitempty"`
	Workgroup      string `json:"workgroup,omitempty"`

	// Used by Github
	GitHubURL string `json:"githubUrl,omitempty"`

	// Used by Graphite
	GraphiteVersion string `json:"graphiteVersion,omitempty"`

	// Used by Prometheus, Elasticsearch, InfluxDB, MySQL, PostgreSQL and MSSQL
	TimeInterval string `json:"timeInterval,omitempty"`

	// Used by Elasticsearch
	// From Grafana 8.x esVersion is the semantic version of Elasticsearch.
	EsVersion                  string `json:"esVersion,omitempty"`
	TimeField                  string `json:"timeField,omitempty"`
	Interval                   string `json:"interval,omitempty"`
	LogMessageField            string `json:"logMessageField,omitempty"`
	LogLevelField              string `json:"logLevelField,omitempty"`
	MaxConcurrentShardRequests int64  `json:"maxConcurrentShardRequests,omitempty"`

	// Used by Cloudwatch
	CustomMetricsNamespaces string `json:"customMetricsNamespaces,omitempty"`

	// Used by Cloudwatch, Athena
	AuthType      string `json:"authType,omitempty"`
	AssumeRoleArn string `json:"assumeRoleArn,omitempty"`
	DefaultRegion string `json:"defaultRegion,omitempty"`
	Endpoint      string `json:"endpoint,omitempty"`
	ExternalID    string `json:"externalId,omitempty"`
	Profile       string `json:"profile,omitempty"`

	// Used by OpenTSDB
	TsdbVersion    int64 `json:"tsdbVersion,omitempty"`
	TsdbResolution int64 `json:"tsdbResolution,omitempty"`

	// Used by MSSQL
	Encrypt string `json:"encrypt,omitempty"`

	// Used by PostgreSQL
	Sslmode         string `json:"sslmode,omitempty"`
	PostgresVersion int64  `json:"postgresVersion,omitempty"`
	Timescaledb     bool   `json:"timescaledb,omitempty"`

	// Used by MySQL, PostgreSQL and MSSQL
	MaxOpenConns    int64 `json:"maxOpenConns,omitempty"`
	MaxIdleConns    int64 `json:"maxIdleConns,omitempty"`
	ConnMaxLifetime int64 `json:"connMaxLifetime,omitempty"`

	// Used by Prometheus
	HTTPMethod   string `json:"httpMethod,omitempty"`
	QueryTimeout string `json:"queryTimeout,omitempty"`

	// Used by Stackdriver
	AuthenticationType string `json:"authenticationType,omitempty"`
	ClientEmail        string `json:"clientEmail,omitempty"`
	DefaultProject     string `json:"defaultProject,omitempty"`
	TokenURI           string `json:"tokenUri,omitempty"`

	// Used by Prometheus and Elasticsearch
	SigV4AssumeRoleArn string `json:"sigV4AssumeRoleArn,omitempty"`
	SigV4Auth          bool   `json:"sigV4Auth,omitempty"`
	SigV4AuthType      string `json:"sigV4AuthType,omitempty"`
	SigV4ExternalID    string `json:"sigV4ExternalID,omitempty"`
	SigV4Profile       string `json:"sigV4Profile,omitempty"`
	SigV4Region        string `json:"sigV4Region,omitempty"`

	// Used by Prometheus and Loki
	ManageAlerts    bool   `json:"manageAlerts,omitempty"`
	AlertmanagerUID string `json:"alertmanagerUid,omitempty"`

	// Used by Alertmanager
	Implementation string `json:"implementation,omitempty"`

	// Used by Sentry
	OrgSlug string `json:"orgSlug,omitempty"`
	URL     string `json:"url,omitempty"` // Sentry is not using the datasource URL attribute

	// Used by InfluxDB
	DefaultBucket string `json:"defaultBucket,omitempty"`
	Organization  string `json:"organization,omitempty"`
	Version       string `json:"version,omitempty"`
}

// Required to avoid recursion during (un)marshal
type _JSONData JSONData

// Marshal JSONData
func (jd JSONData) MarshalJSON() ([]byte, error) {
	jsonData := _JSONData(jd)
	b, err := json.Marshal(jsonData)
	if err != nil {
		return nil, err
	}
	fields := make(map[string]interface{})
	if err = json.Unmarshal(b, &fields); err != nil {
		return nil, err
	}
	for index, name := range jd.httpHeaderNames {
		fields[fmt.Sprintf("httpHeaderName%d", index+1)] = name
	}
	return json.Marshal(fields)
}

// Unmarshal JSONData
func (jd *JSONData) UnmarshalJSON(b []byte) (err error) {
	jsonData := _JSONData(*jd)
	if err = json.Unmarshal(b, &jsonData); err == nil {
		*jd = JSONData(jsonData)
	}
	fields := make(map[string]interface{})
	if err = json.Unmarshal(b, &fields); err == nil {
		headerCount := 0
		for name := range fields {
			match := headerNameRegex.FindStringSubmatch(name)
			if len(match) > 0 {
				headerCount++
			}
		}

		jd.httpHeaderNames = make([]string, headerCount)
		for name, value := range fields {
			match := headerNameRegex.FindStringSubmatch(name)
			if len(match) == 2 {
				index, err := strconv.ParseInt(match[1], 10, 64)
				if err != nil {
					return err
				}
				jd.httpHeaderNames[index-1] = value.(string)
			}
		}
	}
	return err
}

// SecureJSONData is a representation of the datasource `secureJsonData` property
type SecureJSONData struct {
	// Used by all datasources
	TLSCACert         string `json:"tlsCACert,omitempty"`
	TLSClientCert     string `json:"tlsClientCert,omitempty"`
	TLSClientKey      string `json:"tlsClientKey,omitempty"`
	Password          string `json:"password,omitempty"`
	BasicAuthPassword string `json:"basicAuthPassword,omitempty"`
	httpHeaderValues  []string

	// Used by Cloudwatch, Athena
	AccessKey string `json:"accessKey,omitempty"`
	SecretKey string `json:"secretKey,omitempty"`

	// Used by Stackdriver
	PrivateKey string `json:"privateKey,omitempty"`

	// Used by Prometheus and Elasticsearch
	SigV4AccessKey string `json:"sigV4AccessKey,omitempty"`
	SigV4SecretKey string `json:"sigV4SecretKey,omitempty"`

	// Used by GitHub
	AccessToken string `json:"accessToken,omitempty"`

	// Used by Sentry
	AuthToken string `json:"authToken,omitempty"`
}

// Required to avoid recursion during unmarshal
type _SecureJSONData SecureJSONData

// Marshal SecureJSONData
func (sjd SecureJSONData) MarshalJSON() ([]byte, error) {
	secureJSONData := _SecureJSONData(sjd)
	b, err := json.Marshal(secureJSONData)
	if err != nil {
		return nil, err
	}
	fields := make(map[string]interface{})
	if err = json.Unmarshal(b, &fields); err != nil {
		return nil, err
	}
	for index, value := range sjd.httpHeaderValues {
		fields[fmt.Sprintf("httpHeaderValue%d", index+1)] = value
	}
	return json.Marshal(fields)
}

// NewDataSource creates a new Grafana data source.
func (c *Client) NewDataSource(s *DataSource) (int64, error) {
	data, err := json.Marshal(s)
	if err != nil {
		return 0, err
	}

	result := struct {
		ID int64 `json:"id"`
	}{}

	err = c.request("POST", "/api/datasources", nil, bytes.NewBuffer(data), &result)
	if err != nil {
		return 0, err
	}

	return result.ID, err
}

// UpdateDataSource updates a Grafana data source.
func (c *Client) UpdateDataSource(s *DataSource) error {
	path := fmt.Sprintf("/api/datasources/%d", s.ID)
	data, err := json.Marshal(s)
	if err != nil {
		return err
	}

	return c.request("PUT", path, nil, bytes.NewBuffer(data), nil)
}

// DataSource fetches and returns the Grafana data source whose ID it's passed.
func (c *Client) DataSource(id int64) (*DataSource, error) {
	path := fmt.Sprintf("/api/datasources/%d", id)
	result := &DataSource{}
	err := c.request("GET", path, nil, nil, result)
	if err != nil {
		return nil, err
	}

	return result, err
}

// DeleteDataSource deletes the Grafana data source whose ID it's passed.
func (c *Client) DeleteDataSource(id int64) error {
	path := fmt.Sprintf("/api/datasources/%d", id)

	return c.request("DELETE", path, nil, nil, nil)
}
<commit_msg>Allow getting a datasource by its UID (#63)<commit_after>package gapi

import (
	"bytes"
	"encoding/json"
	"fmt"
	"regexp"
	"strconv"
)

var headerNameRegex = regexp.MustCompile(`^httpHeaderName(\d+)$`)

// DataSource represents a Grafana data source.
type DataSource struct {
	ID     int64  `json:"id,omitempty"`
	UID    string `json:"uid,omitempty"`
	Name   string `json:"name"`
	Type   string `json:"type"`
	URL    string `json:"url"`
	Access string `json:"access"`

	Database string `json:"database,omitempty"`
	User     string `json:"user,omitempty"`
	// Deprecated: Use secureJsonData.password instead.
	Password string `json:"password,omitempty"`

	OrgID     int64 `json:"orgId,omitempty"`
	IsDefault bool  `json:"isDefault"`

	BasicAuth     bool   `json:"basicAuth"`
	BasicAuthUser string `json:"basicAuthUser,omitempty"`
	// Deprecated: Use secureJsonData.basicAuthPassword instead.
	BasicAuthPassword string `json:"basicAuthPassword,omitempty"`

	// Helper to read/write http headers
	HTTPHeaders map[string]string `json:"-"`

	JSONData       JSONData       `json:"jsonData,omitempty"`
	SecureJSONData SecureJSONData `json:"secureJsonData,omitempty"`
}

// Required to avoid recursion during (un)marshal
type _DataSource DataSource

// Marshal DataSource
func (ds *DataSource) MarshalJSON() ([]byte, error) {
	dataSource := _DataSource(*ds)
	for name, value := range ds.HTTPHeaders {
		dataSource.JSONData.httpHeaderNames = append(dataSource.JSONData.httpHeaderNames, name)
		dataSource.SecureJSONData.httpHeaderValues = append(dataSource.SecureJSONData.httpHeaderValues, value)
	}

	// Sentry provider expects this value in the JSON data payload,
	// ignoring the url attribute. This hack allows passing the URL as
	// an attribute but then sends it in the payload.
	if ds.Type == "grafana-sentry-datasource" {
		dataSource.JSONData.URL = ds.URL
	}

	return json.Marshal(dataSource)
}

// Unmarshal DataSource
func (ds *DataSource) UnmarshalJSON(b []byte) (err error) {
	dataSource := _DataSource(*ds)
	if err = json.Unmarshal(b, &dataSource); err == nil {
		*ds = DataSource(dataSource)
	}
	ds.HTTPHeaders = make(map[string]string)
	for _, value := range ds.JSONData.httpHeaderNames {
		ds.HTTPHeaders[value] = "true" // HTTP Headers are not returned by the API
	}
	return err
}

// JSONData is a representation of the datasource `jsonData` property
type JSONData struct {
	// Used by all datasources
	TLSAuth           bool `json:"tlsAuth,omitempty"`
	TLSAuthWithCACert bool `json:"tlsAuthWithCACert,omitempty"`
	TLSSkipVerify     bool `json:"tlsSkipVerify,omitempty"`
	httpHeaderNames   []string

	// Used by Athena
	Catalog        string `json:"catalog,omitempty"`
	Database       string `json:"database,omitempty"`
	OutputLocation string `json:"outputLocation,omitempty"`
	Workgroup      string `json:"workgroup,omitempty"`

	// Used by Github
	GitHubURL string `json:"githubUrl,omitempty"`

	// Used by Graphite
	GraphiteVersion string `json:"graphiteVersion,omitempty"`

	// Used by Prometheus, Elasticsearch, InfluxDB, MySQL, PostgreSQL and MSSQL
	TimeInterval string `json:"timeInterval,omitempty"`

	// Used by Elasticsearch
	// From Grafana 8.x esVersion is the semantic version of Elasticsearch.
	EsVersion                  string `json:"esVersion,omitempty"`
	TimeField                  string `json:"timeField,omitempty"`
	Interval                   string `json:"interval,omitempty"`
	LogMessageField            string `json:"logMessageField,omitempty"`
	LogLevelField              string `json:"logLevelField,omitempty"`
	MaxConcurrentShardRequests int64  `json:"maxConcurrentShardRequests,omitempty"`

	// Used by Cloudwatch
	CustomMetricsNamespaces string `json:"customMetricsNamespaces,omitempty"`

	// Used by Cloudwatch, Athena
	AuthType      string `json:"authType,omitempty"`
	AssumeRoleArn string `json:"assumeRoleArn,omitempty"`
	DefaultRegion string `json:"defaultRegion,omitempty"`
	Endpoint      string `json:"endpoint,omitempty"`
	ExternalID    string `json:"externalId,omitempty"`
	Profile       string `json:"profile,omitempty"`

	// Used by OpenTSDB
	TsdbVersion    int64 `json:"tsdbVersion,omitempty"`
	TsdbResolution int64 `json:"tsdbResolution,omitempty"`

	// Used by MSSQL
	Encrypt string `json:"encrypt,omitempty"`

	// Used by PostgreSQL
	Sslmode         string `json:"sslmode,omitempty"`
	PostgresVersion int64  `json:"postgresVersion,omitempty"`
	Timescaledb     bool   `json:"timescaledb,omitempty"`

	// Used by MySQL, PostgreSQL and MSSQL
	MaxOpenConns    int64 `json:"maxOpenConns,omitempty"`
	MaxIdleConns    int64 `json:"maxIdleConns,omitempty"`
	ConnMaxLifetime int64 `json:"connMaxLifetime,omitempty"`

	// Used by Prometheus
	HTTPMethod   string `json:"httpMethod,omitempty"`
	QueryTimeout string `json:"queryTimeout,omitempty"`

	// Used by Stackdriver
	AuthenticationType string `json:"authenticationType,omitempty"`
	ClientEmail        string `json:"clientEmail,omitempty"`
	DefaultProject     string `json:"defaultProject,omitempty"`
	TokenURI           string `json:"tokenUri,omitempty"`

	// Used by Prometheus and Elasticsearch
	SigV4AssumeRoleArn string `json:"sigV4AssumeRoleArn,omitempty"`
	SigV4Auth          bool   `json:"sigV4Auth,omitempty"`
	SigV4AuthType      string `json:"sigV4AuthType,omitempty"`
	SigV4ExternalID    string `json:"sigV4ExternalID,omitempty"`
	SigV4Profile       string `json:"sigV4Profile,omitempty"`
	SigV4Region        string `json:"sigV4Region,omitempty"`

	// Used by Prometheus and Loki
	ManageAlerts    bool   `json:"manageAlerts,omitempty"`
	AlertmanagerUID string `json:"alertmanagerUid,omitempty"`

	// Used by Alertmanager
	Implementation string `json:"implementation,omitempty"`

	// Used by Sentry
	OrgSlug string `json:"orgSlug,omitempty"`
	URL     string `json:"url,omitempty"` // Sentry is not using the datasource URL attribute

	// Used by InfluxDB
	DefaultBucket string `json:"defaultBucket,omitempty"`
	Organization  string `json:"organization,omitempty"`
	Version       string `json:"version,omitempty"`
}

// Required to avoid recursion during (un)marshal
type _JSONData JSONData

// Marshal JSONData
func (jd JSONData) MarshalJSON() ([]byte, error) {
	jsonData := _JSONData(jd)
	b, err := json.Marshal(jsonData)
	if err != nil {
		return nil, err
	}
	fields := make(map[string]interface{})
	if err = json.Unmarshal(b, &fields); err != nil {
		return nil, err
	}
	for index, name := range jd.httpHeaderNames {
		fields[fmt.Sprintf("httpHeaderName%d", index+1)] = name
	}
	return json.Marshal(fields)
}

// Unmarshal JSONData
func (jd *JSONData) UnmarshalJSON(b []byte) (err error) {
	jsonData := _JSONData(*jd)
	if err = json.Unmarshal(b, &jsonData); err == nil {
		*jd = JSONData(jsonData)
	}
	fields := make(map[string]interface{})
	if err = json.Unmarshal(b, &fields); err == nil {
		headerCount := 0
		for name := range fields {
			match := headerNameRegex.FindStringSubmatch(name)
			if len(match) > 0 {
				headerCount++
			}
		}

		jd.httpHeaderNames = make([]string, headerCount)
		for name, value := range fields {
			match := headerNameRegex.FindStringSubmatch(name)
			if len(match) == 2 {
				index, err := strconv.ParseInt(match[1], 10, 64)
				if err != nil {
					return err
				}
				jd.httpHeaderNames[index-1] = value.(string)
			}
		}
	}
	return err
}

// SecureJSONData is a representation of the datasource `secureJsonData` property
type SecureJSONData struct {
	// Used by all datasources
	TLSCACert         string `json:"tlsCACert,omitempty"`
	TLSClientCert     string `json:"tlsClientCert,omitempty"`
	TLSClientKey      string `json:"tlsClientKey,omitempty"`
	Password          string `json:"password,omitempty"`
	BasicAuthPassword string `json:"basicAuthPassword,omitempty"`
	httpHeaderValues  []string

	// Used by Cloudwatch, Athena
	AccessKey string `json:"accessKey,omitempty"`
	SecretKey string `json:"secretKey,omitempty"`

	// Used by Stackdriver
	PrivateKey string `json:"privateKey,omitempty"`

	// Used by Prometheus and Elasticsearch
	SigV4AccessKey string `json:"sigV4AccessKey,omitempty"`
	SigV4SecretKey string `json:"sigV4SecretKey,omitempty"`

	// Used by GitHub
	AccessToken string `json:"accessToken,omitempty"`

	// Used by Sentry
	AuthToken string `json:"authToken,omitempty"`
}

// Required to avoid recursion during unmarshal
type _SecureJSONData SecureJSONData

// Marshal SecureJSONData
func (sjd SecureJSONData) MarshalJSON() ([]byte, error) {
	secureJSONData := _SecureJSONData(sjd)
	b, err := json.Marshal(secureJSONData)
	if err != nil {
		return nil, err
	}
	fields := make(map[string]interface{})
	if err = json.Unmarshal(b, &fields); err != nil {
		return nil, err
	}
	for index, value := range sjd.httpHeaderValues {
		fields[fmt.Sprintf("httpHeaderValue%d", index+1)] = value
	}
	return json.Marshal(fields)
}

// NewDataSource creates a new Grafana data source.
func (c *Client) NewDataSource(s *DataSource) (int64, error) {
	data, err := json.Marshal(s)
	if err != nil {
		return 0, err
	}

	result := struct {
		ID int64 `json:"id"`
	}{}

	err = c.request("POST", "/api/datasources", nil, bytes.NewBuffer(data), &result)
	if err != nil {
		return 0, err
	}

	return result.ID, err
}

// UpdateDataSource updates a Grafana data source.
func (c *Client) UpdateDataSource(s *DataSource) error {
	path := fmt.Sprintf("/api/datasources/%d", s.ID)
	data, err := json.Marshal(s)
	if err != nil {
		return err
	}

	return c.request("PUT", path, nil, bytes.NewBuffer(data), nil)
}

// DataSource fetches and returns the Grafana data source whose ID it's passed.
func (c *Client) DataSource(id int64) (*DataSource, error) {
	path := fmt.Sprintf("/api/datasources/%d", id)
	result := &DataSource{}
	err := c.request("GET", path, nil, nil, result)
	if err != nil {
		return nil, err
	}

	return result, err
}

// DataSourceByUID fetches and returns the Grafana data source whose UID is passed.
func (c *Client) DataSourceByUID(uid string) (*DataSource, error) {
	path := fmt.Sprintf("/api/datasources/uid/%s", uid)
	result := &DataSource{}
	err := c.request("GET", path, nil, nil, result)
	if err != nil {
		return nil, err
	}

	return result, err
}

// DeleteDataSource deletes the Grafana data source whose ID it's passed.
func (c *Client) DeleteDataSource(id int64) error {
	path := fmt.Sprintf("/api/datasources/%d", id)

	return c.request("DELETE", path, nil, nil, nil)
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/md5"
	"fmt"
	"strconv"
	"time"
)

const (
	input = "cxdnnyjw"
)

func checkPass(pass []byte) bool {
	for _, i := range pass {
		if i == 0 {
			return true
		}
	}
	return false
}

func checkHash(hash string) bool {
	for j := 0; j < 5; j++ {
		if hash[j] != '0' {
			return false
		}
	}
	return true
}

func main() {
	start := time.Now()

	password := ""
	passDone := make(chan bool)
	iterations := 0

	go func() {
		j := 0
		i := 0
		for i = 0; j < 8; i++ {
			hash := fmt.Sprintf("%x", md5.Sum([]byte(input+strconv.Itoa(i))))
			if checkHash(hash) {
				password += string(hash[5])
				fmt.Println(password)
				j++
			}
		}
		iterations = i
		passDone <- true
	}()

	password2 := [8]byte{0, 0, 0, 0, 0, 0, 0, 0}
	pass2Done := make(chan bool)
	iterations2 := 0
	go func() {
		i := 0
		for i = 0; checkPass(password2[:]); i++ {
			hash := fmt.Sprintf("%x", md5.Sum([]byte(input+strconv.Itoa(i))))
			if checkHash(hash) {
				k := hash[5] - '0'
				if k < 8 && password2[k] == 0 {
					password2[k] = hash[6]
					fmt.Println(fmt.Sprintf("%c", password2))
				}
			}
		}
		iterations2 = i
		pass2Done <- true
	}()

	<-passDone
	<-pass2Done

	fmt.Println(fmt.Sprintf("\npassword: %s in %d iterations", password, iterations))
	fmt.Println(fmt.Sprintf("\npassword2: %c in %d iterations", password2, iterations2))

	fmt.Println(fmt.Sprintf("\ntime elapsed: %s", time.Since(start)))
}
<commit_msg>day 5 pipeline<commit_after>package main

import (
	"crypto/md5"
	"fmt"
	"strconv"
	"sync"
	"time"
)

type (
	Vec2 struct {
		X byte
		Y byte
	}
)

const (
	input = "cxdnnyjw"
)

func checkHash(hash string) bool {
	for j := 0; j < 5; j++ {
		if hash[j] != '0' {
			return false
		}
	}
	return true
}

func numGen(done <-chan bool) <-chan int {
	send := make(chan int, 128)
	go func() {
		defer close(send)
		for i := 0; true; i++ {
			select {
			case <-done:
				return
			case send <- i:
			}
		}
	}()
	return send
}

func hashWorker(done <-chan bool, receive <-chan int, send chan<- Vec2) {
	for i := range receive {
		hash := fmt.Sprintf("%x", md5.Sum([]byte(input+strconv.Itoa(i))))
		if checkHash(hash) {
			select {
			case <-done:
				return
			case send <- Vec2{hash[5], hash[6]}:
			}
		}
	}
}

func multiSendVec(done <-chan bool, receive <-chan Vec2, send ...chan<- Vec2) {
	for i := range receive {
		for _, j := range send {
			select {
			case <-done:
				return
			case j <- i:
			}
		}
	}
}

func checkPass(pass []byte) bool {
	for _, i := range pass {
		if i == 0 {
			return true
		}
	}
	return false
}

func main() {
	start := time.Now()

	done := make(chan bool)

	n := numGen(done)
	hashChan := make(chan Vec2, 128)
	for i := 0; i < 4; i++ {
		go hashWorker(done, n, hashChan)
	}

	passSend := make(chan Vec2, 128)
	pass2Send := make(chan Vec2, 128)

	go multiSendVec(done, hashChan, passSend, pass2Send)

	var wg sync.WaitGroup

	password := ""
	wg.Add(1)
	go func() {
		defer wg.Done()
		j := 0
		for j = 0; j < 8; j++ {
			hash := <-passSend
			password += string(hash.X)
			fmt.Println(password)
		}
	}()

	password2 := [8]byte{0, 0, 0, 0, 0, 0, 0, 0}
	wg.Add(1)
	go func() {
		defer wg.Done()
		for checkPass(password2[:]) {
			hash := <-pass2Send
			k := hash.X - '0'
			if k < 8 && password2[k] == 0 {
				password2[k] = hash.Y
				fmt.Println(fmt.Sprintf("%c", password2))
			}
		}
	}()

	wg.Wait()
	close(done)

	fmt.Println(fmt.Sprintf("\npassword: %s", password))
	fmt.Println(fmt.Sprintf("\npassword2: %c", password2))

	fmt.Println(fmt.Sprintf("\ntime elapsed: %s", time.Since(start)))
}
<|endoftext|>


<commit_before>/*
Copyright 2016 Google Inc. 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 build

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"

	"github.com/bazelbuild/buildtools/tables"
)

// exists reports whether the named file exists.
func exists(name string) bool {
	_, err := os.Stat(name)
	return err == nil
}

// Test that reading and then writing the golden files
// does not change their output.
func TestPrintGolden(t *testing.T) {
	outs, chdir := findTests(t, ".golden")
	defer chdir()
	for _, out := range outs {
		if strings.Contains(out, ".stripslashes.") {
			tables.StripLabelLeadingSlashes = true
		}
		if strings.Contains(out, "/050.") {
			tables.ShortenAbsoluteLabelsToRelative = true
		}
		testPrint(t, out, out, false)
		tables.StripLabelLeadingSlashes = false
		tables.ShortenAbsoluteLabelsToRelative = false
	}
}

// Test that formatting the input files produces the golden files.
func TestPrintRewrite(t *testing.T) {
	ins, chdir := findTests(t, ".in")
	defer chdir()
	for _, in := range ins {
		prefix := in[:len(in)-len(".in")]
		out := prefix + ".golden"

		if strings.Contains(out, "/050.") {
			tables.ShortenAbsoluteLabelsToRelative = true
		}

		testPrint(t, in, out, true)
		strippedOut := prefix + ".stripslashes.golden"
		if exists(strippedOut) {
			tables.StripLabelLeadingSlashes = true
			testPrint(t, in, strippedOut, true)
			tables.StripLabelLeadingSlashes = false
		}

		tables.ShortenAbsoluteLabelsToRelative = false
	}
}

// findTests finds all files of the passed suffix in the build/testdata directory.
// It changes the working directory to be the directory containing the `testdata` directory,
// and returns a function to call to change back to the current directory.
// This allows tests to assert on alias finding between absolute and relative labels.
func findTests(t *testing.T, suffix string) ([]string, func()) {
	wd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}
	if err := os.Chdir(filepath.Join(os.Getenv("TEST_SRCDIR"), os.Getenv("TEST_WORKSPACE"), "build")); err != nil {
		t.Fatal(err)
	}
	outs, err := filepath.Glob("testdata/*" + suffix)
	if err != nil {
		t.Fatal(err)
	}
	if len(outs) == 0 {
		t.Fatal("Didn't find any test cases")
	}
	return outs, func() { os.Chdir(wd) }
}

// testPrint is a helper for testing the printer.
// It reads the file named in, reformats it, and compares
// the result to the file named out. If rewrite is true, the
// reformatting includes buildifier's higher-level rewrites.
func testPrint(t *testing.T, in, out string, rewrite bool) {
	data, err := ioutil.ReadFile(in)
	if err != nil {
		t.Error(err)
		return
	}

	golden, err := ioutil.ReadFile(out)
	if err != nil {
		t.Error(err)
		return
	}

	base := "testdata/" + filepath.Base(in)
	bld, err := Parse(base, data)
	if err != nil {
		t.Error(err)
		return
	}

	if rewrite {
		Rewrite(bld, nil)
	}

	ndata := Format(bld)

	if !bytes.Equal(ndata, golden) {
		t.Errorf("formatted %s incorrectly: diff shows -%s, +ours", base, filepath.Base(out))
		tdiff(t, string(golden), string(ndata))
		return
	}
}

// Test that when files in the testdata directory are parsed
// and printed and parsed again, we get the same parse tree
// both times.
func TestPrintParse(t *testing.T) {
	outs, chdir := findTests(t, "")
	defer chdir()
	for _, out := range outs {
		data, err := ioutil.ReadFile(out)
		if err != nil {
			t.Error(err)
			continue
		}

		base := "testdata/" + filepath.Base(out)
		f, err := Parse(base, data)
		if err != nil {
			t.Errorf("parsing original: %v", err)
		}

		ndata := Format(f)

		f2, err := Parse(base, ndata)
		if err != nil {
			t.Errorf("parsing reformatted: %v", err)
		}

		eq := eqchecker{file: base}
		if err := eq.check(f, f2); err != nil {
			t.Errorf("not equal: %v", err)
		}
	}
}

// An eqchecker holds state for checking the equality of two parse trees.
type eqchecker struct {
	file string
	pos  Position
}

// errorf returns an error described by the printf-style format and arguments,
// inserting the current file position before the error text.
func (eq *eqchecker) errorf(format string, args ...interface{}) error {
	return fmt.Errorf("%s:%d: %s", eq.file, eq.pos.Line,
		fmt.Sprintf(format, args...))
}

// check checks that v and w represent the same parse tree.
// If not, it returns an error describing the first difference.
func (eq *eqchecker) check(v, w interface{}) error {
	return eq.checkValue(reflect.ValueOf(v), reflect.ValueOf(w))
}

var (
	posType        = reflect.TypeOf(Position{})
	commentsType   = reflect.TypeOf(Comments{})
	parenType      = reflect.TypeOf((*ParenExpr)(nil))
	stringExprType = reflect.TypeOf(StringExpr{})
)

// checkValue checks that v and w represent the same parse tree.
// If not, it returns an error describing the first difference.
func (eq *eqchecker) checkValue(v, w reflect.Value) error {
	// inner returns the innermost expression for v.
	// If v is a parenthesized expression (X) it returns x.
	// if v is a non-nil interface value, it returns the concrete
	// value in the interface.
	inner := func(v reflect.Value) reflect.Value {
		for v.IsValid() {
			if v.Type() == parenType {
				v = v.Elem().FieldByName("X")
				continue
			}
			if v.Kind() == reflect.Interface && !v.IsNil() {
				v = v.Elem()
				continue
			}
			break
		}
		return v
	}

	v = inner(v)
	w = inner(w)

	if v.Kind() != w.Kind() {
		return eq.errorf("%s became %s", v.Kind(), w.Kind())
	}

	// There is nothing to compare for zero values, so exit early.
	if !v.IsValid() {
		return nil
	}

	if v.Type() != w.Type() {
		return eq.errorf("%s became %s", v.Type(), w.Type())
	}

	if p, ok := v.Interface().(Expr); ok {
		eq.pos, _ = p.Span()
	}

	switch v.Kind() {
	default:
		return eq.errorf("unexpected type %s", v.Type())

	case reflect.Bool, reflect.Int, reflect.String:
		vi := v.Interface()
		wi := w.Interface()
		if vi != wi {
			return eq.errorf("%v became %v", vi, wi)
		}

	case reflect.Slice:
		vl := v.Len()
		wl := w.Len()
		for i := 0; i < vl || i < wl; i++ {
			if i >= vl {
				return eq.errorf("unexpected %s", w.Index(i).Type())
			}
			if i >= wl {
				return eq.errorf("missing %s", v.Index(i).Type())
			}
			if err := eq.checkValue(v.Index(i), w.Index(i)); err != nil {
				return err
			}
		}

	case reflect.Struct:
		// Fields in struct must match.
		t := v.Type()
		n := t.NumField()
		for i := 0; i < n; i++ {
			tf := t.Field(i)
			switch {
			default:
				if err := eq.checkValue(v.Field(i), w.Field(i)); err != nil {
					return err
				}

			case tf.Type == posType: // ignore positions
			case tf.Type == commentsType: // ignore comment assignment
			case tf.Name == "MultiLine": // ignore multiline setting
			case tf.Name == "LineBreak": // ignore line break setting
			case t == stringExprType && tf.Name == "Token": // ignore raw string token
			}
		}

	case reflect.Ptr, reflect.Interface:
		if v.IsNil() != w.IsNil() {
			if v.IsNil() {
				return eq.errorf("unexpected %s", w.Elem().Type())
			}
			return eq.errorf("missing %s", v.Elem().Type())
		}
		if err := eq.checkValue(v.Elem(), w.Elem()); err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>Add comment<commit_after>/*
Copyright 2016 Google Inc. 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 build

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"

	"github.com/bazelbuild/buildtools/tables"
)

// exists reports whether the named file exists.
func exists(name string) bool {
	_, err := os.Stat(name)
	return err == nil
}

// Test that reading and then writing the golden files
// does not change their output.
func TestPrintGolden(t *testing.T) {
	outs, chdir := findTests(t, ".golden")
	defer chdir()
	for _, out := range outs {
		if strings.Contains(out, ".stripslashes.") {
			tables.StripLabelLeadingSlashes = true
		}
		// Test file 050 tests the ShortenAbsoluteLabelsToRelative behavior, all other tests assume that ShortenAbsoluteLabelsToRelative is false.
		if strings.Contains(out, "/050.") {
			tables.ShortenAbsoluteLabelsToRelative = true
		}
		testPrint(t, out, out, false)
		tables.StripLabelLeadingSlashes = false
		tables.ShortenAbsoluteLabelsToRelative = false
	}
}

// Test that formatting the input files produces the golden files.
func TestPrintRewrite(t *testing.T) {
	ins, chdir := findTests(t, ".in")
	defer chdir()
	for _, in := range ins {
		prefix := in[:len(in)-len(".in")]
		out := prefix + ".golden"

		// Test file 050 tests the ShortenAbsoluteLabelsToRelative behavior, all other tests assume that ShortenAbsoluteLabelsToRelative is false.
		if strings.Contains(out, "/050.") {
			tables.ShortenAbsoluteLabelsToRelative = true
		}

		testPrint(t, in, out, true)
		strippedOut := prefix + ".stripslashes.golden"
		if exists(strippedOut) {
			tables.StripLabelLeadingSlashes = true
			testPrint(t, in, strippedOut, true)
			tables.StripLabelLeadingSlashes = false
		}

		tables.ShortenAbsoluteLabelsToRelative = false
	}
}

// findTests finds all files of the passed suffix in the build/testdata directory.
// It changes the working directory to be the directory containing the `testdata` directory,
// and returns a function to call to change back to the current directory.
// This allows tests to assert on alias finding between absolute and relative labels.
func findTests(t *testing.T, suffix string) ([]string, func()) {
	wd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}
	if err := os.Chdir(filepath.Join(os.Getenv("TEST_SRCDIR"), os.Getenv("TEST_WORKSPACE"), "build")); err != nil {
		t.Fatal(err)
	}
	outs, err := filepath.Glob("testdata/*" + suffix)
	if err != nil {
		t.Fatal(err)
	}
	if len(outs) == 0 {
		t.Fatal("Didn't find any test cases")
	}
	return outs, func() { os.Chdir(wd) }
}

// testPrint is a helper for testing the printer.
// It reads the file named in, reformats it, and compares
// the result to the file named out. If rewrite is true, the
// reformatting includes buildifier's higher-level rewrites.
func testPrint(t *testing.T, in, out string, rewrite bool) {
	data, err := ioutil.ReadFile(in)
	if err != nil {
		t.Error(err)
		return
	}

	golden, err := ioutil.ReadFile(out)
	if err != nil {
		t.Error(err)
		return
	}

	base := "testdata/" + filepath.Base(in)
	bld, err := Parse(base, data)
	if err != nil {
		t.Error(err)
		return
	}

	if rewrite {
		Rewrite(bld, nil)
	}

	ndata := Format(bld)

	if !bytes.Equal(ndata, golden) {
		t.Errorf("formatted %s incorrectly: diff shows -%s, +ours", base, filepath.Base(out))
		tdiff(t, string(golden), string(ndata))
		return
	}
}

// Test that when files in the testdata directory are parsed
// and printed and parsed again, we get the same parse tree
// both times.
func TestPrintParse(t *testing.T) {
	outs, chdir := findTests(t, "")
	defer chdir()
	for _, out := range outs {
		data, err := ioutil.ReadFile(out)
		if err != nil {
			t.Error(err)
			continue
		}

		base := "testdata/" + filepath.Base(out)
		f, err := Parse(base, data)
		if err != nil {
			t.Errorf("parsing original: %v", err)
		}

		ndata := Format(f)

		f2, err := Parse(base, ndata)
		if err != nil {
			t.Errorf("parsing reformatted: %v", err)
		}

		eq := eqchecker{file: base}
		if err := eq.check(f, f2); err != nil {
			t.Errorf("not equal: %v", err)
		}
	}
}

// An eqchecker holds state for checking the equality of two parse trees.
type eqchecker struct {
	file string
	pos  Position
}

// errorf returns an error described by the printf-style format and arguments,
// inserting the current file position before the error text.
func (eq *eqchecker) errorf(format string, args ...interface{}) error {
	return fmt.Errorf("%s:%d: %s", eq.file, eq.pos.Line,
		fmt.Sprintf(format, args...))
}

// check checks that v and w represent the same parse tree.
// If not, it returns an error describing the first difference.
func (eq *eqchecker) check(v, w interface{}) error {
	return eq.checkValue(reflect.ValueOf(v), reflect.ValueOf(w))
}

var (
	posType        = reflect.TypeOf(Position{})
	commentsType   = reflect.TypeOf(Comments{})
	parenType      = reflect.TypeOf((*ParenExpr)(nil))
	stringExprType = reflect.TypeOf(StringExpr{})
)

// checkValue checks that v and w represent the same parse tree.
// If not, it returns an error describing the first difference.
func (eq *eqchecker) checkValue(v, w reflect.Value) error {
	// inner returns the innermost expression for v.
	// If v is a parenthesized expression (X) it returns x.
	// if v is a non-nil interface value, it returns the concrete
	// value in the interface.
	inner := func(v reflect.Value) reflect.Value {
		for v.IsValid() {
			if v.Type() == parenType {
				v = v.Elem().FieldByName("X")
				continue
			}
			if v.Kind() == reflect.Interface && !v.IsNil() {
				v = v.Elem()
				continue
			}
			break
		}
		return v
	}

	v = inner(v)
	w = inner(w)

	if v.Kind() != w.Kind() {
		return eq.errorf("%s became %s", v.Kind(), w.Kind())
	}

	// There is nothing to compare for zero values, so exit early.
	if !v.IsValid() {
		return nil
	}

	if v.Type() != w.Type() {
		return eq.errorf("%s became %s", v.Type(), w.Type())
	}

	if p, ok := v.Interface().(Expr); ok {
		eq.pos, _ = p.Span()
	}

	switch v.Kind() {
	default:
		return eq.errorf("unexpected type %s", v.Type())

	case reflect.Bool, reflect.Int, reflect.String:
		vi := v.Interface()
		wi := w.Interface()
		if vi != wi {
			return eq.errorf("%v became %v", vi, wi)
		}

	case reflect.Slice:
		vl := v.Len()
		wl := w.Len()
		for i := 0; i < vl || i < wl; i++ {
			if i >= vl {
				return eq.errorf("unexpected %s", w.Index(i).Type())
			}
			if i >= wl {
				return eq.errorf("missing %s", v.Index(i).Type())
			}
			if err := eq.checkValue(v.Index(i), w.Index(i)); err != nil {
				return err
			}
		}

	case reflect.Struct:
		// Fields in struct must match.
		t := v.Type()
		n := t.NumField()
		for i := 0; i < n; i++ {
			tf := t.Field(i)
			switch {
			default:
				if err := eq.checkValue(v.Field(i), w.Field(i)); err != nil {
					return err
				}

			case tf.Type == posType: // ignore positions
			case tf.Type == commentsType: // ignore comment assignment
			case tf.Name == "MultiLine": // ignore multiline setting
			case tf.Name == "LineBreak": // ignore line break setting
			case t == stringExprType && tf.Name == "Token": // ignore raw string token
			}
		}

	case reflect.Ptr, reflect.Interface:
		if v.IsNil() != w.IsNil() {
			if v.IsNil() {
				return eq.errorf("unexpected %s", w.Elem().Type())
			}
			return eq.errorf("missing %s", v.Elem().Type())
		}
		if err := eq.checkValue(v.Elem(), w.Elem()); err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package dev

import (
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/bmizerany/pat"
	"github.com/puma/puma-dev/httpu"
	"github.com/puma/puma-dev/httputil"
)

type HTTPServer struct {
	Address    string
	TLSAddress string
	Pool       *AppPool
	Debug      bool
	Events     *Events

	mux       *pat.PatternServeMux
	transport *httpu.Transport
	proxy     *httputil.ReverseProxy
}

func (h *HTTPServer) Setup() {
	h.transport = &httpu.Transport{
		Dial: (&net.Dialer{
			Timeout:   5 * time.Second,
			KeepAlive: 10 * time.Second,
		}).Dial,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	}

	h.Pool.AppClosed = h.AppClosed

	h.proxy = &httputil.ReverseProxy{
		Proxy:         h.proxyReq,
		Transport:     h.transport,
		FlushInterval: 1 * time.Second,
		Debug:         h.Debug,
	}

	h.mux = pat.New()

	h.mux.Get("/status", http.HandlerFunc(h.status))
	h.mux.Get("/events", http.HandlerFunc(h.events))
}

func (h *HTTPServer) AppClosed(app *App) {
	// Whenever an app is closed, wipe out all idle conns. This
	// obviously closes down more than just this one apps connections
	// but that's ok.
	h.transport.CloseIdleConnections()
}

func pruneSub(name string) string {
	dot := strings.IndexByte(name, '.')
	if dot == -1 {
		return ""
	}

	return name[dot+1:]
}

func (h *HTTPServer) findApp(name string) (*App, error) {
	var (
		app *App
		err error
	)

	for name != "" {
		app, err = h.Pool.App(name)
		if err != nil {
			if err == ErrUnknownApp {
				name = pruneSub(name)
				continue
			}

			return nil, err
		}

		break
	}

	if app == nil {
		app, err = h.Pool.App("default")
		if err != nil {
			return nil, err
		}
	}

	err = app.WaitTilReady()
	if err != nil {
		return nil, err
	}

	return app, nil
}

func (h *HTTPServer) hostForApp(name string) (string, string, error) {
	var (
		app *App
		err error
	)

	for name != "" {
		app, err = h.Pool.App(name)
		if err != nil {
			if err == ErrUnknownApp {
				name = pruneSub(name)
				continue
			}

			return "", "", err
		}

		break
	}

	if app == nil {
		app, err = h.Pool.App("default")
		if err != nil {
			return "", "", err
		}
	}

	err = app.WaitTilReady()
	if err != nil {
		return "", "", err
	}

	return app.Scheme, app.Address(), nil
}

func (h *HTTPServer) removeTLD(host string) string {
	colon := strings.LastIndexByte(host, ':')
	if colon != -1 {
		if h, _, err := net.SplitHostPort(host); err == nil {
			host = h
		}
	}

	if strings.HasSuffix(host, ".xip.io") || strings.HasSuffix(host, ".nip.io") {
		parts := strings.Split(host, ".")
		if len(parts) < 6 {
			return ""
		}

		name := strings.Join(parts[:len(parts)-6], ".")

		return name
	}

	dot := strings.LastIndexByte(host, '.')

	if dot == -1 {
		return host
	} else {
		return host[:dot]
	}
}

func (h *HTTPServer) proxyReq(w http.ResponseWriter, req *http.Request) error {
	name := h.removeTLD(req.Host)

	app, err := h.findApp(name)
	if err != nil {
		if err == ErrUnknownApp {
			h.Events.Add("unknown_app", "name", name, "host", req.Host)
		} else {
			h.Events.Add("lookup_error", "error", err.Error())
		}

		return err
	}

	if app.Public && req.URL.Path != "/" {
		path := filepath.Join(app.dir, "public", path.Clean(req.URL.Path))

		fi, err := os.Stat(path)
		if err == nil && !fi.IsDir() {
			http.ServeFile(w, req, path)
			return httputil.ErrHandled
		}
	}

	req.URL.Scheme, req.URL.Host = app.Scheme, app.Address()
	return err
}

func (h *HTTPServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if h.Debug {
		fmt.Fprintf(os.Stderr, "%s: %s '%s' (host=%s)\n",
			time.Now().Format(time.RFC3339Nano),
			req.Method, req.URL.Path, req.Host)
	}

	if req.Host == "puma-dev" {
		h.mux.ServeHTTP(w, req)
	} else {
		h.proxy.ServeHTTP(w, req)
	}
}

func (h *HTTPServer) status(w http.ResponseWriter, req *http.Request) {
	type appStatus struct {
		Scheme  string `json:"scheme"`
		Address string `json:"address"`
		Status  string `json:"status"`
		Log     string `json:"log"`
	}

	statuses := map[string]appStatus{}

	h.Pool.ForApps(func(a *App) {
		var status string

		switch a.Status() {
		case Dead:
			status = "dead"
		case Booting:
			status = "booting"
		case Running:
			status = "running"
		default:
			status = "unknown"
		}

		statuses[a.Name] = appStatus{
			Scheme:  a.Scheme,
			Address: a.Address(),
			Status:  status,
			Log:     a.Log(),
		}
	})

	json.NewEncoder(w).Encode(statuses)
}

func (h *HTTPServer) events(w http.ResponseWriter, req *http.Request) {
	h.Events.WriteTo(w)
}
<commit_msg>remove unused method<commit_after>package dev

import (
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/bmizerany/pat"
	"github.com/puma/puma-dev/httpu"
	"github.com/puma/puma-dev/httputil"
)

type HTTPServer struct {
	Address    string
	TLSAddress string
	Pool       *AppPool
	Debug      bool
	Events     *Events

	mux       *pat.PatternServeMux
	transport *httpu.Transport
	proxy     *httputil.ReverseProxy
}

func (h *HTTPServer) Setup() {
	h.transport = &httpu.Transport{
		Dial: (&net.Dialer{
			Timeout:   5 * time.Second,
			KeepAlive: 10 * time.Second,
		}).Dial,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	}

	h.Pool.AppClosed = h.AppClosed

	h.proxy = &httputil.ReverseProxy{
		Proxy:         h.proxyReq,
		Transport:     h.transport,
		FlushInterval: 1 * time.Second,
		Debug:         h.Debug,
	}

	h.mux = pat.New()

	h.mux.Get("/status", http.HandlerFunc(h.status))
	h.mux.Get("/events", http.HandlerFunc(h.events))
}

func (h *HTTPServer) AppClosed(app *App) {
	// Whenever an app is closed, wipe out all idle conns. This
	// obviously closes down more than just this one apps connections
	// but that's ok.
	h.transport.CloseIdleConnections()
}

func pruneSub(name string) string {
	dot := strings.IndexByte(name, '.')
	if dot == -1 {
		return ""
	}

	return name[dot+1:]
}

func (h *HTTPServer) findApp(name string) (*App, error) {
	var (
		app *App
		err error
	)

	for name != "" {
		app, err = h.Pool.App(name)
		if err != nil {
			if err == ErrUnknownApp {
				name = pruneSub(name)
				continue
			}

			return nil, err
		}

		break
	}

	if app == nil {
		app, err = h.Pool.App("default")
		if err != nil {
			return nil, err
		}
	}

	err = app.WaitTilReady()
	if err != nil {
		return nil, err
	}

	return app, nil
}

func (h *HTTPServer) removeTLD(host string) string {
	colon := strings.LastIndexByte(host, ':')
	if colon != -1 {
		if h, _, err := net.SplitHostPort(host); err == nil {
			host = h
		}
	}

	if strings.HasSuffix(host, ".xip.io") || strings.HasSuffix(host, ".nip.io") {
		parts := strings.Split(host, ".")
		if len(parts) < 6 {
			return ""
		}

		name := strings.Join(parts[:len(parts)-6], ".")

		return name
	}

	dot := strings.LastIndexByte(host, '.')

	if dot == -1 {
		return host
	} else {
		return host[:dot]
	}
}

func (h *HTTPServer) proxyReq(w http.ResponseWriter, req *http.Request) error {
	name := h.removeTLD(req.Host)

	app, err := h.findApp(name)
	if err != nil {
		if err == ErrUnknownApp {
			h.Events.Add("unknown_app", "name", name, "host", req.Host)
		} else {
			h.Events.Add("lookup_error", "error", err.Error())
		}

		return err
	}

	if app.Public && req.URL.Path != "/" {
		path := filepath.Join(app.dir, "public", path.Clean(req.URL.Path))

		fi, err := os.Stat(path)
		if err == nil && !fi.IsDir() {
			http.ServeFile(w, req, path)
			return httputil.ErrHandled
		}
	}

	req.URL.Scheme, req.URL.Host = app.Scheme, app.Address()
	return err
}

func (h *HTTPServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if h.Debug {
		fmt.Fprintf(os.Stderr, "%s: %s '%s' (host=%s)\n",
			time.Now().Format(time.RFC3339Nano),
			req.Method, req.URL.Path, req.Host)
	}

	if req.Host == "puma-dev" {
		h.mux.ServeHTTP(w, req)
	} else {
		h.proxy.ServeHTTP(w, req)
	}
}

func (h *HTTPServer) status(w http.ResponseWriter, req *http.Request) {
	type appStatus struct {
		Scheme  string `json:"scheme"`
		Address string `json:"address"`
		Status  string `json:"status"`
		Log     string `json:"log"`
	}

	statuses := map[string]appStatus{}

	h.Pool.ForApps(func(a *App) {
		var status string

		switch a.Status() {
		case Dead:
			status = "dead"
		case Booting:
			status = "booting"
		case Running:
			status = "running"
		default:
			status = "unknown"
		}

		statuses[a.Name] = appStatus{
			Scheme:  a.Scheme,
			Address: a.Address(),
			Status:  status,
			Log:     a.Log(),
		}
	})

	json.NewEncoder(w).Encode(statuses)
}

func (h *HTTPServer) events(w http.ResponseWriter, req *http.Request) {
	h.Events.WriteTo(w)
}
<|endoftext|>


<commit_before>package buildbox

import (
	"errors"
	"fmt"
	"mime"
	"os"
	"path/filepath"
	"strings"
)

type Artifact struct {
	// The ID of the artifact
	ID string `json:"id,omitempty"`

	// The current state of the artifact. Default is "new"
	State string `json:"state,omitempty"`

	// The relative path to the file
	Path string `json:"path"`

	// The absolute path path to the file
	AbsolutePath string `json:"absolute_path"`

	// The glob path that was used to identify this file
	GlobPath string `json:"glob_path"`

	// The size of the file
	FileSize int64 `json:"file_size"`

	// Where we should upload the artifact to. If nil,
	// it will upload to Buildbox.
	URL string `json:"url,omitempty"`

	// When uploading artifacts to Buildbox, the API will return some
	// extra information on how/where to upload the file.
	Uploader struct {
		// Where/how to upload the file
		Action struct {
			// What the host to post to
			URL string `json:"url,omitempty"`

			// POST, PUT, GET, etc.
			Method string

			// What's the path at the URL we need to upload to
			Path string

			// What's the key of the file input named?
			FileInput string `json:"file_input"`
		}

		// Data that should be sent along with the upload
		Data map[string]string
	}
}

func (a Artifact) String() string {
	return fmt.Sprintf("Artifact{ID: %s, Path: %s, URL: %s, AbsolutePath: %s, GlobPath: %s, FileSize: %d}", a.ID, a.Path, a.URL, a.AbsolutePath, a.GlobPath, a.FileSize)
}

func (a Artifact) MimeType() string {
	extension := filepath.Ext(a.Path)
	mimeType := mime.TypeByExtension(extension)

	if mimeType != "" {
		return mimeType
	} else {
		return "binary/octet-stream"
	}
}

func (c *Client) ArtifactUpdate(job *Job, artifact Artifact) (*Artifact, error) {
	// Create a new instance of a artifact that will be populated
	// with the updated data by the client
	var updatedArtifact Artifact

	// Return the job.
	return &updatedArtifact, c.Put(&updatedArtifact, "jobs/"+job.ID+"/artifacts/"+artifact.ID, artifact)
}

// Sends all the artifacts at once to the Buildbox Agent API. This will allow
// the UI to show what artifacts will be uploaded. Their state starts out as
// "new"
func (c *Client) CreateArtifacts(job *Job, artifacts []*Artifact) ([]Artifact, error) {
	var createdArtifacts []Artifact

	return createdArtifacts, c.Post(&createdArtifacts, "jobs/"+job.ID+"/artifacts", artifacts)
}

func CollectArtifacts(job *Job, artifactPaths string) (artifacts []*Artifact, err error) {
	globs := strings.Split(artifactPaths, ";")
	workingDirectory, _ := os.Getwd()

	for _, glob := range globs {
		glob = strings.TrimSpace(glob)

		if glob != "" {
			Logger.Debugf("Globbing %s for %s", workingDirectory, glob)

			files, err := Glob(workingDirectory, glob)
			if err != nil {
				return nil, err
			}

			for _, file := range files {
				absolutePath, err := filepath.Abs(file)
				if err != nil {
					return nil, err
				}

				fileInfo, err := os.Stat(absolutePath)
				if fileInfo.IsDir() {
					Logger.Debugf("Skipping directory %s", file)
					continue
				}

				artifact, err := BuildArtifact(file, absolutePath, glob)
				if err != nil {
					return nil, err
				}

				artifacts = append(artifacts, artifact)
			}
		}
	}

	return artifacts, nil
}

func BuildArtifact(path string, absolutePath string, globPath string) (*Artifact, error) {
	// Temporarily open the file to get it's size
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	// Grab it's file info (which includes it's file size)
	fileInfo, err := file.Stat()
	if err != nil {
		return nil, err
	}

	// Create our new artifact data structure
	artifact := new(Artifact)
	artifact.State = "new"
	artifact.Path = path
	artifact.AbsolutePath = absolutePath
	artifact.GlobPath = globPath
	artifact.FileSize = fileInfo.Size()

	return artifact, nil
}

func UploadArtifacts(client Client, job *Job, artifacts []*Artifact, destination string) error {
	var uploader Uploader

	// Determine what uploader to use
	if destination != "" {
		if strings.HasPrefix(destination, "s3://") {
			uploader = new(S3Uploader)
		} else {
			return errors.New("Unknown upload destination: " + destination)
		}
	} else {
		uploader = new(FormUploader)
	}

	// Setup the uploader
	err := uploader.Setup(destination)
	if err != nil {
		return err
	}

	// Set the URL's of the artifacts based on the uploader
	for _, artifact := range artifacts {
		artifact.URL = uploader.URL(artifact)
	}

	// Create artifacts on buildbox in batches to prevent timeouts with many artifacts
	var lenArtifacts = len(artifacts)
	var createdArtifacts = []Artifact{}

	for i := 0; i < lenArtifacts; i += 100 {
		j := i + 100
		if lenArtifacts < j {
			j = lenArtifacts
		}

		someArtifacts := artifacts[i:j]

		someCreatedArtifacts, err := client.CreateArtifacts(job, someArtifacts)
		if err != nil {
			return err
		}

		createdArtifacts = append(createdArtifacts, someCreatedArtifacts...)
	}

	// Upload the artifacts by spinning up some routines
	var routines []chan string
	var concurrency int = 10

	Logger.Debugf("Spinning up %d concurrent threads for uploads", concurrency)

	count := 0
	for _, artifact := range createdArtifacts {
		// Create a channel and apend it to the routines array. Once we've hit our
		// concurrency limit, we'll block until one finishes, then this loop will
		// startup up again.
		count++
		wait := make(chan string)
		go uploadRoutine(wait, client, job, artifact, uploader)
		routines = append(routines, wait)

		if count >= concurrency {
			Logger.Debug("Maxiumum concurrent threads running. Waiting.")

			// Wait for all the routines to finish, then reset
			waitForRoutines(routines)
			count = 0
			routines = routines[0:0]
		}
	}

	// Wait for any other routines to finish
	waitForRoutines(routines)

	return nil
}

func uploadRoutine(quit chan string, client Client, job *Job, artifact Artifact, uploader Uploader) {
	// Show a nice message that we're starting to upload the file
	Logger.Infof("Uploading %s (%d bytes)", artifact.Path, artifact.FileSize)

	// Upload the artifact and then set the state depending on whether or not
	// it passed.
	err := uploader.Upload(&artifact)
	if err != nil {
		artifact.State = "error"
		Logger.Errorf("Error uploading artifact %s (%s)", artifact.Path, err)
	} else {
		artifact.State = "finished"
	}

	// Update the state of the artifact on Buildbox
	_, err = client.ArtifactUpdate(job, artifact)
	if err != nil {
		Logger.Errorf("Error marking artifact %s as uploaded (%s)", artifact.Path, err)
	}

	// We can notify the channel that this routine has finished now
	quit <- "finished"
}

func waitForRoutines(routines []chan string) {
	for _, r := range routines {
		<-r
	}
}
<commit_msg>Paths should be relative when uploading them to Buildbox.<commit_after>package buildbox

import (
	"errors"
	"fmt"
	"mime"
	"os"
	"path/filepath"
	"strings"
)

type Artifact struct {
	// The ID of the artifact
	ID string `json:"id,omitempty"`

	// The current state of the artifact. Default is "new"
	State string `json:"state,omitempty"`

	// The relative path to the file
	Path string `json:"path"`

	// The absolute path path to the file
	AbsolutePath string `json:"absolute_path"`

	// The glob path that was used to identify this file
	GlobPath string `json:"glob_path"`

	// The size of the file
	FileSize int64 `json:"file_size"`

	// Where we should upload the artifact to. If nil,
	// it will upload to Buildbox.
	URL string `json:"url,omitempty"`

	// When uploading artifacts to Buildbox, the API will return some
	// extra information on how/where to upload the file.
	Uploader struct {
		// Where/how to upload the file
		Action struct {
			// What the host to post to
			URL string `json:"url,omitempty"`

			// POST, PUT, GET, etc.
			Method string

			// What's the path at the URL we need to upload to
			Path string

			// What's the key of the file input named?
			FileInput string `json:"file_input"`
		}

		// Data that should be sent along with the upload
		Data map[string]string
	}
}

func (a Artifact) String() string {
	return fmt.Sprintf("Artifact{ID: %s, Path: %s, URL: %s, AbsolutePath: %s, GlobPath: %s, FileSize: %d}", a.ID, a.Path, a.URL, a.AbsolutePath, a.GlobPath, a.FileSize)
}

func (a Artifact) MimeType() string {
	extension := filepath.Ext(a.Path)
	mimeType := mime.TypeByExtension(extension)

	if mimeType != "" {
		return mimeType
	} else {
		return "binary/octet-stream"
	}
}

func (c *Client) ArtifactUpdate(job *Job, artifact Artifact) (*Artifact, error) {
	// Create a new instance of a artifact that will be populated
	// with the updated data by the client
	var updatedArtifact Artifact

	// Return the job.
	return &updatedArtifact, c.Put(&updatedArtifact, "jobs/"+job.ID+"/artifacts/"+artifact.ID, artifact)
}

// Sends all the artifacts at once to the Buildbox Agent API. This will allow
// the UI to show what artifacts will be uploaded. Their state starts out as
// "new"
func (c *Client) CreateArtifacts(job *Job, artifacts []*Artifact) ([]Artifact, error) {
	var createdArtifacts []Artifact

	return createdArtifacts, c.Post(&createdArtifacts, "jobs/"+job.ID+"/artifacts", artifacts)
}

func CollectArtifacts(job *Job, artifactPaths string) (artifacts []*Artifact, err error) {
	globs := strings.Split(artifactPaths, ";")
	workingDirectory, _ := os.Getwd()

	for _, glob := range globs {
		glob = strings.TrimSpace(glob)

		if glob != "" {
			Logger.Debugf("Globbing %s for %s", workingDirectory, glob)

			files, err := Glob(workingDirectory, glob)
			if err != nil {
				return nil, err
			}

			for _, file := range files {
				// Generate an absolute path for the artifact
				absolutePath, err := filepath.Abs(file)
				if err != nil {
					return nil, err
				}

				fileInfo, err := os.Stat(absolutePath)
				if fileInfo.IsDir() {
					Logger.Debugf("Skipping directory %s", file)
					continue
				}

				// Create a relative path (from the workingDirectory) to the artifact, by removing the
				// first part of the absolutePath that is the workingDirectory.
				relativePath := strings.Replace(absolutePath, workingDirectory, "", 1)

				// Ensure the relativePath doesn't have a file seperator "/" as the first character
				relativePath = strings.TrimPrefix(relativePath, string(os.PathSeparator))

				// Build an artifact object using the paths we have.
				artifact, err := BuildArtifact(relativePath, absolutePath, glob)
				if err != nil {
					return nil, err
				}

				artifacts = append(artifacts, artifact)
			}
		}
	}

	return artifacts, nil
}

func BuildArtifact(relativePath string, absolutePath string, globPath string) (*Artifact, error) {
	// Temporarily open the file to get it's size
	file, err := os.Open(absolutePath)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	// Grab it's file info (which includes it's file size)
	fileInfo, err := file.Stat()
	if err != nil {
		return nil, err
	}

	// Create our new artifact data structure
	artifact := new(Artifact)
	artifact.State = "new"
	artifact.Path = relativePath
	artifact.AbsolutePath = absolutePath
	artifact.GlobPath = globPath
	artifact.FileSize = fileInfo.Size()

	return artifact, nil
}

func UploadArtifacts(client Client, job *Job, artifacts []*Artifact, destination string) error {
	var uploader Uploader

	// Determine what uploader to use
	if destination != "" {
		if strings.HasPrefix(destination, "s3://") {
			uploader = new(S3Uploader)
		} else {
			return errors.New("Unknown upload destination: " + destination)
		}
	} else {
		uploader = new(FormUploader)
	}

	// Setup the uploader
	err := uploader.Setup(destination)
	if err != nil {
		return err
	}

	// Set the URL's of the artifacts based on the uploader
	for _, artifact := range artifacts {
		artifact.URL = uploader.URL(artifact)
	}

	// Create artifacts on buildbox in batches to prevent timeouts with many artifacts
	var lenArtifacts = len(artifacts)
	var createdArtifacts = []Artifact{}

	for i := 0; i < lenArtifacts; i += 100 {
		j := i + 100
		if lenArtifacts < j {
			j = lenArtifacts
		}

		someArtifacts := artifacts[i:j]

		someCreatedArtifacts, err := client.CreateArtifacts(job, someArtifacts)
		if err != nil {
			return err
		}

		createdArtifacts = append(createdArtifacts, someCreatedArtifacts...)
	}

	// Upload the artifacts by spinning up some routines
	var routines []chan string
	var concurrency int = 10

	Logger.Debugf("Spinning up %d concurrent threads for uploads", concurrency)

	count := 0
	for _, artifact := range createdArtifacts {
		// Create a channel and apend it to the routines array. Once we've hit our
		// concurrency limit, we'll block until one finishes, then this loop will
		// startup up again.
		count++
		wait := make(chan string)
		go uploadRoutine(wait, client, job, artifact, uploader)
		routines = append(routines, wait)

		if count >= concurrency {
			Logger.Debug("Maxiumum concurrent threads running. Waiting.")

			// Wait for all the routines to finish, then reset
			waitForRoutines(routines)
			count = 0
			routines = routines[0:0]
		}
	}

	// Wait for any other routines to finish
	waitForRoutines(routines)

	return nil
}

func uploadRoutine(quit chan string, client Client, job *Job, artifact Artifact, uploader Uploader) {
	// Show a nice message that we're starting to upload the file
	Logger.Infof("Uploading %s (%d bytes)", artifact.Path, artifact.FileSize)

	// Upload the artifact and then set the state depending on whether or not
	// it passed.
	err := uploader.Upload(&artifact)
	if err != nil {
		artifact.State = "error"
		Logger.Errorf("Error uploading artifact %s (%s)", artifact.Path, err)
	} else {
		artifact.State = "finished"
	}

	// Update the state of the artifact on Buildbox
	_, err = client.ArtifactUpdate(job, artifact)
	if err != nil {
		Logger.Errorf("Error marking artifact %s as uploaded (%s)", artifact.Path, err)
	}

	// We can notify the channel that this routine has finished now
	quit <- "finished"
}

func waitForRoutines(routines []chan string) {
	for _, r := range routines {
		<-r
	}
}
<|endoftext|>


<commit_before>// Copyright 2014-2015 Chadev. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/danryan/hal"
)

var lunchHandler = hear(`is today (devlunch|dev lunch) day\b`, "is today devlunch day", "Tells if today is lunch day, and what the talk is", func(res *hal.Response) error {
	d := time.Now().Weekday().String()
	if d != "Thursday" {
		msg, err := getTalkDetails(false)
		if err != nil {
			hal.Logger.Error(err)
			return res.Send("Sorry I was unable to get details on the next dev lunch.  Please check https://meetup.com/chadevs")
		}

		return res.Send(fmt.Sprintf("No, sorry!  %s", msg))
	}

	msg, err := getTalkDetails(true)
	if err != nil {
		hal.Logger.Error(err)
		return res.Send("Sorry I was unable to get details on the next dev lunch.  Please check https://meetup.com/chadevs")
	}

	return res.Send(fmt.Sprintf("Yes!  %s", msg))
})

var talkHandler = hear(`tell me about the next talk\b`, "tell me about the next talk", "Returns details on the next Chadev Lunch Talk", func(res *hal.Response) error {
	msg, err := getTalkDetails(false)
	if err != nil {
		hal.Logger.Error(err)
		return res.Send("Sorry I was unable to get details on the next dev lunch.  Please check https://meetup.com/chadevs")
	}

	return res.Send(msg)
})

var addTalkHandler = hear(`devlunch url ([a-z0-9-\s]*)(http(s)?://.+)`, "devlunch url (date) (url)", "Set live stream url for dev lunch talks", func(res *hal.Response) error {
	var d, u string
	var date time.Time

	// grab the arguments
	d = strings.TrimSpace(res.Match[1])
	u = res.Match[2]

	// if d is empty or "today" use todays date
	if d == "" || d == "today" {
		date = time.Now()
	} else {
		var err error
		date, err = time.Parse("2006-01-02", d)
		if err != nil {
			// could not parse the given date, fallback to today
			hal.Logger.Error(err)
			date = time.Now()
		}
	}

	hal.Logger.Info(fmt.Sprintf("parsed date: %v", date.Format("2006-01-02")))
	if !validateURL(u) {
		return res.Send(fmt.Sprintf("%s is not a valid URL", u))
	}

	b, err := json.Marshal(DevTalk{Date: date.Format("2006-01-02"), URL: u})
	if err != nil {
		hal.Logger.Error(err)
		return res.Send("I have failed you, I was unable to JSON")
	}

	err = res.Robot.Store.Set("devtalk", b)
	if err != nil {
		hal.Logger.Error(err)
		return res.Send("I couldn't store the live stream details")
	}

	return res.Send("Dev Talk live stream details stored")
})

var devTalkLinkHandler = hear(`link to devlunch`, "link to devlunch", "Returns the link to the dev lunch live stream", func(res *hal.Response) error {
	// check if today is Thursday
	t := time.Now()
	if t.Weekday().String() != "thursday" {
		return res.Send("Sorry today is not dev lunch day.")
	}

	// check if there is a url stored, and if the stored url is current
	b, err := res.Robot.Store.Get("devtalk")
	if err != nil || b == nil {
		hal.Logger.Error(err)
		return res.Send("Sorry, I don't have a URL for today's live stream.  You can check if it is posted to the Meeup page at http://www.meetup.com/chadevs/ or our Google+ page at https://plus.google.com/b/103401260409601780643/103401260409601780643/posts")
	}

	var talk DevTalk
	err = json.Unmarshal(b, &talk)
	if err != nil {
		hal.Logger.Error(err)
		return res.Send("Sorry, I don't have a URL for today's live stream.  You can check if it is posted to the Meeup page at http://www.meetup.com/chadevs/ or our Google+ page at https://plus.google.com/b/103401260409601780643/103401260409601780643/posts")
	}

	if talk.Date != t.Format("2006-01-02") {
		return res.Send("Sorry, I don't have a URL for today's live stream.  You can check if it is posted to the Meeup page at http://www.meetup.com/chadevs/ or our Google+ page at https://plus.google.com/b/103401260409601780643/103401260409601780643/posts")
	}

	return res.Send(fmt.Sprintf("You can access the live stream for the talk here %s", talk.URL))
})

func (m *Meetup) string(lunchDay bool) string {
	if !lunchDay {
		return fmt.Sprintf("The next talk is \"%s\", you can join us at %s on %s.  If you plan to come please make sure you have RSVPed at %s",
			m.Results[0].Name,
			m.Results[0].Venue.Name,
			m.Results[0].parseDateTime(false),
			m.Results[0].EventURL)
	}

	return fmt.Sprintf("The talk today is \"%s\", you can join us at %s on %s.  If you plan to come please make sure you have RSVPed at %s",
		m.Results[0].Name,
		m.Results[0].Venue.Name,
		m.Results[0].parseDateTime(true),
		m.Results[0].EventURL)
}

func getTalkDetails(lunchDay bool) (string, error) {
	URL := fmt.Sprintf("https://api.meetup.com/2/events?&sign=true&photo-host=secure&group_urlname=chadevs&page=20&key=%s", os.Getenv("CHADEV_MEETUP"))
	resp, err := http.Get(URL)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	var Events Meetup
	err = json.Unmarshal(body, &Events)
	if err != nil {
		return "", err
	}

	return Events.string(lunchDay), nil
}
<commit_msg>thursday != Thursday closes #28<commit_after>// Copyright 2014-2015 Chadev. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/danryan/hal"
)

var lunchHandler = hear(`is today (devlunch|dev lunch) day\b`, "is today devlunch day", "Tells if today is lunch day, and what the talk is", func(res *hal.Response) error {
	d := time.Now().Weekday().String()
	if d != "Thursday" {
		msg, err := getTalkDetails(false)
		if err != nil {
			hal.Logger.Error(err)
			return res.Send("Sorry I was unable to get details on the next dev lunch.  Please check https://meetup.com/chadevs")
		}

		return res.Send(fmt.Sprintf("No, sorry!  %s", msg))
	}

	msg, err := getTalkDetails(true)
	if err != nil {
		hal.Logger.Error(err)
		return res.Send("Sorry I was unable to get details on the next dev lunch.  Please check https://meetup.com/chadevs")
	}

	return res.Send(fmt.Sprintf("Yes!  %s", msg))
})

var talkHandler = hear(`tell me about the next talk\b`, "tell me about the next talk", "Returns details on the next Chadev Lunch Talk", func(res *hal.Response) error {
	msg, err := getTalkDetails(false)
	if err != nil {
		hal.Logger.Error(err)
		return res.Send("Sorry I was unable to get details on the next dev lunch.  Please check https://meetup.com/chadevs")
	}

	return res.Send(msg)
})

var addTalkHandler = hear(`devlunch url ([a-z0-9-\s]*)(http(s)?://.+)`, "devlunch url (date) (url)", "Set live stream url for dev lunch talks", func(res *hal.Response) error {
	var d, u string
	var date time.Time

	// grab the arguments
	d = strings.TrimSpace(res.Match[1])
	u = res.Match[2]

	// if d is empty or "today" use todays date
	if d == "" || d == "today" {
		date = time.Now()
	} else {
		var err error
		date, err = time.Parse("2006-01-02", d)
		if err != nil {
			// could not parse the given date, fallback to today
			hal.Logger.Error(err)
			date = time.Now()
		}
	}

	hal.Logger.Info(fmt.Sprintf("parsed date: %v", date.Format("2006-01-02")))
	if !validateURL(u) {
		return res.Send(fmt.Sprintf("%s is not a valid URL", u))
	}

	b, err := json.Marshal(DevTalk{Date: date.Format("2006-01-02"), URL: u})
	if err != nil {
		hal.Logger.Error(err)
		return res.Send("I have failed you, I was unable to JSON")
	}

	err = res.Robot.Store.Set("devtalk", b)
	if err != nil {
		hal.Logger.Error(err)
		return res.Send("I couldn't store the live stream details")
	}

	return res.Send("Dev Talk live stream details stored")
})

var devTalkLinkHandler = hear(`link to devlunch`, "link to devlunch", "Returns the link to the dev lunch live stream", func(res *hal.Response) error {
	// check if today is Thursday
	t := time.Now()
	if t.Weekday().String() != "Thursday" {
		return res.Send("Sorry today is not dev lunch day.")
	}

	// check if there is a url stored, and if the stored url is current
	b, err := res.Robot.Store.Get("devtalk")
	if err != nil || b == nil {
		hal.Logger.Error(err)
		return res.Send("Sorry, I don't have a URL for today's live stream.  You can check if it is posted to the Meeup page at http://www.meetup.com/chadevs/ or our Google+ page at https://plus.google.com/b/103401260409601780643/103401260409601780643/posts")
	}

	var talk DevTalk
	err = json.Unmarshal(b, &talk)
	if err != nil {
		hal.Logger.Error(err)
		return res.Send("Sorry, I don't have a URL for today's live stream.  You can check if it is posted to the Meeup page at http://www.meetup.com/chadevs/ or our Google+ page at https://plus.google.com/b/103401260409601780643/103401260409601780643/posts")
	}

	if talk.Date != t.Format("2006-01-02") {
		return res.Send("Sorry, I don't have a URL for today's live stream.  You can check if it is posted to the Meeup page at http://www.meetup.com/chadevs/ or our Google+ page at https://plus.google.com/b/103401260409601780643/103401260409601780643/posts")
	}

	return res.Send(fmt.Sprintf("You can access the live stream for the talk here %s", talk.URL))
})

func (m *Meetup) string(lunchDay bool) string {
	if !lunchDay {
		return fmt.Sprintf("The next talk is \"%s\", you can join us at %s on %s.  If you plan to come please make sure you have RSVPed at %s",
			m.Results[0].Name,
			m.Results[0].Venue.Name,
			m.Results[0].parseDateTime(false),
			m.Results[0].EventURL)
	}

	return fmt.Sprintf("The talk today is \"%s\", you can join us at %s on %s.  If you plan to come please make sure you have RSVPed at %s",
		m.Results[0].Name,
		m.Results[0].Venue.Name,
		m.Results[0].parseDateTime(true),
		m.Results[0].EventURL)
}

func getTalkDetails(lunchDay bool) (string, error) {
	URL := fmt.Sprintf("https://api.meetup.com/2/events?&sign=true&photo-host=secure&group_urlname=chadevs&page=20&key=%s", os.Getenv("CHADEV_MEETUP"))
	resp, err := http.Get(URL)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	var Events Meetup
	err = json.Unmarshal(body, &Events)
	if err != nil {
		return "", err
	}

	return Events.string(lunchDay), nil
}
<|endoftext|>