Add new dependencies

Signed-off-by: Patrik Cyvoct <patrik@ptrk.io>
This commit is contained in:
Patrik Cyvoct
2018-06-14 11:06:48 +02:00
parent 5ab6b88029
commit f8d399490e
75 changed files with 10984 additions and 0 deletions

View File

@@ -53,3 +53,11 @@ golang.org/x/text c4d099d611ac3ded35360abf03581e13d91c828f
google.golang.org/api 373a4c220f5c90e5b7ff7101779c5be385d171be
google.golang.org/grpc 0713829b980f4ddd276689a36235c5fcc82a21bf
gopkg.in/yaml.v2 a3f3340b5840cee44f372bddb5880fcbc419b46a
github.com/scaleway/go-scaleway f8c3b31c65867b4cb96b3bd41e574c33fd1d69ae
github.com/moul/anonuuid c6987e46f8a0231504bcd402962749f8dd9970b0
golang.org/x/sync 1d60e4601c6fd243af51cc01ddf169918a5407ca
github.com/renstrom/fuzzysearch 7a8f9a1c4bed53899ecd512daeaf8207cc454156
github.com/moul/gotty-client e5589f6df35953284b091b8394daa6be6c453469
github.com/gorilla/websocket 21ab95fa12b9bdd8fecf5fa3586aad941cc98785
github.com/creack/goselect 58854f77ee8d858ce751b0a9bcc5533fef7bfa9e
github.com/containerd/console cb7008ab3d8359b78c5f464cb7cf160107ad5925

View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@@ -0,0 +1,17 @@
# console
[![Build Status](https://travis-ci.org/containerd/console.svg?branch=master)](https://travis-ci.org/containerd/console)
Golang package for dealing with consoles. Light on deps and a simple API.
## Modifying the current process
```go
current := console.Current()
defer current.Reset()
if err := current.SetRaw(); err != nil {
}
ws, err := current.Size()
current.Resize(ws)
```

View File

@@ -0,0 +1,78 @@
/*
Copyright The containerd Authors.
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.
*/
package console
import (
"errors"
"io"
"os"
)
var ErrNotAConsole = errors.New("provided file is not a console")
type Console interface {
io.Reader
io.Writer
io.Closer
// Resize resizes the console to the provided window size
Resize(WinSize) error
// ResizeFrom resizes the calling console to the size of the
// provided console
ResizeFrom(Console) error
// SetRaw sets the console in raw mode
SetRaw() error
// DisableEcho disables echo on the console
DisableEcho() error
// Reset restores the console to its orignal state
Reset() error
// Size returns the window size of the console
Size() (WinSize, error)
// Fd returns the console's file descriptor
Fd() uintptr
// Name returns the console's file name
Name() string
}
// WinSize specifies the window size of the console
type WinSize struct {
// Height of the console
Height uint16
// Width of the console
Width uint16
x uint16
y uint16
}
// Current returns the current processes console
func Current() Console {
c, err := ConsoleFromFile(os.Stdin)
if err != nil {
// stdin should always be a console for the design
// of this function
panic(err)
}
return c
}
// ConsoleFromFile returns a console using the provided file
func ConsoleFromFile(f *os.File) (Console, error) {
if err := checkConsole(f); err != nil {
return nil, err
}
return newMaster(f)
}

View File

@@ -0,0 +1,271 @@
// +build linux
/*
Copyright The containerd Authors.
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.
*/
package console
import (
"io"
"os"
"sync"
"golang.org/x/sys/unix"
)
const (
maxEvents = 128
)
// Epoller manages multiple epoll consoles using edge-triggered epoll api so we
// dont have to deal with repeated wake-up of EPOLLER or EPOLLHUP.
// For more details, see:
// - https://github.com/systemd/systemd/pull/4262
// - https://github.com/moby/moby/issues/27202
//
// Example usage of Epoller and EpollConsole can be as follow:
//
// epoller, _ := NewEpoller()
// epollConsole, _ := epoller.Add(console)
// go epoller.Wait()
// var (
// b bytes.Buffer
// wg sync.WaitGroup
// )
// wg.Add(1)
// go func() {
// io.Copy(&b, epollConsole)
// wg.Done()
// }()
// // perform I/O on the console
// epollConsole.Shutdown(epoller.CloseConsole)
// wg.Wait()
// epollConsole.Close()
type Epoller struct {
efd int
mu sync.Mutex
fdMapping map[int]*EpollConsole
}
// NewEpoller returns an instance of epoller with a valid epoll fd.
func NewEpoller() (*Epoller, error) {
efd, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC)
if err != nil {
return nil, err
}
return &Epoller{
efd: efd,
fdMapping: make(map[int]*EpollConsole),
}, nil
}
// Add creates a epoll console based on the provided console. The console will
// be registered with EPOLLET (i.e. using edge-triggered notification) and its
// file descriptor will be set to non-blocking mode. After this, user should use
// the return console to perform I/O.
func (e *Epoller) Add(console Console) (*EpollConsole, error) {
sysfd := int(console.Fd())
// Set sysfd to non-blocking mode
if err := unix.SetNonblock(sysfd, true); err != nil {
return nil, err
}
ev := unix.EpollEvent{
Events: unix.EPOLLIN | unix.EPOLLOUT | unix.EPOLLRDHUP | unix.EPOLLET,
Fd: int32(sysfd),
}
if err := unix.EpollCtl(e.efd, unix.EPOLL_CTL_ADD, sysfd, &ev); err != nil {
return nil, err
}
ef := &EpollConsole{
Console: console,
sysfd: sysfd,
readc: sync.NewCond(&sync.Mutex{}),
writec: sync.NewCond(&sync.Mutex{}),
}
e.mu.Lock()
e.fdMapping[sysfd] = ef
e.mu.Unlock()
return ef, nil
}
// Wait starts the loop to wait for its consoles' notifications and signal
// appropriate console that it can perform I/O.
func (e *Epoller) Wait() error {
events := make([]unix.EpollEvent, maxEvents)
for {
n, err := unix.EpollWait(e.efd, events, -1)
if err != nil {
// EINTR: The call was interrupted by a signal handler before either
// any of the requested events occurred or the timeout expired
if err == unix.EINTR {
continue
}
return err
}
for i := 0; i < n; i++ {
ev := &events[i]
// the console is ready to be read from
if ev.Events&(unix.EPOLLIN|unix.EPOLLHUP|unix.EPOLLERR) != 0 {
if epfile := e.getConsole(int(ev.Fd)); epfile != nil {
epfile.signalRead()
}
}
// the console is ready to be written to
if ev.Events&(unix.EPOLLOUT|unix.EPOLLHUP|unix.EPOLLERR) != 0 {
if epfile := e.getConsole(int(ev.Fd)); epfile != nil {
epfile.signalWrite()
}
}
}
}
}
// Close unregister the console's file descriptor from epoll interface
func (e *Epoller) CloseConsole(fd int) error {
e.mu.Lock()
defer e.mu.Unlock()
delete(e.fdMapping, fd)
return unix.EpollCtl(e.efd, unix.EPOLL_CTL_DEL, fd, &unix.EpollEvent{})
}
func (e *Epoller) getConsole(sysfd int) *EpollConsole {
e.mu.Lock()
f := e.fdMapping[sysfd]
e.mu.Unlock()
return f
}
// Close the epoll fd
func (e *Epoller) Close() error {
return unix.Close(e.efd)
}
// EpollConsole acts like a console but register its file descriptor with a
// epoll fd and uses epoll API to perform I/O.
type EpollConsole struct {
Console
readc *sync.Cond
writec *sync.Cond
sysfd int
closed bool
}
// Read reads up to len(p) bytes into p. It returns the number of bytes read
// (0 <= n <= len(p)) and any error encountered.
//
// If the console's read returns EAGAIN or EIO, we assumes that its a
// temporary error because the other side went away and wait for the signal
// generated by epoll event to continue.
func (ec *EpollConsole) Read(p []byte) (n int, err error) {
var read int
ec.readc.L.Lock()
defer ec.readc.L.Unlock()
for {
read, err = ec.Console.Read(p[n:])
n += read
if err != nil {
var hangup bool
if perr, ok := err.(*os.PathError); ok {
hangup = (perr.Err == unix.EAGAIN || perr.Err == unix.EIO)
} else {
hangup = (err == unix.EAGAIN || err == unix.EIO)
}
// if the other end disappear, assume this is temporary and wait for the
// signal to continue again. Unless we didnt read anything and the
// console is already marked as closed then we should exit
if hangup && !(n == 0 && len(p) > 0 && ec.closed) {
ec.readc.Wait()
continue
}
}
break
}
// if we didnt read anything then return io.EOF to end gracefully
if n == 0 && len(p) > 0 && err == nil {
err = io.EOF
}
// signal for others that we finished the read
ec.readc.Signal()
return n, err
}
// Writes len(p) bytes from p to the console. It returns the number of bytes
// written from p (0 <= n <= len(p)) and any error encountered that caused
// the write to stop early.
//
// If writes to the console returns EAGAIN or EIO, we assumes that its a
// temporary error because the other side went away and wait for the signal
// generated by epoll event to continue.
func (ec *EpollConsole) Write(p []byte) (n int, err error) {
var written int
ec.writec.L.Lock()
defer ec.writec.L.Unlock()
for {
written, err = ec.Console.Write(p[n:])
n += written
if err != nil {
var hangup bool
if perr, ok := err.(*os.PathError); ok {
hangup = (perr.Err == unix.EAGAIN || perr.Err == unix.EIO)
} else {
hangup = (err == unix.EAGAIN || err == unix.EIO)
}
// if the other end disappear, assume this is temporary and wait for the
// signal to continue again.
if hangup {
ec.writec.Wait()
continue
}
}
// unrecoverable error, break the loop and return the error
break
}
if n < len(p) && err == nil {
err = io.ErrShortWrite
}
// signal for others that we finished the write
ec.writec.Signal()
return n, err
}
// Close closed the file descriptor and signal call waiters for this fd.
// It accepts a callback which will be called with the console's fd. The
// callback typically will be used to do further cleanup such as unregister the
// console's fd from the epoll interface.
// User should call Shutdown and wait for all I/O operation to be finished
// before closing the console.
func (ec *EpollConsole) Shutdown(close func(int) error) error {
ec.readc.L.Lock()
defer ec.readc.L.Unlock()
ec.writec.L.Lock()
defer ec.writec.L.Unlock()
ec.readc.Broadcast()
ec.writec.Broadcast()
ec.closed = true
return close(ec.sysfd)
}
// signalRead signals that the console is readable.
func (ec *EpollConsole) signalRead() {
ec.readc.Signal()
}
// signalWrite signals that the console is writable.
func (ec *EpollConsole) signalWrite() {
ec.writec.Signal()
}

View File

@@ -0,0 +1,158 @@
// +build darwin freebsd linux openbsd solaris
/*
Copyright The containerd Authors.
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.
*/
package console
import (
"os"
"golang.org/x/sys/unix"
)
// NewPty creates a new pty pair
// The master is returned as the first console and a string
// with the path to the pty slave is returned as the second
func NewPty() (Console, string, error) {
f, err := os.OpenFile("/dev/ptmx", unix.O_RDWR|unix.O_NOCTTY|unix.O_CLOEXEC, 0)
if err != nil {
return nil, "", err
}
slave, err := ptsname(f)
if err != nil {
return nil, "", err
}
if err := unlockpt(f); err != nil {
return nil, "", err
}
m, err := newMaster(f)
if err != nil {
return nil, "", err
}
return m, slave, nil
}
type master struct {
f *os.File
original *unix.Termios
}
func (m *master) Read(b []byte) (int, error) {
return m.f.Read(b)
}
func (m *master) Write(b []byte) (int, error) {
return m.f.Write(b)
}
func (m *master) Close() error {
return m.f.Close()
}
func (m *master) Resize(ws WinSize) error {
return tcswinsz(m.f.Fd(), ws)
}
func (m *master) ResizeFrom(c Console) error {
ws, err := c.Size()
if err != nil {
return err
}
return m.Resize(ws)
}
func (m *master) Reset() error {
if m.original == nil {
return nil
}
return tcset(m.f.Fd(), m.original)
}
func (m *master) getCurrent() (unix.Termios, error) {
var termios unix.Termios
if err := tcget(m.f.Fd(), &termios); err != nil {
return unix.Termios{}, err
}
return termios, nil
}
func (m *master) SetRaw() error {
rawState, err := m.getCurrent()
if err != nil {
return err
}
rawState = cfmakeraw(rawState)
rawState.Oflag = rawState.Oflag | unix.OPOST
return tcset(m.f.Fd(), &rawState)
}
func (m *master) DisableEcho() error {
rawState, err := m.getCurrent()
if err != nil {
return err
}
rawState.Lflag = rawState.Lflag &^ unix.ECHO
return tcset(m.f.Fd(), &rawState)
}
func (m *master) Size() (WinSize, error) {
return tcgwinsz(m.f.Fd())
}
func (m *master) Fd() uintptr {
return m.f.Fd()
}
func (m *master) Name() string {
return m.f.Name()
}
// checkConsole checks if the provided file is a console
func checkConsole(f *os.File) error {
var termios unix.Termios
if tcget(f.Fd(), &termios) != nil {
return ErrNotAConsole
}
return nil
}
func newMaster(f *os.File) (Console, error) {
m := &master{
f: f,
}
t, err := m.getCurrent()
if err != nil {
return nil, err
}
m.original = &t
return m, nil
}
// ClearONLCR sets the necessary tty_ioctl(4)s to ensure that a pty pair
// created by us acts normally. In particular, a not-very-well-known default of
// Linux unix98 ptys is that they have +onlcr by default. While this isn't a
// problem for terminal emulators, because we relay data from the terminal we
// also relay that funky line discipline.
func ClearONLCR(fd uintptr) error {
return setONLCR(fd, false)
}
// SetONLCR sets the necessary tty_ioctl(4)s to ensure that a pty pair
// created by us acts as intended for a terminal emulator.
func SetONLCR(fd uintptr) error {
return setONLCR(fd, true)
}

View File

@@ -0,0 +1,216 @@
/*
Copyright The containerd Authors.
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.
*/
package console
import (
"fmt"
"os"
"github.com/pkg/errors"
"golang.org/x/sys/windows"
)
var (
vtInputSupported bool
ErrNotImplemented = errors.New("not implemented")
)
func (m *master) initStdios() {
m.in = windows.Handle(os.Stdin.Fd())
if err := windows.GetConsoleMode(m.in, &m.inMode); err == nil {
// Validate that windows.ENABLE_VIRTUAL_TERMINAL_INPUT is supported, but do not set it.
if err = windows.SetConsoleMode(m.in, m.inMode|windows.ENABLE_VIRTUAL_TERMINAL_INPUT); err == nil {
vtInputSupported = true
}
// Unconditionally set the console mode back even on failure because SetConsoleMode
// remembers invalid bits on input handles.
windows.SetConsoleMode(m.in, m.inMode)
} else {
fmt.Printf("failed to get console mode for stdin: %v\n", err)
}
m.out = windows.Handle(os.Stdout.Fd())
if err := windows.GetConsoleMode(m.out, &m.outMode); err == nil {
if err := windows.SetConsoleMode(m.out, m.outMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil {
m.outMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
} else {
windows.SetConsoleMode(m.out, m.outMode)
}
} else {
fmt.Printf("failed to get console mode for stdout: %v\n", err)
}
m.err = windows.Handle(os.Stderr.Fd())
if err := windows.GetConsoleMode(m.err, &m.errMode); err == nil {
if err := windows.SetConsoleMode(m.err, m.errMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil {
m.errMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
} else {
windows.SetConsoleMode(m.err, m.errMode)
}
} else {
fmt.Printf("failed to get console mode for stderr: %v\n", err)
}
}
type master struct {
in windows.Handle
inMode uint32
out windows.Handle
outMode uint32
err windows.Handle
errMode uint32
}
func (m *master) SetRaw() error {
if err := makeInputRaw(m.in, m.inMode); err != nil {
return err
}
// Set StdOut and StdErr to raw mode, we ignore failures since
// windows.DISABLE_NEWLINE_AUTO_RETURN might not be supported on this version of
// Windows.
windows.SetConsoleMode(m.out, m.outMode|windows.DISABLE_NEWLINE_AUTO_RETURN)
windows.SetConsoleMode(m.err, m.errMode|windows.DISABLE_NEWLINE_AUTO_RETURN)
return nil
}
func (m *master) Reset() error {
for _, s := range []struct {
fd windows.Handle
mode uint32
}{
{m.in, m.inMode},
{m.out, m.outMode},
{m.err, m.errMode},
} {
if err := windows.SetConsoleMode(s.fd, s.mode); err != nil {
return errors.Wrap(err, "unable to restore console mode")
}
}
return nil
}
func (m *master) Size() (WinSize, error) {
var info windows.ConsoleScreenBufferInfo
err := windows.GetConsoleScreenBufferInfo(m.out, &info)
if err != nil {
return WinSize{}, errors.Wrap(err, "unable to get console info")
}
winsize := WinSize{
Width: uint16(info.Window.Right - info.Window.Left + 1),
Height: uint16(info.Window.Bottom - info.Window.Top + 1),
}
return winsize, nil
}
func (m *master) Resize(ws WinSize) error {
return ErrNotImplemented
}
func (m *master) ResizeFrom(c Console) error {
return ErrNotImplemented
}
func (m *master) DisableEcho() error {
mode := m.inMode &^ windows.ENABLE_ECHO_INPUT
mode |= windows.ENABLE_PROCESSED_INPUT
mode |= windows.ENABLE_LINE_INPUT
if err := windows.SetConsoleMode(m.in, mode); err != nil {
return errors.Wrap(err, "unable to set console to disable echo")
}
return nil
}
func (m *master) Close() error {
return nil
}
func (m *master) Read(b []byte) (int, error) {
panic("not implemented on windows")
}
func (m *master) Write(b []byte) (int, error) {
panic("not implemented on windows")
}
func (m *master) Fd() uintptr {
return uintptr(m.in)
}
// on windows, console can only be made from os.Std{in,out,err}, hence there
// isnt a single name here we can use. Return a dummy "console" value in this
// case should be sufficient.
func (m *master) Name() string {
return "console"
}
// makeInputRaw puts the terminal (Windows Console) connected to the given
// file descriptor into raw mode
func makeInputRaw(fd windows.Handle, mode uint32) error {
// See
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
// Disable these modes
mode &^= windows.ENABLE_ECHO_INPUT
mode &^= windows.ENABLE_LINE_INPUT
mode &^= windows.ENABLE_MOUSE_INPUT
mode &^= windows.ENABLE_WINDOW_INPUT
mode &^= windows.ENABLE_PROCESSED_INPUT
// Enable these modes
mode |= windows.ENABLE_EXTENDED_FLAGS
mode |= windows.ENABLE_INSERT_MODE
mode |= windows.ENABLE_QUICK_EDIT_MODE
if vtInputSupported {
mode |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
}
if err := windows.SetConsoleMode(fd, mode); err != nil {
return errors.Wrap(err, "unable to set console to raw mode")
}
return nil
}
func checkConsole(f *os.File) error {
var mode uint32
if err := windows.GetConsoleMode(windows.Handle(f.Fd()), &mode); err != nil {
return err
}
return nil
}
func newMaster(f *os.File) (Console, error) {
if f != os.Stdin && f != os.Stdout && f != os.Stderr {
return nil, errors.New("creating a console from a file is not supported on windows")
}
m := &master{}
m.initStdios()
return m, nil
}

View File

@@ -0,0 +1,53 @@
/*
Copyright The containerd Authors.
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.
*/
package console
import (
"fmt"
"os"
"unsafe"
"golang.org/x/sys/unix"
)
const (
cmdTcGet = unix.TIOCGETA
cmdTcSet = unix.TIOCSETA
)
func ioctl(fd, flag, data uintptr) error {
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, flag, data); err != 0 {
return err
}
return nil
}
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
// unlockpt should be called before opening the slave side of a pty.
func unlockpt(f *os.File) error {
var u int32
return ioctl(f.Fd(), unix.TIOCPTYUNLK, uintptr(unsafe.Pointer(&u)))
}
// ptsname retrieves the name of the first available pts for the given master.
func ptsname(f *os.File) (string, error) {
n, err := unix.IoctlGetInt(int(f.Fd()), unix.TIOCPTYGNAME)
if err != nil {
return "", err
}
return fmt.Sprintf("/dev/pts/%d", n), nil
}

View File

@@ -0,0 +1,45 @@
/*
Copyright The containerd Authors.
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.
*/
package console
import (
"fmt"
"os"
"golang.org/x/sys/unix"
)
const (
cmdTcGet = unix.TIOCGETA
cmdTcSet = unix.TIOCSETA
)
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
// unlockpt should be called before opening the slave side of a pty.
// This does not exist on FreeBSD, it does not allocate controlling terminals on open
func unlockpt(f *os.File) error {
return nil
}
// ptsname retrieves the name of the first available pts for the given master.
func ptsname(f *os.File) (string, error) {
n, err := unix.IoctlGetInt(int(f.Fd()), unix.TIOCGPTN)
if err != nil {
return "", err
}
return fmt.Sprintf("/dev/pts/%d", n), nil
}

View File

@@ -0,0 +1,49 @@
/*
Copyright The containerd Authors.
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.
*/
package console
import (
"fmt"
"os"
"unsafe"
"golang.org/x/sys/unix"
)
const (
cmdTcGet = unix.TCGETS
cmdTcSet = unix.TCSETS
)
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
// unlockpt should be called before opening the slave side of a pty.
func unlockpt(f *os.File) error {
var u int32
if _, _, err := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))); err != 0 {
return err
}
return nil
}
// ptsname retrieves the name of the first available pts for the given master.
func ptsname(f *os.File) (string, error) {
var u uint32
if _, _, err := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.TIOCGPTN, uintptr(unsafe.Pointer(&u))); err != 0 {
return "", err
}
return fmt.Sprintf("/dev/pts/%d", u), nil
}

View File

