<commit_before>package main

import (
	"database/sql"
	"fmt"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"

	"git.zxq.co/ripple/rippleapi/common"
	"git.zxq.co/ripple/schiavolib"
	"github.com/asaskevich/govalidator"
	"github.com/gin-gonic/gin"
)

func register(c *gin.Context) {
	if getContext(c).User.ID != 0 {
		resp403(c)
		return
	}
	if c.Query("stopsign") != "1" {
		u, _ := tryBotnets(c)
		if u != "" {
			simple(c, getSimpleByFilename("register/elmo.html"), nil, map[string]interface{}{
				"Username": u,
			})
			return
		}
	}
	registerResp(c)
}

func registerSubmit(c *gin.Context) {
	if getContext(c).User.ID != 0 {
		resp403(c)
		return
	}
	// check registrations are enabled
	if !registrationsEnabled() {
		registerResp(c, errorMessage{"Sorry, it's not possible to register at the moment. Please try again later."})
		return
	}

	// check username is valid by our criteria
	username := strings.TrimSpace(c.PostForm("username"))
	if !usernameRegex.MatchString(username) {
		registerResp(c, errorMessage{"Your username must contain alphanumerical characters, spaces, or any of _[]-"})
		return
	}

	// check whether an username is e.g. cookiezi, shigetora, peppy, wubwoofwolf, loctav
	if in(strings.ToLower(username), forbiddenUsernames) {
		registerResp(c, errorMessage{"You're not allowed to register with that username."})
		return
	}

	// check email is valid
	if !govalidator.IsEmail(c.PostForm("email")) {
		registerResp(c, errorMessage{"Please pass a valid email address."})
		return
	}

	// passwords check (too short/too common)
	if x := validatePassword(c.PostForm("password")); x != "" {
		registerResp(c, errorMessage{x})
		return
	}

	// usernames with both _ and spaces are not allowed
	if strings.Contains(username, "_") && strings.Contains(username, " ") {
		registerResp(c, errorMessage{"An username can't contain both underscores and spaces."})
		return
	}

	// check whether username already exists
	if db.QueryRow("SELECT 1 FROM users WHERE username_safe = ?", safeUsername(username)).
		Scan(new(int)) != sql.ErrNoRows {
		registerResp(c, errorMessage{"An user with that username already exists!"})
		return
	}

	// check whether an user with that email already exists
	if db.QueryRow("SELECT 1 FROM users WHERE email = ?", c.PostForm("email")).
		Scan(new(int)) != sql.ErrNoRows {
		registerResp(c, errorMessage{"An user with that email address already exists!"})
		return
	}

	// recaptcha verify
	if config.RecaptchaPrivate != "" && !recaptchaCheck(c) {
		registerResp(c, errorMessage{"Captcha is invalid."})
		return
	}

	uMulti, criteria := tryBotnets(c)
	if criteria != "" {
		schiavo.CMs.Send(
			fmt.Sprintf(
				"User **%s** registered with the same %s as %s (%s/u/%s). **POSSIBLE MULTIACCOUNT!!!**. Waiting for ingame verification...",
				username, criteria, uMulti, config.BaseURL, url.QueryEscape(uMulti),
			),
		)
	}

	// The actual registration.
	pass, err := generatePassword(c.PostForm("password"))
	if err != nil {
		resp500(c)
		return
	}

	res, err := db.Exec(`INSERT INTO users(username, username_safe, password_md5, salt, email, register_datetime, privileges, password_version)
							  VALUES (?,        ?,             ?,            '',   ?,     ?,                 ?,          2);`,
		username, safeUsername(username), pass, c.PostForm("email"), time.Now().Unix(), common.UserPrivilegePendingVerification)
	if err != nil {
		registerResp(c, errorMessage{"Whoops, an error slipped in. You might have been registered, though. I don't know."})
		return
	}
	lid, _ := res.LastInsertId()

	db.Exec("INSERT INTO `users_stats`(id, username, user_color, user_style, ranked_score_std, playcount_std, total_score_std, ranked_score_taiko, playcount_taiko, total_score_taiko, ranked_score_ctb, playcount_ctb, total_score_ctb, ranked_score_mania, playcount_mania, total_score_mania) VALUES (?, ?, 'black', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);", lid, username)

	for _, m := range []string{"std", "taiko", "ctb", "mania"} {
		var lastPosition int
		db.QueryRow("SELECT position FROM leaderboard_" + m + " ORDER BY position DESC LIMIT 1").Scan(&lastPosition)
		db.Exec("INSERT INTO leaderboard_"+m+" (position, user, v) VALUES (?, ?, ?)", lastPosition+1, lid, 0)
	}

	schiavo.CMs.Send(fmt.Sprintf("User (**%s** | %s) registered from %s", username, c.PostForm("email"), clientIP(c)))

	setYCookie(int(lid), c)
	logIP(c, int(lid))

	addMessage(c, successMessage{"You have been successfully registered on Ripple! You now need to verify your account."})
	getSession(c).Save()
	c.Redirect(302, "/register/verify?u="+strconv.Itoa(int(lid)))
}

func registerResp(c *gin.Context, messages ...message) {
	resp(c, 200, "register/register.html", &baseTemplateData{
		TitleBar:  "Register",
		KyutGrill: "register.jpg",
		Scripts:   []string{"https://www.google.com/recaptcha/api.js"},
		Messages:  messages,
		FormData:  normaliseURLValues(c.Request.PostForm),
	})
}

func registrationsEnabled() bool {
	var enabled bool
	db.QueryRow("SELECT value_int FROM system_settings WHERE name = 'registrations_enabled'").Scan(&enabled)
	return enabled
}

func verifyAccount(c *gin.Context) {
	if getContext(c).User.ID != 0 {
		resp403(c)
		return
	}

	i, ret := checkUInQS(c)
	if ret {
		return
	}

	sess := getSession(c)
	var rPrivileges uint64
	db.Get(&rPrivileges, "SELECT privileges FROM users WHERE id = ?", i)
	if common.UserPrivileges(rPrivileges)&common.UserPrivilegePendingVerification == 0 {
		addMessage(c, warningMessage{"Nope."})
		sess.Save()
		c.Redirect(302, "/")
		return
	}

	resp(c, 200, "register/verify.html", &baseTemplateData{
		TitleBar:       "Verify account",
		HeadingOnRight: true,
		KyutGrill:      "welcome.jpg",
	})
}

func welcome(c *gin.Context) {
	if getContext(c).User.ID != 0 {
		resp403(c)
		return
	}

	i, ret := checkUInQS(c)
	if ret {
		return
	}

	var rPrivileges uint64
	db.Get(&rPrivileges, "SELECT privileges FROM users WHERE id = ?", i)
	if common.UserPrivileges(rPrivileges)&common.UserPrivilegePendingVerification > 0 {
		c.Redirect(302, "/register/verify?u="+c.Query("u"))
		return
	}

	t := "Welcome!"
	if common.UserPrivileges(rPrivileges)&common.UserPrivilegeNormal == 0 {
		// if the user has no UserNormal, it means they're banned = they multiaccounted
		t = "Welcome back!"
	}

	resp(c, 200, "register/welcome.html", &baseTemplateData{
		TitleBar:       t,
		HeadingOnRight: true,
		KyutGrill:      "welcome.jpg",
	})
}

// Check User In Query Is Same As User In Y Cookie
func checkUInQS(c *gin.Context) (int, bool) {
	sess := getSession(c)

	i, _ := strconv.Atoi(c.Query("u"))
	y, _ := c.Cookie("y")
	err := db.QueryRow("SELECT 1 FROM identity_tokens WHERE token = ? AND userid = ?", y, i).Scan(new(int))
	if err == sql.ErrNoRows {
		addMessage(c, warningMessage{"Nope."})
		sess.Save()
		c.Redirect(302, "/")
		return 0, true
	}
	return i, false
}

func tryBotnets(c *gin.Context) (string, string) {
	var username string

	err := db.QueryRow("SELECT u.username FROM ip_user i LEFT JOIN users u ON u.id = i.userid WHERE i.ip = ?", clientIP(c)).Scan(&username)
	if err != nil {
		if err != sql.ErrNoRows {
			c.Error(err)
		}
		return "", ""
	}
	if username != "" {
		return username, "IP"
	}

	cook, _ := c.Cookie("y")
	err = db.QueryRow("SELECT u.username FROM identity_tokens i LEFT JOIN users u ON u.id = i.userid WHERE i.token = ?",
		cook).Scan(&username)
	if err != nil {
		if err != sql.ErrNoRows {
			c.Error(err)
		}
		return "", ""
	}
	if username != "" {
		return username, "username"
	}

	return "", ""
}

func in(s string, ss []string) bool {
	for _, x := range ss {
		if x == s {
			return true
		}
	}
	return false
}

var usernameRegex = regexp.MustCompile(`^[A-Za-z0-9 _\[\]-]{2,15}$`)
var forbiddenUsernames = []string{
	"peppy",
	"rrtyui",
	"cookiezi",
	"azer",
	"loctav",
	"banchobot",
	"happystick",
	"doomsday",
	"sharingan33",
	"andrea",
	"cptnxn",
	"reimu-desu",
	"hvick225",
	"_index",
	"my aim sucks",
	"kynan",
	"rafis",
	"sayonara-bye",
	"thelewa",
	"wubwoofwolf",
	"millhioref",
	"tom94",
	"tillerino",
	"clsw",
	"spectator",
	"exgon",
	"axarious",
	"angelsim",
	"recia",
	"nara",
	"emperorpenguin83",
	"bikko",
	"xilver",
	"vettel",
	"kuu01",
	"_yu68",
	"tasuke912",
	"dusk",
	"ttobas",
	"velperk",
	"jakads",
	"jhlee0133",
	"abcdullah",
	"yuko-",
	"entozer",
	"hdhr",
	"ekoro",
	"snowwhite",
	"osuplayer111",
	"musty",
	"nero",
	"elysion",
	"ztrot",
	"koreapenguin",
	"fort",
	"asphyxia",
	"niko",
	"shigetora",
}
<commit_msg>left join -> inner joins<commit_after>package main

import (
	"database/sql"
	"fmt"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"

	"git.zxq.co/ripple/rippleapi/common"
	"git.zxq.co/ripple/schiavolib"
	"github.com/asaskevich/govalidator"
	"github.com/gin-gonic/gin"
)

func register(c *gin.Context) {
	if getContext(c).User.ID != 0 {
		resp403(c)
		return
	}
	if c.Query("stopsign") != "1" {
		u, _ := tryBotnets(c)
		if u != "" {
			simple(c, getSimpleByFilename("register/elmo.html"), nil, map[string]interface{}{
				"Username": u,
			})
			return
		}
	}
	registerResp(c)
}

