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

package http

import (
	"bufio"
	"io"
	"os"
	"strconv"
	"strings"
)

// transferWriter inspects the fields of a user-supplied Request or Response,
// sanitizes them without changing the user object and provides methods for
// writing the respective header, body and trailer in wire format.
type transferWriter struct {
	Body             io.ReadCloser
	ResponseToHEAD   bool
	ContentLength    int64
	Close            bool
	TransferEncoding []string
	Trailer          map[string]string
}

func newTransferWriter(r interface{}) (t *transferWriter, err os.Error) {
	t = &transferWriter{}

	// Extract relevant fields
	atLeastHTTP11 := false
	switch rr := r.(type) {
	case *Request:
		t.Body = rr.Body
		t.ContentLength = rr.ContentLength
		t.Close = rr.Close
		t.TransferEncoding = rr.TransferEncoding
		t.Trailer = rr.Trailer
		atLeastHTTP11 = rr.ProtoAtLeast(1, 1)
	case *Response:
		t.Body = rr.Body
		t.ContentLength = rr.ContentLength
		t.Close = rr.Close
		t.TransferEncoding = rr.TransferEncoding
		t.Trailer = rr.Trailer
		atLeastHTTP11 = rr.ProtoAtLeast(1, 1)
		t.ResponseToHEAD = noBodyExpected(rr.RequestMethod)
	}

	// Sanitize Body,ContentLength,TransferEncoding
	if t.ResponseToHEAD {
		t.Body = nil
		t.TransferEncoding = nil
		// ContentLength is expected to hold Content-Length
		if t.ContentLength < 0 {
			return nil, ErrMissingContentLength
		}
	} else {
		if !atLeastHTTP11 || t.Body == nil {
			t.TransferEncoding = nil
		}
		if chunked(t.TransferEncoding) {
			t.ContentLength = -1
		} else if t.Body == nil { // no chunking, no body
			t.ContentLength = 0
		}
	}

	// Sanitize Trailer
	if !chunked(t.TransferEncoding) {
		t.Trailer = nil
	}

	return t, nil
}

func noBodyExpected(requestMethod string) bool {
	return requestMethod == "HEAD"
}

func (t *transferWriter) WriteHeader(w io.Writer) (err os.Error) {
	if t.Close {
		_, err = io.WriteString(w, "Connection: close\r\n")
		if err != nil {
			return
		}
	}

	// Write Content-Length and/or Transfer-Encoding whose values are a
	// function of the sanitized field triple (Body, ContentLength,
	// TransferEncoding)
	if chunked(t.TransferEncoding) {
		_, err = io.WriteString(w, "Transfer-Encoding: chunked\r\n")
		if err != nil {
			return
		}
	} else if t.ContentLength > 0 || t.ResponseToHEAD {
		io.WriteString(w, "Content-Length: ")
		_, err = io.WriteString(w, strconv.Itoa64(t.ContentLength)+"\r\n")
		if err != nil {
			return
		}
	}

	// Write Trailer header
	if t.Trailer != nil {
		// TODO: At some point, there should be a generic mechanism for
		// writing long headers, using HTTP line splitting
		io.WriteString(w, "Trailer: ")
		needComma := false
		for k, _ := range t.Trailer {
			k = CanonicalHeaderKey(k)
			switch k {
			case "Transfer-Encoding", "Trailer", "Content-Length":
				return &badStringError{"invalid Trailer key", k}
			}
			if needComma {
				io.WriteString(w, ",")
			}
			io.WriteString(w, k)
			needComma = true
		}
		_, err = io.WriteString(w, "\r\n")
	}

	return
}

func (t *transferWriter) WriteBody(w io.Writer) (err os.Error) {
	// Write body
	if t.Body != nil {
		if chunked(t.TransferEncoding) {
			cw := NewChunkedWriter(w)
			_, err = io.Copy(cw, t.Body)
			if err == nil {
				err = cw.Close()
			}
		} else if t.ContentLength == -1 {
			_, err = io.Copy(w, t.Body)
		} else {
			_, err = io.Copy(w, io.LimitReader(t.Body, t.ContentLength))
		}
		if err != nil {
			return err
		}
		if err = t.Body.Close(); err != nil {
			return err
		}
	}

	// TODO(petar): Place trailer writer code here.
	if chunked(t.TransferEncoding) {
		// Last chunk, empty trailer
		_, err = io.WriteString(w, "\r\n")
	}

	return
}

type transferReader struct {
	// Input
	Header        map[string]string
	StatusCode    int
	RequestMethod string
	ProtoMajor    int
	ProtoMinor    int
	// Output
	Body             io.ReadCloser
	ContentLength    int64
	TransferEncoding []string
	Close            bool
	Trailer          map[string]string
}

// msg is *Request or *Response.
func readTransfer(msg interface{}, r *bufio.Reader) (err os.Error) {
	t := &transferReader{}

	// Unify input
	switch rr := msg.(type) {
	case *Response:
		t.Header = rr.Header
		t.StatusCode = rr.StatusCode
		t.RequestMethod = rr.RequestMethod
		t.ProtoMajor = rr.ProtoMajor
		t.ProtoMinor = rr.ProtoMinor
	case *Request:
		t.Header = rr.Header
		t.ProtoMajor = rr.ProtoMajor
		t.ProtoMinor = rr.ProtoMinor
		// Transfer semantics for Requests are exactly like those for
		// Responses with status code 200, responding to a GET method
		t.StatusCode = 200
		t.RequestMethod = "GET"
	}

	// Default to HTTP/1.1
	if t.ProtoMajor == 0 && t.ProtoMinor == 0 {
		t.ProtoMajor, t.ProtoMinor = 1, 1
	}

	// Transfer encoding, content length
	t.TransferEncoding, err = fixTransferEncoding(t.Header)
	if err != nil {
		return err
	}

	t.ContentLength, err = fixLength(t.StatusCode, t.RequestMethod, t.Header, t.TransferEncoding)
	if err != nil {
		return err
	}

	// Closing
	t.Close = shouldClose(t.ProtoMajor, t.ProtoMinor, t.Header)

	// Trailer
	t.Trailer, err = fixTrailer(t.Header, t.TransferEncoding)
	if err != nil {
		return err
	}

	// Prepare body reader.  ContentLength < 0 means chunked encoding
	// or close connection when finished, since multipart is not supported yet
	switch {
	case chunked(t.TransferEncoding):
		t.Body = &body{Reader: newChunkedReader(r), hdr: msg, r: r, closing: t.Close}
	case t.ContentLength >= 0:
		// TODO: limit the Content-Length. This is an easy DoS vector.
		t.Body = &body{Reader: io.LimitReader(r, t.ContentLength), closing: t.Close}
	default:
		// t.ContentLength < 0, i.e. "Content-Length" not mentioned in header
		if t.Close {
			// Close semantics (i.e. HTTP/1.0)
			t.Body = &body{Reader: r, closing: t.Close}
		} else {
			// Persistent connection (i.e. HTTP/1.1)
			t.Body = &body{Reader: io.LimitReader(r, 0), closing: t.Close}
		}
		// TODO(petar): It may be a good idea, for extra robustness, to
		// assume ContentLength=0 for GET requests (and other special
		// cases?). This logic should be in fixLength().
	}

	// Unify output
	switch rr := msg.(type) {
	case *Request:
		rr.Body = t.Body
		rr.ContentLength = t.ContentLength
		rr.TransferEncoding = t.TransferEncoding
		rr.Close = t.Close
		rr.Trailer = t.Trailer
	case *Response:
		rr.Body = t.Body
		rr.ContentLength = t.ContentLength
		rr.TransferEncoding = t.TransferEncoding
		rr.Close = t.Close
		rr.Trailer = t.Trailer
	}

	return nil
}

// Checks whether chunked is part of the encodings stack
func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" }

// Sanitize transfer encoding
func fixTransferEncoding(header map[string]string) ([]string, os.Error) {
	raw, present := header["Transfer-Encoding"]
	if !present {
		return nil, nil
	}

	header["Transfer-Encoding"] = "", false
	encodings := strings.Split(raw, ",", -1)
	te := make([]string, 0, len(encodings))
	// TODO: Even though we only support "identity" and "chunked"
	// encodings, the loop below is designed with foresight. One
	// invariant that must be maintained is that, if present,
	// chunked encoding must always come first.
	for _, encoding := range encodings {
		encoding = strings.ToLower(strings.TrimSpace(encoding))
		// "identity" encoding is not recored
		if encoding == "identity" {
			break
		}
		if encoding != "chunked" {
			return nil, &badStringError{"unsupported transfer encoding", encoding}
		}
		te = te[0 : len(te)+1]
		te[len(te)-1] = encoding
	}
	if len(te) > 1 {
		return nil, &badStringError{"too many transfer encodings", strings.Join(te, ",")}
	}
	if len(te) > 0 {
		// Chunked encoding trumps Content-Length. See RFC 2616
		// Section 4.4. Currently len(te) > 0 implies chunked
		// encoding.
		header["Content-Length"] = "", false
		return te, nil
	}

	return nil, nil
}

// Determine the expected body length, using RFC 2616 Section 4.4. This
// function is not a method, because ultimately it should be shared by
// ReadResponse and ReadRequest.
func fixLength(status int, requestMethod string, header map[string]string, te []string) (int64, os.Error) {

	// Logic based on response type or status
	if noBodyExpected(requestMethod) {
		return 0, nil
	}
	if status/100 == 1 {
		return 0, nil
	}
	switch status {
	case 204, 304:
		return 0, nil
	}

	// Logic based on Transfer-Encoding
	if chunked(te) {
		return -1, nil
	}

	// Logic based on Content-Length
	if cl, present := header["Content-Length"]; present {
		cl = strings.TrimSpace(cl)
		if cl != "" {
			n, err := strconv.Atoi64(cl)
			if err != nil || n < 0 {
				return -1, &badStringError{"bad Content-Length", cl}
			}
			return n, nil
		} else {
			header["Content-Length"] = "", false
		}
	}

	// Logic based on media type. The purpose of the following code is just
	// to detect whether the unsupported "multipart/byteranges" is being
	// used. A proper Content-Type parser is needed in the future.
	if strings.Index(strings.ToLower(header["Content-Type"]), "multipart/byteranges") >= 0 {
		return -1, ErrNotSupported
	}

	// Body-EOF logic based on other methods (like closing, or chunked coding)
	return -1, nil
}

// Determine whether to hang up after sending a request and body, or
// receiving a response and body
// 'header' is the request headers
func shouldClose(major, minor int, header map[string]string) bool {
	if major < 1 {
		return true
	} else if major == 1 && minor == 0 {
		v, present := header["Connection"]
		if !present {
			return true
		}
		v = strings.ToLower(v)
		if strings.Index(v, "keep-alive") == -1 {
			return true
		}
		return false
	} else if v, present := header["Connection"]; present {
		// TODO: Should split on commas, toss surrounding white space,
		// and check each field.
		if v == "close" {
			header["Connection"] = "", false
			return true
		}
	}
	return false
}

// Parse the trailer header
func fixTrailer(header map[string]string, te []string) (map[string]string, os.Error) {
	raw, present := header["Trailer"]
	if !present {
		return nil, nil
	}

	header["Trailer"] = "", false
	trailer := make(map[string]string)
	keys := strings.Split(raw, ",", -1)
	for _, key := range keys {
		key = CanonicalHeaderKey(strings.TrimSpace(key))
		switch key {
		case "Transfer-Encoding", "Trailer", "Content-Length":
			return nil, &badStringError{"bad trailer key", key}
		}
		trailer[key] = ""
	}
	if len(trailer) == 0 {
		return nil, nil
	}
	if !chunked(te) {
		// Trailer and no chunking
		return nil, ErrUnexpectedTrailer
	}
	return trailer, nil
}

// body turns a Reader into a ReadCloser.
// Close ensures that the body has been fully read
// and then reads the trailer if necessary.
type body struct {
	io.Reader
	hdr     interface{}   // non-nil (Response or Request) value means read trailer
	r       *bufio.Reader // underlying wire-format reader for the trailer
	closing bool          // is the connection to be closed after reading body?
}

func (b *body) Close() os.Error {
	if b.hdr == nil && b.closing {
		// no trailer and closing the connection next.
		// no point in reading to EOF.
		return nil
	}

	trashBuf := make([]byte, 1024) // local for thread safety
	for {
		_, err := b.Read(trashBuf)
		if err == nil {
			continue
		}
		if err == os.EOF {
			break
		}
		return err
	}
	if b.hdr == nil { // not reading trailer
		return nil
	}

	// TODO(petar): Put trailer reader code here

	return nil
}
<commit_msg>http: do not close connection after sending HTTP/1.0 request<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package http

import (
	"bufio"
	"io"
	"os"
	"strconv"
	"strings"
)

