<commit_before>package goinsta

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
)

var (
	proxyUrl string
)

func (insta *Instagram) NewRequest(endpoint string, post string) ([]byte, error) {
	return insta.sendRequest(endpoint, post, false)
}

func (insta *Instagram) sendRequest(endpoint string, post string, options ...bool) (body []byte, err error) {
  isLoggedIn := false // Optional third argument
  checkStatus := true // Optional forth argument
  if len(options) == 1 && options[0] == true {
    isLoggedIn = true
  } else if len(options) == 2 && options[1] == false {
    checkStatus = false
  }

  if !insta.IsLoggedIn && !isLoggedIn {
		return nil, fmt.Errorf("not logged in")
	}

	var req *http.Request

	method := "GET"
	if len(post) > 0 {
		method = "POST"
	}
	req, err = http.NewRequest(method, GOINSTA_API_URL+endpoint, bytes.NewBuffer([]byte(post)))
	if err != nil {
		return
	}

	req.Header.Set("Connection", "close")
	req.Header.Set("Accept", "*/*")
	req.Header.Set("Content-type", "application/x-www-form-urlencoded; charset=UTF-8")
	req.Header.Set("Cookie2", "$Version=1")
	req.Header.Set("Accept-Language", "en-US")
	req.Header.Set("User-Agent", GOINSTA_USER_AGENT)

	client := &http.Client{
		Jar: insta.cookiejar,
	}
	if proxyUrl != "" {
		proxy, err := url.Parse(proxyUrl)
		if err != nil {
			return body, err
		}
		client.Transport = &http.Transport{Proxy: http.ProxyURL(proxy)}
	}

	resp, err := client.Do(req)
	if err != nil {
		return body, err
	}
	defer resp.Body.Close()

	insta.cookie = resp.Header.Get("Set-Cookie")

	body, _ = ioutil.ReadAll(resp.Body)

  if resp.StatusCode != 200 && checkStatus {
		return nil, fmt.Errorf("Invalid status code %s", string(body))
	}

	return body, err
}
<commit_msg>gofmt the code<commit_after>package goinsta

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
)

var (
	proxyUrl string
)

func (insta *Instagram) NewRequest(endpoint string, post string) ([]byte, error) {
	return insta.sendRequest(endpoint, post, false)
}

func (insta *Instagram) sendRequest(endpoint string, post string, options ...bool) (body []byte, err error) {
	isLoggedIn := false // Optional third argument
	checkStatus := true // Optional forth argument
	if len(options) == 1 && options[0] == true {
		isLoggedIn = true
	} else if len(options) == 2 && options[1] == false {
		checkStatus = false
	}

	if !insta.IsLoggedIn && !isLoggedIn {
		return nil, fmt.Errorf("not logged in")
	}

	var req *http.Request

	method := "GET"
	if len(post) > 0 {
		method = "POST"
	}
	req, err = http.NewRequest(method, GOINSTA_API_URL+endpoint, bytes.NewBuffer([]byte(post)))
	if err != nil {
		return
	}

	req.Header.Set("Connection", "close")
	req.Header.Set("Accept", "*/*")
	req.Header.Set("Content-type", "application/x-www-form-urlencoded; charset=UTF-8")
	req.Header.Set("Cookie2", "$Version=1")
	req.Header.Set("Accept-Language", "en-US")
	req.Header.Set("User-Agent", GOINSTA_USER_AGENT)

	client := &http.Client{
		Jar: insta.cookiejar,
	}
	if proxyUrl != "" {
		proxy, err := url.Parse(proxyUrl)
		if err != nil {
			return body, err
		}
		client.Transport = &http.Transport{Proxy: http.ProxyURL(proxy)}
	}

	resp, err := client.Do(req)
	if err != nil {
		return body, err
	}
	defer resp.Body.Close()

	insta.cookie = resp.Header.Get("Set-Cookie")

	body, _ = ioutil.ReadAll(resp.Body)

	if resp.StatusCode != 200 && checkStatus {
		return nil, fmt.Errorf("Invalid status code %s", string(body))
	}

	return body, err
}
<|endoftext|>


<commit_before>package web

import (
	"net/http"
	"reflect"
)

type Request struct {
	*http.Request

	// This map exists if you have wildcards in your URL that you need to capture.
	// Eg, /users/:id/tickets/:ticket_id and /users/1/tickets/33 would yield the map {id: "3", ticket_id: "33"}
	PathParams map[string]string

	// The actual route that got invoked
	route *Route

	rootContext   reflect.Value // Root context. Set immediately.
	targetContext reflect.Value // The target context corresponding to the route. Not set until root middleware is done.
}

func (r *Request) IsRouted() bool {
	return r.route != nil
}
func (r *Request) RoutePath() string {
	return r.route.Path
}
<commit_msg>BUGFIX: Don't panic if there's no route matched. Instead, return the empty string.<commit_after>package web

import (
	"net/http"
	"reflect"
)

type Request struct {
	*http.Request

	// This map exists if you have wildcards in your URL that you need to capture.
	// Eg, /users/:id/tickets/:ticket_id and /users/1/tickets/33 would yield the map {id: "3", ticket_id: "33"}
	PathParams map[string]string

	// The actual route that got invoked
	route *Route

	rootContext   reflect.Value // Root context. Set immediately.
	targetContext reflect.Value // The target context corresponding to the route. Not set until root middleware is done.
}

func (r *Request) IsRouted() bool {
	return r.route != nil
}

func (r *Request) RoutePath() string {
	if r.route != nil {
		return r.route.Path
	}
	return ""
}
<|endoftext|>


<commit_before>package httpfake

import (
	"net/url"
	"strings"
)

// Request stores the settings for a request handler
// Such as how to match this handler for the incoming requests
// And how this request will respond back
type Request struct {
	Method       string
	URL          *url.URL
	Response     *Response
	CustomHandle Responder
}

// NewRequest creates a new Request
func NewRequest() *Request {
	return &Request{
		URL:      &url.URL{},
		Response: NewResponse(),
	}
}

// Get sets a GET request handler for a given path
func (r *Request) Get(path string) *Request {
	return r.method("GET", path)
}

// Post sets a POST request handler for a given path
func (r *Request) Post(path string) *Request {
	return r.method("POST", path)
}

// Put sets a PUT request handler for a given path
func (r *Request) Put(path string) *Request {
	return r.method("PUT", path)
}

// Patch sets a PATCH request handler for a given path
func (r *Request) Patch(path string) *Request {
	return r.method("PATCH", path)
}

// Delete ...
func (r *Request) Delete(path string) *Request {
	return r.method("DELETE", path)
}

// Head sets a HEAD request handler for a given path
func (r *Request) Head(path string) *Request {
	return r.method("HEAD", path)
}

// Handle sets a custom handle
// By setting this responder it gives full control to the user over this request handler
func (r *Request) Handle(handle Responder) {
	r.CustomHandle = handle
}

// Reply sets a response status for this request
// And returns the Reponse struct to allow chaining the response settings
func (r *Request) Reply(status int) *Response {
	return r.Response.Status(status)
}

func (r *Request) method(method, path string) *Request {
	if path != "/" {
		r.URL.Path = path
	}
	r.Method = strings.ToUpper(method)
	return r
}
<commit_msg>Fix english typo<commit_after>package httpfake

import (
	"net/url"
	"strings"
)

// Request stores the settings for a request handler
// Such as how to match this handler for the incoming requests
// And how this request will respond back
type Request struct {
	Method       string
	URL          *url.URL
	Response     *Response
	CustomHandle Responder
}

// NewRequest creates a new Request
func NewRequest() *Request {
	return &Request{
		URL:      &url.URL{},
		Response: NewResponse(),
	}
}

// Get sets a GET request handler for a given path
func (r *Request) Get(path string) *Request {
	return r.method("GET", path)
}

// Post sets a POST request handler for a given path
func (r *Request) Post(path string) *Request {
	return r.method("POST", path)
}

// Put sets a PUT request handler for a given path
func (r *Request) Put(path string) *Request {
	return r.method("PUT", path)
}

// Patch sets a PATCH request handler for a given path
func (r *Request) Patch(path string) *Request {
	return r.method("PATCH", path)
}

// Delete ...
func (r *Request) Delete(path string) *Request {
	return r.method("DELETE", path)
}

// Head sets a HEAD request handler for a given path
func (r *Request) Head(path string) *Request {
	return r.method("HEAD", path)
}

// Handle sets a custom handle
// By setting this responder it gives full control to the user over this request handler
func (r *Request) Handle(handle Responder) {
	r.CustomHandle = handle
}

// Reply sets a response status for this request
// And returns the Response struct to allow chaining the response settings
func (r *Request) Reply(status int) *Response {
	return r.Response.Status(status)
}

func (r *Request) method(method, path string) *Request {
	if path != "/" {
		r.URL.Path = path
	}
	r.Method = strings.ToUpper(method)
	return r
}
<|endoftext|>


<commit_before>package goinsta

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
)

type reqOptions struct {
	Endpoint     string
	PostData     string
	IsLoggedIn   bool
	IgnoreStatus bool
	Query        map[string]string
}

func (insta *Instagram) OptionalRequest(endpoint string, a ...interface{}) (body []byte, err error) {
	return insta.sendRequest(&reqOptions{
		Endpoint: fmt.Sprintf(endpoint, a...),
	})
}

func (insta *Instagram) sendSimpleRequest(endpoint string, a ...interface{}) (body []byte, err error) {
	return insta.sendRequest(&reqOptions{
		Endpoint: fmt.Sprintf(endpoint, a...),
	})
}

func (insta *Instagram) sendRequest(o *reqOptions) (body []byte, err error) {
	if !insta.isLoggedIn && !o.IsLoggedIn {
		return nil, ErrLoggedOut
	}

	method := "GET"
	if len(o.PostData) > 0 {
		method = "POST"
	}

	u, err := url.Parse(GOINSTA_API_URL + o.Endpoint)
	if err != nil {
		return nil, err
	}

	q := u.Query()
	for k, v := range o.Query {
		q.Add(k, v)
	}
	u.RawQuery = q.Encode()

	var req *http.Request
	req, err = http.NewRequest(method, u.String(), bytes.NewBuffer([]byte(o.PostData)))
	if err != nil {
		return
	}

	req.Header.Set("Connection", "close")
	req.Header.Set("Accept", "*/*")
	req.Header.Set("Content-type", "application/x-www-form-urlencoded; charset=UTF-8")
	req.Header.Set("Cookie2", "$Version=1")
	req.Header.Set("Accept-Language", "en-US")
	req.Header.Set("User-Agent", GOINSTA_USER_AGENT)

	client := &http.Client{
		Jar: insta.cookiejar,
	}

	if insta.proxy != "" {
		proxy, err := url.Parse(insta.proxy)
		if err != nil {
			return body, err
		}
		insta.transport.Proxy = http.ProxyURL(proxy)

		client.Transport = &insta.transport
	} else {
		// Remove proxy if insta.Proxy was removed
		insta.transport.Proxy = nil
		client.Transport = &insta.transport
	}

	resp, err := client.Do(req)
	if err != nil {
		return body, err
	}
	defer resp.Body.Close()

	u, _ = url.Parse(GOINSTA_API_URL)
	for _, value := range insta.cookiejar.Cookies(u) {
		if strings.Contains(value.Name, "csrftoken") {
			insta.token = value.Value
		}
	}

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

	if resp.StatusCode != 200 && !o.IgnoreStatus {
		e := fmt.Errorf("Invalid status code %s", string(body))
		switch resp.StatusCode {
		case 400:
			e = ErrLoggedOut
		case 404:
			e = ErrNotFound
		}
		return nil, e
	}

	return body, err
}