func registerSubmit(c *gin.Context) {
	if getContext(c).User.ID != 0 {
		resp403(c)
		return
	}
	// check registrations are enabled
	if !registrationsEnabled() {
		registerResp(c, errorMessage{"Sorry, it's not possible to register at the moment. Please try again later."})
		return
	}

	// check username is valid by our criteria
	username := strings.TrimSpace(c.PostForm("username"))
	if !usernameRegex.MatchString(username) {
		registerResp(c, errorMessage{"Your username must contain alphanumerical characters, spaces, or any of _[]-"})
		return
	}

	// check whether an username is e.g. cookiezi, shigetora, peppy, wubwoofwolf, loctav
	if in(strings.ToLower(username), forbiddenUsernames) {
		registerResp(c, errorMessage{"You're not allowed to register with that username."})
		return
	}

	// check email is valid
	if !govalidator.IsEmail(c.PostForm("email")) {
		registerResp(c, errorMessage{"Please pass a valid email address."})
		return
	}

	// passwords check (too short/too common)
	if x := validatePassword(c.PostForm("password")); x != "" {
		registerResp(c, errorMessage{x})
		return
	}

	// usernames with both _ and spaces are not allowed
	if strings.Contains(username, "_") && strings.Contains(username, " ") {
		registerResp(c, errorMessage{"An username can't contain both underscores and spaces."})
		return
	}

	// check whether username already exists
	if db.QueryRow("SELECT 1 FROM users WHERE username_safe = ?", safeUsername(username)).
		Scan(new(int)) != sql.ErrNoRows {
		registerResp(c, errorMessage{"An user with that username already exists!"})
		return
	}

	// check whether an user with that email already exists
	if db.QueryRow("SELECT 1 FROM users WHERE email = ?", c.PostForm("email")).
		Scan(new(int)) != sql.ErrNoRows {
		registerResp(c, errorMessage{"An user with that email address already exists!"})
		return
	}

	// recaptcha verify
	if config.RecaptchaPrivate != "" && !recaptchaCheck(c) {
		registerResp(c, errorMessage{"Captcha is invalid."})
		return
	}

	uMulti, criteria := tryBotnets(c)
	if criteria != "" {
		schiavo.CMs.Send(
			fmt.Sprintf(
				"User **%s** registered with the same %s as %s (%s/u/%s). **POSSIBLE MULTIACCOUNT!!!**. Waiting for ingame verification...",
				username, criteria, uMulti, config.BaseURL, url.QueryEscape(uMulti),
			),
		)
	}

	// The actual registration.
	pass, err := generatePassword(c.PostForm("password"))
	if err != nil {
		resp500(c)
		return
	}

	res, err := db.Exec(`INSERT INTO users(username, username_safe, password_md5, salt, email, register_datetime, privileges, password_version)
							  VALUES (?,        ?,             ?,            '',   ?,     ?,                 ?,          2);`,
		username, safeUsername(username), pass, c.PostForm("email"), time.Now().Unix(), common.UserPrivilegePendingVerification)
	if err != nil {
		registerResp(c, errorMessage{"Whoops, an error slipped in. You might have been registered, though. I don't know."})
		return
	}
	lid, _ := res.LastInsertId()

	db.Exec("INSERT INTO `users_stats`(id, username, user_color, user_style, ranked_score_std, playcount_std, total_score_std, ranked_score_taiko, playcount_taiko, total_score_taiko, ranked_score_ctb, playcount_ctb, total_score_ctb, ranked_score_mania, playcount_mania, total_score_mania) VALUES (?, ?, 'black', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);", lid, username)

	for _, m := range []string{"std", "taiko", "ctb", "mania"} {
		var lastPosition int
		db.QueryRow("SELECT position FROM leaderboard_" + m + " ORDER BY position DESC LIMIT 1").Scan(&lastPosition)
		db.Exec("INSERT INTO leaderboard_"+m+" (position, user, v) VALUES (?, ?, ?)", lastPosition+1, lid, 0)
	}

	schiavo.CMs.Send(fmt.Sprintf("User (**%s** | %s) registered from %s", username, c.PostForm("email"), clientIP(c)))

	setYCookie(int(lid), c)
	logIP(c, int(lid))

	addMessage(c, successMessage{"You have been successfully registered on Ripple! You now need to verify your account."})
	getSession(c).Save()
	c.Redirect(302, "/register/verify?u="+strconv.Itoa(int(lid)))
}

func registerResp(c *gin.Context, messages ...message) {
	resp(c, 200, "register/register.html", &baseTemplateData{
		TitleBar:  "Register",
		KyutGrill: "register.jpg",
		Scripts:   []string{"https://www.google.com/recaptcha/api.js"},
		Messages:  messages,
		FormData:  normaliseURLValues(c.Request.PostForm),
	})
}

func registrationsEnabled() bool {
	var enabled bool
	db.QueryRow("SELECT value_int FROM system_settings WHERE name = 'registrations_enabled'").Scan(&enabled)
	return enabled
}

func verifyAccount(c *gin.Context) {
	if getContext(c).User.ID != 0 {
		resp403(c)
		return
	}

	i, ret := checkUInQS(c)
	if ret {
		return
	}

	sess := getSession(c)
	var rPrivileges uint64
	db.Get(&rPrivileges, "SELECT privileges FROM users WHERE id = ?", i)
	if common.UserPrivileges(rPrivileges)&common.UserPrivilegePendingVerification == 0 {
		addMessage(c, warningMessage{"Nope."})
		sess.Save()
		c.Redirect(302, "/")
		return
	}

	resp(c, 200, "register/verify.html", &baseTemplateData{
		TitleBar:       "Verify account",
		HeadingOnRight: true,
		KyutGrill:      "welcome.jpg",
	})
}

func welcome(c *gin.Context) {
	if getContext(c).User.ID != 0 {
		resp403(c)
		return
	}

	i, ret := checkUInQS(c)
	if ret {
		return
	}

	var rPrivileges uint64
	db.Get(&rPrivileges, "SELECT privileges FROM users WHERE id = ?", i)
	if common.UserPrivileges(rPrivileges)&common.UserPrivilegePendingVerification > 0 {
		c.Redirect(302, "/register/verify?u="+c.Query("u"))
		return
	}

	t := "Welcome!"
	if common.UserPrivileges(rPrivileges)&common.UserPrivilegeNormal == 0 {
		// if the user has no UserNormal, it means they're banned = they multiaccounted
		t = "Welcome back!"
	}

	resp(c, 200, "register/welcome.html", &baseTemplateData{
		TitleBar:       t,
		HeadingOnRight: true,
		KyutGrill:      "welcome.jpg",
	})
}

// Check User In Query Is Same As User In Y Cookie
func checkUInQS(c *gin.Context) (int, bool) {
	sess := getSession(c)

	i, _ := strconv.Atoi(c.Query("u"))
	y, _ := c.Cookie("y")
	err := db.QueryRow("SELECT 1 FROM identity_tokens WHERE token = ? AND userid = ?", y, i).Scan(new(int))
	if err == sql.ErrNoRows {
		addMessage(c, warningMessage{"Nope."})
		sess.Save()
		c.Redirect(302, "/")
		return 0, true
	}
	return i, false
}

func tryBotnets(c *gin.Context) (string, string) {
	var username string

	err := db.QueryRow("SELECT u.username FROM ip_user i INNER JOIN users u ON u.id = i.userid WHERE i.ip = ?", clientIP(c)).Scan(&username)
	if err != nil {
		if err != sql.ErrNoRows {
			c.Error(err)
		}
		return "", ""
	}
	if username != "" {
		return username, "IP"
	}

	cook, _ := c.Cookie("y")
	err = db.QueryRow("SELECT u.username FROM identity_tokens i INNER JOIN users u ON u.id = i.userid WHERE i.token = ?",
		cook).Scan(&username)
	if err != nil {
		if err != sql.ErrNoRows {
			c.Error(err)
		}
		return "", ""
	}
	if username != "" {
		return username, "username"
	}

	return "", ""
}

func in(s string, ss []string) bool {
	for _, x := range ss {
		if x == s {
			return true
		}
	}
	return false
}

var usernameRegex = regexp.MustCompile(`^[A-Za-z0-9 _\[\]-]{2,15}$`)
var forbiddenUsernames = []string{
	"peppy",
	"rrtyui",
	"cookiezi",
	"azer",
	"loctav",
	"banchobot",
	"happystick",
	"doomsday",
	"sharingan33",
	"andrea",
	"cptnxn",
	"reimu-desu",
	"hvick225",
	"_index",
	"my aim sucks",
	"kynan",
	"rafis",
	"sayonara-bye",
	"thelewa",
	"wubwoofwolf",
	"millhioref",
	"tom94",
	"tillerino",
	"clsw",
	"spectator",
	"exgon",
	"axarious",
	"angelsim",
	"recia",
	"nara",
	"emperorpenguin83",
	"bikko",
	"xilver",
	"vettel",
	"kuu01",
	"_yu68",
	"tasuke912",
	"dusk",
	"ttobas",
	"velperk",
	"jakads",
	"jhlee0133",
	"abcdullah",
	"yuko-",
	"entozer",
	"hdhr",
	"ekoro",
	"snowwhite",
	"osuplayer111",
	"musty",
	"nero",
	"elysion",
	"ztrot",
	"koreapenguin",
	"fort",
	"asphyxia",
	"niko",
	"shigetora",
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/bmizerany/mc"
	"github.com/elazarl/goproxy"
	"github.com/jackc/pgx"
)

var (
	cn    *mc.Conn
	pool  *pgx.ConnPool
	proxy *goproxy.ProxyHttpServer
)

func urlHasPrefix(prefix string) goproxy.ReqConditionFunc {
	return func(req *http.Request, ctx *goproxy.ProxyCtx) bool {
		isGET := req.Method == http.MethodGet
		hasPrefix := strings.HasPrefix(req.URL.Path, prefix)
		isSearch := strings.HasPrefix(req.URL.Path, "/packages/search/")
		return isGET && hasPrefix && !isSearch
	}
}

func pathIs(path string) goproxy.ReqConditionFunc {
	return func(req *http.Request, ctx *goproxy.ProxyCtx) bool {
		return req.Method == http.MethodGet && req.URL.Path == path
	}
}

func getEnv(key, def string) string {
	k := os.Getenv(key)
	if k == "" {
		return def
	}
	return k
}

func main() {
	memcachedURL := getEnv("MEMCACHEDCLOUD_SERVERS", "localhost:11211")
	var err error
	cn, err = mc.Dial("tcp", memcachedURL)
	if err != nil {
		log.Fatalf("Memcached connection error: %s", err)
	}

	memcachedUsername := os.Getenv("MEMCACHEDCLOUD_USERNAME")
	memcachedPassword := os.Getenv("MEMCACHEDCLOUD_PASSWORD")
	if memcachedUsername != "" && memcachedPassword != "" {
		if err := cn.Auth(memcachedUsername, memcachedPassword); err != nil {
			log.Fatalf("Memcached auth error: %s", err)
		}
	}

	pgxcfg, err := pgx.ParseURI(os.Getenv("DATABASE_URL"))
	if err != nil {
		log.Fatalf("Parse URI error: %s", err)
	}
	pool, err = pgx.NewConnPool(pgx.ConnPoolConfig{
		ConnConfig:     pgxcfg,
		MaxConnections: 20,
		AfterConnect: func(conn *pgx.Conn) error {
			_, err := conn.Prepare("getPackage", `SELECT name, url FROM packages WHERE name = $1`)
			return err
		},
	})
	if err != nil {
		log.Fatalf("Connection error: %s", err)
	}
	defer pool.Close()

	binary, err := exec.LookPath("node")
	if err != nil {
		log.Fatalf("Could not lookup node path: %s", err)
	}

	cmd := exec.Command(binary, "--expose_gc", "index.js")
	env := os.Environ()
	env = append(env, "PORT=3001")
	cmd.Env = env
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Start(); err != nil {
		log.Fatalf("Could not start node: %s", err)
	}
	go func() {
		if err := cmd.Wait(); err != nil {
			log.Fatalf("Node process failed: %s", err)
		}
	}()

	proxy = goproxy.NewProxyHttpServer()
	proxy.Verbose = false
	proxy.NonproxyHandler = http.HandlerFunc(nonProxy)

	proxy.OnRequest().DoFunc(
		func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
			if r.Method == "GET" && r.Host != "registry.bower.io" && r.Host != "components.bower.io" {
				if r.Method == "GET" {
					if strings.HasPrefix(r.URL.Path, "/packages/search/") {

						response := goproxy.NewResponse(r, "application/json", http.StatusOK, `[{"name":"deprecated","url":"This bower version is deprecated. Please update it: npm update -g bower"}]`)
						return r, response
					}
					time.Sleep(15 * time.Second)
					response := goproxy.NewResponse(r, "application/json", http.StatusPermanentRedirect, "")
					target := "https://registry.bower.io" + r.URL.Path
					if len(r.URL.RawQuery) > 0 {
						target += "?" + r.URL.RawQuery
					}
					response.Header.Set("Location", target)
					return r, response
				} else {
					response := goproxy.NewResponse(r, "application/json", http.StatusBadGateway, "This Bower version is deprecated. Please upgrade")
					return r, response
				}
			}

			return r, nil
		})

	proxy.OnRequest(pathIs("/packages")).DoFunc(listPackages)
	proxy.OnRequest(urlHasPrefix("/packages/")).DoFunc(getPackage)

	port := getEnv("PORT", "3000")
	log.Println("Starting web server at port", port)
	log.Fatal(http.ListenAndServe(":"+port, proxy))
}

func nonProxy(w http.ResponseWriter, req *http.Request) {
	req.URL.Scheme = "http"
	req.URL.Host = "localhost:3001"
	proxy.ServeHTTP(w, req)
}

type Package struct {
	Name string `json:"name"`
	URL  string `json:"url"`
}

func getPackage(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
	elements := strings.Split(r.URL.Path, "/")
	packageName := elements[len(elements)-1]

	var name, url string
	if err := pool.QueryRow("getPackage", packageName).Scan(&name, &url); err != nil {
		if err == pgx.ErrNoRows {
			return r, goproxy.NewResponse(r, "text/html", http.StatusNotFound, "Package not found")
		}
		return r, goproxy.NewResponse(r, "text/html", http.StatusInternalServerError, "Internal server error")
	}

	data, err := json.Marshal(Package{Name: name, URL: url})
	if err != nil {
		return r, goproxy.NewResponse(r, "text/html", http.StatusInternalServerError, "Internal server error")
	}
	response := goproxy.NewResponse(r, "application/json", http.StatusOK, string(data))
	response.Header.Add("Cache-Control", "public, max-age=604800")
	return r, response
}

func listPackages(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
	val, _, _, err := cn.Get("packages")
	if err != nil {
		return r, nil
	}
	response := goproxy.NewResponse(r, "application/json", http.StatusOK, val)
	response.Header.Add("Cache-Control", "public, max-age=604800")
	return r, response
}
<commit_msg>Fix previous commit<commit_after>package main

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/bmizerany/mc"
	"github.com/elazarl/goproxy"
	"github.com/jackc/pgx"
)

var (
	cn    *mc.Conn
	pool  *pgx.ConnPool
	proxy *goproxy.ProxyHttpServer
)

func urlHasPrefix(prefix string) goproxy.ReqConditionFunc {
	return func(req *http.Request, ctx *goproxy.ProxyCtx) bool {
		isGET := req.Method == http.MethodGet
		hasPrefix := strings.HasPrefix(req.URL.Path, prefix)
		isSearch := strings.HasPrefix(req.URL.Path, "/packages/search/")
		return isGET && hasPrefix && !isSearch
	}
}

func pathIs(path string) goproxy.ReqConditionFunc {
	return func(req *http.Request, ctx *goproxy.ProxyCtx) bool {
		return req.Method == http.MethodGet && req.URL.Path == path
	}
}

func getEnv(key, def string) string {
	k := os.Getenv(key)
	if k == "" {
		return def
	}
	return k
}

func main() {
	memcachedURL := getEnv("MEMCACHEDCLOUD_SERVERS", "localhost:11211")
	var err error
	cn, err = mc.Dial("tcp", memcachedURL)
	if err != nil {
		log.Fatalf("Memcached connection error: %s", err)
	}

	memcachedUsername := os.Getenv("MEMCACHEDCLOUD_USERNAME")
	memcachedPassword := os.Getenv("MEMCACHEDCLOUD_PASSWORD")
	if memcachedUsername != "" && memcachedPassword != "" {
		if err := cn.Auth(memcachedUsername, memcachedPassword); err != nil {
			log.Fatalf("Memcached auth error: %s", err)
		}
	}

	pgxcfg, err := pgx.ParseURI(os.Getenv("DATABASE_URL"))
	if err != nil {
		log.Fatalf("Parse URI error: %s", err)
	}
	pool, err = pgx.NewConnPool(pgx.ConnPoolConfig{
		ConnConfig:     pgxcfg,
		MaxConnections: 20,
		AfterConnect: func(conn *pgx.Conn) error {
			_, err := conn.Prepare("getPackage", `SELECT name, url FROM packages WHERE name = $1`)
			return err
		},
	})
	if err != nil {
		log.Fatalf("Connection error: %s", err)
	}
	defer pool.Close()

	binary, err := exec.LookPath("node")
	if err != nil {
		log.Fatalf("Could not lookup node path: %s", err)
	}

	cmd := exec.Command(binary, "--expose_gc", "index.js")
	env := os.Environ()
	env = append(env, "PORT=3001")
	cmd.Env = env
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Start(); err != nil {
		log.Fatalf("Could not start node: %s", err)
	}
	go func() {
		if err := cmd.Wait(); err != nil {
			log.Fatalf("Node process failed: %s", err)
		}
	}()

	proxy = goproxy.NewProxyHttpServer()
	proxy.Verbose = false
	proxy.NonproxyHandler = http.HandlerFunc(nonProxy)

	proxy.OnRequest().DoFunc(
		func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
			if r.Host != "registry.bower.io" && r.Host != "components.bower.io" {
				if r.Method == "GET" {
					if strings.HasPrefix(r.URL.Path, "/packages/search/") {

						response := goproxy.NewResponse(r, "application/json", http.StatusOK, `[{"name":"deprecated","url":"This bower version is deprecated. Please update it: npm update -g bower"}]`)
						return r, response
					}
					time.Sleep(15 * time.Second)
					response := goproxy.NewResponse(r, "application/json", http.StatusPermanentRedirect, "")
					target := "https://registry.bower.io" + r.URL.Path
					if len(r.URL.RawQuery) > 0 {
						target += "?" + r.URL.RawQuery
					}
					response.Header.Set("Location", target)
					return r, response
				} else {
					response := goproxy.NewResponse(r, "application/json", http.StatusBadGateway, "This Bower version is deprecated. Please upgrade")
					return r, response
				}
			}

			return r, nil
		})

	proxy.OnRequest(pathIs("/packages")).DoFunc(listPackages)
	proxy.OnRequest(urlHasPrefix("/packages/")).DoFunc(getPackage)

	port := getEnv("PORT", "3000")
	log.Println("Starting web server at port", port)
	log.Fatal(http.ListenAndServe(":"+port, proxy))
}

func nonProxy(w http.ResponseWriter, req *http.Request) {
	req.URL.Scheme = "http"
	req.URL.Host = "localhost:3001"
	proxy.ServeHTTP(w, req)
}

type Package struct {
	Name string `json:"name"`
	URL  string `json:"url"`
}

func getPackage(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
	elements := strings.Split(r.URL.Path, "/")
	packageName := elements[len(elements)-1]

	var name, url string
	if err := pool.QueryRow("getPackage", packageName).Scan(&name, &url); err != nil {
		if err == pgx.ErrNoRows {
			return r, goproxy.NewResponse(r, "text/html", http.StatusNotFound, "Package not found")
		}
		return r, goproxy.NewResponse(r, "text/html", http.StatusInternalServerError, "Internal server error")
	}

	data, err := json.Marshal(Package{Name: name, URL: url})
	if err != nil {
		return r, goproxy.NewResponse(r, "text/html", http.StatusInternalServerError, "Internal server error")
	}
	response := goproxy.NewResponse(r, "application/json", http.StatusOK, string(data))
	response.Header.Add("Cache-Control", "public, max-age=604800")
	return r, response
}

func listPackages(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
	val, _, _, err := cn.Get("packages")
	if err != nil {
		return r, nil
	}
	response := goproxy.NewResponse(r, "application/json", http.StatusOK, val)
	response.Header.Add("Cache-Control", "public, max-age=604800")
	return r, response
}
<|endoftext|>


<commit_before>package gowebdav

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"path"
	"strings"
)

func (c *Client) req(method, path string, body io.Reader, intercept func(*http.Request)) (req *http.Response, err error) {
	var r *http.Request
	var retryBuf io.Reader

	if body != nil {
		// If the authorization fails, we will need to restart reading
		// from the passed body stream.
		// When body is seekable, use seek to reset the streams
		// cursor to the start.
		// Otherwise, copy the stream into a buffer while uploading
		// and use the buffers content on retry.
		if sk, ok := body.(io.Seeker); ok {
			if _, err = sk.Seek(0, io.SeekStart); err != nil {
				return
			}
			retryBuf = body
		} else {
			buff := &bytes.Buffer{}
			retryBuf = buff
			body = io.TeeReader(body, buff)
		}
		r, err = http.NewRequest(method, PathEscape(Join(c.root, path)), body)
	} else {
		r, err = http.NewRequest(method, PathEscape(Join(c.root, path)), nil)
	}

	if err != nil {
		return nil, err
	}

	for k, vals := range c.headers {
		for _, v := range vals {
			r.Header.Add(k, v)
		}
	}

	// make sure we read 'c.auth' only once since it will be substituted below
	// and that is unsafe to do when multiple goroutines are running at the same time.
	c.authMutex.Lock()
	auth := c.auth
	c.authMutex.Unlock()

	auth.Authorize(r, method, path)

	if intercept != nil {
		intercept(r)
	}

	if c.interceptor != nil {
		c.interceptor(method, r)
	}

	rs, err := c.c.Do(r)
	if err != nil {
		return nil, err
	}

	if rs.StatusCode == 401 && auth.Type() == "NoAuth" {
		wwwAuthenticateHeader := strings.ToLower(rs.Header.Get("Www-Authenticate"))

		if strings.Index(wwwAuthenticateHeader, "digest") > -1 {
			c.authMutex.Lock()
			c.auth = &DigestAuth{auth.User(), auth.Pass(), digestParts(rs)}
			c.authMutex.Unlock()
		} else if strings.Index(wwwAuthenticateHeader, "basic") > -1 {
			c.authMutex.Lock()
			c.auth = &BasicAuth{auth.User(), auth.Pass()}
			c.authMutex.Unlock()
		} else {
			return rs, newPathError("Authorize", c.root, rs.StatusCode)
		}

		// retryBuf will be nil if body was nil initially so no check
		// for body == nil is required here.
		return c.req(method, path, retryBuf, intercept)
	} else if rs.StatusCode == 401 {
		return rs, newPathError("Authorize", c.root, rs.StatusCode)
	}

	return rs, err
}

func (c *Client) mkcol(path string) int {
	rs, err := c.req("MKCOL", path, nil, nil)
	if err != nil {
		return 400
	}
	defer rs.Body.Close()

	if rs.StatusCode == 201 || rs.StatusCode == 405 {
		return 201
	}

	return rs.StatusCode
}

func (c *Client) options(path string) (*http.Response, error) {
	return c.req("OPTIONS", path, nil, func(rq *http.Request) {
		rq.Header.Add("Depth", "0")
	})
}

func (c *Client) propfind(path string, self bool, body string, resp interface{}, parse func(resp interface{}) error) error {
	rs, err := c.req("PROPFIND", path, strings.NewReader(body), func(rq *http.Request) {
		if self {
			rq.Header.Add("Depth", "0")
		} else {
			rq.Header.Add("Depth", "1")
		}
		rq.Header.Add("Content-Type", "application/xml;charset=UTF-8")
		rq.Header.Add("Accept", "application/xml,text/xml")
		rq.Header.Add("Accept-Charset", "utf-8")
		// TODO add support for 'gzip,deflate;q=0.8,q=0.7'
		rq.Header.Add("Accept-Encoding", "")
	})
	if err != nil {
		return err
	}
	defer rs.Body.Close()

	if rs.StatusCode != 207 {
		return fmt.Errorf("%s - %s %s", rs.Status, "PROPFIND", path)
	}

	return parseXML(rs.Body, resp, parse)
}

func (c *Client) doCopyMove(method string, oldpath string, newpath string, overwrite bool) (int, io.ReadCloser) {
	rs, err := c.req(method, oldpath, nil, func(rq *http.Request) {
		rq.Header.Add("Destination", Join(c.root, newpath))
		if overwrite {
			rq.Header.Add("Overwrite", "T")
		} else {
			rq.Header.Add("Overwrite", "F")
		}
	})
	if err != nil {
		return 400, nil
	}
	return rs.StatusCode, rs.Body
}

func (c *Client) copymove(method string, oldpath string, newpath string, overwrite bool) error {
	s, data := c.doCopyMove(method, oldpath, newpath, overwrite)
	if data != nil {
		defer data.Close()
	}

	switch s {
	case 201, 204:
		return nil

	case 207:
		// TODO handle multistat errors, worst case ...
		log(fmt.Sprintf(" TODO handle %s - %s multistatus result %s", method, oldpath, String(data)))

	case 409:
		err := c.createParentCollection(newpath)
		if err != nil {
			return err
		}

		return c.copymove(method, oldpath, newpath, overwrite)
	}

	return newPathError(method, oldpath, s)
}

func (c *Client) put(path string, stream io.Reader) int {
	rs, err := c.req("PUT", path, stream, nil)
	if err != nil {
		return 400
	}
	defer rs.Body.Close()

	return rs.StatusCode
}

func (c *Client) createParentCollection(itemPath string) (err error) {
	parentPath := path.Dir(itemPath)
	if parentPath == "." || parentPath == "/" {
		return nil
	}

	return c.MkdirAll(parentPath, 0755)
}
<commit_msg>Escapes destination path on copy and move #42<commit_after>package gowebdav

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"path"
	"strings"
)

func (c *Client) req(method, path string, body io.Reader, intercept func(*http.Request)) (req *http.Response, err error) {
	var r *http.Request
	var retryBuf io.Reader

	if body != nil {
		// If the authorization fails, we will need to restart reading
		// from the passed body stream.
		// When body is seekable, use seek to reset the streams
		// cursor to the start.
		// Otherwise, copy the stream into a buffer while uploading
		// and use the buffers content on retry.
		if sk, ok := body.(io.Seeker); ok {
			if _, err = sk.Seek(0, io.SeekStart); err != nil {
				return
			}
			retryBuf = body
		} else {
			buff := &bytes.Buffer{}
			retryBuf = buff
			body = io.TeeReader(body, buff)
		}
		r, err = http.NewRequest(method, PathEscape(Join(c.root, path)), body)
	} else {
		r, err = http.NewRequest(method, PathEscape(Join(c.root, path)), nil)
	}

	if err != nil {
		return nil, err
	}

	for k, vals := range c.headers {
		for _, v := range vals {
			r.Header.Add(k, v)
		}
	}

	// make sure we read 'c.auth' only once since it will be substituted below
	// and that is unsafe to do when multiple goroutines are running at the same time.
	c.authMutex.Lock()
	auth := c.auth
	c.authMutex.Unlock()

	auth.Authorize(r, method, path)

	if intercept != nil {
		intercept(r)
	}

	if c.interceptor != nil {
		c.interceptor(method, r)
	}

	rs, err := c.c.Do(r)
	if err != nil {
		return nil, err
	}

	if rs.StatusCode == 401 && auth.Type() == "NoAuth" {
		wwwAuthenticateHeader := strings.ToLower(rs.Header.Get("Www-Authenticate"))

		if strings.Index(wwwAuthenticateHeader, "digest") > -1 {
			c.authMutex.Lock()
			c.auth = &DigestAuth{auth.User(), auth.Pass(), digestParts(rs)}
			c.authMutex.Unlock()
		} else if strings.Index(wwwAuthenticateHeader, "basic") > -1 {
			c.authMutex.Lock()
			c.auth = &BasicAuth{auth.User(), auth.Pass()}
			c.authMutex.Unlock()
		} else {
			return rs, newPathError("Authorize", c.root, rs.StatusCode)
		}

		// retryBuf will be nil if body was nil initially so no check
		// for body == nil is required here.
		return c.req(method, path, retryBuf, intercept)
	} else if rs.StatusCode == 401 {
		return rs, newPathError("Authorize", c.root, rs.StatusCode)
	}

	return rs, err
}

func (c *Client) mkcol(path string) int {
	rs, err := c.req("MKCOL", path, nil, nil)
	if err != nil {
		return 400
	}
	defer rs.Body.Close()

	if rs.StatusCode == 201 || rs.StatusCode == 405 {
		return 201
	}

	return rs.StatusCode
}

func (c *Client) options(path string) (*http.Response, error) {
	return c.req("OPTIONS", path, nil, func(rq *http.Request) {
		rq.Header.Add("Depth", "0")
	})
}

func (c *Client) propfind(path string, self bool, body string, resp interface{}, parse func(resp interface{}) error) error {
	rs, err := c.req("PROPFIND", path, strings.NewReader(body), func(rq *http.Request) {
		if self {
			rq.Header.Add("Depth", "0")
		} else {
			rq.Header.Add("Depth", "1")
		}
		rq.Header.Add("Content-Type", "application/xml;charset=UTF-8")
		rq.Header.Add("Accept", "application/xml,text/xml")
		rq.Header.Add("Accept-Charset", "utf-8")
		// TODO add support for 'gzip,deflate;q=0.8,q=0.7'
		rq.Header.Add("Accept-Encoding", "")
	})
	if err != nil {
		return err
	}
	defer rs.Body.Close()

	if rs.StatusCode != 207 {
		return fmt.Errorf("%s - %s %s", rs.Status, "PROPFIND", path)
	}

	return parseXML(rs.Body, resp, parse)
}

func (c *Client) doCopyMove(method string, oldpath string, newpath string, overwrite bool) (int, io.ReadCloser) {
	rs, err := c.req(method, oldpath, nil, func(rq *http.Request) {
		rq.Header.Add("Destination", PathEscape(Join(c.root, newpath)))
		if overwrite {
			rq.Header.Add("Overwrite", "T")
		} else {
			rq.Header.Add("Overwrite", "F")
		}
	})
	if err != nil {
		return 400, nil
	}
	return rs.StatusCode, rs.Body
}

func (c *Client) copymove(method string, oldpath string, newpath string, overwrite bool) error {
	s, data := c.doCopyMove(method, oldpath, newpath, overwrite)
	if data != nil {
		defer data.Close()
	}

	switch s {
	case 201, 204:
		return nil

	case 207:
		// TODO handle multistat errors, worst case ...
		log(fmt.Sprintf(" TODO handle %s - %s multistatus result %s", method, oldpath, String(data)))

	case 409:
		err := c.createParentCollection(newpath)
		if err != nil {
			return err
		}

		return c.copymove(method, oldpath, newpath, overwrite)
	}

	return newPathError(method, oldpath, s)
}

func (c *Client) put(path string, stream io.Reader) int {
	rs, err := c.req("PUT", path, stream, nil)
	if err != nil {
		return 400
	}
	defer rs.Body.Close()

	return rs.StatusCode
}

func (c *Client) createParentCollection(itemPath string) (err error) {
	parentPath := path.Dir(itemPath)
	if parentPath == "." || parentPath == "/" {
		return nil
	}

	return c.MkdirAll(parentPath, 0755)
}
<|endoftext|>


<commit_before>package requests

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

	"github.com/pkg/errors"
)

// Client is a HTTP Client.
type Client struct {
}

// Header is a HTTP header.
type Header struct {
	Key    string
	Values []string
}

// Get issues a GET to the specified URL.
func (c *Client) Get(url string, options ...func(*Request) error) (*Response, error) {
	req := Request{
		Method: "GET",
		URL:    url,
	}
	if err := c.applyOptions(&req, options...); err != nil {
		return nil, err
	}
	return c.do(&req)
}

func (c *Client) do(request *Request) (*Response, error) {
	req, err := http.NewRequest(request.Method, request.URL, request.Body)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	r := Response{
		Request: request,
		Status: Status{
			Code:   resp.StatusCode,
			Reason: resp.Status[4:],
		},
		Headers: headers(resp.Header),
		Body: Body{
			ReadCloser: resp.Body,
		},
	}
	return &r, nil
}

func (c *Client) applyOptions(req *Request, options ...func(*Request) error) error {
	for _, opt := range options {
		if err := opt(req); err != nil {
			return err
		}
	}
	return nil
}

// Request is a HTTP request.
type Request struct {
	Method  string
	URL     string
	Headers []Header
	Body    io.Reader
}

// Response is a HTTP response.
type Response struct {
	*Request
	Status
	Headers []Header
	Body
}

// Header returns the canonicalised version of a response header as a string
// If there is no key present in the response the empty string is returned.
// If multiple headers are present, they are canonicalised into as single string
// by joining them with a comma. See RFC 2616 § 4.2.
func (r *Response) Header(key string) string {
	var vals []string
	for _, h := range r.Headers {

		// TODO(dfc) § 4.2 states that not all header values can be combined, but equally those
		// that cannot be combined with a comma may not be present more than once in a
		// header block.
		if h.Key == key {
			vals = append(vals, h.Values...)
		}
	}
	return strings.Join(vals, ",")
}

type Body struct {
	io.ReadCloser

	json *json.Decoder
}

// JSON decodes the next JSON encoded object in the body to v.
func (b *Body) JSON(v interface{}) error {
	if b.json == nil {
		b.json = json.NewDecoder(b)
	}
	return b.json.Decode(v)
}

// return the body as a string, or bytes, or something

func headers(h map[string][]string) []Header {
	headers := make([]Header, 0, len(h))
	for k, v := range h {
		headers = append(headers, Header{
			Key:    k,
			Values: v,
		})
	}
	return headers
}
<commit_msg>requests: do not follow redirects by default<commit_after>package requests

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

	"github.com/pkg/errors"
)

// Client is a HTTP Client.
type Client struct {
	client *http.Client
}

// Header is a HTTP header.
type Header struct {
	Key    string
	Values []string
}

// Get issues a GET to the specified URL.
func (c *Client) Get(url string, options ...func(*Request) error) (*Response, error) {
	req := Request{
		Method: "GET",
		URL:    url,
	}
	if err := c.applyOptions(&req, options...); err != nil {
		return nil, err
	}
	return c.do(&req)
}

func (c *Client) do(request *Request) (*Response, error) {
	req, err := http.NewRequest(request.Method, request.URL, request.Body)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	if c.client == nil {
		c.client = &*http.DefaultClient
		c.client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		}
	}

	resp, err := c.client.Do(req)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	r := Response{
		Request: request,
		Status: Status{
			Code:   resp.StatusCode,
			Reason: resp.Status[4:],
		},
		Headers: headers(resp.Header),
		Body: Body{
			ReadCloser: resp.Body,
		},
	}
	return &r, nil
}

func (c *Client) applyOptions(req *Request, options ...func(*Request) error) error {
	for _, opt := range options {
		if err := opt(req); err != nil {
			return err
		}
	}
	return nil
}