// transferWriter inspects the fields of a user-supplied Request or Response,
// sanitizes them without changing the user object and provides methods for
// writing the respective header, body and trailer in wire format.
type transferWriter struct {
	Body             io.ReadCloser
	ResponseToHEAD   bool
	ContentLength    int64
	Close            bool
	TransferEncoding []string
	Trailer          map[string]string
}

func newTransferWriter(r interface{}) (t *transferWriter, err os.Error) {
	t = &transferWriter{}

	// Extract relevant fields
	atLeastHTTP11 := false
	switch rr := r.(type) {
	case *Request:
		t.Body = rr.Body
		t.ContentLength = rr.ContentLength
		t.Close = rr.Close
		t.TransferEncoding = rr.TransferEncoding
		t.Trailer = rr.Trailer
		atLeastHTTP11 = rr.ProtoAtLeast(1, 1)
	case *Response:
		t.Body = rr.Body
		t.ContentLength = rr.ContentLength
		t.Close = rr.Close
		t.TransferEncoding = rr.TransferEncoding
		t.Trailer = rr.Trailer
		atLeastHTTP11 = rr.ProtoAtLeast(1, 1)
		t.ResponseToHEAD = noBodyExpected(rr.RequestMethod)
	}

	// Sanitize Body,ContentLength,TransferEncoding
	if t.ResponseToHEAD {
		t.Body = nil
		t.TransferEncoding = nil
		// ContentLength is expected to hold Content-Length
		if t.ContentLength < 0 {
			return nil, ErrMissingContentLength
		}
	} else {
		if !atLeastHTTP11 || t.Body == nil {
			t.TransferEncoding = nil
		}
		if chunked(t.TransferEncoding) {
			t.ContentLength = -1
		} else if t.Body == nil { // no chunking, no body
			t.ContentLength = 0
		}
	}

	// Sanitize Trailer
	if !chunked(t.TransferEncoding) {
		t.Trailer = nil
	}

	return t, nil
}

func noBodyExpected(requestMethod string) bool {
	return requestMethod == "HEAD"
}

func (t *transferWriter) WriteHeader(w io.Writer) (err os.Error) {
	if t.Close {
		_, err = io.WriteString(w, "Connection: close\r\n")
		if err != nil {
			return
		}
	}

	// Write Content-Length and/or Transfer-Encoding whose values are a
	// function of the sanitized field triple (Body, ContentLength,
	// TransferEncoding)
	if chunked(t.TransferEncoding) {
		_, err = io.WriteString(w, "Transfer-Encoding: chunked\r\n")
		if err != nil {
			return
		}
	} else if t.ContentLength > 0 || t.ResponseToHEAD {
		io.WriteString(w, "Content-Length: ")
		_, err = io.WriteString(w, strconv.Itoa64(t.ContentLength)+"\r\n")
		if err != nil {
			return
		}
	}

	// Write Trailer header
	if t.Trailer != nil {
		// TODO: At some point, there should be a generic mechanism for
		// writing long headers, using HTTP line splitting
		io.WriteString(w, "Trailer: ")
		needComma := false
		for k, _ := range t.Trailer {
			k = CanonicalHeaderKey(k)
			switch k {
			case "Transfer-Encoding", "Trailer", "Content-Length":
				return &badStringError{"invalid Trailer key", k}
			}
			if needComma {
				io.WriteString(w, ",")
			}
			io.WriteString(w, k)
			needComma = true
		}
		_, err = io.WriteString(w, "\r\n")
	}

	return
}

func (t *transferWriter) WriteBody(w io.Writer) (err os.Error) {
	// Write body
	if t.Body != nil {
		if chunked(t.TransferEncoding) {
			cw := NewChunkedWriter(w)
			_, err = io.Copy(cw, t.Body)
			if err == nil {
				err = cw.Close()
			}
		} else if t.ContentLength == -1 {
			_, err = io.Copy(w, t.Body)
		} else {
			_, err = io.Copy(w, io.LimitReader(t.Body, t.ContentLength))
		}
		if err != nil {
			return err
		}
		if err = t.Body.Close(); err != nil {
			return err
		}
	}

	// TODO(petar): Place trailer writer code here.
	if chunked(t.TransferEncoding) {
		// Last chunk, empty trailer
		_, err = io.WriteString(w, "\r\n")
	}

	return
}

type transferReader struct {
	// Input
	Header        map[string]string
	StatusCode    int
	RequestMethod string
	ProtoMajor    int
	ProtoMinor    int
	// Output
	Body             io.ReadCloser
	ContentLength    int64
	TransferEncoding []string
	Close            bool
	Trailer          map[string]string
}

// msg is *Request or *Response.
func readTransfer(msg interface{}, r *bufio.Reader) (err os.Error) {
	t := &transferReader{}

	// Unify input
	switch rr := msg.(type) {
	case *Response:
		t.Header = rr.Header
		t.StatusCode = rr.StatusCode
		t.RequestMethod = rr.RequestMethod
		t.ProtoMajor = rr.ProtoMajor
		t.ProtoMinor = rr.ProtoMinor
		t.Close = shouldClose(t.ProtoMajor, t.ProtoMinor, t.Header)
	case *Request:
		t.Header = rr.Header
		t.ProtoMajor = rr.ProtoMajor
		t.ProtoMinor = rr.ProtoMinor
		// Transfer semantics for Requests are exactly like those for
		// Responses with status code 200, responding to a GET method
		t.StatusCode = 200
		t.RequestMethod = "GET"
	}

	// Default to HTTP/1.1
	if t.ProtoMajor == 0 && t.ProtoMinor == 0 {
		t.ProtoMajor, t.ProtoMinor = 1, 1
	}

	// Transfer encoding, content length
	t.TransferEncoding, err = fixTransferEncoding(t.Header)
	if err != nil {
		return err
	}

	t.ContentLength, err = fixLength(t.StatusCode, t.RequestMethod, t.Header, t.TransferEncoding)
	if err != nil {
		return err
	}

	// Trailer
	t.Trailer, err = fixTrailer(t.Header, t.TransferEncoding)
	if err != nil {
		return err
	}

	// Prepare body reader.  ContentLength < 0 means chunked encoding
	// or close connection when finished, since multipart is not supported yet
	switch {
	case chunked(t.TransferEncoding):
		t.Body = &body{Reader: newChunkedReader(r), hdr: msg, r: r, closing: t.Close}
	case t.ContentLength >= 0:
		// TODO: limit the Content-Length. This is an easy DoS vector.
		t.Body = &body{Reader: io.LimitReader(r, t.ContentLength), closing: t.Close}
	default:
		// t.ContentLength < 0, i.e. "Content-Length" not mentioned in header
		if t.Close {
			// Close semantics (i.e. HTTP/1.0)
			t.Body = &body{Reader: r, closing: t.Close}
		} else {
			// Persistent connection (i.e. HTTP/1.1)
			t.Body = &body{Reader: io.LimitReader(r, 0), closing: t.Close}
		}
		// TODO(petar): It may be a good idea, for extra robustness, to
		// assume ContentLength=0 for GET requests (and other special
		// cases?). This logic should be in fixLength().
	}

	// Unify output
	switch rr := msg.(type) {
	case *Request:
		rr.Body = t.Body
		rr.ContentLength = t.ContentLength
		rr.TransferEncoding = t.TransferEncoding
		rr.Close = t.Close
		rr.Trailer = t.Trailer
	case *Response:
		rr.Body = t.Body
		rr.ContentLength = t.ContentLength
		rr.TransferEncoding = t.TransferEncoding
		rr.Close = t.Close
		rr.Trailer = t.Trailer
	}

	return nil
}

// Checks whether chunked is part of the encodings stack
func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" }

// Sanitize transfer encoding
func fixTransferEncoding(header map[string]string) ([]string, os.Error) {
	raw, present := header["Transfer-Encoding"]
	if !present {
		return nil, nil
	}

	header["Transfer-Encoding"] = "", false
	encodings := strings.Split(raw, ",", -1)
	te := make([]string, 0, len(encodings))
	// TODO: Even though we only support "identity" and "chunked"
	// encodings, the loop below is designed with foresight. One
	// invariant that must be maintained is that, if present,
	// chunked encoding must always come first.
	for _, encoding := range encodings {
		encoding = strings.ToLower(strings.TrimSpace(encoding))
		// "identity" encoding is not recored
		if encoding == "identity" {
			break
		}
		if encoding != "chunked" {
			return nil, &badStringError{"unsupported transfer encoding", encoding}
		}
		te = te[0 : len(te)+1]
		te[len(te)-1] = encoding
	}
	if len(te) > 1 {
		return nil, &badStringError{"too many transfer encodings", strings.Join(te, ",")}
	}
	if len(te) > 0 {
		// Chunked encoding trumps Content-Length. See RFC 2616
		// Section 4.4. Currently len(te) > 0 implies chunked
		// encoding.
		header["Content-Length"] = "", false
		return te, nil
	}

	return nil, nil
}

// Determine the expected body length, using RFC 2616 Section 4.4. This
// function is not a method, because ultimately it should be shared by
// ReadResponse and ReadRequest.
func fixLength(status int, requestMethod string, header map[string]string, te []string) (int64, os.Error) {

	// Logic based on response type or status
	if noBodyExpected(requestMethod) {
		return 0, nil
	}
	if status/100 == 1 {
		return 0, nil
	}
	switch status {
	case 204, 304:
		return 0, nil
	}

	// Logic based on Transfer-Encoding
	if chunked(te) {
		return -1, nil
	}

	// Logic based on Content-Length
	if cl, present := header["Content-Length"]; present {
		cl = strings.TrimSpace(cl)
		if cl != "" {
			n, err := strconv.Atoi64(cl)
			if err != nil || n < 0 {
				return -1, &badStringError{"bad Content-Length", cl}
			}
			return n, nil
		} else {
			header["Content-Length"] = "", false
		}
	}

	// Logic based on media type. The purpose of the following code is just
	// to detect whether the unsupported "multipart/byteranges" is being
	// used. A proper Content-Type parser is needed in the future.
	if strings.Index(strings.ToLower(header["Content-Type"]), "multipart/byteranges") >= 0 {
		return -1, ErrNotSupported
	}

	// Body-EOF logic based on other methods (like closing, or chunked coding)
	return -1, nil
}

// Determine whether to hang up after sending a request and body, or
// receiving a response and body
// 'header' is the request headers
func shouldClose(major, minor int, header map[string]string) bool {
	if major < 1 {
		return true
	} else if major == 1 && minor == 0 {
		v, present := header["Connection"]
		if !present {
			return true
		}
		v = strings.ToLower(v)
		if strings.Index(v, "keep-alive") == -1 {
			return true
		}
		return false
	} else if v, present := header["Connection"]; present {
		// TODO: Should split on commas, toss surrounding white space,
		// and check each field.
		if v == "close" {
			header["Connection"] = "", false
			return true
		}
	}
	return false
}

// Parse the trailer header
func fixTrailer(header map[string]string, te []string) (map[string]string, os.Error) {
	raw, present := header["Trailer"]
	if !present {
		return nil, nil
	}

	header["Trailer"] = "", false
	trailer := make(map[string]string)
	keys := strings.Split(raw, ",", -1)
	for _, key := range keys {
		key = CanonicalHeaderKey(strings.TrimSpace(key))
		switch key {
		case "Transfer-Encoding", "Trailer", "Content-Length":
			return nil, &badStringError{"bad trailer key", key}
		}
		trailer[key] = ""
	}
	if len(trailer) == 0 {
		return nil, nil
	}
	if !chunked(te) {
		// Trailer and no chunking
		return nil, ErrUnexpectedTrailer
	}
	return trailer, nil
}

// body turns a Reader into a ReadCloser.
// Close ensures that the body has been fully read
// and then reads the trailer if necessary.
type body struct {
	io.Reader
	hdr     interface{}   // non-nil (Response or Request) value means read trailer
	r       *bufio.Reader // underlying wire-format reader for the trailer
	closing bool          // is the connection to be closed after reading body?
}

func (b *body) Close() os.Error {
	if b.hdr == nil && b.closing {
		// no trailer and closing the connection next.
		// no point in reading to EOF.
		return nil
	}

	trashBuf := make([]byte, 1024) // local for thread safety
	for {
		_, err := b.Read(trashBuf)
		if err == nil {
			continue
		}
		if err == os.EOF {
			break
		}
		return err
	}
	if b.hdr == nil { // not reading trailer
		return nil
	}

	// TODO(petar): Put trailer reader code here

	return nil
}
<|endoftext|>


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

package net

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

func newLocalListener(t *testing.T) Listener {
	ln, err := Listen("tcp", "127.0.0.1:0")
	if err != nil {
		ln, err = Listen("tcp6", "[::1]:0")
	}
	if err != nil {
		t.Fatal(err)
	}
	return ln
}

func TestDialTimeout(t *testing.T) {
	ln := newLocalListener(t)
	defer ln.Close()

	errc := make(chan error)

	numConns := listenerBacklog + 10

	// TODO(bradfitz): It's hard to test this in a portable
	// way. This is unfortunate, but works for now.
	switch runtime.GOOS {
	case "linux":
		// The kernel will start accepting TCP connections before userspace
		// gets a chance to not accept them, so fire off a bunch to fill up
		// the kernel's backlog.  Then we test we get a failure after that.
		for i := 0; i < numConns; i++ {
			go func() {
				_, err := DialTimeout("tcp", ln.Addr().String(), 200*time.Millisecond)
				errc <- err
			}()
		}
	case "darwin", "windows":
		// At least OS X 10.7 seems to accept any number of
		// connections, ignoring listen's backlog, so resort
		// to connecting to a hopefully-dead 127/8 address.
		// Same for windows.
		//
		// Use a bogus port (44444) instead of 80, because
		// on our 386 builder, this Dial succeeds, connecting
		// to an IIS web server somewhere.  The data center
		// or VM or firewall must be stealing the TCP connection.
		go func() {
			c, err := DialTimeout("tcp", "127.0.71.111:44444", 200*time.Millisecond)
			if err == nil {
				err = fmt.Errorf("unexpected: connected to %s!", c.RemoteAddr())
				c.Close()
			}
			errc <- err
		}()
	default:
		// TODO(bradfitz):
		// OpenBSD may have a reject route to 127/8 except 127.0.0.1/32
		// by default. FreeBSD likely works, but is untested.
		// TODO(rsc):
		// The timeout never happens on Windows.  Why?  Issue 3016.
		t.Logf("skipping test on %q; untested.", runtime.GOOS)
		return
	}

	connected := 0
	for {
		select {
		case <-time.After(15 * time.Second):
			t.Fatal("too slow")
		case err := <-errc:
			if err == nil {
				connected++
				if connected == numConns {
					t.Fatal("all connections connected; expected some to time out")
				}
			} else {
				terr, ok := err.(timeout)
				if !ok {
					t.Fatalf("got error %q; want error with timeout interface", err)
				}
				if !terr.Timeout() {
					t.Fatalf("got error %q; not a timeout", err)
				}
				// Pass. We saw a timeout error.
				return
			}
		}
	}
}

func TestSelfConnect(t *testing.T) {
	if runtime.GOOS == "windows" {
		// TODO(brainman): do not know why it hangs.
		t.Logf("skipping known-broken test on windows")
		return
	}
	// Test that Dial does not honor self-connects.
	// See the comment in DialTCP.

	// Find a port that would be used as a local address.
	l, err := Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	c, err := Dial("tcp", l.Addr().String())
	if err != nil {
		t.Fatal(err)
	}
	addr := c.LocalAddr().String()
	c.Close()
	l.Close()

	// Try to connect to that address repeatedly.
	n := 100000
	if testing.Short() {
		n = 1000
	}
	switch runtime.GOOS {
	case "darwin", "freebsd", "openbsd", "windows":
		// Non-Linux systems take a long time to figure
		// out that there is nothing listening on localhost.
		n = 100
	}
	for i := 0; i < n; i++ {
		c, err := Dial("tcp", addr)
		if err == nil {
			c.Close()
			t.Errorf("#%d: Dial %q succeeded", i, addr)
		}
	}
}

var runErrorTest = flag.Bool("run_error_test", false, "let TestDialError check for dns errors")

type DialErrorTest struct {
	Net     string
	Raddr   string
	Pattern string
}

var dialErrorTests = []DialErrorTest{
	{
		"datakit", "mh/astro/r70",
		"dial datakit mh/astro/r70: unknown network datakit",
	},
	{
		"tcp", "127.0.0.1:☺",
		"dial tcp 127.0.0.1:☺: unknown port tcp/☺",
	},
	{
		"tcp", "no-such-name.google.com.:80",
		"dial tcp no-such-name.google.com.:80: lookup no-such-name.google.com.( on .*)?: no (.*)",
	},
	{
		"tcp", "no-such-name.no-such-top-level-domain.:80",
		"dial tcp no-such-name.no-such-top-level-domain.:80: lookup no-such-name.no-such-top-level-domain.( on .*)?: no (.*)",
	},
	{
		"tcp", "no-such-name:80",
		`dial tcp no-such-name:80: lookup no-such-name\.(.*\.)?( on .*)?: no (.*)`,
	},
	{
		"tcp", "mh/astro/r70:http",
		"dial tcp mh/astro/r70:http: lookup mh/astro/r70: invalid domain name",
	},
	{
		"unix", "/etc/file-not-found",
		"dial unix /etc/file-not-found: no such file or directory",
	},
	{
		"unix", "/etc/",
		"dial unix /etc/: (permission denied|socket operation on non-socket|connection refused)",
	},
	{
		"unixpacket", "/etc/file-not-found",
		"dial unixpacket /etc/file-not-found: no such file or directory",
	},
	{
		"unixpacket", "/etc/",
		"dial unixpacket /etc/: (permission denied|socket operation on non-socket|connection refused)",
	},
}

var duplicateErrorPattern = `dial (.*) dial (.*)`

func TestDialError(t *testing.T) {
	if !*runErrorTest {
		t.Logf("test disabled; use -run_error_test to enable")
		return
	}
	for i, tt := range dialErrorTests {
		c, err := Dial(tt.Net, tt.Raddr)
		if c != nil {
			c.Close()
		}
		if err == nil {
			t.Errorf("#%d: nil error, want match for %#q", i, tt.Pattern)
			continue
		}
		s := err.Error()
		match, _ := regexp.MatchString(tt.Pattern, s)
		if !match {
			t.Errorf("#%d: %q, want match for %#q", i, s, tt.Pattern)
		}
		match, _ = regexp.MatchString(duplicateErrorPattern, s)
		if match {
			t.Errorf("#%d: %q, duplicate error return from Dial", i, s)
		}
	}
}
<commit_msg>net: use IANA reserved port to test dial timeout<commit_after>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package net

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

func newLocalListener(t *testing.T) Listener {
	ln, err := Listen("tcp", "127.0.0.1:0")
	if err != nil {
		ln, err = Listen("tcp6", "[::1]:0")
	}
	if err != nil {
		t.Fatal(err)
	}
	return ln
}

func TestDialTimeout(t *testing.T) {
	ln := newLocalListener(t)
	defer ln.Close()

	errc := make(chan error)

	numConns := listenerBacklog + 10

	// TODO(bradfitz): It's hard to test this in a portable
	// way. This is unfortunate, but works for now.
	switch runtime.GOOS {
	case "linux":
		// The kernel will start accepting TCP connections before userspace
		// gets a chance to not accept them, so fire off a bunch to fill up
		// the kernel's backlog.  Then we test we get a failure after that.
		for i := 0; i < numConns; i++ {
			go func() {
				_, err := DialTimeout("tcp", ln.Addr().String(), 200*time.Millisecond)
				errc <- err
			}()
		}
	case "darwin", "windows":
		// At least OS X 10.7 seems to accept any number of
		// connections, ignoring listen's backlog, so resort
		// to connecting to a hopefully-dead 127/8 address.
		// Same for windows.
		//
		// Use an IANA reserved port (49151) instead of 80, because
		// on our 386 builder, this Dial succeeds, connecting
		// to an IIS web server somewhere.  The data center
		// or VM or firewall must be stealing the TCP connection.
		//
		// IANA Service Name and Transport Protocol Port Number Registry
		// <http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml>
		go func() {
			c, err := DialTimeout("tcp", "127.0.71.111:49151", 200*time.Millisecond)
			if err == nil {
				err = fmt.Errorf("unexpected: connected to %s!", c.RemoteAddr())
				c.Close()
			}
			errc <- err
		}()
	default:
		// TODO(bradfitz):
		// OpenBSD may have a reject route to 127/8 except 127.0.0.1/32
		// by default. FreeBSD likely works, but is untested.
		// TODO(rsc):
		// The timeout never happens on Windows.  Why?  Issue 3016.
		t.Logf("skipping test on %q; untested.", runtime.GOOS)
		return
	}

	connected := 0
	for {
		select {
		case <-time.After(15 * time.Second):
			t.Fatal("too slow")
		case err := <-errc:
			if err == nil {
				connected++
				if connected == numConns {
					t.Fatal("all connections connected; expected some to time out")
				}
			} else {
				terr, ok := err.(timeout)
				if !ok {
					t.Fatalf("got error %q; want error with timeout interface", err)
				}
				if !terr.Timeout() {
					t.Fatalf("got error %q; not a timeout", err)
				}
				// Pass. We saw a timeout error.
				return
			}
		}
	}
}

func TestSelfConnect(t *testing.T) {
	if runtime.GOOS == "windows" {
		// TODO(brainman): do not know why it hangs.
		t.Logf("skipping known-broken test on windows")
		return
	}
	// Test that Dial does not honor self-connects.
	// See the comment in DialTCP.

	// Find a port that would be used as a local address.
	l, err := Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	c, err := Dial("tcp", l.Addr().String())
	if err != nil {
		t.Fatal(err)
	}
	addr := c.LocalAddr().String()
	c.Close()
	l.Close()

	// Try to connect to that address repeatedly.
	n := 100000
	if testing.Short() {
		n = 1000
	}
	switch runtime.GOOS {
	case "darwin", "freebsd", "openbsd", "windows":
		// Non-Linux systems take a long time to figure
		// out that there is nothing listening on localhost.
		n = 100
	}
	for i := 0; i < n; i++ {
		c, err := Dial("tcp", addr)
		if err == nil {
			c.Close()
			t.Errorf("#%d: Dial %q succeeded", i, addr)
		}
	}
}

var runErrorTest = flag.Bool("run_error_test", false, "let TestDialError check for dns errors")

type DialErrorTest struct {
	Net     string
	Raddr   string
	Pattern string
}

var dialErrorTests = []DialErrorTest{
	{
		"datakit", "mh/astro/r70",
		"dial datakit mh/astro/r70: unknown network datakit",
	},
	{
		"tcp", "127.0.0.1:☺",
		"dial tcp 127.0.0.1:☺: unknown port tcp/☺",
	},
	{
		"tcp", "no-such-name.google.com.:80",
		"dial tcp no-such-name.google.com.:80: lookup no-such-name.google.com.( on .*)?: no (.*)",
	},
	{
		"tcp", "no-such-name.no-such-top-level-domain.:80",
		"dial tcp no-such-name.no-such-top-level-domain.:80: lookup no-such-name.no-such-top-level-domain.( on .*)?: no (.*)",
	},
	{
		"tcp", "no-such-name:80",
		`dial tcp no-such-name:80: lookup no-such-name\.(.*\.)?( on .*)?: no (.*)`,
	},
	{
		"tcp", "mh/astro/r70:http",
		"dial tcp mh/astro/r70:http: lookup mh/astro/r70: invalid domain name",
	},
	{
		"unix", "/etc/file-not-found",
		"dial unix /etc/file-not-found: no such file or directory",
	},
	{
		"unix", "/etc/",
		"dial unix /etc/: (permission denied|socket operation on non-socket|connection refused)",
	},
	{
		"unixpacket", "/etc/file-not-found",
		"dial unixpacket /etc/file-not-found: no such file or directory",
	},
	{
		"unixpacket", "/etc/",
		"dial unixpacket /etc/: (permission denied|socket operation on non-socket|connection refused)",
	},
}

var duplicateErrorPattern = `dial (.*) dial (.*)`

func TestDialError(t *testing.T) {
	if !*runErrorTest {
		t.Logf("test disabled; use -run_error_test to enable")
		return
	}
	for i, tt := range dialErrorTests {
		c, err := Dial(tt.Net, tt.Raddr)
		if c != nil {
			c.Close()
		}
		if err == nil {
			t.Errorf("#%d: nil error, want match for %#q", i, tt.Pattern)
			continue
		}
		s := err.Error()
		match, _ := regexp.MatchString(tt.Pattern, s)
		if !match {
			t.Errorf("#%d: %q, want match for %#q", i, s, tt.Pattern)
		}
		match, _ = regexp.MatchString(duplicateErrorPattern, s)
		if match {
			t.Errorf("#%d: %q, duplicate error return from Dial", i, s)
		}
	}
}
<|endoftext|>