func (insta *Instagram) prepareData(otherData ...map[string]interface{}) (string, error) {
	data := map[string]interface{}{
		"_uuid":      insta.uuid,
		"_uid":       insta.CurrentUser.ID,
		"_csrftoken": insta.token,
	}
	if len(otherData) > 0 {
		for i := range otherData {
			for key, value := range otherData[i] {
				data[key] = value
			}
		}
	}
	bytes, err := json.Marshal(data)
	return string(bytes), err
}
<commit_msg>Changed request fields<commit_after>package goinsta

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
)

type reqOptions struct {
	// Endpoint is the request path of instagram api
	Endpoint string

	// IsPost setted to true will send request with POST method.
	//
	// By default this option is false.
	IsPost bool

	// Query is the parameters of the request
	//
	// This parameters are independents of the request method (POST|GET)
	Query map[string]string
}

func (insta *Instagram) sendSimpleRequest(uri string, a ...interface{}) (body []byte, err error) {
	return insta.sendRequest(&reqOptions{
		Endpoint: fmt.Sprintf(uri, a...),
	})
}

func (inst *Instagram) sendRequest(o *reqOptions) (body []byte, err error) {
	if !inst.isLoggedIn && !o.IsLoggedIn {
		return nil, ErrLoggedOut
	}

	method := "GET"
	if o.IsPost {
		method = "POST"
	}

	u, err := url.Parse(goInstaAPIUrl + o.Endpoint)
	if err != nil {
		return nil, err
	}

	bf := bytes.NewBuffer([]byte{})

	q := u.Query()
	for k, v := range o.Query {
		q.Add(k, v)
	}

	if o.IsPost {
		bf.WriteString(q.Encode())
	} else {
		u.RawQuery = q.Encode()
	}

	var req *http.Request
	req, err = http.NewRequest(method, u.String(), bf)
	if err != nil {
		return
	}

	req.Header.Set("Connection", "close")
	req.Header.Set("Accept", "*/*")
	req.Header.Set("Content-type", "application/x-www-form-urlencoded; charset=UTF-8")
	req.Header.Set("Cookie2", "$Version=1")
	req.Header.Set("Accept-Language", "en-US")
	req.Header.Set("User-Agent", goInstaUserAgent)

	resp, err := client.Do(req)
	if err != nil {
		return body, err
	}
	defer resp.Body.Close()

	u, _ = url.Parse(goInstaAPIUrl)
	for _, value := range inst.c.Jar.Cookies(u) {
		if strings.Contains(value.Name, "csrftoken") {
			inst.token = value.Value
		}
	}

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

	if resp.StatusCode != 200 {
		e := fmt.Errorf("Invalid status code %s", string(body))
		switch resp.StatusCode {
		case 400:
			e = ErrLoggedOut
		case 404:
			e = ErrNotFound
		}
		return nil, e
	}

	return body, err
}

func (insta *Instagram) prepareData(otherData ...map[string]interface{}) (string, error) {
	data := map[string]interface{}{
		"_uuid":      insta.uuid,
		"_uid":       insta.CurrentUser.ID,
		"_csrftoken": insta.token,
	}
	if len(otherData) > 0 {
		for i := range otherData {
			for key, value := range otherData[i] {
				data[key] = value
			}
		}
	}
	bytes, err := json.Marshal(data)
	return string(bytes), err
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/franela/goreq"
)

func init() {
	goreq.SetConnectTimeout(5 * time.Second)
	goreq.DefaultTransport = &http.Transport{
		Dial:            goreq.DefaultDialer.Dial,
		Proxy:           http.ProxyFromEnvironment,
		TLSClientConfig: &tls.Config{RootCAs: getCACerts()},
	}
	goreq.DefaultClient.Transport = goreq.DefaultTransport
}

func apiRequest(authToken string) *goreq.Request {
	req := goreq.Request{
		Uri:       "https://" + apiHost(),
		Accept:    "application/vnd.heroku+json; version=3",
		ShowDebug: debugging,
		Insecure:  !shouldVerifyHost(apiHost()),
	}
	if authToken != "" {
		req.AddHeader("Authorization", "Bearer "+authToken)
	}
	if os.Getenv("HEROKU_HEADERS") != "" {
		var h map[string]string
		json.Unmarshal([]byte(os.Getenv("HEROKU_HEADERS")), &h)
		for k, v := range h {
			req.AddHeader(k, v)
		}
	}
	return &req
}

func shouldVerifyHost(host string) bool {
	return !strings.HasSuffix(host, "herokudev.com")
}

func getCACerts() *x509.CertPool {
	certs := x509.NewCertPool()
	path := filepath.Join(AppDir, "cacert.pem")
	data, err := ioutil.ReadFile(path)
	if err != nil {
		downloadCert(path)
		data, err = ioutil.ReadFile(path)
		if err != nil {
			PrintError(err)
			return nil
		}
	}
	ok := certs.AppendCertsFromPEM(data)
	if !ok {
		Warn("Error parsing " + path)
		return nil
	}
	return certs
}

func downloadCert(path string) {
	f, err := os.Create(path)
	if err != nil {
		PrintError(err)
		return
	}
	res, err := goreq.Request{
		Uri:       "https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt",
		ShowDebug: debugging,
	}.Do()
	if err != nil {
		PrintError(err)
		return
	}
	defer res.Body.Close()
	defer f.Close()
	io.Copy(f, res.Body)
}
<commit_msg>goreq init refactor<commit_after>package main

import (
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/franela/goreq"
)

func init() {
	goreq.SetConnectTimeout(5 * time.Second)
	goreq.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{RootCAs: getCACerts()}
}

func apiRequest(authToken string) *goreq.Request {
	req := goreq.Request{
		Uri:       "https://" + apiHost(),
		Accept:    "application/vnd.heroku+json; version=3",
		ShowDebug: debugging,
		Insecure:  !shouldVerifyHost(apiHost()),
	}
	if authToken != "" {
		req.AddHeader("Authorization", "Bearer "+authToken)
	}
	if os.Getenv("HEROKU_HEADERS") != "" {
		var h map[string]string
		json.Unmarshal([]byte(os.Getenv("HEROKU_HEADERS")), &h)
		for k, v := range h {
			req.AddHeader(k, v)
		}
	}
	return &req
}

func shouldVerifyHost(host string) bool {
	return !strings.HasSuffix(host, "herokudev.com")
}

func getCACerts() *x509.CertPool {
	certs := x509.NewCertPool()
	path := filepath.Join(AppDir, "cacert.pem")
	data, err := ioutil.ReadFile(path)
	if err != nil {
		downloadCert(path)
		data, err = ioutil.ReadFile(path)
		if err != nil {
			PrintError(err)
			return nil
		}
	}
	ok := certs.AppendCertsFromPEM(data)
	if !ok {
		Warn("Error parsing " + path)
		return nil
	}
	return certs
}

func downloadCert(path string) {
	f, err := os.Create(path)
	if err != nil {
		PrintError(err)
		return
	}
	res, err := goreq.Request{
		Uri:       "https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt",
		ShowDebug: debugging,
	}.Do()
	if err != nil {
		PrintError(err)
		return
	}
	defer res.Body.Close()
	defer f.Close()
	io.Copy(f, res.Body)
}
<|endoftext|>


<commit_before>package play

import (
	"errors"
	"reflect"
)

type Result interface {
	Apply(req *Request, resp *Response)
}

// Action methods return this result to request a template be rendered.
type RenderTemplateResult struct {
	Template   Template
	RenderArgs map[string]interface{}
	Response   *Response
}

func (r *RenderTemplateResult) Apply(req *Request, resp *Response) {
	// TODO: Put the session, request, flash, params, errors into context.

	// Render the template into the response buffer.
	err := r.Template.Render(resp.out, r.RenderArgs)
	if err != nil {
		line, description := parseTemplateError(err)
		compileError := CompileError{
			Title:       "Template Execution Error",
			Path:        r.Template.Name(),
			Description: description,
			Line:        line,
			SourceLines: r.Template.Content(),
			SourceType:  "template",
		}
		resp.out.Write([]byte(compileError.Html()))
	}
}

type RedirectResult struct {
	val interface{}
}

func (r *RedirectResult) Apply(req *Request, resp *Response) {
	url, err := getRedirectUrl(r.val)
	if err != nil {
		LOG.Println("Couldn't resolve redirect:", err.Error())
		resp.out.WriteHeader(500)
		return
	}
	resp.Headers.Set("Location", url)
	resp.out.WriteHeader(302)
}

func getRedirectUrl(item interface{}) (string, error) {
	// Handle strings
	if url, ok := item.(string); ok {
		return url, nil
	}

	// Handle funcs
	val := reflect.ValueOf(item)
	typ := reflect.TypeOf(item)
	if typ.Kind() == reflect.Func && typ.NumIn() > 0 {
		// Get the Controller Method
		recvType := typ.In(0)
		method := FindMethod(recvType, &val)
		if method == nil {
			return "", errors.New("couldn't find method")
		}

		// Construct the action string (e.g. "Controller.Method")
		if recvType.Kind() == reflect.Ptr {
			recvType = recvType.Elem()
		}
		action := recvType.Name() + "." + method.Name
		actionDef := router.Reverse(action, make(map[string]string))
		if actionDef == nil {
			return "", errors.New("no route for action " + action)
		}

		return actionDef.String(), nil
	}

	// Out of guesses
	return "", errors.New("didn't recognize type: " + typ.String())
}
<commit_msg>Write rendered template into a temporary buffer to avoid corrupting HTML on error pages.<commit_after>package play

import (
	"bytes"
	"errors"
	"reflect"
)

type Result interface {
	Apply(req *Request, resp *Response)
}

// Action methods return this result to request a template be rendered.
type RenderTemplateResult struct {
	Template   Template
	RenderArgs map[string]interface{}
	Response   *Response
}

func (r *RenderTemplateResult) Apply(req *Request, resp *Response) {
	// Render the template into a temporary buffer, to see if there was an error
	// rendering the template.  If not, then copy it into the response buffer.
	// TODO: It seems a shame to make a copy of everything, but if we don't,
	// template errors result in unpredictable HTML for error pages.
	var b bytes.Buffer
	err := r.Template.Render(&b, r.RenderArgs)
	if err != nil {
		line, description := parseTemplateError(err)
		compileError := CompileError{
			Title:       "Template Execution Error",
			Path:        r.Template.Name(),
			Description: description,
			Line:        line,
			SourceLines: r.Template.Content(),
			SourceType:  "template",
		}
		resp.out.Write([]byte(compileError.Html()))
		return
	}

	b.WriteTo(resp.out)
}
}

type RedirectResult struct {
	val interface{}
}

func (r *RedirectResult) Apply(req *Request, resp *Response) {
	url, err := getRedirectUrl(r.val)
	if err != nil {
		LOG.Println("Couldn't resolve redirect:", err.Error())
		resp.out.WriteHeader(500)
		return
	}
	resp.Headers.Set("Location", url)
	resp.out.WriteHeader(302)
}

func getRedirectUrl(item interface{}) (string, error) {
	// Handle strings
	if url, ok := item.(string); ok {
		return url, nil
	}

	// Handle funcs
	val := reflect.ValueOf(item)
	typ := reflect.TypeOf(item)
	if typ.Kind() == reflect.Func && typ.NumIn() > 0 {
		// Get the Controller Method
		recvType := typ.In(0)
		method := FindMethod(recvType, &val)
		if method == nil {
			return "", errors.New("couldn't find method")
		}

		// Construct the action string (e.g. "Controller.Method")
		if recvType.Kind() == reflect.Ptr {
			recvType = recvType.Elem()
		}
		action := recvType.Name() + "." + method.Name
		actionDef := router.Reverse(action, make(map[string]string))
		if actionDef == nil {
			return "", errors.New("no route for action " + action)
		}

		return actionDef.String(), nil
	}

	// Out of guesses
	return "", errors.New("didn't recognize type: " + typ.String())
}
<|endoftext|>


<commit_before>package application