@@ -0,0 +1,51 @@
// +build openbsd,cgo
/*
Copyright The containerd Authors.
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.
*/
package console
import (
"os"
"golang.org/x/sys/unix"
)
//#include <stdlib.h>
import "C"
const (
cmdTcGet = unix.TIOCGETA
cmdTcSet = unix.TIOCSETA
)
// ptsname retrieves the name of the first available pts for the given master.
func ptsname(f *os.File) (string, error) {
ptspath, err := C.ptsname(C.int(f.Fd()))
if err != nil {
return "", err
}
return C.GoString(ptspath), nil
}
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
// unlockpt should be called before opening the slave side of a pty.
func unlockpt(f *os.File) error {
if _, err := C.grantpt(C.int(f.Fd())); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,47 @@
// +build openbsd,!cgo
/*
Copyright The containerd Authors.
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.
*/
//
// Implementing the functions below requires cgo support. Non-cgo stubs
// versions are defined below to enable cross-compilation of source code
// that depends on these functions, but the resultant cross-compiled
// binaries cannot actually be used. If the stub function(s) below are
// actually invoked they will display an error message and cause the
// calling process to exit.
//
package console
import (
"os"
"golang.org/x/sys/unix"
)
const (
cmdTcGet = unix.TIOCGETA
cmdTcSet = unix.TIOCSETA
)
func ptsname(f *os.File) (string, error) {
panic("ptsname() support requires cgo.")
}
func unlockpt(f *os.File) error {
panic("unlockpt() support requires cgo.")
}

View File

@@ -0,0 +1,51 @@
// +build solaris,cgo
/*
Copyright The containerd Authors.
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.
*/
package console
import (
"os"
"golang.org/x/sys/unix"
)
//#include <stdlib.h>
import "C"
const (
cmdTcGet = unix.TCGETS
cmdTcSet = unix.TCSETS
)
// ptsname retrieves the name of the first available pts for the given master.
func ptsname(f *os.File) (string, error) {
ptspath, err := C.ptsname(C.int(f.Fd()))
if err != nil {
return "", err
}
return C.GoString(ptspath), nil
}
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
// unlockpt should be called before opening the slave side of a pty.
func unlockpt(f *os.File) error {
if _, err := C.grantpt(C.int(f.Fd())); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,47 @@
// +build solaris,!cgo
/*
Copyright The containerd Authors.
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.
*/
//
// Implementing the functions below requires cgo support. Non-cgo stubs
// versions are defined below to enable cross-compilation of source code
// that depends on these functions, but the resultant cross-compiled
// binaries cannot actually be used. If the stub function(s) below are
// actually invoked they will display an error message and cause the
// calling process to exit.
//
package console
import (
"os"
"golang.org/x/sys/unix"
)
const (
cmdTcGet = unix.TCGETS
cmdTcSet = unix.TCSETS
)
func ptsname(f *os.File) (string, error) {
panic("ptsname() support requires cgo.")
}
func unlockpt(f *os.File) error {
panic("unlockpt() support requires cgo.")
}

View File

@@ -0,0 +1,91 @@
// +build darwin freebsd linux openbsd solaris
/*
Copyright The containerd Authors.
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.
*/
package console
import (
"golang.org/x/sys/unix"
)
func tcget(fd uintptr, p *unix.Termios) error {
termios, err := unix.IoctlGetTermios(int(fd), cmdTcGet)
if err != nil {
return err
}
*p = *termios
return nil
}
func tcset(fd uintptr, p *unix.Termios) error {
return unix.IoctlSetTermios(int(fd), cmdTcSet, p)
}
func tcgwinsz(fd uintptr) (WinSize, error) {
var ws WinSize
uws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ)
if err != nil {
return ws, err
}
// Translate from unix.Winsize to console.WinSize
ws.Height = uws.Row
ws.Width = uws.Col
ws.x = uws.Xpixel
ws.y = uws.Ypixel
return ws, nil
}
func tcswinsz(fd uintptr, ws WinSize) error {
// Translate from console.WinSize to unix.Winsize
var uws unix.Winsize
uws.Row = ws.Height
uws.Col = ws.Width
uws.Xpixel = ws.x
uws.Ypixel = ws.y
return unix.IoctlSetWinsize(int(fd), unix.TIOCSWINSZ, &uws)
}
func setONLCR(fd uintptr, enable bool) error {
var termios unix.Termios
if err := tcget(fd, &termios); err != nil {
return err
}
if enable {
// Set +onlcr so we can act like a real terminal
termios.Oflag |= unix.ONLCR
} else {
// Set -onlcr so we don't have to deal with \r.
termios.Oflag &^= unix.ONLCR
}
return tcset(fd, &termios)
}
func cfmakeraw(t unix.Termios) unix.Termios {
t.Iflag &^= (unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON)
t.Oflag &^= unix.OPOST
t.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN)
t.Cflag &^= (unix.CSIZE | unix.PARENB)
t.Cflag &^= unix.CS8
t.Cc[unix.VMIN] = 1
t.Cc[unix.VTIME] = 0
return t
}

View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014 Guillaume J. Charmes
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,30 @@
# go-select
select(2) implementation in Go
## Supported platforms
| | 386 | amd64 | arm | arm64 |
|---------------|-----|-------|-----|-------|
| **linux** | yes | yes | yes | yes |
| **darwin** | yes | yes | n/a | ?? |
| **freebsd** | yes | yes | yes | ?? |
| **openbsd** | yes | yes | yes | ?? |
| **netbsd** | yes | yes | yes | ?? |
| **dragonfly** | n/a | yes | n/a | ?? |
| **solaris** | n/a | no | n/a | ?? |
| **plan9** | no | no | n/a | ?? |
| **windows** | yes | yes | n/a | ?? |
| **android** | n/a | n/a | no | ?? |
*n/a: platform not supported by Go
Go on `plan9` and `solaris` do not implement `syscall.Select` not `syscall.SYS_SELECT`.
## Cross compile
Using davecheney's https://github.com/davecheney/golang-crosscompile
```
export PLATFORMS="darwin/386 darwin/amd64 freebsd/386 freebsd/amd64 freebsd/arm linux/386 linux/amd64 linux/arm windows/386 windows/amd64 openbsd/386 openbsd/amd64 netbsd/386 netbsd/amd64 dragonfly/amd64 plan9/386 plan9/amd64 solaris/amd64"
```

View File

@@ -0,0 +1,33 @@
// +build !freebsd,!windows,!plan9
package goselect
import "syscall"
const FD_SETSIZE = syscall.FD_SETSIZE
// FDSet wraps syscall.FdSet with convenience methods
type FDSet syscall.FdSet
// Set adds the fd to the set
func (fds *FDSet) Set(fd uintptr) {
fds.Bits[fd/NFDBITS] |= (1 << (fd % NFDBITS))
}
// Clear remove the fd from the set
func (fds *FDSet) Clear(fd uintptr) {
fds.Bits[fd/NFDBITS] &^= (1 << (fd % NFDBITS))
}
// IsSet check if the given fd is set
func (fds *FDSet) IsSet(fd uintptr) bool {
return fds.Bits[fd/NFDBITS]&(1<<(fd%NFDBITS)) != 0
}
// Keep a null set to avoid reinstatiation
var nullFdSet = &FDSet{}
// Zero empties the Set
func (fds *FDSet) Zero() {
copy(fds.Bits[:], (nullFdSet).Bits[:])
}

View File

@@ -0,0 +1,10 @@
// +build darwin openbsd netbsd 386 arm mips mipsle
package goselect
// darwin, netbsd and openbsd uses uint32 on both amd64 and 386
const (
// NFDBITS is the amount of bits per mask
NFDBITS = 4 * 8
)

View File

@@ -0,0 +1,11 @@
// +build !darwin,!netbsd,!openbsd
// +build amd64 arm64 ppc64le mips64 mips64le s390x
package goselect
// darwin, netbsd and openbsd uses uint32 on both amd64 and 386
const (
// NFDBITS is the amount of bits per mask
NFDBITS = 8 * 8
)

View File

@@ -0,0 +1,93 @@
package goselect
/**
From: XCode's MacOSX10.10.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/sys/select.h
--
// darwin/amd64 / 386
sizeof(__int32_t) == 4
--
typedef __int32_t __fd_mask;
#define FD_SETSIZE 1024
#define __NFDBITS (sizeof(__fd_mask) * 8)
#define __howmany(x, y) ((((x) % (y)) == 0) ? ((x) / (y)) : (((x) / (y)) + 1))
typedef struct fd_set {
__fd_mask fds_bits[__howmany(__FD_SETSIZE, __NFDBITS)];
} fd_set;
#define __FD_MASK(n) ((__fd_mask)1 << ((n) % __NFDBITS))
#define FD_SET(n, p) ((p)->fds_bits[(n)/__NFDBITS] |= __FD_MASK(n))
#define FD_CLR(n, p) ((p)->fds_bits[(n)/__NFDBITS] &= ~__FD_MASK(n))
#define FD_ISSET(n, p) (((p)->fds_bits[(n)/__NFDBITS] & __FD_MASK(n)) != 0)
*/
/**
From: /usr/include/i386-linux-gnu/sys/select.h
--
// linux/i686
sizeof(long int) == 4
--
typedef long int __fd_mask;
#define FD_SETSIZE 1024
#define __NFDBITS (sizeof(__fd_mask) * 8)
typedef struct fd_set {
__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
} fd_set;
#define __FD_MASK(n) ((__fd_mask)1 << ((n) % __NFDBITS))
#define FD_SET(n, p) ((p)->fds_bits[(n)/__NFDBITS] |= __FD_MASK(n))
#define FD_CLR(n, p) ((p)->fds_bits[(n)/__NFDBITS] &= ~__FD_MASK(n))
#define FD_ISSET(n, p) (((p)->fds_bits[(n)/__NFDBITS] & __FD_MASK(n)) != 0)
*/
/**
From: /usr/include/x86_64-linux-gnu/sys/select.h
--
// linux/amd64
sizeof(long int) == 8
--
typedef long int __fd_mask;
#define FD_SETSIZE 1024
#define __NFDBITS (sizeof(__fd_mask) * 8)
typedef struct fd_set {
__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
} fd_set;
#define __FD_MASK(n) ((__fd_mask)1 << ((n) % __NFDBITS))
#define FD_SET(n, p) ((p)->fds_bits[(n)/__NFDBITS] |= __FD_MASK(n))
#define FD_CLR(n, p) ((p)->fds_bits[(n)/__NFDBITS] &= ~__FD_MASK(n))
#define FD_ISSET(n, p) (((p)->fds_bits[(n)/__NFDBITS] & __FD_MASK(n)) != 0)
*/
/**
From: /usr/include/sys/select.h
--
// freebsd/amd64
sizeof(unsigned long) == 8
--
typedef unsigned long __fd_mask;
#define FD_SETSIZE 1024U
#define __NFDBITS (sizeof(__fd_mask) * 8)
#define _howmany(x, y) (((x) + ((y) - 1)) / (y))
typedef struct fd_set {
__fd_mask fds_bits[_howmany(FD_SETSIZE, __NFDBITS)];
} fd_set;
#define __FD_MASK(n) ((__fd_mask)1 << ((n) % __NFDBITS))
#define FD_SET(n, p) ((p)->fds_bits[(n)/__NFDBITS] |= __FD_MASK(n))
#define FD_CLR(n, p) ((p)->fds_bits[(n)/__NFDBITS] &= ~__FD_MASK(n))
#define FD_ISSET(n, p) (((p)->fds_bits[(n)/__NFDBITS] & __FD_MASK(n)) != 0)
*/

View File

@@ -0,0 +1,33 @@
// +build freebsd
package goselect
import "syscall"
const FD_SETSIZE = syscall.FD_SETSIZE
// FDSet wraps syscall.FdSet with convenience methods
type FDSet syscall.FdSet
// Set adds the fd to the set
func (fds *FDSet) Set(fd uintptr) {
fds.X__fds_bits[fd/NFDBITS] |= (1 << (fd % NFDBITS))
}
// Clear remove the fd from the set
func (fds *FDSet) Clear(fd uintptr) {
fds.X__fds_bits[fd/NFDBITS] &^= (1 << (fd % NFDBITS))
}
// IsSet check if the given fd is set
func (fds *FDSet) IsSet(fd uintptr) bool {
return fds.X__fds_bits[fd/NFDBITS]&(1<<(fd%NFDBITS)) != 0
}
// Keep a null set to avoid reinstatiation
var nullFdSet = &FDSet{}
// Zero empties the Set
func (fds *FDSet) Zero() {
copy(fds.X__fds_bits[:], (nullFdSet).X__fds_bits[:])
}

View File

@@ -0,0 +1,20 @@
// +build plan9
package goselect
const FD_SETSIZE = 0
// FDSet wraps syscall.FdSet with convenience methods
type FDSet struct{}
// Set adds the fd to the set
func (fds *FDSet) Set(fd uintptr) {}
// Clear remove the fd from the set
func (fds *FDSet) Clear(fd uintptr) {}
// IsSet check if the given fd is set
func (fds *FDSet) IsSet(fd uintptr) bool { return false }
// Zero empties the Set
func (fds *FDSet) Zero() {}

View File

@@ -0,0 +1,57 @@
// +build windows
package goselect
import "syscall"
const FD_SETSIZE = 64
// FDSet extracted from mingw libs source code
type FDSet struct {
fd_count uint
fd_array [FD_SETSIZE]uintptr
}
// Set adds the fd to the set
func (fds *FDSet) Set(fd uintptr) {
var i uint
for i = 0; i < fds.fd_count; i++ {
if fds.fd_array[i] == fd {
break
}
}
if i == fds.fd_count {
if fds.fd_count < FD_SETSIZE {
fds.fd_array[i] = fd
fds.fd_count++
}
}
}
// Clear remove the fd from the set
func (fds *FDSet) Clear(fd uintptr) {
var i uint
for i = 0; i < fds.fd_count; i++ {
if fds.fd_array[i] == fd {
for i < fds.fd_count-1 {
fds.fd_array[i] = fds.fd_array[i+1]
i++
}
fds.fd_count--
break
}
}
}
// IsSet check if the given fd is set
func (fds *FDSet) IsSet(fd uintptr) bool {
if isset, err := __WSAFDIsSet(syscall.Handle(fd), fds); err == nil && isset != 0 {
return true
}
return false
}
// Zero empties the Set
func (fds *FDSet) Zero() {
fds.fd_count = 0
}

View File

@@ -0,0 +1,28 @@
package goselect
import (
"syscall"
"time"
)
// Select wraps syscall.Select with Go types
func Select(n int, r, w, e *FDSet, timeout time.Duration) error {
var timeval *syscall.Timeval
if timeout >= 0 {
t := syscall.NsecToTimeval(timeout.Nanoseconds())
timeval = &t
}
return sysSelect(n, r, w, e, timeval)
}
// RetrySelect wraps syscall.Select with Go types, and retries a number of times, with a given retryDelay.
func RetrySelect(n int, r, w, e *FDSet, timeout time.Duration, retries int, retryDelay time.Duration) (err error) {
for i := 0; i < retries; i++ {
if err = Select(n, r, w, e, timeout); err != syscall.EINTR {
return err
}
time.Sleep(retryDelay)
}
return err
}

View File

@@ -0,0 +1,10 @@
// +build linux
package goselect
import "syscall"
func sysSelect(n int, r, w, e *FDSet, timeout *syscall.Timeval) error {
_, err := syscall.Select(n, (*syscall.FdSet)(r), (*syscall.FdSet)(w), (*syscall.FdSet)(e), timeout)
return err
}

View File

@@ -0,0 +1,9 @@
// +build !linux,!windows,!plan9,!solaris
package goselect
import "syscall"
func sysSelect(n int, r, w, e *FDSet, timeout *syscall.Timeval) error {
return syscall.Select(n, (*syscall.FdSet)(r), (*syscall.FdSet)(w), (*syscall.FdSet)(e), timeout)
}

View File

@@ -0,0 +1,16 @@
// +build plan9 solaris
package goselect
import (
"fmt"
"runtime"
"syscall"
)
// ErrUnsupported .
var ErrUnsupported = fmt.Errorf("Platofrm %s/%s unsupported", runtime.GOOS, runtime.GOARCH)
func sysSelect(n int, r, w, e *FDSet, timeout *syscall.Timeval) error {
return ErrUnsupported
}

View File

@@ -0,0 +1,13 @@
// +build windows
package goselect
import "syscall"
//sys _select(nfds int, readfds *FDSet, writefds *FDSet, exceptfds *FDSet, timeout *syscall.Timeval) (total int, err error) = ws2_32.select
//sys __WSAFDIsSet(handle syscall.Handle, fdset *FDSet) (isset int, err error) = ws2_32.__WSAFDIsSet
func sysSelect(n int, r, w, e *FDSet, timeout *syscall.Timeval) error {
_, err := _select(n, r, w, e, timeout)
return err
}

View File

@@ -0,0 +1,39 @@
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
package goselect
import "unsafe"
import "syscall"
var _ unsafe.Pointer
var (
modws2_32 = syscall.NewLazyDLL("ws2_32.dll")
procselect = modws2_32.NewProc("select")
proc__WSAFDIsSet = modws2_32.NewProc("__WSAFDIsSet")
)
func _select(nfds int, readfds *FDSet, writefds *FDSet, exceptfds *FDSet, timeout *syscall.Timeval) (total int, err error) {
r0, _, e1 := syscall.Syscall6(procselect.Addr(), 5, uintptr(nfds), uintptr(unsafe.Pointer(readfds)), uintptr(unsafe.Pointer(writefds)), uintptr(unsafe.Pointer(exceptfds)), uintptr(unsafe.Pointer(timeout)), 0)
total = int(r0)
if total == 0 {
if e1 != 0 {
err = error(e1)
}
}
return
}
func __WSAFDIsSet(handle syscall.Handle, fdset *FDSet) (isset int, err error) {
r0, _, e1 := syscall.Syscall(proc__WSAFDIsSet.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(fdset)), 0)
isset = int(r0)
if isset == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}

View File

@@ -0,0 +1,22 @@
Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,64 @@
# Gorilla WebSocket
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
[![Build Status](https://travis-ci.org/gorilla/websocket.svg?branch=master)](https://travis-ci.org/gorilla/websocket)
[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket)
### Documentation
* [API Reference](http://godoc.org/github.com/gorilla/websocket)
* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat)
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command)
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo)
* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch)
### Status
The Gorilla WebSocket package provides a complete and tested implementation of
the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The
package API is stable.
### Installation
go get github.com/gorilla/websocket
### Protocol Compliance
The Gorilla WebSocket package passes the server tests in the [Autobahn Test
Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn).
### Gorilla WebSocket compared with other packages
<table>
<tr>
<th></th>
<th><a href="http://godoc.org/github.com/gorilla/websocket">github.com/gorilla</a></th>
<th><a href="http://godoc.org/golang.org/x/net/websocket">golang.org/x/net</a></th>
</tr>
<tr>
<tr><td colspan="3"><a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> Features</td></tr>
<tr><td>Passes <a href="http://autobahn.ws/testsuite/">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
<tr><td>Receive <a href="https://tools.ietf.org/html/rfc6455#section-5.4">fragmented</a> message<td>Yes</td><td><a href="https://code.google.com/p/go/issues/detail?id=7632">No</a>, see note 1</td></tr>
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">close</a> message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=4588">No</a></td></tr>
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr>
<tr><td>Get the <a href="https://tools.ietf.org/html/rfc6455#section-5.6">type</a> of a received data message</td><td>Yes</td><td>Yes, see note 2</td></tr>
<tr><td colspan="3">Other Features</tr></td>
<tr><td><a href="https://tools.ietf.org/html/rfc7692">Compression Extensions</a></td><td>Experimental</td><td>No</td></tr>
<tr><td>Read message using io.Reader</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextReader">Yes</a></td><td>No, see note 3</td></tr>
<tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr>
</table>
Notes:
1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html).
2. The application can get the type of a received data message by implementing
a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal)
function.
3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries.
Read returns when the input buffer is full or a frame boundary is
encountered. Each call to Write sends a single frame message. The Gorilla
io.Reader and io.WriteCloser operate on a single WebSocket message.

View File

@@ -0,0 +1,330 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"bytes"
"crypto/tls"
"errors"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"strings"
"time"
)
// ErrBadHandshake is returned when the server response to opening handshake is
// invalid.
var ErrBadHandshake = errors.New("websocket: bad handshake")
var errInvalidCompression = errors.New("websocket: invalid compression negotiation")
// NewClient creates a new client connection using the given net connection.
// The URL u specifies the host and request URI. Use requestHeader to specify
// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies
// (Cookie). Use the response.Header to get the selected subprotocol
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
//
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
// non-nil *http.Response so that callers can handle redirects, authentication,
// etc.
//
// Deprecated: Use Dialer instead.
func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) {
d := Dialer{
ReadBufferSize: readBufSize,
WriteBufferSize: writeBufSize,
NetDial: func(net, addr string) (net.Conn, error) {
return netConn, nil
},
}
return d.Dial(u.String(), requestHeader)
}
// A Dialer contains options for connecting to WebSocket server.
type Dialer struct {
// NetDial specifies the dial function for creating TCP connections. If
// NetDial is nil, net.Dial is used.
NetDial func(network, addr string) (net.Conn, error)
// Proxy specifies a function to return a proxy for a given
// Request. If the function returns a non-nil error, the
// request is aborted with the provided error.
// If Proxy is nil or returns a nil *URL, no proxy is used.
Proxy func(*http.Request) (*url.URL, error)
// TLSClientConfig specifies the TLS configuration to use with tls.Client.
// If nil, the default configuration is used.
TLSClientConfig *tls.Config
// HandshakeTimeout specifies the duration for the handshake to complete.
HandshakeTimeout time.Duration
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
// size is zero, then a useful default size is used. The I/O buffer sizes
// do not limit the size of the messages that can be sent or received.
ReadBufferSize, WriteBufferSize int
// Subprotocols specifies the client's requested subprotocols.
Subprotocols []string
// EnableCompression specifies if the client should attempt to negotiate
// per message compression (RFC 7692). Setting this value to true does not
// guarantee that compression will be supported. Currently only "no context
// takeover" modes are supported.
EnableCompression bool
// Jar specifies the cookie jar.
// If Jar is nil, cookies are not sent in requests and ignored
// in responses.
Jar http.CookieJar
}
var errMalformedURL = errors.New("malformed ws or wss URL")
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
hostPort = u.Host
hostNoPort = u.Host
if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") {
hostNoPort = hostNoPort[:i]
} else {
switch u.Scheme {
case "wss":
hostPort += ":443"
case "https":
hostPort += ":443"
default:
hostPort += ":80"
}
}
return hostPort, hostNoPort
}
// DefaultDialer is a dialer with all fields set to the default values.
var DefaultDialer = &Dialer{
Proxy: http.ProxyFromEnvironment,
HandshakeTimeout: 45 * time.Second,
}
// nilDialer is dialer to use when receiver is nil.
var nilDialer Dialer = *DefaultDialer
// Dial creates a new client connection. Use requestHeader to specify the
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
// Use the response.Header to get the selected subprotocol
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
//
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
// non-nil *http.Response so that callers can handle redirects, authentication,
// etcetera. The response body may not contain the entire response and does not
// need to be closed by the application.
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
if d == nil {
d = &nilDialer
}
challengeKey, err := generateChallengeKey()
if err != nil {
return nil, nil, err
}
u, err := url.Parse(urlStr)
if err != nil {
return nil, nil, err
}
switch u.Scheme {
case "ws":
u.Scheme = "http"
case "wss":
u.Scheme = "https"
default:
return nil, nil, errMalformedURL
}
if u.User != nil {
// User name and password are not allowed in websocket URIs.
return nil, nil, errMalformedURL
}
req := &http.Request{
Method: "GET",
URL: u,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
Host: u.Host,
}
// Set the cookies present in the cookie jar of the dialer
if d.Jar != nil {
for _, cookie := range d.Jar.Cookies(u) {
req.AddCookie(cookie)
}
}
// Set the request headers using the capitalization for names and values in
// RFC examples. Although the capitalization shouldn't matter, there are
// servers that depend on it. The Header.Set method is not used because the
// method canonicalizes the header names.
req.Header["Upgrade"] = []string{"websocket"}
req.Header["Connection"] = []string{"Upgrade"}
req.Header["Sec-WebSocket-Key"] = []string{challengeKey}
req.Header["Sec-WebSocket-Version"] = []string{"13"}
if len(d.Subprotocols) > 0 {
req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")}
}
for k, vs := range requestHeader {
switch {
case k == "Host":
if len(vs) > 0 {
req.Host = vs[0]
}
case k == "Upgrade" ||
k == "Connection" ||
k == "Sec-Websocket-Key" ||
k == "Sec-Websocket-Version" ||
k == "Sec-Websocket-Extensions" ||
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
case k == "Sec-Websocket-Protocol":
req.Header["Sec-WebSocket-Protocol"] = vs
default:
req.Header[k] = vs
}
}
if d.EnableCompression {
req.Header["Sec-WebSocket-Extensions"] = []string{"permessage-deflate; server_no_context_takeover; client_no_context_takeover"}
}
var deadline time.Time
if d.HandshakeTimeout != 0 {
deadline = time.Now().Add(d.HandshakeTimeout)
}
// Get network dial function.
netDial := d.NetDial
if netDial == nil {
netDialer := &net.Dialer{Deadline: deadline}
netDial = netDialer.Dial
}
// If needed, wrap the dial function to set the connection deadline.
if !deadline.Equal(time.Time{}) {
forwardDial := netDial
netDial = func(network, addr string) (net.Conn, error) {
c, err := forwardDial(network, addr)
if err != nil {
return nil, err
}
err = c.SetDeadline(deadline)
if err != nil {
c.Close()
return nil, err
}
return c, nil
}
}
// If needed, wrap the dial function to connect through a proxy.
if d.Proxy != nil {
proxyURL, err := d.Proxy(req)
if err != nil {
return nil, nil, err
}
if proxyURL != nil {
dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial))
if err != nil {
return nil, nil, err
}
netDial = dialer.Dial
}
}
hostPort, hostNoPort := hostPortNoPort(u)
netConn, err := netDial("tcp", hostPort)
if err != nil {
return nil, nil, err
}
defer func() {
if netConn != nil {
netConn.Close()
}
}()
if u.Scheme == "https" {
cfg := cloneTLSConfig(d.TLSClientConfig)
if cfg.ServerName == "" {
cfg.ServerName = hostNoPort
}
tlsConn := tls.Client(netConn, cfg)
netConn = tlsConn
if err := tlsConn.Handshake(); err != nil {
return nil, nil, err
}
if !cfg.InsecureSkipVerify {
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
return nil, nil, err
}
}
}
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize)
if err := req.Write(netConn); err != nil {
return nil, nil, err
}
resp, err := http.ReadResponse(conn.br, req)
if err != nil {
return nil, nil, err
}
if d.Jar != nil {
if rc := resp.Cookies(); len(rc) > 0 {
d.Jar.SetCookies(u, rc)
}
}
if resp.StatusCode != 101 ||
!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) {
// Before closing the network connection on return from this
// function, slurp up some of the response to aid application
// debugging.
buf := make([]byte, 1024)
n, _ := io.ReadFull(resp.Body, buf)
resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n]))
return nil, resp, ErrBadHandshake
}
for _, ext := range parseExtensions(resp.Header) {
if ext[""] != "permessage-deflate" {
continue
}
_, snct := ext["server_no_context_takeover"]
_, cnct := ext["client_no_context_takeover"]
if !snct || !cnct {
return nil, resp, errInvalidCompression
}
conn.newCompressionWriter = compressNoContextTakeover
conn.newDecompressionReader = decompressNoContextTakeover
break
}
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
netConn.SetDeadline(time.Time{})
netConn = nil // to avoid close in defer.
return conn, resp, nil
}