// Request is a HTTP request.
type Request struct {
	Method  string
	URL     string
	Headers []Header
	Body    io.Reader
}

// Response is a HTTP response.
type Response struct {
	*Request
	Status
	Headers []Header
	Body
}

// Header returns the canonicalised version of a response header as a string
// If there is no key present in the response the empty string is returned.
// If multiple headers are present, they are canonicalised into as single string
// by joining them with a comma. See RFC 2616 § 4.2.
func (r *Response) Header(key string) string {
	var vals []string
	for _, h := range r.Headers {

		// TODO(dfc) § 4.2 states that not all header values can be combined, but equally those
		// that cannot be combined with a comma may not be present more than once in a
		// header block.
		if h.Key == key {
			vals = append(vals, h.Values...)
		}
	}
	return strings.Join(vals, ",")
}

type Body struct {
	io.ReadCloser

	json *json.Decoder
}

// JSON decodes the next JSON encoded object in the body to v.
func (b *Body) JSON(v interface{}) error {
	if b.json == nil {
		b.json = json.NewDecoder(b)
	}
	return b.json.Decode(v)
}

// return the body as a string, or bytes, or something

func headers(h map[string][]string) []Header {
	headers := make([]Header, 0, len(h))
	for k, v := range h {
		headers = append(headers, Header{
			Key:    k,
			Values: v,
		})
	}
	return headers
}
<|endoftext|>


<commit_before>// Copyright 2014 Vadim Kravcenko
//
// 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.

// Gopencils is a Golang REST Client with which you can easily consume REST API's. Supported Response formats: JSON
package gopencils

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

var queryString map[string]string

// Resource is basically an url relative to given API Baseurl.
type Resource struct {
	Api         *ApiStruct
	Url         string
	id          string
	Querystring map[string]string
	Payload     io.Reader
	Headers     http.Header
	Response    interface{}
	Raw         *http.Response
}

// Creates a new Resource.
func (r *Resource) Res(options ...interface{}) *Resource {
	if len(options) > 0 {
		var url string
		if len(r.Url) > 0 {
			url = r.Url + "/" + options[0].(string)
		} else {
			url = options[0].(string)
		}

		r.Api.Methods[url] = &Resource{Url: url, Api: r.Api, Headers: http.Header{}}

		if len(options) > 1 {
			r.Api.Methods[url].Response = options[1]
		}

		return r.Api.Methods[url]
	}
	return r
}

// Same as Res() Method, but returns a Resource with url resource/:id
func (r *Resource) Id(options ...interface{}) *Resource {
	if len(options) > 0 {
		id := ""
		switch v := options[0].(type) {
		default:
			id = v.(string)
		case int:
			id = strconv.Itoa(v)
		}
		url := r.Url + "/" + id
		r.Api.Methods[url] = &Resource{id: id, Url: url, Api: r.Api, Headers: http.Header{}}

		if len(options) > 1 {
			r.Api.Methods[url].Response = options[1]
		} else {
			r.Api.Methods[url].Response = &r.Api.Methods[r.Url].Response
		}
		return r.Api.Methods[url]
	}
	return r
}

// Sets Querystring for current Resource
func (r *Resource) SetQuery(querystring map[string]string) *Resource {
	r.Querystring = querystring
	return r
}

// Performs a GET request on given Resource
// Accepts map[string]string as parameter, will be used as querystring.
func (r *Resource) Get(options ...interface{}) (*Resource, error) {
	if len(options) > 0 {
		r.Querystring = options[0].(map[string]string)
	}
	return r.do("GET")
}

// Performs a HEAD request on given Resource
// Accepts map[string]string as parameter, will be used as querystring.
func (r *Resource) Head(options ...interface{}) (*Resource, error) {
	if len(options) > 0 {
		r.Querystring = options[0].(map[string]string)
	}
	return r.do("HEAD")
}

// Performs a PUT request on given Resource.
// Accepts interface{} as parameter, will be used as payload.
func (r *Resource) Put(options ...interface{}) (*Resource, error) {
	if len(options) > 0 {
		r.Payload = r.SetPayload(options[0])
	}
	return r.do("PUT")
}

// Performs a POST request on given Resource.
// Accepts interface{} as parameter, will be used as payload.
func (r *Resource) Post(options ...interface{}) (*Resource, error) {
	if len(options) > 0 {
		r.Payload = r.SetPayload(options[0])
	}
	return r.do("POST")
}

// Performs a Delete request on given Resource.
// Accepts map[string]string as parameter, will be used as querystring.
func (r *Resource) Delete(options ...interface{}) (*Resource, error) {
	if len(options) > 0 {
		r.Querystring = options[0].(map[string]string)
	}
	return r.do("DELETE")
}

// Performs a Delete request on given Resource.
// Accepts map[string]string as parameter, will be used as querystring.
func (r *Resource) Options(options ...interface{}) (*Resource, error) {
	if len(options) > 0 {
		r.Querystring = options[0].(map[string]string)
	}
	return r.do("OPTIONS")
}

// Performs a PATCH request on given Resource.
// Accepts interface{} as parameter, will be used as payload.
func (r *Resource) Patch(options ...interface{}) (*Resource, error) {
	if len(options) > 0 {
		r.Payload = r.SetPayload(options[0])
	}
	return r.do("PATCH")
}

// Main method, opens the connection, sets basic auth, applies headers,
// parses response json.
func (r *Resource) do(method string) (*Resource, error) {
	url := r.parseUrl()
	req, err := http.NewRequest(method, url, r.Payload)
	if err != nil {
		return r, err
	}

	if r.Api.BasicAuth != nil {
		req.SetBasicAuth(r.Api.BasicAuth.Username, r.Api.BasicAuth.Password)
	}

	if r.Headers != nil {
		for k, _ := range r.Headers {
			req.Header.Set(k, r.Headers.Get(k))
		}
	}

	resp, err := r.Api.Client.Do(req)
	if err != nil {
		return r, err
	}

	r.Raw = resp

	defer resp.Body.Close()

	contents, _ := ioutil.ReadAll(resp.Body)
	err = json.Unmarshal(contents, r.Api.Methods[r.Url].Response)
	if err != nil {
		return r, err
	}

	return r, nil
}

// Sets Payload for current Resource
func (r *Resource) SetPayload(args interface{}) io.Reader {
	var b []byte
	b, _ = json.Marshal(args)
	r.SetHeader("Content-Type", "application/json")
	return bytes.NewBuffer(b)
}

// Sets Headers
func (r *Resource) SetHeader(key string, value string) {
	r.Headers.Add(key, value)
}

// Overwrites the client that will be used for requests.
// For example if you want to use your own client with OAuth2
func (r *Resource) SetClient(c *http.Client) {
	r.Api.Client = c
}

// Parses url and all Query parameters
func (r Resource) parseUrl() string {
	url := r.Api.Base + r.Url
	separator := "?"
	for k, v := range r.Querystring {
		url += fmt.Sprintf("%s%s=%s", separator, k, v)
		separator = "&"
	}
	return url
}
<commit_msg>replace outil.ReadAll with json.NewDecoder<commit_after>// Copyright 2014 Vadim Kravcenko
//
// 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.

// Gopencils is a Golang REST Client with which you can easily consume REST API's. Supported Response formats: JSON
package gopencils

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strconv"
)

var queryString map[string]string

// Resource is basically an url relative to given API Baseurl.
type Resource struct {
	Api         *ApiStruct
	Url         string
	id          string
	Querystring map[string]string
	Payload     io.Reader
	Headers     http.Header
	Response    interface{}
	Raw         *http.Response
}

// Creates a new Resource.
func (r *Resource) Res(options ...interface{}) *Resource {
	if len(options) > 0 {
		var url string
		if len(r.Url) > 0 {
			url = r.Url + "/" + options[0].(string)
		} else {
			url = options[0].(string)
		}

		r.Api.Methods[url] = &Resource{Url: url, Api: r.Api, Headers: http.Header{}}

		if len(options) > 1 {
			r.Api.Methods[url].Response = options[1]
		}

		return r.Api.Methods[url]
	}
	return r
}

// Same as Res() Method, but returns a Resource with url resource/:id
func (r *Resource) Id(options ...interface{}) *Resource {
	if len(options) > 0 {
		id := ""
		switch v := options[0].(type) {
		default:
			id = v.(string)
		case int:
			id = strconv.Itoa(v)
		}
		url := r.Url + "/" + id
		r.Api.Methods[url] = &Resource{id: id, Url: url, Api: r.Api, Headers: http.Header{}}

		if len(options) > 1 {
			r.Api.Methods[url].Response = options[1]
		} else {
			r.Api.Methods[url].Response = &r.Api.Methods[r.Url].Response
		}
		return r.Api.Methods[url]
	}
	return r
}

// Sets Querystring for current Resource
func (r *Resource) SetQuery(querystring map[string]string) *Resource {
	r.Querystring = querystring
	return r
}

// Performs a GET request on given Resource
// Accepts map[string]string as parameter, will be used as querystring.
func (r *Resource) Get(options ...interface{}) (*Resource, error) {
	if len(options) > 0 {
		r.Querystring = options[0].(map[string]string)
	}
	return r.do("GET")
}

// Performs a HEAD request on given Resource
// Accepts map[string]string as parameter, will be used as querystring.
func (r *Resource) Head(options ...interface{}) (*Resource, error) {
	if len(options) > 0 {
		r.Querystring = options[0].(map[string]string)
	}
	return r.do("HEAD")
}

// Performs a PUT request on given Resource.
// Accepts interface{} as parameter, will be used as payload.
func (r *Resource) Put(options ...interface{}) (*Resource, error) {
	if len(options) > 0 {
		r.Payload = r.SetPayload(options[0])
	}
	return r.do("PUT")
}

// Performs a POST request on given Resource.
// Accepts interface{} as parameter, will be used as payload.
func (r *Resource) Post(options ...interface{}) (*Resource, error) {
	if len(options) > 0 {
		r.Payload = r.SetPayload(options[0])
	}
	return r.do("POST")
}

// Performs a Delete request on given Resource.
// Accepts map[string]string as parameter, will be used as querystring.
func (r *Resource) Delete(options ...interface{}) (*Resource, error) {
	if len(options) > 0 {
		r.Querystring = options[0].(map[string]string)
	}
	return r.do("DELETE")
}

// Performs a Delete request on given Resource.
// Accepts map[string]string as parameter, will be used as querystring.
func (r *Resource) Options(options ...interface{}) (*Resource, error) {
	if len(options) > 0 {
		r.Querystring = options[0].(map[string]string)
	}
	return r.do("OPTIONS")
}

// Performs a PATCH request on given Resource.
// Accepts interface{} as parameter, will be used as payload.
func (r *Resource) Patch(options ...interface{}) (*Resource, error) {
	if len(options) > 0 {
		r.Payload = r.SetPayload(options[0])
	}
	return r.do("PATCH")
}

// Main method, opens the connection, sets basic auth, applies headers,
// parses response json.
func (r *Resource) do(method string) (*Resource, error) {
	url := r.parseUrl()
	req, err := http.NewRequest(method, url, r.Payload)
	if err != nil {
		return r, err
	}

	if r.Api.BasicAuth != nil {
		req.SetBasicAuth(r.Api.BasicAuth.Username, r.Api.BasicAuth.Password)
	}

	if r.Headers != nil {
		for k, _ := range r.Headers {
			req.Header.Set(k, r.Headers.Get(k))
		}
	}

	resp, err := r.Api.Client.Do(req)
	if err != nil {
		return r, err
	}

	r.Raw = resp

	defer resp.Body.Close()

	err = json.NewDecoder(resp.Body).Decode(r.Api.Methods[r.Url].Response)
	if err != nil {
		return r, err
	}

	return r, nil
}