import (
	"os"
	"os/signal"
	"syscall"
	"strconv"
	"fmt"
	"runtime"
	"time"
	"gowork/net"
	e "gowork/error"
	"gowork/extern/logging"
)

var (
	shutdown = make(chan os.Signal)
	running  = make(chan bool)
	hup      = make(chan os.Signal)
	dumpPrefix = "panic"
	dumpMode   = os.FileMode(0777)
	dumpFlag   = os.O_CREATE | os.O_WRONLY
)

func reviewDumpPanic(file *os.File) *e.WError {
	fileinfo, err := file.Stat()
	if err != nil {
		return e.WrapError(e.ERR_CODE_SYS, err)
	}
	if fileinfo.Size() == 0 {
		file.Close()
		err := os.Remove(file.Name())
		if err != nil {
			return e.WrapError(e.ERR_CODE_IO, err)
		}
	}
	return nil
}

type InitHandlerFunc func() *e.WError
type Handler net.Handler

type Application struct {
	appName    string
	baseConfig *configure
	health Handler
	handler Handler
	initHandler InitHandlerFunc
	config  map[string]interface{}
	handlerMap map[string]net.HandlerFunc
}

func NewApplication(name string, conf map[string]interface{}) *Application {
	app := &Application {
		appName: name,
		baseConfig: new(configure),
		health: nil,
		handler: nil,
		config: conf,
		handlerMap: make(map[string]net.HandlerFunc),
	}

	return app
}

func (app *Application) signal() {
	signal.Notify(shutdown, syscall.SIGINT)
	signal.Notify(shutdown, syscall.SIGTERM)
	signal.Notify(hup, syscall.SIGHUP)
	go func() {
		for {
			select {
				case <-shutdown:
					logging.Info("[Application.signal] receive signal SIGINT or SIGTERM, to stop server...")
					running <- false
				case <-hup:
			}
		}
	}()
	logging.Info("[Application.signal] register signal ok")
}

func (app *Application) dump() (*os.File, *e.WError) {
	suffix := fmt.Sprintf("-dump-%s", app.appName)
	filename := dumpPrefix + suffix + "." + strconv.Itoa(os.Getpid())
	file, err := os.OpenFile(filename, dumpFlag, dumpMode)
	if err != nil {
		return file, e.WrapError(e.ERR_CODE_SYS, err)
	}

	if err := syscall.Dup2(int(file.Fd()), int(os.Stderr.Fd())); err != nil {
		return file, e.WrapError(e.ERR_CODE_SYS, err)
	}
	return file, nil
}

func (app *Application) initBaseModule() *e.WError {
	err := initLogger(app.appName,
					  app.baseConfig.Log.Level,
					  app.baseConfig.Log.Suffix,
					  app.baseConfig.Prog.Deamon )
	if err != nil {
		logging.Error("[Application.initBaseModule] Initialize logger moduler failed, error = %s", err.Error())
		return err
	}

	initCPU(app.baseConfig.Prog.CPU)

	return nil
}

func (app *Application) register() {
	for k, v := range app.handlerMap {
		net.HandleFunc(k, v)
		logging.Info("[Application.register] register handler[addr: %s]", k)
	}
}

func (app *Application) run() *e.WError {
	// service
	if app.baseConfig.Server.PortInfo == "" {
		return e.NewWError(e.ERR_CODE_PARA, "Invalid Serve port for application, port: %#+v", app.baseConfig.Server.PortInfo)
	}

	net.Serve(app.baseConfig.Server.PortInfo, app.handler)

	// health
	if app.baseConfig.Prog.HealthPort != "" && app.health != nil {
		net.Health(app.baseConfig.Prog.HealthPort, app.health)
	}

	return nil
}

func (app *Application) SetHealthHandler(handler Handler) {
	app.health = handler
}

func (app *Application) SetServeHandler(handler Handler) {
	app.handler = handler
}

func (app *Application) SetInitHandler(handler InitHandlerFunc) {
	app.initHandler = handler
}

func (app *Application) Set(key string, value interface{}) {
	if v, ok := app.config[key]; ok {
		logging.Warning("[Application.Set] Try to replace value[%#+v] to key = %s, original value: %s", value, key, v)
		//return e.NewWError(e.ERR_CODE_PARA, "Failed to insert value[%#+v] into config, key = %s", value, key)
	}

	app.config[key] = value
	logging.Info("[Application.Set] Add/Replace [key: %s, value: %#+v] into config ok", key, value)
}

func (app *Application) Get(key string) interface{} {
	if v, ok := app.config[key]; ok {
		return v
	}

	logging.Error("[Application.Get] Failed to get value of key[%s], value is NULL", key)
	return nil
}

func (app *Application) AddHandlerFunc(addr string, handler net.HandlerFunc) {
	_, ok := app.handlerMap[addr]
	if ok {
		logging.Warning("[Application.AddHandlerFunc] Try to replace handler to addr = %s", addr)
	}

	app.handlerMap[addr] = handler
	logging.Info("[Application.AddHandlerFunc] Add/Replace [addr: %s] ok", addr)
}

func (app *Application) Go() *e.WError {
	logging.Info("[Application] start")
	// register signal
	app.signal()

	// dump when error occurs
	file, err := app.dump()
	if err != nil {
		logging.Error("[Application.Go] Error occurs when initialize dump panic file, error = %s", err.Error())
	}

	// output exit info
	defer func() {
		logging.Info("[Application.Go] server stop...code: %d", runtime.NumGoroutine())
		time.Sleep(time.Second)
		logging.Info("[Application.Go] server stop...ok")
		if err := reviewDumpPanic(file); err != nil {
			logging.Error("[Application.Go] Failed to review dump panic file, error = %s", err.Error())
		}
	}()

	// parse config file content
	err = initConfigure(app.baseConfig)
	if err != nil {
		logging.Error("[Application.Go] Cannot parse config file, error = %s", err.Error())
		return err
	}

	// init logger
	err = app.initBaseModule()
	if err != nil {
		logging.Error("[Application.Go] Cannot init logger module, error = %s", err.Error())
		return err
	}

	if app.initHandler != nil {
		err := app.initHandler()
		if err != nil {
			logging.Error("[Application.Go] Error occurs when initialize application, error = %s", err.Error())
			return err
		}
	}

	// register handler
	app.register()
	// run
	err = app.run();
	if err != nil {
		logging.Error("[Application.Go] Error in running application, error = %s", err.Error())
		return err
	}

	<- running
	return err
}<commit_msg>export Application instance<commit_after>package application

import (
	"os"
	"os/signal"
	"syscall"
	"strconv"
	"fmt"
	"runtime"
	"time"
	"gowork/net"
	e "gowork/error"
	"gowork/extern/logging"
)

var (
	shutdown = make(chan os.Signal)
	running  = make(chan bool)
	hup      = make(chan os.Signal)
	dumpPrefix = "panic"
	dumpMode   = os.FileMode(0777)
	dumpFlag   = os.O_CREATE | os.O_WRONLY
)

func reviewDumpPanic(file *os.File) *e.WError {
	fileinfo, err := file.Stat()
	if err != nil {
		return e.WrapError(e.ERR_CODE_SYS, err)
	}
	if fileinfo.Size() == 0 {
		file.Close()
		err := os.Remove(file.Name())
		if err != nil {
			return e.WrapError(e.ERR_CODE_IO, err)
		}
	}
	return nil
}

type InitHandlerFunc func() *e.WError
type Handler net.Handler

var (
	App *Application
)

type Application struct {
	appName    string
	baseConfig *configure
	health Handler
	handler Handler
	initHandler InitHandlerFunc
	config  map[string]interface{}
	handlerMap map[string]net.HandlerFunc
}

func NewApplication(name string, conf map[string]interface{}) *Application {
	App := &Application {
		appName: name,
		baseConfig: new(configure),
		health: nil,
		handler: nil,
		initHandler: nil,
		config: conf,
		handlerMap: make(map[string]net.HandlerFunc),
	}

	return App
}

func (app *Application) signal() {
	signal.Notify(shutdown, syscall.SIGINT)
	signal.Notify(shutdown, syscall.SIGTERM)
	signal.Notify(hup, syscall.SIGHUP)
	go func() {
		for {
			select {
				case <-shutdown:
					logging.Info("[Application.signal] receive signal SIGINT or SIGTERM, to stop server...")
					running <- false
				case <-hup:
			}
		}
	}()
	logging.Info("[Application.signal] register signal ok")
}

func (app *Application) dump() (*os.File, *e.WError) {
	suffix := fmt.Sprintf("-dump-%s", app.appName)
	filename := dumpPrefix + suffix + "." + strconv.Itoa(os.Getpid())
	file, err := os.OpenFile(filename, dumpFlag, dumpMode)
	if err != nil {
		return file, e.WrapError(e.ERR_CODE_SYS, err)
	}

	if err := syscall.Dup2(int(file.Fd()), int(os.Stderr.Fd())); err != nil {
		return file, e.WrapError(e.ERR_CODE_SYS, err)
	}
	return file, nil
}

func (app *Application) initBaseModule() *e.WError {
	err := initLogger(app.appName,
					  app.baseConfig.Log.Level,
					  app.baseConfig.Log.Suffix,
					  app.baseConfig.Prog.Deamon )
	if err != nil {
		logging.Error("[Application.initBaseModule] Initialize logger moduler failed, error = %s", err.Error())
		return err
	}

	initCPU(app.baseConfig.Prog.CPU)

	return nil
}

func (app *Application) register() {
	for k, v := range app.handlerMap {
		net.HandleFunc(k, v)
		logging.Info("[Application.register] register handler[addr: %s]", k)
	}
}

func (app *Application) run() *e.WError {
	// service
	if app.baseConfig.Server.PortInfo == "" {
		return e.NewWError(e.ERR_CODE_PARA, "Invalid Serve port for application, port: %#+v", app.baseConfig.Server.PortInfo)
	}

	net.Serve(app.baseConfig.Server.PortInfo, app.handler)

	// health
	if app.baseConfig.Prog.HealthPort != "" && app.health != nil {
		net.Health(app.baseConfig.Prog.HealthPort, app.health)
	}

	return nil
}

func (app *Application) SetHealthHandler(handler Handler) {
	app.health = handler
}

func (app *Application) SetServeHandler(handler Handler) {
	app.handler = handler
}

func (app *Application) SetInitHandler(handler InitHandlerFunc) {
	app.initHandler = handler
}

func (app *Application) Set(key string, value interface{}) {
	if v, ok := app.config[key]; ok {
		logging.Warning("[Application.Set] Try to replace value[%#+v] to key = %s, original value: %s", value, key, v)
		//return e.NewWError(e.ERR_CODE_PARA, "Failed to insert value[%#+v] into config, key = %s", value, key)
	}

	app.config[key] = value
	logging.Info("[Application.Set] Add/Replace [key: %s, value: %#+v] into config ok", key, value)
}

func (app *Application) Get(key string) interface{} {
	if v, ok := app.config[key]; ok {
		return v
	}

	logging.Error("[Application.Get] Failed to get value of key[%s], value is NULL", key)
	return nil
}

func (app *Application) AddHandlerFunc(addr string, handler net.HandlerFunc) {
	_, ok := app.handlerMap[addr]
	if ok {
		logging.Warning("[Application.AddHandlerFunc] Try to replace handler to addr = %s", addr)
	}

	app.handlerMap[addr] = handler
	logging.Info("[Application.AddHandlerFunc] Add/Replace [addr: %s] ok", addr)
}