View File

@@ -0,0 +1,16 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.8
package websocket
import "crypto/tls"
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return cfg.Clone()
}

View File

@@ -0,0 +1,38 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.8
package websocket
import "crypto/tls"
// cloneTLSConfig clones all public fields except the fields
// SessionTicketsDisabled and SessionTicketKey. This avoids copying the
// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a
// config in active use.
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return &tls.Config{
Rand: cfg.Rand,
Time: cfg.Time,
Certificates: cfg.Certificates,
NameToCertificate: cfg.NameToCertificate,
GetCertificate: cfg.GetCertificate,
RootCAs: cfg.RootCAs,
NextProtos: cfg.NextProtos,
ServerName: cfg.ServerName,
ClientAuth: cfg.ClientAuth,
ClientCAs: cfg.ClientCAs,
InsecureSkipVerify: cfg.InsecureSkipVerify,
CipherSuites: cfg.CipherSuites,
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
ClientSessionCache: cfg.ClientSessionCache,
MinVersion: cfg.MinVersion,
MaxVersion: cfg.MaxVersion,
CurvePreferences: cfg.CurvePreferences,
}
}

View File

@@ -0,0 +1,148 @@
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"compress/flate"
"errors"
"io"
"strings"
"sync"
)
const (
minCompressionLevel = -2 // flate.HuffmanOnly not defined in Go < 1.6
maxCompressionLevel = flate.BestCompression
defaultCompressionLevel = 1
)
var (
flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool
flateReaderPool = sync.Pool{New: func() interface{} {
return flate.NewReader(nil)
}}
)
func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
const tail =
// Add four bytes as specified in RFC
"\x00\x00\xff\xff" +
// Add final block to squelch unexpected EOF error from flate reader.
"\x01\x00\x00\xff\xff"
fr, _ := flateReaderPool.Get().(io.ReadCloser)
fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil)
return &flateReadWrapper{fr}
}
func isValidCompressionLevel(level int) bool {
return minCompressionLevel <= level && level <= maxCompressionLevel
}
func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser {
p := &flateWriterPools[level-minCompressionLevel]
tw := &truncWriter{w: w}
fw, _ := p.Get().(*flate.Writer)
if fw == nil {
fw, _ = flate.NewWriter(tw, level)
} else {
fw.Reset(tw)
}
return &flateWriteWrapper{fw: fw, tw: tw, p: p}
}
// truncWriter is an io.Writer that writes all but the last four bytes of the
// stream to another io.Writer.
type truncWriter struct {
w io.WriteCloser
n int
p [4]byte
}
func (w *truncWriter) Write(p []byte) (int, error) {
n := 0
// fill buffer first for simplicity.
if w.n < len(w.p) {
n = copy(w.p[w.n:], p)
p = p[n:]
w.n += n
if len(p) == 0 {
return n, nil
}
}
m := len(p)
if m > len(w.p) {
m = len(w.p)
}
if nn, err := w.w.Write(w.p[:m]); err != nil {
return n + nn, err
}
copy(w.p[:], w.p[m:])
copy(w.p[len(w.p)-m:], p[len(p)-m:])
nn, err := w.w.Write(p[:len(p)-m])
return n + nn, err
}
type flateWriteWrapper struct {
fw *flate.Writer
tw *truncWriter
p *sync.Pool
}
func (w *flateWriteWrapper) Write(p []byte) (int, error) {
if w.fw == nil {
return 0, errWriteClosed
}
return w.fw.Write(p)
}
func (w *flateWriteWrapper) Close() error {
if w.fw == nil {
return errWriteClosed
}
err1 := w.fw.Flush()
w.p.Put(w.fw)
w.fw = nil
if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
}
err2 := w.tw.w.Close()
if err1 != nil {
return err1
}
return err2
}
type flateReadWrapper struct {
fr io.ReadCloser
}
func (r *flateReadWrapper) Read(p []byte) (int, error) {
if r.fr == nil {
return 0, io.ErrClosedPipe
}
n, err := r.fr.Read(p)
if err == io.EOF {
// Preemptively place the reader back in the pool. This helps with
// scenarios where the application does not call NextReader() soon after
// this final read.
r.Close()
}
return n, err
}
func (r *flateReadWrapper) Close() error {
if r.fr == nil {
return io.ErrClosedPipe
}
err := r.fr.Close()
flateReaderPool.Put(r.fr)
r.fr = nil
return err
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.5
package websocket
import "io"
func (c *Conn) read(n int) ([]byte, error) {
p, err := c.br.Peek(n)
if err == io.EOF {
err = errUnexpectedEOF
}
c.br.Discard(len(p))
return p, err
}

View File

@@ -0,0 +1,21 @@
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.5
package websocket
import "io"
func (c *Conn) read(n int) ([]byte, error) {
p, err := c.br.Peek(n)
if err == io.EOF {
err = errUnexpectedEOF
}
if len(p) > 0 {
// advance over the bytes just read
io.ReadFull(c.br, p)
}
return p, err
}

View File

@@ -0,0 +1,15 @@
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.8
package websocket
import "net"
func (c *Conn) writeBufs(bufs ...[]byte) error {
b := net.Buffers(bufs)
_, err := b.WriteTo(c.conn)
return err
}

View File

@@ -0,0 +1,18 @@
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.8
package websocket
func (c *Conn) writeBufs(bufs ...[]byte) error {
for _, buf := range bufs {
if len(buf) > 0 {
if _, err := c.conn.Write(buf); err != nil {
return err
}
}
}
return nil
}

View File

@@ -0,0 +1,180 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package websocket implements the WebSocket protocol defined in RFC 6455.
//
// Overview
//
// The Conn type represents a WebSocket connection. A server application calls
// the Upgrader.Upgrade method from an HTTP request handler to get a *Conn:
//
// var upgrader = websocket.Upgrader{
// ReadBufferSize: 1024,
// WriteBufferSize: 1024,
// }
//
// func handler(w http.ResponseWriter, r *http.Request) {
// conn, err := upgrader.Upgrade(w, r, nil)
// if err != nil {
// log.Println(err)
// return
// }
// ... Use conn to send and receive messages.
// }
//
// Call the connection's WriteMessage and ReadMessage methods to send and
// receive messages as a slice of bytes. This snippet of code shows how to echo
// messages using these methods:
//
// for {
// messageType, p, err := conn.ReadMessage()
// if err != nil {
// log.Println(err)
// return
// }
// if err := conn.WriteMessage(messageType, p); err != nil {
// log.Println(err)
// return
// }
// }
//
// In above snippet of code, p is a []byte and messageType is an int with value
// websocket.BinaryMessage or websocket.TextMessage.
//
// An application can also send and receive messages using the io.WriteCloser
// and io.Reader interfaces. To send a message, call the connection NextWriter
// method to get an io.WriteCloser, write the message to the writer and close
// the writer when done. To receive a message, call the connection NextReader
// method to get an io.Reader and read until io.EOF is returned. This snippet
// shows how to echo messages using the NextWriter and NextReader methods:
//
// for {
// messageType, r, err := conn.NextReader()
// if err != nil {
// return
// }
// w, err := conn.NextWriter(messageType)
// if err != nil {
// return err
// }
// if _, err := io.Copy(w, r); err != nil {
// return err
// }
// if err := w.Close(); err != nil {
// return err
// }
// }
//
// Data Messages
//
// The WebSocket protocol distinguishes between text and binary data messages.
// Text messages are interpreted as UTF-8 encoded text. The interpretation of
// binary messages is left to the application.
//
// This package uses the TextMessage and BinaryMessage integer constants to
// identify the two data message types. The ReadMessage and NextReader methods
// return the type of the received message. The messageType argument to the
// WriteMessage and NextWriter methods specifies the type of a sent message.
//
// It is the application's responsibility to ensure that text messages are
// valid UTF-8 encoded text.
//
// Control Messages
//
// The WebSocket protocol defines three types of control messages: close, ping
// and pong. Call the connection WriteControl, WriteMessage or NextWriter
// methods to send a control message to the peer.
//
// Connections handle received close messages by calling the handler function
// set with the SetCloseHandler method and by returning a *CloseError from the
// NextReader, ReadMessage or the message Read method. The default close
// handler sends a close message to the peer.
//
// Connections handle received ping messages by calling the handler function
// set with the SetPingHandler method. The default ping handler sends a pong
// message to the peer.
//
// Connections handle received pong messages by calling the handler function
// set with the SetPongHandler method. The default pong handler does nothing.
// If an application sends ping messages, then the application should set a
// pong handler to receive the corresponding pong.
//
// The control message handler functions are called from the NextReader,
// ReadMessage and message reader Read methods. The default close and ping
// handlers can block these methods for a short time when the handler writes to
// the connection.
//
// The application must read the connection to process close, ping and pong
// messages sent from the peer. If the application is not otherwise interested
// in messages from the peer, then the application should start a goroutine to
// read and discard messages from the peer. A simple example is:
//
// func readLoop(c *websocket.Conn) {
// for {
// if _, _, err := c.NextReader(); err != nil {
// c.Close()
// break
// }
// }
// }
//
// Concurrency
//
// Connections support one concurrent reader and one concurrent writer.
//
// Applications are responsible for ensuring that no more than one goroutine
// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage,
// WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and
// that no more than one goroutine calls the read methods (NextReader,
// SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler)
// concurrently.
//
// The Close and WriteControl methods can be called concurrently with all other
// methods.
//
// Origin Considerations
//
// Web browsers allow Javascript applications to open a WebSocket connection to
// any host. It's up to the server to enforce an origin policy using the Origin
// request header sent by the browser.
//
// The Upgrader calls the function specified in the CheckOrigin field to check
// the origin. If the CheckOrigin function returns false, then the Upgrade
// method fails the WebSocket handshake with HTTP status 403.
//
// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail
// the handshake if the Origin request header is present and the Origin host is
// not equal to the Host request header.
//
// The deprecated package-level Upgrade function does not perform origin
// checking. The application is responsible for checking the Origin header
// before calling the Upgrade function.
//
// Compression EXPERIMENTAL
//
// Per message compression extensions (RFC 7692) are experimentally supported
// by this package in a limited capacity. Setting the EnableCompression option
// to true in Dialer or Upgrader will attempt to negotiate per message deflate
// support.
//
// var upgrader = websocket.Upgrader{
// EnableCompression: true,
// }
//
// If compression was successfully negotiated with the connection's peer, any
// message received in compressed form will be automatically decompressed.
// All Read methods will return uncompressed bytes.
//
// Per message compression of messages written to a connection can be enabled
// or disabled by calling the corresponding Conn method:
//
// conn.EnableWriteCompression(false)
//
// Currently this package does not support compression with "context takeover".
// This means that messages must be compressed and decompressed in isolation,
// without retaining sliding window or dictionary state across messages. For
// more details refer to RFC 7692.
//
// Use of compression is experimental and may result in decreased performance.
package websocket

View File

@@ -0,0 +1,60 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"encoding/json"
"io"
)
// WriteJSON writes the JSON encoding of v as a message.
//
// Deprecated: Use c.WriteJSON instead.
func WriteJSON(c *Conn, v interface{}) error {
return c.WriteJSON(v)
}
// WriteJSON writes the JSON encoding of v as a message.
//
// See the documentation for encoding/json Marshal for details about the
// conversion of Go values to JSON.
func (c *Conn) WriteJSON(v interface{}) error {
w, err := c.NextWriter(TextMessage)
if err != nil {
return err
}
err1 := json.NewEncoder(w).Encode(v)
err2 := w.Close()
if err1 != nil {
return err1
}
return err2
}
// ReadJSON reads the next JSON-encoded message from the connection and stores
// it in the value pointed to by v.
//
// Deprecated: Use c.ReadJSON instead.
func ReadJSON(c *Conn, v interface{}) error {
return c.ReadJSON(v)
}
// ReadJSON reads the next JSON-encoded message from the connection and stores
// it in the value pointed to by v.
//
// See the documentation for the encoding/json Unmarshal function for details
// about the conversion of JSON to a Go value.
func (c *Conn) ReadJSON(v interface{}) error {
_, r, err := c.NextReader()
if err != nil {
return err
}
err = json.NewDecoder(r).Decode(v)
if err == io.EOF {
// One value is expected in the message.
err = io.ErrUnexpectedEOF
}
return err
}

View File

@@ -0,0 +1,54 @@
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in the
// LICENSE file.
// +build !appengine
package websocket
import "unsafe"
const wordSize = int(unsafe.Sizeof(uintptr(0)))
func maskBytes(key [4]byte, pos int, b []byte) int {
// Mask one byte at a time for small buffers.
if len(b) < 2*wordSize {
for i := range b {
b[i] ^= key[pos&3]
pos++
}
return pos & 3
}
// Mask one byte at a time to word boundary.
if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 {
n = wordSize - n
for i := range b[:n] {
b[i] ^= key[pos&3]
pos++
}
b = b[n:]
}
// Create aligned word size key.
var k [wordSize]byte
for i := range k {
k[i] = key[(pos+i)&3]
}
kw := *(*uintptr)(unsafe.Pointer(&k))
// Mask one word at a time.
n := (len(b) / wordSize) * wordSize
for i := 0; i < n; i += wordSize {
*(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw
}
// Mask one byte at a time for remaining bytes.
b = b[n:]
for i := range b {
b[i] ^= key[pos&3]
pos++
}
return pos & 3
}

View File

@@ -0,0 +1,15 @@
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in the
// LICENSE file.
// +build appengine
package websocket
func maskBytes(key [4]byte, pos int, b []byte) int {
for i := range b {
b[i] ^= key[pos&3]
pos++
}
return pos & 3
}

View File

@@ -0,0 +1,103 @@
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"bytes"
"net"
"sync"
"time"
)
// PreparedMessage caches on the wire representations of a message payload.
// Use PreparedMessage to efficiently send a message payload to multiple
// connections. PreparedMessage is especially useful when compression is used
// because the CPU and memory expensive compression operation can be executed
// once for a given set of compression options.
type PreparedMessage struct {
messageType int
data []byte
err error
mu sync.Mutex
frames map[prepareKey]*preparedFrame
}
// prepareKey defines a unique set of options to cache prepared frames in PreparedMessage.
type prepareKey struct {
isServer bool
compress bool
compressionLevel int
}
// preparedFrame contains data in wire representation.
type preparedFrame struct {
once sync.Once
data []byte
}
// NewPreparedMessage returns an initialized PreparedMessage. You can then send
// it to connection using WritePreparedMessage method. Valid wire
// representation will be calculated lazily only once for a set of current
// connection options.
func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage, error) {
pm := &PreparedMessage{
messageType: messageType,
frames: make(map[prepareKey]*preparedFrame),
data: data,
}
// Prepare a plain server frame.
_, frameData, err := pm.frame(prepareKey{isServer: true, compress: false})
if err != nil {
return nil, err
}
// To protect against caller modifying the data argument, remember the data
// copied to the plain server frame.
pm.data = frameData[len(frameData)-len(data):]
return pm, nil
}
func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) {
pm.mu.Lock()
frame, ok := pm.frames[key]
if !ok {
frame = &preparedFrame{}
pm.frames[key] = frame
}
pm.mu.Unlock()
var err error
frame.once.Do(func() {
// Prepare a frame using a 'fake' connection.
// TODO: Refactor code in conn.go to allow more direct construction of
// the frame.
mu := make(chan bool, 1)
mu <- true
var nc prepareConn
c := &Conn{
conn: &nc,
mu: mu,
isServer: key.isServer,
compressionLevel: key.compressionLevel,
enableWriteCompression: true,
writeBuf: make([]byte, defaultWriteBufferSize+maxFrameHeaderSize),
}
if key.compress {
c.newCompressionWriter = compressNoContextTakeover
}
err = c.WriteMessage(pm.messageType, pm.data)
frame.data = nc.buf.Bytes()
})
return pm.messageType, frame.data, err
}
type prepareConn struct {
buf bytes.Buffer
net.Conn
}
func (pc *prepareConn) Write(p []byte) (int, error) { return pc.buf.Write(p) }
func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil }

View File

@@ -0,0 +1,77 @@
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"bufio"
"encoding/base64"
"errors"
"net"
"net/http"
"net/url"
"strings"
)
type netDialerFunc func(network, addr string) (net.Conn, error)
func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) {
return fn(network, addr)
}
func init() {
proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) {
return &httpProxyDialer{proxyURL: proxyURL, fowardDial: forwardDialer.Dial}, nil
})
}
type httpProxyDialer struct {
proxyURL *url.URL
fowardDial func(network, addr string) (net.Conn, error)
}
func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) {
hostPort, _ := hostPortNoPort(hpd.proxyURL)
conn, err := hpd.fowardDial(network, hostPort)
if err != nil {
return nil, err
}
connectHeader := make(http.Header)
if user := hpd.proxyURL.User; user != nil {
proxyUser := user.Username()
if proxyPassword, passwordSet := user.Password(); passwordSet {
credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
connectHeader.Set("Proxy-Authorization", "Basic "+credential)
}
}
connectReq := &http.Request{
Method: "CONNECT",
URL: &url.URL{Opaque: addr},
Host: addr,
Header: connectHeader,
}
if err := connectReq.Write(conn); err != nil {
conn.Close()
return nil, err
}
// Read response. It's OK to use and discard buffered reader here becaue
// the remote server does not speak until spoken to.
br := bufio.NewReader(conn)
resp, err := http.ReadResponse(br, connectReq)
if err != nil {
conn.Close()
return nil, err
}
if resp.StatusCode != 200 {
conn.Close()
f := strings.SplitN(resp.Status, " ", 2)
return nil, errors.New(f[1])
}
return conn, nil
}

View File

@@ -0,0 +1,298 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"bufio"
"errors"
"net"
"net/http"
"net/url"
"strings"
"time"
)
// HandshakeError describes an error with the handshake from the peer.
type HandshakeError struct {
message string
}
func (e HandshakeError) Error() string { return e.message }
// Upgrader specifies parameters for upgrading an HTTP connection to a
// WebSocket connection.
type Upgrader struct {
// HandshakeTimeout specifies the duration for the handshake to complete.
HandshakeTimeout time.Duration
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
// size is zero, then buffers allocated by the HTTP server are used. The
// I/O buffer sizes do not limit the size of the messages that can be sent
// or received.
ReadBufferSize, WriteBufferSize int
// Subprotocols specifies the server's supported protocols in order of
// preference. If this field is set, then the Upgrade method negotiates a
// subprotocol by selecting the first match in this list with a protocol
// requested by the client.
Subprotocols []string
// Error specifies the function for generating HTTP error responses. If Error
// is nil, then http.Error is used to generate the HTTP response.
Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
// CheckOrigin returns true if the request Origin header is acceptable. If
// CheckOrigin is nil, then a safe default is used: return false if the
// Origin request header is present and the origin host is not equal to
// request Host header.
//
// A CheckOrigin function should carefully validate the request origin to
// prevent cross-site request forgery.
CheckOrigin func(r *http.Request) bool
// EnableCompression specify if the server should attempt to negotiate per
// message compression (RFC 7692). Setting this value to true does not
// guarantee that compression will be supported. Currently only "no context
// takeover" modes are supported.
EnableCompression bool
}
func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) {
err := HandshakeError{reason}
if u.Error != nil {
u.Error(w, r, status, err)
} else {
w.Header().Set("Sec-Websocket-Version", "13")
http.Error(w, http.StatusText(status), status)
}
return nil, err
}
// checkSameOrigin returns true if the origin is not set or is equal to the request host.
func checkSameOrigin(r *http.Request) bool {
origin := r.Header["Origin"]
if len(origin) == 0 {
return true
}
u, err := url.Parse(origin[0])
if err != nil {
return false
}
return equalASCIIFold(u.Host, r.Host)
}
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
if u.Subprotocols != nil {
clientProtocols := Subprotocols(r)
for _, serverProtocol := range u.Subprotocols {
for _, clientProtocol := range clientProtocols {
if clientProtocol == serverProtocol {
return clientProtocol
}
}
}
} else if responseHeader != nil {
return responseHeader.Get("Sec-Websocket-Protocol")
}
return ""
}
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
//
// The responseHeader is included in the response to the client's upgrade
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
// application negotiated subprotocol (Sec-WebSocket-Protocol).
//
// If the upgrade fails, then Upgrade replies to the client with an HTTP error
// response.
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
const badHandshake = "websocket: the client is not using the websocket protocol: "
if !tokenListContainsValue(r.Header, "Connection", "upgrade") {
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'upgrade' token not found in 'Connection' header")
}
if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'websocket' token not found in 'Upgrade' header")
}
if r.Method != "GET" {
return u.returnError(w, r, http.StatusMethodNotAllowed, badHandshake+"request method is not GET")
}
if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {
return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header")
}
if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok {
return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-WebSocket-Extensions' headers are unsupported")
}
checkOrigin := u.CheckOrigin
if checkOrigin == nil {
checkOrigin = checkSameOrigin
}
if !checkOrigin(r) {
return u.returnError(w, r, http.StatusForbidden, "websocket: request origin not allowed by Upgrader.CheckOrigin")
}
challengeKey := r.Header.Get("Sec-Websocket-Key")
if challengeKey == "" {
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-WebSocket-Key' header is missing or blank")
}
subprotocol := u.selectSubprotocol(r, responseHeader)
// Negotiate PMCE
var compress bool
if u.EnableCompression {
for _, ext := range parseExtensions(r.Header) {
if ext[""] != "permessage-deflate" {
continue
}
compress = true
break
}
}
var (
netConn net.Conn
err error
)
h, ok := w.(http.Hijacker)
if !ok {
return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker")
}
var brw *bufio.ReadWriter
netConn, brw, err = h.Hijack()
if err != nil {
return u.returnError(w, r, http.StatusInternalServerError, err.Error())
}
if brw.Reader.Buffered() > 0 {
netConn.Close()
return nil, errors.New("websocket: client sent data before handshake is complete")
}
c := newConnBRW(netConn, true, u.ReadBufferSize, u.WriteBufferSize, brw)
c.subprotocol = subprotocol
if compress {
c.newCompressionWriter = compressNoContextTakeover
c.newDecompressionReader = decompressNoContextTakeover
}
p := c.writeBuf[:0]
p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)
p = append(p, computeAcceptKey(challengeKey)...)
p = append(p, "\r\n"...)
if c.subprotocol != "" {
p = append(p, "Sec-WebSocket-Protocol: "...)
p = append(p, c.subprotocol...)
p = append(p, "\r\n"...)
}
if compress {
p = append(p, "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...)
}
for k, vs := range responseHeader {
if k == "Sec-Websocket-Protocol" {
continue
}
for _, v := range vs {
p = append(p, k...)
p = append(p, ": "...)
for i := 0; i < len(v); i++ {
b := v[i]
if b <= 31 {
// prevent response splitting.
b = ' '
}
p = append(p, b)
}
p = append(p, "\r\n"...)
}
}
p = append(p, "\r\n"...)
// Clear deadlines set by HTTP server.
netConn.SetDeadline(time.Time{})
if u.HandshakeTimeout > 0 {
netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout))
}
if _, err = netConn.Write(p); err != nil {
netConn.Close()
return nil, err
}
if u.HandshakeTimeout > 0 {
netConn.SetWriteDeadline(time.Time{})
}
return c, nil
}
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
//
// Deprecated: Use websocket.Upgrader instead.
//
// Upgrade does not perform origin checking. The application is responsible for
// checking the Origin header before calling Upgrade. An example implementation
// of the same origin policy check is:
//
// if req.Header.Get("Origin") != "http://"+req.Host {
// http.Error(w, "Origin not allowed", http.StatusForbidden)
// return
// }
//
// If the endpoint supports subprotocols, then the application is responsible
// for negotiating the protocol used on the connection. Use the Subprotocols()
// function to get the subprotocols requested by the client. Use the
// Sec-Websocket-Protocol response header to specify the subprotocol selected
// by the application.
//
// The responseHeader is included in the response to the client's upgrade
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
// negotiated subprotocol (Sec-Websocket-Protocol).
//
// The connection buffers IO to the underlying network connection. The
// readBufSize and writeBufSize parameters specify the size of the buffers to
// use. Messages can be larger than the buffers.
//
// If the request is not a valid WebSocket handshake, then Upgrade returns an
// error of type HandshakeError. Applications should handle this error by
// replying to the client with an HTTP error response.
func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) {
u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize}
u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) {
// don't return errors to maintain backwards compatibility
}
u.CheckOrigin = func(r *http.Request) bool {
// allow all connections by default
return true
}
return u.Upgrade(w, r, responseHeader)
}
// Subprotocols returns the subprotocols requested by the client in the
// Sec-Websocket-Protocol header.
func Subprotocols(r *http.Request) []string {
h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol"))
if h == "" {
return nil
}
protocols := strings.Split(h, ",")
for i := range protocols {
protocols[i] = strings.TrimSpace(protocols[i])
}
return protocols
}
// IsWebSocketUpgrade returns true if the client requested upgrade to the
// WebSocket protocol.
func IsWebSocketUpgrade(r *http.Request) bool {
return tokenListContainsValue(r.Header, "Connection", "upgrade") &&
tokenListContainsValue(r.Header, "Upgrade", "websocket")
}