// Sets Payload for current Resource
func (r *Resource) SetPayload(args interface{}) io.Reader {
	var b []byte
	b, _ = json.Marshal(args)
	r.SetHeader("Content-Type", "application/json")
	return bytes.NewBuffer(b)
}

// Sets Headers
func (r *Resource) SetHeader(key string, value string) {
	r.Headers.Add(key, value)
}

// Overwrites the client that will be used for requests.
// For example if you want to use your own client with OAuth2
func (r *Resource) SetClient(c *http.Client) {
	r.Api.Client = c
}

// Parses url and all Query parameters
func (r Resource) parseUrl() string {
	url := r.Api.Base + r.Url
	separator := "?"
	for k, v := range r.Querystring {
		url += fmt.Sprintf("%s%s=%s", separator, k, v)
		separator = "&"
	}
	return url
}
<|endoftext|>


<commit_before>package erede

import (
	"encoding/xml"
)

const (
	RStatSuccess                       = 1
	RStatSocketWriteError              = 2
	RStatTimeout                       = 3
	RStatEditError                     = 5
	RStatCommsError                    = 6
	RStatUnauthorized                  = 7
	RStatCurrencyError                 = 9
	RStatAuthError                     = 10
	RStatInvalidAuthCode               = 12
	RStatTypeFieldMissing              = 13
	RStatDBServerError                 = 14
	RStatInvalidType                   = 15
	RStatCannotFulfillTransaction      = 19
	RStatDuplicateTransactionReference = 20
	RStatInvalidCardType               = 21
	RStatInvalidReference              = 22
	RStatExpiryDateInvalid             = 23
	RStatCardExpired                   = 24
	RStatCardNumberInvalid             = 25
	RStatCardNumberWrongLength         = 26
	RStatIssueNumberError              = 27
	RStatStartDateError                = 28
	RStatCardNotValidYet               = 29
	RStatStartDateAfterExpiryDate      = 30
	//TODO: fill more errors
	RStatCurrencyNotSupportedByCard = 59
	RStatInvalidXML                 = 60
	//TODO: fill more errors
	// 3DS
	RStat3DSPayerVerificationRequired = 150
	RStat3DSInvalidTransactionType    = 151
	RStat3DSManualAuthNotSupported    = 152
	RStat3DSVerifyElmtMissing         = 153
	RStat3DSInvalidVerifyValue        = 154
	RStat3DSFieldMissing              = 155
	RStat3DSInvalidBrowserDeviceCateg = 156
	RStat3DSMerchantNotEnabled        = 157
	RStat3DSSchemeNotSupported        = 158
	RStat3DSVerificationFailed        = 159
	RStat3DSInvalidIssuerResponse     = 160
	RStat3DSAuthFailedCallCentre      = 161
	RStat3DSCardNotEnrolled           = 162
	//TODO: fill more errors
	RStatPaymentGatewayBusy = 440
	//TODO: fill more errors
	RStat3DSRequired               = 471
	RStatInvalidTransactionType    = 473
	RStatInvalidValueForMerchantID = 480
)

const (
	RRejectedServiceUnauthorized = 51
	//TODO: fill more errors
	RRejectedInvalidVendor   = 57
	RRejectedUnauthorized    = 58
	RRejectedInvalidPassword = 65
	//TODO: fill more errors
)

type TransactionResponse struct {
	XMLName         xml.Name        `xml:"Response"`
	Card            TrRespCard      `xml:"Card"`
	CardTxn         TrRespCardTxn   `xml:"CardTxn"`
	BoletoTxn       TrRespBoletoTxn `xml:"BoletoTxn"`
	Acquirer        string          `xml:"acquirer"`
	AuthHostRef     int             `xml:"auth_host_reference"`
	GatewayRef      string          `xml:"gateway_reference"`
	ExtendedRespMsg string          `xml:"extended_response_message"`
	ExtendedStatus  string          `xml:"extended_status"`
	MerchantRef     string          `xml:"merchant_reference"`
	MID             string          `xml:"mid"`
	Mode            string          `xml:"mode"`
	Reason          string          `xml:"reason"`
	Status          int             `xml:"status"`
	Time            int64           `xml:"time"`
}

type QueryResponse struct {
	XMLName        xml.Name `xml:"Response"`
	QueryTxnResult QueryResponseTxnResult
}

type QueryResponseTxnResult struct {
	BoletoTxn   TrRespBoletoTxn
	CardTxn     TrRespCardTxn
	GatewayRef  string `xml:"gateway_reference"`
	MerchantRef string `xml:"merchantreference"`
	Reason      string `xml:"reason"`
	Status      string `xml:"status"`
}

//TODO: CHECK xid, aav, caavAlgorithm, eci
type TrRespThreeDSecure struct {
	AcsURL        string `xml:"acs_url"`
	PareqMessage  string `xml:"pareq_message"`
	XID           string `xml:"xid"`
	AAV           string `xml:"aav"`
	CAVVAlgorithm string `xml:"cavvAlgorithm"`
	ECI           string `xml:"eci"`
}

type TrRespCard struct {
	AccType string `xml:"card_account_type"`
}

type TrRespCardTxn struct {
	AccType      string              `xml:"card_account_type"`
	Cv2Avs       TrRespCardTxnCv2AVS `xml:"Cv2Avs"`
	AuthCode     string              `xml:"authcode"`
	CardScheme   string              `xml:"card_scheme"`
	Country      string              `xml:"country"`
	Issuer       string              `xml:"issuer"`
	ThreeDSecure TrRespThreeDSecure  `xml:"ThreeDSecure"`
}

type TrRespBoletoTxn struct {
	Method        string `xml:"method"`
	Language      string `xml:"language"`
	Title         string `xml:"title"`
	Country       string `xml:"country"`
	URL           string `xml:"url"`
	TxnStatus     string `xml:"txn_status"`
	BarcodeNumber string `xml:"barcode_number"`
	// query:
	Amount            float64 `xml:"amount"`
	BillingCity       string  `xml:"billing_city"`
	BillingCountry    string  `xml:"billing_country"`
	BillingPostcode   string  `xml:"billing_postcode"`
	BillingStreet1    string  `xml:"billing_street1"`
	BoletoNumber      string  `xml:"boleto_number"`
	BoletoURL         string  `xml:"boleto_url"`
	CustomerEmail     string  `xml:"customer_email"`
	CustomerIP        string  `xml:"customer_ip"`
	CustomerTelephone string  `xml:"customer_telephone"`
	ExpiryDate        string  `xml:"expiry_date"`
	FisrtName         string  `xml:"first_name"`
	LastName          string  `xml:"last_name"`
	Instructions      string  `xml:"instructions"`
	InterestPerDay    float64 `xml:"interest_per_day"`
	MerchantID        string  `xml:"merchant_id"`
	OrderID           string  `xml:"order_id"`
	OverdueFine       float64 `xml:"overdue_fine"`
	PaymentStatus     string  `xml:"payment_status"`
	ProcessorID       string  `xml:"processor_id"`
	TransactionID     string  `xml:"transaction_id"`
}

type TrRespCardTxnCv2AVS struct {
	Status string `xml:"cv2avs_status"`
	Policy int    `xml:"policy"`
}

// type TransactionResponse2 struct {
// 	XMLName         xml.Name           `xml:"Response"`
// 	QueryTxnResult  RespQueryTxnResult `xml:"QueryTxnResult"`
// 	ExtendedRespMsg string             `xml:"extended_response_message"`
// 	ExtendedStatus  string             `xml:"extended_status"`
// 	Mode            string             `xml:"mode"`
// 	Reason          string             `xml:"reason"`
// 	Status          int                `xml:"status"`
// 	Time            int64              `xml:"time"`
// }
//
// type RespQueryTxnResult struct {
// 	Card                 TrRespCard2 `xml:"Card"`
// 	Acquirer             string      `xml:"acquirer"`
// 	AuthHostRef          int         `xml:"auth_host_reference"`
// 	AuthCode             string      `xml:"authcode"`
// 	GatewayRef           string      `xml:"gateway_reference"`
// 	Environment          string      `xml:"environment"`
// 	FulfillDate          string      `xml:"fulfill_date"`
// 	FulfillTimestamp     int64       `xml:"fulfill_timestamp"`
// 	MerchantRef          int         `xml:"merchant_reference"`
// 	Reason               string      `xml:"reason"`
// 	Sent                 string      `xml:"sent"`
// 	Status               int         `xml:"status"`
// 	TransactionDate      string      `xml:"transaction_date"`
// 	TransactionTimestamp int64       `xml:"transaction_timestamp"`
// }

type TrRespCard2 struct {
	Category   string `xml:"card_category"`
	Country    string `xml:"country"`
	ExpiryDate string `xml:"expirydate"`
	Issuer     string `xml:"issuer"`
	PAN        string `xml:"pan"`
	Scheme     string `xml:"scheme"`
}

func GetRespRejectionDescription(code int) string {
	switch code {
	case 51:
		return "Produto ou serviço não habilitado para o estabelecimento. Entre em contato com a Rede."
	case 53:
		return "Transação não permitida para o emissor. Entre em contato com a Rede."
	case 56:
		return "Erro nos dados informados. Tente novamente."
	case 57:
		return "Estabelecimento inválido."
	case 58:
		return "Transação não autorizada. Contate o emissor."
	case 65:
		return "Senha inválida. Tente novamente."
	case 69:
		return "Transação não permitida para este produto ou serviço."
	case 72:
		return "Contate o emissor."
	case 74:
		return "Falha na comunicação. Tente novamente."
	case 79:
		return "Cartão expirado. Transação não pode ser resubmetida. Contate o emissor."
	case 80:
		return "Transação não autorizada. Contate o emissor. (Saldo Insuficiente)"
	case 81:
		return "Produto ou serviço não habilitado para o emissor (AVS)."
	case 82:
		return "Transação não autorizada para cartão de débito."
	case 83:
		return "Transação não autorizada. Problemas com cartão. Contate o emissor."
	case 84:
		return "Transação não autorizada. Transação não pode ser resubmetida. Contate o emissor."
	}
	return "ERRO!"
}

func GetGenRespDescription(code int) string {
	switch code {
	case 1:
		return "Sucesso."
	case 2:
		return "A comunicação foi interrompida"
	case 3:
		return "Ocorreu um timeout enquanto os detalhes da transação eram lidos"
	case 5:
		return "Um campo foi especificado duas vezes. Foram enviados dados excessivos ou inválidos, um fulfill de pré-autorização falhou ou um campo foi omitido. O argumento oferecerá uma melhor indicação do que exatamente deu errado"
	case 6:
		return "Erro no link de comunicação; reenvie"
	case 9:
		return "A moeda especificada não existe"
	case 10:
		return "O vTID ou senha são incorretos"
	case 12:
		return "O código de autorização fornecido é inválido"
	case 13:
		return "Não foi inserido um tipo de transação"
	case 14:
		return "Os detalhes da transação não foram enviados ao nosso banco de dados"
	case 15:
		return "Foi especificado um tipo de transação inválido"
	case 19:
		return "Houve uma tentativa de fulfill de uma transação que não pode ser confirmada ou que já foi confirmada"
	case 20:
		return "Já foi enviada uma transação bem-sucedida que utiliza este vTID e número de referência"
	case 21:
		return "Este terminal não aceita transações para este tipo de cartão"
	case 22:
		return "Os números de referência devem ter 16 dígitos para transações de fulfill, ou de 6 a 30 dígitos para todas as outras"
	case 23:
		return "Expiry date do cartão inválido."
	case 24:
		return "A data de validade fornecida é anterior à data atual"
	case 25, 26:
		return "Número do cartão inválido"
	}
	return ""
}
<commit_msg>added query response vars<commit_after>package erede