func (app *Application) Go() *e.WError {
	logging.Info("[Application] start")
	// register signal
	app.signal()

	// dump when error occurs
	file, err := app.dump()
	if err != nil {
		logging.Error("[Application.Go] Error occurs when initialize dump panic file, error = %s", err.Error())
	}

	// output exit info
	defer func() {
		logging.Info("[Application.Go] server stop...code: %d", runtime.NumGoroutine())
		time.Sleep(time.Second)
		logging.Info("[Application.Go] server stop...ok")
		if err := reviewDumpPanic(file); err != nil {
			logging.Error("[Application.Go] Failed to review dump panic file, error = %s", err.Error())
		}
	}()

	// parse config file content
	err = initConfigure(app.baseConfig)
	if err != nil {
		logging.Error("[Application.Go] Cannot parse config file, error = %s", err.Error())
		return err
	}

	// init logger
	err = app.initBaseModule()
	if err != nil {
		logging.Error("[Application.Go] Cannot init logger module, error = %s", err.Error())
		return err
	}

	if app.initHandler != nil {
		err := app.initHandler()
		if err != nil {
			logging.Error("[Application.Go] Error occurs when initialize application, error = %s", err.Error())
			return err
		}
	}

	// register handler
	app.register()
	// run
	err = app.run();
	if err != nil {
		logging.Error("[Application.Go] Error in running application, error = %s", err.Error())
		return err
	}

	<- running
	return err
}<|endoftext|>


<commit_before>package main

import (
	"crypto/md5"
	"crypto/sha1"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	libgeo "github.com/nranchev/go-libGeoIP"
	"golang.org/x/crypto/bcrypt"
)

var (
	errEmptyDurationString   = errors.New("Empty Duration string")
	errInvalidDurationString = errors.New("Invalid Duration string")
	durationRegexp           = regexp.MustCompile(`^((\d+)\s?ye?a?r?s?)?\s?((\d+)\s?mon?t?h?s?)?\s?((\d+)\s?we?e?k?s?)?\s?((\d+)\s?da?y?s?)?\s?((\d+)\s?ho?u?r?s?)?\s?((\d+)\s?mi?n?u?t?e?s?)?\s?((\d+)\s?s?e?c?o?n?d?s?)?$`)
)

func arrToString(arr []string) string {
	var out string
	for i, val := range arr {
		out += val
		if i < len(arr)-1 {
			out += ","
		}
	}
	return out
}

func md5Sum(str string) string {
	hash := md5.New()
	io.WriteString(hash, str)
	return fmt.Sprintf("%x", hash.Sum(nil))
}

func sha1Sum(str string) string {
	hash := sha1.New()
	io.WriteString(hash, str)
	return fmt.Sprintf("%x", hash.Sum(nil))
}

func bcryptSum(str string) string {
	digest, err := bcrypt.GenerateFromPassword([]byte(str), 4)
	if err == nil {
		return string(digest)
	}
	return ""
}

func byteByByteReplace(input, from, to string) string {
	if len(from) != len(to) {
		return ""
	}
	for i := 0; i < len(from); i++ {
		input = strings.Replace(input, from[i:i+1], to[i:i+1], -1)
	}
	return input
}

func closeHandle(handle io.Closer) {
	if handle != nil {
		handle.Close()
	}
}

/*
 * Deletes files in a folder (root) that match a given regular expression.
 * Returns the number of files that were deleted, and any error encountered.
 */
func deleteMatchingFiles(root, match string) (filesDeleted int, err error) {
	files, err := ioutil.ReadDir(root)
	if err != nil {
		return 0, err
	}
	for _, f := range files {
		match, _ := regexp.MatchString(match, f.Name())
		if match {
			os.Remove(filepath.Join(root, f.Name()))
			filesDeleted++
		}
	}
	return filesDeleted, err
}

// getBoardArr performs a query against the database, and returns an array of Boards along with an error value.
// If specified, the string where is added to the query, prefaced by WHERE. An example valid value is where = "id = 1".
func getBoardArr(parameterList map[string]interface{}, extra string) (boards []Board, err error) {
	queryString := "SELECT * FROM DBPREFIXboards "
	numKeys := len(parameterList)
	var parameterValues []interface{}
	if numKeys > 0 {
		queryString += "WHERE "
	}

	for key, value := range parameterList {
		queryString += fmt.Sprintf("%s = ? AND ", key)
		parameterValues = append(parameterValues, value)
	}

	// Find and remove any trailing instances of "AND "
	if numKeys > 0 {
		queryString = queryString[:len(queryString)-4]
	}

	queryString += fmt.Sprintf(" %s ORDER BY list_order", extra)

	rows, err := querySQL(queryString, parameterValues...)
	defer closeHandle(rows)
	if err != nil {
		return
	}

	// For each row in the results from the database, populate a new Board instance,
	// 	then append it to the boards array we are going to return
	for rows.Next() {
		board := new(Board)
		if err = rows.Scan(
			&board.ID,
			&board.ListOrder,
			&board.Dir,
			&board.Type,
			&board.UploadType,
			&board.Title,
			&board.Subtitle,
			&board.Description,
			&board.Section,
			&board.MaxFilesize,
			&board.MaxPages,
			&board.DefaultStyle,
			&board.Locked,
			&board.CreatedOn,
			&board.Anonymous,
			&board.ForcedAnon,
			&board.MaxAge,
			&board.AutosageAfter,
			&board.NoImagesAfter,
			&board.MaxMessageLength,
			&board.EmbedsAllowed,
			&board.RedirectToThread,
			&board.RequireFile,
			&board.EnableCatalog,
		); err != nil {
			return
		}
		boards = append(boards, *board)
	}
	return
}

/* func getBoardFromID(id int) (*Board, error) {
	board := new(Board)
	err := queryRowSQL("SELECT list_order,dir,type,upload_type,title,subtitle,description,section,"+
		"max_file_size,max_pages,default_style,locked,created_on,anonymous,forced_anon,max_age,"+
		"autosage_after,no_images_after,max_message_length,embeds_allowed,redirect_to_thread,require_file,"+
		"enable_catalog FROM DBPREFIXboards WHERE id = ?",
		[]interface{}{id},
		[]interface{}{
			&board.ListOrder, &board.Dir, &board.Type, &board.UploadType, &board.Title,
			&board.Subtitle, &board.Description, &board.Section, &board.MaxFilesize,
			&board.MaxPages, &board.DefaultStyle, &board.Locked, &board.CreatedOn,
			&board.Anonymous, &board.ForcedAnon, &board.MaxAge, &board.AutosageAfter,
			&board.NoImagesAfter, &board.MaxMessageLength, &board.EmbedsAllowed,
			&board.RedirectToThread, &board.RequireFile, &board.EnableCatalog,
		},
	)
	board.ID = id
	return board, err
} */

// if parameterList is nil, ignore it and treat extra like a whole SQL query
func getPostArr(parameterList map[string]interface{}, extra string) (posts []Post, err error) {
	queryString := "SELECT * FROM DBPREFIXposts "
	numKeys := len(parameterList)
	var parameterValues []interface{}
	if numKeys > 0 {
		queryString += "WHERE "
	}

	for key, value := range parameterList {
		queryString += fmt.Sprintf("%s = ? AND ", key)
		parameterValues = append(parameterValues, value)
	}

	// Find and remove any trailing instances of "AND "
	if numKeys > 0 {
		queryString = queryString[:len(queryString)-4]
	}

	queryString += " " + extra
	rows, err := querySQL(queryString, parameterValues...)
	defer closeHandle(rows)
	if err != nil {
		return
	}

	// For each row in the results from the database, populate a new Post instance,
	// then append it to the posts array we are going to return
	for rows.Next() {
		var post Post

		if err = rows.Scan(&post.ID, &post.BoardID, &post.ParentID, &post.Name, &post.Tripcode,
			&post.Email, &post.Subject, &post.MessageHTML, &post.MessageText, &post.Password, &post.Filename,
			&post.FilenameOriginal, &post.FileChecksum, &post.Filesize, &post.ImageW,
			&post.ImageH, &post.ThumbW, &post.ThumbH, &post.IP, &post.Capcode, &post.Timestamp,
			&post.Autosage, &post.DeletedTimestamp, &post.Bumped, &post.Stickied, &post.Locked, &post.Reviewed,
		); err != nil {
			return
		}
		posts = append(posts, post)
	}
	return
}

// TODO: replace where with a map[string]interface{} like getBoardsArr()
func getSectionArr(where string) (sections []BoardSection, err error) {
	if where != "" {
		where = "WHERE " + where
	}
	rows, err := querySQL("SELECT * FROM DBPREFIXsections " + where + " ORDER BY list_order")
	defer closeHandle(rows)
	if err != nil {
		gclog.Print(lErrorLog, "Error getting section list: ", err.Error())
		return
	}

	for rows.Next() {
		var section BoardSection
		if err = rows.Scan(&section.ID, &section.ListOrder, &section.Hidden, &section.Name, &section.Abbreviation); err != nil {
			gclog.Print(lErrorLog, "Error getting section list: ", err.Error())
			return
		}
		sections = append(sections, section)
	}
	return
}

func getCountryCode(ip string) (string, error) {
	if config.EnableGeoIP && config.GeoIPDBlocation != "" {
		gi, err := libgeo.Load(config.GeoIPDBlocation)
		if err != nil {
			return "", err
		}
		return gi.GetLocationByIP(ip).CountryCode, nil
	}
	return "", nil
}

func randomString(length int) string {
	var str string
	for i := 0; i < length; i++ {
		num := rand.Intn(127)
		if num < 32 {
			num += 32
		}
		str += string(num)
	}
	return str
}

func getFileExtension(filename string) (extension string) {
	if !strings.Contains(filename, ".") {
		extension = ""
	} else {
		extension = filename[strings.LastIndex(filename, ".")+1:]
	}
	return
}

func getFormattedFilesize(size float64) string {
	if size < 1000 {
		return fmt.Sprintf("%dB", int(size))
	} else if size <= 100000 {
		return fmt.Sprintf("%fKB", size/1024)
	} else if size <= 100000000 {
		return fmt.Sprintf("%fMB", size/1024.0/1024.0)
	}
	return fmt.Sprintf("%0.2fGB", size/1024.0/1024.0/1024.0)
}

func humanReadableTime(t time.Time) string {
	return t.Format(config.DateTimeFormat)
}

func getThumbnailPath(thumbType string, img string) string {
	filetype := strings.ToLower(img[strings.LastIndex(img, ".")+1:])
	if filetype == "gif" || filetype == "webm" {
		filetype = "jpg"
	}
	index := strings.LastIndex(img, ".")
	if index < 0 || index > len(img) {
		return ""
	}
	thumbSuffix := "t." + filetype
	if thumbType == "catalog" {
		thumbSuffix = "c." + filetype
	}
	return img[0:index] + thumbSuffix
}

// findResource searches for a file in the given paths and returns the first one it finds
// or a blank string if none of the paths exist
func findResource(paths ...string) string {
	var err error
	for _, filepath := range paths {
		if _, err = os.Stat(filepath); err == nil {
			return filepath
		}
	}
	return ""
}

// paginate returns a 2d array of a specified interface from a 1d array passed in,
// with a specified number of values per array in the 2d array.
// interfaceLength is the number of interfaces per array in the 2d array (e.g, threads per page)
// interf is the array of interfaces to be split up.
func paginate(interfaceLength int, interf []interface{}) [][]interface{} {
	// paginatedInterfaces = the finished interface array
	// numArrays = the current number of arrays (before remainder overflow)
	// interfacesRemaining = if greater than 0, these are the remaining interfaces
	// 	that will be added to the super-interface

	var paginatedInterfaces [][]interface{}
	numArrays := len(interf) / interfaceLength
	interfacesRemaining := len(interf) % interfaceLength
	currentInterface := 0
	for l := 0; l < numArrays; l++ {
		paginatedInterfaces = append(paginatedInterfaces,
			interf[currentInterface:currentInterface+interfaceLength])
		currentInterface += interfaceLength
	}
	if interfacesRemaining > 0 {
		paginatedInterfaces = append(paginatedInterfaces, interf[len(interf)-interfacesRemaining:])
	}
	return paginatedInterfaces
}

func resetBoardSectionArrays() {
	// run when the board list needs to be changed (board/section is added, deleted, etc)
	allBoards = nil
	allSections = nil

	allBoardsArr, _ := getBoardArr(nil, "")
	allBoards = append(allBoards, allBoardsArr...)

	allSectionsArr, _ := getSectionArr("")
	allSections = append(allSections, allSectionsArr...)
}

func searchStrings(item string, arr []string, permissive bool) int {
	for i, str := range arr {
		if item == str {
			return i
		}
	}
	return -1
}

// Checks the validity of the Akismet API key given in the config file.
func checkAkismetAPIKey(key string) error {
	if key == "" {
		return errors.New("blank key given, Akismet spam checking won't be used")
	}
	resp, err := http.PostForm("https://rest.akismet.com/1.1/verify-key", url.Values{"key": {key}, "blog": {"http://" + config.SiteDomain}})
	if resp != nil {
		defer closeHandle(resp.Body)
	}
	if err != nil {
		return err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	if string(body) == "invalid" {
		// This should disable the Akismet checks if the API key is not valid.
		errmsg := "Akismet API key is invalid, Akismet spam protection will be disabled."
		gclog.Print(lErrorLog, errmsg)
		return errors.New(errmsg)
	}
	return nil
}

// Checks a given post for spam with Akismet. Only checks if Akismet API key is set.
func checkPostForSpam(userIP string, userAgent string, referrer string,
	author string, email string, postContent string) string {
	if config.AkismetAPIKey != "" {
		client := &http.Client{}
		data := url.Values{"blog": {"http://" + config.SiteDomain}, "user_ip": {userIP}, "user_agent": {userAgent}, "referrer": {referrer},
			"comment_type": {"forum-post"}, "comment_author": {author}, "comment_author_email": {email},
			"comment_content": {postContent}}

		req, err := http.NewRequest("POST", "https://"+config.AkismetAPIKey+".rest.akismet.com/1.1/comment-check",
			strings.NewReader(data.Encode()))
		if err != nil {
			gclog.Print(lErrorLog, err.Error())
			return "other_failure"
		}
		req.Header.Set("User-Agent", "gochan/1.0 | Akismet/0.1")
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
		resp, err := client.Do(req)
		if resp != nil {
			defer closeHandle(resp.Body)
		}
		if err != nil {
			gclog.Print(lErrorLog, err.Error())
			return "other_failure"
		}
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			gclog.Print(lErrorLog, err.Error())
			return "other_failure"
		}
		gclog.Print(lErrorLog, "Response from Akismet: ", string(body))

		if string(body) == "true" {
			if proTip, ok := resp.Header["X-akismet-pro-tip"]; ok && proTip[0] == "discard" {
				return "discard"
			}
			return "spam"
		} else if string(body) == "invalid" {
			return "invalid"
		} else if string(body) == "false" {
			return "ham"
		}
	}
	return "other_failure"
}

func marshalJSON(data interface{}, indent bool) (string, error) {
	var jsonBytes []byte
	var err error

	if indent {
		jsonBytes, err = json.MarshalIndent(data, "", "	")
	} else {
		jsonBytes, err = json.Marshal(data)
	}

	if err != nil {
		jsonBytes, _ = json.Marshal(map[string]string{"error": err.Error()})
	}
	return string(jsonBytes), err
}

func limitArraySize(arr []string, maxSize int) []string {
	if maxSize > len(arr)-1 || maxSize < 0 {
		return arr
	}
	return arr[:maxSize]
}

func numReplies(boardid, threadid int) int {
	var num int

	if err := queryRowSQL(
		"SELECT COUNT(*) FROM DBPREFIXposts WHERE boardid = ? AND parentid = ?",
		[]interface{}{boardid, threadid}, []interface{}{&num}); err != nil {
		return 0
	}
	return num
}

// based on TinyBoard's parse_time function
func parseDurationString(str string) (time.Duration, error) {
	if str == "" {
		return 0, errEmptyDurationString
	}

	matches := durationRegexp.FindAllStringSubmatch(str, -1)
	if len(matches) == 0 {
		return 0, errInvalidDurationString
	}

	var expire int
	if matches[0][2] != "" {
		years, _ := strconv.Atoi(matches[0][2])
		expire += years * 60 * 60 * 24 * 365
	}
	if matches[0][4] != "" {
		months, _ := strconv.Atoi(matches[0][4])
		expire += months * 60 * 60 * 24 * 30
	}
	if matches[0][6] != "" {
		weeks, _ := strconv.Atoi(matches[0][6])
		expire += weeks * 60 * 60 * 24 * 7
	}
	if matches[0][8] != "" {
		days, _ := strconv.Atoi(matches[0][8])
		expire += days * 60 * 60 * 24
	}
	if matches[0][10] != "" {
		hours, _ := strconv.Atoi(matches[0][10])
		expire += hours * 60 * 60
	}
	if matches[0][12] != "" {
		minutes, _ := strconv.Atoi(matches[0][12])
		expire += minutes * 60
	}
	if matches[0][14] != "" {
		seconds, _ := strconv.Atoi(matches[0][14])
		expire += seconds
	}
	return time.ParseDuration(strconv.Itoa(expire) + "s")
}

func timezone() int {
	_, offset := time.Now().Zone()
	return offset / 60 / 60
}
<commit_msg>delete leftover src directory<commit_after><|endoftext|>


<commit_before>package rollbar

import (
	"bytes"
	"encoding/json"
	"fmt"
	"hash/adler32"
	"net/http"
	"net/url"
	"os"
	"reflect"
	"regexp"
	"runtime"
	"strings"
	"sync"
	"time"
)

const (
	// NAME is the name of this notifier library as reported to the Rollbar API.
	NAME = "go-rollbar"

	// VERSION is the version number of this notifier library as reported to the
	// Rollbar API.
	VERSION = "0.4.0"

	// CRIT is the critical Rollbar severity level as reported to the Rollbar
	// API.
	CRIT = "critical"

	// ERR is the error Rollbar severity level as reported to the Rollbar API.
	ERR = "error"

	// WARN is the warning Rollbar severity level as reported to the Rollbar API.
	WARN = "warning"

	// INFO is the info Rollbar severity level as reported to the Rollbar API.
	INFO = "info"

	// DEBUG is the debug Rollbar severity level as reported to the Rollbar API.
	DEBUG = "debug"

	// FILTERED is the text that replaces all sensitive values in items sent to
	// the Rollbar API.
	FILTERED = "[FILTERED]"
)

var (
	// Token is the Rollbar access token under which all items will be reported.
	// If Token is blank, no errors will be reported to Rollbar.
	Token = ""

	// Environment is the environment under which all items will be reported.
	Environment = "development"

	// Platform is the platform reported for all Rollbar items. The default is
	// the running operating system (darwin, freebsd, linux, etc.) but it can
	// also be application specific (client, heroku, etc.).
	Platform = runtime.GOOS

	// Endpoint is the URL destination for all Rollbar item POST requests.
	Endpoint = "https://api.rollbar.com/api/1/item/"

	// Buffer is the maximum number of errors that will be queued for sending.
	// When the buffer is full, new errors are dropped on the floor until the API
	// can catch up.
	Buffer = 1000

	// FilterFields is a regular expression that matches field names that should
	// not be sent to Rollbar. Values for these fields are replaced with
	// "[FILTERED]".
	FilterFields = regexp.MustCompile("password|secret|token")

	// ErrorWriter is the destination for errors encountered while POSTing items
	// to Rollbar. By default, this is stderr. This can be nil.
	ErrorWriter = os.Stderr

	// CodeVersion is the optional code version reported to the Rollbar API for
	// all items.
	CodeVersion = ""

	bodyChannel chan map[string]interface{}
	waitGroup   sync.WaitGroup
	postErrors  chan error
	nilErrTitle = "<nil>"
)

// Field is a custom data field used to report arbitrary data to the Rollbar
// API.
type Field struct {
	Name string
	Data interface{}
}

// -- Setup

func init() {
	bodyChannel = make(chan map[string]interface{}, Buffer)
	postErrors = make(chan error, Buffer)

	go func() {
		var err error
		for body := range bodyChannel {
			err = post(body)
			if err != nil {
				if len(postErrors) == cap(postErrors) {
					<-postErrors
				}
				postErrors <- err
			}
			waitGroup.Done()
		}
		close(postErrors)
	}()
}

// -- Error reporting

func Errorf(level string, format string, args ...interface{}) {
	ErrorWithStackSkip(level, fmt.Errorf(format, args), 1)
}

// Error asynchronously sends an error to Rollbar with the given severity
// level. You can pass, optionally, custom Fields to be passed on to Rollbar.
func Error(level string, err error, fields ...*Field) {
	ErrorWithStackSkip(level, err, 1, fields...)
}

// ErrorWithStackSkip asynchronously sends an error to Rollbar with the given
// severity level and a given number of stack trace frames skipped. You can
// pass, optionally, custom Fields to be passed on to Rollbar.
func ErrorWithStackSkip(level string, err error, skip int, fields ...*Field) {
	stack := BuildStack(2 + skip)
	ErrorWithStack(level, err, stack, fields...)
}

// ErrorWithStack asynchronously sends and error to Rollbar with the given
// stacktrace and (optionally) custom Fields to be passed on to Rollbar.
func ErrorWithStack(level string, err error, stack Stack, fields ...*Field) {
	buildAndPushError(level, err, stack, fields...)
}

// RequestError asynchronously sends an error to Rollbar with the given
// severity level and request-specific information. You can pass, optionally,
// custom Fields to be passed on to Rollbar.
func RequestError(level string, r *http.Request, err error, fields ...*Field) {
	RequestErrorWithStackSkip(level, r, err, 1, fields...)
}

// RequestErrorWithStackSkip asynchronously sends an error to Rollbar with the
// given severity level and a given number of stack trace frames skipped, in
// addition to extra request-specific information. You can pass, optionally,
// custom Fields to be passed on to Rollbar.
func RequestErrorWithStackSkip(level string, r *http.Request, err error, skip int, fields ...*Field) {
	stack := BuildStack(2 + skip)
	RequestErrorWithStack(level, r, err, stack, fields...)
}

// RequestErrorWithStack asynchronously sends an error to Rollbar with the
// given severity level, request-specific information provided by the given
// http.Request, and a custom Stack. You You can pass, optionally, custom
// Fields to be passed on to Rollbar.
func RequestErrorWithStack(level string, r *http.Request, err error, stack Stack, fields ...*Field) {
	buildAndPushError(level, err, stack, append(fields, &Field{Name: "request", Data: errorRequest(r)})...)
}

func buildError(level string, err error, stack Stack, fields ...*Field) map[string]interface{} {
	title := nilErrTitle
	if err != nil {
		title = err.Error()
	}

	body := buildBody(level, title)
	data := body["data"].(map[string]interface{})
	errBody, fingerprint := errorBody(err, stack)
	data["body"] = errBody
	data["fingerprint"] = fingerprint

	for _, field := range fields {
		data[field.Name] = field.Data
	}

	return body
}

func buildAndPushError(level string, err error, stack Stack, fields ...*Field) {
	push(buildError(level, err, stack, fields...))
}

// -- Message reporting

// Message asynchronously sends a message to Rollbar with the given severity
// level.
func Message(level string, msg string) {
	body := buildBody(level, msg)
	data := body["data"].(map[string]interface{})
	data["body"] = messageBody(msg)

	push(body)
}

// -- Misc.

// PostErrors returns a channel that receives all errors encountered while
// POSTing items to the Rollbar API.
func PostErrors() <-chan error {
	return postErrors
}

// Wait will block until the queue of errors / messages is empty. This allows
// you to ensure that errors / messages are sent to Rollbar before exiting an
// application.
func Wait() {
	waitGroup.Wait()
}