View File

@@ -0,0 +1,237 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"crypto/rand"
"crypto/sha1"
"encoding/base64"
"io"
"net/http"
"strings"
"unicode/utf8"
)
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
func computeAcceptKey(challengeKey string) string {
h := sha1.New()
h.Write([]byte(challengeKey))
h.Write(keyGUID)
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
func generateChallengeKey() (string, error) {
p := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, p); err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(p), nil
}
// Octet types from RFC 2616.
var octetTypes [256]byte
const (
isTokenOctet = 1 << iota
isSpaceOctet
)
func init() {
// From RFC 2616
//
// OCTET = <any 8-bit sequence of data>
// CHAR = <any US-ASCII character (octets 0 - 127)>
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
// CR = <US-ASCII CR, carriage return (13)>
// LF = <US-ASCII LF, linefeed (10)>
// SP = <US-ASCII SP, space (32)>
// HT = <US-ASCII HT, horizontal-tab (9)>
// <"> = <US-ASCII double-quote mark (34)>
// CRLF = CR LF
// LWS = [CRLF] 1*( SP | HT )
// TEXT = <any OCTET except CTLs, but including LWS>
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
// token = 1*<any CHAR except CTLs or separators>
// qdtext = <any TEXT except <">>
for c := 0; c < 256; c++ {
var t byte
isCtl := c <= 31 || c == 127
isChar := 0 <= c && c <= 127
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
t |= isSpaceOctet
}
if isChar && !isCtl && !isSeparator {
t |= isTokenOctet
}
octetTypes[c] = t
}
}
func skipSpace(s string) (rest string) {
i := 0
for ; i < len(s); i++ {
if octetTypes[s[i]]&isSpaceOctet == 0 {
break
}
}
return s[i:]
}
func nextToken(s string) (token, rest string) {
i := 0
for ; i < len(s); i++ {
if octetTypes[s[i]]&isTokenOctet == 0 {
break
}
}
return s[:i], s[i:]
}
func nextTokenOrQuoted(s string) (value string, rest string) {
if !strings.HasPrefix(s, "\"") {
return nextToken(s)
}
s = s[1:]
for i := 0; i < len(s); i++ {
switch s[i] {
case '"':
return s[:i], s[i+1:]
case '\\':
p := make([]byte, len(s)-1)
j := copy(p, s[:i])
escape := true
for i = i + 1; i < len(s); i++ {
b := s[i]
switch {
case escape:
escape = false
p[j] = b
j++
case b == '\\':
escape = true
case b == '"':
return string(p[:j]), s[i+1:]
default:
p[j] = b
j++
}
}
return "", ""
}
}
return "", ""
}
// equalASCIIFold returns true if s is equal to t with ASCII case folding.
func equalASCIIFold(s, t string) bool {
for s != "" && t != "" {
sr, size := utf8.DecodeRuneInString(s)
s = s[size:]
tr, size := utf8.DecodeRuneInString(t)
t = t[size:]
if sr == tr {
continue
}
if 'A' <= sr && sr <= 'Z' {
sr = sr + 'a' - 'A'
}
if 'A' <= tr && tr <= 'Z' {
tr = tr + 'a' - 'A'
}
if sr != tr {
return false
}
}
return s == t
}
// tokenListContainsValue returns true if the 1#token header with the given
// name contains a token equal to value with ASCII case folding.
func tokenListContainsValue(header http.Header, name string, value string) bool {
headers:
for _, s := range header[name] {
for {
var t string
t, s = nextToken(skipSpace(s))
if t == "" {
continue headers
}
s = skipSpace(s)
if s != "" && s[0] != ',' {
continue headers
}
if equalASCIIFold(t, value) {
return true
}
if s == "" {
continue headers
}
s = s[1:]
}
}
return false
}
// parseExtensiosn parses WebSocket extensions from a header.
func parseExtensions(header http.Header) []map[string]string {
// From RFC 6455:
//
// Sec-WebSocket-Extensions = extension-list
// extension-list = 1#extension
// extension = extension-token *( ";" extension-param )
// extension-token = registered-token
// registered-token = token
// extension-param = token [ "=" (token | quoted-string) ]
// ;When using the quoted-string syntax variant, the value
// ;after quoted-string unescaping MUST conform to the
// ;'token' ABNF.
var result []map[string]string
headers:
for _, s := range header["Sec-Websocket-Extensions"] {
for {
var t string
t, s = nextToken(skipSpace(s))
if t == "" {
continue headers
}
ext := map[string]string{"": t}
for {
s = skipSpace(s)
if !strings.HasPrefix(s, ";") {
break
}
var k string
k, s = nextToken(skipSpace(s[1:]))
if k == "" {
continue headers
}
s = skipSpace(s)
var v string
if strings.HasPrefix(s, "=") {
v, s = nextTokenOrQuoted(skipSpace(s[1:]))
s = skipSpace(s)
}
if s != "" && s[0] != ',' && s[0] != ';' {
continue headers
}
ext[k] = v
}
if s != "" && s[0] != ',' {
continue headers
}
result = append(result, ext)
if s == "" {
continue headers
}
s = s[1:]
}
}
return result
}

View File

