Commit f3f694b9 authored by Kirill Smelkov's avatar Kirill Smelkov

~gofmt

parent cc6ac54f
This diff is collapsed.
This diff is collapsed.
...@@ -21,142 +21,142 @@ package main ...@@ -21,142 +21,142 @@ package main
// Git-backup | Run git subprocess // Git-backup | Run git subprocess
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
"lab.nexedi.com/kirr/go123/exc" "lab.nexedi.com/kirr/go123/exc"
"lab.nexedi.com/kirr/go123/mem" "lab.nexedi.com/kirr/go123/mem"
) )
// how/whether to redirect stdio of spawned process // how/whether to redirect stdio of spawned process
type StdioRedirect int type StdioRedirect int
const ( const (
PIPE StdioRedirect = iota // connect stdio channel via PIPE to parent (default value) PIPE StdioRedirect = iota // connect stdio channel via PIPE to parent (default value)
DontRedirect DontRedirect
) )
type RunWith struct { type RunWith struct {
stdin string stdin string
stdout StdioRedirect // PIPE | DontRedirect stdout StdioRedirect // PIPE | DontRedirect
stderr StdioRedirect // PIPE | DontRedirect stderr StdioRedirect // PIPE | DontRedirect
raw bool // !raw -> stdout, stderr are stripped raw bool // !raw -> stdout, stderr are stripped
env map[string]string // !nil -> subprocess environment setup from env env map[string]string // !nil -> subprocess environment setup from env
} }
// run `git *argv` -> error, stdout, stderr // run `git *argv` -> error, stdout, stderr
func _git(argv []string, ctx RunWith) (err error, stdout, stderr string) { func _git(argv []string, ctx RunWith) (err error, stdout, stderr string) {
debugf("git %s", strings.Join(argv, " ")) debugf("git %s", strings.Join(argv, " "))
cmd := exec.Command("git", argv...) cmd := exec.Command("git", argv...)
stdoutBuf := bytes.Buffer{} stdoutBuf := bytes.Buffer{}
stderrBuf := bytes.Buffer{} stderrBuf := bytes.Buffer{}
if ctx.stdin != "" { if ctx.stdin != "" {
cmd.Stdin = strings.NewReader(ctx.stdin) cmd.Stdin = strings.NewReader(ctx.stdin)
} }
switch ctx.stdout { switch ctx.stdout {
case PIPE: case PIPE:
cmd.Stdout = &stdoutBuf cmd.Stdout = &stdoutBuf
case DontRedirect: case DontRedirect:
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
default: default:
panic("git: stdout redirect mode invalid") panic("git: stdout redirect mode invalid")
} }
switch ctx.stderr { switch ctx.stderr {
case PIPE: case PIPE:
cmd.Stderr = &stderrBuf cmd.Stderr = &stderrBuf
case DontRedirect: case DontRedirect:
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
default: default:
panic("git: stderr redirect mode invalid") panic("git: stderr redirect mode invalid")
} }
if ctx.env != nil { if ctx.env != nil {
env := []string{} env := []string{}
for k, v := range ctx.env { for k, v := range ctx.env {
env = append(env, k+"="+v) env = append(env, k+"="+v)
} }
cmd.Env = env cmd.Env = env
} }
err = cmd.Run() err = cmd.Run()
stdout = mem.String(stdoutBuf.Bytes()) stdout = mem.String(stdoutBuf.Bytes())
stderr = mem.String(stderrBuf.Bytes()) stderr = mem.String(stderrBuf.Bytes())
if !ctx.raw { if !ctx.raw {
// prettify stdout (e.g. so that 'sha1\n' becomes 'sha1' and can be used directly // prettify stdout (e.g. so that 'sha1\n' becomes 'sha1' and can be used directly
stdout = strings.TrimSpace(stdout) stdout = strings.TrimSpace(stdout)
stderr = strings.TrimSpace(stderr) stderr = strings.TrimSpace(stderr)
} }
return err, stdout, stderr return err, stdout, stderr
} }
// error a git command returned // error a git command returned
type GitError struct { type GitError struct {
GitErrContext GitErrContext
*exec.ExitError *exec.ExitError
} }
type GitErrContext struct { type GitErrContext struct {
argv []string argv []string
stdin string stdin string
stdout string stdout string
stderr string stderr string
} }
func (e *GitError) Error() string { func (e *GitError) Error() string {
msg := e.GitErrContext.Error() msg := e.GitErrContext.Error()
if e.stderr == "" { if e.stderr == "" {
msg += "(failed)\n" msg += "(failed)\n"
} }
return msg return msg
} }
func (e *GitErrContext) Error() string { func (e *GitErrContext) Error() string {
msg := "git " + strings.Join(e.argv, " ") msg := "git " + strings.Join(e.argv, " ")
if e.stdin == "" { if e.stdin == "" {
msg += " </dev/null\n" msg += " </dev/null\n"
} else { } else {
msg += " <<EOF\n" + e.stdin msg += " <<EOF\n" + e.stdin
if !strings.HasSuffix(msg, "\n") { if !strings.HasSuffix(msg, "\n") {
msg += "\n" msg += "\n"
} }
msg += "EOF\n" msg += "EOF\n"
} }
msg += e.stderr msg += e.stderr
if !strings.HasSuffix(msg, "\n") { if !strings.HasSuffix(msg, "\n") {
msg += "\n" msg += "\n"
} }
return msg return msg
} }
// argv -> []string, ctx (for passing argv + RunWith handy - see ggit() for details) // argv -> []string, ctx (for passing argv + RunWith handy - see ggit() for details)
func _gitargv(argv ...interface{}) (argvs []string, ctx RunWith) { func _gitargv(argv ...interface{}) (argvs []string, ctx RunWith) {
ctx_seen := false ctx_seen := false
for _, arg := range argv { for _, arg := range argv {
switch arg := arg.(type) { switch arg := arg.(type) {
case string: case string:
argvs = append(argvs, arg) argvs = append(argvs, arg)
default: default:
argvs = append(argvs, fmt.Sprint(arg)) argvs = append(argvs, fmt.Sprint(arg))
case RunWith: case RunWith:
if ctx_seen { if ctx_seen {
panic("git: multiple RunWith contexts") panic("git: multiple RunWith contexts")
} }
ctx, ctx_seen = arg, true ctx, ctx_seen = arg, true
} }
} }
return argvs, ctx return argvs, ctx
} }
// run `git *argv` -> err, stdout, stderr // run `git *argv` -> err, stdout, stderr
...@@ -167,59 +167,59 @@ func _gitargv(argv ...interface{}) (argvs []string, ctx RunWith) { ...@@ -167,59 +167,59 @@ func _gitargv(argv ...interface{}) (argvs []string, ctx RunWith) {
// //
// NOTE err is concrete *GitError, not error // NOTE err is concrete *GitError, not error
func ggit(argv ...interface{}) (err *GitError, stdout, stderr string) { func ggit(argv ...interface{}) (err *GitError, stdout, stderr string) {
return ggit2(_gitargv(argv...)) return ggit2(_gitargv(argv...))
} }
func ggit2(argv []string, ctx RunWith) (err *GitError, stdout, stderr string) { func ggit2(argv []string, ctx RunWith) (err *GitError, stdout, stderr string) {
e, stdout, stderr := _git(argv, ctx) e, stdout, stderr := _git(argv, ctx)
eexec, _ := e.(*exec.ExitError) eexec, _ := e.(*exec.ExitError)
if e != nil && eexec == nil { if e != nil && eexec == nil {
exc.Raisef("git %s : ", strings.Join(argv, " "), e) exc.Raisef("git %s : ", strings.Join(argv, " "), e)
} }
if eexec != nil { if eexec != nil {
err = &GitError{GitErrContext{argv, ctx.stdin, stdout, stderr}, eexec} err = &GitError{GitErrContext{argv, ctx.stdin, stdout, stderr}, eexec}
} }
return err, stdout, stderr return err, stdout, stderr
} }
// run `git *argv` -> stdout // run `git *argv` -> stdout
// on error - raise exception // on error - raise exception
func xgit(argv ...interface{}) string { func xgit(argv ...interface{}) string {
return xgit2(_gitargv(argv...)) return xgit2(_gitargv(argv...))
} }
func xgit2(argv []string, ctx RunWith) string { func xgit2(argv []string, ctx RunWith) string {
gerr, stdout, _ := ggit2(argv, ctx) gerr, stdout, _ := ggit2(argv, ctx)
if gerr != nil { if gerr != nil {
exc.Raise(gerr) exc.Raise(gerr)
} }
return stdout return stdout
} }
// like xgit(), but automatically parse stdout to Sha1 // like xgit(), but automatically parse stdout to Sha1
func xgitSha1(argv ...interface{}) Sha1 { func xgitSha1(argv ...interface{}) Sha1 {
return xgit2Sha1(_gitargv(argv...)) return xgit2Sha1(_gitargv(argv...))
} }
// error when git output is not valid sha1 // error when git output is not valid sha1
type GitSha1Error struct { type GitSha1Error struct {
GitErrContext GitErrContext
} }
func (e *GitSha1Error) Error() string { func (e *GitSha1Error) Error() string {
msg := e.GitErrContext.Error() msg := e.GitErrContext.Error()
msg += fmt.Sprintf("expected valid sha1 (got %q)\n", e.stdout) msg += fmt.Sprintf("expected valid sha1 (got %q)\n", e.stdout)
return msg return msg
} }
func xgit2Sha1(argv []string, ctx RunWith) Sha1 { func xgit2Sha1(argv []string, ctx RunWith) Sha1 {
gerr, stdout, stderr := ggit2(argv, ctx) gerr, stdout, stderr := ggit2(argv, ctx)
if gerr != nil { if gerr != nil {
exc.Raise(gerr) exc.Raise(gerr)
} }
sha1, err := Sha1Parse(stdout) sha1, err := Sha1Parse(stdout)
if err != nil { if err != nil {
exc.Raise(&GitSha1Error{GitErrContext{argv, ctx.stdin, stdout, stderr}}) exc.Raise(&GitSha1Error{GitErrContext{argv, ctx.stdin, stdout, stderr}})
} }
return sha1 return sha1
} }
This diff is collapsed.
...@@ -25,44 +25,44 @@ package main ...@@ -25,44 +25,44 @@ package main
type Sha1Set map[Sha1]struct{} type Sha1Set map[Sha1]struct{}
func (s Sha1Set) Add(v Sha1) { func (s Sha1Set) Add(v Sha1) {
s[v] = struct{}{} s[v] = struct{}{}
} }
func (s Sha1Set) Contains(v Sha1) bool { func (s Sha1Set) Contains(v Sha1) bool {
_, ok := s[v] _, ok := s[v]
return ok return ok
} }
// all elements of set as slice // all elements of set as slice
func (s Sha1Set) Elements() []Sha1 { func (s Sha1Set) Elements() []Sha1 {
ev := make([]Sha1, len(s)) ev := make([]Sha1, len(s))
i := 0 i := 0
for e := range s { for e := range s {
ev[i] = e ev[i] = e
i++ i++
} }
return ev return ev
} }
// Set<string> // Set<string>
type StrSet map[string]struct{} type StrSet map[string]struct{}
func (s StrSet) Add(v string) { func (s StrSet) Add(v string) {
s[v] = struct{}{} s[v] = struct{}{}
} }
func (s StrSet) Contains(v string) bool { func (s StrSet) Contains(v string) bool {
_, ok := s[v] _, ok := s[v]
return ok return ok
} }
// all elements of set as slice // all elements of set as slice
func (s StrSet) Elements() []string { func (s StrSet) Elements() []string {
ev := make([]string, len(s)) ev := make([]string, len(s))
i := 0 i := 0
for e := range s { for e := range s {
ev[i] = e ev[i] = e
i++ i++
} }
return ev return ev
} }
...@@ -21,13 +21,13 @@ package main ...@@ -21,13 +21,13 @@ package main
// Git-backup | Sha1 type to work with SHA1 oids // Git-backup | Sha1 type to work with SHA1 oids
import ( import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"lab.nexedi.com/kirr/go123/mem" "lab.nexedi.com/kirr/go123/mem"
git "github.com/libgit2/git2go" git "github.com/libgit2/git2go"
) )
const SHA1_RAWSIZE = 20 const SHA1_RAWSIZE = 20
...@@ -39,51 +39,51 @@ const SHA1_RAWSIZE = 20 ...@@ -39,51 +39,51 @@ const SHA1_RAWSIZE = 20
// - slice size = 24 bytes // - slice size = 24 bytes
// -> so it is reasonable to pass Sha1 not by reference // -> so it is reasonable to pass Sha1 not by reference
type Sha1 struct { type Sha1 struct {
sha1 [SHA1_RAWSIZE]byte sha1 [SHA1_RAWSIZE]byte
} }
// fmt.Stringer // fmt.Stringer
var _ fmt.Stringer = Sha1{} var _ fmt.Stringer = Sha1{}
func (sha1 Sha1) String() string { func (sha1 Sha1) String() string {
return hex.EncodeToString(sha1.sha1[:]) return hex.EncodeToString(sha1.sha1[:])
} }
func Sha1Parse(sha1str string) (Sha1, error) { func Sha1Parse(sha1str string) (Sha1, error) {
sha1 := Sha1{} sha1 := Sha1{}
if hex.DecodedLen(len(sha1str)) != SHA1_RAWSIZE { if hex.DecodedLen(len(sha1str)) != SHA1_RAWSIZE {
return Sha1{}, fmt.Errorf("sha1parse: %q invalid", sha1str) return Sha1{}, fmt.Errorf("sha1parse: %q invalid", sha1str)
} }
_, err := hex.Decode(sha1.sha1[:], mem.Bytes(sha1str)) _, err := hex.Decode(sha1.sha1[:], mem.Bytes(sha1str))
if err != nil { if err != nil {
return Sha1{}, fmt.Errorf("sha1parse: %q invalid: %s", sha1str, err) return Sha1{}, fmt.Errorf("sha1parse: %q invalid: %s", sha1str, err)
} }
return sha1, nil return sha1, nil
} }
// fmt.Scanner // fmt.Scanner
var _ fmt.Scanner = (*Sha1)(nil) var _ fmt.Scanner = (*Sha1)(nil)
func (sha1 *Sha1) Scan(s fmt.ScanState, ch rune) error { func (sha1 *Sha1) Scan(s fmt.ScanState, ch rune) error {
switch ch { switch ch {
case 's', 'v': case 's', 'v':
default: default:
return fmt.Errorf("Sha1.Scan: invalid verb %q", ch) return fmt.Errorf("Sha1.Scan: invalid verb %q", ch)
} }
tok, err := s.Token(true, nil) tok, err := s.Token(true, nil)
if err != nil { if err != nil {
return err return err
} }
*sha1, err = Sha1Parse(mem.String(tok)) *sha1, err = Sha1Parse(mem.String(tok))
return err return err
} }
// check whether sha1 is null // check whether sha1 is null
func (sha1 *Sha1) IsNull() bool { func (sha1 *Sha1) IsNull() bool {
return *sha1 == Sha1{} return *sha1 == Sha1{}
} }
// for sorting by Sha1 // for sorting by Sha1
...@@ -95,9 +95,9 @@ func (p BySha1) Less(i, j int) bool { return bytes.Compare(p[i].sha1[:], p[j].sh ...@@ -95,9 +95,9 @@ func (p BySha1) Less(i, j int) bool { return bytes.Compare(p[i].sha1[:], p[j].sh
// interoperability with git2go // interoperability with git2go
func (sha1 *Sha1) AsOid() *git.Oid { func (sha1 *Sha1) AsOid() *git.Oid {
return (*git.Oid)(&sha1.sha1) return (*git.Oid)(&sha1.sha1)
} }
func Sha1FromOid(oid *git.Oid) Sha1 { func Sha1FromOid(oid *git.Oid) Sha1 {
return Sha1{*oid} return Sha1{*oid}
} }
...@@ -21,161 +21,161 @@ package main ...@@ -21,161 +21,161 @@ package main
// Git-backup | Miscellaneous utilities // Git-backup | Miscellaneous utilities
import ( import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"os" "os"
"strings" "strings"
"syscall" "syscall"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
"lab.nexedi.com/kirr/go123/mem" "lab.nexedi.com/kirr/go123/mem"
) )
// strip_prefix("/a/b", "/a/b/c/d/e") -> "c/d/e" (without leading /) // strip_prefix("/a/b", "/a/b/c/d/e") -> "c/d/e" (without leading /)
// path must start with prefix // path must start with prefix
func strip_prefix(prefix, path string) string { func strip_prefix(prefix, path string) string {
if !strings.HasPrefix(path, prefix) { if !strings.HasPrefix(path, prefix) {
panic(fmt.Errorf("strip_prefix: %q has no prefix %q", path, prefix)) panic(fmt.Errorf("strip_prefix: %q has no prefix %q", path, prefix))
} }
path = path[len(prefix):] path = path[len(prefix):]
for strings.HasPrefix(path, "/") { for strings.HasPrefix(path, "/") {
path = path[1:] // strip leading / path = path[1:] // strip leading /
} }
return path return path
} }
// reprefix("/a", "/b", "/a/str") -> "/b/str" // reprefix("/a", "/b", "/a/str") -> "/b/str"
// path must start with prefix_from // path must start with prefix_from
func reprefix(prefix_from, prefix_to, path string) string { func reprefix(prefix_from, prefix_to, path string) string {
path = strip_prefix(prefix_from, path) path = strip_prefix(prefix_from, path)
return fmt.Sprintf("%s/%s", prefix_to, path) return fmt.Sprintf("%s/%s", prefix_to, path)
} }
// like ioutil.WriteFile() but takes native mode/perm // like ioutil.WriteFile() but takes native mode/perm
func writefile(path string, data []byte, perm uint32) error { func writefile(path string, data []byte, perm uint32) error {
fd, err := syscall.Open(path, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_TRUNC, perm) fd, err := syscall.Open(path, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_TRUNC, perm)
if err != nil { if err != nil {
return &os.PathError{"open", path, err} return &os.PathError{"open", path, err}
} }
f := os.NewFile(uintptr(fd), path) f := os.NewFile(uintptr(fd), path)
_, err = f.Write(data) _, err = f.Write(data)
err2 := f.Close() err2 := f.Close()
if err == nil { if err == nil {
err = err2 err = err2
} }
return err return err
} }
// escape path so that git is happy to use it as ref // escape path so that git is happy to use it as ref
// https://git.kernel.org/cgit/git/git.git/tree/refs.c?h=v2.9.0-37-g6d523a3#n34 // https://git.kernel.org/cgit/git/git.git/tree/refs.c?h=v2.9.0-37-g6d523a3#n34
// XXX very suboptimal // XXX very suboptimal
func path_refescape(path string) string { func path_refescape(path string) string {
outv := []string{} outv := []string{}
for _, component := range strings.Split(path, "/") { for _, component := range strings.Split(path, "/") {
out := "" out := ""
dots := 0 // number of seen consecutive dots dots := 0 // number of seen consecutive dots
for len(component) > 0 { for len(component) > 0 {
r, size := utf8.DecodeRuneInString(component) r, size := utf8.DecodeRuneInString(component)
// no ".." anywhere - we replace dots run to %46%46... with trailing "." // no ".." anywhere - we replace dots run to %46%46... with trailing "."
// this way for single "." case we'll have it intact and avoid .. anywhere // this way for single "." case we'll have it intact and avoid .. anywhere
// also this way: trailing .git is always encoded as ".git" // also this way: trailing .git is always encoded as ".git"
if r == '.' { if r == '.' {
dots += 1 dots += 1
component = component[size:] component = component[size:]
continue continue
} }
if dots != 0 { if dots != 0 {
out += strings.Repeat(escape("."), dots-1) out += strings.Repeat(escape("."), dots-1)
out += "." out += "."
dots = 0 dots = 0
} }
rbytes := component[:size] rbytes := component[:size]
if shouldEscape(r) { if shouldEscape(r) {
rbytes = escape(rbytes) rbytes = escape(rbytes)
} }
out += rbytes out += rbytes
component = component[size:] component = component[size:]
} }
// handle trailing dots // handle trailing dots
if dots != 0 { if dots != 0 {
out += strings.Repeat(escape("."), dots-1) out += strings.Repeat(escape("."), dots-1)
out += "." out += "."
} }
if len(out) > 0 { if len(out) > 0 {
// ^. not allowed // ^. not allowed
if out[0] == '.' { if out[0] == '.' {
out = escape(".") + out[1:] out = escape(".") + out[1:]
} }
// .lock$ not allowed // .lock$ not allowed
if strings.HasSuffix(out, ".lock") { if strings.HasSuffix(out, ".lock") {
out = out[:len(out)-5] + escape(".") + "lock" out = out[:len(out)-5] + escape(".") + "lock"
} }
} }
outv = append(outv, out) outv = append(outv, out)
} }
// strip trailing / // strip trailing /
for len(outv) > 0 { for len(outv) > 0 {
if len(outv[len(outv)-1]) != 0 { if len(outv[len(outv)-1]) != 0 {
break break
} }
outv = outv[:len(outv)-1] outv = outv[:len(outv)-1]
} }
return strings.Join(outv, "/") return strings.Join(outv, "/")
} }
func shouldEscape(r rune) bool { func shouldEscape(r rune) bool {
if unicode.IsSpace(r) || unicode.IsControl(r) { if unicode.IsSpace(r) || unicode.IsControl(r) {
return true return true
} }
switch r { switch r {
// NOTE RuneError is for always escaping non-valid UTF-8 // NOTE RuneError is for always escaping non-valid UTF-8
case ':', '?', '[', '\\', '^', '~', '*', '@', '%', utf8.RuneError: case ':', '?', '[', '\\', '^', '~', '*', '@', '%', utf8.RuneError:
return true return true
} }
return false return false
} }
func escape(s string) string { func escape(s string) string {
out := "" out := ""
for i := 0; i < len(s); i++ { for i := 0; i < len(s); i++ {
out += fmt.Sprintf("%%%02X", s[i]) out += fmt.Sprintf("%%%02X", s[i])
} }
return out return out
} }
// unescape path encoded by path_refescape() // unescape path encoded by path_refescape()
// decoding is permissive - any byte can be %-encoded, not only special cases // decoding is permissive - any byte can be %-encoded, not only special cases
// XXX very suboptimal // XXX very suboptimal
func path_refunescape(s string) (string, error) { func path_refunescape(s string) (string, error) {
l := len(s) l := len(s)
out := make([]byte, 0, len(s)) out := make([]byte, 0, len(s))
for i := 0; i < l; i++ { for i := 0; i < l; i++ {
c := s[i] c := s[i]
if c == '%' { if c == '%' {
if i+2 >= l { if i+2 >= l {
return "", EscapeError(s) return "", EscapeError(s)
} }
b, err := hex.DecodeString(s[i+1:i+3]) b, err := hex.DecodeString(s[i+1 : i+3])
if err != nil { if err != nil {
return "", EscapeError(s) return "", EscapeError(s)
} }
c = b[0] c = b[0]
i += 2 i += 2
} }
out = append(out, c) out = append(out, c)
} }
return mem.String(out), nil return mem.String(out), nil
} }
type EscapeError string type EscapeError string
func (e EscapeError) Error() string { func (e EscapeError) Error() string {
return fmt.Sprintf("%q: invalid escape format", string(e)) return fmt.Sprintf("%q: invalid escape format", string(e))
} }
...@@ -20,81 +20,81 @@ ...@@ -20,81 +20,81 @@
package main package main
import ( import (
"strings" "strings"
"testing" "testing"
) )
func TestPathEscapeUnescape(t *testing.T) { func TestPathEscapeUnescape(t *testing.T) {
type TestEntry struct { path string; escapedv []string } type TestEntry struct { path string; escapedv []string }
te := func(path string, escaped ...string) TestEntry { te := func(path string, escaped ...string) TestEntry {
return TestEntry{path, escaped} return TestEntry{path, escaped}
} }
var tests = []TestEntry{ var tests = []TestEntry{
// path escaped non-canonical escapes // path escaped non-canonical escapes
te("hello/world", "hello/world", "%68%65%6c%6c%6f%2f%77%6f%72%6c%64"), te("hello/world", "hello/world", "%68%65%6c%6c%6f%2f%77%6f%72%6c%64"),
te("hello/мир", "hello/мир"), te("hello/мир", "hello/мир"),
te("hello/ мир", "hello/%20мир"), te("hello/ мир", "hello/%20мир"),
te("hel%lo/мир", "hel%25lo/мир"), te("hel%lo/мир", "hel%25lo/мир"),
te(".hello/.world", "%2Ehello/%2Eworld"), te(".hello/.world", "%2Ehello/%2Eworld"),
te("..hello/world.loc", "%2E.hello/world.loc"), te("..hello/world.loc", "%2E.hello/world.loc"),
te("..hello/world.lock", "%2E.hello/world%2Elock"), te("..hello/world.lock", "%2E.hello/world%2Elock"),
// leading / // leading /
te("/hello/world", "/hello/world"), te("/hello/world", "/hello/world"),
te("//hello///world", "//hello///world"), te("//hello///world", "//hello///world"),
// trailing / // trailing /
te("/hello/world/", "/hello/world"), te("/hello/world/", "/hello/world"),
te("/hello/world//", "/hello/world"), te("/hello/world//", "/hello/world"),
// trailing ... // trailing ...
te("/hello/world.", "/hello/world."), te("/hello/world.", "/hello/world."),
te("/hello/world..", "/hello/world%2E."), te("/hello/world..", "/hello/world%2E."),
te("/hello/world...", "/hello/world%2E%2E."), te("/hello/world...", "/hello/world%2E%2E."),
te("/hello/world...git", "/hello/world%2E%2E.git"), te("/hello/world...git", "/hello/world%2E%2E.git"),
// .. anywhere // .. anywhere
te("/hello/./world", "/hello/%2E/world"), te("/hello/./world", "/hello/%2E/world"),
te("/hello/.a/world", "/hello/%2Ea/world"), te("/hello/.a/world", "/hello/%2Ea/world"),
te("/hello/a./world", "/hello/a./world"), te("/hello/a./world", "/hello/a./world"),
te("/hello/../world", "/hello/%2E./world"), te("/hello/../world", "/hello/%2E./world"),
te("/hello/a..b/world", "/hello/a%2E.b/world"), te("/hello/a..b/world", "/hello/a%2E.b/world"),
te("/hello/a.c.b/world", "/hello/a.c.b/world"), te("/hello/a.c.b/world", "/hello/a.c.b/world"),
te("/hello/a.c..b/world", "/hello/a.c%2E.b/world"), te("/hello/a.c..b/world", "/hello/a.c%2E.b/world"),
// special & control characters // special & control characters
te("/hel lo/wor\tld/a:?[\\^~*@%b/\001\004\n\xc2\xa0", "/hel%20lo/wor%09ld/a%3A%3F%5B%5C%5E%7E%2A%40%25b/%01%04%0A%C2%A0"), te("/hel lo/wor\tld/a:?[\\^~*@%b/\001\004\n\xc2\xa0", "/hel%20lo/wor%09ld/a%3A%3F%5B%5C%5E%7E%2A%40%25b/%01%04%0A%C2%A0"),
// utf8 error // utf8 error
te("a\xc5z", "a%C5z"), te("a\xc5z", "a%C5z"),
} }
for _, tt := range tests { for _, tt := range tests {
escaped := path_refescape(tt.path) escaped := path_refescape(tt.path)
if escaped != tt.escapedv[0] { if escaped != tt.escapedv[0] {
t.Errorf("path_refescape(%q) -> %q ; want %q", tt.path, escaped, tt.escapedv[0]) t.Errorf("path_refescape(%q) -> %q ; want %q", tt.path, escaped, tt.escapedv[0])
} }
// also check the decoding // also check the decoding
pathok := strings.TrimRight(tt.path, "/") pathok := strings.TrimRight(tt.path, "/")
for _, escaped := range tt.escapedv { for _, escaped := range tt.escapedv {
unescaped, err := path_refunescape(escaped) unescaped, err := path_refunescape(escaped)
if unescaped != pathok || err != nil { if unescaped != pathok || err != nil {
t.Errorf("path_refunescape(%q) -> %q %v ; want %q nil", escaped, unescaped, err, tt.path) t.Errorf("path_refunescape(%q) -> %q %v ; want %q nil", escaped, unescaped, err, tt.path)
} }
} }
} }
} }
func TestPathUnescapeErr(t *testing.T) { func TestPathUnescapeErr(t *testing.T) {
var tests = []struct{ escaped string }{ var tests = []struct{ escaped string }{
{"%"}, {"%"},
{"%2"}, {"%2"},
{"%2q"}, {"%2q"},
{"hell%2q/world"}, {"hell%2q/world"},
} }
for _, tt := range tests { for _, tt := range tests {
unescaped, err := path_refunescape(tt.escaped) unescaped, err := path_refunescape(tt.escaped)
if err == nil || unescaped != "" { if err == nil || unescaped != "" {
t.Errorf("path_refunescape(%q) -> %q %v ; want \"\" err", tt.escaped, unescaped, err) t.Errorf("path_refunescape(%q) -> %q %v ; want \"\" err", tt.escaped, unescaped, err)
} }
} }
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment