Compare commits
8 Commits
23f637dd47
...
f8bcfe447c
Author | SHA1 | Date | |
---|---|---|---|
f8bcfe447c | |||
fb648c37be | |||
bb0113021a | |||
5a40d7c2ed | |||
f32e2f1483 | |||
62418ebb54 | |||
6b45865e3d | |||
198cb87036 |
14
README
14
README
@ -108,7 +108,7 @@ The c2 wiki unsurprisingly has a lot of material around the design and
|
||||
realisation of GUIs, which might be useful.
|
||||
|
||||
It seems like an aligning/constraint-based "layout manager" will be one of the
|
||||
first harder problems here. However I certainly don't want to used fixed
|
||||
first harder problems here. However I certainly don't want to use fixed
|
||||
coordinates as they would introduce problems with different fonts and i18n.
|
||||
|
||||
We could use BDF fonts from the X11 distribution, but draw2d has native support
|
||||
@ -117,13 +117,18 @@ for FreeType fonts and it's more of a choice between vectors and bitmaps.
|
||||
The looks will be heavily inspired by Haiku and Windows 2000 and the user will
|
||||
have no say in this, for simplicity.
|
||||
|
||||
Resources:
|
||||
- https://github.com/golang/exp/tree/master/shiny is a GUI library
|
||||
- https://github.com/as/shiny is a fork of it
|
||||
- http://man.cat-v.org/plan_9/1/rio has a particular, unusual model
|
||||
|
||||
Internationalisation
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
For i18n https://github.com/leonelquinteros/gotext could be used, however I'll
|
||||
probably give up on this issue as I'm fine enough with English.
|
||||
|
||||
Go also has x/text packages for this purpose, which might be better than GNU,
|
||||
but is essentially still in development.
|
||||
but they're essentially still in development.
|
||||
|
||||
Versioning
|
||||
~~~~~~~~~~
|
||||
@ -191,7 +196,7 @@ takes with my 10 dictionaries isn't particularly bad), pack everything with
|
||||
archive/zip.
|
||||
|
||||
Instead of ICU we may use x/text/collate and that's about everything we need.
|
||||
Since we have our own format, we may expect the indexed to be ordered by the
|
||||
Since we have our own format, we may expect the index to be ordered by the
|
||||
locale's rules, assuming they don't change between versions.
|
||||
|
||||
hmpc -- MPD client
|
||||
@ -220,6 +225,9 @@ The real model for the editor is Qt Creator with FakeVIM, though this is not to
|
||||
be a clone of it, e.g. the various "Output" lists could be just special buffers,
|
||||
which may be have names starting on "// ".
|
||||
|
||||
Resources:
|
||||
- http://doc.cat-v.org/plan_9/4th_edition/papers/sam/
|
||||
|
||||
hfm -- file manager
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
All we need to achieve here is replace Midnight Commander, which besides the
|
||||
|
221
hid/main.go
221
hid/main.go
@ -27,7 +27,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"log/syslog"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
@ -48,6 +48,100 @@ const (
|
||||
projectVersion = "0"
|
||||
)
|
||||
|
||||
// --- Logging -----------------------------------------------------------------
|
||||
|
||||
type logPrio int
|
||||
|
||||
const (
|
||||
prioFatal logPrio = iota
|
||||
prioError
|
||||
prioWarning
|
||||
prioStatus
|
||||
prioDebug
|
||||
)
|
||||
|
||||
func (lp logPrio) prefix() string {
|
||||
switch lp {
|
||||
case prioFatal:
|
||||
return "fatal: "
|
||||
case prioError:
|
||||
return "error: "
|
||||
case prioWarning:
|
||||
return "warning: "
|
||||
case prioStatus:
|
||||
return ""
|
||||
case prioDebug:
|
||||
return "debug: "
|
||||
default:
|
||||
panic("unhandled log priority")
|
||||
}
|
||||
}
|
||||
|
||||
func (lp logPrio) syslogPrio() syslog.Priority {
|
||||
switch lp {
|
||||
case prioFatal:
|
||||
return syslog.LOG_ERR
|
||||
case prioError:
|
||||
return syslog.LOG_ERR
|
||||
case prioWarning:
|
||||
return syslog.LOG_WARNING
|
||||
case prioStatus:
|
||||
return syslog.LOG_INFO
|
||||
case prioDebug:
|
||||
return syslog.LOG_DEBUG
|
||||
default:
|
||||
panic("unhandled log priority")
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
func logMessageStdio(prio logPrio, format string, args ...interface{}) {
|
||||
// TODO: isatty-enabled colors based on prio.
|
||||
os.Stderr.WriteString(time.Now().Format("2006-01-02 15:04:05 ") +
|
||||
prio.prefix() + fmt.Sprintf(format, args...) + "\n")
|
||||
}
|
||||
|
||||
func logMessageSystemd(prio logPrio, format string, args ...interface{}) {
|
||||
if prio == prioFatal {
|
||||
// There is no corresponding syslog severity.
|
||||
format = "fatal: " + format
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "<%d>%s\n",
|
||||
prio.syslogPrio(), fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
var logMessage = logMessageStdio
|
||||
|
||||
func printDebug(format string, args ...interface{}) {
|
||||
if debugMode {
|
||||
logMessage(prioDebug, format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func printStatus(format string, args ...interface{}) {
|
||||
logMessage(prioStatus, format, args...)
|
||||
}
|
||||
func printWarning(format string, args ...interface{}) {
|
||||
logMessage(prioWarning, format, args...)
|
||||
}
|
||||
func printError(format string, args ...interface{}) {
|
||||
logMessage(prioError, format, args...)
|
||||
}
|
||||
|
||||
// "fatal" is reserved for failures that would harm further operation.
|
||||
|
||||
func printFatal(format string, args ...interface{}) {
|
||||
logMessage(prioFatal, format, args...)
|
||||
}
|
||||
|
||||
func exitFatal(format string, args ...interface{}) {
|
||||
printFatal(format, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// --- Utilities ---------------------------------------------------------------
|
||||
|
||||
// Split a string by a set of UTF-8 delimiters, optionally ignoring empty items.
|
||||
@ -164,9 +258,7 @@ func findTildeHome(username string) string {
|
||||
} else if v, ok := os.LookupEnv("HOME"); ok {
|
||||
return v
|
||||
}
|
||||
if debugMode {
|
||||
log.Printf("failed to expand the home directory for %s", username)
|
||||
}
|
||||
printDebug("failed to expand the home directory for %s", username)
|
||||
return "~" + username
|
||||
}
|
||||
|
||||
@ -192,27 +284,30 @@ func resolveFilename(filename string, relativeCB func(string) string) string {
|
||||
// --- Simple file I/O ---------------------------------------------------------
|
||||
|
||||
// Overwrites filename contents with data; creates directories as needed.
|
||||
func writeFile(filename string, data []byte) error {
|
||||
if dir := filepath.Dir(filename); dir != "." {
|
||||
func writeFile(path string, data []byte) error {
|
||||
if dir := filepath.Dir(path); dir != "." {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return ioutil.WriteFile(filename, data, 0644)
|
||||
return ioutil.WriteFile(path, data, 0644)
|
||||
}
|
||||
|
||||
// Wrapper for writeFile that makes sure that the new data has been written
|
||||
// to disk in its entirety before overriding the old file.
|
||||
func writeFileSafe(filename string, data []byte) error {
|
||||
temp := filename + ".new"
|
||||
func writeFileSafe(path string, data []byte) error {
|
||||
temp := path + ".new"
|
||||
if err := writeFile(temp, data); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(temp, filename)
|
||||
return os.Rename(temp, path)
|
||||
}
|
||||
|
||||
// --- Simple configuration ----------------------------------------------------
|
||||
|
||||
// This is the bare minimum to make an application configurable.
|
||||
// Keys are stripped of surrounding whitespace, values are not.
|
||||
|
||||
type simpleConfigItem struct {
|
||||
key string // INI key
|
||||
def string // default value
|
||||
@ -229,8 +324,8 @@ func (sc simpleConfig) loadDefaults(table []simpleConfigItem) {
|
||||
|
||||
func (sc simpleConfig) updateFromFile() error {
|
||||
basename := projectName + ".conf"
|
||||
filename := resolveFilename(basename, resolveRelativeConfigFilename)
|
||||
if filename == "" {
|
||||
path := resolveFilename(basename, resolveRelativeConfigFilename)
|
||||
if path == "" {
|
||||
return &os.PathError{
|
||||
Op: "cannot find",
|
||||
Path: basename,
|
||||
@ -238,7 +333,7 @@ func (sc simpleConfig) updateFromFile() error {
|
||||
}
|
||||
}
|
||||
|
||||
f, err := os.Open(filename)
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -253,7 +348,7 @@ func (sc simpleConfig) updateFromFile() error {
|
||||
|
||||
equals := strings.IndexByte(line, '=')
|
||||
if equals <= 0 {
|
||||
return fmt.Errorf("%s:%d: malformed line", filename, lineNo)
|
||||
return fmt.Errorf("%s:%d: malformed line", path, lineNo)
|
||||
}
|
||||
|
||||
sc[strings.TrimRight(line[:equals], " \t")] = line[equals+1:]
|
||||
@ -295,13 +390,13 @@ func callSimpleConfigWriteDefault(pathHint string, table []simpleConfigItem) {
|
||||
``,
|
||||
}
|
||||
|
||||
filename, err := simpleConfigWriteDefault(
|
||||
path, err := simpleConfigWriteDefault(
|
||||
pathHint, strings.Join(prologLines, "\n"), table)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
exitFatal("%s", err)
|
||||
}
|
||||
|
||||
log.Printf("configuration written to `%s'\n", filename)
|
||||
printStatus("configuration written to `%s'", path)
|
||||
}
|
||||
|
||||
// --- Configuration -----------------------------------------------------------
|
||||
@ -420,7 +515,7 @@ func ircFnmatch(pattern string, s string) bool {
|
||||
}
|
||||
|
||||
var reMsg = regexp.MustCompile(
|
||||
`^(@[^ ]* +)?(?::([^! ]*)(?:!([^@]*)@([^ ]*))? +)?([^ ]+)(.*)?$`)
|
||||
`^(?:@([^ ]*) +)?(?::([^! ]*)(?:!([^@]*)@([^ ]*))? +)?([^ ]+)(.*)?$`)
|
||||
var reArgs = regexp.MustCompile(`:.*| [^: ][^ ]*`)
|
||||
|
||||
type message struct {
|
||||
@ -563,7 +658,7 @@ func ircIsValidFingerprint(fp string) bool {
|
||||
|
||||
// --- Clients (equals users) --------------------------------------------------
|
||||
|
||||
type connCloseWrite interface {
|
||||
type connCloseWriter interface {
|
||||
net.Conn
|
||||
CloseWrite() error
|
||||
}
|
||||
@ -589,7 +684,7 @@ const (
|
||||
type client struct {
|
||||
transport net.Conn // underlying connection
|
||||
tls *tls.Conn // TLS, if detected
|
||||
conn connCloseWrite // high-level connection
|
||||
conn connCloseWriter // high-level connection
|
||||
recvQ []byte // unprocessed input
|
||||
sendQ []byte // unprocessed output
|
||||
reading bool // whether a reading goroutine is running
|
||||
@ -793,10 +888,10 @@ var (
|
||||
// Forcefully tear down all connections.
|
||||
func forceQuit(reason string) {
|
||||
if !quitting {
|
||||
log.Fatalln("forceQuit called without initiateQuit")
|
||||
exitFatal("forceQuit called without initiateQuit")
|
||||
}
|
||||
|
||||
log.Printf("forced shutdown (%s)\n", reason)
|
||||
printStatus("forced shutdown (%s)", reason)
|
||||
for c := range clients {
|
||||
// initiateQuit has already unregistered the client.
|
||||
c.kill("Shutting down")
|
||||
@ -805,10 +900,10 @@ func forceQuit(reason string) {
|
||||
|
||||
// Initiate a clean shutdown of the whole daemon.
|
||||
func initiateQuit() {
|
||||
log.Println("shutting down")
|
||||
printStatus("shutting down")
|
||||
for _, ln := range listeners {
|
||||
if err := ln.Close(); err != nil {
|
||||
log.Println(err)
|
||||
printError("%s", err)
|
||||
}
|
||||
}
|
||||
for c := range clients {
|
||||
@ -857,6 +952,12 @@ func ircNotifyRoommates(c *client, message string) {
|
||||
|
||||
// --- Clients (continued) -----------------------------------------------------
|
||||
|
||||
func (c *client) printDebug(format string, args ...interface{}) {
|
||||
if debugMode {
|
||||
printDebug("(%s) %s", c.address, fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func ircAppendClientModes(m uint, mode []byte) []byte {
|
||||
if 0 != m&ircUserModeInvisible {
|
||||
mode = append(mode, 'i')
|
||||
@ -903,6 +1004,8 @@ func (c *client) send(line string) {
|
||||
bytes = bytes[:ircMaxMessageLength]
|
||||
}
|
||||
|
||||
c.printDebug("<- %s", bytes)
|
||||
|
||||
// TODO: Kill the connection above some "SendQ" threshold (careful!)
|
||||
c.sendQ = append(c.sendQ, bytes...)
|
||||
c.sendQ = append(c.sendQ, "\r\n"...)
|
||||
@ -954,12 +1057,12 @@ func (c *client) unregister(reason string) {
|
||||
// Close the connection and forget about the client.
|
||||
func (c *client) kill(reason string) {
|
||||
if reason == "" {
|
||||
reason = "Client exited"
|
||||
}
|
||||
c.unregister("Client exited")
|
||||
} else {
|
||||
c.unregister(reason)
|
||||
}
|
||||
|
||||
// TODO: Log the address; seems like we always have c.address.
|
||||
log.Println("client destroyed")
|
||||
c.printDebug("client destroyed (%s)", reason)
|
||||
|
||||
// Try to send a "close notify" alert if the TLS object is ready,
|
||||
// otherwise just tear down the transport.
|
||||
@ -2852,14 +2955,14 @@ func ircProcessMessage(c *client, msg *message, raw string) {
|
||||
|
||||
// Handle the results from initializing the client's connection.
|
||||
func (c *client) onPrepared(host string, isTLS bool) {
|
||||
c.printDebug("client resolved to %s, TLS %t", host, isTLS)
|
||||
if !isTLS {
|
||||
c.conn = c.transport.(connCloseWrite)
|
||||
c.conn = c.transport.(connCloseWriter)
|
||||
} else if tlsConf != nil {
|
||||
c.tls = tls.Server(c.transport, tlsConf)
|
||||
c.conn = c.tls
|
||||
} else {
|
||||
log.Printf("could not initialize TLS for %s: TLS support disabled\n",
|
||||
c.address)
|
||||
c.printDebug("could not initialize TLS: disabled")
|
||||
c.kill("TLS support disabled")
|
||||
return
|
||||
}
|
||||
@ -2892,10 +2995,10 @@ func (c *client) onRead(data []byte, readErr error) {
|
||||
// XXX: And since it accepts LF, we miscalculate receivedBytes within.
|
||||
c.recvQ = c.recvQ[advance:]
|
||||
line := string(token)
|
||||
log.Printf("-> %s\n", line)
|
||||
c.printDebug("-> %s", line)
|
||||
|
||||
if msg := ircParseMessage(line); msg == nil {
|
||||
log.Println("error: invalid line")
|
||||
c.printDebug("error: invalid line")
|
||||
} else {
|
||||
ircProcessMessage(c, msg, line)
|
||||
}
|
||||
@ -2905,18 +3008,17 @@ func (c *client) onRead(data []byte, readErr error) {
|
||||
c.reading = false
|
||||
|
||||
if readErr != io.EOF {
|
||||
log.Println(readErr)
|
||||
c.printDebug("%s", readErr)
|
||||
c.kill(readErr.Error())
|
||||
} else if c.closing {
|
||||
// Disregarding whether a clean shutdown has happened or not.
|
||||
log.Println("client finished shutdown")
|
||||
c.kill("TODO")
|
||||
c.printDebug("client finished shutdown")
|
||||
c.kill("")
|
||||
} else {
|
||||
log.Println("client EOF")
|
||||
c.printDebug("client EOF")
|
||||
c.closeLink("")
|
||||
}
|
||||
} else if len(c.recvQ) > 8192 {
|
||||
log.Println("client recvQ overrun")
|
||||
c.closeLink("recvQ overrun")
|
||||
|
||||
// tls.Conn doesn't have the CloseRead method (and it needs to be able
|
||||
@ -2941,7 +3043,7 @@ func (c *client) onWrite(written int, writeErr error) {
|
||||
c.writing = false
|
||||
|
||||
if writeErr != nil {
|
||||
log.Println(writeErr)
|
||||
c.printDebug("%s", writeErr)
|
||||
c.kill(writeErr.Error())
|
||||
} else if len(c.sendQ) > 0 {
|
||||
c.flushSendQ()
|
||||
@ -2949,7 +3051,7 @@ func (c *client) onWrite(written int, writeErr error) {
|
||||
if c.reading {
|
||||
c.conn.CloseWrite()
|
||||
} else {
|
||||
c.kill("TODO")
|
||||
c.kill("")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2968,9 +3070,9 @@ func accept(ln net.Listener) {
|
||||
return
|
||||
}
|
||||
if op, ok := err.(net.Error); !ok || !op.Temporary() {
|
||||
log.Fatalln(err)
|
||||
exitFatal("%s", err)
|
||||
} else {
|
||||
log.Println(err)
|
||||
printError("%s", err)
|
||||
}
|
||||
} else {
|
||||
// TCP_NODELAY is set by default on TCPConns.
|
||||
@ -2980,12 +3082,7 @@ func accept(ln net.Listener) {
|
||||
}
|
||||
|
||||
func prepare(client *client) {
|
||||
conn := client.transport
|
||||
host, _, err := net.SplitHostPort(conn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
// In effect, we require TCP/UDP, as they have port numbers.
|
||||
log.Fatalln(err)
|
||||
}
|
||||
conn, host := client.transport, client.hostname
|
||||
|
||||
// The Cgo resolver doesn't pthread_cancel getnameinfo threads, so not
|
||||
// bothering with pointless contexts.
|
||||
@ -2993,7 +3090,7 @@ func prepare(client *client) {
|
||||
go func() {
|
||||
defer close(ch)
|
||||
if names, err := net.LookupAddr(host); err != nil {
|
||||
log.Println(err)
|
||||
printError("%s", err)
|
||||
} else {
|
||||
ch <- names[0]
|
||||
}
|
||||
@ -3013,7 +3110,7 @@ func prepare(client *client) {
|
||||
isTLS := false
|
||||
if sysconn, err := conn.(syscall.Conn).SyscallConn(); err != nil {
|
||||
// This is just for the TLS detection and doesn't need to be fatal.
|
||||
log.Println(err)
|
||||
printError("%s", err)
|
||||
} else {
|
||||
isTLS = detectTLS(sysconn)
|
||||
}
|
||||
@ -3062,18 +3159,16 @@ func processOneEvent() {
|
||||
|
||||
case conn := <-conns:
|
||||
if maxConnections > 0 && len(clients) >= maxConnections {
|
||||
log.Println("connection limit reached, refusing connection")
|
||||
printDebug("connection limit reached, refusing connection")
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
|
||||
log.Println("accepted client connection")
|
||||
|
||||
// In effect, we require TCP/UDP, as they have port numbers.
|
||||
address := conn.RemoteAddr().String()
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
// In effect, we require TCP/UDP, as they have port numbers.
|
||||
exitFatal("%s", err)
|
||||
}
|
||||
|
||||
c := &client{
|
||||
@ -3087,26 +3182,25 @@ func processOneEvent() {
|
||||
// TODO: Make this configurable and more fine-grained.
|
||||
antiflood: newFloodDetector(10*time.Second, 20),
|
||||
}
|
||||
|
||||
clients[c] = true
|
||||
c.printDebug("new client")
|
||||
go prepare(c)
|
||||
|
||||
// The TLS autodetection in prepare needs to have a timeout.
|
||||
c.setKillTimer()
|
||||
|
||||
case ev := <-prepared:
|
||||
log.Println("client is ready:", ev.host)
|
||||
if _, ok := clients[ev.client]; ok {
|
||||
ev.client.onPrepared(ev.host, ev.isTLS)
|
||||
}
|
||||
|
||||
case ev := <-reads:
|
||||
log.Println("received data from client")
|
||||
if _, ok := clients[ev.client]; ok {
|
||||
ev.client.onRead(ev.data, ev.err)
|
||||
}
|
||||
|
||||
case ev := <-writes:
|
||||
log.Println("sent data to client")
|
||||
if _, ok := clients[ev.client]; ok {
|
||||
ev.client.onWrite(ev.written, ev.err)
|
||||
}
|
||||
@ -3296,6 +3390,7 @@ func ircSetupListenFDs() error {
|
||||
return err
|
||||
}
|
||||
listeners = append(listeners, ln)
|
||||
printStatus("listening on %s", address)
|
||||
}
|
||||
if len(listeners) == 0 {
|
||||
return errors.New("network setup failed: no ports to listen on")
|
||||
@ -3309,10 +3404,11 @@ func ircSetupListenFDs() error {
|
||||
// --- Main --------------------------------------------------------------------
|
||||
|
||||
func main() {
|
||||
flag.BoolVar(&debugMode, "debug", false, "run in debug mode")
|
||||
flag.BoolVar(&debugMode, "debug", false, "run in verbose debug mode")
|
||||
version := flag.Bool("version", false, "show version and exit")
|
||||
writeDefaultCfg := flag.Bool("writedefaultcfg", false,
|
||||
"write a default configuration file and exit")
|
||||
systemd := flag.Bool("systemd", false, "log in systemd format")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
@ -3324,6 +3420,9 @@ func main() {
|
||||
callSimpleConfigWriteDefault("", configTable)
|
||||
return
|
||||
}
|
||||
if *systemd {
|
||||
logMessage = logMessageSystemd
|
||||
}
|
||||
if flag.NArg() > 0 {
|
||||
flag.Usage()
|
||||
os.Exit(2)
|
||||
@ -3332,7 +3431,7 @@ func main() {
|
||||
config = make(simpleConfig)
|
||||
config.loadDefaults(configTable)
|
||||
if err := config.updateFromFile(); err != nil && !os.IsNotExist(err) {
|
||||
log.Println("error loading configuration", err)
|
||||
printError("error loading configuration: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@ -3348,7 +3447,7 @@ func main() {
|
||||
ircSetupListenFDs,
|
||||
} {
|
||||
if err := fn(); err != nil {
|
||||
log.Fatalln(err)
|
||||
exitFatal("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
168
hid/main_test.go
Normal file
168
hid/main_test.go
Normal file
@ -0,0 +1,168 @@
|
||||
//
|
||||
// Copyright (c) 2015 - 2018, Přemysl Janouch <p@janouch.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSplitString(t *testing.T) {
|
||||
var splitStringTests = []struct {
|
||||
s, delims string
|
||||
ignoreEmpty bool
|
||||
result []string
|
||||
}{
|
||||
{",a,,bc", ",", false, []string{"", "a", "", "bc"}},
|
||||
{",a,,bc", ",", true, []string{"a", "bc"}},
|
||||
{"a,;bc,", ",;", false, []string{"a", "", "bc", ""}},
|
||||
{"a,;bc,", ",;", true, []string{"a", "bc"}},
|
||||
{"", ",", false, []string{""}},
|
||||
{"", ",", true, nil},
|
||||
}
|
||||
|
||||
for i, d := range splitStringTests {
|
||||
got := splitString(d.s, d.delims, d.ignoreEmpty)
|
||||
if !reflect.DeepEqual(got, d.result) {
|
||||
t.Errorf("case %d: %v should be %v\n", i, got, d.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func socketpair() (*os.File, *os.File, error) {
|
||||
pair, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// See go #24331, this makes 1.11 use the internal poller
|
||||
// while there wasn't a way to achieve that before.
|
||||
if err := syscall.SetNonblock(int(pair[0]), true); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := syscall.SetNonblock(int(pair[1]), true); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
fa := os.NewFile(uintptr(pair[0]), "a")
|
||||
if fa == nil {
|
||||
return nil, nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
fb := os.NewFile(uintptr(pair[1]), "b")
|
||||
if fb == nil {
|
||||
fa.Close()
|
||||
return nil, nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
return fa, fb, nil
|
||||
}
|
||||
|
||||
func TestDetectTLS(t *testing.T) {
|
||||
detectTLSFromFunc := func(t *testing.T, writer func(net.Conn)) bool {
|
||||
// net.Pipe doesn't use file descriptors, we need a socketpair.
|
||||
sockA, sockB, err := socketpair()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer sockA.Close()
|
||||
defer sockB.Close()
|
||||
|
||||
fcB, err := net.FileConn(sockB)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go writer(fcB)
|
||||
|
||||
fcA, err := net.FileConn(sockA)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sc, err := fcA.(syscall.Conn).SyscallConn()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return detectTLS(sc)
|
||||
}
|
||||
|
||||
t.Run("SSL_2.0", func(t *testing.T) {
|
||||
if !detectTLSFromFunc(t, func(fc net.Conn) {
|
||||
// The obsolete, useless, unsupported SSL 2.0 record format.
|
||||
_, _ = fc.Write([]byte{0x80, 0x01, 0x01})
|
||||
}) {
|
||||
t.Error("could not detect SSL")
|
||||
}
|
||||
})
|
||||
t.Run("crypto_tls", func(t *testing.T) {
|
||||
if !detectTLSFromFunc(t, func(fc net.Conn) {
|
||||
conn := tls.Client(fc, &tls.Config{InsecureSkipVerify: true})
|
||||
_ = conn.Handshake()
|
||||
}) {
|
||||
t.Error("could not detect TLS")
|
||||
}
|
||||
})
|
||||
t.Run("text", func(t *testing.T) {
|
||||
if detectTLSFromFunc(t, func(fc net.Conn) {
|
||||
_, _ = fc.Write([]byte("ПРЕВЕД"))
|
||||
}) {
|
||||
t.Error("detected UTF-8 as TLS")
|
||||
}
|
||||
})
|
||||
t.Run("EOF", func(t *testing.T) {
|
||||
type connCloseWriter interface {
|
||||
net.Conn
|
||||
CloseWrite() error
|
||||
}
|
||||
if detectTLSFromFunc(t, func(fc net.Conn) {
|
||||
_ = fc.(connCloseWriter).CloseWrite()
|
||||
}) {
|
||||
t.Error("detected EOF as TLS")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestIRC(t *testing.T) {
|
||||
msg := ircParseMessage(
|
||||
`@first=a\:\s\r\n\\;2nd :srv hi there :good m8 :how are you?`)
|
||||
|
||||
if !reflect.DeepEqual(msg.tags, map[string]string{
|
||||
"first": "a; \r\n\\",
|
||||
"2nd": "",
|
||||
}) {
|
||||
t.Error("tags parsed incorrectly")
|
||||
}
|
||||
|
||||
if msg.nick != "srv" || msg.user != "" || msg.host != "" {
|
||||
t.Error("server name parsed incorrectly")
|
||||
}
|
||||
if msg.command != "hi" {
|
||||
t.Error("command name parsed incorrectly")
|
||||
}
|
||||
if !reflect.DeepEqual(msg.params,
|
||||
[]string{"there", "good m8 :how are you?"}) {
|
||||
t.Error("params parsed incorrectly")
|
||||
}
|
||||
|
||||
if !ircEqual("[fag]^", "{FAG}~") {
|
||||
t.Error("string case comparison not according to RFC 2812")
|
||||
}
|
||||
|
||||
// TODO: More tests.
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user