import (
	"encoding/xml"
)

const (
	RStatSuccess                       = 1
	RStatSocketWriteError              = 2
	RStatTimeout                       = 3
	RStatEditError                     = 5
	RStatCommsError                    = 6
	RStatUnauthorized                  = 7
	RStatCurrencyError                 = 9
	RStatAuthError                     = 10
	RStatInvalidAuthCode               = 12
	RStatTypeFieldMissing              = 13
	RStatDBServerError                 = 14
	RStatInvalidType                   = 15
	RStatCannotFulfillTransaction      = 19
	RStatDuplicateTransactionReference = 20
	RStatInvalidCardType               = 21
	RStatInvalidReference              = 22
	RStatExpiryDateInvalid             = 23
	RStatCardExpired                   = 24
	RStatCardNumberInvalid             = 25
	RStatCardNumberWrongLength         = 26
	RStatIssueNumberError              = 27
	RStatStartDateError                = 28
	RStatCardNotValidYet               = 29
	RStatStartDateAfterExpiryDate      = 30
	//TODO: fill more errors
	RStatCurrencyNotSupportedByCard = 59
	RStatInvalidXML                 = 60
	//TODO: fill more errors
	// 3DS
	RStat3DSPayerVerificationRequired = 150
	RStat3DSInvalidTransactionType    = 151
	RStat3DSManualAuthNotSupported    = 152
	RStat3DSVerifyElmtMissing         = 153
	RStat3DSInvalidVerifyValue        = 154
	RStat3DSFieldMissing              = 155
	RStat3DSInvalidBrowserDeviceCateg = 156
	RStat3DSMerchantNotEnabled        = 157
	RStat3DSSchemeNotSupported        = 158
	RStat3DSVerificationFailed        = 159
	RStat3DSInvalidIssuerResponse     = 160
	RStat3DSAuthFailedCallCentre      = 161
	RStat3DSCardNotEnrolled           = 162
	//TODO: fill more errors
	RStatPaymentGatewayBusy = 440
	//TODO: fill more errors
	RStat3DSRequired               = 471
	RStatInvalidTransactionType    = 473
	RStatInvalidValueForMerchantID = 480
)

const (
	RRejectedServiceUnauthorized = 51
	//TODO: fill more errors
	RRejectedInvalidVendor   = 57
	RRejectedUnauthorized    = 58
	RRejectedInvalidPassword = 65
	//TODO: fill more errors
)

type TransactionResponse struct {
	XMLName         xml.Name        `xml:"Response"`
	Card            TrRespCard      `xml:"Card"`
	CardTxn         TrRespCardTxn   `xml:"CardTxn"`
	BoletoTxn       TrRespBoletoTxn `xml:"BoletoTxn"`
	Acquirer        string          `xml:"acquirer"`
	AuthHostRef     int             `xml:"auth_host_reference"`
	GatewayRef      string          `xml:"gateway_reference"`
	ExtendedRespMsg string          `xml:"extended_response_message"`
	ExtendedStatus  string          `xml:"extended_status"`
	MerchantRef     string          `xml:"merchant_reference"`
	MID             string          `xml:"mid"`
	Mode            string          `xml:"mode"`
	Reason          string          `xml:"reason"`
	Status          int             `xml:"status"`
	Time            int64           `xml:"time"`
}

type QueryResponse struct {
	XMLName        xml.Name `xml:"Response"`
	QueryTxnResult QueryResponseTxnResult
	Mode           string `xml:"mode"`
	Reason         string `xml:"reason"`
	Status         int    `xml:"status"`
	Time           int64  `xml:"time"`
}

type QueryResponseTxnResult struct {
	BoletoTxn   TrRespBoletoTxn
	CardTxn     TrRespCardTxn
	GatewayRef  string `xml:"gateway_reference"`
	MerchantRef string `xml:"merchantreference"`
	Reason      string `xml:"reason"`
	Status      string `xml:"status"`
}

//TODO: CHECK xid, aav, caavAlgorithm, eci
type TrRespThreeDSecure struct {
	AcsURL        string `xml:"acs_url"`
	PareqMessage  string `xml:"pareq_message"`
	XID           string `xml:"xid"`
	AAV           string `xml:"aav"`
	CAVVAlgorithm string `xml:"cavvAlgorithm"`
	ECI           string `xml:"eci"`
}

type TrRespCard struct {
	AccType string `xml:"card_account_type"`
}

type TrRespCardTxn struct {
	AccType      string              `xml:"card_account_type"`
	Cv2Avs       TrRespCardTxnCv2AVS `xml:"Cv2Avs"`
	AuthCode     string              `xml:"authcode"`
	CardScheme   string              `xml:"card_scheme"`
	Country      string              `xml:"country"`
	Issuer       string              `xml:"issuer"`
	ThreeDSecure TrRespThreeDSecure  `xml:"ThreeDSecure"`
}

type TrRespBoletoTxn struct {
	Method        string `xml:"method"`
	Language      string `xml:"language"`
	Title         string `xml:"title"`
	Country       string `xml:"country"`
	URL           string `xml:"url"`
	TxnStatus     string `xml:"txn_status"`
	BarcodeNumber string `xml:"barcode_number"`
	// query:
	Amount            float64 `xml:"amount"`
	BillingCity       string  `xml:"billing_city"`
	BillingCountry    string  `xml:"billing_country"`
	BillingPostcode   string  `xml:"billing_postcode"`
	BillingStreet1    string  `xml:"billing_street1"`
	BoletoNumber      string  `xml:"boleto_number"`
	BoletoURL         string  `xml:"boleto_url"`
	CustomerEmail     string  `xml:"customer_email"`
	CustomerIP        string  `xml:"customer_ip"`
	CustomerTelephone string  `xml:"customer_telephone"`
	ExpiryDate        string  `xml:"expiry_date"`
	FisrtName         string  `xml:"first_name"`
	LastName          string  `xml:"last_name"`
	Instructions      string  `xml:"instructions"`
	InterestPerDay    float64 `xml:"interest_per_day"`
	MerchantID        string  `xml:"merchant_id"`
	OrderID           string  `xml:"order_id"`
	OverdueFine       float64 `xml:"overdue_fine"`
	PaymentStatus     string  `xml:"payment_status"`
	ProcessorID       string  `xml:"processor_id"`
	TransactionID     string  `xml:"transaction_id"`
}

type TrRespCardTxnCv2AVS struct {
	Status string `xml:"cv2avs_status"`
	Policy int    `xml:"policy"`
}

// type TransactionResponse2 struct {
// 	XMLName         xml.Name           `xml:"Response"`
// 	QueryTxnResult  RespQueryTxnResult `xml:"QueryTxnResult"`
// 	ExtendedRespMsg string             `xml:"extended_response_message"`
// 	ExtendedStatus  string             `xml:"extended_status"`
// 	Mode            string             `xml:"mode"`
// 	Reason          string             `xml:"reason"`
// 	Status          int                `xml:"status"`
// 	Time            int64              `xml:"time"`
// }
//
// type RespQueryTxnResult struct {
// 	Card                 TrRespCard2 `xml:"Card"`
// 	Acquirer             string      `xml:"acquirer"`
// 	AuthHostRef          int         `xml:"auth_host_reference"`
// 	AuthCode             string      `xml:"authcode"`
// 	GatewayRef           string      `xml:"gateway_reference"`
// 	Environment          string      `xml:"environment"`
// 	FulfillDate          string      `xml:"fulfill_date"`
// 	FulfillTimestamp     int64       `xml:"fulfill_timestamp"`
// 	MerchantRef          int         `xml:"merchant_reference"`
// 	Reason               string      `xml:"reason"`
// 	Sent                 string      `xml:"sent"`
// 	Status               int         `xml:"status"`
// 	TransactionDate      string      `xml:"transaction_date"`
// 	TransactionTimestamp int64       `xml:"transaction_timestamp"`
// }

type TrRespCard2 struct {
	Category   string `xml:"card_category"`
	Country    string `xml:"country"`
	ExpiryDate string `xml:"expirydate"`
	Issuer     string `xml:"issuer"`
	PAN        string `xml:"pan"`
	Scheme     string `xml:"scheme"`
}

func GetRespRejectionDescription(code int) string {
	switch code {
	case 51:
		return "Produto ou serviço não habilitado para o estabelecimento. Entre em contato com a Rede."
	case 53:
		return "Transação não permitida para o emissor. Entre em contato com a Rede."
	case 56:
		return "Erro nos dados informados. Tente novamente."
	case 57:
		return "Estabelecimento inválido."
	case 58:
		return "Transação não autorizada. Contate o emissor."
	case 65:
		return "Senha inválida. Tente novamente."
	case 69:
		return "Transação não permitida para este produto ou serviço."
	case 72:
		return "Contate o emissor."
	case 74:
		return "Falha na comunicação. Tente novamente."
	case 79:
		return "Cartão expirado. Transação não pode ser resubmetida. Contate o emissor."
	case 80:
		return "Transação não autorizada. Contate o emissor. (Saldo Insuficiente)"
	case 81:
		return "Produto ou serviço não habilitado para o emissor (AVS)."
	case 82:
		return "Transação não autorizada para cartão de débito."
	case 83:
		return "Transação não autorizada. Problemas com cartão. Contate o emissor."
	case 84:
		return "Transação não autorizada. Transação não pode ser resubmetida. Contate o emissor."
	}
	return "ERRO!"
}

func GetGenRespDescription(code int) string {
	switch code {
	case 1:
		return "Sucesso."
	case 2:
		return "A comunicação foi interrompida"
	case 3:
		return "Ocorreu um timeout enquanto os detalhes da transação eram lidos"
	case 5:
		return "Um campo foi especificado duas vezes. Foram enviados dados excessivos ou inválidos, um fulfill de pré-autorização falhou ou um campo foi omitido. O argumento oferecerá uma melhor indicação do que exatamente deu errado"
	case 6:
		return "Erro no link de comunicação; reenvie"
	case 9:
		return "A moeda especificada não existe"
	case 10:
		return "O vTID ou senha são incorretos"
	case 12:
		return "O código de autorização fornecido é inválido"
	case 13:
		return "Não foi inserido um tipo de transação"
	case 14:
		return "Os detalhes da transação não foram enviados ao nosso banco de dados"
	case 15:
		return "Foi especificado um tipo de transação inválido"
	case 19:
		return "Houve uma tentativa de fulfill de uma transação que não pode ser confirmada ou que já foi confirmada"
	case 20:
		return "Já foi enviada uma transação bem-sucedida que utiliza este vTID e número de referência"
	case 21:
		return "Este terminal não aceita transações para este tipo de cartão"
	case 22:
		return "Os números de referência devem ter 16 dígitos para transações de fulfill, ou de 6 a 30 dígitos para todas as outras"
	case 23:
		return "Expiry date do cartão inválido."
	case 24:
		return "A data de validade fornecida é anterior à data atual"
	case 25, 26:
		return "Número do cartão inválido"
	}
	return ""
}
<|endoftext|>


<commit_before>package jsonapi

import (
	"errors"
	"fmt"
	"reflect"
	"strings"
	"time"
)