// Build the main JSON structure that will be sent to Rollbar with the
// appropriate metadata.
func buildBody(level, title string) map[string]interface{} {
	timestamp := time.Now().Unix()
	hostname, _ := os.Hostname()

	data := map[string]interface{}{
		"environment": Environment,
		"title":       title,
		"level":       level,
		"timestamp":   timestamp,
		"platform":    Platform,
		"language":    "go",
		"server": map[string]interface{}{
			"host": hostname,
		},
		"notifier": map[string]interface{}{
			"name":    NAME,
			"version": VERSION,
		},
	}
	if CodeVersion != "" {
		data["code_version"] = CodeVersion
	}

	return map[string]interface{}{
		"access_token": Token,
		"data":         data,
	}
}

// errorBody generates a Rollbar error body with a given stack trace.
func errorBody(err error, stack Stack) (map[string]interface{}, string) {
	message := nilErrTitle
	if err != nil {
		message = err.Error()
	}

	fingerprint := stack.Fingerprint()
	errBody := map[string]interface{}{
		"trace": map[string]interface{}{
			"frames": stack,
			"exception": map[string]interface{}{
				"class":   errorClass(err),
				"message": message,
			},
		},
	}
	return errBody, fingerprint
}

// errorRequest extracts details from a Request in a format that Rollbar
// accepts.
func errorRequest(r *http.Request) map[string]interface{} {
	cleanQuery := filterParams(r.URL.Query())

	return map[string]interface{}{
		"url":     r.URL.String(),
		"method":  r.Method,
		"headers": flattenValues(r.Header),

		// GET params
		"query_string": url.Values(cleanQuery).Encode(),
		"GET":          flattenValues(cleanQuery),

		// POST / PUT params
		"POST":    flattenValues(filterParams(r.Form)),
		"user_ip": r.RemoteAddr,
	}
}

// filterParams filters sensitive information like passwords from being sent to
// Rollbar.
func filterParams(values map[string][]string) map[string][]string {
	for key := range values {
		if FilterFields.Match([]byte(key)) {
			values[key] = []string{FILTERED}
		}
	}

	return values
}

func flattenValues(values map[string][]string) map[string]interface{} {
	result := make(map[string]interface{})

	for k, v := range values {
		if len(v) == 1 {
			result[k] = v[0]
		} else {
			result[k] = v
		}
	}

	return result
}

// Build a message inner-body for the given message string.
func messageBody(s string) map[string]interface{} {
	return map[string]interface{}{
		"message": map[string]interface{}{
			"body": s,
		},
	}
}

func errorClass(err error) string {
	if err == nil {
		return nilErrTitle
	}

	class := reflect.TypeOf(err).String()
	if class == "" {
		return "panic"
	} else if class == "*errors.errorString" {
		checksum := adler32.Checksum([]byte(err.Error()))
		return fmt.Sprintf("{%x}", checksum)
	} else {
		return strings.TrimPrefix(class, "*")
	}
}

// -- POST handling

// Queue the given JSON body to be POSTed to Rollbar.
func push(body map[string]interface{}) {
	if len(bodyChannel) < Buffer {
		waitGroup.Add(1)
		bodyChannel <- body
	} else {
		stderr("buffer full, dropping error on the floor")
	}
}

// POST the given JSON body to Rollbar synchronously.
func post(body map[string]interface{}) error {
	if len(Token) == 0 {
		stderr("empty token")
		return nil
	}

	jsonBody, err := json.Marshal(body)
	if err != nil {
		stderr("failed to encode payload: %s", err.Error())
		return err
	}

	resp, err := http.Post(Endpoint, "application/json", bytes.NewReader(jsonBody))
	if err != nil {
		stderr("POST failed: %s", err.Error())
		return err
	}

	if resp.StatusCode != 200 {
		stderr("received response: %s", resp.Status)
		return ErrHTTPError(resp.StatusCode)
	}

	if resp != nil {
		resp.Body.Close()
	}

	return nil
}

// -- stderr
func stderr(format string, args ...interface{}) {
	if ErrorWriter != nil {
		format = "Rollbar error: " + format + "\n"
		fmt.Fprintf(ErrorWriter, format, args...)
	}
}
<commit_msg>Fix Errorf argument passing to fmt.Errorf<commit_after>package rollbar

import (
	"bytes"
	"encoding/json"
	"fmt"
	"hash/adler32"
	"net/http"
	"net/url"
	"os"
	"reflect"
	"regexp"
	"runtime"
	"strings"
	"sync"
	"time"
)

const (
	// NAME is the name of this notifier library as reported to the Rollbar API.
	NAME = "go-rollbar"

	// VERSION is the version number of this notifier library as reported to the
	// Rollbar API.
	VERSION = "0.4.0"

	// CRIT is the critical Rollbar severity level as reported to the Rollbar
	// API.
	CRIT = "critical"

	// ERR is the error Rollbar severity level as reported to the Rollbar API.
	ERR = "error"

	// WARN is the warning Rollbar severity level as reported to the Rollbar API.
	WARN = "warning"

	// INFO is the info Rollbar severity level as reported to the Rollbar API.
	INFO = "info"

	// DEBUG is the debug Rollbar severity level as reported to the Rollbar API.
	DEBUG = "debug"

	// FILTERED is the text that replaces all sensitive values in items sent to
	// the Rollbar API.
	FILTERED = "[FILTERED]"
)

var (
	// Token is the Rollbar access token under which all items will be reported.
	// If Token is blank, no errors will be reported to Rollbar.
	Token = ""

	// Environment is the environment under which all items will be reported.
	Environment = "development"

	// Platform is the platform reported for all Rollbar items. The default is
	// the running operating system (darwin, freebsd, linux, etc.) but it can
	// also be application specific (client, heroku, etc.).
	Platform = runtime.GOOS

	// Endpoint is the URL destination for all Rollbar item POST requests.
	Endpoint = "https://api.rollbar.com/api/1/item/"

	// Buffer is the maximum number of errors that will be queued for sending.
	// When the buffer is full, new errors are dropped on the floor until the API
	// can catch up.
	Buffer = 1000

	// FilterFields is a regular expression that matches field names that should
	// not be sent to Rollbar. Values for these fields are replaced with
	// "[FILTERED]".
	FilterFields = regexp.MustCompile("password|secret|token")

	// ErrorWriter is the destination for errors encountered while POSTing items
	// to Rollbar. By default, this is stderr. This can be nil.
	ErrorWriter = os.Stderr

	// CodeVersion is the optional code version reported to the Rollbar API for
	// all items.
	CodeVersion = ""

	bodyChannel chan map[string]interface{}
	waitGroup   sync.WaitGroup
	postErrors  chan error
	nilErrTitle = "<nil>"
)

// Field is a custom data field used to report arbitrary data to the Rollbar
// API.
type Field struct {
	Name string
	Data interface{}
}

// -- Setup

func init() {
	bodyChannel = make(chan map[string]interface{}, Buffer)
	postErrors = make(chan error, Buffer)

	go func() {
		var err error
		for body := range bodyChannel {
			err = post(body)
			if err != nil {
				if len(postErrors) == cap(postErrors) {
					<-postErrors
				}
				postErrors <- err
			}
			waitGroup.Done()
		}
		close(postErrors)
	}()
}

// -- Error reporting

func Errorf(level string, format string, args ...interface{}) {
	ErrorWithStackSkip(level, fmt.Errorf(format, args...), 1)
}

// Error asynchronously sends an error to Rollbar with the given severity
// level. You can pass, optionally, custom Fields to be passed on to Rollbar.
func Error(level string, err error, fields ...*Field) {
	ErrorWithStackSkip(level, err, 1, fields...)
}

// ErrorWithStackSkip asynchronously sends an error to Rollbar with the given
// severity level and a given number of stack trace frames skipped. You can
// pass, optionally, custom Fields to be passed on to Rollbar.
func ErrorWithStackSkip(level string, err error, skip int, fields ...*Field) {
	stack := BuildStack(2 + skip)
	ErrorWithStack(level, err, stack, fields...)
}

// ErrorWithStack asynchronously sends and error to Rollbar with the given
// stacktrace and (optionally) custom Fields to be passed on to Rollbar.
func ErrorWithStack(level string, err error, stack Stack, fields ...*Field) {
	buildAndPushError(level, err, stack, fields...)
}

// RequestError asynchronously sends an error to Rollbar with the given
// severity level and request-specific information. You can pass, optionally,
// custom Fields to be passed on to Rollbar.
func RequestError(level string, r *http.Request, err error, fields ...*Field) {
	RequestErrorWithStackSkip(level, r, err, 1, fields...)
}

// RequestErrorWithStackSkip asynchronously sends an error to Rollbar with the
// given severity level and a given number of stack trace frames skipped, in
// addition to extra request-specific information. You can pass, optionally,
// custom Fields to be passed on to Rollbar.
func RequestErrorWithStackSkip(level string, r *http.Request, err error, skip int, fields ...*Field) {
	stack := BuildStack(2 + skip)
	RequestErrorWithStack(level, r, err, stack, fields...)
}

// RequestErrorWithStack asynchronously sends an error to Rollbar with the
// given severity level, request-specific information provided by the given
// http.Request, and a custom Stack. You You can pass, optionally, custom
// Fields to be passed on to Rollbar.
func RequestErrorWithStack(level string, r *http.Request, err error, stack Stack, fields ...*Field) {
	buildAndPushError(level, err, stack, append(fields, &Field{Name: "request", Data: errorRequest(r)})...)
}

func buildError(level string, err error, stack Stack, fields ...*Field) map[string]interface{} {
	title := nilErrTitle
	if err != nil {
		title = err.Error()
	}

	body := buildBody(level, title)
	data := body["data"].(map[string]interface{})
	errBody, fingerprint := errorBody(err, stack)
	data["body"] = errBody
	data["fingerprint"] = fingerprint

	for _, field := range fields {
		data[field.Name] = field.Data
	}

	return body
}

func buildAndPushError(level string, err error, stack Stack, fields ...*Field) {
	push(buildError(level, err, stack, fields...))
}

// -- Message reporting

// Message asynchronously sends a message to Rollbar with the given severity
// level.
func Message(level string, msg string) {
	body := buildBody(level, msg)
	data := body["data"].(map[string]interface{})
	data["body"] = messageBody(msg)

	push(body)
}

// -- Misc.

// PostErrors returns a channel that receives all errors encountered while
// POSTing items to the Rollbar API.
func PostErrors() <-chan error {
	return postErrors
}

// Wait will block until the queue of errors / messages is empty. This allows
// you to ensure that errors / messages are sent to Rollbar before exiting an
// application.
func Wait() {
	waitGroup.Wait()
}

// Build the main JSON structure that will be sent to Rollbar with the
// appropriate metadata.
func buildBody(level, title string) map[string]interface{} {
	timestamp := time.Now().Unix()
	hostname, _ := os.Hostname()

	data := map[string]interface{}{
		"environment": Environment,
		"title":       title,
		"level":       level,
		"timestamp":   timestamp,
		"platform":    Platform,
		"language":    "go",
		"server": map[string]interface{}{
			"host": hostname,
		},
		"notifier": map[string]interface{}{
			"name":    NAME,
			"version": VERSION,
		},
	}
	if CodeVersion != "" {
		data["code_version"] = CodeVersion
	}

	return map[string]interface{}{
		"access_token": Token,
		"data":         data,
	}
}

// errorBody generates a Rollbar error body with a given stack trace.
func errorBody(err error, stack Stack) (map[string]interface{}, string) {
	message := nilErrTitle
	if err != nil {
		message = err.Error()
	}

	fingerprint := stack.Fingerprint()
	errBody := map[string]interface{}{
		"trace": map[string]interface{}{
			"frames": stack,
			"exception": map[string]interface{}{
				"class":   errorClass(err),
				"message": message,
			},
		},
	}
	return errBody, fingerprint
}