@@ -0,0 +1,473 @@
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
//go:generate bundle -o x_net_proxy.go golang.org/x/net/proxy
// Package proxy provides support for a variety of protocols to proxy network
// data.
//
package websocket
import (
"errors"
"io"
"net"
"net/url"
"os"
"strconv"
"strings"
"sync"
)
type proxy_direct struct{}
// Direct is a direct proxy: one that makes network connections directly.
var proxy_Direct = proxy_direct{}
func (proxy_direct) Dial(network, addr string) (net.Conn, error) {
return net.Dial(network, addr)
}
// A PerHost directs connections to a default Dialer unless the host name
// requested matches one of a number of exceptions.
type proxy_PerHost struct {
def, bypass proxy_Dialer
bypassNetworks []*net.IPNet
bypassIPs []net.IP
bypassZones []string
bypassHosts []string
}
// NewPerHost returns a PerHost Dialer that directs connections to either
// defaultDialer or bypass, depending on whether the connection matches one of
// the configured rules.
func proxy_NewPerHost(defaultDialer, bypass proxy_Dialer) *proxy_PerHost {
return &proxy_PerHost{
def: defaultDialer,
bypass: bypass,
}
}
// Dial connects to the address addr on the given network through either
// defaultDialer or bypass.
func (p *proxy_PerHost) Dial(network, addr string) (c net.Conn, err error) {
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
return p.dialerForRequest(host).Dial(network, addr)
}
func (p *proxy_PerHost) dialerForRequest(host string) proxy_Dialer {
if ip := net.ParseIP(host); ip != nil {
for _, net := range p.bypassNetworks {
if net.Contains(ip) {
return p.bypass
}
}
for _, bypassIP := range p.bypassIPs {
if bypassIP.Equal(ip) {
return p.bypass
}
}
return p.def
}
for _, zone := range p.bypassZones {
if strings.HasSuffix(host, zone) {
return p.bypass
}
if host == zone[1:] {
// For a zone ".example.com", we match "example.com"
// too.
return p.bypass
}
}
for _, bypassHost := range p.bypassHosts {
if bypassHost == host {
return p.bypass
}
}
return p.def
}
// AddFromString parses a string that contains comma-separated values
// specifying hosts that should use the bypass proxy. Each value is either an
// IP address, a CIDR range, a zone (*.example.com) or a host name
// (localhost). A best effort is made to parse the string and errors are
// ignored.
func (p *proxy_PerHost) AddFromString(s string) {
hosts := strings.Split(s, ",")
for _, host := range hosts {
host = strings.TrimSpace(host)
if len(host) == 0 {
continue
}
if strings.Contains(host, "/") {
// We assume that it's a CIDR address like 127.0.0.0/8
if _, net, err := net.ParseCIDR(host); err == nil {
p.AddNetwork(net)
}
continue
}
if ip := net.ParseIP(host); ip != nil {
p.AddIP(ip)
continue
}
if strings.HasPrefix(host, "*.") {
p.AddZone(host[1:])
continue
}
p.AddHost(host)
}
}
// AddIP specifies an IP address that will use the bypass proxy. Note that
// this will only take effect if a literal IP address is dialed. A connection
// to a named host will never match an IP.
func (p *proxy_PerHost) AddIP(ip net.IP) {
p.bypassIPs = append(p.bypassIPs, ip)
}
// AddNetwork specifies an IP range that will use the bypass proxy. Note that
// this will only take effect if a literal IP address is dialed. A connection
// to a named host will never match.
func (p *proxy_PerHost) AddNetwork(net *net.IPNet) {
p.bypassNetworks = append(p.bypassNetworks, net)
}
// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of
// "example.com" matches "example.com" and all of its subdomains.
func (p *proxy_PerHost) AddZone(zone string) {
if strings.HasSuffix(zone, ".") {
zone = zone[:len(zone)-1]
}
if !strings.HasPrefix(zone, ".") {
zone = "." + zone
}
p.bypassZones = append(p.bypassZones, zone)
}
// AddHost specifies a host name that will use the bypass proxy.
func (p *proxy_PerHost) AddHost(host string) {
if strings.HasSuffix(host, ".") {
host = host[:len(host)-1]
}
p.bypassHosts = append(p.bypassHosts, host)
}
// A Dialer is a means to establish a connection.
type proxy_Dialer interface {
// Dial connects to the given address via the proxy.
Dial(network, addr string) (c net.Conn, err error)
}
// Auth contains authentication parameters that specific Dialers may require.
type proxy_Auth struct {
User, Password string
}
// FromEnvironment returns the dialer specified by the proxy related variables in
// the environment.
func proxy_FromEnvironment() proxy_Dialer {
allProxy := proxy_allProxyEnv.Get()
if len(allProxy) == 0 {
return proxy_Direct
}
proxyURL, err := url.Parse(allProxy)
if err != nil {
return proxy_Direct
}
proxy, err := proxy_FromURL(proxyURL, proxy_Direct)
if err != nil {
return proxy_Direct
}
noProxy := proxy_noProxyEnv.Get()
if len(noProxy) == 0 {
return proxy
}
perHost := proxy_NewPerHost(proxy, proxy_Direct)
perHost.AddFromString(noProxy)
return perHost
}
// proxySchemes is a map from URL schemes to a function that creates a Dialer
// from a URL with such a scheme.
var proxy_proxySchemes map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error)
// RegisterDialerType takes a URL scheme and a function to generate Dialers from
// a URL with that scheme and a forwarding Dialer. Registered schemes are used
// by FromURL.
func proxy_RegisterDialerType(scheme string, f func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) {
if proxy_proxySchemes == nil {
proxy_proxySchemes = make(map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error))
}
proxy_proxySchemes[scheme] = f
}
// FromURL returns a Dialer given a URL specification and an underlying
// Dialer for it to make network requests.
func proxy_FromURL(u *url.URL, forward proxy_Dialer) (proxy_Dialer, error) {
var auth *proxy_Auth
if u.User != nil {
auth = new(proxy_Auth)
auth.User = u.User.Username()
if p, ok := u.User.Password(); ok {
auth.Password = p
}
}
switch u.Scheme {
case "socks5":
return proxy_SOCKS5("tcp", u.Host, auth, forward)
}
// If the scheme doesn't match any of the built-in schemes, see if it
// was registered by another package.
if proxy_proxySchemes != nil {
if f, ok := proxy_proxySchemes[u.Scheme]; ok {
return f(u, forward)
}
}
return nil, errors.New("proxy: unknown scheme: " + u.Scheme)
}
var (
proxy_allProxyEnv = &proxy_envOnce{
names: []string{"ALL_PROXY", "all_proxy"},
}
proxy_noProxyEnv = &proxy_envOnce{
names: []string{"NO_PROXY", "no_proxy"},
}
)
// envOnce looks up an environment variable (optionally by multiple
// names) once. It mitigates expensive lookups on some platforms
// (e.g. Windows).
// (Borrowed from net/http/transport.go)
type proxy_envOnce struct {
names []string
once sync.Once
val string
}
func (e *proxy_envOnce) Get() string {
e.once.Do(e.init)
return e.val
}
func (e *proxy_envOnce) init() {
for _, n := range e.names {
e.val = os.Getenv(n)
if e.val != "" {
return
}
}
}
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
// with an optional username and password. See RFC 1928 and RFC 1929.
func proxy_SOCKS5(network, addr string, auth *proxy_Auth, forward proxy_Dialer) (proxy_Dialer, error) {
s := &proxy_socks5{
network: network,
addr: addr,
forward: forward,
}
if auth != nil {
s.user = auth.User
s.password = auth.Password
}
return s, nil
}
type proxy_socks5 struct {
user, password string
network, addr string
forward proxy_Dialer
}
const proxy_socks5Version = 5
const (
proxy_socks5AuthNone = 0
proxy_socks5AuthPassword = 2
)
const proxy_socks5Connect = 1
const (
proxy_socks5IP4 = 1
proxy_socks5Domain = 3
proxy_socks5IP6 = 4
)
var proxy_socks5Errors = []string{
"",
"general failure",
"connection forbidden",
"network unreachable",
"host unreachable",
"connection refused",
"TTL expired",
"command not supported",
"address type not supported",
}
// Dial connects to the address addr on the given network via the SOCKS5 proxy.
func (s *proxy_socks5) Dial(network, addr string) (net.Conn, error) {
switch network {
case "tcp", "tcp6", "tcp4":
default:
return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
}
conn, err := s.forward.Dial(s.network, s.addr)
if err != nil {
return nil, err
}
if err := s.connect(conn, addr); err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
// connect takes an existing connection to a socks5 proxy server,
// and commands the server to extend that connection to target,
// which must be a canonical address with a host and port.
func (s *proxy_socks5) connect(conn net.Conn, target string) error {
host, portStr, err := net.SplitHostPort(target)
if err != nil {
return err
}
port, err := strconv.Atoi(portStr)
if err != nil {
return errors.New("proxy: failed to parse port number: " + portStr)
}
if port < 1 || port > 0xffff {
return errors.New("proxy: port number out of range: " + portStr)
}
// the size here is just an estimate
buf := make([]byte, 0, 6+len(host))
buf = append(buf, proxy_socks5Version)
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
buf = append(buf, 2 /* num auth methods */, proxy_socks5AuthNone, proxy_socks5AuthPassword)
} else {
buf = append(buf, 1 /* num auth methods */, proxy_socks5AuthNone)
}
if _, err := conn.Write(buf); err != nil {
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if buf[0] != 5 {
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
}
if buf[1] == 0xff {
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
}
// See RFC 1929
if buf[1] == proxy_socks5AuthPassword {
buf = buf[:0]
buf = append(buf, 1 /* password protocol version */)
buf = append(buf, uint8(len(s.user)))
buf = append(buf, s.user...)
buf = append(buf, uint8(len(s.password)))
buf = append(buf, s.password...)
if _, err := conn.Write(buf); err != nil {
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if buf[1] != 0 {
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
}
}
buf = buf[:0]
buf = append(buf, proxy_socks5Version, proxy_socks5Connect, 0 /* reserved */)
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
buf = append(buf, proxy_socks5IP4)
ip = ip4
} else {
buf = append(buf, proxy_socks5IP6)
}
buf = append(buf, ip...)
} else {
if len(host) > 255 {
return errors.New("proxy: destination host name too long: " + host)
}
buf = append(buf, proxy_socks5Domain)
buf = append(buf, byte(len(host)))
buf = append(buf, host...)
}
buf = append(buf, byte(port>>8), byte(port))
if _, err := conn.Write(buf); err != nil {
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
failure := "unknown error"
if int(buf[1]) < len(proxy_socks5Errors) {
failure = proxy_socks5Errors[buf[1]]
}
if len(failure) > 0 {
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
}
bytesToDiscard := 0
switch buf[3] {
case proxy_socks5IP4:
bytesToDiscard = net.IPv4len
case proxy_socks5IP6:
bytesToDiscard = net.IPv6len
case proxy_socks5Domain:
_, err := io.ReadFull(conn, buf[:1])
if err != nil {
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
bytesToDiscard = int(buf[0])
default:
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
}
if cap(buf) < bytesToDiscard {
buf = make([]byte, bytesToDiscard)
} else {
buf = buf[:bytesToDiscard]
}
if _, err := io.ReadFull(conn, buf); err != nil {
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
// Also need to discard the port number
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
}
return nil
}

View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Manfred Touron
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,171 @@
# AnonUUID
[![Build Status](https://travis-ci.org/moul/anonuuid.svg)](https://travis-ci.org/moul/anonuuid)
[![GoDoc](https://godoc.org/github.com/moul/anonuuid?status.svg)](https://godoc.org/github.com/moul/anonuuid)
[![Coverage Status](https://coveralls.io/repos/moul/anonuuid/badge.svg?branch=master&service=github)](https://coveralls.io/github/moul/anonuuid?branch=master)
:wrench: Anonymize UUIDs outputs (written in Golang)
![AnonUUID Logo](https://raw.githubusercontent.com/moul/anonuuid/master/assets/anonuuid.png)
**anonuuid** anonymize an input string by replacing all UUIDs by an anonymized
new one.
The fake UUIDs are cached, so if AnonUUID encounter the same real UUIDs multiple
times, the translation will be the same.
## Usage
```console
$ anonuuid --help
NAME:
anonuuid - Anonymize UUIDs outputs
USAGE:
anonuuid [global options] command [command options] [arguments...]
VERSION:
1.0.0-dev
AUTHOR(S):
Manfred Touron <https://github.com/moul>
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--hexspeak Generate hexspeak style fake UUIDs
--random, -r Generate random fake UUIDs
--keep-beginning Keep first part of the UUID unchanged
--keep-end Keep last part of the UUID unchanged
--prefix, -p Prefix generated UUIDs
--suffix Suffix generated UUIDs
--help, -h show help
--version, -v print the version
```
## Example
Replace all UUIDs and cache the correspondance.
```command
$ anonuuid git:(master) ✗ cat <<EOF | anonuuid
VOLUMES_0_SERVER_ID=15573749-c89d-41dd-a655-16e79bed52e0
VOLUMES_0_SERVER_NAME=hello
VOLUMES_0_ID=c245c3cb-3336-4567-ada1-70cb1fe4eefe
VOLUMES_0_SIZE=50000000000
ORGANIZATION=fe1e54e8-d69d-4f7c-a9f1-42069e03da31
TEST=15573749-c89d-41dd-a655-16e79bed52e0
EOF
VOLUMES_0_SERVER_ID=00000000-0000-0000-0000-000000000000
VOLUMES_0_SERVER_NAME=hello
VOLUMES_0_ID=11111111-1111-1111-1111-111111111111
VOLUMES_0_SIZE=50000000000
ORGANIZATION=22222222-2222-2222-2222-222222222222
TEST=00000000-0000-0000-0000-000000000000
```
---
Inline
```command
$ echo 'VOLUMES_0_SERVER_ID=15573749-c89d-41dd-a655-16e79bed52e0 VOLUMES_0_SERVER_NAME=bitrig1 VOLUMES_0_ID=c245c3cb-3336-4567-ada1-70cb1fe4eefe VOLUMES_0_SIZE=50000000000 ORGANIZATION=fe1e54e8-d69d-4f7c-a9f1-42069e03da31 TEST=15573749-c89d-41dd-a655-16e79bed52e0' | ./anonuuid
VOLUMES_0_SERVER_ID=00000000-0000-0000-0000-000000000000 VOLUMES_0_SERVER_NAME=bitrig1 VOLUMES_0_ID=11111111-1111-1111-1111-111111111111 VOLUMES_0_SIZE=50000000000 ORGANIZATION=22222222-2222-2222-2222-222222222222 TEST=00000000-0000-0000-0000-000000000000
```
---
```command
$ curl -s https://api.pathwar.net/achievements\?max_results\=2 | anonuuid | jq .
{
"_items": [
{
"_updated": "Thu, 30 Apr 2015 13:00:58 GMT",
"description": "You",
"_links": {
"self": {
"href": "achievements/00000000-0000-0000-0000-000000000000",
"title": "achievement"
}
},
"_created": "Thu, 30 Apr 2015 13:00:58 GMT",
"_id": "00000000-0000-0000-0000-000000000000",
"_etag": "b1e9f850accfcb952c58384db41d89728890a69f",
"name": "finish-20-levels"
},
{
"_updated": "Thu, 30 Apr 2015 13:01:07 GMT",
"description": "You",
"_links": {
"self": {
"href": "achievements/11111111-1111-1111-1111-111111111111",
"title": "achievement"
}
},
"_created": "Thu, 30 Apr 2015 13:01:07 GMT",
"_id": "11111111-1111-1111-1111-111111111111",
"_etag": "c346f5e1c4f7658f2dfc4124efa87aba909a9821",
"name": "buy-30-levels"
}
],
"_links": {
"self": {
"href": "achievements?max_results=2",
"title": "achievements"
},
"last": {
"href": "achievements?max_results=2&page=23",
"title": "last page"
},
"parent": {
"href": "/",
"title": "home"
},
"next": {
"href": "achievements?max_results=2&page=2",
"title": "next page"
}
},
"_meta": {
"max_results": 2,
"total": 46,
"page": 1
}
}
```
## Install
Using go
- `go get github.com/moul/anonuuid/...`
## Changelog
### [v1.1.0](https://github.com/moul/anonuuid/releases/tag/v1.1.0) (2018-04-02)
* Switch from `Godep` to `Glide`
* Add mutex to protect the cache field ([@QuentinPerez](https://github.com/QuentinPerez))
* Switch from `Party` to `Godep`
* Support of `--suffix=xxx`, `--keep-beginning` and `--keep-end` options ([#4](https://github.com/moul/anonuuid/issues/4))
* Using **party** to stabilize vendor package versions ([#8](https://github.com/moul/anonuuid/issues/8))
* Add homebrew package ([#6](https://github.com/moul/anonuuid/issues/6))
[full commits list](https://github.com/moul/anonuuid/compare/v1.0.0...master)
### [v1.0.0](https://github.com/moul/anonuuid/releases/tag/v1.0.0) (2015-10-07)
**Initial release**
#### Features
* Support of `--hexspeak` option
* Support of `--random` option
* Support of `--prefix` option
* Anonymize input stream
* Anonymize files
## License
MIT

View File

@@ -0,0 +1,229 @@
package anonuuid
import (
"fmt"
"log"
"math/rand"
"regexp"
"strings"
"sync"
"time"
)
var (
// UUIDRegex is the regex used to find UUIDs in texts
UUIDRegex = "[a-z0-9]{8}-[a-z0-9]{4}-[1-5][a-z0-9]{3}-[a-z0-9]{4}-[a-z0-9]{12}"
)
// AnonUUID is the main structure, it contains the cache map and helpers
type AnonUUID struct {
cache map[string]string
guard sync.Mutex // cache guard
// Hexspeak flag will generate hexspeak style fake UUIDs
Hexspeak bool
// Random flag will generate random fake UUIDs
Random bool
// Prefix will be the beginning of all the generated UUIDs
Prefix string
// Suffix will be the end of all the generated UUIDs
Suffix string
// AllowNonUUIDInput tells FakeUUID to accept non UUID input string
AllowNonUUIDInput bool
// KeepBeginning tells FakeUUID to let the beginning of the UUID as it is
KeepBeginning bool
// KeepEnd tells FakeUUID to let the last part of the UUID as it is
KeepEnd bool
}
// Sanitize takes a string as input and return sanitized string
func (a *AnonUUID) Sanitize(input string) string {
r := regexp.MustCompile(UUIDRegex)
return r.ReplaceAllStringFunc(input, func(m string) string {
parts := r.FindStringSubmatch(m)
return a.FakeUUID(parts[0])
})
}
// FakeUUID takes a word (real UUID or standard string) and returns its corresponding (mapped) fakeUUID
func (a *AnonUUID) FakeUUID(input string) string {
if !a.AllowNonUUIDInput {
err := IsUUID(input)
if err != nil {
return "invaliduuid"
}
}
a.guard.Lock()
defer a.guard.Unlock()
if _, ok := a.cache[input]; !ok {
if a.KeepBeginning {
a.Prefix = input[:8]
}
if a.KeepEnd {
a.Suffix = input[36-12:]
}
if a.Prefix != "" {
matched, err := regexp.MatchString("^[a-z0-9]+$", a.Prefix)
if err != nil || !matched {
a.Prefix = "invalidprefix"
}
}
if a.Suffix != "" {
matched, err := regexp.MatchString("^[a-z0-9]+$", a.Suffix)
if err != nil || !matched {
a.Suffix = "invalsuffix"
}
}
var fakeUUID string
var err error
if a.Hexspeak {
fakeUUID, err = GenerateHexspeakUUID(len(a.cache))
} else if a.Random {
fakeUUID, err = GenerateRandomUUID(10)
} else {
fakeUUID, err = GenerateLenUUID(len(a.cache))
}
if err != nil {
log.Fatalf("Failed to generate an UUID: %v", err)
}
if a.Prefix != "" {
fakeUUID, err = PrefixUUID(a.Prefix, fakeUUID)
if err != nil {
panic(err)
}
}
if a.Suffix != "" {
fakeUUID, err = SuffixUUID(a.Suffix, fakeUUID)
if err != nil {
panic(err)
}
}
// FIXME: check for duplicates and retry
a.cache[input] = fakeUUID
}
return a.cache[input]
}
// New returns a prepared AnonUUID structure
func New() *AnonUUID {
return &AnonUUID{
cache: make(map[string]string),
Hexspeak: false,
Random: false,
}
}
func init() {
rand.Seed(time.Now().UTC().UnixNano())
}
// PrefixUUID returns a prefixed UUID
func PrefixUUID(prefix string, uuid string) (string, error) {
uuidLetters := uuid[:8] + uuid[9:13] + uuid[14:18] + uuid[19:23] + uuid[24:36]
prefixedUUID, err := FormatUUID(prefix + uuidLetters)
if err != nil {
return "", err
}
return prefixedUUID, nil
}
// SuffixUUID returns a suffixed UUID
func SuffixUUID(suffix string, uuid string) (string, error) {
uuidLetters := uuid[:8] + uuid[9:13] + uuid[14:18] + uuid[19:23] + uuid[24:36]
uuidLetters = uuidLetters[:32-len(suffix)] + suffix
suffixedUUID, err := FormatUUID(uuidLetters)
if err != nil {
return "", err
}
return suffixedUUID, nil
}
// IsUUID returns nil if the input is an UUID, else it returns an error
func IsUUID(input string) error {
matched, err := regexp.MatchString("^"+UUIDRegex+"$", input)
if err != nil {
return err
}
if !matched {
return fmt.Errorf("String '%s' is not a valid UUID", input)
}
return nil
}
// FormatUUID takes a string in input and return an UUID formatted string by repeating the string and placing dashes if necessary
func FormatUUID(part string) (string, error) {
if len(part) < 1 {
return "", fmt.Errorf("Empty UUID")
}
if len(part) < 32 {
part = strings.Repeat(part, 32)
}
if len(part) > 32 {
part = part[:32]
}
uuid := part[:8] + "-" + part[8:12] + "-1" + part[13:16] + "-" + part[16:20] + "-" + part[20:32]
err := IsUUID(uuid)
if err != nil {
return "", err
}
return uuid, nil
}
// GenerateRandomUUID returns an UUID based on random strings
func GenerateRandomUUID(length int) (string, error) {
var letters = []rune("abcdef0123456789")
b := make([]rune, length)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return FormatUUID(string(b))
}
// GenerateHexspeakUUID returns an UUID formatted string containing hexspeak words
func GenerateHexspeakUUID(i int) (string, error) {
if i < 0 {
i = -i
}
hexspeaks := []string{
"0ff1ce",
"31337",
"4b1d",
"badc0de",
"badcafe",
"badf00d",
"deadbabe",
"deadbeef",
"deadc0de",
"deadfeed",
"fee1bad",
}
return FormatUUID(hexspeaks[i%len(hexspeaks)])
}
// GenerateLenUUID returns an UUID formatted string based on an index number
func GenerateLenUUID(i int) (string, error) {
if i < 0 {
i = 2<<29 + i
}
return FormatUUID(fmt.Sprintf("%x", i))
}

View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Manfred Touron
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,192 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2013-2017 Docker, Inc.
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
https://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.

View File

@@ -0,0 +1,217 @@
# gotty-client
:wrench: Terminal client for [GoTTY](https://github.com/yudai/gotty).
![](https://raw.githubusercontent.com/moul/gotty-client/master/resources/gotty-client.png)
[![Build Status](https://travis-ci.org/moul/gotty-client.svg?branch=master)](https://travis-ci.org/moul/gotty-client)
[![GoDoc](https://godoc.org/github.com/moul/gotty-client?status.svg)](https://godoc.org/github.com/moul/gotty-client)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmoul%2Fgotty-client.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmoul%2Fgotty-client?ref=badge_shield)
```ruby
┌─────────────────┐
┌──────▶│ /bin/bash
└─────────────────┘
┌──────────────┐ ┌──────────┐
Gotty
┌───────┐ ┌──▶│ Browser │───────┐
└──────────────┘ ┌─────────────────┐
Bob │───┤ websockets─▶│ │─▶│ emacs /var/www
╔═ ══ ══ ══ ══ └─────────────────┘
└───────┘ └──▶║ gotty-client ───────┘
╚═ ══ ══ ══ ══ └──────────┘
┌─────────────────┐
└──────▶│ tmux attach
└─────────────────┘
```
## Example
Server side ([GoTTY](https://github.com/yudai/gotty))
```console
$ gotty -p 9191 sh -c 'while true; do date; sleep 1; done'
2015/08/24 18:54:31 Server is starting with command: sh -c while true; do date; sleep 1; done
2015/08/24 18:54:31 URL: http://[::1]:9191/
2015/08/24 18:54:34 GET /ws
2015/08/24 18:54:34 New client connected: 127.0.0.1:61811
2015/08/24 18:54:34 Command is running for client 127.0.0.1:61811 with PID 64834
2015/08/24 18:54:39 Command exited for: 127.0.0.1:61811
2015/08/24 18:54:39 Connection closed: 127.0.0.1:61811
...
```
**Client side**
```console
$ gotty-client http://localhost:9191/
INFO[0000] New title: GoTTY - sh -c while true; do date; sleep 1; done (jean-michel-van-damme.local)
WARN[0000] Unhandled protocol message: json pref: 2{}
Mon Aug 24 18:54:34 CEST 2015
Mon Aug 24 18:54:35 CEST 2015
Mon Aug 24 18:54:36 CEST 2015
Mon Aug 24 18:54:37 CEST 2015
Mon Aug 24 18:54:38 CEST 2015
^C
```
## Usage
```console
$ gotty-client -h
NAME:
gotty-client - GoTTY client for your terminal
USAGE:
gotty-client [global options] command [command options] GOTTY_URL
AUTHOR:
Manfred Touron <https://github.com/moul/gotty-client>
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--debug, -D Enable debug mode [$GOTTY_CLIENT_DEBUG]
--skip-tls-verify Skip TLS verify [$SKIP_TLS_VERIFY]
--use-proxy-from-env Use Proxy from environment [$USE_PROXY_FROM_ENV]
--detach-keys value Key sequence for detaching gotty-client (default: "ctrl-p,ctrl-q")
--v2 For Gotty 2.0 [$GOTTY_CLIENT_GOTTY2]
--ws-origin value, -w value WebSocket Origin URL [$GOTTY_CLIENT_WS_ORIGIN]
--help, -h show help
--version, -v print the version
```
## Install
Install latest version using Golang (recommended)
```console
$ go get github.com/moul/gotty-client/cmd/gotty-client
```
---
Install latest version using Homebrew (Mac OS X)
```console
$ brew install https://raw.githubusercontent.com/moul/gotty-client/master/contrib/homebrew/gotty-client.rb --HEAD
```
or the latest released version
```console
$ brew install https://raw.githubusercontent.com/moul/gotty-client/master/contrib/homebrew/gotty-client.rb
```
## Changelog
### master (unreleased)
* Fix TTY restoring by switching to [`github.com/moby/moby/pkg/term`](https://github.com/moby/moby/tree/master/pkg/term) ([#59](https://github.com/moul/gotty-client/pull/59) + ([#60](https://github.com/moul/gotty-client/pull/60) + ([#62](https://github.com/moul/gotty-client/pull/62) ([@Sh4d1](https://github.com/Sh4d1))
[full commits list](https://github.com/moul/gotty-client/compare/v1.7.0...master)
### [v1.7.0](https://github.com/moul/gotty-client/releases/tag/v1.7.0) (2018-04-11)
* Add `--detach-keys` option ([#52](https://github.com/moul/gotty-client/issues/52))
* Cross build on Solaris ([#55](https://github.com/moul/gotty-client/pull/55) ([@dimtion](https://github.com/dimtion))
* Fix terminal resizing issue ([#56](https://github.com/moul/gotty-client/pull/56) ([@byung2](https://github.com/byung2))
* Support for gotty v2.0 ([#58](https://github.com/moul/gotty-client/pull/58) ([@byung2](https://github.com/byung2))
* Support ws-origin (CORS) ([#58](https://github.com/moul/gotty-client/pull/58) ([@byung2](https://github.com/byung2))
[full commits list](https://github.com/moul/gotty-client/compare/v1.6.1...v1.7.0)
### [v1.6.1](https://github.com/moul/gotty-client/releases/tag/v1.6.1) (2017-01-19)
* Do not exit on EOF ([#45](https://github.com/moul/gotty-client/pull/45)) ([@gurjeet](https://github.com/gurjeet))
[full commits list](https://github.com/moul/gotty-client/compare/v1.6.0...v1.6.1)
### [v1.6.0](https://github.com/moul/gotty-client/releases/tag/v1.6.0) (2016-05-23)
* Support of `--use-proxy-from-env` (Add Proxy support) ([#36](https://github.com/moul/gotty-client/pull/36)) ([@byung2](https://github.com/byung2))
* Add debug mode ([#18](https://github.com/moul/gotty-client/issues/18))
* Fix argument passing ([#16](https://github.com/moul/gotty-client/issues/16))
* Remove warnings + golang fixes and refactoring ([@QuentinPerez](https://github.com/QuentinPerez))
[full commits list](https://github.com/moul/gotty-client/compare/v1.5.0...v1.6.0)
### [v1.5.0](https://github.com/moul/gotty-client/releases/tag/v1.5.0) (2016-02-18)
* Add autocomplete support ([#19](https://github.com/moul/gotty-client/issues/19))
* Switch from `Party` to `Godep`
* Fix terminal data being interpreted as format string ([#34](https://github.com/moul/gotty-client/pull/34)) ([@mickael9](https://github.com/mickael9))
[full commits list](https://github.com/moul/gotty-client/compare/v1.4.0...v1.5.0)
### [v1.4.0](https://github.com/moul/gotty-client/releases/tag/v1.4.0) (2015-12-09)
* Remove solaris,plan9,nacl for `.goxc.json`
* Add an error if the go version is lower than 1.5
* Flexible parsing of the input URL
* Add tests
* Support of `--skip-tls-verify`
[full commits list](https://github.com/moul/gotty-client/compare/v1.3.0...v1.4.0)
### [v1.3.0](https://github.com/moul/gotty-client/releases/tag/v1.3.0) (2015-10-27)
* Fix `connected` state when using `Connect()` + `Loop()` methods
* Add `ExitLoop` which allow to exit from `Loop` function
[full commits list](https://github.com/moul/gotty-client/compare/v1.2.0...v1.3.0)
### [v1.2.0](https://github.com/moul/gotty-client/releases/tag/v1.2.0) (2015-10-23)
* Removed an annoying warning when exiting connection ([#22](https://github.com/moul/gotty-client/issues/22)) ([@QuentinPerez](https://github.com/QuentinPerez))
* Add the ability to configure alternative stdout ([#21](https://github.com/moul/gotty-client/issues/21)) ([@QuentinPerez](https://github.com/QuentinPerez))
* Refactored the goroutine system with select, improve speed and stability ([@QuentinPerez](https://github.com/QuentinPerez))
* Add debug mode (`--debug`/`-D`) ([#18](https://github.com/moul/gotty-client/issues/18))
* Improve error message when connecting by checking HTTP status code
* Fix arguments passing ([#16](https://github.com/moul/gotty-client/issues/16))
* Dropped support for golang<1.5
* Small fixes
[full commits list](https://github.com/moul/gotty-client/compare/v1.1.0...v1.2.0)
### [v1.1.0](https://github.com/moul/gotty-client/releases/tag/v1.1.0) (2015-10-10)
* Handling arguments + using mutexes (thanks to [@QuentinPerez](https://github.com/QuentinPerez))
* Add logo ([#9](https://github.com/moul/gotty-client/issues/9))
* Using codegansta/cli for CLI parsing ([#3](https://github.com/moul/gotty-client/issues/3))
* Fix panic when running on older GoTTY server ([#13](https://github.com/moul/gotty-client/issues/13))
* Add 'homebrew support' ([#1](https://github.com/moul/gotty-client/issues/1))
* Add Changelog ([#5](https://github.com/moul/gotty-client/issues/5))
* Add GOXC configuration to build binaries for multiple architectures ([#2](https://github.com/moul/gotty-client/issues/2))
[full commits list](https://github.com/moul/gotty-client/compare/v1.0.1...v1.1.0)
### [v1.0.1](https://github.com/moul/gotty-client/releases/tag/v1.0.1) (2015-09-27)
* Using party to manage dependencies
[full commits list](https://github.com/moul/gotty-client/compare/v1.0.0...v1.0.1)
### [v1.0.0](https://github.com/moul/gotty-client/releases/tag/v1.0.0) (2015-09-27)
Compatible with [GoTTY](https://github.com/yudai/gotty) version: [v0.0.10](https://github.com/yudai/gotty/releases/tag/v0.0.10)
#### Features
* Support **basic-auth**
* Support **terminal-(re)size**
* Support **write**
* Support **title**
* Support **custom URI**
[full commits list](https://github.com/moul/gotty-client/compare/cf0c1146c7ce20fe0bd65764c13253bc575cd43a...v1.0.0)
## License
MIT
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmoul%2Fgotty-client.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmoul%2Fgotty-client?ref=badge_large)

View File

@@ -0,0 +1,33 @@
// +build !windows
package gottyclient
import (
"encoding/json"
"fmt"
"golang.org/x/sys/unix"
"os"
"os/signal"
"syscall"
)
func notifySignalSIGWINCH(c chan<- os.Signal) {
signal.Notify(c, syscall.SIGWINCH)
}
func resetSignalSIGWINCH() {
signal.Reset(syscall.SIGWINCH)
}
func syscallTIOCGWINSZ() ([]byte, error) {
ws, err := unix.IoctlGetWinsize(0, unix.TIOCGWINSZ)
if err != nil {
return nil, fmt.Errorf("ioctl error: %v", err)
}
tws := winsize{Rows: ws.Row, Columns: ws.Col}
b, err := json.Marshal(tws)
if err != nil {
return nil, fmt.Errorf("json.Marshal error: %v", err)
}
return b, err
}

View File

@@ -0,0 +1,16 @@
package gottyclient
import (
"errors"
"os"
)
func notifySignalSIGWINCH(c chan<- os.Signal) {
}
func resetSignalSIGWINCH() {
}
func syscallTIOCGWINSZ() ([]byte, error) {
return nil, errors.New("SIGWINCH isn't supported on this ARCH")
}

View File

@@ -0,0 +1,66 @@
package gottyclient // from github.com/moby/moby/pkg/term
import (
"fmt"
"strings"
)
// ASCII list the possible supported ASCII key sequence
var ASCII = []string{
"ctrl-@",
"ctrl-a",
"ctrl-b",
"ctrl-c",
"ctrl-d",
"ctrl-e",
"ctrl-f",
"ctrl-g",
"ctrl-h",
"ctrl-i",
"ctrl-j",
"ctrl-k",
"ctrl-l",
"ctrl-m",
"ctrl-n",
"ctrl-o",
"ctrl-p",
"ctrl-q",
"ctrl-r",
"ctrl-s",
"ctrl-t",
"ctrl-u",
"ctrl-v",
"ctrl-w",
"ctrl-x",
"ctrl-y",
"ctrl-z",
"ctrl-[",
"ctrl-\\",
"ctrl-]",
"ctrl-^",
"ctrl-_",
}
// ToBytes converts a string representing a suite of key-sequence to the corresponding ASCII code.
func ToBytes(keys string) ([]byte, error) {
codes := []byte{}
next:
for _, key := range strings.Split(keys, ",") {
if len(key) != 1 {
for code, ctrl := range ASCII {
if ctrl == key {
codes = append(codes, byte(code))
continue next
}
}
if key == "DEL" {
codes = append(codes, 127)
} else {
return nil, fmt.Errorf("Unknown character: '%s'", key)
}
} else {
codes = append(codes, key[0])
}
}
return codes, nil
}

View File

@@ -0,0 +1,560 @@
package gottyclient
import (
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"regexp"
"strings"
"sync"
"time"
"github.com/containerd/console"
"github.com/creack/goselect"
"github.com/gorilla/websocket"
"github.com/sirupsen/logrus"
)
// message types for gotty
const (
OutputV1 = '0'
PongV1 = '1'
SetWindowTitleV1 = '2'
SetPreferencesV1 = '3'
SetReconnectV1 = '4'
InputV1 = '0'
PingV1 = '1'
ResizeTerminalV1 = '2'
)
// message types for gotty v2.0
const (
// Unknown message type, maybe set by a bug
UnknownOutput = '0'
// Normal output to the terminal
Output = '1'
// Pong to the browser
Pong = '2'
// Set window title of the terminal
SetWindowTitle = '3'
// Set terminal preference
SetPreferences = '4'
// Make terminal to reconnect
SetReconnect = '5'
// Unknown message type, maybe sent by a bug
UnknownInput = '0'
// User input typically from a keyboard
Input = '1'
// Ping to the server
Ping = '2'
// Notify that the browser size has been changed
ResizeTerminal = '3'
)
type gottyMessageType struct {
output byte
pong byte
setWindowTitle byte
setPreferences byte
setReconnect byte
input byte
ping byte
resizeTerminal byte
}
// GetAuthTokenURL transforms a GoTTY http URL to its AuthToken file URL
func GetAuthTokenURL(httpURL string) (*url.URL, *http.Header, error) {
header := http.Header{}
target, err := url.Parse(httpURL)
if err != nil {
return nil, nil, err
}
target.Path = strings.TrimLeft(target.Path+"auth_token.js", "/")
if target.User != nil {
header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(target.User.String())))
target.User = nil
}
return target, &header, nil
}
// GetURLQuery returns url.query
func GetURLQuery(rawurl string) (url.Values, error) {
target, err := url.Parse(rawurl)
if err != nil {
return nil, err
}
return target.Query(), nil
}
// GetWebsocketURL transforms a GoTTY http URL to its WebSocket URL
func GetWebsocketURL(httpURL string) (*url.URL, *http.Header, error) {
header := http.Header{}
target, err := url.Parse(httpURL)
if err != nil {
return nil, nil, err
}
if target.Scheme == "https" {
target.Scheme = "wss"
} else {
target.Scheme = "ws"
}
target.Path = strings.TrimLeft(target.Path+"ws", "/")
if target.User != nil {
header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(target.User.String())))
target.User = nil
}
return target, &header, nil
}
type Client struct {
Dialer *websocket.Dialer
Conn *websocket.Conn
URL string
WriteMutex *sync.Mutex
Output io.Writer
poison chan bool
SkipTLSVerify bool
UseProxyFromEnv bool
Connected bool
EscapeKeys []byte
V2 bool
message *gottyMessageType
WSOrigin string
}
type querySingleType struct {
AuthToken string `json:"AuthToken"`
Arguments string `json:"Arguments"`
}
func (c *Client) write(data []byte) error {
c.WriteMutex.Lock()
defer c.WriteMutex.Unlock()
return c.Conn.WriteMessage(websocket.TextMessage, data)
}
// GetAuthToken retrieves an Auth Token from dynamic auth_token.js file
func (c *Client) GetAuthToken() (string, error) {
target, header, err := GetAuthTokenURL(c.URL)
if err != nil {
return "", err
}
logrus.Debugf("Fetching auth token auth-token: %q", target.String())
req, err := http.NewRequest("GET", target.String(), nil)
req.Header = *header
tr := &http.Transport{}
if c.SkipTLSVerify {
conf := &tls.Config{InsecureSkipVerify: true}
tr.TLSClientConfig = conf
}
if c.UseProxyFromEnv {
tr.Proxy = http.ProxyFromEnvironment
}
client := &http.Client{Transport: tr}
resp, err := client.Do(req)
if err != nil {
return "", err
}
switch resp.StatusCode {
case 200:
// Everything is OK
default:
return "", fmt.Errorf("unknown status code: %d (%s)", resp.StatusCode, http.StatusText(resp.StatusCode))
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
re := regexp.MustCompile("var gotty_auth_token = '(.*)'")
output := re.FindStringSubmatch(string(body))
if len(output) == 0 {
return "", fmt.Errorf("Cannot fetch GoTTY auth-token, please upgrade your GoTTY server.")
}
return output[1], nil
}
// Connect tries to dial a websocket server
func (c *Client) Connect() error {
// Retrieve AuthToken
authToken, err := c.GetAuthToken()
if err != nil {
return err
}
logrus.Debugf("Auth-token: %q", authToken)
// Open WebSocket connection
target, header, err := GetWebsocketURL(c.URL)
if err != nil {
return err
}
if c.WSOrigin != "" {
header.Add("Origin", c.WSOrigin)
}
logrus.Debugf("Connecting to websocket: %q", target.String())
if c.SkipTLSVerify {
c.Dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
if c.UseProxyFromEnv {
c.Dialer.Proxy = http.ProxyFromEnvironment
}
conn, _, err := c.Dialer.Dial(target.String(), *header)
if err != nil {
return err
}
c.Conn = conn
c.Connected = true
// Pass arguments and auth-token
query, err := GetURLQuery(c.URL)
if err != nil {
return err
}
querySingle := querySingleType{
Arguments: "?" + query.Encode(),
AuthToken: authToken,
}
json, err := json.Marshal(querySingle)
if err != nil {
logrus.Errorf("Failed to parse init message %v", err)
return err
}
// Send Json
logrus.Debugf("Sending arguments and auth-token")
err = c.write(json)
if err != nil {
return err
}
// Initialize message types for gotty
c.initMessageType()
go c.pingLoop()
return nil
}
// initMessageType initialize message types for gotty
func (c *Client) initMessageType() {
if c.V2 {
c.message = &gottyMessageType{
output: Output,
pong: Pong,
setWindowTitle: SetWindowTitle,
setPreferences: SetPreferences,
setReconnect: SetReconnect,
input: Input,
ping: Ping,
resizeTerminal: ResizeTerminal,
}
} else {
c.message = &gottyMessageType{
output: OutputV1,
pong: PongV1,
setWindowTitle: SetWindowTitleV1,
setPreferences: SetPreferencesV1,
setReconnect: SetReconnectV1,
input: InputV1,
ping: PingV1,
resizeTerminal: ResizeTerminalV1,
}
}
}
func (c *Client) pingLoop() {
for {
logrus.Debugf("Sending ping")
c.write([]byte{c.message.ping})
time.Sleep(30 * time.Second)
}
}
// Close will nicely close the dialer
func (c *Client) Close() {
c.Conn.Close()
}
// ExitLoop will kill all goroutines launched by c.Loop()
// ExitLoop() -> wait Loop() -> Close()
func (c *Client) ExitLoop() {
fname := "ExitLoop"
openPoison(fname, c.poison)
}
// Loop will look indefinitely for new messages
func (c *Client) Loop() error {
if !c.Connected {
err := c.Connect()
if err != nil {
return err
}
}
term := console.Current()
err := term.SetRaw()
if err != nil {
return fmt.Errorf("Error setting raw terminal: %v", err)
}
defer term.Reset()
wg := &sync.WaitGroup{}
wg.Add(1)
go c.termsizeLoop(wg)
wg.Add(1)
go c.readLoop(wg)
wg.Add(1)
go c.writeLoop(wg)
/* Wait for all of the above goroutines to finish */
wg.Wait()
logrus.Debug("Client.Loop() exiting")
return nil
}
type winsize struct {
Rows uint16 `json:"rows"`
Columns uint16 `json:"columns"`
// unused
x uint16
y uint16
}
type posionReason int
const (
committedSuicide = iota
killed
)
func openPoison(fname string, poison chan bool) posionReason {
logrus.Debug(fname + " suicide")
/*
* The close() may raise panic if multiple goroutines commit suicide at the
* same time. Prevent that panic from bubbling up.
*/
defer func() {
if r := recover(); r != nil {
logrus.Debug("Prevented panic() of simultaneous suicides", r)
}
}()
/* Signal others to die */
close(poison)
return committedSuicide
}
func die(fname string, poison chan bool) posionReason {
logrus.Debug(fname + " died")
wasOpen := <-poison
if wasOpen {
logrus.Error("ERROR: The channel was open when it wasn't suppoed to be")
}
return killed
}
func (c *Client) termsizeLoop(wg *sync.WaitGroup) posionReason {
defer wg.Done()
fname := "termsizeLoop"
ch := make(chan os.Signal, 1)
notifySignalSIGWINCH(ch)
defer resetSignalSIGWINCH()
for {
if b, err := syscallTIOCGWINSZ(); err != nil {
logrus.Warn(err)
} else {
if err = c.write(append([]byte{c.message.resizeTerminal}, b...)); err != nil {
logrus.Warnf("ws.WriteMessage failed: %v", err)
}
}
select {
case <-c.poison:
/* Somebody poisoned the well; die */
return die(fname, c.poison)
case <-ch:
}
}
}
type exposeFd interface {
Fd() uintptr
}
func (c *Client) writeLoop(wg *sync.WaitGroup) posionReason {
defer wg.Done()
fname := "writeLoop"
buff := make([]byte, 128)
rdfs := &goselect.FDSet{}
reader := io.ReadCloser(os.Stdin)
pr := NewEscapeProxy(reader, c.EscapeKeys)
defer reader.Close()
for {
select {
case <-c.poison:
/* Somebody poisoned the well; die */
return die(fname, c.poison)
default:
}
rdfs.Zero()
rdfs.Set(reader.(exposeFd).Fd())
err := goselect.Select(1, rdfs, nil, nil, 50*time.Millisecond)
if err != nil {
return openPoison(fname, c.poison)
}
if rdfs.IsSet(reader.(exposeFd).Fd()) {
size, err := pr.Read(buff)
if err != nil {
if err == io.EOF {
// Send EOF to GoTTY
// Send 'Input' marker, as defined in GoTTY::client_context.go,
// followed by EOT (a translation of Ctrl-D for terminals)
err = c.write(append([]byte{c.message.input}, byte(4)))
if err != nil {
return openPoison(fname, c.poison)
}
continue
} else {
return openPoison(fname, c.poison)
}
}
if size <= 0 {
continue
}
data := buff[:size]
err = c.write(append([]byte{c.message.input}, data...))
if err != nil {
return openPoison(fname, c.poison)
}
}
}
}
func (c *Client) readLoop(wg *sync.WaitGroup) posionReason {
defer wg.Done()
fname := "readLoop"
type MessageNonBlocking struct {
Data []byte
Err error
}
msgChan := make(chan MessageNonBlocking)
for {
go func() {
_, data, err := c.Conn.ReadMessage()
msgChan <- MessageNonBlocking{Data: data, Err: err}
}()
select {
case <-c.poison:
/* Somebody poisoned the well; die */
return die(fname, c.poison)
case msg := <-msgChan:
if msg.Err != nil {
if _, ok := msg.Err.(*websocket.CloseError); !ok {
logrus.Warnf("c.Conn.ReadMessage: %v", msg.Err)
}
return openPoison(fname, c.poison)
}
if len(msg.Data) == 0 {
logrus.Warnf("An error has occured")
return openPoison(fname, c.poison)
}
switch msg.Data[0] {
case c.message.output: // data
buf, err := base64.StdEncoding.DecodeString(string(msg.Data[1:]))
if err != nil {
logrus.Warnf("Invalid base64 content: %q", msg.Data[1:])
break
}
c.Output.Write(buf)
case c.message.pong: // pong
case c.message.setWindowTitle: // new title
newTitle := string(msg.Data[1:])
fmt.Fprintf(c.Output, "\033]0;%s\007", newTitle)
case c.message.setPreferences: // json prefs
logrus.Debugf("Unhandled protocol message: json pref: %s", string(msg.Data[1:]))
case c.message.setReconnect: // autoreconnect
logrus.Debugf("Unhandled protocol message: autoreconnect: %s", string(msg.Data))
default:
logrus.Warnf("Unhandled protocol message: %s", string(msg.Data))
}
}
}
}
// SetOutput changes the output stream
func (c *Client) SetOutput(w io.Writer) {
c.Output = w
}
// ParseURL parses an URL which may be incomplete and tries to standardize it
func ParseURL(input string) (string, error) {
parsed, err := url.Parse(input)
if err != nil {
return "", err
}
switch parsed.Scheme {
case "http", "https":
// everything is ok
default:
return ParseURL(fmt.Sprintf("http://%s", input))
}
return parsed.String(), nil
}
// NewClient returns a GoTTY client object
func NewClient(inputURL string) (*Client, error) {
url, err := ParseURL(inputURL)
if err != nil {
return nil, err
}
return &Client{
Dialer: &websocket.Dialer{},
URL: url,
WriteMutex: &sync.Mutex{},
Output: os.Stdout,
poison: make(chan bool),
}, nil
}

View File

@@ -0,0 +1,93 @@
// Copyright 2013-2017 Docker, Inc.
//
// 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.
// CHANGES:
// - update package
package gottyclient
import (
"io"
)
// EscapeError is special error which returned by a TTY proxy reader's Read()
// method in case its detach escape sequence is read.
type EscapeError struct{}
func (EscapeError) Error() string {
return "read escape sequence"
}
// escapeProxy is used only for attaches with a TTY. It is used to proxy
// stdin keypresses from the underlying reader and look for the passed in
// escape key sequence to signal a detach.
type escapeProxy struct {
escapeKeys []byte
escapeKeyPos int
r io.Reader
}
// NewEscapeProxy returns a new TTY proxy reader which wraps the given reader
// and detects when the specified escape keys are read, in which case the Read
// method will return an error of type EscapeError.
func NewEscapeProxy(r io.Reader, escapeKeys []byte) io.Reader {
return &escapeProxy{
escapeKeys: escapeKeys,
r: r,
}
}
func (r *escapeProxy) Read(buf []byte) (int, error) {
nr, err := r.r.Read(buf)
if len(r.escapeKeys) == 0 {
return nr, err
}
preserve := func() {
// this preserves the original key presses in the passed in buffer
nr += r.escapeKeyPos
preserve := make([]byte, 0, r.escapeKeyPos+len(buf))
preserve = append(preserve, r.escapeKeys[:r.escapeKeyPos]...)
preserve = append(preserve, buf...)
r.escapeKeyPos = 0
copy(buf[0:nr], preserve)
}
if nr != 1 || err != nil {
if r.escapeKeyPos > 0 {
preserve()
}
return nr, err
}
if buf[0] != r.escapeKeys[r.escapeKeyPos] {
if r.escapeKeyPos > 0 {
preserve()
}
return nr, nil
}
if r.escapeKeyPos == len(r.escapeKeys)-1 {
return 0, EscapeError{}
}
// Looks like we've got an escape key, but we need to match again on the next
// read.
// Store the current escape key we found so we can look for the next one on
// the next read.
// Since this is an escape key, make sure we don't let the caller read it
// If later on we find that this is not the escape sequence, we'll add the
// keys back
r.escapeKeyPos++
return nr - r.escapeKeyPos, nil
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Peter Renström
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,43 @@
# Fuzzy Search
[![Build Status](https://img.shields.io/travis/renstrom/fuzzysearch.svg?style=flat-square)](https://travis-ci.org/renstrom/fuzzysearch)
[![Godoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/renstrom/fuzzysearch/fuzzy)
Inspired by _[bevacqua/fuzzysearch][1]_, a fuzzy matching library written in JavaScript. But contains some extras like ranking using _[Levenshtein distance][2]_ (see [`RankMatch()`](https://godoc.org/github.com/renstrom/fuzzysearch/fuzzy#RankMatch)) and finding matches in a list of words (see [`Find()`](https://godoc.org/github.com/renstrom/fuzzysearch/fuzzy#Find)).
Fuzzy searching allows for flexibly matching a string with partial input, useful for filtering data very quickly based on lightweight user input.
The current implementation uses the algorithm suggested by Mr. Aleph, a russian compiler engineer working at V8.
## Usage
```go
fuzzy.Match("twl", "cartwheel") // true
fuzzy.Match("cart", "cartwheel") // true
fuzzy.Match("cw", "cartwheel") // true
fuzzy.Match("ee", "cartwheel") // true
fuzzy.Match("art", "cartwheel") // true
fuzzy.Match("eeel", "cartwheel") // false
fuzzy.Match("dog", "cartwheel") // false
fuzzy.RankMatch("kitten", "sitting") // 3
words := []string{"cartwheel", "foobar", "wheel", "baz"}
fuzzy.Find("whl", words) // [cartwheel wheel]
fuzzy.RankFind("whl", words) // [{whl cartwheel 6} {whl wheel 2}]
```
You can sort the result of a `fuzzy.RankFind()` call using the [`sort`](https://golang.org/pkg/sort/) package in the standard library:
```go
matches := fuzzy.RankFind("whl", words) // [{whl cartwheel 6} {whl wheel 2}]
sort.Sort(matches) // [{whl wheel 2} {whl cartwheel 6}]
```
## License
MIT
[1]: https://github.com/bevacqua/fuzzysearch
[2]: http://en.wikipedia.org/wiki/Levenshtein_distance

View File

@@ -0,0 +1,167 @@
// Fuzzy searching allows for flexibly matching a string with partial input,
// useful for filtering data very quickly based on lightweight user input.
package fuzzy
import (
"unicode"
"unicode/utf8"
)
var noop = func(r rune) rune { return r }
// Match returns true if source matches target using a fuzzy-searching
// algorithm. Note that it doesn't implement Levenshtein distance (see
// RankMatch instead), but rather a simplified version where there's no
// approximation. The method will return true only if each character in the
// source can be found in the target and occurs after the preceding matches.
func Match(source, target string) bool {
return match(source, target, noop)
}
// MatchFold is a case-insensitive version of Match.
func MatchFold(source, target string) bool {
return match(source, target, unicode.ToLower)
}
func match(source, target string, fn func(rune) rune) bool {
lenDiff := len(target) - len(source)
if lenDiff < 0 {
return false
}
if lenDiff == 0 && source == target {
return true
}
Outer:
for _, r1 := range source {
for i, r2 := range target {
if fn(r1) == fn(r2) {
target = target[i+utf8.RuneLen(r2):]
continue Outer
}
}
return false
}
return true
}
// Find will return a list of strings in targets that fuzzy matches source.
func Find(source string, targets []string) []string {
return find(source, targets, noop)
}
// FindFold is a case-insensitive version of Find.
func FindFold(source string, targets []string) []string {
return find(source, targets, unicode.ToLower)
}
func find(source string, targets []string, fn func(rune) rune) []string {
var matches []string
for _, target := range targets {
if match(source, target, fn) {
matches = append(matches, target)
}
}
return matches
}
// RankMatch is similar to Match except it will measure the Levenshtein
// distance between the source and the target and return its result. If there
// was no match, it will return -1.
// Given the requirements of match, RankMatch only needs to perform a subset of
// the Levenshtein calculation, only deletions need be considered, required
// additions and substitutions would fail the match test.
func RankMatch(source, target string) int {
return rank(source, target, noop)
}
// RankMatchFold is a case-insensitive version of RankMatch.
func RankMatchFold(source, target string) int {
return rank(source, target, unicode.ToLower)
}
func rank(source, target string, fn func(rune) rune) int {
lenDiff := len(target) - len(source)
if lenDiff < 0 {
return -1
}
if lenDiff == 0 && source == target {
return 0
}
runeDiff := 0
Outer:
for _, r1 := range source {
for i, r2 := range target {
if fn(r1) == fn(r2) {
target = target[i+utf8.RuneLen(r2):]
continue Outer
} else {
runeDiff++
}
}
return -1
}
// Count up remaining char
for len(target) > 0 {
target = target[utf8.RuneLen(rune(target[0])):]
runeDiff++
}
return runeDiff
}
// RankFind is similar to Find, except it will also rank all matches using
// Levenshtein distance.
func RankFind(source string, targets []string) Ranks {
var r Ranks
for _, target := range find(source, targets, noop) {
distance := LevenshteinDistance(source, target)
r = append(r, Rank{source, target, distance})
}
return r
}
// RankFindFold is a case-insensitive version of RankFind.
func RankFindFold(source string, targets []string) Ranks {
var r Ranks
for _, target := range find(source, targets, unicode.ToLower) {
distance := LevenshteinDistance(source, target)
r = append(r, Rank{source, target, distance})
}
return r
}
type Rank struct {
// Source is used as the source for matching.
Source string
// Target is the word matched against.
Target string
// Distance is the Levenshtein distance between Source and Target.
Distance int
}
type Ranks []Rank
func (r Ranks) Len() int {
return len(r)
}
func (r Ranks) Swap(i, j int) {
r[i], r[j] = r[j], r[i]
}
func (r Ranks) Less(i, j int) bool {
return r[i].Distance < r[j].Distance
}

View File

@@ -0,0 +1,43 @@
package fuzzy
// LevenshteinDistance measures the difference between two strings.
// The Levenshtein distance between two words is the minimum number of
// single-character edits (i.e. insertions, deletions or substitutions)
// required to change one word into the other.
//
// This implemention is optimized to use O(min(m,n)) space and is based on the
// optimized C version found here:
// http://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Levenshtein_distance#C
func LevenshteinDistance(s, t string) int {
r1, r2 := []rune(s), []rune(t)
column := make([]int, len(r1)+1)
for y := 1; y <= len(r1); y++ {
column[y] = y
}
for x := 1; x <= len(r2); x++ {
column[0] = x
for y, lastDiag := 1, x-1; y <= len(r1); y++ {
oldDiag := column[y]
cost := 0
if r1[y-1] != r2[x-1] {
cost = 1
}
column[y] = min(column[y]+1, column[y-1]+1, lastDiag+cost)
lastDiag = oldDiag
}
}
return column[len(r1)]
}
func min(a, b, c int) int {
if a < b && a < c {
return a
} else if b < c {
return b
}
return c
}

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Scaleway
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,27 @@
# Scaleway's API golang client
[![GoDoc](https://godoc.org/github.com/scaleway/go-scaleway?status.svg)](https://godoc.org/github.com/scaleway/go-scaleway)
This package contains facilities to play with the Scaleway API, it includes the following features:
- dedicated configuration file containing credentials to deal with the API
- caching to resolve UUIDs without contacting the API
## Links
- [API documentation](https://developer.scaleway.com)
- [Official Python SDK](https://github.com/scaleway/python-scaleway)
- Projects using this SDK
- https://github.com/scaleway/scaleway-cli
- https://github.com/scaleway/devhub
- https://github.com/scaleway/docker-machine-driver-scaleway
- https://github.com/huseyin/docker-machine-driver-scaleway
- https://github.com/scaleway-community/scaleway-ubuntu-coreos/blob/master/overlay/usr/local/update-firewall/scw-api/cache.go
- https://github.com/pulcy/quark
- https://github.com/hex-sh/terraform-provider-scaleway
- https://github.com/tscolari/bosh-scaleway-cpi
- Other **golang** clients
- https://github.com/lalyos/onlabs
- https://github.com/meatballhat/packer-builder-onlinelabs
- https://github.com/nlamirault/go-scaleway
- https://github.com/golang/build/blob/master/cmd/scaleway/scaleway.go

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,693 @@
// Copyright (C) 2018 Scaleway. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE.md file.
package cache
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"github.com/moul/anonuuid"
"github.com/renstrom/fuzzysearch/fuzzy"
"github.com/scaleway/go-scaleway/types"
)
const (
// CacheRegion permits to access at the region field
CacheRegion = iota
// CacheArch permits to access at the arch field
CacheArch
// CacheOwner permits to access at the owner field
CacheOwner
// CacheTitle permits to access at the title field
CacheTitle
// CacheMarketPlaceUUID is used to determine the UUID of local images
CacheMarketPlaceUUID
// CacheMaxfield is used to determine the size of array
CacheMaxfield
)
// ScalewayCache is used not to query the API to resolve full identifiers
type ScalewayCache struct {
// Images contains names of Scaleway images indexed by identifier
Images map[string][CacheMaxfield]string `json:"images"`
// Snapshots contains names of Scaleway snapshots indexed by identifier
Snapshots map[string][CacheMaxfield]string `json:"snapshots"`
// Volumes contains names of Scaleway volumes indexed by identifier
Volumes map[string][CacheMaxfield]string `json:"volumes"`
// Bootscripts contains names of Scaleway bootscripts indexed by identifier
Bootscripts map[string][CacheMaxfield]string `json:"bootscripts"`
// Servers contains names of Scaleway servers indexed by identifier
Servers map[string][CacheMaxfield]string `json:"servers"`
// Path is the path to the cache file
Path string `json:"-"`
// Modified tells if the cache needs to be overwritten or not
Modified bool `json:"-"`
// Lock allows ScalewayCache to be used concurrently
Lock sync.Mutex `json:"-"`
hookSave func()
}
// NewScalewayCache loads a per-user cache
func NewScalewayCache(hookSave func()) (*ScalewayCache, error) {
var cache ScalewayCache
cache.hookSave = hookSave
homeDir := os.Getenv("HOME") // *nix
if homeDir == "" { // Windows
homeDir = os.Getenv("USERPROFILE")
}
if homeDir == "" {
homeDir = "/tmp"
}
cachePath := filepath.Join(homeDir, ".scw-cache.db")
cache.Path = cachePath
_, err := os.Stat(cachePath)
if os.IsNotExist(err) {
cache.Clear()
return &cache, nil
} else if err != nil {
return nil, err
}
file, err := ioutil.ReadFile(cachePath)
if err != nil {
return nil, err
}
err = json.Unmarshal(file, &cache)
if err != nil {
// fix compatibility with older version
if err = os.Remove(cachePath); err != nil {
return nil, err
}
cache.Clear()
return &cache, nil
}
if cache.Images == nil {
cache.Images = make(map[string][CacheMaxfield]string)
}
if cache.Snapshots == nil {
cache.Snapshots = make(map[string][CacheMaxfield]string)
}
if cache.Volumes == nil {
cache.Volumes = make(map[string][CacheMaxfield]string)
}
if cache.Servers == nil {
cache.Servers = make(map[string][CacheMaxfield]string)
}
if cache.Bootscripts == nil {
cache.Bootscripts = make(map[string][CacheMaxfield]string)
}
return &cache, nil
}
// Clear removes all information from the cache
func (c *ScalewayCache) Clear() {
c.Images = make(map[string][CacheMaxfield]string)
c.Snapshots = make(map[string][CacheMaxfield]string)
c.Volumes = make(map[string][CacheMaxfield]string)
c.Bootscripts = make(map[string][CacheMaxfield]string)
c.Servers = make(map[string][CacheMaxfield]string)
c.Modified = true
}
// Flush flushes the cache database
func (c *ScalewayCache) Flush() error {
return os.Remove(c.Path)
}
// Save atomically overwrites the current cache database
func (c *ScalewayCache) Save() error {
c.Lock.Lock()
defer c.Lock.Unlock()
c.hookSave()
if c.Modified {
file, err := ioutil.TempFile(filepath.Dir(c.Path), filepath.Base(c.Path))
if err != nil {
return err
}
if err := json.NewEncoder(file).Encode(c); err != nil {
file.Close()
os.Remove(file.Name())
return err
}
file.Close()
if err := os.Rename(file.Name(), c.Path); err != nil {
os.Remove(file.Name())
return err
}
}
return nil
}
// ComputeRankMatch fills `ScalewayResolverResult.RankMatch` with its `fuzzy` score
func ComputeRankMatch(s *types.ScalewayResolverResult, needle string) {
s.Needle = needle
s.RankMatch = fuzzy.RankMatch(needle, s.Name)
}
// LookUpImages attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpImages(needle string, acceptUUID bool) (types.ScalewayResolverResults, error) {
c.Lock.Lock()
defer c.Lock.Unlock()
var res types.ScalewayResolverResults
var exactMatches types.ScalewayResolverResults
if acceptUUID && anonuuid.IsUUID(needle) == nil {
if fields, ok := c.Images[needle]; ok {
entry, err := types.NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], types.IdentifierImage)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
res = append(res, entry)
}
}
needle = regexp.MustCompile(`^user/`).ReplaceAllString(needle, "")
// FIXME: if 'user/' is in needle, only watch for a user image
nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
for identifier, fields := range c.Images {
if fields[CacheTitle] == needle {
entry, err := types.NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], types.IdentifierImage)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
exactMatches = append(exactMatches, entry)
}
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
entry, err := types.NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], types.IdentifierImage)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
res = append(res, entry)
} else if strings.HasPrefix(fields[CacheMarketPlaceUUID], needle) || nameRegex.MatchString(fields[CacheMarketPlaceUUID]) {
entry, err := types.NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], types.IdentifierImage)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
res = append(res, entry)
}
}
if len(exactMatches) == 1 {
return exactMatches, nil
}
return removeDuplicatesResults(res), nil
}
// LookUpSnapshots attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpSnapshots(needle string, acceptUUID bool) (types.ScalewayResolverResults, error) {
c.Lock.Lock()
defer c.Lock.Unlock()
var res types.ScalewayResolverResults
var exactMatches types.ScalewayResolverResults
if acceptUUID && anonuuid.IsUUID(needle) == nil {
if fields, ok := c.Snapshots[needle]; ok {
entry, err := types.NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], types.IdentifierSnapshot)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
res = append(res, entry)
}
}
needle = regexp.MustCompile(`^user/`).ReplaceAllString(needle, "")
nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
for identifier, fields := range c.Snapshots {
if fields[CacheTitle] == needle {
entry, err := types.NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], types.IdentifierSnapshot)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
exactMatches = append(exactMatches, entry)
}
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
entry, err := types.NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], types.IdentifierSnapshot)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
res = append(res, entry)
}
}
if len(exactMatches) == 1 {
return exactMatches, nil
}
return removeDuplicatesResults(res), nil
}
// LookUpVolumes attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpVolumes(needle string, acceptUUID bool) (types.ScalewayResolverResults, error) {
c.Lock.Lock()
defer c.Lock.Unlock()
var res types.ScalewayResolverResults
var exactMatches types.ScalewayResolverResults
if acceptUUID && anonuuid.IsUUID(needle) == nil {
if fields, ok := c.Volumes[needle]; ok {
entry, err := types.NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], types.IdentifierVolume)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
res = append(res, entry)
}
}
nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
for identifier, fields := range c.Volumes {
if fields[CacheTitle] == needle {
entry, err := types.NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], types.IdentifierVolume)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
exactMatches = append(exactMatches, entry)
}
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
entry, err := types.NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], types.IdentifierVolume)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
res = append(res, entry)
}
}
if len(exactMatches) == 1 {
return exactMatches, nil
}
return removeDuplicatesResults(res), nil
}
// LookUpBootscripts attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpBootscripts(needle string, acceptUUID bool) (types.ScalewayResolverResults, error) {
c.Lock.Lock()
defer c.Lock.Unlock()
var res types.ScalewayResolverResults
var exactMatches types.ScalewayResolverResults
if acceptUUID && anonuuid.IsUUID(needle) == nil {
if fields, ok := c.Bootscripts[needle]; ok {
entry, err := types.NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], types.IdentifierBootscript)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
res = append(res, entry)
}
}
nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
for identifier, fields := range c.Bootscripts {
if fields[CacheTitle] == needle {
entry, err := types.NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], types.IdentifierBootscript)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
exactMatches = append(exactMatches, entry)
}
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
entry, err := types.NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], types.IdentifierBootscript)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
res = append(res, entry)
}
}
if len(exactMatches) == 1 {
return exactMatches, nil
}
return removeDuplicatesResults(res), nil
}
// LookUpServers attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpServers(needle string, acceptUUID bool) (types.ScalewayResolverResults, error) {
c.Lock.Lock()
defer c.Lock.Unlock()
var res types.ScalewayResolverResults
var exactMatches types.ScalewayResolverResults
if acceptUUID && anonuuid.IsUUID(needle) == nil {
if fields, ok := c.Servers[needle]; ok {
entry, err := types.NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], types.IdentifierServer)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
res = append(res, entry)
}
}
nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
for identifier, fields := range c.Servers {
if fields[CacheTitle] == needle {
entry, err := types.NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], types.IdentifierServer)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
exactMatches = append(exactMatches, entry)
}
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
entry, err := types.NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], types.IdentifierServer)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
res = append(res, entry)
}
}
if len(exactMatches) == 1 {
return exactMatches, nil
}
return removeDuplicatesResults(res), nil
}
// removeDuplicatesResults transforms an array into a unique array
func removeDuplicatesResults(elements types.ScalewayResolverResults) types.ScalewayResolverResults {
encountered := map[string]types.ScalewayResolverResult{}
// Create a map of all unique elements.
for v := range elements {
encountered[elements[v].Identifier] = elements[v]
}
// Place all keys from the map into a slice.
results := types.ScalewayResolverResults{}
for _, result := range encountered {
results = append(results, result)
}
return results
}
// LookUpIdentifiers attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpIdentifiers(needle string) (types.ScalewayResolverResults, error) {
results := types.ScalewayResolverResults{}
identifierType, needle := types.ParseNeedle(needle)
if identifierType&(types.IdentifierUnknown|types.IdentifierServer) > 0 {
servers, err := c.LookUpServers(needle, false)
if err != nil {
return types.ScalewayResolverResults{}, err
}
for _, result := range servers {
entry, err := types.NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, types.IdentifierServer)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
results = append(results, entry)
}
}
if identifierType&(types.IdentifierUnknown|types.IdentifierImage) > 0 {
images, err := c.LookUpImages(needle, false)
if err != nil {
return types.ScalewayResolverResults{}, err
}
for _, result := range images {
entry, err := types.NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, types.IdentifierImage)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
results = append(results, entry)
}
}
if identifierType&(types.IdentifierUnknown|types.IdentifierSnapshot) > 0 {
snapshots, err := c.LookUpSnapshots(needle, false)
if err != nil {
return types.ScalewayResolverResults{}, err
}
for _, result := range snapshots {
entry, err := types.NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, types.IdentifierSnapshot)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
results = append(results, entry)
}
}
if identifierType&(types.IdentifierUnknown|types.IdentifierVolume) > 0 {
volumes, err := c.LookUpVolumes(needle, false)
if err != nil {
return types.ScalewayResolverResults{}, err
}
for _, result := range volumes {
entry, err := types.NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, types.IdentifierVolume)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
results = append(results, entry)
}
}
if identifierType&(types.IdentifierUnknown|types.IdentifierBootscript) > 0 {
bootscripts, err := c.LookUpBootscripts(needle, false)
if err != nil {
return types.ScalewayResolverResults{}, err
}
for _, result := range bootscripts {
entry, err := types.NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, types.IdentifierBootscript)
if err != nil {
return types.ScalewayResolverResults{}, err
}
ComputeRankMatch(&entry, needle)
results = append(results, entry)
}
}
return results, nil
}
// InsertServer registers a server in the cache
func (c *ScalewayCache) InsertServer(identifier, region, arch, owner, name string) {
c.Lock.Lock()
defer c.Lock.Unlock()
fields, exists := c.Servers[identifier]
if !exists || fields[CacheTitle] != name {
c.Servers[identifier] = [CacheMaxfield]string{region, arch, owner, name}
c.Modified = true
}
}
// RemoveServer removes a server from the cache
func (c *ScalewayCache) RemoveServer(identifier string) {
c.Lock.Lock()
defer c.Lock.Unlock()
delete(c.Servers, identifier)
c.Modified = true
}
// ClearServers removes all servers from the cache
func (c *ScalewayCache) ClearServers() {
c.Lock.Lock()
defer c.Lock.Unlock()
c.Servers = make(map[string][CacheMaxfield]string)
c.Modified = true
}
// InsertImage registers an image in the cache
func (c *ScalewayCache) InsertImage(identifier, region, arch, owner, name, marketPlaceUUID string) {
c.Lock.Lock()
defer c.Lock.Unlock()
fields, exists := c.Images[identifier]
if !exists || fields[CacheTitle] != name {
c.Images[identifier] = [CacheMaxfield]string{region, arch, owner, name, marketPlaceUUID}
c.Modified = true
}
}
// RemoveImage removes a server from the cache
func (c *ScalewayCache) RemoveImage(identifier string) {
c.Lock.Lock()
defer c.Lock.Unlock()
delete(c.Images, identifier)
c.Modified = true
}
// ClearImages removes all images from the cache
func (c *ScalewayCache) ClearImages() {
c.Lock.Lock()
defer c.Lock.Unlock()
c.Images = make(map[string][CacheMaxfield]string)
c.Modified = true
}
// InsertSnapshot registers an snapshot in the cache
func (c *ScalewayCache) InsertSnapshot(identifier, region, arch, owner, name string) {
c.Lock.Lock()
defer c.Lock.Unlock()
fields, exists := c.Snapshots[identifier]
if !exists || fields[CacheTitle] != name {
c.Snapshots[identifier] = [CacheMaxfield]string{region, arch, owner, name}
c.Modified = true
}
}
// RemoveSnapshot removes a server from the cache
func (c *ScalewayCache) RemoveSnapshot(identifier string) {
c.Lock.Lock()
defer c.Lock.Unlock()
delete(c.Snapshots, identifier)
c.Modified = true
}
// ClearSnapshots removes all snapshots from the cache
func (c *ScalewayCache) ClearSnapshots() {
c.Lock.Lock()
defer c.Lock.Unlock()
c.Snapshots = make(map[string][CacheMaxfield]string)
c.Modified = true
}
// InsertVolume registers an volume in the cache
func (c *ScalewayCache) InsertVolume(identifier, region, arch, owner, name string) {
c.Lock.Lock()
defer c.Lock.Unlock()
fields, exists := c.Volumes[identifier]
if !exists || fields[CacheTitle] != name {
c.Volumes[identifier] = [CacheMaxfield]string{region, arch, owner, name}
c.Modified = true
}
}
// RemoveVolume removes a server from the cache
func (c *ScalewayCache) RemoveVolume(identifier string) {
c.Lock.Lock()
defer c.Lock.Unlock()
delete(c.Volumes, identifier)
c.Modified = true
}
// ClearVolumes removes all volumes from the cache
func (c *ScalewayCache) ClearVolumes() {
c.Lock.Lock()
defer c.Lock.Unlock()
c.Volumes = make(map[string][CacheMaxfield]string)
c.Modified = true
}
// InsertBootscript registers an bootscript in the cache
func (c *ScalewayCache) InsertBootscript(identifier, region, arch, owner, name string) {
c.Lock.Lock()
defer c.Lock.Unlock()
fields, exists := c.Bootscripts[identifier]
if !exists || fields[CacheTitle] != name {
c.Bootscripts[identifier] = [CacheMaxfield]string{region, arch, owner, name}
c.Modified = true
}
}
// RemoveBootscript removes a bootscript from the cache
func (c *ScalewayCache) RemoveBootscript(identifier string) {
c.Lock.Lock()
defer c.Lock.Unlock()
delete(c.Bootscripts, identifier)
c.Modified = true
}
// ClearBootscripts removes all bootscripts from the cache
func (c *ScalewayCache) ClearBootscripts() {
c.Lock.Lock()
defer c.Lock.Unlock()
c.Bootscripts = make(map[string][CacheMaxfield]string)
c.Modified = true
}
// GetNbServers returns the number of servers in the cache
func (c *ScalewayCache) GetNbServers() int {
c.Lock.Lock()
defer c.Lock.Unlock()
return len(c.Servers)
}
// GetNbImages returns the number of images in the cache
func (c *ScalewayCache) GetNbImages() int {
c.Lock.Lock()
defer c.Lock.Unlock()
return len(c.Images)
}
// GetNbSnapshots returns the number of snapshots in the cache
func (c *ScalewayCache) GetNbSnapshots() int {
c.Lock.Lock()
defer c.Lock.Unlock()
return len(c.Snapshots)
}
// GetNbVolumes returns the number of volumes in the cache
func (c *ScalewayCache) GetNbVolumes() int {
c.Lock.Lock()
defer c.Lock.Unlock()
return len(c.Volumes)
}
// GetNbBootscripts returns the number of bootscripts in the cache
func (c *ScalewayCache) GetNbBootscripts() int {
c.Lock.Lock()
defer c.Lock.Unlock()
return len(c.Bootscripts)
}

View File

@@ -0,0 +1,77 @@
// Copyright (C) 2018 Scaleway. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE.md file.
package logger
import (
"fmt"
"log"
"net/http"
"os"
)
// Logger handles logging concerns for the Scaleway API SDK
type Logger interface {
LogHTTP(*http.Request)
Fatalf(format string, v ...interface{})
Debugf(format string, v ...interface{})
Infof(format string, v ...interface{})
Warnf(format string, v ...interface{})
}
// NewDefaultLogger returns a logger which is configured for stdout
func NewDefaultLogger() Logger {
return &defaultLogger{
Logger: log.New(os.Stdout, "", log.LstdFlags),
}
}
type defaultLogger struct {
*log.Logger
}
func (l *defaultLogger) LogHTTP(r *http.Request) {
l.Printf("%s %s\n", r.Method, r.URL.RawPath)
}
func (l *defaultLogger) Fatalf(format string, v ...interface{}) {
l.Printf("[FATAL] %s\n", fmt.Sprintf(format, v))
os.Exit(1)
}
func (l *defaultLogger) Debugf(format string, v ...interface{}) {
l.Printf("[DEBUG] %s\n", fmt.Sprintf(format, v))
}
func (l *defaultLogger) Infof(format string, v ...interface{}) {
l.Printf("[INFO ] %s\n", fmt.Sprintf(format, v))
}
func (l *defaultLogger) Warnf(format string, v ...interface{}) {
l.Printf("[WARN ] %s\n", fmt.Sprintf(format, v))
}
type disableLogger struct {
}
// NewDisableLogger returns a logger which is configured to do nothing
func NewDisableLogger() Logger {
return &disableLogger{}
}
func (d *disableLogger) LogHTTP(r *http.Request) {
}
func (d *disableLogger) Fatalf(format string, v ...interface{}) {
panic(fmt.Sprintf(format, v))
}
func (d *disableLogger) Debugf(format string, v ...interface{}) {
}
func (d *disableLogger) Infof(format string, v ...interface{}) {
}
func (d *disableLogger) Warnf(format string, v ...interface{}) {
}

View File

@@ -0,0 +1,983 @@
// Copyright (C) 2018 Scaleway. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE.md file.
package types
import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"regexp"
"strings"
"time"
"github.com/moul/anonuuid"
)
// ParseNeedle parses a user needle and try to extract a forced object type
// i.e:
// - server:blah-blah -> kind=server, needle=blah-blah
// - blah-blah -> kind="", needle=blah-blah
// - not-existing-type:blah-blah
func ParseNeedle(input string) (identifierType int, needle string) {
parts := strings.Split(input, ":")
if len(parts) == 2 {
switch parts[0] {
case "server":
return IdentifierServer, parts[1]
case "image":
return IdentifierImage, parts[1]
case "snapshot":
return IdentifierSnapshot, parts[1]
case "bootscript":
return IdentifierBootscript, parts[1]
case "volume":
return IdentifierVolume, parts[1]
}
}
return IdentifierUnknown, input
}
const (
// IdentifierUnknown is used when we don't know explicitly the type key of the object (used for nil comparison)
IdentifierUnknown = 1 << iota
// IdentifierServer is the type key of cached server objects
IdentifierServer
// IdentifierImage is the type key of cached image objects
IdentifierImage
// IdentifierSnapshot is the type key of cached snapshot objects
IdentifierSnapshot
// IdentifierBootscript is the type key of cached bootscript objects
IdentifierBootscript
// IdentifierVolume is the type key of cached volume objects
IdentifierVolume
)
// ScalewayResolverResult is a structure containing human-readable information
// about resolver results. This structure is used to display the user choices.
type ScalewayResolverResult struct {
Identifier string
Type int
Name string
Arch string
Needle string
RankMatch int
Region string
}
// ScalewayResolverResults is a list of `ScalewayResolverResult`
type ScalewayResolverResults []ScalewayResolverResult
// NewScalewayResolverResult returns a new ScalewayResolverResult
func NewScalewayResolverResult(Identifier, Name, Arch, Region string, Type int) (ScalewayResolverResult, error) {
if err := anonuuid.IsUUID(Identifier); err != nil {
return ScalewayResolverResult{}, err
}
return ScalewayResolverResult{
Identifier: Identifier,
Type: Type,
Name: Name,
Arch: Arch,
Region: Region,
}, nil
}
func (s ScalewayResolverResults) Len() int {
return len(s)
}
func (s ScalewayResolverResults) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s ScalewayResolverResults) Less(i, j int) bool {
return s[i].RankMatch < s[j].RankMatch
}
// TruncIdentifier returns first 8 characters of an Identifier (UUID)
func (s *ScalewayResolverResult) TruncIdentifier() string {
return s.Identifier[:8]
}
func identifierTypeName(kind int) string {
switch kind {
case IdentifierServer:
return "Server"
case IdentifierImage:
return "Image"
case IdentifierSnapshot:
return "Snapshot"
case IdentifierVolume:
return "Volume"
case IdentifierBootscript:
return "Bootscript"
}
return ""
}
// CodeName returns a full resource name with typed prefix
func (s *ScalewayResolverResult) CodeName() string {
name := strings.ToLower(s.Name)
name = regexp.MustCompile(`[^a-z0-9-]`).ReplaceAllString(name, "-")
name = regexp.MustCompile(`--+`).ReplaceAllString(name, "-")
name = strings.Trim(name, "-")
return fmt.Sprintf("%s:%s", strings.ToLower(identifierTypeName(s.Type)), name)
}
// FilterByArch deletes the elements which not match with arch
func (s *ScalewayResolverResults) FilterByArch(arch string) {
REDO:
for i := range *s {
if (*s)[i].Arch != arch {
(*s)[i] = (*s)[len(*s)-1]
*s = (*s)[:len(*s)-1]
goto REDO
}
}
}
// ScalewayAPIError represents a Scaleway API Error
type ScalewayAPIError struct {
// Message is a human-friendly error message
APIMessage string `json:"message,omitempty"`
// Type is a string code that defines the kind of error
Type string `json:"type,omitempty"`
// Fields contains detail about validation error
Fields map[string][]string `json:"fields,omitempty"`
// StatusCode is the HTTP status code received
StatusCode int `json:"-"`
// Message
Message string `json:"-"`
}
// Error returns a string representing the error
func (e ScalewayAPIError) Error() string {
var b bytes.Buffer
fmt.Fprintf(&b, "StatusCode: %v, ", e.StatusCode)
fmt.Fprintf(&b, "Type: %v, ", e.Type)
fmt.Fprintf(&b, "APIMessage: \x1b[31m%v\x1b[0m", e.APIMessage)
if len(e.Fields) > 0 {
fmt.Fprintf(&b, ", Details: %v", e.Fields)
}
return b.String()
}
// ScalewayIPAddress represents a Scaleway IP address
type ScalewayIPAddress struct {
// Identifier is a unique identifier for the IP address
Identifier string `json:"id,omitempty"`
// IP is an IPv4 address
IP string `json:"address,omitempty"`
// Dynamic is a flag that defines an IP that change on each reboot
Dynamic *bool `json:"dynamic,omitempty"`
}
// ScalewayVolume represents a Scaleway Volume
type ScalewayVolume struct {
// Identifier is a unique identifier for the volume
Identifier string `json:"id,omitempty"`
// Size is the allocated size of the volume
Size uint64 `json:"size,omitempty"`
// CreationDate is the creation date of the volume
CreationDate string `json:"creation_date,omitempty"`
// ModificationDate is the date of the last modification of the volume
ModificationDate string `json:"modification_date,omitempty"`
// Organization is the organization owning the volume
Organization string `json:"organization,omitempty"`
// Name is the name of the volume
Name string `json:"name,omitempty"`
// Server is the server using this image
Server *struct {
Identifier string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
} `json:"server,omitempty"`
// VolumeType is a Scaleway identifier for the kind of volume (default: l_ssd)
VolumeType string `json:"volume_type,omitempty"`
// ExportURI represents the url used by initrd/scripts to attach the volume
ExportURI string `json:"export_uri,omitempty"`
}
// ScalewayOneVolume represents the response of a GET /volumes/UUID API call
type ScalewayOneVolume struct {
Volume ScalewayVolume `json:"volume,omitempty"`
}
// ScalewayVolumes represents a group of Scaleway volumes
type ScalewayVolumes struct {
// Volumes holds scaleway volumes of the response
Volumes []ScalewayVolume `json:"volumes,omitempty"`
}
// ScalewayVolumeDefinition represents a Scaleway volume definition
type ScalewayVolumeDefinition struct {
// Name is the user-defined name of the volume
Name string `json:"name"`
// Image is the image used by the volume
Size uint64 `json:"size"`
// Bootscript is the bootscript used by the volume
Type string `json:"volume_type"`
// Organization is the owner of the volume
Organization string `json:"organization"`
}
// ScalewayVolumePutDefinition represents a Scaleway volume with nullable fields (for PUT)
type ScalewayVolumePutDefinition struct {
Identifier *string `json:"id,omitempty"`
Size *uint64 `json:"size,omitempty"`
CreationDate *string `json:"creation_date,omitempty"`
ModificationDate *string `json:"modification_date,omitempty"`
Organization *string `json:"organization,omitempty"`
Name *string `json:"name,omitempty"`
Server struct {
Identifier *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
} `json:"server,omitempty"`
VolumeType *string `json:"volume_type,omitempty"`
ExportURI *string `json:"export_uri,omitempty"`
}
// ScalewayImage represents a Scaleway Image
type ScalewayImage struct {
// Identifier is a unique identifier for the image
Identifier string `json:"id,omitempty"`
// Name is a user-defined name for the image
Name string `json:"name,omitempty"`
// CreationDate is the creation date of the image
CreationDate string `json:"creation_date,omitempty"`
// ModificationDate is the date of the last modification of the image
ModificationDate string `json:"modification_date,omitempty"`
// RootVolume is the root volume bound to the image
RootVolume ScalewayVolume `json:"root_volume,omitempty"`
// Public is true for public images and false for user images
Public bool `json:"public,omitempty"`
// Bootscript is the bootscript bound to the image
DefaultBootscript *ScalewayBootscript `json:"default_bootscript,omitempty"`
// Organization is the owner of the image
Organization string `json:"organization,omitempty"`
// Arch is the architecture target of the image
Arch string `json:"arch,omitempty"`
// FIXME: extra_volumes
}
// ScalewayImageIdentifier represents a Scaleway Image Identifier
type ScalewayImageIdentifier struct {
Identifier string
Arch string
Region string
Owner string
}
// ScalewayOneImage represents the response of a GET /images/UUID API call
type ScalewayOneImage struct {
Image ScalewayImage `json:"image,omitempty"`
}
// ScalewayImages represents a group of Scaleway images
type ScalewayImages struct {
// Images holds scaleway images of the response
Images []ScalewayImage `json:"images,omitempty"`
}
// ProductNetworkInterface gives interval and external allowed bandwidth
type ProductNetworkInterface struct {
InternalBandwidth uint64 `json:"internal_bandwidth,omitempty"`
InternetBandwidth uint64 `json:"internet_bandwidth,omitempty"`
}
// ProductNetwork lists all the network interfaces
type ProductNetwork struct {
Interfaces []ProductNetworkInterface `json:"interfaces,omitempty"`
TotalInternalBandwidth uint64 `json:"sum_internal_bandwidth,omitempty"`
TotalInternetBandwidth uint64 `json:"sum_internet_bandwidth,omitempty"`
IPv6_Support bool `json:"ipv6_support,omitempty"`
}
// ProductVolumeConstraint contains any volume constraint that the offer has
type ProductVolumeConstraint struct {
MinSize uint64 `json:"min_size,omitempty"`
MaxSize uint64 `json:"max_size,omitempty"`
}
// ProductServerOffer represents a specific offer
type ProductServer struct {
Arch string `json:"arch,omitempty"`
Ncpus uint64 `json:"ncpus,omitempty"`
Ram uint64 `json:"ram,omitempty"`
Baremetal bool `json:"baremetal,omitempty"`
VolumesConstraint ProductVolumeConstraint `json:"volumes_constraint,omitempty"`
AltNames []string `json:"alt_names,omitempty"`
Network ProductNetwork `json:"network,omitempty"`
}
// Products holds a map of all Scaleway servers
type ScalewayProductsServers struct {
Servers map[string]ProductServer `json:"servers"`
}
// ScalewaySnapshot represents a Scaleway Snapshot
type ScalewaySnapshot struct {
// Identifier is a unique identifier for the snapshot
Identifier string `json:"id,omitempty"`
// Name is a user-defined name for the snapshot
Name string `json:"name,omitempty"`
// CreationDate is the creation date of the snapshot
CreationDate string `json:"creation_date,omitempty"`
// ModificationDate is the date of the last modification of the snapshot
ModificationDate string `json:"modification_date,omitempty"`
// Size is the allocated size of the volume
Size uint64 `json:"size,omitempty"`
// Organization is the owner of the snapshot
Organization string `json:"organization"`
// State is the current state of the snapshot
State string `json:"state"`
// VolumeType is the kind of volume behind the snapshot
VolumeType string `json:"volume_type"`
// BaseVolume is the volume from which the snapshot inherits
BaseVolume ScalewayVolume `json:"base_volume,omitempty"`
}
// ScalewayOneSnapshot represents the response of a GET /snapshots/UUID API call
type ScalewayOneSnapshot struct {
Snapshot ScalewaySnapshot `json:"snapshot,omitempty"`
}
// ScalewaySnapshots represents a group of Scaleway snapshots
type ScalewaySnapshots struct {
// Snapshots holds scaleway snapshots of the response
Snapshots []ScalewaySnapshot `json:"snapshots,omitempty"`
}
// ScalewayBootscript represents a Scaleway Bootscript
type ScalewayBootscript struct {
Bootcmdargs string `json:"bootcmdargs,omitempty"`
Dtb string `json:"dtb,omitempty"`
Initrd string `json:"initrd,omitempty"`
Kernel string `json:"kernel,omitempty"`
// Arch is the architecture target of the bootscript
Arch string `json:"architecture,omitempty"`
// Identifier is a unique identifier for the bootscript
Identifier string `json:"id,omitempty"`
// Organization is the owner of the bootscript
Organization string `json:"organization,omitempty"`
// Name is a user-defined name for the bootscript
Title string `json:"title,omitempty"`
// Public is true for public bootscripts and false for user bootscripts
Public bool `json:"public,omitempty"`
Default bool `json:"default,omitempty"`
}
// ScalewayOneBootscript represents the response of a GET /bootscripts/UUID API call
type ScalewayOneBootscript struct {
Bootscript ScalewayBootscript `json:"bootscript,omitempty"`
}
// ScalewayBootscripts represents a group of Scaleway bootscripts
type ScalewayBootscripts struct {
// Bootscripts holds Scaleway bootscripts of the response
Bootscripts []ScalewayBootscript `json:"bootscripts,omitempty"`
}
// ScalewayTask represents a Scaleway Task
type ScalewayTask struct {
// Identifier is a unique identifier for the task
Identifier string `json:"id,omitempty"`
// StartDate is the start date of the task
StartDate string `json:"started_at,omitempty"`
// TerminationDate is the termination date of the task
TerminationDate string `json:"terminated_at,omitempty"`
HrefFrom string `json:"href_from,omitempty"`
Description string `json:"description,omitempty"`
Status string `json:"status,omitempty"`
Progress int `json:"progress,omitempty"`
}
// ScalewayOneTask represents the response of a GET /tasks/UUID API call
type ScalewayOneTask struct {
Task ScalewayTask `json:"task,omitempty"`
}
// ScalewayTasks represents a group of Scaleway tasks
type ScalewayTasks struct {
// Tasks holds scaleway tasks of the response
Tasks []ScalewayTask `json:"tasks,omitempty"`
}
// ScalewaySecurityGroupRule definition
type ScalewaySecurityGroupRule struct {
Direction string `json:"direction"`
Protocol string `json:"protocol"`
IPRange string `json:"ip_range"`
DestPortFrom int `json:"dest_port_from,omitempty"`
Action string `json:"action"`
Position int `json:"position"`
DestPortTo string `json:"dest_port_to"`
Editable bool `json:"editable"`
ID string `json:"id"`
}
// ScalewayGetSecurityGroupRules represents the response of a GET /security_group/{groupID}/rules
type ScalewayGetSecurityGroupRules struct {
Rules []ScalewaySecurityGroupRule `json:"rules"`
}
// ScalewayGetSecurityGroupRule represents the response of a GET /security_group/{groupID}/rules/{ruleID}
type ScalewayGetSecurityGroupRule struct {
Rules ScalewaySecurityGroupRule `json:"rule"`
}
// ScalewayNewSecurityGroupRule definition POST/PUT request /security_group/{groupID}
type ScalewayNewSecurityGroupRule struct {
Action string `json:"action"`
Direction string `json:"direction"`
IPRange string `json:"ip_range"`
Protocol string `json:"protocol"`
DestPortFrom int `json:"dest_port_from,omitempty"`
}
// ScalewaySecurityGroups definition
type ScalewaySecurityGroups struct {
Description string `json:"description"`
ID string `json:"id"`
Organization string `json:"organization"`
Name string `json:"name"`
Servers []ScalewaySecurityGroup `json:"servers"`
EnableDefaultSecurity bool `json:"enable_default_security"`
OrganizationDefault bool `json:"organization_default"`
}
// ScalewayGetSecurityGroups represents the response of a GET /security_groups/
type ScalewayGetSecurityGroups struct {
SecurityGroups []ScalewaySecurityGroups `json:"security_groups"`
}
// ScalewayGetSecurityGroup represents the response of a GET /security_groups/{groupID}
type ScalewayGetSecurityGroup struct {
SecurityGroups ScalewaySecurityGroups `json:"security_group"`
}
// ScalewayIPDefinition represents the IP's fields
type ScalewayIPDefinition struct {
Organization string `json:"organization"`
Reverse *string `json:"reverse"`
ID string `json:"id"`
Server *struct {
Identifier string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
} `json:"server"`
Address string `json:"address"`
}
// ScalewayGetIPS represents the response of a GET /ips/
type ScalewayGetIPS struct {
IPS []ScalewayIPDefinition `json:"ips"`
}
// ScalewayGetIP represents the response of a GET /ips/{id_ip}
type ScalewayGetIP struct {
IP ScalewayIPDefinition `json:"ip"`
}
// ScalewaySecurityGroup represents a Scaleway security group
type ScalewaySecurityGroup struct {
// Identifier is a unique identifier for the security group
Identifier string `json:"id,omitempty"`
// Name is the user-defined name of the security group
Name string `json:"name,omitempty"`
}
// ScalewayNewSecurityGroup definition POST request /security_groups
type ScalewayNewSecurityGroup struct {
Organization string `json:"organization"`
Name string `json:"name"`
Description string `json:"description"`
}
// ScalewayUpdateSecurityGroup definition PUT request /security_groups
type ScalewayUpdateSecurityGroup struct {
Organization string `json:"organization"`
Name string `json:"name"`
Description string `json:"description"`
OrganizationDefault bool `json:"organization_default"`
}
// ScalewayServer represents a Scaleway server
type ScalewayServer struct {
// Arch is the architecture target of the server
Arch string `json:"arch,omitempty"`
// Identifier is a unique identifier for the server
Identifier string `json:"id,omitempty"`
// Name is the user-defined name of the server
Name string `json:"name,omitempty"`
// CreationDate is the creation date of the server
CreationDate string `json:"creation_date,omitempty"`
// ModificationDate is the date of the last modification of the server
ModificationDate string `json:"modification_date,omitempty"`
// Image is the image used by the server
Image ScalewayImage `json:"image,omitempty"`
// DynamicIPRequired is a flag that defines a server with a dynamic ip address attached
DynamicIPRequired *bool `json:"dynamic_ip_required,omitempty"`
// PublicIP is the public IP address bound to the server
PublicAddress ScalewayIPAddress `json:"public_ip,omitempty"`
// State is the current status of the server
State string `json:"state,omitempty"`
// StateDetail is the detailed status of the server
StateDetail string `json:"state_detail,omitempty"`
// PrivateIP represents the private IPV4 attached to the server (changes on each boot)
PrivateIP string `json:"private_ip,omitempty"`
// Bootscript is the unique identifier of the selected bootscript
Bootscript *ScalewayBootscript `json:"bootscript,omitempty"`
// Hostname represents the ServerName in a format compatible with unix's hostname
Hostname string `json:"hostname,omitempty"`
// Tags represents user-defined tags
Tags []string `json:"tags,omitempty"`
// Volumes are the attached volumes
Volumes map[string]ScalewayVolume `json:"volumes,omitempty"`
// SecurityGroup is the selected security group object
SecurityGroup ScalewaySecurityGroup `json:"security_group,omitempty"`
// Organization is the owner of the server
Organization string `json:"organization,omitempty"`
// CommercialType is the commercial type of the server (i.e: C1, C2[SML], VC1S)
CommercialType string `json:"commercial_type,omitempty"`
// Location of the server
Location struct {
Platform string `json:"platform_id,omitempty"`
Chassis string `json:"chassis_id,omitempty"`
Cluster string `json:"cluster_id,omitempty"`
Hypervisor string `json:"hypervisor_id,omitempty"`
Blade string `json:"blade_id,omitempty"`
Node string `json:"node_id,omitempty"`
ZoneID string `json:"zone_id,omitempty"`
} `json:"location,omitempty"`
IPV6 *ScalewayIPV6Definition `json:"ipv6,omitempty"`
EnableIPV6 bool `json:"enable_ipv6,omitempty"`
// This fields are not returned by the API, we generate it
DNSPublic string `json:"dns_public,omitempty"`
DNSPrivate string `json:"dns_private,omitempty"`
}
// ScalewayIPV6Definition represents a Scaleway ipv6
type ScalewayIPV6Definition struct {
Netmask string `json:"netmask"`
Gateway string `json:"gateway"`
Address string `json:"address"`
}
// ScalewayServerPatchDefinition represents a Scaleway server with nullable fields (for PATCH)
type ScalewayServerPatchDefinition struct {
Arch *string `json:"arch,omitempty"`
Name *string `json:"name,omitempty"`
CreationDate *string `json:"creation_date,omitempty"`
ModificationDate *string `json:"modification_date,omitempty"`
Image *ScalewayImage `json:"image,omitempty"`
DynamicIPRequired *bool `json:"dynamic_ip_required,omitempty"`
PublicAddress *ScalewayIPAddress `json:"public_ip,omitempty"`
State *string `json:"state,omitempty"`
StateDetail *string `json:"state_detail,omitempty"`
PrivateIP *string `json:"private_ip,omitempty"`
Bootscript *string `json:"bootscript,omitempty"`
Hostname *string `json:"hostname,omitempty"`
Volumes *map[string]ScalewayVolume `json:"volumes,omitempty"`
SecurityGroup *ScalewaySecurityGroup `json:"security_group,omitempty"`
Organization *string `json:"organization,omitempty"`
Tags *[]string `json:"tags,omitempty"`
IPV6 *ScalewayIPV6Definition `json:"ipv6,omitempty"`
EnableIPV6 *bool `json:"enable_ipv6,omitempty"`
}
// ScalewayServerDefinition represents a Scaleway server with image definition
type ScalewayServerDefinition struct {
// Name is the user-defined name of the server
Name string `json:"name"`
// Image is the image used by the server
Image *string `json:"image,omitempty"`
// Volumes are the attached volumes
Volumes map[string]string `json:"volumes,omitempty"`
// DynamicIPRequired is a flag that defines a server with a dynamic ip address attached
DynamicIPRequired *bool `json:"dynamic_ip_required,omitempty"`
// BootType defines the type of boot
BootType string `json:"boot_type,omitempty"`
// Bootscript is the bootscript used by the server
Bootscript *string `json:"bootscript"`
// Tags are the metadata tags attached to the server
Tags []string `json:"tags,omitempty"`
// Organization is the owner of the server
Organization string `json:"organization"`
// CommercialType is the commercial type of the server (i.e: C1, C2[SML], VC1S)
CommercialType string `json:"commercial_type"`
PublicIP string `json:"public_ip,omitempty"`
EnableIPV6 bool `json:"enable_ipv6,omitempty"`
SecurityGroup string `json:"security_group,omitempty"`
}
// ScalewayOneServer represents the response of a GET /servers/UUID API call
type ScalewayOneServer struct {
Server ScalewayServer `json:"server,omitempty"`
}
// ScalewayServers represents a group of Scaleway servers
type ScalewayServers struct {
// Servers holds scaleway servers of the response
Servers []ScalewayServer `json:"servers,omitempty"`
}
// ScalewayServerAction represents an action to perform on a Scaleway server
type ScalewayServerAction struct {
// Action is the name of the action to trigger
Action string `json:"action,omitempty"`
}
// ScalewaySnapshotDefinition represents a Scaleway snapshot definition
type ScalewaySnapshotDefinition struct {
VolumeIDentifier string `json:"volume_id"`
Name string `json:"name,omitempty"`
Organization string `json:"organization"`
}
// ScalewayImageDefinition represents a Scaleway image definition
type ScalewayImageDefinition struct {
SnapshotIDentifier string `json:"root_volume"`
Name string `json:"name,omitempty"`
Organization string `json:"organization"`
Arch string `json:"arch"`
DefaultBootscript *string `json:"default_bootscript,omitempty"`
}
// ScalewayRoleDefinition represents a Scaleway Token UserId Role
type ScalewayRoleDefinition struct {
Organization ScalewayOrganizationDefinition `json:"organization,omitempty"`
Role string `json:"role,omitempty"`
}
// ScalewayTokenDefinition represents a Scaleway Token
type ScalewayTokenDefinition struct {
UserID string `json:"user_id"`
Description string `json:"description,omitempty"`
Roles ScalewayRoleDefinition `json:"roles"`
Expires string `json:"expires"`
InheritsUsersPerms bool `json:"inherits_user_perms"`
ID string `json:"id"`
}
// ScalewayTokensDefinition represents a Scaleway Tokens
type ScalewayTokensDefinition struct {
Token ScalewayTokenDefinition `json:"token"`
}
// ScalewayGetTokens represents a list of Scaleway Tokens
type ScalewayGetTokens struct {
Tokens []ScalewayTokenDefinition `json:"tokens"`
}
// ScalewayContainerData represents a Scaleway container data (S3)
type ScalewayContainerData struct {
LastModified string `json:"last_modified"`
Name string `json:"name"`
Size string `json:"size"`
}
// ScalewayGetContainerDatas represents a list of Scaleway containers data (S3)
type ScalewayGetContainerDatas struct {
Container []ScalewayContainerData `json:"container"`
}
// ScalewayContainer represents a Scaleway container (S3)
type ScalewayContainer struct {
ScalewayOrganizationDefinition `json:"organization"`
Name string `json:"name"`
Size string `json:"size"`
}
// ScalewayGetContainers represents a list of Scaleway containers (S3)
type ScalewayGetContainers struct {
Containers []ScalewayContainer `json:"containers"`
}
// ScalewayConnectResponse represents the answer from POST /tokens
type ScalewayConnectResponse struct {
Token ScalewayTokenDefinition `json:"token"`
}
// ScalewayConnect represents the data to connect
type ScalewayConnect struct {
Email string `json:"email"`
Password string `json:"password"`
Description string `json:"description"`
Expires bool `json:"expires"`
}
// ScalewayConnectInterface is the interface implemented by ScalewayConnect,
// ScalewayConnectByOTP and ScalewayConnectByBackupCode
type ScalewayConnectInterface interface {
GetPassword() string
}
func (s *ScalewayConnect) GetPassword() string {
return s.Password
}
type ScalewayConnectByOTP struct {
ScalewayConnect
TwoFAToken string `json:"2FA_token"`
}
type ScalewayConnectByBackupCode struct {
ScalewayConnect
TwoFABackupCode string `json:"2FA_backup_code"`
}
// ScalewayOrganizationDefinition represents a Scaleway Organization
type ScalewayOrganizationDefinition struct {
ID string `json:"id"`
Name string `json:"name"`
Users []ScalewayUserDefinition `json:"users"`
}
// ScalewayOrganizationsDefinition represents a Scaleway Organizations
type ScalewayOrganizationsDefinition struct {
Organizations []ScalewayOrganizationDefinition `json:"organizations"`
}
// ScalewayUserDefinition represents a Scaleway User
type ScalewayUserDefinition struct {
Email string `json:"email"`
Firstname string `json:"firstname"`
Fullname string `json:"fullname"`
ID string `json:"id"`
Lastname string `json:"lastname"`
Organizations []ScalewayOrganizationDefinition `json:"organizations"`
Roles []ScalewayRoleDefinition `json:"roles"`
SSHPublicKeys []ScalewayKeyDefinition `json:"ssh_public_keys"`
}
// ScalewayUsersDefinition represents the response of a GET /user
type ScalewayUsersDefinition struct {
User ScalewayUserDefinition `json:"user"`
}
// ScalewayKeyDefinition represents a key
type ScalewayKeyDefinition struct {
Key string `json:"key"`
Fingerprint string `json:"fingerprint,omitempty"`
}
// ScalewayUserPatchSSHKeyDefinition represents a User Patch
type ScalewayUserPatchSSHKeyDefinition struct {
SSHPublicKeys []ScalewayKeyDefinition `json:"ssh_public_keys"`
}
// ScalewayDashboardResp represents a dashboard received from the API
type ScalewayDashboardResp struct {
Dashboard ScalewayDashboard
}
// ScalewayDashboard represents a dashboard
type ScalewayDashboard struct {
VolumesCount int `json:"volumes_count"`
RunningServersCount int `json:"running_servers_count"`
ImagesCount int `json:"images_count"`
SnapshotsCount int `json:"snapshots_count"`
ServersCount int `json:"servers_count"`
IPsCount int `json:"ips_count"`
}
// ScalewayPermissions represents the response of GET /permissions
type ScalewayPermissions map[string]ScalewayPermCategory
// ScalewayPermCategory represents ScalewayPermissions's fields
type ScalewayPermCategory map[string][]string
// ScalewayPermissionDefinition represents the permissions
type ScalewayPermissionDefinition struct {
Permissions ScalewayPermissions `json:"permissions"`
}
// ScalewayUserdatas represents the response of a GET /user_data
type ScalewayUserdatas struct {
UserData []string `json:"user_data"`
}
// ScalewayQuota represents a map of quota (name, value)
type ScalewayQuota map[string]int
// ScalewayGetQuotas represents the response of GET /organizations/{orga_id}/quotas
type ScalewayGetQuotas struct {
Quotas ScalewayQuota `json:"quotas"`
}
// ScalewayUserdata represents []byte
type ScalewayUserdata []byte
// FuncMap used for json inspection
var FuncMap = template.FuncMap{
"json": func(v interface{}) string {
a, _ := json.Marshal(v)
return string(a)
},
}
// MarketLocalImageDefinition represents localImage of marketplace version
type MarketLocalImageDefinition struct {
Arch string `json:"arch"`
ID string `json:"id"`
Zone string `json:"zone"`
}
// MarketLocalImages represents an array of local images
type MarketLocalImages struct {
LocalImages []MarketLocalImageDefinition `json:"local_images"`
}
// MarketLocalImage represents local image
type MarketLocalImage struct {
LocalImages MarketLocalImageDefinition `json:"local_image"`
}
// MarketVersionDefinition represents version of marketplace image
type MarketVersionDefinition struct {
CreationDate string `json:"creation_date"`
ID string `json:"id"`
Image struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"image"`
ModificationDate string `json:"modification_date"`
Name string `json:"name"`
MarketLocalImages
}
// MarketVersions represents an array of marketplace image versions
type MarketVersions struct {
Versions []MarketVersionDefinition `json:"versions"`
}
// MarketVersion represents version of marketplace image
type MarketVersion struct {
Version MarketVersionDefinition `json:"version"`
}
// MarketImage represents MarketPlace image
type MarketImage struct {
Categories []string `json:"categories"`
CreationDate string `json:"creation_date"`
CurrentPublicVersion string `json:"current_public_version"`
Description string `json:"description"`
ID string `json:"id"`
Logo string `json:"logo"`
ModificationDate string `json:"modification_date"`
Name string `json:"name"`
Organization struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"organization"`
Public bool `json:"-"`
MarketVersions
}
// MarketImages represents MarketPlace images
type MarketImages struct {
Images []MarketImage `json:"images"`
}
// ScalewaySortServers represents a wrapper to sort by CreationDate the servers
type ScalewaySortServers []ScalewayServer
func (s ScalewaySortServers) Len() int {
return len(s)
}
func (s ScalewaySortServers) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s ScalewaySortServers) Less(i, j int) bool {
date1, _ := time.Parse("2006-01-02T15:04:05.000000+00:00", s[i].CreationDate)
date2, _ := time.Parse("2006-01-02T15:04:05.000000+00:00", s[j].CreationDate)
return date2.Before(date1)
}
func (s *ScalewayUserdata) String() string {
return string(*s)
}

27
src/cmd/linuxkit/vendor/golang.org/x/sync/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

22
src/cmd/linuxkit/vendor/golang.org/x/sync/PATENTS generated vendored Normal file
View File

@@ -0,0 +1,22 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.

18
src/cmd/linuxkit/vendor/golang.org/x/sync/README.md generated vendored Normal file
View File

@@ -0,0 +1,18 @@
# Go Sync
This repository provides Go concurrency primitives in addition to the
ones provided by the language and "sync" and "sync/atomic" packages.
## Download/Install
The easiest way to install is to run `go get -u golang.org/x/sync`. You can
also manually git clone the repository to `$GOPATH/src/golang.org/x/sync`.
## Report Issues / Send Patches
This repository uses Gerrit for code changes. To learn how to submit changes to
this repository, see https://golang.org/doc/contribute.html.
The main issue tracker for the sync repository is located at
https://github.com/golang/go/issues. Prefix your issue with "x/sync:" in the
subject line, so it is easy to find.

View File

@@ -0,0 +1,67 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package errgroup provides synchronization, error propagation, and Context
// cancelation for groups of goroutines working on subtasks of a common task.
package errgroup
import (
"sync"
"golang.org/x/net/context"
)
// A Group is a collection of goroutines working on subtasks that are part of
// the same overall task.
//
// A zero Group is valid and does not cancel on error.
type Group struct {
cancel func()
wg sync.WaitGroup
errOnce sync.Once
err error
}
// WithContext returns a new Group and an associated Context derived from ctx.
//
// The derived Context is canceled the first time a function passed to Go
// returns a non-nil error or the first time Wait returns, whichever occurs
// first.
func WithContext(ctx context.Context) (*Group, context.Context) {
ctx, cancel := context.WithCancel(ctx)
return &Group{cancel: cancel}, ctx
}
// Wait blocks until all function calls from the Go method have returned, then
// returns the first non-nil error (if any) from them.
func (g *Group) Wait() error {
g.wg.Wait()
if g.cancel != nil {
g.cancel()
}
return g.err
}
// Go calls the given function in a new goroutine.
//
// The first call to return a non-nil error cancels the group; its error will be
// returned by Wait.
func (g *Group) Go(f func() error) {
g.wg.Add(1)
go func() {
defer g.wg.Done()
if err := f(); err != nil {
g.errOnce.Do(func() {
g.err = err
if g.cancel != nil {
g.cancel()
}
})
}
}()
}