func MarshalJsonApiManyPayload(models Models) (*JsonApiManyPayload, error) {
	d := models.GetData()
	data := make([]*JsonApiNode, 0, len(d))

	incl := make([]*JsonApiNode, 0)

	for _, model := range d {
		node, included, err := visitModelNode(model, true)
		if err != nil {
			return nil, err
		}
		data = append(data, node)
		incl = append(incl, included...)
	}

	uniqueIncluded := make(map[string]*JsonApiNode)

	for i, n := range incl {
		k := fmt.Sprintf("%s,%s", n.Type, n.Id)
		if uniqueIncluded[k] == nil {
			uniqueIncluded[k] = n
		} else {
			incl = deleteNode(incl, i)
		}
	}

	return &JsonApiManyPayload{
		Data:     data,
		Included: incl,
	}, nil
}

func MarshalJsonApiOnePayloadEmbedded(model interface{}) (*JsonApiOnePayload, error) {
	rootNode, _, err := visitModelNode(model, false)
	if err != nil {
		return nil, err
	}

	resp := &JsonApiOnePayload{Data: rootNode}

	return resp, nil

}

func MarshalJsonApiOnePayload(model interface{}) (*JsonApiOnePayload, error) {
	rootNode, included, err := visitModelNode(model, true)
	if err != nil {
		return nil, err
	}

	resp := &JsonApiOnePayload{Data: rootNode}

	uniqueIncluded := make(map[string]*JsonApiNode)

	for i, n := range included {
		k := fmt.Sprintf("%s,%s", n.Type, n.Id)
		if uniqueIncluded[k] == nil {
			uniqueIncluded[k] = n
		} else {
			included = deleteNode(included, i)
		}
	}

	resp.Included = included

	return resp, nil
}

func visitModelNode(model interface{}, sideload bool) (*JsonApiNode, []*JsonApiNode, error) {
	node := new(JsonApiNode)

	var er error
	var included []*JsonApiNode

	modelType := reflect.TypeOf(model).Elem()
	modelValue := reflect.ValueOf(model).Elem()

	var i = 0
	modelType.FieldByNameFunc(func(name string) bool {
		fieldValue := modelValue.Field(i)
		structField := modelType.Field(i)

		i += 1

		tag := structField.Tag.Get("jsonapi")

		if tag == "" {
			return false
		}

		args := strings.Split(tag, ",")

		if len(args) != 2 {
			er = errors.New(fmt.Sprintf("jsonapi tag, on %s, had two few arguments", structField.Name))
			return false
		}

		if len(args) >= 1 && args[0] != "" {
			annotation := args[0]

			if annotation == "primary" {
				node.Id = fmt.Sprintf("%v", fieldValue.Interface())
				node.Type = args[1]
			} else if annotation == "attr" {
				if node.Attributes == nil {
					node.Attributes = make(map[string]interface{})
				}

				if fieldValue.Type() == reflect.TypeOf(time.Time{}) {
					isZeroMethod := fieldValue.MethodByName("IsZero")
					isZero := isZeroMethod.Call(make([]reflect.Value, 0))[0].Interface().(bool)
					if isZero {
						return false
					}

					unix := fieldValue.MethodByName("Unix")
					val := unix.Call(make([]reflect.Value, 0))[0]
					node.Attributes[args[1]] = val.Int()
				} else {
					node.Attributes[args[1]] = fieldValue.Interface()
				}
			} else if annotation == "relation" {

				isSlice := fieldValue.Type().Kind() == reflect.Slice

				if (isSlice && fieldValue.Len() < 1) || (!isSlice && fieldValue.IsNil()) {
					return false
				}

				if node.Relationships == nil {
					node.Relationships = make(map[string]interface{})
				}

				if included == nil {
					included = make([]*JsonApiNode, 0)
				}

				if isSlice {
					relationship, err := visitModelNodeRelationships(args[1], fieldValue, sideload)
					d := relationship[args[1]].Data

					if err == nil {
						if sideload {
							shallowNodes := make([]*JsonApiNode, 0)
							for _, node := range d {
								included = append(included, node)

								shallowNode := *node
								shallowNode.Attributes = nil
								shallowNodes = append(shallowNodes, &shallowNode)

							}

							node.Relationships[args[1]] = &JsonApiRelationshipManyNode{Data: shallowNodes}
						} else {
							node.Relationships[args[1]] = &JsonApiRelationshipManyNode{Data: d}
						}
					} else {
						er = err
						return false
					}
				} else {
					relationship, _, err := visitModelNode(fieldValue.Interface(), sideload)
					if err == nil {
						if sideload {
							shallowNode := *relationship
							shallowNode.Attributes = nil

							included = append(included, relationship)

							node.Relationships[args[1]] = &JsonApiRelationshipOneNode{Data: &shallowNode}
						} else {
							node.Relationships[args[1]] = &JsonApiRelationshipOneNode{Data: relationship}
						}
					} else {
						er = err
						return false
					}
				}

			} else {
				er = errors.New(fmt.Sprintf("Unsupported jsonapi tag annotation, %s", annotation))
				return false
			}
		}

		return false
	})

	if er != nil {
		return nil, nil, er
	}

	return node, included, nil
}

func visitModelNodeRelationships(relationName string, models reflect.Value, sideload bool) (map[string]*JsonApiRelationshipManyNode, error) {
	m := make(map[string]*JsonApiRelationshipManyNode)
	nodes := make([]*JsonApiNode, 0)

	for i := 0; i < models.Len(); i++ {
		node, _, err := visitModelNode(models.Index(i).Interface(), sideload)
		if err != nil {
			return nil, err
		}

		nodes = append(nodes, node)
	}

	m[relationName] = &JsonApiRelationshipManyNode{Data: nodes}

	return m, nil
}

func deleteNode(a []*JsonApiNode, i int) []*JsonApiNode {
	if i < len(a)-1 {
		a = append(a[:i], a[i+1:]...)
	} else {
		a = a[:i]
	}

	return a
}
<commit_msg>dry off<commit_after>package jsonapi

import (
	"errors"
	"fmt"
	"reflect"
	"strings"
	"time"
)

func MarshalJsonApiManyPayload(models Models) (*JsonApiManyPayload, error) {
	d := models.GetData()
	data := make([]*JsonApiNode, 0, len(d))

	incl := make([]*JsonApiNode, 0)

	for _, model := range d {
		node, included, err := visitModelNode(model, true)
		if err != nil {
			return nil, err
		}
		data = append(data, node)
		incl = append(incl, included...)
	}

	uniqueIncluded := make(map[string]*JsonApiNode)

	for i, n := range incl {
		k := fmt.Sprintf("%s,%s", n.Type, n.Id)
		if uniqueIncluded[k] == nil {
			uniqueIncluded[k] = n
		} else {
			incl = deleteNode(incl, i)
		}
	}

	return &JsonApiManyPayload{
		Data:     data,
		Included: incl,
	}, nil
}

func MarshalJsonApiOnePayloadEmbedded(model interface{}) (*JsonApiOnePayload, error) {
	rootNode, _, err := visitModelNode(model, false)
	if err != nil {
		return nil, err
	}

	resp := &JsonApiOnePayload{Data: rootNode}

	return resp, nil

}

func MarshalJsonApiOnePayload(model interface{}) (*JsonApiOnePayload, error) {
	rootNode, included, err := visitModelNode(model, true)
	if err != nil {
		return nil, err
	}

	resp := &JsonApiOnePayload{Data: rootNode}

	uniqueIncluded := make(map[string]*JsonApiNode)

	for i, n := range included {
		k := fmt.Sprintf("%s,%s", n.Type, n.Id)
		if uniqueIncluded[k] == nil {
			uniqueIncluded[k] = n
		} else {
			included = deleteNode(included, i)
		}
	}

	resp.Included = included

	return resp, nil
}

func visitModelNode(model interface{}, sideload bool) (*JsonApiNode, []*JsonApiNode, error) {
	node := new(JsonApiNode)

	var er error
	var included []*JsonApiNode

	modelType := reflect.TypeOf(model).Elem()
	modelValue := reflect.ValueOf(model).Elem()

	var i = 0
	modelType.FieldByNameFunc(func(name string) bool {
		fieldValue := modelValue.Field(i)
		structField := modelType.Field(i)

		i += 1

		tag := structField.Tag.Get("jsonapi")

		if tag == "" {
			return false
		}

		args := strings.Split(tag, ",")

		if len(args) != 2 {
			er = errors.New(fmt.Sprintf("jsonapi tag, on %s, had two few arguments", structField.Name))
			return false
		}

		if len(args) >= 1 && args[0] != "" {
			annotation := args[0]

			if annotation == "primary" {
				node.Id = fmt.Sprintf("%v", fieldValue.Interface())
				node.Type = args[1]
			} else if annotation == "attr" {
				if node.Attributes == nil {
					node.Attributes = make(map[string]interface{})
				}

				if fieldValue.Type() == reflect.TypeOf(time.Time{}) {
					isZeroMethod := fieldValue.MethodByName("IsZero")
					isZero := isZeroMethod.Call(make([]reflect.Value, 0))[0].Interface().(bool)
					if isZero {
						return false
					}

					unix := fieldValue.MethodByName("Unix")
					val := unix.Call(make([]reflect.Value, 0))[0]
					node.Attributes[args[1]] = val.Int()
				} else {
					node.Attributes[args[1]] = fieldValue.Interface()
				}
			} else if annotation == "relation" {

				isSlice := fieldValue.Type().Kind() == reflect.Slice

				if (isSlice && fieldValue.Len() < 1) || (!isSlice && fieldValue.IsNil()) {
					return false
				}

				if node.Relationships == nil {
					node.Relationships = make(map[string]interface{})
				}

				if included == nil {
					included = make([]*JsonApiNode, 0)
				}

				if isSlice {
					relationship, err := visitModelNodeRelationships(args[1], fieldValue, sideload)
					d := relationship[args[1]].Data

					if err == nil {
						if sideload {
							shallowNodes := make([]*JsonApiNode, 0)
							for _, node := range d {
								included = append(included, node)
								shallowNodes = append(shallowNodes, cloneAndRemoveAttributes(node))
							}

							node.Relationships[args[1]] = &JsonApiRelationshipManyNode{Data: shallowNodes}
						} else {
							node.Relationships[args[1]] = &JsonApiRelationshipManyNode{Data: d}
						}
					} else {
						er = err
						return false
					}
				} else {
					relationship, _, err := visitModelNode(fieldValue.Interface(), sideload)
					if err == nil {
						if sideload {
							included = append(included, relationship)
							node.Relationships[args[1]] = &JsonApiRelationshipOneNode{Data: cloneAndRemoveAttributes(relationship)}
						} else {
							node.Relationships[args[1]] = &JsonApiRelationshipOneNode{Data: relationship}
						}
					} else {
						er = err
						return false
					}
				}

			} else {
				er = errors.New(fmt.Sprintf("Unsupported jsonapi tag annotation, %s", annotation))
				return false
			}
		}

		return false
	})

	if er != nil {
		return nil, nil, er
	}

	return node, included, nil
}

func cloneAndRemoveAttributes(node *JsonApiNode) *JsonApiNode {
	n := *node
	n.Attributes = nil

	return &n
}

func visitModelNodeRelationships(relationName string, models reflect.Value, sideload bool) (map[string]*JsonApiRelationshipManyNode, error) {
	m := make(map[string]*JsonApiRelationshipManyNode)
	nodes := make([]*JsonApiNode, 0)

	for i := 0; i < models.Len(); i++ {
		node, _, err := visitModelNode(models.Index(i).Interface(), sideload)
		if err != nil {
			return nil, err
		}

		nodes = append(nodes, node)
	}

	m[relationName] = &JsonApiRelationshipManyNode{Data: nodes}

	return m, nil
}

func deleteNode(a []*JsonApiNode, i int) []*JsonApiNode {
	if i < len(a)-1 {
		a = append(a[:i], a[i+1:]...)
	} else {
		a = a[:i]
	}

	return a
}
<|endoftext|>