<commit_before>/**
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

// Sample demonstrating use of the Bigtable client library to perform basic CRUD operations
package main

import (
	"flag"
	"fmt"
	"log"

	"cloud.google.com/go/bigtable"
	"golang.org/x/net/context"
)

// User-provided constants.
const (
	tableName        = "Hello-Bigtable"
	columnFamilyName = "cf1"
	columnName       = "greeting"
)

var greetings = []string{"Hello World!", "Hello Cloud Bigtable!", "Hello golang!"}

// sliceContains reports whether the provided string is present in the given slice of strings.
func sliceContains(list []string, target string) bool {
	for _, s := range list {
		if s == target {
			return true
		}
	}
	return false
}

func main() {
	project := flag.String("project", "", "The Google Cloud Platform project ID. Required.")
	instance := flag.String("instance", "", "The Google Cloud Bigtable instance ID. Required.")
	flag.Parse()

	for _, f := range []string{"project", "instance"} {
		if flag.Lookup(f).Value.String() == "" {
			log.Fatalf("The %s flag is required.", f)
		}
	}

	ctx := context.Background()

	// Set up admin client, tables, and column families.
	// NewAdminClient uses Application Default Credentials to authenticate.
	adminClient, err := bigtable.NewAdminClient(ctx, *project, *instance)
	if err != nil {
		log.Fatalf("Could not create admin client: %v", err)
	}

	tables, err := adminClient.Tables(ctx)
	if err != nil {
		log.Fatalf("Could not fetch table list: %v", err)
	}

	if !sliceContains(tables, tableName) {
		log.Printf("Creating table %s", tableName)
		if err := adminClient.CreateTable(ctx, tableName); err != nil {
			log.Fatalf("Could not create table %s: %v", tableName, err)
		}
	}

	tblInfo, err := adminClient.TableInfo(ctx, tableName)
	if err != nil {
		log.Fatalf("Could not read info for table %s: %v", tableName, err)
	}

	if !sliceContains(tblInfo.Families, columnFamilyName) {
		if err := adminClient.CreateColumnFamily(ctx, tableName, columnFamilyName); err != nil {
			log.Fatalf("Could not create column family %s: %v", columnFamilyName, err)
		}
	}

	// Set up Bigtable data operations client.
	// NewClient uses Application Default Credentials to authenticate.
	client, err := bigtable.NewClient(ctx, *project, *instance)
	if err != nil {
		log.Fatalf("Could not create data operations client: %v", err)
	}

	tbl := client.Open(tableName)
	muts := make([]*bigtable.Mutation, len(greetings))
	rowKeys := make([]string, len(greetings))

	log.Printf("Writing greeting rows to table")
	for i, greeting := range greetings {
		muts[i] = bigtable.NewMutation()
		muts[i].Set(columnFamilyName, columnName, bigtable.Now(), []byte(greeting))

		// Each row has a unique row key.
		//
		// Note: This example uses sequential numeric IDs for simplicity, but
		// this can result in poor performance in a production application.
		// Since rows are stored in sorted order by key, sequential keys can
		// result in poor distribution of operations across nodes.
		//
		// For more information about how to design a Bigtable schema for the
		// best performance, see the documentation:
		//
		//     https://cloud.google.com/bigtable/docs/schema-design
		rowKeys[i] = fmt.Sprintf("%s%d", columnName, i)
	}

	rowErrs, err := tbl.ApplyBulk(ctx, rowKeys, muts)
	if err != nil {
		log.Fatalf("Could not apply bulk row mutation: %v", err)
	}
	if rowErrs != nil {
		for _, rowErr := range rowErrs {
			log.Printf("Error writing row: %v", rowErr)
		}
		log.Fatalf("Could not write some rows")
	}

	log.Printf("Getting a single greeting by row key:")
	row, err := tbl.ReadRow(ctx, rowKeys[0], bigtable.RowFilter(bigtable.ColumnFilter(columnName)))
	if err != nil {
		log.Fatalf("Could not read row with key %s: %v", rowKeys[0], err)
	}
	log.Printf("\t%s = %s\n", rowKeys[0], string(row[columnFamilyName][0].Value))

	log.Printf("Reading all greeting rows:")
	err = tbl.ReadRows(ctx, bigtable.PrefixRange(columnName), func(row bigtable.Row) bool {
		item := row[columnFamilyName][0]
		log.Printf("\t%s = %s\n", item.Row, string(item.Value))
		return true
	}, bigtable.RowFilter(bigtable.ColumnFilter(columnName)))

	if err = client.Close(); err != nil {
		log.Fatalf("Could not close data operations client: %v", err)
	}

	log.Printf("Deleting the table")
	if err = adminClient.DeleteTable(ctx, tableName); err != nil {
		log.Fatalf("Could not delete table %s: %v", tableName, err)
	}

	if err = adminClient.Close(); err != nil {
		log.Fatalf("Could not close admin client: %v", err)
	}
}
<commit_msg>examples/bigtable: fix license in hello_world<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.

// Sample demonstrating use of the Bigtable client library to perform basic CRUD operations
package main

import (
	"flag"
	"fmt"
	"log"

	"cloud.google.com/go/bigtable"
	"golang.org/x/net/context"
)

// User-provided constants.
const (
	tableName        = "Hello-Bigtable"
	columnFamilyName = "cf1"
	columnName       = "greeting"
)

var greetings = []string{"Hello World!", "Hello Cloud Bigtable!", "Hello golang!"}

// sliceContains reports whether the provided string is present in the given slice of strings.
func sliceContains(list []string, target string) bool {
	for _, s := range list {
		if s == target {
			return true
		}
	}
	return false
}

func main() {
	project := flag.String("project", "", "The Google Cloud Platform project ID. Required.")
	instance := flag.String("instance", "", "The Google Cloud Bigtable instance ID. Required.")
	flag.Parse()

	for _, f := range []string{"project", "instance"} {
		if flag.Lookup(f).Value.String() == "" {
			log.Fatalf("The %s flag is required.", f)
		}
	}

	ctx := context.Background()

	// Set up admin client, tables, and column families.
	// NewAdminClient uses Application Default Credentials to authenticate.
	adminClient, err := bigtable.NewAdminClient(ctx, *project, *instance)
	if err != nil {
		log.Fatalf("Could not create admin client: %v", err)
	}

	tables, err := adminClient.Tables(ctx)
	if err != nil {
		log.Fatalf("Could not fetch table list: %v", err)
	}

	if !sliceContains(tables, tableName) {
		log.Printf("Creating table %s", tableName)
		if err := adminClient.CreateTable(ctx, tableName); err != nil {
			log.Fatalf("Could not create table %s: %v", tableName, err)
		}
	}

	tblInfo, err := adminClient.TableInfo(ctx, tableName)
	if err != nil {
		log.Fatalf("Could not read info for table %s: %v", tableName, err)
	}

	if !sliceContains(tblInfo.Families, columnFamilyName) {
		if err := adminClient.CreateColumnFamily(ctx, tableName, columnFamilyName); err != nil {
			log.Fatalf("Could not create column family %s: %v", columnFamilyName, err)
		}
	}

	// Set up Bigtable data operations client.
	// NewClient uses Application Default Credentials to authenticate.
	client, err := bigtable.NewClient(ctx, *project, *instance)
	if err != nil {
		log.Fatalf("Could not create data operations client: %v", err)
	}

	tbl := client.Open(tableName)
	muts := make([]*bigtable.Mutation, len(greetings))
	rowKeys := make([]string, len(greetings))

	log.Printf("Writing greeting rows to table")
	for i, greeting := range greetings {
		muts[i] = bigtable.NewMutation()
		muts[i].Set(columnFamilyName, columnName, bigtable.Now(), []byte(greeting))

		// Each row has a unique row key.
		//
		// Note: This example uses sequential numeric IDs for simplicity, but
		// this can result in poor performance in a production application.
		// Since rows are stored in sorted order by key, sequential keys can
		// result in poor distribution of operations across nodes.
		//
		// For more information about how to design a Bigtable schema for the
		// best performance, see the documentation:
		//
		//     https://cloud.google.com/bigtable/docs/schema-design
		rowKeys[i] = fmt.Sprintf("%s%d", columnName, i)
	}

	rowErrs, err := tbl.ApplyBulk(ctx, rowKeys, muts)
	if err != nil {
		log.Fatalf("Could not apply bulk row mutation: %v", err)
	}
	if rowErrs != nil {
		for _, rowErr := range rowErrs {
			log.Printf("Error writing row: %v", rowErr)
		}
		log.Fatalf("Could not write some rows")
	}

	log.Printf("Getting a single greeting by row key:")
	row, err := tbl.ReadRow(ctx, rowKeys[0], bigtable.RowFilter(bigtable.ColumnFilter(columnName)))
	if err != nil {
		log.Fatalf("Could not read row with key %s: %v", rowKeys[0], err)
	}
	log.Printf("\t%s = %s\n", rowKeys[0], string(row[columnFamilyName][0].Value))

	log.Printf("Reading all greeting rows:")
	err = tbl.ReadRows(ctx, bigtable.PrefixRange(columnName), func(row bigtable.Row) bool {
		item := row[columnFamilyName][0]
		log.Printf("\t%s = %s\n", item.Row, string(item.Value))
		return true
	}, bigtable.RowFilter(bigtable.ColumnFilter(columnName)))

	if err = client.Close(); err != nil {
		log.Fatalf("Could not close data operations client: %v", err)
	}

	log.Printf("Deleting the table")
	if err = adminClient.DeleteTable(ctx, tableName); err != nil {
		log.Fatalf("Could not delete table %s: %v", tableName, err)
	}

	if err = adminClient.Close(); err != nil {
		log.Fatalf("Could not close admin client: %v", err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	log "github.com/sirupsen/logrus"
	"path/filepath"
	"path"
	"strings"
	"github.com/spf13/cobra/doc"
	"github.com/crunchydata/postgres-operator/pgo/cmd"

)

const fmTemplate = `---
title: "%s"
---
`

func main() {

	fmt.Println("generate CLI markdown")

	filePrepender := func(filename string) string {
		// now := time.Now().Format(time.RFC3339)
		name := filepath.Base(filename)
		base := strings.TrimSuffix(name, path.Ext(name))
		fmt.Println(base);
		// url := "/commands/" + strings.ToLower(base) + "/"
		return fmt.Sprintf(fmTemplate, base)
	}

	linkHandler := func(name string) string {
		base := strings.TrimSuffix(name, path.Ext(name))
		return "/commands/" + strings.ToLower(base) + "/"
	}

	err := doc.GenMarkdownTreeCustom(cmd.RootCmd, "./", filePrepender, linkHandler)
	if err != nil {
		log.Fatal(err)
	}
}

<commit_msg>updating cli generated path (#626)<commit_after>package main

import (
	"fmt"
	log "github.com/sirupsen/logrus"
	"path/filepath"
	"path"
	"strings"
	"github.com/spf13/cobra/doc"
	"github.com/crunchydata/postgres-operator/pgo/cmd"

)

const fmTemplate = `---
title: "%s"
---
`

func main() {

	fmt.Println("generate CLI markdown")

	filePrepender := func(filename string) string {
		// now := time.Now().Format(time.RFC3339)
		name := filepath.Base(filename)
		base := strings.TrimSuffix(name, path.Ext(name))
		fmt.Println(base);
		// url := "/commands/" + strings.ToLower(base) + "/"
		return fmt.Sprintf(fmTemplate, base)
	}

	linkHandler := func(name string) string {
		base := strings.TrimSuffix(name, path.Ext(name))
		return "/cli/" + strings.ToLower(base) + "/"
	}

	err := doc.GenMarkdownTreeCustom(cmd.RootCmd, "./", filePrepender, linkHandler)
	if err != nil {
		log.Fatal(err)
	}
}

<|endoftext|>


<commit_before>//
// model.go
// template model implementation
//
package srnd

import (
  "fmt"
  "io"
  "path/filepath"
  "strings"
  "time"
)

// base model type
type BaseModel interface {

  // site url prefix
  Prefix() string

  // render to a writer
  RenderTo(wr io.Writer) error

}

// for attachments
type AttachmentModel interface {

  BaseModel

  Thumbnail() string
  Source() string
  Filename() string

}

// for individual posts
type PostModel interface {

  BaseModel

  CSSClass() string

  MessageID() string
  PostHash() string
  ShortHash() string
  PostURL() string
  Frontend() string
  Subject() string
  Name() string
  Date() string
  OP() bool
  Attachments() []AttachmentModel
  Board() string
  Sage() bool
  Pubkey() string
  Reference() string

  RenderBody() string
  RenderPost() string

  IsI2P() bool
  IsTor() bool
  IsClearnet() bool

  // truncate body to a certain size
  // return copy
  Truncate() PostModel

}

// interface for models that have a navbar
type NavbarModel interface {

  Navbar() string

}

// for threads
type ThreadModel interface {

  BaseModel
  NavbarModel

  OP() PostModel
  Replies() []PostModel
  Board() string
  BoardURL() string
  // return a short version of the thread
  // does not include all replies
  Truncate() ThreadModel
  // update the thread's replies
  // return the updated model
  Update(db Database) ThreadModel
}

// board interface
// for 1 page on a board
type BoardModel interface {

  BaseModel
  NavbarModel

  Frontend() string
  Name() string
  Threads() []ThreadModel

  // JUST update this thread
  // if we don't have it already loaded do nothing
  UpdateThread(message_id string, db Database) BoardModel

  // get a thread model with this id
  // returns nil if we don't have it
  GetThread(message_id string) ThreadModel

  // deprecated, use GetThread
  HasThread(message_id string) bool

  // update the board's contents
  // return the updated model
  Update(db Database) BoardModel
}

type LinkModel interface {

  Text() string
  LinkURL() string
}

// newsgroup model
// every page on a newsgroup
type GroupModel []BoardModel

// TODO: optimize using 1 query?
// update every page
// return updated model
func (self GroupModel) UpdateAll(db Database) GroupModel {
  for idx, page := range self {
    self[idx] = page.Update(db)
  }
  return self
}

// update a certain page
// does nothing if out of bounds
func (self GroupModel) Update(page int, db Database) GroupModel {
  if len(self) > page {
    self[page] = self[page].Update(db)
  }
  return self
}

type boardModel struct {
  frontend string
  prefix string
  board string
  page int
  pages int
  threads []ThreadModel
}

func (self boardModel) Navbar() string {
  param := make(map[string]interface{})
  param["name"] = fmt.Sprintf("page %d for %s", self.page, self.board)
  param["frontend"] = self.frontend
  var links []LinkModel
  for i := 0 ; i < self.pages ; i ++ {
    links = append(links, linkModel{
      link: fmt.Sprintf("%s%s-%d.html", self.prefix, self.board, i),
      text: fmt.Sprintf("[ %d ]", i),
    })
  }
  param["prefix"] = self.prefix
  param["links"] = links
  return template.renderTemplate("navbar.mustache", param)
}

func (self boardModel) UpdateThread(messageID string, db Database) BoardModel {

  for idx, th := range self.threads {
    if th.OP().MessageID() == messageID {
      // found it
      self.threads[idx] = th.Update(db)
      break
    }
  }
  return self
}

func (self boardModel) GetThread(messageID string) ThreadModel {
  for _, th := range self.threads {
    if th.OP().MessageID() == messageID {
      return th
    }
  }
  return nil
}

func (self boardModel) HasThread(messageID string) bool {
  return self.GetThread(messageID) != nil
}

func (self boardModel) Frontend() string {
  return self.frontend
}

func (self boardModel) Prefix() string {
  return self.prefix
}

func (self boardModel) Name() string {
  return self.board
}

func (self boardModel) Threads() []ThreadModel {
  return self.threads
}

func (self boardModel) RenderTo(wr io.Writer) error {
  param := make(map[string]interface{})
  param["board"] = self
  param["form"] = renderPostForm(self.Prefix(), self.board, "")
  _, err := io.WriteString(wr, template.renderTemplate("board.mustache", param))
  return err
}

// refetch all threads on this page
func (self boardModel) Update(db Database) BoardModel {
  // ignore error
  perpage, _ := db.GetThreadsPerPage(self.board)
  // refetch all on this page
  model := db.GetGroupForPage(self.prefix, self.frontend, self.board, self.page, int(perpage))
  var threads []ThreadModel
  for _, th := range model.Threads() {
    threads = append(threads, th.Update(db))
  }
  return boardModel{
    frontend: self.frontend,
    prefix: self.prefix,
    board: self.board,
    page: self.page,
    pages: self.pages,
    threads: threads,
  }
}

type post struct {
  prefix string
  board string
  name string
  subject string
  message string
  message_rendered string
  message_id string
  path string
  addr string
  op bool
  posted int64
  parent string
  sage bool
  pubkey string
  reference string
  attachments []AttachmentModel
}

type attachment struct {
  prefix string
  filepath string
  filename string
}

func (self attachment) Prefix() string {
  return self.prefix
}

func (self attachment) RenderTo(wr io.Writer) error {
  // does nothing
  return nil
}

func (self attachment) Thumbnail() string {
  return self.prefix + "thm/" + self.filepath + ".jpg"
}

func (self attachment) Source() string {
  return self.prefix + "img/" + self.filepath
}

func (self attachment) Filename() string {
  return self.filename
}

func PostModelFromMessage(parent, prefix string, nntp NNTPMessage) PostModel {
  p := post{}
  p.name = nntp.Name()
  p.subject = nntp.Subject()
  p.message = nntp.Message()
  p.path = nntp.Path()
  p.message_id = nntp.MessageID()
  p.board = nntp.Newsgroup()
  p.posted = nntp.Posted()
  p.op = nntp.OP()
  p.prefix = prefix
  p.parent = parent
  p.addr = nntp.Addr()
  p.sage = nntp.Sage()
  p.pubkey = nntp.Pubkey()
  for _, att := range nntp.Attachments() {
    p.attachments = append(p.attachments, att.ToModel(prefix))
  }
  return p
}

func (self post) Reference() string {
  return self.parent
}

func (self post) ShortHash() string {
  return ShortHashMessageID(self.message_id)
}

func (self post) Pubkey() string {
  if len(self.pubkey) > 0 {
    return fmt.Sprintf("%s", self.pubkey, makeTripcode(self.pubkey))
  }
  return ""
}

func (self post) Sage() bool {
  return self.sage
}

func (self post) CSSClass() string {
  if self.OP() {
    return "post op"
  } else {
    return "post reply"
  }
}

func (self post) OP() bool {
  return self.parent == self.message_id || len(self.parent) == 0
}

func (self post) Date() string {
  return time.Unix(self.posted, 0).Format(time.ANSIC)
}

func (self post) TemplateDir() string {
  return filepath.Join("contrib", "templates", "default")
}

func (self post) MessageID() string  {
  return self.message_id
}

func (self post) Frontend() string {
  idx := strings.LastIndex(self.path, "!")
  if idx == -1 {
    return self.path
  }
  return self.path[idx+1:]
}

func (self post) Board() string {
  return self.board
}

func (self post) PostHash() string {
  return HashMessageID(self.message_id)
}

func (self post) Name() string {
  return self.name
}

func (self post) Subject() string {
  return self.subject
}

func (self post) Attachments() []AttachmentModel {
  return self.attachments
}

func (self post) PostURL() string {
  return fmt.Sprintf("%sthread-%s.html#%s", self.Prefix(), ShortHashMessageID(self.parent), self.PostHash())
}

func (self post) Prefix() string {
  return self.prefix
}

func (self post) IsClearnet() bool {
  return len(self.addr) == encAddrLen()
}

func (self post) IsI2P() bool {
  return len(self.addr) == i2pDestHashLen()
}

func (self post) IsTor() bool {
  return len(self.addr) == 0
}

func (self post) RenderTo(wr io.Writer) error {
  _, err := io.WriteString(wr, self.RenderPost())
  return err
}

func (self post) RenderPost() string {
  return template.renderTemplate("post.mustache", self)
}

func (self post) Truncate() PostModel {
  if len(self.message) > 500 {
    message := self.message[:500] + "\n...\n[Post Truncated]\n"
    return post{
      message: message,
      prefix: self.prefix,
      board: self.board,
      name: self.name,
      subject: self.subject,
      message_id: self.message_id,
      path: self.path,
      op: self.op,
      posted: self.posted,
      parent: self.parent,
      sage: self.sage,
      pubkey: self.pubkey,
      reference: self.reference,
      attachments: self.attachments,
      addr: self.addr,
    }
  }
  return self
}

func (self post) RenderShortBody() string {
  // TODO: hardcoded limit
  return memeposting(self.message)
}

func (self post) RenderBody() string {
  // :^)
  if len(self.message_rendered) == 0 {
    self.message_rendered = memeposting(self.message)
  }
  return self.message_rendered
}

type thread struct {
  prefix string
  links []LinkModel
  posts []PostModel
}

func (self thread) Prefix() string {
  return self.prefix
}

func (self thread) Navbar() string {
  param := make(map[string]interface{})
  param["name"] = fmt.Sprintf("Thread %s", self.posts[0].ShortHash())
  param["frontend"] = self.Board()
  param["links"] = self.links
  param["prefix"] = self.prefix
  return template.renderTemplate("navbar.mustache", param)
}

func (self thread) Board() string {
  return self.posts[0].Board()
}

func (self thread) BoardURL() string {
  return fmt.Sprintf("%s%s-0.html", self.Prefix(), self.Board())
}

// get our default template dir
func defaultTemplateDir() string {
  return  filepath.Join("contrib", "templates", "default")
}

func (self thread) RenderTo(wr io.Writer) error {
  postform := renderPostForm(self.prefix, self.Board(), self.posts[0].MessageID())
  data := template.renderTemplate("thread.mustache", map[string]interface{} { "thread": self, "form" : postform})
  io.WriteString(wr, data)
  return nil
}

func (self thread) OP() PostModel {
  return self.posts[0]
}

func (self thread) Replies() []PostModel {
  if len(self.posts) > 1 {
    return self.posts[1:]
  }
  return []PostModel{}
}

func (self thread) Truncate() ThreadModel {
  trunc := 5
  if len(self.posts) > trunc {
    return thread{
      links: self.links,
      posts: append([]PostModel{self.posts[0]}, self.posts[len(self.posts)-trunc:]...),
      prefix: self.prefix,
    }
  }
  return self
}

// refetch all replies if anything differs
func (self thread) Update(db Database) ThreadModel {
  root := self.posts[0].MessageID()
  reply_count := db.CountThreadReplies(root)

  if int(reply_count) + 1 != len(self.posts) {

    return thread{
      posts: append([]PostModel{self.posts[0]}, db.GetThreadReplyPostModels(self.prefix, root, 0)...),
      links: self.links,
      prefix: self.prefix,
    }
  }
  return self
}

type linkModel struct {
  text string
  link string
}

func (self linkModel) LinkURL() string {
  return self.link
}

func (self linkModel) Text() string {
  return self.text
}

type boardPageRow struct {
  Board string
  Hour int64
  Day int64
  All int64
}

type boardPageRows []boardPageRow

func (self boardPageRows) Len() int {
  return len(self)
}

func (self boardPageRows) Less(i, j int) bool {
  i_val := self[i]
  j_val := self[j]
  return (i_val.Day + i_val.Hour * 24 ) > ( j_val.Day + j_val.Hour * 24)
}

func (self boardPageRows) Swap(i, j int) {
  self[i] , self[j] = self[j], self[i]
}

type postsGraphRow struct {
  day time.Time
  Num int64
}

func (p *postsGraphRow) GraphRune(r string) (s string) {
  num := p.Num
  for num > 0 {
    s += r
    num --
  }
  return
}

func (p postsGraphRow) Day() (s string) {
  return p.day.String()[5:11]
}

func (p postsGraphRow) RegularGraph() (s string) {
  return p.GraphRune("=")
}

// :0========3 overcock :3 graph of data
func (p postsGraphRow) OvercockGraph() (s string) {
  num := p.Num
  if num > 0 {
    s = ":0"
    num -= 1
    for num > 0 {
      s += "="
      num--
    }
    s += "3"
  } else {
    s = ":3"
  }
  return
}

type postsGraph []postsGraphRow

func (graph postsGraph) Render() string {
  return template.renderTemplate("posts_graph.mustache", map[string]interface{} { "graph": graph})
}

func (self postsGraph) Len() int {
  return len(self)
}

func (self postsGraph) Less(i, j int) bool {
  i_val := self[i]
  j_val := self[j]
  return i_val.day.Day() > j_val.day.Day()
}

func (self postsGraph) Swap(i, j int) {
  self[i] , self[j] = self[j], self[i]
}
<commit_msg>scale down graphs<commit_after>//
// model.go
// template model implementation
//
package srnd

import (
  "fmt"
  "io"
  "path/filepath"
  "strings"
  "time"
)

// base model type
type BaseModel interface {

  // site url prefix
  Prefix() string

  // render to a writer
  RenderTo(wr io.Writer) error

}

// for attachments
type AttachmentModel interface {

  BaseModel

  Thumbnail() string
  Source() string
  Filename() string

}

// for individual posts
type PostModel interface {

  BaseModel

  CSSClass() string

  MessageID() string
  PostHash() string
  ShortHash() string
  PostURL() string
  Frontend() string
  Subject() string
  Name() string
  Date() string
  OP() bool
  Attachments() []AttachmentModel
  Board() string
  Sage() bool
  Pubkey() string
  Reference() string

  RenderBody() string
  RenderPost() string

  IsI2P() bool
  IsTor() bool
  IsClearnet() bool

  // truncate body to a certain size
  // return copy
  Truncate() PostModel

}

// interface for models that have a navbar
type NavbarModel interface {

  Navbar() string

}

// for threads
type ThreadModel interface {

  BaseModel
  NavbarModel

  OP() PostModel
  Replies() []PostModel
  Board() string
  BoardURL() string
  // return a short version of the thread
  // does not include all replies
  Truncate() ThreadModel
  // update the thread's replies
  // return the updated model
  Update(db Database) ThreadModel
}

// board interface
// for 1 page on a board
type BoardModel interface {

  BaseModel
  NavbarModel

  Frontend() string
  Name() string
  Threads() []ThreadModel

  // JUST update this thread
  // if we don't have it already loaded do nothing
  UpdateThread(message_id string, db Database) BoardModel

  // get a thread model with this id
  // returns nil if we don't have it
  GetThread(message_id string) ThreadModel

  // deprecated, use GetThread
  HasThread(message_id string) bool

  // update the board's contents
  // return the updated model
  Update(db Database) BoardModel
}

type LinkModel interface {

  Text() string
  LinkURL() string
}

// newsgroup model
// every page on a newsgroup
type GroupModel []BoardModel

// TODO: optimize using 1 query?
// update every page
// return updated model
func (self GroupModel) UpdateAll(db Database) GroupModel {
  for idx, page := range self {
    self[idx] = page.Update(db)
  }
  return self
}

// update a certain page
// does nothing if out of bounds
func (self GroupModel) Update(page int, db Database) GroupModel {
  if len(self) > page {
    self[page] = self[page].Update(db)
  }
  return self
}

type boardModel struct {
  frontend string
  prefix string
  board string
  page int
  pages int
  threads []ThreadModel
}

func (self boardModel) Navbar() string {
  param := make(map[string]interface{})
  param["name"] = fmt.Sprintf("page %d for %s", self.page, self.board)
  param["frontend"] = self.frontend
  var links []LinkModel
  for i := 0 ; i < self.pages ; i ++ {
    links = append(links, linkModel{
      link: fmt.Sprintf("%s%s-%d.html", self.prefix, self.board, i),
      text: fmt.Sprintf("[ %d ]", i),
    })
  }
  param["prefix"] = self.prefix
  param["links"] = links
  return template.renderTemplate("navbar.mustache", param)
}

func (self boardModel) UpdateThread(messageID string, db Database) BoardModel {

  for idx, th := range self.threads {
    if th.OP().MessageID() == messageID {
      // found it
      self.threads[idx] = th.Update(db)
      break
    }
  }
  return self
}

func (self boardModel) GetThread(messageID string) ThreadModel {
  for _, th := range self.threads {
    if th.OP().MessageID() == messageID {
      return th
    }
  }
  return nil
}

func (self boardModel) HasThread(messageID string) bool {
  return self.GetThread(messageID) != nil
}

func (self boardModel) Frontend() string {
  return self.frontend
}

func (self boardModel) Prefix() string {
  return self.prefix
}

func (self boardModel) Name() string {
  return self.board
}

func (self boardModel) Threads() []ThreadModel {
  return self.threads
}

func (self boardModel) RenderTo(wr io.Writer) error {
  param := make(map[string]interface{})
  param["board"] = self
  param["form"] = renderPostForm(self.Prefix(), self.board, "")
  _, err := io.WriteString(wr, template.renderTemplate("board.mustache", param))
  return err
}

// refetch all threads on this page
func (self boardModel) Update(db Database) BoardModel {
  // ignore error
  perpage, _ := db.GetThreadsPerPage(self.board)
  // refetch all on this page
  model := db.GetGroupForPage(self.prefix, self.frontend, self.board, self.page, int(perpage))
  var threads []ThreadModel
  for _, th := range model.Threads() {
    threads = append(threads, th.Update(db))
  }
  return boardModel{
    frontend: self.frontend,
    prefix: self.prefix,
    board: self.board,
    page: self.page,
    pages: self.pages,
    threads: threads,
  }
}

type post struct {
  prefix string
  board string
  name string
  subject string
  message string
  message_rendered string
  message_id string
  path string
  addr string
  op bool
  posted int64
  parent string
  sage bool
  pubkey string
  reference string
  attachments []AttachmentModel
}

type attachment struct {
  prefix string
  filepath string
  filename string
}

func (self attachment) Prefix() string {
  return self.prefix
}

func (self attachment) RenderTo(wr io.Writer) error {
  // does nothing
  return nil
}

func (self attachment) Thumbnail() string {
  return self.prefix + "thm/" + self.filepath + ".jpg"
}

func (self attachment) Source() string {
  return self.prefix + "img/" + self.filepath
}

func (self attachment) Filename() string {
  return self.filename
}

func PostModelFromMessage(parent, prefix string, nntp NNTPMessage) PostModel {
  p := post{}
  p.name = nntp.Name()
  p.subject = nntp.Subject()
  p.message = nntp.Message()
  p.path = nntp.Path()
  p.message_id = nntp.MessageID()
  p.board = nntp.Newsgroup()
  p.posted = nntp.Posted()
  p.op = nntp.OP()
  p.prefix = prefix
  p.parent = parent
  p.addr = nntp.Addr()
  p.sage = nntp.Sage()
  p.pubkey = nntp.Pubkey()
  for _, att := range nntp.Attachments() {
    p.attachments = append(p.attachments, att.ToModel(prefix))
  }
  return p
}

func (self post) Reference() string {
  return self.parent
}

func (self post) ShortHash() string {
  return ShortHashMessageID(self.message_id)
}

func (self post) Pubkey() string {
  if len(self.pubkey) > 0 {
    return fmt.Sprintf("%s", self.pubkey, makeTripcode(self.pubkey))
  }
  return ""
}

func (self post) Sage() bool {
  return self.sage
}

func (self post) CSSClass() string {
  if self.OP() {
    return "post op"
  } else {
    return "post reply"
  }
}

func (self post) OP() bool {
  return self.parent == self.message_id || len(self.parent) == 0
}

func (self post) Date() string {
  return time.Unix(self.posted, 0).Format(time.ANSIC)
}

func (self post) TemplateDir() string {
  return filepath.Join("contrib", "templates", "default")
}

func (self post) MessageID() string  {
  return self.message_id
}

func (self post) Frontend() string {
  idx := strings.LastIndex(self.path, "!")
  if idx == -1 {
    return self.path
  }
  return self.path[idx+1:]
}

func (self post) Board() string {
  return self.board
}

func (self post) PostHash() string {
  return HashMessageID(self.message_id)
}

func (self post) Name() string {
  return self.name
}

func (self post) Subject() string {
  return self.subject
}

func (self post) Attachments() []AttachmentModel {
  return self.attachments
}

func (self post) PostURL() string {
  return fmt.Sprintf("%sthread-%s.html#%s", self.Prefix(), ShortHashMessageID(self.parent), self.PostHash())
}

func (self post) Prefix() string {
  return self.prefix
}

func (self post) IsClearnet() bool {
  return len(self.addr) == encAddrLen()
}

func (self post) IsI2P() bool {
  return len(self.addr) == i2pDestHashLen()
}

func (self post) IsTor() bool {
  return len(self.addr) == 0
}

func (self post) RenderTo(wr io.Writer) error {
  _, err := io.WriteString(wr, self.RenderPost())
  return err
}

func (self post) RenderPost() string {
  return template.renderTemplate("post.mustache", self)
}

func (self post) Truncate() PostModel {
  if len(self.message) > 500 {
    message := self.message[:500] + "\n...\n[Post Truncated]\n"
    return post{
      message: message,
      prefix: self.prefix,
      board: self.board,
      name: self.name,
      subject: self.subject,
      message_id: self.message_id,
      path: self.path,
      op: self.op,
      posted: self.posted,
      parent: self.parent,
      sage: self.sage,
      pubkey: self.pubkey,
      reference: self.reference,
      attachments: self.attachments,
      addr: self.addr,
    }
  }
  return self
}

func (self post) RenderShortBody() string {
  // TODO: hardcoded limit
  return memeposting(self.message)
}

func (self post) RenderBody() string {
  // :^)
  if len(self.message_rendered) == 0 {
    self.message_rendered = memeposting(self.message)
  }
  return self.message_rendered
}

type thread struct {
  prefix string
  links []LinkModel
  posts []PostModel
}

func (self thread) Prefix() string {
  return self.prefix
}

func (self thread) Navbar() string {
  param := make(map[string]interface{})
  param["name"] = fmt.Sprintf("Thread %s", self.posts[0].ShortHash())
  param["frontend"] = self.Board()
  param["links"] = self.links
  param["prefix"] = self.prefix
  return template.renderTemplate("navbar.mustache", param)
}

func (self thread) Board() string {
  return self.posts[0].Board()
}

func (self thread) BoardURL() string {
  return fmt.Sprintf("%s%s-0.html", self.Prefix(), self.Board())
}

// get our default template dir
func defaultTemplateDir() string {
  return  filepath.Join("contrib", "templates", "default")
}

func (self thread) RenderTo(wr io.Writer) error {
  postform := renderPostForm(self.prefix, self.Board(), self.posts[0].MessageID())
  data := template.renderTemplate("thread.mustache", map[string]interface{} { "thread": self, "form" : postform})
  io.WriteString(wr, data)
  return nil
}

func (self thread) OP() PostModel {
  return self.posts[0]
}

func (self thread) Replies() []PostModel {
  if len(self.posts) > 1 {
    return self.posts[1:]
  }
  return []PostModel{}
}

func (self thread) Truncate() ThreadModel {
  trunc := 5
  if len(self.posts) > trunc {
    return thread{
      links: self.links,
      posts: append([]PostModel{self.posts[0]}, self.posts[len(self.posts)-trunc:]...),
      prefix: self.prefix,
    }
  }
  return self
}

// refetch all replies if anything differs
func (self thread) Update(db Database) ThreadModel {
  root := self.posts[0].MessageID()
  reply_count := db.CountThreadReplies(root)

  if int(reply_count) + 1 != len(self.posts) {

    return thread{
      posts: append([]PostModel{self.posts[0]}, db.GetThreadReplyPostModels(self.prefix, root, 0)...),
      links: self.links,
      prefix: self.prefix,
    }
  }
  return self
}

type linkModel struct {
  text string
  link string
}

func (self linkModel) LinkURL() string {
  return self.link
}

func (self linkModel) Text() string {
  return self.text
}

type boardPageRow struct {
  Board string
  Hour int64
  Day int64
  All int64
}

type boardPageRows []boardPageRow

func (self boardPageRows) Len() int {
  return len(self)
}

func (self boardPageRows) Less(i, j int) bool {
  i_val := self[i]
  j_val := self[j]
  return (i_val.Day + i_val.Hour * 24 ) > ( j_val.Day + j_val.Hour * 24)
}

func (self boardPageRows) Swap(i, j int) {
  self[i] , self[j] = self[j], self[i]
}

type postsGraphRow struct {
  day time.Time
  Num int64
}

func (p *postsGraphRow) GraphRune(r string) (s string) {
  num := p.Num
  for num > 0 {
    s += r
    num --
  }
  return
}

func (p postsGraphRow) Day() (s string) {
  return p.day.String()[5:11]
}

func (p postsGraphRow) RegularGraph() (s string) {
  return p.GraphRune("=")
}

// :0========3 overcock :3 graph of data
func (p postsGraphRow) OvercockGraph() (s string) {
  num := p.Num / 50
  if num > 0 {
    s = ":0"
    num -= 1
    for num > 0 {
      s += "="
      num--
    }
    s += "3"
  } else {
    s = ":3"
  }
  return
}

type postsGraph []postsGraphRow

func (graph postsGraph) Render() string {
  return template.renderTemplate("posts_graph.mustache", map[string]interface{} { "graph": graph})
}

func (self postsGraph) Len() int {
  return len(self)
}

func (self postsGraph) Less(i, j int) bool {
  i_val := self[i]
  j_val := self[j]
  return i_val.day.Day() > j_val.day.Day()
}

func (self postsGraph) Swap(i, j int) {
  self[i] , self[j] = self[j], self[i]
}
<|endoftext|>


<commit_before>//
// model.go
// template model implementation
//
package srnd

import (
  "fmt"
  "io"
  "path/filepath"
  "strings"
  "time"
)

// base model type
type BaseModel interface {

  // site url prefix
  Prefix() string

  // render to a writer
  RenderTo(wr io.Writer) error

}

// for attachments
type AttachmentModel interface {

  BaseModel

  Thumbnail() string
  Source() string
  Filename() string

}

// for individual posts
type PostModel interface {

  BaseModel

  CSSClass() string

  MessageID() string
  PostHash() string
  ShortHash() string
  PostURL() string
  Frontend() string
  Subject() string
  Name() string
  Date() string
  OP() bool
  Attachments() []AttachmentModel
  Board() string
  Sage() bool
  Pubkey() string
  Reference() string

  RenderBody() string
  RenderPost() string

  // truncate body to a certain size
  // return copy
  Truncate() PostModel

}

// interface for models that have a navbar
type NavbarModel interface {

  Navbar() string

}

// for threads
type ThreadModel interface {

  BaseModel
  NavbarModel

  OP() PostModel
  Replies() []PostModel
  Board() string
  BoardURL() string
  // return a short version of the thread
  // does not include all replies
  Truncate() ThreadModel
  // update the thread's replies
  // return the updated model
  Update(db Database) ThreadModel
}

// board interface
// for 1 page on a board
type BoardModel interface {

  BaseModel
  NavbarModel

  Frontend() string
  Name() string
  Threads() []ThreadModel

  // JUST update this thread
  // if we don't have it already loaded do nothing
  UpdateThread(message_id string, db Database) BoardModel

  // update the board's contents
  // return the updated model
  Update(db Database) BoardModel
}

type LinkModel interface {

  Text() string
  LinkURL() string
}

// newsgroup model
// every page on a newsgroup
type GroupModel []BoardModel

// TODO: optimize using 1 query?
// update every page
// return updated model
func (self GroupModel) UpdateAll(db Database) GroupModel {
  for idx, page := range self {
    self[idx] = page.Update(db)
  }
  return self
}

// update a certain page
// does nothing if out of bounds
func (self GroupModel) Update(page int, db Database) GroupModel {
  if len(self) > page {
    self[page] = self[page].Update(db)
  }
  return self
}

type boardModel struct {
  frontend string
  prefix string
  board string
  page int
  pages int
  threads []ThreadModel
}

func (self boardModel) Navbar() string {
  param := make(map[string]interface{})
  param["name"] = fmt.Sprintf("page %d for %s", self.page, self.board)
  param["frontend"] = self.frontend
  var links []LinkModel
  for i := 0 ; i < self.pages ; i ++ {
    links = append(links, linkModel{
      link: fmt.Sprintf("%s%s-%d.html", self.prefix, self.board, i),
      text: fmt.Sprintf("[ %d ]", i),
    })
  }
  param["prefix"] = self.prefix
  param["links"] = links
  return template.renderTemplate("navbar.mustache", param)
}

func (self boardModel) UpdateThread(messageID string, db Database) BoardModel {
  for idx, th := range self.threads {
    if th.OP().MessageID() == messageID {
      // found it
      self.threads[idx] = th.Update(db)
    }
  }
  return self
}

func (self boardModel) Frontend() string {
  return self.frontend
}

func (self boardModel) Prefix() string {
  return self.prefix
}

func (self boardModel) Name() string {
  return self.board
}

func (self boardModel) Threads() []ThreadModel {
  return self.threads
}

func (self boardModel) RenderTo(wr io.Writer) error {
  param := make(map[string]interface{})
  param["board"] = self
  param["form"] = renderPostForm(self.Prefix(), self.board, "")
  _, err := io.WriteString(wr, template.renderTemplate("board.mustache", param))
  return err
}

// refetch all threads on this page
func (self boardModel) Update(db Database) BoardModel {
  // ignore error
  perpage, _ := db.GetThreadsPerPage(self.board)
  // refetch all on this page
  model := db.GetGroupForPage(self.prefix, self.frontend, self.board, self.page, int(perpage))
  var threads []ThreadModel
  for _, th := range model.Threads() {
    threads = append(threads, th.Update(db))
  }
  return boardModel{
    frontend: self.frontend,
    prefix: self.prefix,
    board: self.board,
    page: self.page,
    pages: self.pages,
    threads: threads,
  }
}

type post struct {
  prefix string
  board string
  name string
  subject string
  message string
  message_id string
  path string
  op bool
  posted int64
  parent string
  sage bool
  pubkey string
  reference string
  attachments []AttachmentModel
}

type attachment struct {
  prefix string
  thumbnail string
  source string
  filename string
}

func (self attachment) Prefix() string {
  return self.prefix
}

func (self attachment) RenderTo(wr io.Writer) error {
  // does nothing
  return nil
}

func (self attachment) Thumbnail() string {
  return self.thumbnail
}

func (self attachment) Source() string {
  return self.source
}

func (self attachment) Filename() string {
  return self.filename
}

func PostModelFromMessage(parent, prefix string, nntp NNTPMessage) PostModel {
  p := post{}
  p.name = nntp.Name()
  p.subject = nntp.Subject()
  p.message = nntp.Message()
  p.path = nntp.Path()
  p.message_id = nntp.MessageID()
  p.board = nntp.Newsgroup()
  p.posted = nntp.Posted()
  p.op = nntp.OP()
  p.prefix = prefix
  p.parent = parent
  p.sage = nntp.Sage()
  p.pubkey = nntp.Pubkey()
  for _, att := range nntp.Attachments() {
    p.attachments = append(p.attachments, att.ToModel(prefix))
  }
  return p
}

func (self post) Reference() string {
  return self.parent
}

func (self post) ShortHash() string {
  return ShortHashMessageID(self.message_id)
}

func (self post) Pubkey() string {
  if len(self.pubkey) > 0 {
    return fmt.Sprintf("%s", self.pubkey, makeTripcode(self.pubkey))
  }
  return ""
}

func (self post) Sage() bool {
  return self.sage
}

func (self post) CSSClass() string {
  if self.OP() {
    return "post op"
  } else {
    return "post reply"
  }
}

func (self post) OP() bool {
  return self.parent == self.message_id || len(self.parent) == 0
}

func (self post) Date() string {
  return time.Unix(self.posted, 0).Format(time.ANSIC)
}

func (self post) TemplateDir() string {
  return filepath.Join("contrib", "templates", "default")
}

func (self post) MessageID() string  {
  return self.message_id
}

func (self post) Frontend() string {
  idx := strings.LastIndex(self.path, "!")
  if idx == -1 {
    return self.path
  }
  return self.path[idx+1:]
}

func (self post) Board() string {
  return self.board
}

func (self post) PostHash() string {
  return HashMessageID(self.message_id)
}

func (self post) Name() string {
  return self.name
}

func (self post) Subject() string {
  return self.subject
}

// TODO: implement
func (self post) Attachments() []AttachmentModel {
  return self.attachments
}

func (self post) PostURL() string {
  return fmt.Sprintf("%sthread-%s.html#%s", self.Prefix(), ShortHashMessageID(self.parent), self.PostHash())
}

func (self post) Prefix() string {
  return self.prefix
}

func (self post) RenderTo(wr io.Writer) error {
  _, err := io.WriteString(wr, self.RenderPost())
  return err
}

func (self post) RenderPost() string {
  return template.renderTemplate("post.mustache", self)
}

func (self post) Truncate() PostModel {
  if len(self.message) > 500 {
    message := self.message[:500] + "\n...\n[Post Truncated]\n"
    return post{
      message: message,
      prefix: self.prefix,
      board: self.board,
      name: self.name,
      subject: self.subject,
      message_id: self.message_id,
      path: self.path,
      op: self.op,
      posted: self.posted,
      parent: self.parent,
      sage: self.sage,
      pubkey: self.pubkey,
      reference: self.reference,
      attachments: self.attachments,
    }
  }
  return self
}

func (self post) RenderShortBody() string {
  // TODO: hardcoded limit
  return memeposting(self.message)
}

func (self post) RenderBody() string {
  // :^)
  return memeposting(self.message)
}

type thread struct {
  prefix string
  links []LinkModel
  posts []PostModel
}

func (self thread) Prefix() string {
  return self.prefix
}

func (self thread) Navbar() string {
  param := make(map[string]interface{})
  param["name"] = fmt.Sprintf("Thread %s", self.posts[0].ShortHash())
  param["frontend"] = self.Board()
  param["links"] = self.links
  param["prefix"] = self.prefix
  return template.renderTemplate("navbar.mustache", param)
}

func (self thread) Board() string {
  return self.posts[0].Board()
}

func (self thread) BoardURL() string {
  return fmt.Sprintf("%s%s-0.html", self.Prefix(), self.Board())
}

// get our default template dir
func defaultTemplateDir() string {
  return  filepath.Join("contrib", "templates", "default")
}

func (self thread) RenderTo(wr io.Writer) error {
  postform := renderPostForm(self.prefix, self.Board(), self.posts[0].MessageID())
  data := template.renderTemplate("thread.mustache", map[string]interface{} { "thread": self, "form" : postform})
  io.WriteString(wr, data)
  return nil
}

func (self thread) OP() PostModel {
  return self.posts[0]
}

func (self thread) Replies() []PostModel {
  if len(self.posts) > 1 {
    return self.posts[1:]
  }
  return []PostModel{}
}

func (self thread) Truncate() ThreadModel {
  trunc := 5
  if len(self.posts) > trunc {
    return thread{
      links: self.links,
      posts: append([]PostModel{self.posts[0]}, self.posts[len(self.posts)-trunc:]...),
      prefix: self.prefix,
    }
  }
  return self
}

// refetch all replies if anything differs
func (self thread) Update(db Database) ThreadModel {
  root := self.posts[0].MessageID()
  reply_count := db.CountThreadReplies(root)

  if int(reply_count) + 1 != len(self.posts) {

    return thread{
      posts: append([]PostModel{self.posts[0]}, db.GetThreadReplyPostModels(self.prefix, root, 0)...),
      links: self.links,
      prefix: self.prefix,
    }
  }
  return self
}

type linkModel struct {
  text string
  link string
}

func (self linkModel) LinkURL() string {
  return self.link
}

func (self linkModel) Text() string {
  return self.text
}
<commit_msg>fixes<commit_after>//
// model.go
// template model implementation
//
package srnd

import (
  "fmt"
  "io"
  "path/filepath"
  "strings"
  "time"
)

// base model type
type BaseModel interface {

  // site url prefix
  Prefix() string

  // render to a writer
  RenderTo(wr io.Writer) error

}

// for attachments
type AttachmentModel interface {

  BaseModel

  Thumbnail() string
  Source() string
  Filename() string

}

// for individual posts
type PostModel interface {

  BaseModel

  CSSClass() string

  MessageID() string
  PostHash() string
  ShortHash() string
  PostURL() string
  Frontend() string
  Subject() string
  Name() string
  Date() string
  OP() bool
  Attachments() []AttachmentModel
  Board() string
  Sage() bool
  Pubkey() string
  Reference() string

  RenderBody() string
  RenderPost() string

  // truncate body to a certain size
  // return copy
  Truncate() PostModel

}

// interface for models that have a navbar
type NavbarModel interface {

  Navbar() string

}

// for threads
type ThreadModel interface {

  BaseModel
  NavbarModel

  OP() PostModel
  Replies() []PostModel
  Board() string
  BoardURL() string
  // return a short version of the thread
  // does not include all replies
  Truncate() ThreadModel
  // update the thread's replies
  // return the updated model
  Update(db Database) ThreadModel
}

// board interface
// for 1 page on a board
type BoardModel interface {

  BaseModel
  NavbarModel

  Frontend() string
  Name() string
  Threads() []ThreadModel

  // JUST update this thread
  // if we don't have it already loaded do nothing
  UpdateThread(message_id string, db Database) BoardModel

  // update the board's contents
  // return the updated model
  Update(db Database) BoardModel
}

type LinkModel interface {

  Text() string
  LinkURL() string
}

// newsgroup model
// every page on a newsgroup
type GroupModel []BoardModel

// TODO: optimize using 1 query?
// update every page
// return updated model
func (self GroupModel) UpdateAll(db Database) GroupModel {
  for idx, page := range self {
    self[idx] = page.Update(db)
  }
  return self
}

// update a certain page
// does nothing if out of bounds
func (self GroupModel) Update(page int, db Database) GroupModel {
  if len(self) > page {
    self[page] = self[page].Update(db)
  }
  return self
}

type boardModel struct {
  frontend string
  prefix string
  board string
  page int
  pages int
  threads []ThreadModel
}

func (self boardModel) Navbar() string {
  param := make(map[string]interface{})
  param["name"] = fmt.Sprintf("page %d for %s", self.page, self.board)
  param["frontend"] = self.frontend
  var links []LinkModel
  for i := 0 ; i < self.pages ; i ++ {
    links = append(links, linkModel{
      link: fmt.Sprintf("%s%s-%d.html", self.prefix, self.board, i),
      text: fmt.Sprintf("[ %d ]", i),
    })
  }
  param["prefix"] = self.prefix
  param["links"] = links
  return template.renderTemplate("navbar.mustache", param)
}

func (self boardModel) UpdateThread(messageID string, db Database) BoardModel {
  for idx, th := range self.threads {
    if th.OP().MessageID() == messageID {
      // found it
      self.threads[idx] = th.Update(db)
    }
  }
  return self
}

func (self boardModel) Frontend() string {
  return self.frontend
}

func (self boardModel) Prefix() string {
  return self.prefix
}

func (self boardModel) Name() string {
  return self.board
}

func (self boardModel) Threads() []ThreadModel {
  return self.threads
}

func (self boardModel) RenderTo(wr io.Writer) error {
  param := make(map[string]interface{})
  param["board"] = self
  param["form"] = renderPostForm(self.Prefix(), self.board, "")
  _, err := io.WriteString(wr, template.renderTemplate("board.mustache", param))
  return err
}

// refetch all threads on this page
func (self boardModel) Update(db Database) BoardModel {
  // ignore error
  perpage, _ := db.GetThreadsPerPage(self.board)
  // refetch all on this page
  model := db.GetGroupForPage(self.prefix, self.frontend, self.board, self.page, int(perpage))
  var threads []ThreadModel
  for _, th := range model.Threads() {
    threads = append(threads, th.Update(db))
  }
  return boardModel{
    frontend: self.frontend,
    prefix: self.prefix,
    board: self.board,
    page: self.page,
    pages: self.pages,
    threads: threads,
  }
}

type post struct {
  prefix string
  board string
  name string
  subject string
  message string
  message_rendered string
  message_id string
  path string
  op bool
  posted int64
  parent string
  sage bool
  pubkey string
  reference string
  attachments []AttachmentModel
}

type attachment struct {
  prefix string
  thumbnail string
  source string
  filename string
}

func (self attachment) Prefix() string {
  return self.prefix
}

func (self attachment) RenderTo(wr io.Writer) error {
  // does nothing
  return nil
}

func (self attachment) Thumbnail() string {
  return self.thumbnail
}

func (self attachment) Source() string {
  return self.source
}

func (self attachment) Filename() string {
  return self.filename
}

func PostModelFromMessage(parent, prefix string, nntp NNTPMessage) PostModel {
  p := post{}
  p.name = nntp.Name()
  p.subject = nntp.Subject()
  p.message = nntp.Message()
  p.path = nntp.Path()
  p.message_id = nntp.MessageID()
  p.board = nntp.Newsgroup()
  p.posted = nntp.Posted()
  p.op = nntp.OP()
  p.prefix = prefix
  p.parent = parent
  p.sage = nntp.Sage()
  p.pubkey = nntp.Pubkey()
  for _, att := range nntp.Attachments() {
    p.attachments = append(p.attachments, att.ToModel(prefix))
  }
  return p
}

func (self post) Reference() string {
  return self.parent
}

func (self post) ShortHash() string {
  return ShortHashMessageID(self.message_id)
}

func (self post) Pubkey() string {
  if len(self.pubkey) > 0 {
    return fmt.Sprintf("%s", self.pubkey, makeTripcode(self.pubkey))
  }
  return ""
}

func (self post) Sage() bool {
  return self.sage
}

func (self post) CSSClass() string {
  if self.OP() {
    return "post op"
  } else {
    return "post reply"
  }
}

func (self post) OP() bool {
  return self.parent == self.message_id || len(self.parent) == 0
}

func (self post) Date() string {
  return time.Unix(self.posted, 0).Format(time.ANSIC)
}

func (self post) TemplateDir() string {
  return filepath.Join("contrib", "templates", "default")
}

func (self post) MessageID() string  {
  return self.message_id
}

func (self post) Frontend() string {
  idx := strings.LastIndex(self.path, "!")
  if idx == -1 {
    return self.path
  }
  return self.path[idx+1:]
}

func (self post) Board() string {
  return self.board
}

func (self post) PostHash() string {
  return HashMessageID(self.message_id)
}

func (self post) Name() string {
  return self.name
}

func (self post) Subject() string {
  return self.subject
}

func (self post) Attachments() []AttachmentModel {
  return self.attachments
}

func (self post) PostURL() string {
  return fmt.Sprintf("%sthread-%s.html#%s", self.Prefix(), ShortHashMessageID(self.parent), self.PostHash())
}

func (self post) Prefix() string {
  return self.prefix
}

func (self post) RenderTo(wr io.Writer) error {
  _, err := io.WriteString(wr, self.RenderPost())
  return err
}

func (self post) RenderPost() string {
  return template.renderTemplate("post.mustache", self)
}

func (self post) Truncate() PostModel {
  if len(self.message) > 500 {
    message := self.message[:500] + "\n...\n[Post Truncated]\n"
    return post{
      message: message,
      prefix: self.prefix,
      board: self.board,
      name: self.name,
      subject: self.subject,
      message_id: self.message_id,
      path: self.path,
      op: self.op,
      posted: self.posted,
      parent: self.parent,
      sage: self.sage,
      pubkey: self.pubkey,
      reference: self.reference,
      attachments: self.attachments,
    }
  }
  return self
}

func (self post) RenderShortBody() string {
  // TODO: hardcoded limit
  return memeposting(self.message)
}

func (self post) RenderBody() string {
  // :^)
  if len(self.message_rendered) == 0 {
    self.message_rendered = memeposting(self.message)
  }
  return self.message_rendered
}

type thread struct {
  prefix string
  links []LinkModel
  posts []PostModel
}

func (self thread) Prefix() string {
  return self.prefix
}

func (self thread) Navbar() string {
  param := make(map[string]interface{})
  param["name"] = fmt.Sprintf("Thread %s", self.posts[0].ShortHash())
  param["frontend"] = self.Board()
  param["links"] = self.links
  param["prefix"] = self.prefix
  return template.renderTemplate("navbar.mustache", param)
}

func (self thread) Board() string {
  return self.posts[0].Board()
}

func (self thread) BoardURL() string {
  return fmt.Sprintf("%s%s-0.html", self.Prefix(), self.Board())
}

// get our default template dir
func defaultTemplateDir() string {
  return  filepath.Join("contrib", "templates", "default")
}

func (self thread) RenderTo(wr io.Writer) error {
  postform := renderPostForm(self.prefix, self.Board(), self.posts[0].MessageID())
  data := template.renderTemplate("thread.mustache", map[string]interface{} { "thread": self, "form" : postform})
  io.WriteString(wr, data)
  return nil
}

func (self thread) OP() PostModel {
  return self.posts[0]
}

func (self thread) Replies() []PostModel {
  if len(self.posts) > 1 {
    return self.posts[1:]
  }
  return []PostModel{}
}

func (self thread) Truncate() ThreadModel {
  trunc := 5
  if len(self.posts) > trunc {
    return thread{
      links: self.links,
      posts: append([]PostModel{self.posts[0]}, self.posts[len(self.posts)-trunc:]...),
      prefix: self.prefix,
    }
  }
  return self
}

// refetch all replies if anything differs
func (self thread) Update(db Database) ThreadModel {
  root := self.posts[0].MessageID()
  reply_count := db.CountThreadReplies(root)

  if int(reply_count) + 1 != len(self.posts) {

    return thread{
      posts: append([]PostModel{self.posts[0]}, db.GetThreadReplyPostModels(self.prefix, root, 0)...),
      links: self.links,
      prefix: self.prefix,
    }
  }
  return self
}

type linkModel struct {
  text string
  link string
}

func (self linkModel) LinkURL() string {
  return self.link
}

func (self linkModel) Text() string {
  return self.text
}
<|endoftext|>


<commit_before>package handler

import (
	"net/http"

	"github.com/drone/drone/pkg/database"
	. "github.com/drone/drone/pkg/model"
	"github.com/drone/go-github/github"
	"github.com/drone/go-github/oauth2"
)

// Create the User session.
func Authorize(w http.ResponseWriter, r *http.Request) error {
	// extract form data
	username := r.FormValue("username")
	password := r.FormValue("password")
	returnTo := r.FormValue("return_to")

	// get the user from the database
	user, err := database.GetUserEmail(username)
	if err != nil {
		return RenderTemplate(w, "login_error.html", nil)
	}

	// verify the password
	if err := user.ComparePassword(password); err != nil {
		return RenderTemplate(w, "login_error.html", nil)
	}

	// add the user to the session object
	SetCookie(w, r, "_sess", username)

	// where should we send the user to?
	if len(returnTo) == 0 {
		returnTo = "/dashboard"
	}

	// redirect to the homepage
	http.Redirect(w, r, returnTo, http.StatusSeeOther)
	return nil
}

func LinkGithub(w http.ResponseWriter, r *http.Request, u *User) error {

	// get settings from database
	settings := database.SettingsMust()

	// github OAuth2 Data
	var oauth = oauth2.Client{
		RedirectURL:      settings.URL().String() + "/auth/login/github",
		AccessTokenURL:   "https://github.com/login/oauth/access_token",
		AuthorizationURL: "https://github.com/login/oauth/authorize",
		ClientId:         settings.GitHubKey,
		ClientSecret:     settings.GitHubSecret,
	}

	// get the OAuth code
	code := r.FormValue("code")
	if len(code) == 0 {
		scope := "repo,repo:status,user:email"
		state := "FqB4EbagQ2o"
		redirect := oauth.AuthorizeRedirect(scope, state)
		http.Redirect(w, r, redirect, http.StatusSeeOther)
		return nil
	}

	// exchange code for an auth token
	token, err := oauth.GrantToken(code)
	if err != nil {
		return err
	}

	// create the client
	client := github.New(token.AccessToken)

	// get the user information
	githubUser, err := client.Users.Current()
	if err != nil {
		return err
	}

	// save the github token to the user account
	u.GithubToken = token.AccessToken
	u.GithubLogin = githubUser.Login
	if err := database.SaveUser(u); err != nil {
		return err
	}

	http.Redirect(w, r, "/new/github.com", http.StatusSeeOther)
	return nil
}
<commit_msg>Put values from settings into handler/auth.go<commit_after>package handler

import (
	"net/http"

	"github.com/drone/drone/pkg/database"
	. "github.com/drone/drone/pkg/model"
	"github.com/drone/go-github/github"
	"github.com/drone/go-github/oauth2"
)

// Create the User session.
func Authorize(w http.ResponseWriter, r *http.Request) error {
	// extract form data
	username := r.FormValue("username")
	password := r.FormValue("password")
	returnTo := r.FormValue("return_to")

	// get the user from the database
	user, err := database.GetUserEmail(username)
	if err != nil {
		return RenderTemplate(w, "login_error.html", nil)
	}

	// verify the password
	if err := user.ComparePassword(password); err != nil {
		return RenderTemplate(w, "login_error.html", nil)
	}

	// add the user to the session object
	SetCookie(w, r, "_sess", username)

	// where should we send the user to?
	if len(returnTo) == 0 {
		returnTo = "/dashboard"
	}

	// redirect to the homepage
	http.Redirect(w, r, returnTo, http.StatusSeeOther)
	return nil
}

func LinkGithub(w http.ResponseWriter, r *http.Request, u *User) error {

	// get settings from database
	settings := database.SettingsMust()

	// github OAuth2 Data
	var oauth = oauth2.Client{
		RedirectURL:      settings.URL().String() + "/auth/login/github",
		AccessTokenURL:   "https://" + settings.GitHubDomain + "/login/oauth/access_token",
		AuthorizationURL: "https://" + settings.GitHubDomain + "/login/oauth/authorize",
		ClientId:         settings.GitHubKey,
		ClientSecret:     settings.GitHubSecret,
	}

	// get the OAuth code
	code := r.FormValue("code")
	if len(code) == 0 {
		scope := "repo,repo:status,user:email"
		state := "FqB4EbagQ2o"
		redirect := oauth.AuthorizeRedirect(scope, state)
		http.Redirect(w, r, redirect, http.StatusSeeOther)
		return nil
	}

	// exchange code for an auth token
	token, err := oauth.GrantToken(code)
	if err != nil {
		return err
	}

	// create the client
	client := github.New(token.AccessToken)
	client.ApiUrl = settings.GitHubApiUrl

	// get the user information
	githubUser, err := client.Users.Current()
	if err != nil {
		return err
	}

	// save the github token to the user account
	u.GithubToken = token.AccessToken
	u.GithubLogin = githubUser.Login
	if err := database.SaveUser(u); err != nil {
		return err
	}

	http.Redirect(w, r, "/new/github.com", http.StatusSeeOther)
	return nil
}
<|endoftext|>


<commit_before>package model

import (
	"io"
)

type StorableConfig struct {
	UserInfo
}
type Config struct {
	StorableConfig
	Tokens
	APIaddr     string
	Fingerprint string
	Log         io.Writer
}
<commit_msg>add default namespace to config<commit_after>package model

import (
	"io"
)

type StorableConfig struct {
	UserInfo
	DefaultNamespace string
}
type Config struct {
	StorableConfig
	Tokens
	APIaddr     string
	Fingerprint string
	Log         io.Writer
}
<|endoftext|>