@@ -0,0 +1,27 @@ | |||||
--- | |||||
kind: pipeline | |||||
name: default | |||||
steps: | |||||
- name: build-and-test | |||||
image: r.mills.io/prologic/golang-alpine:latest | |||||
commands: | |||||
- go test | |||||
- name: notify-irc | |||||
image: plugins/webhook | |||||
settings: | |||||
urls: | |||||
- https://msgbus.mills.io/ci.mills.io | |||||
when: | |||||
status: | |||||
- success | |||||
- failure | |||||
trigger: | |||||
branch: | |||||
- main | |||||
event: | |||||
- tag | |||||
- push | |||||
- pull_request |
@@ -1,10 +1,6 @@ | |||||
# Gopher protocol library for Golang | # Gopher protocol library for Golang | ||||
[![Build Status](https://cloud.drone.io/api/badges/prologic/go-gopher/status.svg)](https://cloud.drone.io/prologic/go-gopher) | |||||
[![CodeCov](https://codecov.io/gh/prologic/go-gopher/branch/master/graph/badge.svg)](https://codecov.io/gh/prologic/go-gopher) | |||||
[![Go Report Card](https://goreportcard.com/badge/prologic/go-gopher)](https://goreportcard.com/report/prologic/go-gopher) | |||||
[![GoDoc](https://godoc.org/git.mills.io/prologic/go-gopher?status.svg)](https://godoc.org/git.mills.io/prologic/go-gopher) | |||||
[![Sourcegraph](https://sourcegraph.com/git.mills.io/prologic/go-gopher/-/badge.svg)](https://sourcegraph.com/git.mills.io/prologic/go-gopher?badge) | |||||
[![Build Status](https://ci.mills.io/api/badges/prologic/go-gopher/status.svg)](https://ci.mills.io/prologic/go-gopher) | |||||
This is a standards compliant Gopher library for the Go programming language | This is a standards compliant Gopher library for the Go programming language | ||||
implementing the RFC 1436 specification. The library includes both client and | implementing the RFC 1436 specification. The library includes both client and | ||||
@@ -2,6 +2,7 @@ module git.mills.io/prologic/go-gopher | |||||
require ( | require ( | ||||
github.com/davecgh/go-spew v1.1.1 // indirect | github.com/davecgh/go-spew v1.1.1 // indirect | ||||
github.com/sasha-s/go-deadlock v0.3.1 | |||||
github.com/stretchr/testify v1.7.0 | github.com/stretchr/testify v1.7.0 | ||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3 | ||||
) | ) | ||||
@@ -1,8 +1,12 @@ | |||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= | |||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= | |||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= | |||||
github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= | |||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | ||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
@@ -26,8 +26,8 @@ import ( | |||||
"sort" | "sort" | ||||
"strconv" | "strconv" | ||||
"strings" | "strings" | ||||
"sync" | |||||
sync "github.com/sasha-s/go-deadlock" | |||||
"golang.org/x/net/context" | "golang.org/x/net/context" | ||||
) | ) | ||||
@@ -437,7 +437,6 @@ func (i *Item) FetchDirectory() (Directory, error) { | |||||
// LocalHost and LocalPort may be used by the Handler for local links. | // LocalHost and LocalPort may be used by the Handler for local links. | ||||
// These are specified in the call to ListenAndServe. | // These are specified in the call to ListenAndServe. | ||||
type Request struct { | type Request struct { | ||||
conn net.Conn | |||||
Selector string | Selector string | ||||
LocalHost string | LocalHost string | ||||
LocalPort int | LocalPort int | ||||
@@ -672,10 +671,6 @@ type conn struct { | |||||
// This is the value of a Handler's (*Request).RemoteAddr. | // This is the value of a Handler's (*Request).RemoteAddr. | ||||
remoteAddr string | remoteAddr string | ||||
// tlsState is the TLS connection state when using TLS. | |||||
// nil means not TLS. | |||||
tlsState *tls.ConnectionState | |||||
// mu guards hijackedv, use of bufr, (*response).closeNotifyCh. | // mu guards hijackedv, use of bufr, (*response).closeNotifyCh. | ||||
mu sync.Mutex | mu sync.Mutex | ||||
} | } | ||||
@@ -777,14 +772,6 @@ func (c *conn) readRequest(ctx context.Context) (w *response, err error) { | |||||
return w, nil | return w, nil | ||||
} | } | ||||
func (s *Server) logf(format string, args ...interface{}) { | |||||
if s.ErrorLog != nil { | |||||
s.ErrorLog.Printf(format, args...) | |||||
} else { | |||||
log.Printf(format, args...) | |||||
} | |||||
} | |||||
// ListenAndServe listens on the TCP network address addr | // ListenAndServe listens on the TCP network address addr | ||||
// and then calls Serve with handler to handle requests | // and then calls Serve with handler to handle requests | ||||
// on incoming connections. | // on incoming connections. | ||||
@@ -911,23 +898,6 @@ func selectorMatch(pattern, selector string) bool { | |||||
return len(selector) >= n && selector[0:n] == pattern | return len(selector) >= n && selector[0:n] == pattern | ||||
} | } | ||||
// Return the canonical path for p, eliminating . and .. elements. | |||||
func cleanPath(p string) string { | |||||
if p == "" { | |||||
return "/" | |||||
} | |||||
if p[0] != '/' { | |||||
p = "/" + p | |||||
} | |||||
np := path.Clean(p) | |||||
// path.Clean removes trailing slash except for root; | |||||
// put the trailing slash back if necessary. | |||||
if p[len(p)-1] == '/' && np != "/" { | |||||
np += "/" | |||||
} | |||||
return np | |||||
} | |||||
// Find a handler on a handler map given a path string | // Find a handler on a handler map given a path string | ||||
// Most-specific (longest) pattern wins | // Most-specific (longest) pattern wins | ||||
func (mux *ServeMux) match(selector string) (h Handler, pattern string) { | func (mux *ServeMux) match(selector string) (h Handler, pattern string) { | ||||
@@ -1342,7 +1312,6 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string) { | |||||
defer ff.Close() | defer ff.Close() | ||||
dd, err := ff.Stat() | dd, err := ff.Stat() | ||||
if err == nil { | if err == nil { | ||||
name = gophermap | |||||
d = dd | d = dd | ||||
f = ff | f = ff | ||||
} | } | ||||
@@ -3,17 +3,52 @@ package gopher_test | |||||
import ( | import ( | ||||
"fmt" | "fmt" | ||||
"log" | "log" | ||||
"net" | |||||
"os" | "os" | ||||
"testing" | "testing" | ||||
"time" | "time" | ||||
"git.mills.io/prologic/go-gopher" | |||||
"github.com/stretchr/testify/assert" | "github.com/stretchr/testify/assert" | ||||
"github.com/stretchr/testify/require" | |||||
"git.mills.io/prologic/go-gopher" | |||||
) | |||||
var ( | |||||
testHost string = "localhost" | |||||
testPort int | |||||
) | ) | ||||
func pickUnusedPort() (int, error) { | |||||
addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") | |||||
if err != nil { | |||||
return 0, err | |||||
} | |||||
l, err := net.ListenTCP("tcp", addr) | |||||
if err != nil { | |||||
return 0, err | |||||
} | |||||
port := l.Addr().(*net.TCPAddr).Port | |||||
if err := l.Close(); err != nil { | |||||
return 0, err | |||||
} | |||||
return port, nil | |||||
} | |||||
func TestMain(m *testing.M) { | func TestMain(m *testing.M) { | ||||
ch := startTestServer() | |||||
defer stopTestServer(ch) | |||||
port, err := pickUnusedPort() | |||||
if err != nil { | |||||
log.Fatalf("error finding a free port: %s", err) | |||||
} | |||||
testPort = port | |||||
go func() { | |||||
gopher.Handle("/", gopher.FileServer(gopher.Dir("./testdata"))) | |||||
gopher.HandleFunc("/hello", hello) | |||||
log.Printf("Test server starting on :%d\n", testPort) | |||||
log.Fatal(gopher.ListenAndServe(fmt.Sprintf(":%d", testPort), nil)) | |||||
}() | |||||
// Because it can take some time for the server to spin up | // Because it can take some time for the server to spin up | ||||
// the tests are inconsistent - they'll fail if the server isn't | // the tests are inconsistent - they'll fail if the server isn't | ||||
@@ -22,15 +57,14 @@ func TestMain(m *testing.M) { | |||||
// | // | ||||
// It seems like there should be a better way to do this | // It seems like there should be a better way to do this | ||||
for attempts := 3; attempts > 0; attempts-- { | for attempts := 3; attempts > 0; attempts-- { | ||||
_, err := gopher.Get("gopher://localhost:7000") | |||||
_, err := gopher.Get(fmt.Sprintf("gopher://%s:%d", testHost, testPort)) | |||||
if err == nil { | if err == nil { | ||||
fmt.Println("Server ready") | |||||
log.Println("Server ready") | |||||
break | break | ||||
} | } | ||||
fmt.Printf("Server not ready, going to try again in a sec. %v", err) | |||||
log.Printf("Server not ready, going to try again in a sec. %v\n", err) | |||||
time.Sleep(1 * time.Second) | time.Sleep(1 * time.Second) | ||||
} | } | ||||
///// | |||||
code := m.Run() | code := m.Run() | ||||
os.Exit(code) | os.Exit(code) | ||||
@@ -40,32 +74,6 @@ func hello(w gopher.ResponseWriter, r *gopher.Request) { | |||||
w.WriteInfo("Hello World!") | w.WriteInfo("Hello World!") | ||||
} | } | ||||
func startTestServer() chan bool { | |||||
quit := make(chan bool) | |||||
go func() { | |||||
for { | |||||
select { | |||||
case <-quit: | |||||
return | |||||
default: | |||||
gopher.Handle("/", gopher.FileServer(gopher.Dir("./testdata"))) | |||||
gopher.HandleFunc("/hello", hello) | |||||
log.Println("Test server starting on 7000") | |||||
err := gopher.ListenAndServe("localhost:7000", nil) | |||||
if err != nil { | |||||
log.Fatal(err) | |||||
} | |||||
} | |||||
} | |||||
}() | |||||
return quit | |||||
} | |||||
func stopTestServer(c chan bool) { | |||||
c <- true | |||||
} | |||||
func Example_client() { | func Example_client() { | ||||
res, err := gopher.Get("gopher://gopher.floodgap.com") | res, err := gopher.Get("gopher://gopher.floodgap.com") | ||||
if err != nil { | if err != nil { | ||||
@@ -86,27 +94,29 @@ func Example_fileserver() { | |||||
func TestGet(t *testing.T) { | func TestGet(t *testing.T) { | ||||
assert := assert.New(t) | assert := assert.New(t) | ||||
require := require.New(t) | |||||
res, err := gopher.Get("gopher://localhost:7000/1hello") | |||||
assert.NoError(err) | |||||
res, err := gopher.Get(fmt.Sprintf("gopher://%s:%d/1hello", testHost, testPort)) | |||||
require.NoError(err) | |||||
assert.Len(res.Dir.Items, 1) | assert.Len(res.Dir.Items, 1) | ||||
assert.Equal(res.Dir.Items[0].Type, gopher.INFO) | assert.Equal(res.Dir.Items[0].Type, gopher.INFO) | ||||
assert.Equal(res.Dir.Items[0].Description, "Hello World!") | assert.Equal(res.Dir.Items[0].Description, "Hello World!") | ||||
out, err := res.Dir.ToText() | out, err := res.Dir.ToText() | ||||
assert.NoError(err) | |||||
require.NoError(err) | |||||
assert.Equal(string(out), "iHello World!\t\terror.host\t1\r\n") | assert.Equal(string(out), "iHello World!\t\terror.host\t1\r\n") | ||||
} | } | ||||
func TestFileServer(t *testing.T) { | func TestFileServer(t *testing.T) { | ||||
assert := assert.New(t) | assert := assert.New(t) | ||||
require := require.New(t) | |||||
res, err := gopher.Get("gopher://localhost:7000/") | |||||
assert.NoError(err) | |||||
res, err := gopher.Get(fmt.Sprintf("gopher://%s:%d/", testHost, testPort)) | |||||
require.NoError(err) | |||||
assert.Len(res.Dir.Items, 1) | assert.Len(res.Dir.Items, 1) | ||||
json, err := res.Dir.ToJSON() | json, err := res.Dir.ToJSON() | ||||
assert.Nil(err) | |||||
require.NoError(err) | |||||
log.Println(string(json)) | log.Println(string(json)) | ||||
assert.JSONEq( | assert.JSONEq( | ||||
@@ -124,9 +134,10 @@ func TestParseItemNull(t *testing.T) { | |||||
func TestParseItem(t *testing.T) { | func TestParseItem(t *testing.T) { | ||||
assert := assert.New(t) | assert := assert.New(t) | ||||
require := require.New(t) | |||||
item, err := gopher.ParseItem("0foo\t/foo\tlocalhost\t70\r\n") | item, err := gopher.ParseItem("0foo\t/foo\tlocalhost\t70\r\n") | ||||
assert.NoError(err) | |||||
require.NoError(err) | |||||
assert.NotNil(item) | assert.NotNil(item) | ||||
assert.Equal(item, &gopher.Item{ | assert.Equal(item, &gopher.Item{ | ||||
Type: gopher.FILE, | Type: gopher.FILE, | ||||
@@ -140,10 +151,11 @@ func TestParseItem(t *testing.T) { | |||||
func TestParseItemMarshal(t *testing.T) { | func TestParseItemMarshal(t *testing.T) { | ||||
assert := assert.New(t) | assert := assert.New(t) | ||||
require := require.New(t) | |||||
data := "0foo\t/foo\tlocalhost\t70\r\n" | data := "0foo\t/foo\tlocalhost\t70\r\n" | ||||
item, err := gopher.ParseItem(data) | item, err := gopher.ParseItem(data) | ||||
assert.NoError(err) | |||||
require.NoError(err) | |||||
assert.NotNil(item) | assert.NotNil(item) | ||||
assert.Equal(item, &gopher.Item{ | assert.Equal(item, &gopher.Item{ | ||||
Type: gopher.FILE, | Type: gopher.FILE, | ||||
@@ -161,10 +173,11 @@ func TestParseItemMarshal(t *testing.T) { | |||||
func TestParseItemMarshalIdempotency(t *testing.T) { | func TestParseItemMarshalIdempotency(t *testing.T) { | ||||
assert := assert.New(t) | assert := assert.New(t) | ||||
require := require.New(t) | |||||
data := "0" | data := "0" | ||||
item, err := gopher.ParseItem(data) | item, err := gopher.ParseItem(data) | ||||
assert.NoError(err) | |||||
require.NoError(err) | |||||
assert.NotNil(item) | assert.NotNil(item) | ||||
data1, err := item.MarshalText() | data1, err := item.MarshalText() | ||||