// errorRequest extracts details from a Request in a format that Rollbar
// accepts.
func errorRequest(r *http.Request) map[string]interface{} {
	cleanQuery := filterParams(r.URL.Query())

	return map[string]interface{}{
		"url":     r.URL.String(),
		"method":  r.Method,
		"headers": flattenValues(r.Header),

		// GET params
		"query_string": url.Values(cleanQuery).Encode(),
		"GET":          flattenValues(cleanQuery),

		// POST / PUT params
		"POST":    flattenValues(filterParams(r.Form)),
		"user_ip": r.RemoteAddr,
	}
}

// filterParams filters sensitive information like passwords from being sent to
// Rollbar.
func filterParams(values map[string][]string) map[string][]string {
	for key := range values {
		if FilterFields.Match([]byte(key)) {
			values[key] = []string{FILTERED}
		}
	}

	return values
}

func flattenValues(values map[string][]string) map[string]interface{} {
	result := make(map[string]interface{})

	for k, v := range values {
		if len(v) == 1 {
			result[k] = v[0]
		} else {
			result[k] = v
		}
	}

	return result
}

// Build a message inner-body for the given message string.
func messageBody(s string) map[string]interface{} {
	return map[string]interface{}{
		"message": map[string]interface{}{
			"body": s,
		},
	}
}

func errorClass(err error) string {
	if err == nil {
		return nilErrTitle
	}

	class := reflect.TypeOf(err).String()
	if class == "" {
		return "panic"
	} else if class == "*errors.errorString" {
		checksum := adler32.Checksum([]byte(err.Error()))
		return fmt.Sprintf("{%x}", checksum)
	} else {
		return strings.TrimPrefix(class, "*")
	}
}

// -- POST handling

// Queue the given JSON body to be POSTed to Rollbar.
func push(body map[string]interface{}) {
	if len(bodyChannel) < Buffer {
		waitGroup.Add(1)
		bodyChannel <- body
	} else {
		stderr("buffer full, dropping error on the floor")
	}
}

// POST the given JSON body to Rollbar synchronously.
func post(body map[string]interface{}) error {
	if len(Token) == 0 {
		stderr("empty token")
		return nil
	}

	jsonBody, err := json.Marshal(body)
	if err != nil {
		stderr("failed to encode payload: %s", err.Error())
		return err
	}

	resp, err := http.Post(Endpoint, "application/json", bytes.NewReader(jsonBody))
	if err != nil {
		stderr("POST failed: %s", err.Error())
		return err
	}

	if resp.StatusCode != 200 {
		stderr("received response: %s", resp.Status)
		return ErrHTTPError(resp.StatusCode)
	}

	if resp != nil {
		resp.Body.Close()
	}

	return nil
}

// -- stderr
func stderr(format string, args ...interface{}) {
	if ErrorWriter != nil {
		format = "Rollbar error: " + format + "\n"
		fmt.Fprintf(ErrorWriter, format, args...)
	}
}
<|endoftext|>


<commit_before>package rollbar

import (
	"bytes"
	"encoding/json"
	"fmt"
	"hash/adler32"
	"net/http"
	"net/url"
	"os"
	"reflect"
	"regexp"
	"runtime"
	"strings"
	"sync"
	"time"
)

const (
	// NAME is the name of this notifier library as reported to the Rollbar API.
	NAME = "go-rollbar"

	// VERSION is the version number of this notifier library as reported to the
	// Rollbar API.
	VERSION = "0.4.0"

	// CRIT is the critical Rollbar severity level as reported to the Rollbar
	// API.
	CRIT = "critical"

	// ERR is the error Rollbar severity level as reported to the Rollbar API.
	ERR = "error"

	// WARN is the warning Rollbar severity level as reported to the Rollbar API.
	WARN = "warning"

	// INFO is the info Rollbar severity level as reported to the Rollbar API.
	INFO = "info"

	// DEBUG is the debug Rollbar severity level as reported to the Rollbar API.
	DEBUG = "debug"

	// FILTERED is the text that replaces all sensitive values in items sent to
	// the Rollbar API.
	FILTERED = "[FILTERED]"
)

var (
	// Token is the Rollbar access token under which all items will be reported.
	// If Token is blank, no errors will be reported to Rollbar.
	Token = ""

	// Environment is the environment under which all items will be reported.
	Environment = "development"

	// Platform is the platform reported for all Rollbar items. The default is
	// the running operating system (darwin, freebsd, linux, etc.) but it can
	// also be application specific (client, heroku, etc.).
	Platform = runtime.GOOS

	// Endpoint is the URL destination for all Rollbar item POST requests.
	Endpoint = "https://api.rollbar.com/api/1/item/"

	// Buffer is the maximum number of errors that will be queued for sending.
	// When the buffer is full, new errors are dropped on the floor until the API
	// can catch up.
	Buffer = 1000

	// FilterFields is a regular expression that matches field names that should
	// not be sent to Rollbar. Values for these fields are replaced with
	// "[FILTERED]".
	FilterFields = regexp.MustCompile("password|secret|token")

	// ErrorWriter is the destination for errors encountered while POSTing items
	// to Rollbar. By default, this is stderr. This can be nil.
	ErrorWriter = os.Stderr

	// CodeVersion is the optional code version reported to the Rollbar API for
	// all items.
	CodeVersion = ""

	bodyChannel chan map[string]interface{}
	waitGroup   sync.WaitGroup
	postErrors  chan error
	nilErrTitle = "<nil>"
)

// Field is a custom data field used to report arbitrary data to the Rollbar
// API.
type Field struct {
	Name string
	Data interface{}
}

// -- Setup

func init() {
	bodyChannel = make(chan map[string]interface{}, Buffer)
	postErrors = make(chan error, Buffer)

	go func() {
		var err error
		for body := range bodyChannel {
			err = post(body)
			if err != nil {
				if len(postErrors) == cap(postErrors) {
					<-postErrors
				}
				postErrors <- err
			}
			waitGroup.Done()
		}
		close(postErrors)
	}()
}

// -- Error reporting

func Errorf(level string, format string, args ...interface{}) {
	ErrorWithStackSkip(level, fmt.Errorf(format, args...), 1)
}

// Error asynchronously sends an error to Rollbar with the given severity
// level. You can pass, optionally, custom Fields to be passed on to Rollbar.
func Error(level string, err error, fields ...*Field) {
	ErrorWithStackSkip(level, err, 1, fields...)
}

// ErrorWithStackSkip asynchronously sends an error to Rollbar with the given
// severity level and a given number of stack trace frames skipped. You can
// pass, optionally, custom Fields to be passed on to Rollbar.
func ErrorWithStackSkip(level string, err error, skip int, fields ...*Field) {
	stack := BuildStack(2 + skip)
	ErrorWithStack(level, err, stack, fields...)
}

// ErrorWithStack asynchronously sends and error to Rollbar with the given
// stacktrace and (optionally) custom Fields to be passed on to Rollbar.
func ErrorWithStack(level string, err error, stack Stack, fields ...*Field) {
	buildAndPushError(level, err, stack, fields...)
}

// RequestError asynchronously sends an error to Rollbar with the given
// severity level and request-specific information. You can pass, optionally,
// custom Fields to be passed on to Rollbar.
func RequestError(level string, r *http.Request, err error, fields ...*Field) {
	RequestErrorWithStackSkip(level, r, err, 1, fields...)
}

// RequestErrorWithStackSkip asynchronously sends an error to Rollbar with the
// given severity level and a given number of stack trace frames skipped, in
// addition to extra request-specific information. You can pass, optionally,
// custom Fields to be passed on to Rollbar.
func RequestErrorWithStackSkip(level string, r *http.Request, err error, skip int, fields ...*Field) {
	stack := BuildStack(2 + skip)
	RequestErrorWithStack(level, r, err, stack, fields...)
}

// RequestErrorWithStack asynchronously sends an error to Rollbar with the
// given severity level, request-specific information provided by the given
// http.Request, and a custom Stack. You You can pass, optionally, custom
// Fields to be passed on to Rollbar.
func RequestErrorWithStack(level string, r *http.Request, err error, stack Stack, fields ...*Field) {
	buildAndPushError(level, err, stack, append(fields, &Field{Name: "request", Data: errorRequest(r)})...)
}

func buildError(level string, err error, stack Stack, fields ...*Field) map[string]interface{} {
	title := nilErrTitle
	if err != nil {
		title = err.Error()
	}

	body := buildBody(level, title)
	data := body["data"].(map[string]interface{})
	errBody, fingerprint := errorBody(err, stack)
	data["body"] = errBody
	data["fingerprint"] = fingerprint

	for _, field := range fields {
		data[field.Name] = field.Data
	}

	return body
}

func buildAndPushError(level string, err error, stack Stack, fields ...*Field) {
	push(buildError(level, err, stack, fields...))
}

// -- Message reporting

// Message asynchronously sends a message to Rollbar with the given severity
// level.
func Message(level string, msg string) {
	body := buildBody(level, msg)
	data := body["data"].(map[string]interface{})
	data["body"] = messageBody(msg)

	push(body)
}

// -- Misc.

// PostErrors returns a channel that receives all errors encountered while
// POSTing items to the Rollbar API.
func PostErrors() <-chan error {
	return postErrors
}

// Wait will block until the queue of errors / messages is empty. This allows
// you to ensure that errors / messages are sent to Rollbar before exiting an
// application.
func Wait() {
	waitGroup.Wait()
}

// Build the main JSON structure that will be sent to Rollbar with the
// appropriate metadata.
func buildBody(level, title string) map[string]interface{} {
	timestamp := time.Now().Unix()
	hostname, _ := os.Hostname()

	data := map[string]interface{}{
		"environment": Environment,
		"title":       title,
		"level":       level,
		"timestamp":   timestamp,
		"platform":    Platform,
		"language":    "go",
		"server": map[string]interface{}{
			"host": hostname,
		},
		"notifier": map[string]interface{}{
			"name":    NAME,
			"version": VERSION,
		},
	}
	if CodeVersion != "" {
		data["code_version"] = CodeVersion
	}

	return map[string]interface{}{
		"access_token": Token,
		"data":         data,
	}
}

// errorBody generates a Rollbar error body with a given stack trace.
func errorBody(err error, stack Stack) (map[string]interface{}, string) {
	message := nilErrTitle
	if err != nil {
		message = err.Error()
	}

	fingerprint := stack.Fingerprint()
	errBody := map[string]interface{}{
		"trace": map[string]interface{}{
			"frames": stack,
			"exception": map[string]interface{}{
				"class":   errorClass(err),
				"message": message,
			},
		},
	}
	return errBody, fingerprint
}

// errorRequest extracts details from a Request in a format that Rollbar
// accepts.
func errorRequest(r *http.Request) map[string]interface{} {
	cleanQuery := filterParams(r.URL.Query())

	return map[string]interface{}{
		"url":     r.URL.String(),
		"method":  r.Method,
		"headers": flattenValues(r.Header),

		// GET params
		"query_string": url.Values(cleanQuery).Encode(),
		"GET":          flattenValues(cleanQuery),

		// POST / PUT params
		"POST":    flattenValues(filterParams(r.Form)),
		"user_ip": r.RemoteAddr,
	}
}

// filterParams filters sensitive information like passwords from being sent to
// Rollbar.
func filterParams(values map[string][]string) map[string][]string {
	for key := range values {
		if FilterFields.Match([]byte(key)) {
			values[key] = []string{FILTERED}
		}
	}

	return values
}

func flattenValues(values map[string][]string) map[string]interface{} {
	result := make(map[string]interface{})

	for k, v := range values {
		if len(v) == 1 {
			result[k] = v[0]
		} else {
			result[k] = v
		}
	}

	return result
}

// Build a message inner-body for the given message string.
func messageBody(s string) map[string]interface{} {
	return map[string]interface{}{
		"message": map[string]interface{}{
			"body": s,
		},
	}
}

func errorClass(err error) string {
	if err == nil {
		return nilErrTitle
	}

	class := reflect.TypeOf(err).String()
	if class == "" {
		return "panic"
	} else if class == "*errors.errorString" {
		checksum := adler32.Checksum([]byte(err.Error()))
		return fmt.Sprintf("{%x}", checksum)
	} else {
		return strings.TrimPrefix(class, "*")
	}
}

// -- POST handling

// Queue the given JSON body to be POSTed to Rollbar.
func push(body map[string]interface{}) {
	if len(bodyChannel) < Buffer {
		waitGroup.Add(1)
		bodyChannel <- body
	} else {
		stderr("buffer full, dropping error on the floor")
	}
}

// POST the given JSON body to Rollbar synchronously.
func post(body map[string]interface{}) error {
	if len(Token) == 0 {
		stderr("empty token")
		return nil
	}

	jsonBody, err := json.Marshal(body)
	if err != nil {
		stderr("failed to encode payload: %s", err.Error())
		return err
	}

	resp, err := http.Post(Endpoint, "application/json", bytes.NewReader(jsonBody))
	if err != nil {
		stderr("POST failed: %s", err.Error())
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		stderr("received response: %s", resp.Status)
		return ErrHTTPError(resp.StatusCode)
	}

	return nil
}

// -- stderr
func stderr(format string, args ...interface{}) {
	if ErrorWriter != nil {
		format = "Rollbar error: " + format + "\n"
		fmt.Fprintf(ErrorWriter, format, args...)
	}
}
<commit_msg>stop generating a custom error grouping fingerprint<commit_after>package rollbar

import (
	"bytes"
	"encoding/json"
	"fmt"
	"hash/adler32"
	"net/http"
	"net/url"
	"os"
	"reflect"
	"regexp"
	"runtime"
	"strings"
	"sync"
	"time"
)

const (
	// NAME is the name of this notifier library as reported to the Rollbar API.
	NAME = "go-rollbar"

	// VERSION is the version number of this notifier library as reported to the
	// Rollbar API.
	VERSION = "0.4.0"

	// CRIT is the critical Rollbar severity level as reported to the Rollbar
	// API.
	CRIT = "critical"

	// ERR is the error Rollbar severity level as reported to the Rollbar API.
	ERR = "error"

	// WARN is the warning Rollbar severity level as reported to the Rollbar API.
	WARN = "warning"

	// INFO is the info Rollbar severity level as reported to the Rollbar API.
	INFO = "info"

	// DEBUG is the debug Rollbar severity level as reported to the Rollbar API.
	DEBUG = "debug"

	// FILTERED is the text that replaces all sensitive values in items sent to
	// the Rollbar API.
	FILTERED = "[FILTERED]"
)

var (
	// Token is the Rollbar access token under which all items will be reported.
	// If Token is blank, no errors will be reported to Rollbar.
	Token = ""

	// Environment is the environment under which all items will be reported.
	Environment = "development"

	// Platform is the platform reported for all Rollbar items. The default is
	// the running operating system (darwin, freebsd, linux, etc.) but it can
	// also be application specific (client, heroku, etc.).
	Platform = runtime.GOOS

	// Endpoint is the URL destination for all Rollbar item POST requests.
	Endpoint = "https://api.rollbar.com/api/1/item/"

	// Buffer is the maximum number of errors that will be queued for sending.
	// When the buffer is full, new errors are dropped on the floor until the API
	// can catch up.
	Buffer = 1000

	// FilterFields is a regular expression that matches field names that should
	// not be sent to Rollbar. Values for these fields are replaced with
	// "[FILTERED]".
	FilterFields = regexp.MustCompile("password|secret|token")

	// ErrorWriter is the destination for errors encountered while POSTing items
	// to Rollbar. By default, this is stderr. This can be nil.
	ErrorWriter = os.Stderr

	// CodeVersion is the optional code version reported to the Rollbar API for
	// all items.
	CodeVersion = ""

	bodyChannel chan map[string]interface{}
	waitGroup   sync.WaitGroup
	postErrors  chan error
	nilErrTitle = "<nil>"
)

// Field is a custom data field used to report arbitrary data to the Rollbar
// API.
type Field struct {
	Name string
	Data interface{}
}

// -- Setup

func init() {
	bodyChannel = make(chan map[string]interface{}, Buffer)
	postErrors = make(chan error, Buffer)

	go func() {
		var err error
		for body := range bodyChannel {
			err = post(body)
			if err != nil {
				if len(postErrors) == cap(postErrors) {
					<-postErrors
				}
				postErrors <- err
			}
			waitGroup.Done()
		}
		close(postErrors)
	}()
}

// -- Error reporting

func Errorf(level string, format string, args ...interface{}) {
	ErrorWithStackSkip(level, fmt.Errorf(format, args...), 1)
}

// Error asynchronously sends an error to Rollbar with the given severity
// level. You can pass, optionally, custom Fields to be passed on to Rollbar.
func Error(level string, err error, fields ...*Field) {
	ErrorWithStackSkip(level, err, 1, fields...)
}

// ErrorWithStackSkip asynchronously sends an error to Rollbar with the given
// severity level and a given number of stack trace frames skipped. You can
// pass, optionally, custom Fields to be passed on to Rollbar.
func ErrorWithStackSkip(level string, err error, skip int, fields ...*Field) {
	stack := BuildStack(2 + skip)
	ErrorWithStack(level, err, stack, fields...)
}

// ErrorWithStack asynchronously sends and error to Rollbar with the given
// stacktrace and (optionally) custom Fields to be passed on to Rollbar.
func ErrorWithStack(level string, err error, stack Stack, fields ...*Field) {
	buildAndPushError(level, err, stack, fields...)
}

// RequestError asynchronously sends an error to Rollbar with the given
// severity level and request-specific information. You can pass, optionally,
// custom Fields to be passed on to Rollbar.
func RequestError(level string, r *http.Request, err error, fields ...*Field) {
	RequestErrorWithStackSkip(level, r, err, 1, fields...)
}

// RequestErrorWithStackSkip asynchronously sends an error to Rollbar with the
// given severity level and a given number of stack trace frames skipped, in
// addition to extra request-specific information. You can pass, optionally,
// custom Fields to be passed on to Rollbar.
func RequestErrorWithStackSkip(level string, r *http.Request, err error, skip int, fields ...*Field) {
	stack := BuildStack(2 + skip)
	RequestErrorWithStack(level, r, err, stack, fields...)
}

// RequestErrorWithStack asynchronously sends an error to Rollbar with the
// given severity level, request-specific information provided by the given
// http.Request, and a custom Stack. You You can pass, optionally, custom
// Fields to be passed on to Rollbar.
func RequestErrorWithStack(level string, r *http.Request, err error, stack Stack, fields ...*Field) {
	buildAndPushError(level, err, stack, append(fields, &Field{Name: "request", Data: errorRequest(r)})...)
}

func buildError(level string, err error, stack Stack, fields ...*Field) map[string]interface{} {
	title := nilErrTitle
	if err != nil {
		title = err.Error()
	}

	body := buildBody(level, title)
	data := body["data"].(map[string]interface{})
	errBody := errorBody(err, stack)
	data["body"] = errBody

	for _, field := range fields {
		data[field.Name] = field.Data
	}

	return body
}

func buildAndPushError(level string, err error, stack Stack, fields ...*Field) {
	push(buildError(level, err, stack, fields...))
}

// -- Message reporting

// Message asynchronously sends a message to Rollbar with the given severity
// level.
func Message(level string, msg string) {
	body := buildBody(level, msg)
	data := body["data"].(map[string]interface{})
	data["body"] = messageBody(msg)

	push(body)
}

// -- Misc.

// PostErrors returns a channel that receives all errors encountered while
// POSTing items to the Rollbar API.
func PostErrors() <-chan error {
	return postErrors
}

// Wait will block until the queue of errors / messages is empty. This allows
// you to ensure that errors / messages are sent to Rollbar before exiting an
// application.
func Wait() {
	waitGroup.Wait()
}

// Build the main JSON structure that will be sent to Rollbar with the
// appropriate metadata.
func buildBody(level, title string) map[string]interface{} {
	timestamp := time.Now().Unix()
	hostname, _ := os.Hostname()

	data := map[string]interface{}{
		"environment": Environment,
		"title":       title,
		"level":       level,
		"timestamp":   timestamp,
		"platform":    Platform,
		"language":    "go",
		"server": map[string]interface{}{
			"host": hostname,
		},
		"notifier": map[string]interface{}{
			"name":    NAME,
			"version": VERSION,
		},
	}
	if CodeVersion != "" {
		data["code_version"] = CodeVersion
	}

	return map[string]interface{}{
		"access_token": Token,
		"data":         data,
	}
}

// errorBody generates a Rollbar error body with a given stack trace.
func errorBody(err error, stack Stack) map[string]interface{} {
	message := nilErrTitle
	if err != nil {
		message = err.Error()
	}

	errBody := map[string]interface{}{
		"trace": map[string]interface{}{
			"frames": stack,
			"exception": map[string]interface{}{
				"class":   errorClass(err),
				"message": message,
			},
		},
	}
	return errBody
}

// errorRequest extracts details from a Request in a format that Rollbar
// accepts.
func errorRequest(r *http.Request) map[string]interface{} {
	cleanQuery := filterParams(r.URL.Query())

	return map[string]interface{}{
		"url":     r.URL.String(),
		"method":  r.Method,
		"headers": flattenValues(r.Header),

		// GET params
		"query_string": url.Values(cleanQuery).Encode(),
		"GET":          flattenValues(cleanQuery),

		// POST / PUT params
		"POST":    flattenValues(filterParams(r.Form)),
		"user_ip": r.RemoteAddr,
	}
}

// filterParams filters sensitive information like passwords from being sent to
// Rollbar.
func filterParams(values map[string][]string) map[string][]string {
	for key := range values {
		if FilterFields.Match([]byte(key)) {
			values[key] = []string{FILTERED}
		}
	}

	return values
}

func flattenValues(values map[string][]string) map[string]interface{} {
	result := make(map[string]interface{})

	for k, v := range values {
		if len(v) == 1 {
			result[k] = v[0]
		} else {
			result[k] = v
		}
	}

	return result
}

// Build a message inner-body for the given message string.
func messageBody(s string) map[string]interface{} {
	return map[string]interface{}{
		"message": map[string]interface{}{
			"body": s,
		},
	}
}

func errorClass(err error) string {
	if err == nil {
		return nilErrTitle
	}

	class := reflect.TypeOf(err).String()
	if class == "" {
		return "panic"
	} else if class == "*errors.errorString" {
		checksum := adler32.Checksum([]byte(err.Error()))
		return fmt.Sprintf("{%x}", checksum)
	} else {
		return strings.TrimPrefix(class, "*")
	}
}

// -- POST handling

// Queue the given JSON body to be POSTed to Rollbar.
func push(body map[string]interface{}) {
	if len(bodyChannel) < Buffer {
		waitGroup.Add(1)
		bodyChannel <- body
	} else {
		stderr("buffer full, dropping error on the floor")
	}
}

// POST the given JSON body to Rollbar synchronously.
func post(body map[string]interface{}) error {
	if len(Token) == 0 {
		stderr("empty token")
		return nil
	}

	jsonBody, err := json.Marshal(body)
	if err != nil {
		stderr("failed to encode payload: %s", err.Error())
		return err
	}

	resp, err := http.Post(Endpoint, "application/json", bytes.NewReader(jsonBody))
	if err != nil {
		stderr("POST failed: %s", err.Error())
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		stderr("received response: %s", resp.Status)
		return ErrHTTPError(resp.StatusCode)
	}

	return nil
}

// -- stderr
func stderr(format string, args ...interface{}) {
	if ErrorWriter != nil {
		format = "Rollbar error: " + format + "\n"
		fmt.Fprintf(ErrorWriter, format, args...)
	}
}
<|endoftext|>