Fix issues with typing the boot command over VNC and ensure that centos VMs can run kickstarter (albeit with errors)

This commit is contained in:
Dom Del Nano 2020-12-15 23:17:27 -08:00
parent 1221794f0a
commit d258626c85
360 changed files with 282 additions and 178659 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ bin
pkg
crash.log
packer_cache/*
builder-*

Binary file not shown.

View File

@ -3,14 +3,15 @@ package common
import (
"bytes"
"fmt"
"github.com/mitchellh/multistep"
commonssh "github.com/mitchellh/packer/common/ssh"
"github.com/mitchellh/packer/communicator/ssh"
gossh "golang.org/x/crypto/ssh"
"io"
"log"
"net"
"strings"
"github.com/mitchellh/multistep"
commonssh "github.com/mitchellh/packer/common/ssh"
"github.com/mitchellh/packer/communicator/ssh"
gossh "golang.org/x/crypto/ssh"
)
func SSHAddress(state multistep.StateBag) (string, error) {
@ -56,8 +57,9 @@ func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*gossh.ClientConf
}
return &gossh.ClientConfig{
User: config.SSHUser,
Auth: auth,
User: config.SSHUser,
Auth: auth,
HostKeyCallback: gossh.InsecureIgnoreHostKey(),
}, nil
}
}
@ -94,6 +96,7 @@ func ExecuteHostSSHCmd(state multistep.StateBag, cmd string) (stdout string, err
Auth: []gossh.AuthMethod{
gossh.Password(config.Password),
},
HostKeyCallback: gossh.InsecureIgnoreHostKey(),
}
return doExecuteSSHCmd(cmd, sshAddress, sshConfig)
}
@ -162,6 +165,7 @@ func ssh_port_forward(local_listener net.Listener, remote_port uint, remote_dest
Auth: []gossh.AuthMethod{
gossh.Password(password),
},
HostKeyCallback: gossh.InsecureIgnoreHostKey(),
}
for {

View File

@ -3,7 +3,9 @@ package common
/* Heavily borrowed from builder/quemu/step_type_boot_command.go */
import (
"crypto/tls"
"fmt"
"io"
"log"
"net"
"strings"
@ -32,18 +34,46 @@ type StepTypeBootCommand struct {
func (self *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("commonconfig").(CommonConfig)
ui := state.Get("ui").(packer.Ui)
vnc_port := state.Get("local_vnc_port").(uint)
http_port := state.Get("http_port").(uint)
c := state.Get("client").(*Connection)
httpPort := state.Get("http_port").(uint)
// skip this step if we have nothing to type
if len(config.BootCommand) == 0 {
return multistep.ActionContinue
}
// Connect to the local VNC port as we have set up a SSH port forward
ui.Say("Connecting to the VM over VNC")
ui.Message(fmt.Sprintf("Using local port: %d", vnc_port))
net_conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", vnc_port))
vmRef, err := c.client.VM.GetByNameLabel(c.session, config.VMName)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(vmRef) != 1 {
ui.Error(fmt.Sprintf("expected to find a single VM, instead found '%d'. Ensure the VM name is unique", len(vmRef)))
}
consoles, err := c.client.VM.GetConsoles(c.session, vmRef[0])
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(consoles) != 1 {
ui.Error(fmt.Sprintf("expected to find a VM console, instead found '%d'. Ensure there is only one console", len(consoles)))
return multistep.ActionHalt
}
location, err := c.client.Console.GetLocation(c.session, consoles[0])
if err != nil {
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Connecting to the VM console VNC over xapi")
conn, err := net.Dial("tcp", fmt.Sprintf("%s:443", config.HostIp))
if err != nil {
err := fmt.Errorf("Error connecting to VNC: %s", err)
@ -52,9 +82,40 @@ func (self *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAct
return multistep.ActionHalt
}
defer net_conn.Close()
defer conn.Close()
c, err := vnc.Client(net_conn, &vnc.ClientConfig{Exclusive: true})
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
tlsConn := tls.Client(conn, tlsConfig)
locationPieces := strings.SplitAfter(location, "/")
consoleLocation := strings.TrimSpace(fmt.Sprintf("/%s", locationPieces[len(locationPieces)-1]))
httpReq := fmt.Sprintf("CONNECT %s HTTP/1.0\r\nCookie: session_id=%s\r\n\r\n", consoleLocation, c.session)
fmt.Printf("Sending the follow http req: %v", httpReq)
ui.Say(fmt.Sprintf("Making HTTP request to initiate VNC connection: %s", httpReq))
_, err = io.WriteString(tlsConn, httpReq)
if err != nil {
err := fmt.Errorf("failed to start vnc session: %v", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
buffer := make([]byte, 10000)
_, err = tlsConn.Read(buffer)
if err != nil && err != io.EOF {
err := fmt.Errorf("failed to read vnc session response: %v", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Received response: %s", string(buffer)))
vncClient, err := vnc.Client(tlsConn, &vnc.ClientConfig{Exclusive: true})
if err != nil {
err := fmt.Errorf("Error establishing VNC session: %s", err)
@ -63,9 +124,9 @@ func (self *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAct
return multistep.ActionHalt
}
defer c.Close()
defer vncClient.Close()
log.Printf("Connected to the VNC console: %s", c.DesktopName)
log.Printf("Connected to the VNC console: %s", vncClient.DesktopName)
// find local ip
envVar, err := ExecuteHostSSHCmd(state, "echo $SSH_CLIENT")
@ -83,7 +144,7 @@ func (self *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAct
self.Ctx.Data = &bootCommandTemplateData{
config.VMName,
localIp,
http_port,
httpPort,
}
ui.Say("Typing boot commands over VNC...")
@ -102,7 +163,7 @@ func (self *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAct
return multistep.ActionHalt
}
vncSendString(c, command)
vncSendString(vncClient, command)
}
ui.Say("Finished typing.")

View File

@ -34,7 +34,15 @@ func (self *StepUploadVdi) Run(state multistep.StateBag) multistep.StepAction {
srs, err := c.client.SR.GetAll(c.session)
ui.Say(fmt.Sprintf("Step: Found SRs '%v'", srs))
srs, err = c.client.SR.GetByNameLabel(c.session, "LocalISO")
// TODO (ddelnano): This must be changed to match the ISO Storage repository available
nameLabel := "LocalISO"
// nameLabel := "ISOs"
srs, err = c.client.SR.GetByNameLabel(c.session, nameLabel)
if len(srs) != 1 {
ui.Error(fmt.Sprintf("expected to find a single storage repository with name '%s', instead found '%d' storage repositories", nameLabel, len(srs)))
}
sr := srs[0]
ui.Say(fmt.Sprintf("Step: Found SRs '%v' Choosing: '%v'", srs, sr))
if err != nil {

View File

@ -287,14 +287,13 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa
},
new(xscommon.StepStartVmPaused),
new(xscommon.StepSetVmHostSshAddress),
new(xscommon.StepGetVNCPort),
&xscommon.StepForwardPortOverSSH{
RemotePort: xscommon.InstanceVNCPort,
RemoteDest: xscommon.InstanceVNCIP,
HostPortMin: self.config.HostPortMin,
HostPortMax: self.config.HostPortMax,
ResultKey: "local_vnc_port",
},
// &xscommon.StepForwardPortOverSSH{
// RemotePort: xscommon.InstanceVNCPort,
// RemoteDest: xscommon.InstanceVNCIP,
// HostPortMin: self.config.HostPortMin,
// HostPortMax: self.config.HostPortMax,
// ResultKey: "local_vnc_port",
// },
new(xscommon.StepBootWait),
&xscommon.StepTypeBootCommand{
Ctx: self.config.ctx,

View File

@ -64,11 +64,12 @@ func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepActi
return multistep.ActionHalt
}
// err = c.GetClient().VM.SetMemoryStaticRange(c.GetSessionRef(), instance, int(config.VMMemory*1024*1024), int(config.VMMemory*1024*1024))
// if err != nil {
// ui.Error(fmt.Sprintf("Error setting VM memory=%d: %s", config.VMMemory*1024*1024, err.Error()))
// return multistep.ActionHalt
// }
memory := int(config.VMMemory * 1024 * 1024)
err = c.GetClient().VM.SetMemoryLimits(c.GetSessionRef(), instance, memory, memory, memory, memory)
if err != nil {
ui.Error(fmt.Sprintf("Error setting VM memory=%d: %s", memory, err.Error()))
return multistep.ActionHalt
}
err = c.GetClient().VM.SetPlatform(c.GetSessionRef(), instance, config.PlatformArgs)
if err != nil {

View File

@ -157,14 +157,6 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa
},
new(xscommon.StepStartVmPaused),
new(xscommon.StepSetVmHostSshAddress),
new(xscommon.StepGetVNCPort),
&xscommon.StepForwardPortOverSSH{
RemotePort: xscommon.InstanceVNCPort,
RemoteDest: xscommon.InstanceVNCIP,
HostPortMin: self.config.HostPortMin,
HostPortMax: self.config.HostPortMax,
ResultKey: "local_vnc_port",
},
new(xscommon.StepBootWait),
&xscommon.StepTypeBootCommand{
Ctx: self.config.ctx,
@ -173,13 +165,6 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa
Chan: httpReqChan,
Timeout: 300 * time.Minute, /*self.config.InstallTimeout*/ // @todo change this
},
&xscommon.StepForwardPortOverSSH{
RemotePort: xscommon.InstanceSSHPort,
RemoteDest: xscommon.InstanceSSHIP,
HostPortMin: self.config.HostPortMin,
HostPortMax: self.config.HostPortMax,
ResultKey: "local_ssh_port",
},
&communicator.StepConnect{
Config: &self.config.SSHConfig.Comm,
Host: xscommon.CommHost,

View File

@ -0,0 +1,81 @@
install
cdrom
lang en_US.UTF-8
keyboard us
network --bootproto=dhcp
rootpw vagrant
firewall --disabled
selinux --permissive
timezone UTC
bootloader --location=mbr
text
skipx
zerombr
clearpart --all --initlabel
autopart
auth --enableshadow --passalgo=sha512 --kickstart
firstboot --disabled
eula --agreed
services --enabled=NetworkManager,sshd
user --name=vagrant --plaintext --password=vagrant --groups=wheel
reboot
%packages --ignoremissing --excludedocs
@Base
@Core
@Development Tools
openssh-clients
sudo
openssl-devel
readline-devel
zlib-devel
kernel-headers
kernel-devel
net-tools
vim
wget
curl
rsync
# unnecessary firmware
-aic94xx-firmware
-atmel-firmware
-b43-openfwwf
-bfa-firmware
-ipw2100-firmware
-ipw2200-firmware
-ivtv-firmware
-iwl100-firmware
-iwl1000-firmware
-iwl3945-firmware
-iwl4965-firmware
-iwl5000-firmware
-iwl5150-firmware
-iwl6000-firmware
-iwl6000g2a-firmware
-iwl6050-firmware
-libertas-usb8388-firmware
-ql2100-firmware
-ql2200-firmware
-ql23xx-firmware
-ql2400-firmware
-ql2500-firmware
-rt61pci-firmware
-rt73usb-firmware
-xorg-x11-drv-ati-firmware
-zd1211-firmware
%end
%post
yum update -y
# update root certs
wget -O/etc/pki/tls/certs/ca-bundle.crt http://curl.haxx.se/ca/cacert.pem
# sudo
yum install -y sudo
echo "vagrant ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/vagrant
sed -i "s/^.*requiretty/#Defaults requiretty/" /etc/sudoers
yum clean all
%end

View File

@ -1,42 +0,0 @@
{
"builders": [
{
"type": "xenserver-iso",
"tools_iso_name": "guest-tools.iso",
"remote_host": "",
"remote_username": "root",
"remote_password": "",
"boot_command": [
"<tab> text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ks-1804-minimalvm.cfg<enter><wait>"
],
"boot_wait": "10s",
"disk_size": 40960,
"http_directory": "examples/http",
"iso_checksum": "443511f6bf12402c12503733059269a2e10dec602916c0a75263e5d990f6bb93",
"iso_checksum_type": "sha256",
"iso_url": "https://releases.ubuntu.com/20.04.1/ubuntu-20.04.1-live-server-amd64.iso",
"vm_other_config": {
"conversionvm":"true"
},
"network_names": [
"Host internal management network",
"Pool-wide network associated with eth0"
],
"output_directory": "packer-centos-6.6-x86_64-xenserver",
"shutdown_command": "/sbin/halt",
"ssh_username": "root",
"ssh_password": "vmpassword",
"ssh_wait_timeout": "10000s",
"vm_name": "packer-centos-6.6-x86_64",
"vm_description": "Build time: {{isotime}}",
"clone_template": "Ubuntu Bionic Beaver 18.04",
"disk_size": "20000",
"vm_memory": "2048"
}
],
"variables": {
"mirror": "http://www.mirrorservice.org/sites/mirror.centos.org"
}
}

21
go.mod
View File

@ -4,14 +4,24 @@ go 1.14
require (
github.com/Sirupsen/logrus v0.10.1-0.20160601113210-f3cfb454f4c2 // indirect
github.com/amfranz/go-xmlrpc-client v0.0.0-20190612172737-76858463955d
github.com/dylanmei/iso8601 v0.1.0 // indirect
github.com/dylanmei/winrmtest v0.0.0-20151226195028-025617847eb2 // indirect
github.com/elithrar/simple-scrypt v1.3.0 // indirect
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a // indirect
github.com/glycerine/greenpack v5.1.1+incompatible // indirect
github.com/glycerine/sshego v7.0.3+incompatible
github.com/gobuffalo/envy v1.9.0 // indirect
github.com/golang/snappy v0.0.2 // indirect
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce // indirect
github.com/hashicorp/go-multierror v0.0.0-20150916205742-d30f09973e19 // indirect
github.com/hashicorp/go-version v0.0.0-20160119211326-7e3c02b30806 // indirect
github.com/hashicorp/yamux v0.0.0-20151129044643-df949784da9e // indirect
github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b
github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b // indirect
github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169 // indirect
github.com/mailgun/log v0.0.0-20150926000944-2f35a4607f1a // indirect
github.com/mailgun/mailgun-go v2.0.0+incompatible // indirect
github.com/mailgun/mailgun-go/v4 v4.3.0 // indirect
github.com/masterzen/simplexml v0.0.0-20140219194429-95ba30457eb1 // indirect
github.com/masterzen/winrm v0.0.0-20151214220635-54ea5d01478c // indirect
github.com/masterzen/xmlpath v0.0.0-20140218185901-13f4951698ad // indirect
@ -26,13 +36,20 @@ require (
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/packer-community/winrmcp v0.0.0-20160310040704-f1bcf36a69fa // indirect
github.com/philhofer/fwd v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/sftp v0.0.0-20160118190721-e84cc8c755ca // indirect
github.com/pquerna/otp v1.3.0 // indirect
github.com/satori/go.uuid v0.0.0-20151028231719-d41af8bb6a77 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/stretchr/testify v1.6.1 // indirect
github.com/terra-farm/go-xen-api-client v0.0.1
github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1 // indirect
golang.org/x/crypto v0.0.0-20160126184038-1f22c0103821
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
launchpad.net/gocheck v0.0.0-20140225173054-000000000087 // indirect
launchpad.net/xmlpath v0.0.0-20130614043138-000000000004 // indirect
)
// replace github.com/glycerine/sshego => github.com/ddelnano/sshego v1.0.1
replace github.com/glycerine/sshego => /home/ddelnano/code/sshego

76
go.sum
View File

@ -2,13 +2,44 @@ github.com/Sirupsen/logrus v0.10.1-0.20160601113210-f3cfb454f4c2 h1:3BYvDlSNPyoY
github.com/Sirupsen/logrus v0.10.1-0.20160601113210-f3cfb454f4c2/go.mod h1:rmk17hk6i8ZSAJkSDa7nOxamrG+SP4P0mm+DAvExv4U=
github.com/amfranz/go-xmlrpc-client v0.0.0-20190612172737-76858463955d h1:39lR6Kg+GsvDpLMD2Mb7gkjXmmLexqfr7SPy4iQWDTE=
github.com/amfranz/go-xmlrpc-client v0.0.0-20190612172737-76858463955d/go.mod h1:2NlXXRCkTbr/vZtUjcHKhbrESE4a3CDqVrgOROB16dg=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ddelnano/sshego v1.0.1 h1:bGIa4UVYK07/vNN42EoBPDr0s0VzjN85mKYRyUwnpBk=
github.com/ddelnano/sshego v1.0.1/go.mod h1:D3lQWAbXlQCYZnN76ll2XBmd79S07+8Cp6SnAyxNu3E=
github.com/ddelnano/sshego v7.0.4-0.20201209072259-2d72bfe49508+incompatible h1:/iHzMzgdBaY146gfbiOLmH/w9PtLRfZOgmH8sUxejNU=
github.com/ddelnano/sshego v7.0.4-0.20201209072259-2d72bfe49508+incompatible/go.mod h1:zgzEjbZaaS24/l6BXzokfkfjygokZt93n9ucnQGOEkg=
github.com/ddelnano/sshego v7.0.4+incompatible h1:13G3VeEDR4JDzBGVLJydO5SlilXTqhQqX2thQbXTNJI=
github.com/ddelnano/sshego v7.0.4+incompatible/go.mod h1:zgzEjbZaaS24/l6BXzokfkfjygokZt93n9ucnQGOEkg=
github.com/dylanmei/iso8601 v0.1.0 h1:812NGQDBcqquTfH5Yeo7lwR0nzx/cKdsmf3qMjPURUI=
github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ=
github.com/dylanmei/winrmtest v0.0.0-20151226195028-025617847eb2 h1:J29UzyZ34t5zmOLtGgjoSMpbCYCFooaNWlP/j+dX2Go=
github.com/dylanmei/winrmtest v0.0.0-20151226195028-025617847eb2/go.mod h1:VBVDFSBXCIW8JaHQpI8lldSKfYaLMzP9oyq6IJ4fhzY=
github.com/elithrar/simple-scrypt v1.3.0 h1:KIlOlxdoQf9JWKl5lMAJ28SY2URB0XTRDn2TckyzAZg=
github.com/elithrar/simple-scrypt v1.3.0/go.mod h1:U2XQRI95XHY0St410VE3UjT7vuKb1qPwrl/EJwEqnZo=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a h1:FQqoVvjbiUioBBFUL5up+h+GdCa/AnJsL/1bIs/veSI=
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
github.com/glycerine/greenpack v1.6.1 h1:ydR6buBceEO97SK/3SpNWK/9gMTXsyu71wUfuhR+h6I=
github.com/glycerine/greenpack v5.1.1+incompatible h1:fDr9i6MkSGZmAy4VXPfJhW+SyK2/LNnzIp5nHyDiaIM=
github.com/glycerine/greenpack v5.1.1+incompatible/go.mod h1:us0jVISAESGjsEuLlAfCd5nkZm6W6WQF18HPuOecIg4=
github.com/glycerine/sshego v1.9.0 h1:J44gfsWw5Vmx4hFPAjXe7/u5fonph27Vub9CEnPxPiQ=
github.com/glycerine/sshego v7.0.3+incompatible h1:iqc8i9ZF0s/uLmyVw849NRso7jd9Mw1GPoQv2vAK6gQ=
github.com/glycerine/sshego v7.0.3+incompatible/go.mod h1:aJZW1S8J4qojuM+7QL8nTzbNLLR9zid+kDypc1h2eMk=
github.com/go-chi/chi v4.0.0+incompatible h1:SiLLEDyAkqNnw+T/uDTf3aFB9T4FTrwMpuYrgaRcnW4=
github.com/go-chi/chi v4.0.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/gobuffalo/envy v1.9.0 h1:eZR0DuEgVLfeIb1zIKt3bT4YovIMf9O9LXQeCZLXpqE=
github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce h1:prjrVgOk2Yg6w+PflHoszQNLTUh4kaByUcEWM/9uin4=
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v0.0.0-20150916205742-d30f09973e19 h1:gb61U/o4ZJ6TRYvZqJUKYidIhJOEAvNyVMesryROxAY=
@ -17,14 +48,30 @@ github.com/hashicorp/go-version v0.0.0-20160119211326-7e3c02b30806 h1:0MKTKHll8V
github.com/hashicorp/go-version v0.0.0-20160119211326-7e3c02b30806/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/yamux v0.0.0-20151129044643-df949784da9e h1:R35YxgX/iWqsDjzoNO/7uFf8vjcgBIc02rvaV/Vr15c=
github.com/hashicorp/yamux v0.0.0-20151129044643-df949784da9e/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b h1:DzHy0GlWeF0KAglaTMY7Q+khIFoG8toHP+wLFBVBQJc=
github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169 h1:YUrU1/jxRqnt0PSrKj1Uj/wEjk/fjnE80QFfi2Zlj7Q=
github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169/go.mod h1:glhvuHOU9Hy7/8PwwdtnarXqLagOX0b/TbZx2zLMqEg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mailgun/log v0.0.0-20150926000944-2f35a4607f1a h1:7ISCqmDCpXcLg9/cfT0CC5FYYbTcaTieZrXqlgWizxk=
github.com/mailgun/log v0.0.0-20150926000944-2f35a4607f1a/go.mod h1:+cBERKDI/5W5wlS0DmgzjZW6+xzWuGuDaH8zzldF8Xg=
github.com/mailgun/mailgun-go v1.1.1 h1:mjMcm4qz+SbjAYbGJ6DKROViKtO5S0YjpuOUxQfdr2A=
github.com/mailgun/mailgun-go v2.0.0+incompatible h1:0FoRHWwMUctnd8KIR3vtZbqdfjpIMxOZgcSa51s8F8o=
github.com/mailgun/mailgun-go v2.0.0+incompatible/go.mod h1:NWTyU+O4aczg/nsGhQnvHL6v2n5Gy6Sv5tNDVvC6FbU=
github.com/mailgun/mailgun-go/v4 v4.3.0 h1:9nAF7LI3k6bfDPbMZQMMl63Q8/vs+dr1FUN8eR1XMhk=
github.com/mailgun/mailgun-go/v4 v4.3.0/go.mod h1:fWuBI2iaS/pSSyo6+EBpHjatQO3lV8onwqcRy7joSJI=
github.com/masterzen/simplexml v0.0.0-20140219194429-95ba30457eb1 h1:cLEbk5d4t8CDqmQtCMc2lk91cflxOrj31k9LTIabPoA=
github.com/masterzen/simplexml v0.0.0-20140219194429-95ba30457eb1/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
github.com/masterzen/winrm v0.0.0-20151214220635-54ea5d01478c h1:qhyvK0cWkEirK1O/Kz1NLY1PUnOLONqvbdLaFjOYj+A=
@ -46,21 +93,38 @@ github.com/mitchellh/packer v0.10.2-0.20160629004225-63edbd40edc5 h1:s+nOB41LJjS
github.com/mitchellh/packer v0.10.2-0.20160629004225-63edbd40edc5/go.mod h1:3TnGTkplC/koV8K6bCfCN1NB34Tye7lmUzo55/X5wqw=
github.com/mitchellh/reflectwalk v0.0.0-20150527153153-eecf4c70c626 h1:88u9H4mNODBcQUwlNpl6+jBhdoYT3WSi0w4hpYaYZ/o=
github.com/mitchellh/reflectwalk v0.0.0-20150527153153-eecf4c70c626/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/packer-community/winrmcp v0.0.0-20160310040704-f1bcf36a69fa h1:ePOmpdPK/UHhi2SKiOxFiC76BZXaG3FMOzxKxIH/kAE=
github.com/packer-community/winrmcp v0.0.0-20160310040704-f1bcf36a69fa/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk=
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v0.0.0-20160118190721-e84cc8c755ca h1:k8gsErq3rkcbAyCnpOycQsbw88NjCHk7L3KfBZKhQDQ=
github.com/pkg/sftp v0.0.0-20160118190721-e84cc8c755ca/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/rogpeppe/go-internal v1.3.2 h1:XU784Pr0wdahMY2bYcyK6N1KuaRAdLtqD4qd8D18Bfs=
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/satori/go.uuid v0.0.0-20151028231719-d41af8bb6a77 h1:X4eLA68fCmv+kFpUBFXIhv5wD5IFVB+oWUxjmpyl02c=
github.com/satori/go.uuid v0.0.0-20151028231719-d41af8bb6a77/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/terra-farm/go-xen-api-client v0.0.1 h1:EigkWRuAwSktqwFwX1DV64RDWTZTTC0uwmP9W3xlLjE=
@ -71,12 +135,24 @@ github.com/xenserver/go-xenserver-client v0.0.0-20150518141242-42dc9035c2e8 h1:5
github.com/xenserver/go-xenserver-client v0.0.0-20150518141242-42dc9035c2e8/go.mod h1:1ItwtwM/tObrp073TdWdvA+4lzAeWCDMJZ7fmntbJUc=
golang.org/x/crypto v0.0.0-20160126184038-1f22c0103821 h1:Ufz3iUGlZbo8uCQ3IddvmNoW0T2XwdZqjVpOrdtyO0c=
golang.org/x/crypto v0.0.0-20160126184038-1f22c0103821/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20160204225817-50c6bc5e4292 h1:FrW0lbpMBPKe4sGJsd3V/eCGzsC2ksMAG+LlbO74+VQ=
golang.org/x/sys v0.0.0-20160204225817-50c6bc5e4292/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
launchpad.net/gocheck v0.0.0-20140225173054-000000000087 h1:Izowp2XBH6Ya6rv+hqbceQyw/gSGoXfH/UPoTGduL54=

View File

@ -1 +0,0 @@
example/example

View File

@ -1,134 +0,0 @@
package xmlrpc
import (
"fmt"
"io/ioutil"
"net/http"
"net/rpc"
"reflect"
)
type Client struct {
*rpc.Client
}
// clientCodec is rpc.ClientCodec interface implementation.
type clientCodec struct {
// url presents url of xmlrpc service
url string
// httpClient works with HTTP protocol
httpClient *http.Client
// cookies stores cookies received on last request
cookies []*http.Cookie
// responses presents map of active requests. It is required to return request id, that
// rpc.Client can mark them as done.
responses map[uint64]*http.Response
// responseBody holds response body of last request.
responseBody []byte
// ready presents channel, that is used to link request and it`s response.
ready chan uint64
}
func (codec *clientCodec) WriteRequest(request *rpc.Request, params interface{}) (err error) {
httpRequest, err := newRequest(codec.url, request.ServiceMethod, params)
if codec.cookies != nil {
for _, cookie := range codec.cookies {
httpRequest.AddCookie(cookie)
}
}
if err != nil {
return err
}
var httpResponse *http.Response
httpResponse, err = codec.httpClient.Do(httpRequest)
if err != nil {
return err
}
if codec.cookies == nil {
codec.cookies = httpResponse.Cookies()
}
codec.responses[request.Seq] = httpResponse
codec.ready <- request.Seq
return nil
}
func (codec *clientCodec) ReadResponseHeader(response *rpc.Response) (err error) {
seq := <-codec.ready
httpResponse := codec.responses[seq]
codec.responseBody, err = ioutil.ReadAll(httpResponse.Body)
if err != nil {
return err
}
httpResponse.Body.Close()
if fault, _ := responseFailed(codec.responseBody); fault {
response.Error = fmt.Sprintf("%v", parseFailedResponse(codec.responseBody))
}
response.Seq = seq
delete(codec.responses, seq)
return nil
}
func (codec *clientCodec) ReadResponseBody(x interface{}) (err error) {
if x == nil {
return nil
}
var result interface{}
result, err = parseSuccessfulResponse(codec.responseBody)
if err != nil {
return err
}
v := reflect.ValueOf(x)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
v.Set(reflect.ValueOf(result))
return nil
}
func (codec *clientCodec) Close() error {
transport := codec.httpClient.Transport.(*http.Transport)
transport.CloseIdleConnections()
return nil
}
// NewClient returns instance of rpc.Client object, that is used to send request to xmlrpc service.
func NewClient(url string, transport *http.Transport) (*Client, error) {
if transport == nil {
transport = &http.Transport{}
}
httpClient := &http.Client{Transport: transport}
codec := clientCodec{
url: url,
httpClient: httpClient,
ready: make(chan uint64),
responses: make(map[uint64]*http.Response),
}
return &Client{rpc.NewClientWithCodec(&codec)}, nil
}

View File

@ -1,7 +0,0 @@
/*
Package xmlrpc implements the XML-RPC specification:
http://xmlrpc.scripting.com/spec.html
TODO: Actual usage documentation here
*/
package xmlrpc

View File

@ -1,26 +0,0 @@
package xmlrpc
import (
"fmt"
)
// Error represents errors returned on xmlrpc request.
type Error struct {
code string
message string
}
// Error() method implements Error interface
func (e *Error) Error() string {
return fmt.Sprintf("Error: \"%s\" Code: %s", e.message, e.code)
}
// Code ...
func (e *Error) Code() string {
return e.code
}
// Message ...
func (e *Error) Message() string {
return e.message
}

View File

@ -1,142 +0,0 @@
package xmlrpc
import (
"bytes"
"encoding/xml"
"fmt"
"net/http"
"reflect"
"strings"
"time"
)
func newRequest(url string, method string, params ...interface{}) (*http.Request, error) {
body := buildRequestBody(method, params)
request, err := http.NewRequest("POST", url, strings.NewReader(body))
if err != nil {
return nil, err
}
request.Header.Set("Content-Type", "text/xml")
request.Header.Set("Content-Length", fmt.Sprintf("%d", len(body)))
return request, nil
}
func buildRequestBody(method string, params []interface{}) (buffer string) {
buffer += `<?xml version="1.0" encoding="UTF-8"?><methodCall>`
buffer += fmt.Sprintf("<methodName>%s</methodName><params>", method)
if params != nil && len(params) > 0 {
for _, value := range params {
if value != nil {
switch ps := value.(type) {
case Params:
for _, p := range ps.Params {
if p != nil {
buffer += buildParamElement(p)
}
}
default:
buffer += buildParamElement(ps)
}
}
}
}
buffer += "</params></methodCall>"
return
}
func buildParamElement(value interface{}) string {
return fmt.Sprintf("<param>%s</param>", buildValueElement(value))
}
func buildValueElement(value interface{}) (buffer string) {
buffer = `<value>`
switch v := value.(type) {
case Struct:
buffer += buildStructElement(v)
case Base64:
escaped := escapeString(string(v))
buffer += fmt.Sprintf("<base64>%s</base64>", escaped)
case string:
escaped := escapeString(value.(string))
buffer += fmt.Sprintf("<string>%s</string>", escaped)
case int, int8, int16, int32, int64:
buffer += fmt.Sprintf("<int>%d</int>", v)
case float32, float64:
buffer += fmt.Sprintf("<double>%f</double>", v)
case bool:
buffer += buildBooleanElement(v)
case time.Time:
buffer += buildTimeElement(v)
default:
rv := reflect.ValueOf(value)
if rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array {
buffer += buildArrayElement(v)
} else {
fmt.Errorf("Unsupported value type")
}
}
buffer += `</value>`
return
}
func buildStructElement(param Struct) (buffer string) {
buffer = `<struct>`
for name, value := range param {
buffer += fmt.Sprintf("<member><name>%s</name>", name)
buffer += buildValueElement(value)
buffer += `</member>`
}
buffer += `</struct>`
return
}
func buildBooleanElement(value bool) (buffer string) {
if value {
buffer = `<boolean>1</boolean>`
} else {
buffer = `<boolean>0</boolean>`
}
return
}
func buildTimeElement(t time.Time) string {
return fmt.Sprintf(
"<dateTime.iso8601>%d%d%dT%d:%d:%d</dateTime.iso8601>",
t.Year(), t.Month(), t.Day(),
t.Hour(), t.Minute(), t.Second(),
)
}
func buildArrayElement(array interface{}) string {
buffer := `<array><data>`
a := reflect.ValueOf(array)
for i := 0; i < a.Len(); i++ {
buffer += buildValueElement(a.Index(i).Interface())
}
buffer += `</data></array>`
return buffer
}
func escapeString(s string) string {
buffer := bytes.NewBuffer([]byte{})
xml.Escape(buffer, []byte(s))
return fmt.Sprintf("%v", buffer)
}

View File

@ -1,47 +0,0 @@
package xmlrpc
import (
"fmt"
"regexp"
)
// responseFailed checks whether response failed or not. Response defined as failed if it
// contains <fault>...</fault> section.
func responseFailed(response []byte) (bool, error) {
fault := true
faultRegexp, err := regexp.Compile(`<fault>(\s|\S)+</fault>`)
if err == nil {
fault = faultRegexp.Match(response)
}
return fault, err
}
func parseSuccessfulResponse(response []byte) (interface{}, error) {
valueXml := getValueXml(response)
return parseValue(valueXml)
}
func parseFailedResponse(response []byte) (err error) {
var valueXml []byte
valueXml = getValueXml(response)
value, err := parseValue(valueXml)
faultDetails := value.(Struct)
if err != nil {
return err
}
return &(Error{
code: fmt.Sprintf("%v", faultDetails["faultCode"]),
message: faultDetails["faultString"].(string),
})
}
func getValueXml(rawXml []byte) []byte {
expr, _ := regexp.Compile(`<value>(\s|\S)+</value>`)
return expr.Find(rawXml)
}

View File

@ -1,219 +0,0 @@
package xmlrpc
import (
"bytes"
"encoding/xml"
"fmt"
"strconv"
"strings"
"time"
)
// TIME_LAYOUT defines time template defined by iso8601, used to encode/decode time values.
const TIME_LAYOUT = "20060102T15:04:05"
const TIME_LAYOUT_iso8601Z = "20060102T15:04:05Z07:00"
func parseValue(valueXml []byte) (result interface{}, err error) {
parser := xml.NewDecoder(bytes.NewReader(valueXml))
result, err = getValue(parser)
return
}
func getValue(parser *xml.Decoder) (result interface{}, err error) {
var token xml.Token
token, err = parser.Token()
if err != nil {
return nil, err
}
for {
switch t := token.(type) {
case xml.StartElement:
switch t.Name.Local {
case "boolean":
return getBooleanValue(parser)
case "dateTime.iso8601":
return getDateValue(parser)
case "double":
return getDoubleValue(parser)
case "int", "i4", "i8":
return getIntValue(parser)
case "base64":
return getBase64Value(parser)
case "string":
return getStringValue(parser)
case "struct":
result, err = getStructValue(parser)
case "array":
result, err = getArrayValue(parser)
default:
// Move on
}
case xml.EndElement:
if t.Name.Local == "value" {
return result, nil
}
case xml.CharData:
cdata := strings.TrimSpace(string(t))
if cdata != "" {
result = cdata
}
}
token, err = parser.Token()
if err != nil {
return nil, err
}
}
return
}
func getBooleanValue(parser *xml.Decoder) (result interface{}, err error) {
var value string
value, err = getElementValue(parser)
switch value {
case "0":
return false, nil
case "1":
return true, nil
}
return nil, fmt.Errorf("Parse error: invalid boolean value (%s).", value)
}
func getDateValue(parser *xml.Decoder) (result interface{}, err error) {
var value string
value, err = getElementValue(parser)
result, err = time.Parse(TIME_LAYOUT, value)
if err != nil {
result, err = time.Parse(TIME_LAYOUT_iso8601Z, value)
}
return
}
func getDoubleValue(parser *xml.Decoder) (interface{}, error) {
value, _ := getElementValue(parser)
return strconv.ParseFloat(value, 64)
}
func getIntValue(parser *xml.Decoder) (interface{}, error) {
value, _ := getElementValue(parser)
var number int64
number, err := strconv.ParseInt(value, 0, 64)
return number, err
}
func getBase64Value(parser *xml.Decoder) (string, error) {
return getElementValue(parser)
}
func getStringValue(parser *xml.Decoder) (string, error) {
return getElementValue(parser)
}
func getStructValue(parser *xml.Decoder) (result interface{}, err error) {
var token xml.Token
token, err = parser.Token()
result = Struct{}
for {
switch t := token.(type) {
case xml.StartElement:
member := getStructMember(parser)
result.(Struct)[member["name"].(string)] = member["value"]
case xml.EndElement:
if t.Name.Local == "struct" {
return result, err
}
}
token, err = parser.Token()
}
return
}
func getStructMember(parser *xml.Decoder) (member Struct) {
var token xml.Token
token, _ = parser.Token()
member = Struct{}
for {
switch t := token.(type) {
case xml.StartElement:
if t.Name.Local == "name" {
member["name"], _ = getElementValue(parser)
}
if t.Name.Local == "value" {
member["value"], _ = getValue(parser)
}
case xml.EndElement:
if t.Name.Local == "member" {
return member
}
}
token, _ = parser.Token()
}
return
}
func getElementValue(parser *xml.Decoder) (value string, err error) {
var token xml.Token
token, err = parser.Token()
processing := true
for processing {
switch token.(type) {
case xml.CharData:
value = strings.TrimSpace(string(token.(xml.CharData)))
if value != "" {
processing = false
}
case xml.EndElement:
processing = false
}
token, err = parser.Token()
}
return
}
func getArrayValue(parser *xml.Decoder) (result interface{}, err error) {
var token xml.Token
token, err = parser.Token()
result = []interface{}{}
for {
switch t := token.(type) {
case xml.StartElement:
if t.Name.Local == "value" {
var value interface{}
value, err = getValue(parser)
result = append(result.([]interface{}), value)
}
case xml.EndElement:
if t.Name.Local == "array" {
return result, err
}
}
token, err = parser.Token()
}
return
}

View File

@ -1,12 +0,0 @@
package xmlrpc
// Struct presents hash type used in xmlprc requests and responses.
type Struct map[string]interface{}
// Base64 represents base64 data
type Base64 string
// Params represents a list of parameters to a method.
type Params struct {
Params []interface{}
}

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Dylan Meissner
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

@ -1,9 +0,0 @@
iso 8601 parser and formatter
=============================
An [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) Go utility.
- *Time* is not yet implemented
- *Duration* is mostly implemented

View File

@ -1,96 +0,0 @@
package iso8601
import (
"errors"
"fmt"
"regexp"
"strconv"
"time"
)
var (
// ErrBadFormat is returned when parsing fails
ErrBadFormat = errors.New("bad format string")
// ErrNoMonth is raised when a month is in the format string
ErrNoMonth = errors.New("no months allowed")
full = regexp.MustCompile(`P((?P<year>\d+)Y)?((?P<month>\d+)M)?((?P<day>\d+)D)?(T((?P<hour>\d+)H)?((?P<minute>\d+)M)?((?P<second>\d+)S)?)?`)
week = regexp.MustCompile(`P((?P<week>\d+)W)`)
)
// adapted from https://github.com/BrianHicks/finch/duration
func ParseDuration(value string) (time.Duration, error) {
var match []string
var regex *regexp.Regexp
if week.MatchString(value) {
match = week.FindStringSubmatch(value)
regex = week
} else if full.MatchString(value) {
match = full.FindStringSubmatch(value)
regex = full
} else {
return time.Duration(0), ErrBadFormat
}
d := time.Duration(0)
day := time.Hour * 24
week := day * 7
year := day * 365
for i, name := range regex.SubexpNames() {
part := match[i]
if i == 0 || name == "" || part == "" {
continue
}
value, err := strconv.Atoi(part)
if err != nil {
return time.Duration(0), err
}
switch name {
case "year":
d += year * time.Duration(value)
case "month":
return time.Duration(0), ErrNoMonth
case "week":
d += week * time.Duration(value)
case "day":
d += day * time.Duration(value)
case "hour":
d += time.Hour * time.Duration(value)
case "minute":
d += time.Minute * time.Duration(value)
case "second":
d += time.Second * time.Duration(value)
}
}
return d, nil
}
func FormatDuration(duration time.Duration) string {
// we're not doing negative durations
if duration.Seconds() <= 0 {
return "PT0S"
}
hours := int(duration.Hours())
minutes := int(duration.Minutes()) - (hours * 60)
seconds := int(duration.Seconds()) - (hours*3600 + minutes*60)
// we're not doing Y,M,W
s := "PT"
if hours > 0 {
s = fmt.Sprintf("%s%dH", s, hours)
}
if minutes > 0 {
s = fmt.Sprintf("%s%dM", s, minutes)
}
if seconds > 0 {
s = fmt.Sprintf("%s%dS", s, seconds)
}
return s
}

View File

@ -1,22 +0,0 @@
Copyright (c) 2014-2015 Dylan Meissner
MIT License
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

@ -1,48 +0,0 @@
# winrmtest
An in-progress testing package to compliment the [masterzen/winrm](https://github.com/masterzen/winrm) Go-based winrm library.
My primary use-case for this is for [dylanmei/packer-communicator-winrm](https://github.com/dylanmei/packer-communicator-winrm), a [Packer](http://packer.io) communicator plugin for interacting with machines using Windows Remote Management.
## Example Use
A fictitious "Windows tools" package.
```
package wintools
import (
"io"
"testing"
"github.com/dylanmei/winrmtest"
)
func Test_empty_temp_directory(t *testing.T) {
r := winrmtest.NewRemote()
defer r.Close()
r.CommandFunc(wimrmtest.MatchText("dir C:\Temp"), func(out, err io.Writer) int {
out.Write([]byte(` Volume in drive C is Windows 2012 R2
Volume Serial Number is XXXX-XXXX
Directory of C:\
File Not Found`))
return 0
})
lister := NewDirectoryLister(r.Host, r.Port)
list, _ := lister.TempDirectory()
if count := len(list.Dirs()); count != 0 {
t.Errorf("Expected 0 directories but found %d.\n", count)
}
if count := len(list.Files()); count != 0 {
t.Errorf("Expected 0 files but found %d.\n", count)
}
}
```

View File

@ -1,79 +0,0 @@
package winrmtest
import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"regexp"
"strconv"
"strings"
)
// Remote respresents a WinRM server
type Remote struct {
Host string
Port int
server *httptest.Server
service *wsman
}
// NewRemote returns a new initialized Remote
func NewRemote() *Remote {
mux := http.NewServeMux()
srv := httptest.NewServer(mux)
host, port, _ := splitAddr(srv.URL)
remote := Remote{
Host: host,
Port: port,
server: srv,
service: &wsman{},
}
mux.Handle("/wsman", remote.service)
return &remote
}
// Close closes the WinRM server
func (r *Remote) Close() {
r.server.Close()
}
// MatcherFunc respresents a function used to match WinRM commands
type MatcherFunc func(candidate string) bool
// MatchText return a new MatcherFunc based on text matching
func MatchText(text string) MatcherFunc {
return func(candidate string) bool {
return text == candidate
}
}
// MatchPattern return a new MatcherFunc based on pattern matching
func MatchPattern(pattern string) MatcherFunc {
r := regexp.MustCompile(pattern)
return func(candidate string) bool {
return r.MatchString(candidate)
}
}
// CommandFunc respresents a function used to mock WinRM commands
type CommandFunc func(out, err io.Writer) (exitCode int)
// CommandFunc adds a WinRM command mock function to the WinRM server
func (r *Remote) CommandFunc(m MatcherFunc, f CommandFunc) {
r.service.HandleCommand(m, f)
}
func splitAddr(addr string) (host string, port int, err error) {
u, err := url.Parse(addr)
if err != nil {
return
}
split := strings.Split(u.Host, ":")
host = split[0]
port, err = strconv.Atoi(split[1])
return
}

View File

@ -1,170 +0,0 @@
package winrmtest
import (
"bytes"
"encoding/base64"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/masterzen/winrm/soap"
"github.com/masterzen/xmlpath"
"github.com/satori/go.uuid"
)
type wsman struct {
commands []*command
identitySeed int
}
type command struct {
id string
matcher MatcherFunc
handler CommandFunc
}
func (w *wsman) HandleCommand(m MatcherFunc, f CommandFunc) string {
id := uuid.NewV4().String()
w.commands = append(w.commands, &command{
id: id,
matcher: m,
handler: f,
})
return id
}
func (w *wsman) CommandByText(cmd string) *command {
for _, c := range w.commands {
if c.matcher(cmd) {
return c
}
}
return nil
}
func (w *wsman) CommandByID(id string) *command {
for _, c := range w.commands {
if c.id == id {
return c
}
}
return nil
}
func (w *wsman) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
rw.Header().Add("Content-Type", "application/soap+xml")
defer r.Body.Close()
env, err := xmlpath.Parse(r.Body)
if err != nil {
return
}
action := readAction(env)
switch {
case strings.HasSuffix(action, "transfer/Create"):
// create a new shell
rw.Write([]byte(`
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell">
<rsp:ShellId>123</rsp:ShellId>
</env:Envelope>`))
case strings.HasSuffix(action, "shell/Command"):
// execute on behalf of the client
text := readCommand(env)
cmd := w.CommandByText(text)
if cmd == nil {
fmt.Printf("I don't know this command: Command=%s\n", text)
rw.WriteHeader(http.StatusInternalServerError)
return
}
rw.Write([]byte(fmt.Sprintf(`
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell">
<rsp:CommandId>%s</rsp:CommandId>
</env:Envelope>`, cmd.id)))
case strings.HasSuffix(action, "shell/Receive"):
// client ready to receive the results
id := readCommandIDFromDesiredStream(env)
cmd := w.CommandByID(id)
if cmd == nil {
fmt.Printf("I don't know this command: CommandId=%s\n", id)
rw.WriteHeader(http.StatusInternalServerError)
return
}
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
result := cmd.handler(stdout, stderr)
content := base64.StdEncoding.EncodeToString(stdout.Bytes())
rw.Write([]byte(fmt.Sprintf(`
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell">
<rsp:ReceiveResponse>
<rsp:Stream Name="stdout" CommandId="%s">%s</rsp:Stream>
<rsp:Stream Name="stdout" CommandId="%s" End="true"></rsp:Stream>
<rsp:Stream Name="stderr" CommandId="%s" End="true"></rsp:Stream>
<rsp:CommandState State="http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done">
<rsp:ExitCode>%d</rsp:ExitCode>
</rsp:CommandState>
</rsp:ReceiveResponse>
</env:Envelope>`, id, content, id, id, result)))
case strings.HasSuffix(action, "shell/Signal"):
// end of the shell command
rw.WriteHeader(http.StatusOK)
case strings.HasSuffix(action, "transfer/Delete"):
// end of the session
rw.WriteHeader(http.StatusOK)
default:
fmt.Printf("I don't know this action: %s\n", action)
rw.WriteHeader(http.StatusInternalServerError)
}
}
func readAction(env *xmlpath.Node) string {
xpath, err := xmlpath.CompileWithNamespace(
"//a:Action", soap.GetAllNamespaces())
if err != nil {
return ""
}
action, _ := xpath.String(env)
return action
}
func readCommand(env *xmlpath.Node) string {
xpath, err := xmlpath.CompileWithNamespace(
"//rsp:Command", soap.GetAllNamespaces())
if err != nil {
return ""
}
command, _ := xpath.String(env)
if unquoted, err := strconv.Unquote(command); err == nil {
return unquoted
}
return command
}
func readCommandIDFromDesiredStream(env *xmlpath.Node) string {
xpath, err := xmlpath.CompileWithNamespace(
"//rsp:DesiredStream/@CommandId", soap.GetAllNamespaces())
if err != nil {
return ""
}
id, _ := xpath.String(env)
return id
}

View File

@ -1,354 +0,0 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. “Contributor”
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. “Contributor Version”
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributors Contribution.
1.3. “Contribution”
means Covered Software of a particular Contributor.
1.4. “Covered Software”
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. “Incompatible With Secondary Licenses”
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of version
1.1 or earlier of the License, but not also under the terms of a
Secondary License.
1.6. “Executable Form”
means any form of the work other than Source Code Form.
1.7. “Larger Work”
means a work that combines Covered Software with other material, in a separate
file or files, that is not Covered Software.
1.8. “License”
means this document.
1.9. “Licensable”
means having the right to grant, to the maximum extent possible, whether at the
time of the initial grant or subsequently, any and all of the rights conveyed by
this License.
1.10. “Modifications”
means any of the following:
a. any file in Source Code Form that results from an addition to, deletion
from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. “Patent Claims” of a Contributor
means any patent claim(s), including without limitation, method, process,
and apparatus claims, in any patent Licensable by such Contributor that
would be infringed, but for the grant of the License, by the making,
using, selling, offering for sale, having made, import, or transfer of
either its Contributions or its Contributor Version.
1.12. “Secondary License”
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. “Source Code Form”
means the form of the work preferred for making modifications.
1.14. “You” (or “Your”)
means an individual or a legal entity exercising rights under this
License. For legal entities, “You” includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, “control” means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or as
part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its Contributions
or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution become
effective for each Contribution on the date the Contributor first distributes
such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under this
License. No additional rights or licenses will be implied from the distribution
or licensing of Covered Software under this License. Notwithstanding Section
2.1(b) above, no patent license is granted by a Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third partys
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of its
Contributions.
This License does not grant any rights in the trademarks, service marks, or
logos of any Contributor (except as may be necessary to comply with the
notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this License
(see Section 10.2) or under the terms of a Secondary License (if permitted
under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its Contributions
are its original creation(s) or it has sufficient rights to grant the
rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under applicable
copyright doctrines of fair use, fair dealing, or other equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under the
terms of this License. You must inform recipients that the Source Code Form
of the Covered Software is governed by the terms of this License, and how
they can obtain a copy of this License. You may not attempt to alter or
restrict the recipients rights in the Source Code Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this License,
or sublicense it under different terms, provided that the license for
the Executable Form does not attempt to limit or alter the recipients
rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for the
Covered Software. If the Larger Work is a combination of Covered Software
with a work governed by one or more Secondary Licenses, and the Covered
Software is not Incompatible With Secondary Licenses, this License permits
You to additionally distribute such Covered Software under the terms of
such Secondary License(s), so that the recipient of the Larger Work may, at
their option, further distribute the Covered Software under the terms of
either this License or such Secondary License(s).
3.4. Notices
You may not remove or alter the substance of any license notices (including
copyright notices, patent notices, disclaimers of warranty, or limitations
of liability) contained within the Source Code Form of the Covered
Software, except that You may alter any license notices to the extent
required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on behalf
of any Contributor. You must make it absolutely clear that any such
warranty, support, indemnity, or liability obligation is offered by You
alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute, judicial
order, or regulation then You must: (a) comply with the terms of this License
to the maximum extent possible; and (b) describe the limitations and the code
they affect. Such description must be placed in a text file included with all
distributions of the Covered Software under this License. Except to the
extent prohibited by statute or regulation, such description must be
sufficiently detailed for a recipient of ordinary skill to be able to
understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
if such Contributor fails to notify You of the non-compliance by some
reasonable means prior to 60 days after You have come back into compliance.
Moreover, Your grants from a particular Contributor are reinstated on an
ongoing basis if such Contributor notifies You of the non-compliance by
some reasonable means, this is the first time You have received notice of
non-compliance with this License from such Contributor, and You become
compliant prior to 30 days after Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions, counter-claims,
and cross-claims) alleging that a Contributor Version directly or
indirectly infringes any patent, then the rights granted to You by any and
all Contributors for the Covered Software under Section 2.1 of this License
shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an “as is” basis, without
warranty of any kind, either expressed, implied, or statutory, including,
without limitation, warranties that the Covered Software is free of defects,
merchantable, fit for a particular purpose or non-infringing. The entire
risk as to the quality and performance of the Covered Software is with You.
Should any Covered Software prove defective in any respect, You (not any
Contributor) assume the cost of any necessary servicing, repair, or
correction. This disclaimer of warranty constitutes an essential part of this
License. No use of any Covered Software is authorized under this License
except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from such
partys negligence to the extent applicable law prohibits such limitation.
Some jurisdictions do not allow the exclusion or limitation of incidental or
consequential damages, so this exclusion and limitation may not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts of
a jurisdiction where the defendant maintains its principal place of business
and such litigation shall be governed by laws of that jurisdiction, without
reference to its conflict-of-law provisions. Nothing in this Section shall
prevent a partys ability to bring cross-claims or counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject matter
hereof. If any provision of this License is held to be unenforceable, such
provision shall be reformed only to the extent necessary to make it
enforceable. Any law or regulation which provides that the language of a
contract shall be construed against the drafter shall not be used to construe
this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version of
the License under which You originally received the Covered Software, or
under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a modified
version of this License if you rename the license and remove any
references to the name of the license steward (except to note that such
modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file, then
You may include the notice in a location (such as a LICENSE file in a relevant
directory) where a recipient would be likely to look for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - “Incompatible With Secondary Licenses” Notice
This Source Code Form is “Incompatible
With Secondary Licenses”, as defined by
the Mozilla Public License, v. 2.0.

View File

@ -1,89 +0,0 @@
# errwrap
`errwrap` is a package for Go that formalizes the pattern of wrapping errors
and checking if an error contains another error.
There is a common pattern in Go of taking a returned `error` value and
then wrapping it (such as with `fmt.Errorf`) before returning it. The problem
with this pattern is that you completely lose the original `error` structure.
Arguably the _correct_ approach is that you should make a custom structure
implementing the `error` interface, and have the original error as a field
on that structure, such [as this example](http://golang.org/pkg/os/#PathError).
This is a good approach, but you have to know the entire chain of possible
rewrapping that happens, when you might just care about one.
`errwrap` formalizes this pattern (it doesn't matter what approach you use
above) by giving a single interface for wrapping errors, checking if a specific
error is wrapped, and extracting that error.
## Installation and Docs
Install using `go get github.com/hashicorp/errwrap`.
Full documentation is available at
http://godoc.org/github.com/hashicorp/errwrap
## Usage
#### Basic Usage
Below is a very basic example of its usage:
```go
// A function that always returns an error, but wraps it, like a real
// function might.
func tryOpen() error {
_, err := os.Open("/i/dont/exist")
if err != nil {
return errwrap.Wrapf("Doesn't exist: {{err}}", err)
}
return nil
}
func main() {
err := tryOpen()
// We can use the Contains helpers to check if an error contains
// another error. It is safe to do this with a nil error, or with
// an error that doesn't even use the errwrap package.
if errwrap.Contains(err, ErrNotExist) {
// Do something
}
if errwrap.ContainsType(err, new(os.PathError)) {
// Do something
}
// Or we can use the associated `Get` functions to just extract
// a specific error. This would return nil if that specific error doesn't
// exist.
perr := errwrap.GetType(err, new(os.PathError))
}
```
#### Custom Types
If you're already making custom types that properly wrap errors, then
you can get all the functionality of `errwraps.Contains` and such by
implementing the `Wrapper` interface with just one function. Example:
```go
type AppError {
Code ErrorCode
Err error
}
func (e *AppError) WrappedErrors() []error {
return []error{e.Err}
}
```
Now this works:
```go
err := &AppError{Err: fmt.Errorf("an error")}
if errwrap.ContainsType(err, fmt.Errorf("")) {
// This will work!
}
```

View File

@ -1,169 +0,0 @@
// Package errwrap implements methods to formalize error wrapping in Go.
//
// All of the top-level functions that take an `error` are built to be able
// to take any error, not just wrapped errors. This allows you to use errwrap
// without having to type-check and type-cast everywhere.
package errwrap
import (
"errors"
"reflect"
"strings"
)
// WalkFunc is the callback called for Walk.
type WalkFunc func(error)
// Wrapper is an interface that can be implemented by custom types to
// have all the Contains, Get, etc. functions in errwrap work.
//
// When Walk reaches a Wrapper, it will call the callback for every
// wrapped error in addition to the wrapper itself. Since all the top-level
// functions in errwrap use Walk, this means that all those functions work
// with your custom type.
type Wrapper interface {
WrappedErrors() []error
}
// Wrap defines that outer wraps inner, returning an error type that
// can be cleanly used with the other methods in this package, such as
// Contains, GetAll, etc.
//
// This function won't modify the error message at all (the outer message
// will be used).
func Wrap(outer, inner error) error {
return &wrappedError{
Outer: outer,
Inner: inner,
}
}
// Wrapf wraps an error with a formatting message. This is similar to using
// `fmt.Errorf` to wrap an error. If you're using `fmt.Errorf` to wrap
// errors, you should replace it with this.
//
// format is the format of the error message. The string '{{err}}' will
// be replaced with the original error message.
func Wrapf(format string, err error) error {
outerMsg := "<nil>"
if err != nil {
outerMsg = err.Error()
}
outer := errors.New(strings.Replace(
format, "{{err}}", outerMsg, -1))
return Wrap(outer, err)
}
// Contains checks if the given error contains an error with the
// message msg. If err is not a wrapped error, this will always return
// false unless the error itself happens to match this msg.
func Contains(err error, msg string) bool {
return len(GetAll(err, msg)) > 0
}
// ContainsType checks if the given error contains an error with
// the same concrete type as v. If err is not a wrapped error, this will
// check the err itself.
func ContainsType(err error, v interface{}) bool {
return len(GetAllType(err, v)) > 0
}
// Get is the same as GetAll but returns the deepest matching error.
func Get(err error, msg string) error {
es := GetAll(err, msg)
if len(es) > 0 {
return es[len(es)-1]
}
return nil
}
// GetType is the same as GetAllType but returns the deepest matching error.
func GetType(err error, v interface{}) error {
es := GetAllType(err, v)
if len(es) > 0 {
return es[len(es)-1]
}
return nil
}
// GetAll gets all the errors that might be wrapped in err with the
// given message. The order of the errors is such that the outermost
// matching error (the most recent wrap) is index zero, and so on.
func GetAll(err error, msg string) []error {
var result []error
Walk(err, func(err error) {
if err.Error() == msg {
result = append(result, err)
}
})
return result
}
// GetAllType gets all the errors that are the same type as v.
//
// The order of the return value is the same as described in GetAll.
func GetAllType(err error, v interface{}) []error {
var result []error
var search string
if v != nil {
search = reflect.TypeOf(v).String()
}
Walk(err, func(err error) {
var needle string
if err != nil {
needle = reflect.TypeOf(err).String()
}
if needle == search {
result = append(result, err)
}
})
return result
}
// Walk walks all the wrapped errors in err and calls the callback. If
// err isn't a wrapped error, this will be called once for err. If err
// is a wrapped error, the callback will be called for both the wrapper
// that implements error as well as the wrapped error itself.
func Walk(err error, cb WalkFunc) {
if err == nil {
return
}
switch e := err.(type) {
case *wrappedError:
cb(e.Outer)
Walk(e.Inner, cb)
case Wrapper:
cb(err)
for _, err := range e.WrappedErrors() {
Walk(err, cb)
}
default:
cb(err)
}
}
// wrappedError is an implementation of error that has both the
// outer and inner errors.
type wrappedError struct {
Outer error
Inner error
}
func (w *wrappedError) Error() string {
return w.Outer.Error()
}
func (w *wrappedError) WrappedErrors() []error {
return []error{w.Outer, w.Inner}
}

View File

@ -1,353 +0,0 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. “Contributor”
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. “Contributor Version”
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributors Contribution.
1.3. “Contribution”
means Covered Software of a particular Contributor.
1.4. “Covered Software”
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. “Incompatible With Secondary Licenses”
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of version
1.1 or earlier of the License, but not also under the terms of a
Secondary License.
1.6. “Executable Form”
means any form of the work other than Source Code Form.
1.7. “Larger Work”
means a work that combines Covered Software with other material, in a separate
file or files, that is not Covered Software.
1.8. “License”
means this document.
1.9. “Licensable”
means having the right to grant, to the maximum extent possible, whether at the
time of the initial grant or subsequently, any and all of the rights conveyed by
this License.
1.10. “Modifications”
means any of the following:
a. any file in Source Code Form that results from an addition to, deletion
from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. “Patent Claims” of a Contributor
means any patent claim(s), including without limitation, method, process,
and apparatus claims, in any patent Licensable by such Contributor that
would be infringed, but for the grant of the License, by the making,
using, selling, offering for sale, having made, import, or transfer of
either its Contributions or its Contributor Version.
1.12. “Secondary License”
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. “Source Code Form”
means the form of the work preferred for making modifications.
1.14. “You” (or “Your”)
means an individual or a legal entity exercising rights under this
License. For legal entities, “You” includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, “control” means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or as
part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its Contributions
or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution become
effective for each Contribution on the date the Contributor first distributes
such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under this
License. No additional rights or licenses will be implied from the distribution
or licensing of Covered Software under this License. Notwithstanding Section
2.1(b) above, no patent license is granted by a Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third partys
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of its
Contributions.
This License does not grant any rights in the trademarks, service marks, or
logos of any Contributor (except as may be necessary to comply with the
notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this License
(see Section 10.2) or under the terms of a Secondary License (if permitted
under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its Contributions
are its original creation(s) or it has sufficient rights to grant the
rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under applicable
copyright doctrines of fair use, fair dealing, or other equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under the
terms of this License. You must inform recipients that the Source Code Form
of the Covered Software is governed by the terms of this License, and how
they can obtain a copy of this License. You may not attempt to alter or
restrict the recipients rights in the Source Code Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this License,
or sublicense it under different terms, provided that the license for
the Executable Form does not attempt to limit or alter the recipients
rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for the
Covered Software. If the Larger Work is a combination of Covered Software
with a work governed by one or more Secondary Licenses, and the Covered
Software is not Incompatible With Secondary Licenses, this License permits
You to additionally distribute such Covered Software under the terms of
such Secondary License(s), so that the recipient of the Larger Work may, at
their option, further distribute the Covered Software under the terms of
either this License or such Secondary License(s).
3.4. Notices
You may not remove or alter the substance of any license notices (including
copyright notices, patent notices, disclaimers of warranty, or limitations
of liability) contained within the Source Code Form of the Covered
Software, except that You may alter any license notices to the extent
required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on behalf
of any Contributor. You must make it absolutely clear that any such
warranty, support, indemnity, or liability obligation is offered by You
alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute, judicial
order, or regulation then You must: (a) comply with the terms of this License
to the maximum extent possible; and (b) describe the limitations and the code
they affect. Such description must be placed in a text file included with all
distributions of the Covered Software under this License. Except to the
extent prohibited by statute or regulation, such description must be
sufficiently detailed for a recipient of ordinary skill to be able to
understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
if such Contributor fails to notify You of the non-compliance by some
reasonable means prior to 60 days after You have come back into compliance.
Moreover, Your grants from a particular Contributor are reinstated on an
ongoing basis if such Contributor notifies You of the non-compliance by
some reasonable means, this is the first time You have received notice of
non-compliance with this License from such Contributor, and You become
compliant prior to 30 days after Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions, counter-claims,
and cross-claims) alleging that a Contributor Version directly or
indirectly infringes any patent, then the rights granted to You by any and
all Contributors for the Covered Software under Section 2.1 of this License
shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an “as is” basis, without
warranty of any kind, either expressed, implied, or statutory, including,
without limitation, warranties that the Covered Software is free of defects,
merchantable, fit for a particular purpose or non-infringing. The entire
risk as to the quality and performance of the Covered Software is with You.
Should any Covered Software prove defective in any respect, You (not any
Contributor) assume the cost of any necessary servicing, repair, or
correction. This disclaimer of warranty constitutes an essential part of this
License. No use of any Covered Software is authorized under this License
except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from such
partys negligence to the extent applicable law prohibits such limitation.
Some jurisdictions do not allow the exclusion or limitation of incidental or
consequential damages, so this exclusion and limitation may not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts of
a jurisdiction where the defendant maintains its principal place of business
and such litigation shall be governed by laws of that jurisdiction, without
reference to its conflict-of-law provisions. Nothing in this Section shall
prevent a partys ability to bring cross-claims or counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject matter
hereof. If any provision of this License is held to be unenforceable, such
provision shall be reformed only to the extent necessary to make it
enforceable. Any law or regulation which provides that the language of a
contract shall be construed against the drafter shall not be used to construe
this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version of
the License under which You originally received the Covered Software, or
under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a modified
version of this License if you rename the license and remove any
references to the name of the license steward (except to note that such
modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file, then
You may include the notice in a location (such as a LICENSE file in a relevant
directory) where a recipient would be likely to look for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - “Incompatible With Secondary Licenses” Notice
This Source Code Form is “Incompatible
With Secondary Licenses”, as defined by
the Mozilla Public License, v. 2.0.

View File

@ -1,91 +0,0 @@
# go-multierror
`go-multierror` is a package for Go that provides a mechanism for
representing a list of `error` values as a single `error`.
This allows a function in Go to return an `error` that might actually
be a list of errors. If the caller knows this, they can unwrap the
list and access the errors. If the caller doesn't know, the error
formats to a nice human-readable format.
`go-multierror` implements the
[errwrap](https://github.com/hashicorp/errwrap) interface so that it can
be used with that library, as well.
## Installation and Docs
Install using `go get github.com/hashicorp/go-multierror`.
Full documentation is available at
http://godoc.org/github.com/hashicorp/go-multierror
## Usage
go-multierror is easy to use and purposely built to be unobtrusive in
existing Go applications/libraries that may not be aware of it.
**Building a list of errors**
The `Append` function is used to create a list of errors. This function
behaves a lot like the Go built-in `append` function: it doesn't matter
if the first argument is nil, a `multierror.Error`, or any other `error`,
the function behaves as you would expect.
```go
var result error
if err := step1(); err != nil {
result = multierror.Append(result, err)
}
if err := step2(); err != nil {
result = multierror.Append(result, err)
}
return result
```
**Customizing the formatting of the errors**
By specifying a custom `ErrorFormat`, you can customize the format
of the `Error() string` function:
```go
var result *multierror.Error
// ... accumulate errors here, maybe using Append
if result != nil {
result.ErrorFormat = func([]error) string {
return "errors!"
}
}
```
**Accessing the list of errors**
`multierror.Error` implements `error` so if the caller doesn't know about
multierror, it will work just fine. But if you're aware a multierror might
be returned, you can use type switches to access the list of errors:
```go
if err := something(); err != nil {
if merr, ok := err.(*multierror.Error); ok {
// Use merr.Errors
}
}
```
**Returning a multierror only if there are errors**
If you build a `multierror.Error`, you can use the `ErrorOrNil` function
to return an `error` implementation only if there are errors to return:
```go
var result *multierror.Error
// ... accumulate errors here
// Return the `error` only if errors were added to the multierror, otherwise
// return nil since there are no errors.
return result.ErrorOrNil()
```

View File

@ -1,37 +0,0 @@
package multierror
// Append is a helper function that will append more errors
// onto an Error in order to create a larger multi-error.
//
// If err is not a multierror.Error, then it will be turned into
// one. If any of the errs are multierr.Error, they will be flattened
// one level into err.
func Append(err error, errs ...error) *Error {
switch err := err.(type) {
case *Error:
// Typed nils can reach here, so initialize if we are nil
if err == nil {
err = new(Error)
}
// Go through each error and flatten
for _, e := range errs {
switch e := e.(type) {
case *Error:
err.Errors = append(err.Errors, e.Errors...)
default:
err.Errors = append(err.Errors, e)
}
}
return err
default:
newErrs := make([]error, 0, len(errs)+1)
if err != nil {
newErrs = append(newErrs, err)
}
newErrs = append(newErrs, errs...)
return Append(&Error{}, newErrs...)
}
}

View File

@ -1,26 +0,0 @@
package multierror
// Flatten flattens the given error, merging any *Errors together into
// a single *Error.
func Flatten(err error) error {
// If it isn't an *Error, just return the error as-is
if _, ok := err.(*Error); !ok {
return err
}
// Otherwise, make the result and flatten away!
flatErr := new(Error)
flatten(err, flatErr)
return flatErr
}
func flatten(err error, flatErr *Error) {
switch err := err.(type) {
case *Error:
for _, e := range err.Errors {
flatten(e, flatErr)
}
default:
flatErr.Errors = append(flatErr.Errors, err)
}
}

View File

@ -1,23 +0,0 @@
package multierror
import (
"fmt"
"strings"
)
// ErrorFormatFunc is a function callback that is called by Error to
// turn the list of errors into a string.
type ErrorFormatFunc func([]error) string
// ListFormatFunc is a basic formatter that outputs the number of errors
// that occurred along with a bullet point list of the errors.
func ListFormatFunc(es []error) string {
points := make([]string, len(es))
for i, err := range es {
points[i] = fmt.Sprintf("* %s", err)
}
return fmt.Sprintf(
"%d error(s) occurred:\n\n%s",
len(es), strings.Join(points, "\n"))
}

View File

@ -1,51 +0,0 @@
package multierror
import (
"fmt"
)
// Error is an error type to track multiple errors. This is used to
// accumulate errors in cases and return them as a single "error".
type Error struct {
Errors []error
ErrorFormat ErrorFormatFunc
}
func (e *Error) Error() string {
fn := e.ErrorFormat
if fn == nil {
fn = ListFormatFunc
}
return fn(e.Errors)
}
// ErrorOrNil returns an error interface if this Error represents
// a list of errors, or returns nil if the list of errors is empty. This
// function is useful at the end of accumulation to make sure that the value
// returned represents the existence of errors.
func (e *Error) ErrorOrNil() error {
if e == nil {
return nil
}
if len(e.Errors) == 0 {
return nil
}
return e
}
func (e *Error) GoString() string {
return fmt.Sprintf("*%#v", *e)
}
// WrappedErrors returns the list of errors that this Error is wrapping.
// It is an implementatin of the errwrap.Wrapper interface so that
// multierror.Error can be used with that library.
//
// This method is not safe to be called concurrently and is no different
// than accessing the Errors field directly. It is implementd only to
// satisfy the errwrap.Wrapper interface.
func (e *Error) WrappedErrors() []error {
return e.Errors
}

View File

@ -1,37 +0,0 @@
package multierror
import (
"fmt"
"github.com/hashicorp/errwrap"
)
// Prefix is a helper function that will prefix some text
// to the given error. If the error is a multierror.Error, then
// it will be prefixed to each wrapped error.
//
// This is useful to use when appending multiple multierrors
// together in order to give better scoping.
func Prefix(err error, prefix string) error {
if err == nil {
return nil
}
format := fmt.Sprintf("%s {{err}}", prefix)
switch err := err.(type) {
case *Error:
// Typed nils can reach here, so initialize if we are nil
if err == nil {
err = new(Error)
}
// Wrap each of the errors
for i, e := range err.Errors {
err.Errors[i] = errwrap.Wrapf(format, e)
}
return err
default:
return errwrap.Wrapf(format, err)
}
}

View File

@ -1,11 +0,0 @@
language: go
go:
- 1.0
- 1.1
- 1.2
- 1.3
- 1.4
script:
- go test

View File

@ -1,354 +0,0 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. “Contributor”
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. “Contributor Version”
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributors Contribution.
1.3. “Contribution”
means Covered Software of a particular Contributor.
1.4. “Covered Software”
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. “Incompatible With Secondary Licenses”
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of version
1.1 or earlier of the License, but not also under the terms of a
Secondary License.
1.6. “Executable Form”
means any form of the work other than Source Code Form.
1.7. “Larger Work”
means a work that combines Covered Software with other material, in a separate
file or files, that is not Covered Software.
1.8. “License”
means this document.
1.9. “Licensable”
means having the right to grant, to the maximum extent possible, whether at the
time of the initial grant or subsequently, any and all of the rights conveyed by
this License.
1.10. “Modifications”
means any of the following:
a. any file in Source Code Form that results from an addition to, deletion
from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. “Patent Claims” of a Contributor
means any patent claim(s), including without limitation, method, process,
and apparatus claims, in any patent Licensable by such Contributor that
would be infringed, but for the grant of the License, by the making,
using, selling, offering for sale, having made, import, or transfer of
either its Contributions or its Contributor Version.
1.12. “Secondary License”
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. “Source Code Form”
means the form of the work preferred for making modifications.
1.14. “You” (or “Your”)
means an individual or a legal entity exercising rights under this
License. For legal entities, “You” includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, “control” means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or as
part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its Contributions
or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution become
effective for each Contribution on the date the Contributor first distributes
such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under this
License. No additional rights or licenses will be implied from the distribution
or licensing of Covered Software under this License. Notwithstanding Section
2.1(b) above, no patent license is granted by a Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third partys
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of its
Contributions.
This License does not grant any rights in the trademarks, service marks, or
logos of any Contributor (except as may be necessary to comply with the
notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this License
(see Section 10.2) or under the terms of a Secondary License (if permitted
under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its Contributions
are its original creation(s) or it has sufficient rights to grant the
rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under applicable
copyright doctrines of fair use, fair dealing, or other equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under the
terms of this License. You must inform recipients that the Source Code Form
of the Covered Software is governed by the terms of this License, and how
they can obtain a copy of this License. You may not attempt to alter or
restrict the recipients rights in the Source Code Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this License,
or sublicense it under different terms, provided that the license for
the Executable Form does not attempt to limit or alter the recipients
rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for the
Covered Software. If the Larger Work is a combination of Covered Software
with a work governed by one or more Secondary Licenses, and the Covered
Software is not Incompatible With Secondary Licenses, this License permits
You to additionally distribute such Covered Software under the terms of
such Secondary License(s), so that the recipient of the Larger Work may, at
their option, further distribute the Covered Software under the terms of
either this License or such Secondary License(s).
3.4. Notices
You may not remove or alter the substance of any license notices (including
copyright notices, patent notices, disclaimers of warranty, or limitations
of liability) contained within the Source Code Form of the Covered
Software, except that You may alter any license notices to the extent
required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on behalf
of any Contributor. You must make it absolutely clear that any such
warranty, support, indemnity, or liability obligation is offered by You
alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute, judicial
order, or regulation then You must: (a) comply with the terms of this License
to the maximum extent possible; and (b) describe the limitations and the code
they affect. Such description must be placed in a text file included with all
distributions of the Covered Software under this License. Except to the
extent prohibited by statute or regulation, such description must be
sufficiently detailed for a recipient of ordinary skill to be able to
understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
if such Contributor fails to notify You of the non-compliance by some
reasonable means prior to 60 days after You have come back into compliance.
Moreover, Your grants from a particular Contributor are reinstated on an
ongoing basis if such Contributor notifies You of the non-compliance by
some reasonable means, this is the first time You have received notice of
non-compliance with this License from such Contributor, and You become
compliant prior to 30 days after Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions, counter-claims,
and cross-claims) alleging that a Contributor Version directly or
indirectly infringes any patent, then the rights granted to You by any and
all Contributors for the Covered Software under Section 2.1 of this License
shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an “as is” basis, without
warranty of any kind, either expressed, implied, or statutory, including,
without limitation, warranties that the Covered Software is free of defects,
merchantable, fit for a particular purpose or non-infringing. The entire
risk as to the quality and performance of the Covered Software is with You.
Should any Covered Software prove defective in any respect, You (not any
Contributor) assume the cost of any necessary servicing, repair, or
correction. This disclaimer of warranty constitutes an essential part of this
License. No use of any Covered Software is authorized under this License
except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from such
partys negligence to the extent applicable law prohibits such limitation.
Some jurisdictions do not allow the exclusion or limitation of incidental or
consequential damages, so this exclusion and limitation may not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts of
a jurisdiction where the defendant maintains its principal place of business
and such litigation shall be governed by laws of that jurisdiction, without
reference to its conflict-of-law provisions. Nothing in this Section shall
prevent a partys ability to bring cross-claims or counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject matter
hereof. If any provision of this License is held to be unenforceable, such
provision shall be reformed only to the extent necessary to make it
enforceable. Any law or regulation which provides that the language of a
contract shall be construed against the drafter shall not be used to construe
this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version of
the License under which You originally received the Covered Software, or
under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a modified
version of this License if you rename the license and remove any
references to the name of the license steward (except to note that such
modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file, then
You may include the notice in a location (such as a LICENSE file in a relevant
directory) where a recipient would be likely to look for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - “Incompatible With Secondary Licenses” Notice
This Source Code Form is “Incompatible
With Secondary Licenses”, as defined by
the Mozilla Public License, v. 2.0.

View File

@ -1,65 +0,0 @@
# Versioning Library for Go
[![Build Status](https://travis-ci.org/hashicorp/go-version.svg?branch=master)](https://travis-ci.org/hashicorp/go-version)
go-version is a library for parsing versions and version constraints,
and verifying versions against a set of constraints. go-version
can sort a collection of versions properly, handles prerelease/beta
versions, can increment versions, etc.
Versions used with go-version must follow [SemVer](http://semver.org/).
## Installation and Usage
Package documentation can be found on
[GoDoc](http://godoc.org/github.com/hashicorp/go-version).
Installation can be done with a normal `go get`:
```
$ go get github.com/hashicorp/go-version
```
#### Version Parsing and Comparison
```go
v1, err := version.NewVersion("1.2")
v2, err := version.NewVersion("1.5+metadata")
// Comparison example. There is also GreaterThan, Equal, and just
// a simple Compare that returns an int allowing easy >=, <=, etc.
if v1.LessThan(v2) {
fmt.Printf("%s is less than %s", v1, v2)
}
```
#### Version Constraints
```go
v1, err := version.NewVersion("1.2")
// Constraints example.
constraints, err := version.NewConstraint(">= 1.0, < 1.4")
if constraints.Check(v1) {
fmt.Printf("%s satisfies constraints %s", v1, constraints)
}
```
#### Version Sorting
```go
versionsRaw := []string{"1.1", "0.7.1", "1.4-beta", "1.4", "2"}
versions := make([]*version.Version, len(versionsRaw))
for i, raw := range versionsRaw {
v, _ := version.NewVersion(raw)
versions[i] = v
}
// After this, the versions are properly sorted
sort.Sort(version.Collection(versions))
```
## Issues and Contributing
If you find an issue with this library, please report an issue. If you'd
like, we welcome any contributions. Fork this library and submit a pull
request.

View File

@ -1,156 +0,0 @@
package version
import (
"fmt"
"regexp"
"strings"
)
// Constraint represents a single constraint for a version, such as
// ">= 1.0".
type Constraint struct {
f constraintFunc
check *Version
original string
}
// Constraints is a slice of constraints. We make a custom type so that
// we can add methods to it.
type Constraints []*Constraint
type constraintFunc func(v, c *Version) bool
var constraintOperators map[string]constraintFunc
var constraintRegexp *regexp.Regexp
func init() {
constraintOperators = map[string]constraintFunc{
"": constraintEqual,
"=": constraintEqual,
"!=": constraintNotEqual,
">": constraintGreaterThan,
"<": constraintLessThan,
">=": constraintGreaterThanEqual,
"<=": constraintLessThanEqual,
"~>": constraintPessimistic,
}
ops := make([]string, 0, len(constraintOperators))
for k, _ := range constraintOperators {
ops = append(ops, regexp.QuoteMeta(k))
}
constraintRegexp = regexp.MustCompile(fmt.Sprintf(
`^\s*(%s)\s*(%s)\s*$`,
strings.Join(ops, "|"),
VersionRegexpRaw))
}
// NewConstraint will parse one or more constraints from the given
// constraint string. The string must be a comma-separated list of
// constraints.
func NewConstraint(v string) (Constraints, error) {
vs := strings.Split(v, ",")
result := make([]*Constraint, len(vs))
for i, single := range vs {
c, err := parseSingle(single)
if err != nil {
return nil, err
}
result[i] = c
}
return Constraints(result), nil
}
// Check tests if a version satisfies all the constraints.
func (cs Constraints) Check(v *Version) bool {
for _, c := range cs {
if !c.Check(v) {
return false
}
}
return true
}
// Returns the string format of the constraints
func (cs Constraints) String() string {
csStr := make([]string, len(cs))
for i, c := range cs {
csStr[i] = c.String()
}
return strings.Join(csStr, ",")
}
// Check tests if a constraint is validated by the given version.
func (c *Constraint) Check(v *Version) bool {
return c.f(v, c.check)
}
func (c *Constraint) String() string {
return c.original
}
func parseSingle(v string) (*Constraint, error) {
matches := constraintRegexp.FindStringSubmatch(v)
if matches == nil {
return nil, fmt.Errorf("Malformed constraint: %s", v)
}
check, err := NewVersion(matches[2])
if err != nil {
return nil, err
}
return &Constraint{
f: constraintOperators[matches[1]],
check: check,
original: v,
}, nil
}
//-------------------------------------------------------------------
// Constraint functions
//-------------------------------------------------------------------
func constraintEqual(v, c *Version) bool {
return v.Equal(c)
}
func constraintNotEqual(v, c *Version) bool {
return !v.Equal(c)
}
func constraintGreaterThan(v, c *Version) bool {
return v.Compare(c) == 1
}
func constraintLessThan(v, c *Version) bool {
return v.Compare(c) == -1
}
func constraintGreaterThanEqual(v, c *Version) bool {
return v.Compare(c) >= 0
}
func constraintLessThanEqual(v, c *Version) bool {
return v.Compare(c) <= 0
}
func constraintPessimistic(v, c *Version) bool {
if v.LessThan(c) {
return false
}
for i := 0; i < c.si-1; i++ {
if v.segments[i] != c.segments[i] {
return false
}
}
return true
}

View File

@ -1,251 +0,0 @@
package version
import (
"bytes"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
)
// The compiled regular expression used to test the validity of a version.
var versionRegexp *regexp.Regexp
// The raw regular expression string used for testing the validity
// of a version.
const VersionRegexpRaw string = `([0-9]+(\.[0-9]+){0,2})` +
`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
`?`
// Version represents a single version.
type Version struct {
metadata string
pre string
segments []int
si int
}
func init() {
versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$")
}
// NewVersion parses the given version and returns a new
// Version.
func NewVersion(v string) (*Version, error) {
matches := versionRegexp.FindStringSubmatch(v)
if matches == nil {
return nil, fmt.Errorf("Malformed version: %s", v)
}
segmentsStr := strings.Split(matches[1], ".")
segments := make([]int, len(segmentsStr), 3)
si := 0
for i, str := range segmentsStr {
val, err := strconv.ParseInt(str, 10, 32)
if err != nil {
return nil, fmt.Errorf(
"Error parsing version: %s", err)
}
segments[i] = int(val)
si += 1
}
for i := len(segments); i < 3; i++ {
segments = append(segments, 0)
}
return &Version{
metadata: matches[7],
pre: matches[4],
segments: segments,
si: si,
}, nil
}
// Must is a helper that wraps a call to a function returning (*Version, error)
// and panics if error is non-nil.
func Must(v *Version, err error) *Version {
if err != nil {
panic(err)
}
return v
}
// Compare compares this version to another version. This
// returns -1, 0, or 1 if this version is smaller, equal,
// or larger than the other version, respectively.
//
// If you want boolean results, use the LessThan, Equal,
// or GreaterThan methods.
func (v *Version) Compare(other *Version) int {
// A quick, efficient equality check
if v.String() == other.String() {
return 0
}
segmentsSelf := v.Segments()
segmentsOther := other.Segments()
// If the segments are the same, we must compare on prerelease info
if reflect.DeepEqual(segmentsSelf, segmentsOther) {
preSelf := v.Prerelease()
preOther := other.Prerelease()
if preSelf == "" && preOther == "" {
return 0
}
if preSelf == "" {
return 1
}
if preOther == "" {
return -1
}
return comparePrereleases(preSelf, preOther)
}
// Compare the segments
for i := 0; i < len(segmentsSelf); i++ {
lhs := segmentsSelf[i]
rhs := segmentsOther[i]
if lhs == rhs {
continue
} else if lhs < rhs {
return -1
} else {
return 1
}
}
panic("should not be reached")
}
func comparePart(preSelf string, preOther string) int {
if preSelf == preOther {
return 0
}
// if a part is empty, we use the other to decide
if preSelf == "" {
_, notIsNumeric := strconv.ParseInt(preOther, 10, 64)
if notIsNumeric == nil {
return -1
}
return 1
}
if preOther == "" {
_, notIsNumeric := strconv.ParseInt(preSelf, 10, 64)
if notIsNumeric == nil {
return 1
}
return -1
}
if preSelf > preOther {
return 1
}
return -1
}
func comparePrereleases(v string, other string) int {
// the same pre release!
if v == other {
return 0
}
// split both pre releases for analyse their parts
selfPreReleaseMeta := strings.Split(v, ".")
otherPreReleaseMeta := strings.Split(other, ".")
selfPreReleaseLen := len(selfPreReleaseMeta)
otherPreReleaseLen := len(otherPreReleaseMeta)
biggestLen := otherPreReleaseLen
if selfPreReleaseLen > otherPreReleaseLen {
biggestLen = selfPreReleaseLen
}
// loop for parts to find the first difference
for i := 0; i < biggestLen; i = i + 1 {
partSelfPre := ""
if i < selfPreReleaseLen {
partSelfPre = selfPreReleaseMeta[i]
}
partOtherPre := ""
if i < otherPreReleaseLen {
partOtherPre = otherPreReleaseMeta[i]
}
compare := comparePart(partSelfPre, partOtherPre)
// if parts are equals, continue the loop
if compare != 0 {
return compare
}
}
return 0
}
// Equal tests if two versions are equal.
func (v *Version) Equal(o *Version) bool {
return v.Compare(o) == 0
}
// GreaterThan tests if this version is greater than another version.
func (v *Version) GreaterThan(o *Version) bool {
return v.Compare(o) > 0
}
// LessThan tests if this version is less than another version.
func (v *Version) LessThan(o *Version) bool {
return v.Compare(o) < 0
}
// Metadata returns any metadata that was part of the version
// string.
//
// Metadata is anything that comes after the "+" in the version.
// For example, with "1.2.3+beta", the metadata is "beta".
func (v *Version) Metadata() string {
return v.metadata
}
// Prerelease returns any prerelease data that is part of the version,
// or blank if there is no prerelease data.
//
// Prerelease information is anything that comes after the "-" in the
// version (but before any metadata). For example, with "1.2.3-beta",
// the prerelease information is "beta".
func (v *Version) Prerelease() string {
return v.pre
}
// Segments returns the numeric segments of the version as a slice.
//
// This excludes any metadata or pre-release information. For example,
// for a version "1.2.3-beta", segments will return a slice of
// 1, 2, 3.
func (v *Version) Segments() []int {
return v.segments
}
// String returns the full version string included pre-release
// and metadata information.
func (v *Version) String() string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "%d.%d.%d", v.segments[0], v.segments[1], v.segments[2])
if v.pre != "" {
fmt.Fprintf(&buf, "-%s", v.pre)
}
if v.metadata != "" {
fmt.Fprintf(&buf, "+%s", v.metadata)
}
return buf.String()
}

View File

@ -1,17 +0,0 @@
package version
// Collection is a type that implements the sort.Interface interface
// so that versions can be sorted.
type Collection []*Version
func (v Collection) Len() int {
return len(v)
}
func (v Collection) Less(i, j int) bool {
return v[i].LessThan(v[j])
}
func (v Collection) Swap(i, j int) {
v[i], v[j] = v[j], v[i]
}

View File

@ -1,23 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test

View File

@ -1,362 +0,0 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. "Contributor"
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. "Incompatible With Secondary Licenses"
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the terms of
a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in a
separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible, whether
at the time of the initial grant or subsequently, any and all of the
rights conveyed by this License.
1.10. "Modifications"
means any of the following:
a. any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the License,
by the making, using, selling, offering for sale, having made, import,
or transfer of either its Contributions or its Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, "control" means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights to
grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter the
recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty, or
limitations of liability) contained within the Source Code Form of the
Covered Software, except that You may alter any license notices to the
extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute,
judicial order, or regulation then You must: (a) comply with the terms of
this License to the maximum extent possible; and (b) describe the
limitations and the code they affect. Such description must be placed in a
text file included with all distributions of the Covered Software under
this License. Except to the extent prohibited by statute or regulation,
such description must be sufficiently detailed for a recipient of ordinary
skill to be able to understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing
basis, if such Contributor fails to notify You of the non-compliance by
some reasonable means prior to 60 days after You have come back into
compliance. Moreover, Your grants from a particular Contributor are
reinstated on an ongoing basis if such Contributor notifies You of the
non-compliance by some reasonable means, this is the first time You have
received notice of non-compliance with this License from such
Contributor, and You become compliant prior to 30 days after Your receipt
of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an "as is" basis,
without warranty of any kind, either expressed, implied, or statutory,
including, without limitation, warranties that the Covered Software is free
of defects, merchantable, fit for a particular purpose or non-infringing.
The entire risk as to the quality and performance of the Covered Software
is with You. Should any Covered Software prove defective in any respect,
You (not any Contributor) assume the cost of any necessary servicing,
repair, or correction. This disclaimer of warranty constitutes an essential
part of this License. No use of any Covered Software is authorized under
this License except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from
such party's negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of
incidental or consequential damages, so this exclusion and limitation may
not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts
of a jurisdiction where the defendant maintains its principal place of
business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions. Nothing
in this Section shall prevent a party's ability to bring cross-claims or
counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides that
the language of a contract shall be construed against the drafter shall not
be used to construe this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses If You choose to distribute Source Code Form that is
Incompatible With Secondary Licenses under the terms of this version of
the License, the notice described in Exhibit B of this License must be
attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file,
then You may include the notice in a location (such as a LICENSE file in a
relevant directory) where a recipient would be likely to look for such a
notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible
With Secondary Licenses", as defined by
the Mozilla Public License, v. 2.0.

View File

@ -1,86 +0,0 @@
# Yamux
Yamux (Yet another Multiplexer) is a multiplexing library for Golang.
It relies on an underlying connection to provide reliability
and ordering, such as TCP or Unix domain sockets, and provides
stream-oriented multiplexing. It is inspired by SPDY but is not
interoperable with it.
Yamux features include:
* Bi-directional streams
* Streams can be opened by either client or server
* Useful for NAT traversal
* Server-side push support
* Flow control
* Avoid starvation
* Back-pressure to prevent overwhelming a receiver
* Keep Alives
* Enables persistent connections over a load balancer
* Efficient
* Enables thousands of logical streams with low overhead
## Documentation
For complete documentation, see the associated [Godoc](http://godoc.org/github.com/hashicorp/yamux).
## Specification
The full specification for Yamux is provided in the `spec.md` file.
It can be used as a guide to implementors of interoperable libraries.
## Usage
Using Yamux is remarkably simple:
```go
func client() {
// Get a TCP connection
conn, err := net.Dial(...)
if err != nil {
panic(err)
}
// Setup client side of yamux
session, err := yamux.Client(conn, nil)
if err != nil {
panic(err)
}
// Open a new stream
stream, err := session.Open()
if err != nil {
panic(err)
}
// Stream implements net.Conn
stream.Write([]byte("ping"))
}
func server() {
// Accept a TCP connection
conn, err := listener.Accept()
if err != nil {
panic(err)
}
// Setup server side of yamux
session, err := yamux.Server(conn, nil)
if err != nil {
panic(err)
}
// Accept a stream
stream, err := session.Accept()
if err != nil {
panic(err)
}
// Listen for a message
buf := make([]byte, 4)
stream.Read(buf)
}
```

View File

@ -1,60 +0,0 @@
package yamux
import (
"fmt"
"net"
)
// hasAddr is used to get the address from the underlying connection
type hasAddr interface {
LocalAddr() net.Addr
RemoteAddr() net.Addr
}
// yamuxAddr is used when we cannot get the underlying address
type yamuxAddr struct {
Addr string
}
func (*yamuxAddr) Network() string {
return "yamux"
}
func (y *yamuxAddr) String() string {
return fmt.Sprintf("yamux:%s", y.Addr)
}
// Addr is used to get the address of the listener.
func (s *Session) Addr() net.Addr {
return s.LocalAddr()
}
// LocalAddr is used to get the local address of the
// underlying connection.
func (s *Session) LocalAddr() net.Addr {
addr, ok := s.conn.(hasAddr)
if !ok {
return &yamuxAddr{"local"}
}
return addr.LocalAddr()
}
// RemoteAddr is used to get the address of remote end
// of the underlying connection
func (s *Session) RemoteAddr() net.Addr {
addr, ok := s.conn.(hasAddr)
if !ok {
return &yamuxAddr{"remote"}
}
return addr.RemoteAddr()
}
// LocalAddr returns the local address
func (s *Stream) LocalAddr() net.Addr {
return s.session.LocalAddr()
}
// LocalAddr returns the remote address
func (s *Stream) RemoteAddr() net.Addr {
return s.session.RemoteAddr()
}

View File

@ -1,157 +0,0 @@
package yamux
import (
"encoding/binary"
"fmt"
)
var (
// ErrInvalidVersion means we received a frame with an
// invalid version
ErrInvalidVersion = fmt.Errorf("invalid protocol version")
// ErrInvalidMsgType means we received a frame with an
// invalid message type
ErrInvalidMsgType = fmt.Errorf("invalid msg type")
// ErrSessionShutdown is used if there is a shutdown during
// an operation
ErrSessionShutdown = fmt.Errorf("session shutdown")
// ErrStreamsExhausted is returned if we have no more
// stream ids to issue
ErrStreamsExhausted = fmt.Errorf("streams exhausted")
// ErrDuplicateStream is used if a duplicate stream is
// opened inbound
ErrDuplicateStream = fmt.Errorf("duplicate stream initiated")
// ErrReceiveWindowExceeded indicates the window was exceeded
ErrRecvWindowExceeded = fmt.Errorf("recv window exceeded")
// ErrTimeout is used when we reach an IO deadline
ErrTimeout = fmt.Errorf("i/o deadline reached")
// ErrStreamClosed is returned when using a closed stream
ErrStreamClosed = fmt.Errorf("stream closed")
// ErrUnexpectedFlag is set when we get an unexpected flag
ErrUnexpectedFlag = fmt.Errorf("unexpected flag")
// ErrRemoteGoAway is used when we get a go away from the other side
ErrRemoteGoAway = fmt.Errorf("remote end is not accepting connections")
// ErrConnectionReset is sent if a stream is reset. This can happen
// if the backlog is exceeded, or if there was a remote GoAway.
ErrConnectionReset = fmt.Errorf("connection reset")
// ErrConnectionWriteTimeout indicates that we hit the "safety valve"
// timeout writing to the underlying stream connection.
ErrConnectionWriteTimeout = fmt.Errorf("connection write timeout")
// ErrKeepAliveTimeout is sent if a missed keepalive caused the stream close
ErrKeepAliveTimeout = fmt.Errorf("keepalive timeout")
)
const (
// protoVersion is the only version we support
protoVersion uint8 = 0
)
const (
// Data is used for data frames. They are followed
// by length bytes worth of payload.
typeData uint8 = iota
// WindowUpdate is used to change the window of
// a given stream. The length indicates the delta
// update to the window.
typeWindowUpdate
// Ping is sent as a keep-alive or to measure
// the RTT. The StreamID and Length value are echoed
// back in the response.
typePing
// GoAway is sent to terminate a session. The StreamID
// should be 0 and the length is an error code.
typeGoAway
)
const (
// SYN is sent to signal a new stream. May
// be sent with a data payload
flagSYN uint16 = 1 << iota
// ACK is sent to acknowledge a new stream. May
// be sent with a data payload
flagACK
// FIN is sent to half-close the given stream.
// May be sent with a data payload.
flagFIN
// RST is used to hard close a given stream.
flagRST
)
const (
// initialStreamWindow is the initial stream window size
initialStreamWindow uint32 = 256 * 1024
)
const (
// goAwayNormal is sent on a normal termination
goAwayNormal uint32 = iota
// goAwayProtoErr sent on a protocol error
goAwayProtoErr
// goAwayInternalErr sent on an internal error
goAwayInternalErr
)
const (
sizeOfVersion = 1
sizeOfType = 1
sizeOfFlags = 2
sizeOfStreamID = 4
sizeOfLength = 4
headerSize = sizeOfVersion + sizeOfType + sizeOfFlags +
sizeOfStreamID + sizeOfLength
)
type header []byte
func (h header) Version() uint8 {
return h[0]
}
func (h header) MsgType() uint8 {
return h[1]
}
func (h header) Flags() uint16 {
return binary.BigEndian.Uint16(h[2:4])
}
func (h header) StreamID() uint32 {
return binary.BigEndian.Uint32(h[4:8])
}
func (h header) Length() uint32 {
return binary.BigEndian.Uint32(h[8:12])
}
func (h header) String() string {
return fmt.Sprintf("Vsn:%d Type:%d Flags:%d StreamID:%d Length:%d",
h.Version(), h.MsgType(), h.Flags(), h.StreamID(), h.Length())
}
func (h header) encode(msgType uint8, flags uint16, streamID uint32, length uint32) {
h[0] = protoVersion
h[1] = msgType
binary.BigEndian.PutUint16(h[2:4], flags)
binary.BigEndian.PutUint32(h[4:8], streamID)
binary.BigEndian.PutUint32(h[8:12], length)
}

View File

@ -1,87 +0,0 @@
package yamux
import (
"fmt"
"io"
"os"
"time"
)
// Config is used to tune the Yamux session
type Config struct {
// AcceptBacklog is used to limit how many streams may be
// waiting an accept.
AcceptBacklog int
// EnableKeepalive is used to do a period keep alive
// messages using a ping.
EnableKeepAlive bool
// KeepAliveInterval is how often to perform the keep alive
KeepAliveInterval time.Duration
// ConnectionWriteTimeout is meant to be a "safety valve" timeout after
// we which will suspect a problem with the underlying connection and
// close it. This is only applied to writes, where's there's generally
// an expectation that things will move along quickly.
ConnectionWriteTimeout time.Duration
// MaxStreamWindowSize is used to control the maximum
// window size that we allow for a stream.
MaxStreamWindowSize uint32
// LogOutput is used to control the log destination
LogOutput io.Writer
}
// DefaultConfig is used to return a default configuration
func DefaultConfig() *Config {
return &Config{
AcceptBacklog: 256,
EnableKeepAlive: true,
KeepAliveInterval: 30 * time.Second,
ConnectionWriteTimeout: 10 * time.Second,
MaxStreamWindowSize: initialStreamWindow,
LogOutput: os.Stderr,
}
}
// VerifyConfig is used to verify the sanity of configuration
func VerifyConfig(config *Config) error {
if config.AcceptBacklog <= 0 {
return fmt.Errorf("backlog must be positive")
}
if config.KeepAliveInterval == 0 {
return fmt.Errorf("keep-alive interval must be positive")
}
if config.MaxStreamWindowSize < initialStreamWindow {
return fmt.Errorf("MaxStreamWindowSize must be larger than %d", initialStreamWindow)
}
return nil
}
// Server is used to initialize a new server-side connection.
// There must be at most one server-side connection. If a nil config is
// provided, the DefaultConfiguration will be used.
func Server(conn io.ReadWriteCloser, config *Config) (*Session, error) {
if config == nil {
config = DefaultConfig()
}
if err := VerifyConfig(config); err != nil {
return nil, err
}
return newSession(config, conn, false), nil
}
// Client is used to initialize a new client-side connection.
// There must be at most one client-side connection.
func Client(conn io.ReadWriteCloser, config *Config) (*Session, error) {
if config == nil {
config = DefaultConfig()
}
if err := VerifyConfig(config); err != nil {
return nil, err
}
return newSession(config, conn, true), nil
}

View File

@ -1,598 +0,0 @@
package yamux
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"log"
"math"
"net"
"strings"
"sync"
"sync/atomic"
"time"
)
// Session is used to wrap a reliable ordered connection and to
// multiplex it into multiple streams.
type Session struct {
// remoteGoAway indicates the remote side does
// not want futher connections. Must be first for alignment.
remoteGoAway int32
// localGoAway indicates that we should stop
// accepting futher connections. Must be first for alignment.
localGoAway int32
// nextStreamID is the next stream we should
// send. This depends if we are a client/server.
nextStreamID uint32
// config holds our configuration
config *Config
// logger is used for our logs
logger *log.Logger
// conn is the underlying connection
conn io.ReadWriteCloser
// bufRead is a buffered reader
bufRead *bufio.Reader
// pings is used to track inflight pings
pings map[uint32]chan struct{}
pingID uint32
pingLock sync.Mutex
// streams maps a stream id to a stream
streams map[uint32]*Stream
streamLock sync.Mutex
// synCh acts like a semaphore. It is sized to the AcceptBacklog which
// is assumed to be symmetric between the client and server. This allows
// the client to avoid exceeding the backlog and instead blocks the open.
synCh chan struct{}
// acceptCh is used to pass ready streams to the client
acceptCh chan *Stream
// sendCh is used to mark a stream as ready to send,
// or to send a header out directly.
sendCh chan sendReady
// recvDoneCh is closed when recv() exits to avoid a race
// between stream registration and stream shutdown
recvDoneCh chan struct{}
// shutdown is used to safely close a session
shutdown bool
shutdownErr error
shutdownCh chan struct{}
shutdownLock sync.Mutex
}
// sendReady is used to either mark a stream as ready
// or to directly send a header
type sendReady struct {
Hdr []byte
Body io.Reader
Err chan error
}
// newSession is used to construct a new session
func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session {
s := &Session{
config: config,
logger: log.New(config.LogOutput, "", log.LstdFlags),
conn: conn,
bufRead: bufio.NewReader(conn),
pings: make(map[uint32]chan struct{}),
streams: make(map[uint32]*Stream),
synCh: make(chan struct{}, config.AcceptBacklog),
acceptCh: make(chan *Stream, config.AcceptBacklog),
sendCh: make(chan sendReady, 64),
recvDoneCh: make(chan struct{}),
shutdownCh: make(chan struct{}),
}
if client {
s.nextStreamID = 1
} else {
s.nextStreamID = 2
}
go s.recv()
go s.send()
if config.EnableKeepAlive {
go s.keepalive()
}
return s
}
// IsClosed does a safe check to see if we have shutdown
func (s *Session) IsClosed() bool {
select {
case <-s.shutdownCh:
return true
default:
return false
}
}
// NumStreams returns the number of currently open streams
func (s *Session) NumStreams() int {
s.streamLock.Lock()
num := len(s.streams)
s.streamLock.Unlock()
return num
}
// Open is used to create a new stream as a net.Conn
func (s *Session) Open() (net.Conn, error) {
conn, err := s.OpenStream()
if err != nil {
return nil, err
}
return conn, nil
}
// OpenStream is used to create a new stream
func (s *Session) OpenStream() (*Stream, error) {
if s.IsClosed() {
return nil, ErrSessionShutdown
}
if atomic.LoadInt32(&s.remoteGoAway) == 1 {
return nil, ErrRemoteGoAway
}
// Block if we have too many inflight SYNs
select {
case s.synCh <- struct{}{}:
case <-s.shutdownCh:
return nil, ErrSessionShutdown
}
GET_ID:
// Get and ID, and check for stream exhaustion
id := atomic.LoadUint32(&s.nextStreamID)
if id >= math.MaxUint32-1 {
return nil, ErrStreamsExhausted
}
if !atomic.CompareAndSwapUint32(&s.nextStreamID, id, id+2) {
goto GET_ID
}
// Register the stream
stream := newStream(s, id, streamInit)
s.streamLock.Lock()
s.streams[id] = stream
s.streamLock.Unlock()
// Send the window update to create
if err := stream.sendWindowUpdate(); err != nil {
return nil, err
}
return stream, nil
}
// Accept is used to block until the next available stream
// is ready to be accepted.
func (s *Session) Accept() (net.Conn, error) {
conn, err := s.AcceptStream()
if err != nil {
return nil, err
}
return conn, err
}
// AcceptStream is used to block until the next available stream
// is ready to be accepted.
func (s *Session) AcceptStream() (*Stream, error) {
select {
case stream := <-s.acceptCh:
if err := stream.sendWindowUpdate(); err != nil {
return nil, err
}
return stream, nil
case <-s.shutdownCh:
return nil, s.shutdownErr
}
}
// Close is used to close the session and all streams.
// Attempts to send a GoAway before closing the connection.
func (s *Session) Close() error {
s.shutdownLock.Lock()
defer s.shutdownLock.Unlock()
if s.shutdown {
return nil
}
s.shutdown = true
if s.shutdownErr == nil {
s.shutdownErr = ErrSessionShutdown
}
close(s.shutdownCh)
s.conn.Close()
<-s.recvDoneCh
s.streamLock.Lock()
defer s.streamLock.Unlock()
for _, stream := range s.streams {
stream.forceClose()
}
return nil
}
// exitErr is used to handle an error that is causing the
// session to terminate.
func (s *Session) exitErr(err error) {
s.shutdownLock.Lock()
if s.shutdownErr == nil {
s.shutdownErr = err
}
s.shutdownLock.Unlock()
s.Close()
}
// GoAway can be used to prevent accepting further
// connections. It does not close the underlying conn.
func (s *Session) GoAway() error {
return s.waitForSend(s.goAway(goAwayNormal), nil)
}
// goAway is used to send a goAway message
func (s *Session) goAway(reason uint32) header {
atomic.SwapInt32(&s.localGoAway, 1)
hdr := header(make([]byte, headerSize))
hdr.encode(typeGoAway, 0, 0, reason)
return hdr
}
// Ping is used to measure the RTT response time
func (s *Session) Ping() (time.Duration, error) {
// Get a channel for the ping
ch := make(chan struct{})
// Get a new ping id, mark as pending
s.pingLock.Lock()
id := s.pingID
s.pingID++
s.pings[id] = ch
s.pingLock.Unlock()
// Send the ping request
hdr := header(make([]byte, headerSize))
hdr.encode(typePing, flagSYN, 0, id)
if err := s.waitForSend(hdr, nil); err != nil {
return 0, err
}
// Wait for a response
start := time.Now()
select {
case <-ch:
case <-time.After(s.config.ConnectionWriteTimeout):
s.pingLock.Lock()
delete(s.pings, id) // Ignore it if a response comes later.
s.pingLock.Unlock()
return 0, ErrTimeout
case <-s.shutdownCh:
return 0, ErrSessionShutdown
}
// Compute the RTT
return time.Now().Sub(start), nil
}
// keepalive is a long running goroutine that periodically does
// a ping to keep the connection alive.
func (s *Session) keepalive() {
for {
select {
case <-time.After(s.config.KeepAliveInterval):
_, err := s.Ping()
if err != nil {
s.logger.Printf("[ERR] yamux: keepalive failed: %v", err)
s.exitErr(ErrKeepAliveTimeout)
return
}
case <-s.shutdownCh:
return
}
}
}
// waitForSendErr waits to send a header, checking for a potential shutdown
func (s *Session) waitForSend(hdr header, body io.Reader) error {
errCh := make(chan error, 1)
return s.waitForSendErr(hdr, body, errCh)
}
// waitForSendErr waits to send a header with optional data, checking for a
// potential shutdown. Since there's the expectation that sends can happen
// in a timely manner, we enforce the connection write timeout here.
func (s *Session) waitForSendErr(hdr header, body io.Reader, errCh chan error) error {
timer := time.NewTimer(s.config.ConnectionWriteTimeout)
defer timer.Stop()
ready := sendReady{Hdr: hdr, Body: body, Err: errCh}
select {
case s.sendCh <- ready:
case <-s.shutdownCh:
return ErrSessionShutdown
case <-timer.C:
return ErrConnectionWriteTimeout
}
select {
case err := <-errCh:
return err
case <-s.shutdownCh:
return ErrSessionShutdown
case <-timer.C:
return ErrConnectionWriteTimeout
}
}
// sendNoWait does a send without waiting. Since there's the expectation that
// the send happens right here, we enforce the connection write timeout if we
// can't queue the header to be sent.
func (s *Session) sendNoWait(hdr header) error {
timer := time.NewTimer(s.config.ConnectionWriteTimeout)
defer timer.Stop()
select {
case s.sendCh <- sendReady{Hdr: hdr}:
return nil
case <-s.shutdownCh:
return ErrSessionShutdown
case <-timer.C:
return ErrConnectionWriteTimeout
}
}
// send is a long running goroutine that sends data
func (s *Session) send() {
for {
select {
case ready := <-s.sendCh:
// Send a header if ready
if ready.Hdr != nil {
sent := 0
for sent < len(ready.Hdr) {
n, err := s.conn.Write(ready.Hdr[sent:])
if err != nil {
s.logger.Printf("[ERR] yamux: Failed to write header: %v", err)
asyncSendErr(ready.Err, err)
s.exitErr(err)
return
}
sent += n
}
}
// Send data from a body if given
if ready.Body != nil {
_, err := io.Copy(s.conn, ready.Body)
if err != nil {
s.logger.Printf("[ERR] yamux: Failed to write body: %v", err)
asyncSendErr(ready.Err, err)
s.exitErr(err)
return
}
}
// No error, successful send
asyncSendErr(ready.Err, nil)
case <-s.shutdownCh:
return
}
}
}
// recv is a long running goroutine that accepts new data
func (s *Session) recv() {
if err := s.recvLoop(); err != nil {
s.exitErr(err)
}
}
// recvLoop continues to receive data until a fatal error is encountered
func (s *Session) recvLoop() error {
defer close(s.recvDoneCh)
hdr := header(make([]byte, headerSize))
var handler func(header) error
for {
// Read the header
if _, err := io.ReadFull(s.bufRead, hdr); err != nil {
if err != io.EOF && !strings.Contains(err.Error(), "closed") && !strings.Contains(err.Error(), "reset by peer") {
s.logger.Printf("[ERR] yamux: Failed to read header: %v", err)
}
return err
}
// Verify the version
if hdr.Version() != protoVersion {
s.logger.Printf("[ERR] yamux: Invalid protocol version: %d", hdr.Version())
return ErrInvalidVersion
}
// Switch on the type
switch hdr.MsgType() {
case typeData:
handler = s.handleStreamMessage
case typeWindowUpdate:
handler = s.handleStreamMessage
case typeGoAway:
handler = s.handleGoAway
case typePing:
handler = s.handlePing
default:
return ErrInvalidMsgType
}
// Invoke the handler
if err := handler(hdr); err != nil {
return err
}
}
}
// handleStreamMessage handles either a data or window update frame
func (s *Session) handleStreamMessage(hdr header) error {
// Check for a new stream creation
id := hdr.StreamID()
flags := hdr.Flags()
if flags&flagSYN == flagSYN {
if err := s.incomingStream(id); err != nil {
return err
}
}
// Get the stream
s.streamLock.Lock()
stream := s.streams[id]
s.streamLock.Unlock()
// If we do not have a stream, likely we sent a RST
if stream == nil {
// Drain any data on the wire
if hdr.MsgType() == typeData && hdr.Length() > 0 {
s.logger.Printf("[WARN] yamux: Discarding data for stream: %d", id)
if _, err := io.CopyN(ioutil.Discard, s.bufRead, int64(hdr.Length())); err != nil {
s.logger.Printf("[ERR] yamux: Failed to discard data: %v", err)
return nil
}
} else {
s.logger.Printf("[WARN] yamux: frame for missing stream: %v", hdr)
}
return nil
}
// Check if this is a window update
if hdr.MsgType() == typeWindowUpdate {
if err := stream.incrSendWindow(hdr, flags); err != nil {
if sendErr := s.sendNoWait(s.goAway(goAwayProtoErr)); sendErr != nil {
s.logger.Printf("[WARN] yamux: failed to send go away: %v", sendErr)
}
return err
}
return nil
}
// Read the new data
if err := stream.readData(hdr, flags, s.bufRead); err != nil {
if sendErr := s.sendNoWait(s.goAway(goAwayProtoErr)); sendErr != nil {
s.logger.Printf("[WARN] yamux: failed to send go away: %v", sendErr)
}
return err
}
return nil
}
// handlePing is invokde for a typePing frame
func (s *Session) handlePing(hdr header) error {
flags := hdr.Flags()
pingID := hdr.Length()
// Check if this is a query, respond back in a separate context so we
// don't interfere with the receiving thread blocking for the write.
if flags&flagSYN == flagSYN {
go func() {
hdr := header(make([]byte, headerSize))
hdr.encode(typePing, flagACK, 0, pingID)
if err := s.sendNoWait(hdr); err != nil {
s.logger.Printf("[WARN] yamux: failed to send ping reply: %v", err)
}
}()
return nil
}
// Handle a response
s.pingLock.Lock()
ch := s.pings[pingID]
if ch != nil {
delete(s.pings, pingID)
close(ch)
}
s.pingLock.Unlock()
return nil
}
// handleGoAway is invokde for a typeGoAway frame
func (s *Session) handleGoAway(hdr header) error {
code := hdr.Length()
switch code {
case goAwayNormal:
atomic.SwapInt32(&s.remoteGoAway, 1)
case goAwayProtoErr:
s.logger.Printf("[ERR] yamux: received protocol error go away")
return fmt.Errorf("yamux protocol error")
case goAwayInternalErr:
s.logger.Printf("[ERR] yamux: received internal error go away")
return fmt.Errorf("remote yamux internal error")
default:
s.logger.Printf("[ERR] yamux: received unexpected go away")
return fmt.Errorf("unexpected go away received")
}
return nil
}
// incomingStream is used to create a new incoming stream
func (s *Session) incomingStream(id uint32) error {
// Reject immediately if we are doing a go away
if atomic.LoadInt32(&s.localGoAway) == 1 {
hdr := header(make([]byte, headerSize))
hdr.encode(typeWindowUpdate, flagRST, id, 0)
return s.sendNoWait(hdr)
}
// Allocate a new stream
stream := newStream(s, id, streamSYNReceived)
s.streamLock.Lock()
defer s.streamLock.Unlock()
// Check if stream already exists
if _, ok := s.streams[id]; ok {
s.logger.Printf("[ERR] yamux: duplicate stream declared")
if sendErr := s.sendNoWait(s.goAway(goAwayProtoErr)); sendErr != nil {
s.logger.Printf("[WARN] yamux: failed to send go away: %v", sendErr)
}
return ErrDuplicateStream
}
// Register the stream
s.streams[id] = stream
// Check if we've exceeded the backlog
select {
case s.acceptCh <- stream:
return nil
default:
// Backlog exceeded! RST the stream
s.logger.Printf("[WARN] yamux: backlog exceeded, forcing connection reset")
delete(s.streams, id)
stream.sendHdr.encode(typeWindowUpdate, flagRST, id, 0)
return s.sendNoWait(stream.sendHdr)
}
}
// closeStream is used to close a stream once both sides have
// issued a close.
func (s *Session) closeStream(id uint32) {
s.streamLock.Lock()
delete(s.streams, id)
s.streamLock.Unlock()
}
// establishStream is used to mark a stream that was in the
// SYN Sent state as established.
func (s *Session) establishStream() {
select {
case <-s.synCh:
default:
panic("established stream without inflight syn")
}
}

View File

@ -1,141 +0,0 @@
# Specification
We use this document to detail the internal specification of Yamux.
This is used both as a guide for implementing Yamux, but also for
alternative interoperable libraries to be built.
# Framing
Yamux uses a streaming connection underneath, but imposes a message
framing so that it can be shared between many logical streams. Each
frame contains a header like:
* Version (8 bits)
* Type (8 bits)
* Flags (16 bits)
* StreamID (32 bits)
* Length (32 bits)
This means that each header has a 12 byte overhead.
All fields are encoded in network order (big endian).
Each field is described below:
## Version Field
The version field is used for future backwards compatibily. At the
current time, the field is always set to 0, to indicate the initial
version.
## Type Field
The type field is used to switch the frame message type. The following
message types are supported:
* 0x0 Data - Used to transmit data. May transmit zero length payloads
depending on the flags.
* 0x1 Window Update - Used to updated the senders receive window size.
This is used to implement per-session flow control.
* 0x2 Ping - Used to measure RTT. It can also be used to heart-beat
and do keep-alives over TCP.
* 0x3 Go Away - Used to close a session.
## Flag Field
The flags field is used to provide additional information related
to the message type. The following flags are supported:
* 0x1 SYN - Signals the start of a new stream. May be sent with a data or
window update message. Also sent with a ping to indicate outbound.
* 0x2 ACK - Acknowledges the start of a new stream. May be sent with a data
or window update message. Also sent with a ping to indicate response.
* 0x4 FIN - Performs a half-close of a stream. May be sent with a data
message or window update.
* 0x8 RST - Reset a stream immediately. May be sent with a data or
window update message.
## StreamID Field
The StreamID field is used to identify the logical stream the frame
is addressing. The client side should use odd ID's, and the server even.
This prevents any collisions. Additionally, the 0 ID is reserved to represent
the session.
Both Ping and Go Away messages should always use the 0 StreamID.
## Length Field
The meaning of the length field depends on the message type:
* Data - provides the length of bytes following the header
* Window update - provides a delta update to the window size
* Ping - Contains an opaque value, echoed back
* Go Away - Contains an error code
# Message Flow
There is no explicit connection setup, as Yamux relies on an underlying
transport to be provided. However, there is a distinction between client
and server side of the connection.
## Opening a stream
To open a stream, an initial data or window update frame is sent
with a new StreamID. The SYN flag should be set to signal a new stream.
The receiver must then reply with either a data or window update frame
with the StreamID along with the ACK flag to accept the stream or with
the RST flag to reject the stream.
Because we are relying on the reliable stream underneath, a connection
can begin sending data once the SYN flag is sent. The corresponding
ACK does not need to be received. This is particularly well suited
for an RPC system where a client wants to open a stream and immediately
fire a request without wiating for the RTT of the ACK.
This does introduce the possibility of a connection being rejected
after data has been sent already. This is a slight semantic difference
from TCP, where the conection cannot be refused after it is opened.
Clients should be prepared to handle this by checking for an error
that indicates a RST was received.
## Closing a stream
To close a stream, either side sends a data or window update frame
along with the FIN flag. This does a half-close indicating the sender
will send no further data.
Once both sides have closed the connection, the stream is closed.
Alternatively, if an error occurs, the RST flag can be used to
hard close a stream immediately.
## Flow Control
When Yamux is initially starts each stream with a 256KB window size.
There is no window size for the session.
To prevent the streams from stalling, window update frames should be
sent regularly. Yamux can be configured to provide a larger limit for
windows sizes. Both sides assume the initial 256KB window, but can
immediately send a window update as part of the SYN/ACK indicating a
larger window.
Both sides should track the number of bytes sent in Data frames
only, as only they are tracked as part of the window size.
## Session termination
When a session is being terminated, the Go Away message should
be sent. The Length should be set to one of the following to
provide an error code:
* 0x0 Normal termination
* 0x1 Protocol error
* 0x2 Internal error

View File

@ -1,452 +0,0 @@
package yamux
import (
"bytes"
"io"
"sync"
"sync/atomic"
"time"
)
type streamState int
const (
streamInit streamState = iota
streamSYNSent
streamSYNReceived
streamEstablished
streamLocalClose
streamRemoteClose
streamClosed
streamReset
)
// Stream is used to represent a logical stream
// within a session.
type Stream struct {
recvWindow uint32
sendWindow uint32
id uint32
session *Session
state streamState
stateLock sync.Mutex
recvBuf *bytes.Buffer
recvLock sync.Mutex
controlHdr header
controlErr chan error
controlHdrLock sync.Mutex
sendHdr header
sendErr chan error
sendLock sync.Mutex
recvNotifyCh chan struct{}
sendNotifyCh chan struct{}
readDeadline time.Time
writeDeadline time.Time
}
// newStream is used to construct a new stream within
// a given session for an ID
func newStream(session *Session, id uint32, state streamState) *Stream {
s := &Stream{
id: id,
session: session,
state: state,
controlHdr: header(make([]byte, headerSize)),
controlErr: make(chan error, 1),
sendHdr: header(make([]byte, headerSize)),
sendErr: make(chan error, 1),
recvWindow: initialStreamWindow,
sendWindow: initialStreamWindow,
recvNotifyCh: make(chan struct{}, 1),
sendNotifyCh: make(chan struct{}, 1),
}
return s
}
// Session returns the associated stream session
func (s *Stream) Session() *Session {
return s.session
}
// StreamID returns the ID of this stream
func (s *Stream) StreamID() uint32 {
return s.id
}
// Read is used to read from the stream
func (s *Stream) Read(b []byte) (n int, err error) {
defer asyncNotify(s.recvNotifyCh)
START:
s.stateLock.Lock()
switch s.state {
case streamLocalClose:
fallthrough
case streamRemoteClose:
fallthrough
case streamClosed:
if s.recvBuf == nil || s.recvBuf.Len() == 0 {
s.stateLock.Unlock()
return 0, io.EOF
}
case streamReset:
s.stateLock.Unlock()
return 0, ErrConnectionReset
}
s.stateLock.Unlock()
// If there is no data available, block
s.recvLock.Lock()
if s.recvBuf == nil || s.recvBuf.Len() == 0 {
s.recvLock.Unlock()
goto WAIT
}
// Read any bytes
n, _ = s.recvBuf.Read(b)
s.recvLock.Unlock()
// Send a window update potentially
err = s.sendWindowUpdate()
return n, err
WAIT:
var timeout <-chan time.Time
if !s.readDeadline.IsZero() {
delay := s.readDeadline.Sub(time.Now())
timeout = time.After(delay)
}
select {
case <-s.recvNotifyCh:
goto START
case <-timeout:
return 0, ErrTimeout
}
}
// Write is used to write to the stream
func (s *Stream) Write(b []byte) (n int, err error) {
s.sendLock.Lock()
defer s.sendLock.Unlock()
total := 0
for total < len(b) {
n, err := s.write(b[total:])
total += n
if err != nil {
return total, err
}
}
return total, nil
}
// write is used to write to the stream, may return on
// a short write.
func (s *Stream) write(b []byte) (n int, err error) {
var flags uint16
var max uint32
var body io.Reader
START:
s.stateLock.Lock()
switch s.state {
case streamLocalClose:
fallthrough
case streamClosed:
s.stateLock.Unlock()
return 0, ErrStreamClosed
case streamReset:
s.stateLock.Unlock()
return 0, ErrConnectionReset
}
s.stateLock.Unlock()
// If there is no data available, block
window := atomic.LoadUint32(&s.sendWindow)
if window == 0 {
goto WAIT
}
// Determine the flags if any
flags = s.sendFlags()
// Send up to our send window
max = min(window, uint32(len(b)))
body = bytes.NewReader(b[:max])
// Send the header
s.sendHdr.encode(typeData, flags, s.id, max)
if err := s.session.waitForSendErr(s.sendHdr, body, s.sendErr); err != nil {
return 0, err
}
// Reduce our send window
atomic.AddUint32(&s.sendWindow, ^uint32(max-1))
// Unlock
return int(max), err
WAIT:
var timeout <-chan time.Time
if !s.writeDeadline.IsZero() {
delay := s.writeDeadline.Sub(time.Now())
timeout = time.After(delay)
}
select {
case <-s.sendNotifyCh:
goto START
case <-timeout:
return 0, ErrTimeout
}
return 0, nil
}
// sendFlags determines any flags that are appropriate
// based on the current stream state
func (s *Stream) sendFlags() uint16 {
s.stateLock.Lock()
defer s.stateLock.Unlock()
var flags uint16
switch s.state {
case streamInit:
flags |= flagSYN
s.state = streamSYNSent
case streamSYNReceived:
flags |= flagACK
s.state = streamEstablished
}
return flags
}
// sendWindowUpdate potentially sends a window update enabling
// further writes to take place. Must be invoked with the lock.
func (s *Stream) sendWindowUpdate() error {
s.controlHdrLock.Lock()
defer s.controlHdrLock.Unlock()
// Determine the delta update
max := s.session.config.MaxStreamWindowSize
delta := max - atomic.LoadUint32(&s.recvWindow)
// Determine the flags if any
flags := s.sendFlags()
// Check if we can omit the update
if delta < (max/2) && flags == 0 {
return nil
}
// Update our window
atomic.AddUint32(&s.recvWindow, delta)
// Send the header
s.controlHdr.encode(typeWindowUpdate, flags, s.id, delta)
if err := s.session.waitForSendErr(s.controlHdr, nil, s.controlErr); err != nil {
return err
}
return nil
}
// sendClose is used to send a FIN
func (s *Stream) sendClose() error {
s.controlHdrLock.Lock()
defer s.controlHdrLock.Unlock()
flags := s.sendFlags()
flags |= flagFIN
s.controlHdr.encode(typeWindowUpdate, flags, s.id, 0)
if err := s.session.waitForSendErr(s.controlHdr, nil, s.controlErr); err != nil {
return err
}
return nil
}
// Close is used to close the stream
func (s *Stream) Close() error {
closeStream := false
s.stateLock.Lock()
switch s.state {
// Opened means we need to signal a close
case streamSYNSent:
fallthrough
case streamSYNReceived:
fallthrough
case streamEstablished:
s.state = streamLocalClose
goto SEND_CLOSE
case streamLocalClose:
case streamRemoteClose:
s.state = streamClosed
closeStream = true
goto SEND_CLOSE
case streamClosed:
case streamReset:
default:
panic("unhandled state")
}
s.stateLock.Unlock()
return nil
SEND_CLOSE:
s.stateLock.Unlock()
s.sendClose()
s.notifyWaiting()
if closeStream {
s.session.closeStream(s.id)
}
return nil
}
// forceClose is used for when the session is exiting
func (s *Stream) forceClose() {
s.stateLock.Lock()
s.state = streamClosed
s.stateLock.Unlock()
s.notifyWaiting()
}
// processFlags is used to update the state of the stream
// based on set flags, if any. Lock must be held
func (s *Stream) processFlags(flags uint16) error {
// Close the stream without holding the state lock
closeStream := false
defer func() {
if closeStream {
s.session.closeStream(s.id)
}
}()
s.stateLock.Lock()
defer s.stateLock.Unlock()
if flags&flagACK == flagACK {
if s.state == streamSYNSent {
s.state = streamEstablished
}
s.session.establishStream()
}
if flags&flagFIN == flagFIN {
switch s.state {
case streamSYNSent:
fallthrough
case streamSYNReceived:
fallthrough
case streamEstablished:
s.state = streamRemoteClose
s.notifyWaiting()
case streamLocalClose:
s.state = streamClosed
closeStream = true
s.notifyWaiting()
default:
s.session.logger.Printf("[ERR] yamux: unexpected FIN flag in state %d", s.state)
return ErrUnexpectedFlag
}
}
if flags&flagRST == flagRST {
if s.state == streamSYNSent {
s.session.establishStream()
}
s.state = streamReset
closeStream = true
s.notifyWaiting()
}
return nil
}
// notifyWaiting notifies all the waiting channels
func (s *Stream) notifyWaiting() {
asyncNotify(s.recvNotifyCh)
asyncNotify(s.sendNotifyCh)
}
// incrSendWindow updates the size of our send window
func (s *Stream) incrSendWindow(hdr header, flags uint16) error {
if err := s.processFlags(flags); err != nil {
return err
}
// Increase window, unblock a sender
atomic.AddUint32(&s.sendWindow, hdr.Length())
asyncNotify(s.sendNotifyCh)
return nil
}
// readData is used to handle a data frame
func (s *Stream) readData(hdr header, flags uint16, conn io.Reader) error {
if err := s.processFlags(flags); err != nil {
return err
}
// Check that our recv window is not exceeded
length := hdr.Length()
if length == 0 {
return nil
}
if remain := atomic.LoadUint32(&s.recvWindow); length > remain {
s.session.logger.Printf("[ERR] yamux: receive window exceeded (stream: %d, remain: %d, recv: %d)", s.id, remain, length)
return ErrRecvWindowExceeded
}
// Wrap in a limited reader
conn = &io.LimitedReader{R: conn, N: int64(length)}
// Copy into buffer
s.recvLock.Lock()
if s.recvBuf == nil {
// Allocate the receive buffer just-in-time to fit the full data frame.
// This way we can read in the whole packet without further allocations.
s.recvBuf = bytes.NewBuffer(make([]byte, 0, length))
}
if _, err := io.Copy(s.recvBuf, conn); err != nil {
s.session.logger.Printf("[ERR] yamux: Failed to read stream data: %v", err)
s.recvLock.Unlock()
return err
}
// Decrement the receive window
atomic.AddUint32(&s.recvWindow, ^uint32(length-1))
s.recvLock.Unlock()
// Unblock any readers
asyncNotify(s.recvNotifyCh)
return nil
}
// SetDeadline sets the read and write deadlines
func (s *Stream) SetDeadline(t time.Time) error {
if err := s.SetReadDeadline(t); err != nil {
return err
}
if err := s.SetWriteDeadline(t); err != nil {
return err
}
return nil
}
// SetReadDeadline sets the deadline for future Read calls.
func (s *Stream) SetReadDeadline(t time.Time) error {
s.readDeadline = t
return nil
}
// SetWriteDeadline sets the deadline for future Write calls
func (s *Stream) SetWriteDeadline(t time.Time) error {
s.writeDeadline = t
return nil
}
// Shrink is used to compact the amount of buffers utilized
// This is useful when using Yamux in a connection pool to reduce
// the idle memory utilization.
func (s *Stream) Shrink() {
s.recvLock.Lock()
if s.recvBuf != nil && s.recvBuf.Len() == 0 {
s.recvBuf = nil
}
s.recvLock.Unlock()
}

View File

@ -1,28 +0,0 @@
package yamux
// asyncSendErr is used to try an async send of an error
func asyncSendErr(ch chan error, err error) {
if ch == nil {
return
}
select {
case ch <- err:
default:
}
}
// asyncNotify is used to signal a waiting goroutine
func asyncNotify(ch chan struct{}) {
select {
case ch <- struct{}{}:
default:
}
}
// min computes the minimum of two values
func min(a, b uint32) uint32 {
if a < b {
return a
}
return b
}

View File

@ -1,19 +0,0 @@
Copyright (C) 2012 Dmitry Maksimov
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

@ -1,90 +0,0 @@
[![GoDoc](https://godoc.org/github.com/kolo/xmlrpc?status.svg)](https://godoc.org/github.com/kolo/xmlrpc)
## Overview
xmlrpc is an implementation of client side part of XMLRPC protocol in Go language.
## Status
This project is in minimal maintenance mode with no further development. Bug fixes
are accepted, but it might take some time until they will be merged.
## Installation
To install xmlrpc package run `go get github.com/kolo/xmlrpc`. To use
it in application add `"github.com/kolo/xmlrpc"` string to `import`
statement.
## Usage
client, _ := xmlrpc.NewClient("https://bugzilla.mozilla.org/xmlrpc.cgi", nil)
result := struct{
Version string `xmlrpc:"version"`
}{}
client.Call("Bugzilla.version", nil, &result)
fmt.Printf("Version: %s\n", result.Version) // Version: 4.2.7+
Second argument of NewClient function is an object that implements
[http.RoundTripper](http://golang.org/pkg/net/http/#RoundTripper)
interface, it can be used to get more control over connection options.
By default it initialized by http.DefaultTransport object.
### Arguments encoding
xmlrpc package supports encoding of native Go data types to method
arguments.
Data types encoding rules:
* int, int8, int16, int32, int64 encoded to int;
* float32, float64 encoded to double;
* bool encoded to boolean;
* string encoded to string;
* time.Time encoded to datetime.iso8601;
* xmlrpc.Base64 encoded to base64;
* slice encoded to array;
Structs encoded to struct by following rules:
* all public field become struct members;
* field name become member name;
* if field has xmlrpc tag, its value become member name.
* for fields tagged with `",omitempty"`, empty values are omitted;
Server method can accept few arguments, to handle this case there is
special approach to handle slice of empty interfaces (`[]interface{}`).
Each value of such slice encoded as separate argument.
### Result decoding
Result of remote function is decoded to native Go data type.
Data types decoding rules:
* int, i4 decoded to int, int8, int16, int32, int64;
* double decoded to float32, float64;
* boolean decoded to bool;
* string decoded to string;
* array decoded to slice;
* structs decoded following the rules described in previous section;
* datetime.iso8601 decoded as time.Time data type;
* base64 decoded to string.
## Implementation details
xmlrpc package contains clientCodec type, that implements [rpc.ClientCodec](http://golang.org/pkg/net/rpc/#ClientCodec)
interface of [net/rpc](http://golang.org/pkg/net/rpc) package.
xmlrpc package works over HTTP protocol, but some internal functions
and data type were made public to make it easier to create another
implementation of xmlrpc that works over another protocol. To encode
request body there is EncodeMethodCall function. To decode server
response Response data type can be used.
## Contribution
See [project status](#status).
## Authors
Dmitry Maksimov (dmtmax@gmail.com)

View File

@ -1,161 +0,0 @@
package xmlrpc
import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"net/rpc"
"net/url"
"sync"
)
type Client struct {
*rpc.Client
}
// clientCodec is rpc.ClientCodec interface implementation.
type clientCodec struct {
// url presents url of xmlrpc service
url *url.URL
// httpClient works with HTTP protocol
httpClient *http.Client
// cookies stores cookies received on last request
cookies http.CookieJar
// responses presents map of active requests. It is required to return request id, that
// rpc.Client can mark them as done.
responses map[uint64]*http.Response
mutex sync.Mutex
response Response
// ready presents channel, that is used to link request and it`s response.
ready chan uint64
// close notifies codec is closed.
close chan uint64
}
func (codec *clientCodec) WriteRequest(request *rpc.Request, args interface{}) (err error) {
httpRequest, err := NewRequest(codec.url.String(), request.ServiceMethod, args)
if err != nil {
return err
}
if codec.cookies != nil {
for _, cookie := range codec.cookies.Cookies(codec.url) {
httpRequest.AddCookie(cookie)
}
}
var httpResponse *http.Response
httpResponse, err = codec.httpClient.Do(httpRequest)
if err != nil {
return err
}
if codec.cookies != nil {
codec.cookies.SetCookies(codec.url, httpResponse.Cookies())
}
codec.mutex.Lock()
codec.responses[request.Seq] = httpResponse
codec.mutex.Unlock()
codec.ready <- request.Seq
return nil
}
func (codec *clientCodec) ReadResponseHeader(response *rpc.Response) (err error) {
var seq uint64
select {
case seq = <-codec.ready:
case <-codec.close:
return errors.New("codec is closed")
}
response.Seq = seq
codec.mutex.Lock()
httpResponse := codec.responses[seq]
delete(codec.responses, seq)
codec.mutex.Unlock()
defer httpResponse.Body.Close()
if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 {
response.Error = fmt.Sprintf("request error: bad status code - %d", httpResponse.StatusCode)
return nil
}
body, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
response.Error = err.Error()
return nil
}
resp := Response(body)
if err := resp.Err(); err != nil {
response.Error = err.Error()
return nil
}
codec.response = resp
return nil
}
func (codec *clientCodec) ReadResponseBody(v interface{}) (err error) {
if v == nil {
return nil
}
return codec.response.Unmarshal(v)
}
func (codec *clientCodec) Close() error {
if transport, ok := codec.httpClient.Transport.(*http.Transport); ok {
transport.CloseIdleConnections()
}
close(codec.close)
return nil
}
// NewClient returns instance of rpc.Client object, that is used to send request to xmlrpc service.
func NewClient(requrl string, transport http.RoundTripper) (*Client, error) {
if transport == nil {
transport = http.DefaultTransport
}
httpClient := &http.Client{Transport: transport}
jar, err := cookiejar.New(nil)
if err != nil {
return nil, err
}
u, err := url.Parse(requrl)
if err != nil {
return nil, err
}
codec := clientCodec{
url: u,
httpClient: httpClient,
close: make(chan uint64),
ready: make(chan uint64),
responses: make(map[uint64]*http.Response),
cookies: jar,
}
return &Client{rpc.NewClientWithCodec(&codec)}, nil
}

View File

@ -1,473 +0,0 @@
package xmlrpc
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"io"
"reflect"
"strconv"
"strings"
"time"
)
const (
iso8601 = "20060102T15:04:05"
iso8601Z = "20060102T15:04:05Z07:00"
iso8601Hyphen = "2006-01-02T15:04:05"
iso8601HyphenZ = "2006-01-02T15:04:05Z07:00"
)
var (
// CharsetReader is a function to generate reader which converts a non UTF-8
// charset into UTF-8.
CharsetReader func(string, io.Reader) (io.Reader, error)
timeLayouts = []string{iso8601, iso8601Z, iso8601Hyphen, iso8601HyphenZ}
invalidXmlError = errors.New("invalid xml")
)
type TypeMismatchError string
func (e TypeMismatchError) Error() string { return string(e) }
type decoder struct {
*xml.Decoder
}
func unmarshal(data []byte, v interface{}) (err error) {
dec := &decoder{xml.NewDecoder(bytes.NewBuffer(data))}
if CharsetReader != nil {
dec.CharsetReader = CharsetReader
}
var tok xml.Token
for {
if tok, err = dec.Token(); err != nil {
return err
}
if t, ok := tok.(xml.StartElement); ok {
if t.Name.Local == "value" {
val := reflect.ValueOf(v)
if val.Kind() != reflect.Ptr {
return errors.New("non-pointer value passed to unmarshal")
}
if err = dec.decodeValue(val.Elem()); err != nil {
return err
}
break
}
}
}
// read until end of document
err = dec.Skip()
if err != nil && err != io.EOF {
return err
}
return nil
}
func (dec *decoder) decodeValue(val reflect.Value) error {
var tok xml.Token
var err error
if val.Kind() == reflect.Ptr {
if val.IsNil() {
val.Set(reflect.New(val.Type().Elem()))
}
val = val.Elem()
}
var typeName string
for {
if tok, err = dec.Token(); err != nil {
return err
}
if t, ok := tok.(xml.EndElement); ok {
if t.Name.Local == "value" {
return nil
} else {
return invalidXmlError
}
}
if t, ok := tok.(xml.StartElement); ok {
typeName = t.Name.Local
break
}
// Treat value data without type identifier as string
if t, ok := tok.(xml.CharData); ok {
if value := strings.TrimSpace(string(t)); value != "" {
if err = checkType(val, reflect.String); err != nil {
return err
}
val.SetString(value)
return nil
}
}
}
switch typeName {
case "struct":
ismap := false
pmap := val
valType := val.Type()
if err = checkType(val, reflect.Struct); err != nil {
if checkType(val, reflect.Map) == nil {
if valType.Key().Kind() != reflect.String {
return fmt.Errorf("only maps with string key type can be unmarshalled")
}
ismap = true
} else if checkType(val, reflect.Interface) == nil && val.IsNil() {
var dummy map[string]interface{}
valType = reflect.TypeOf(dummy)
pmap = reflect.New(valType).Elem()
val.Set(pmap)
ismap = true
} else {
return err
}
}
var fields map[string]reflect.Value
if !ismap {
fields = make(map[string]reflect.Value)
for i := 0; i < valType.NumField(); i++ {
field := valType.Field(i)
fieldVal := val.FieldByName(field.Name)
if fieldVal.CanSet() {
if fn := field.Tag.Get("xmlrpc"); fn != "" {
fields[fn] = fieldVal
} else {
fields[field.Name] = fieldVal
}
}
}
} else {
// Create initial empty map
pmap.Set(reflect.MakeMap(valType))
}
// Process struct members.
StructLoop:
for {
if tok, err = dec.Token(); err != nil {
return err
}
switch t := tok.(type) {
case xml.StartElement:
if t.Name.Local != "member" {
return invalidXmlError
}
tagName, fieldName, err := dec.readTag()
if err != nil {
return err
}
if tagName != "name" {
return invalidXmlError
}
var fv reflect.Value
ok := true
if !ismap {
fv, ok = fields[string(fieldName)]
} else {
fv = reflect.New(valType.Elem())
}
if ok {
for {
if tok, err = dec.Token(); err != nil {
return err
}
if t, ok := tok.(xml.StartElement); ok && t.Name.Local == "value" {
if err = dec.decodeValue(fv); err != nil {
return err
}
// </value>
if err = dec.Skip(); err != nil {
return err
}
break
}
}
}
// </member>
if err = dec.Skip(); err != nil {
return err
}
if ismap {
pmap.SetMapIndex(reflect.ValueOf(string(fieldName)), reflect.Indirect(fv))
val.Set(pmap)
}
case xml.EndElement:
break StructLoop
}
}
case "array":
slice := val
if checkType(val, reflect.Interface) == nil && val.IsNil() {
slice = reflect.ValueOf([]interface{}{})
} else if err = checkType(val, reflect.Slice); err != nil {
return err
}
ArrayLoop:
for {
if tok, err = dec.Token(); err != nil {
return err
}
switch t := tok.(type) {
case xml.StartElement:
var index int
if t.Name.Local != "data" {
return invalidXmlError
}
DataLoop:
for {
if tok, err = dec.Token(); err != nil {
return err
}
switch tt := tok.(type) {
case xml.StartElement:
if tt.Name.Local != "value" {
return invalidXmlError
}
if index < slice.Len() {
v := slice.Index(index)
if v.Kind() == reflect.Interface {
v = v.Elem()
}
if v.Kind() != reflect.Ptr {
return errors.New("error: cannot write to non-pointer array element")
}
if err = dec.decodeValue(v); err != nil {
return err
}
} else {
v := reflect.New(slice.Type().Elem())
if err = dec.decodeValue(v); err != nil {
return err
}
slice = reflect.Append(slice, v.Elem())
}
// </value>
if err = dec.Skip(); err != nil {
return err
}
index++
case xml.EndElement:
val.Set(slice)
break DataLoop
}
}
case xml.EndElement:
break ArrayLoop
}
}
default:
if tok, err = dec.Token(); err != nil {
return err
}
var data []byte
switch t := tok.(type) {
case xml.EndElement:
return nil
case xml.CharData:
data = []byte(t.Copy())
default:
return invalidXmlError
}
switch typeName {
case "int", "i4", "i8":
if checkType(val, reflect.Interface) == nil && val.IsNil() {
i, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
return err
}
pi := reflect.New(reflect.TypeOf(i)).Elem()
pi.SetInt(i)
val.Set(pi)
} else if err = checkType(val, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64); err != nil {
return err
} else {
i, err := strconv.ParseInt(string(data), 10, val.Type().Bits())
if err != nil {
return err
}
val.SetInt(i)
}
case "string", "base64":
str := string(data)
if checkType(val, reflect.Interface) == nil && val.IsNil() {
pstr := reflect.New(reflect.TypeOf(str)).Elem()
pstr.SetString(str)
val.Set(pstr)
} else if err = checkType(val, reflect.String); err != nil {
return err
} else {
val.SetString(str)
}
case "dateTime.iso8601":
var t time.Time
var err error
for _, layout := range timeLayouts {
t, err = time.Parse(layout, string(data))
if err == nil {
break
}
}
if err != nil {
return err
}
if checkType(val, reflect.Interface) == nil && val.IsNil() {
ptime := reflect.New(reflect.TypeOf(t)).Elem()
ptime.Set(reflect.ValueOf(t))
val.Set(ptime)
} else if _, ok := val.Interface().(time.Time); !ok {
return TypeMismatchError(fmt.Sprintf("error: type mismatch error - can't decode %v to time", val.Kind()))
} else {
val.Set(reflect.ValueOf(t))
}
case "boolean":
v, err := strconv.ParseBool(string(data))
if err != nil {
return err
}
if checkType(val, reflect.Interface) == nil && val.IsNil() {
pv := reflect.New(reflect.TypeOf(v)).Elem()
pv.SetBool(v)
val.Set(pv)
} else if err = checkType(val, reflect.Bool); err != nil {
return err
} else {
val.SetBool(v)
}
case "double":
if checkType(val, reflect.Interface) == nil && val.IsNil() {
i, err := strconv.ParseFloat(string(data), 64)
if err != nil {
return err
}
pdouble := reflect.New(reflect.TypeOf(i)).Elem()
pdouble.SetFloat(i)
val.Set(pdouble)
} else if err = checkType(val, reflect.Float32, reflect.Float64); err != nil {
return err
} else {
i, err := strconv.ParseFloat(string(data), val.Type().Bits())
if err != nil {
return err
}
val.SetFloat(i)
}
default:
return errors.New("unsupported type")
}
// </type>
if err = dec.Skip(); err != nil {
return err
}
}
return nil
}
func (dec *decoder) readTag() (string, []byte, error) {
var tok xml.Token
var err error
var name string
for {
if tok, err = dec.Token(); err != nil {
return "", nil, err
}
if t, ok := tok.(xml.StartElement); ok {
name = t.Name.Local
break
}
}
value, err := dec.readCharData()
if err != nil {
return "", nil, err
}
return name, value, dec.Skip()
}
func (dec *decoder) readCharData() ([]byte, error) {
var tok xml.Token
var err error
if tok, err = dec.Token(); err != nil {
return nil, err
}
if t, ok := tok.(xml.CharData); ok {
return []byte(t.Copy()), nil
} else {
return nil, invalidXmlError
}
}
func checkType(val reflect.Value, kinds ...reflect.Kind) error {
if len(kinds) == 0 {
return nil
}
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
match := false
for _, kind := range kinds {
if val.Kind() == kind {
match = true
break
}
}
if !match {
return TypeMismatchError(fmt.Sprintf("error: type mismatch - can't unmarshal %v to %v",
val.Kind(), kinds[0]))
}
return nil
}

View File

@ -1,181 +0,0 @@
package xmlrpc
import (
"bytes"
"encoding/xml"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"time"
)
// Base64 represents value in base64 encoding
type Base64 string
type encodeFunc func(reflect.Value) ([]byte, error)
func marshal(v interface{}) ([]byte, error) {
if v == nil {
return []byte{}, nil
}
val := reflect.ValueOf(v)
return encodeValue(val)
}
func encodeValue(val reflect.Value) ([]byte, error) {
var b []byte
var err error
if val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface {
if val.IsNil() {
return []byte("<value/>"), nil
}
val = val.Elem()
}
switch val.Kind() {
case reflect.Struct:
switch val.Interface().(type) {
case time.Time:
t := val.Interface().(time.Time)
b = []byte(fmt.Sprintf("<dateTime.iso8601>%s</dateTime.iso8601>", t.Format(iso8601)))
default:
b, err = encodeStruct(val)
}
case reflect.Map:
b, err = encodeMap(val)
case reflect.Slice:
b, err = encodeSlice(val)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
b = []byte(fmt.Sprintf("<int>%s</int>", strconv.FormatInt(val.Int(), 10)))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
b = []byte(fmt.Sprintf("<i4>%s</i4>", strconv.FormatUint(val.Uint(), 10)))
case reflect.Float32, reflect.Float64:
b = []byte(fmt.Sprintf("<double>%s</double>",
strconv.FormatFloat(val.Float(), 'f', -1, val.Type().Bits())))
case reflect.Bool:
if val.Bool() {
b = []byte("<boolean>1</boolean>")
} else {
b = []byte("<boolean>0</boolean>")
}
case reflect.String:
var buf bytes.Buffer
xml.Escape(&buf, []byte(val.String()))
if _, ok := val.Interface().(Base64); ok {
b = []byte(fmt.Sprintf("<base64>%s</base64>", buf.String()))
} else {
b = []byte(fmt.Sprintf("<string>%s</string>", buf.String()))
}
default:
return nil, fmt.Errorf("xmlrpc encode error: unsupported type")
}
if err != nil {
return nil, err
}
return []byte(fmt.Sprintf("<value>%s</value>", string(b))), nil
}
func encodeStruct(structVal reflect.Value) ([]byte, error) {
var b bytes.Buffer
b.WriteString("<struct>")
structType := structVal.Type()
for i := 0; i < structType.NumField(); i++ {
fieldVal := structVal.Field(i)
fieldType := structType.Field(i)
name := fieldType.Tag.Get("xmlrpc")
// if the tag has the omitempty property, skip it
if strings.HasSuffix(name, ",omitempty") && isZero(fieldVal) {
continue
}
name = strings.TrimSuffix(name, ",omitempty")
if name == "" {
name = fieldType.Name
}
p, err := encodeValue(fieldVal)
if err != nil {
return nil, err
}
b.WriteString("<member>")
b.WriteString(fmt.Sprintf("<name>%s</name>", name))
b.Write(p)
b.WriteString("</member>")
}
b.WriteString("</struct>")
return b.Bytes(), nil
}
var sortMapKeys bool
func encodeMap(val reflect.Value) ([]byte, error) {
var t = val.Type()
if t.Key().Kind() != reflect.String {
return nil, fmt.Errorf("xmlrpc encode error: only maps with string keys are supported")
}
var b bytes.Buffer
b.WriteString("<struct>")
keys := val.MapKeys()
if sortMapKeys {
sort.Slice(keys, func(i, j int) bool { return keys[i].String() < keys[j].String() })
}
for i := 0; i < val.Len(); i++ {
key := keys[i]
kval := val.MapIndex(key)
b.WriteString("<member>")
b.WriteString(fmt.Sprintf("<name>%s</name>", key.String()))
p, err := encodeValue(kval)
if err != nil {
return nil, err
}
b.Write(p)
b.WriteString("</member>")
}
b.WriteString("</struct>")
return b.Bytes(), nil
}
func encodeSlice(val reflect.Value) ([]byte, error) {
var b bytes.Buffer
b.WriteString("<array><data>")
for i := 0; i < val.Len(); i++ {
p, err := encodeValue(val.Index(i))
if err != nil {
return nil, err
}
b.Write(p)
}
b.WriteString("</data></array>")
return b.Bytes(), nil
}

View File

@ -1,44 +0,0 @@
package xmlrpc
import (
"math"
. "reflect"
)
func isZero(v Value) bool {
switch v.Kind() {
case Bool:
return !v.Bool()
case Int, Int8, Int16, Int32, Int64:
return v.Int() == 0
case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
return v.Uint() == 0
case Float32, Float64:
return math.Float64bits(v.Float()) == 0
case Complex64, Complex128:
c := v.Complex()
return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0
case Array:
for i := 0; i < v.Len(); i++ {
if !isZero(v.Index(i)) {
return false
}
}
return true
case Chan, Func, Interface, Map, Ptr, Slice, UnsafePointer:
return v.IsNil()
case String:
return v.Len() == 0
case Struct:
for i := 0; i < v.NumField(); i++ {
if !isZero(v.Field(i)) {
return false
}
}
return true
default:
// This should never happens, but will act as a safeguard for
// later, as a default value doesn't makes sense here.
panic(&ValueError{"reflect.Value.IsZero", v.Kind()})
}
}

View File

@ -1,57 +0,0 @@
package xmlrpc
import (
"bytes"
"fmt"
"net/http"
)
func NewRequest(url string, method string, args interface{}) (*http.Request, error) {
var t []interface{}
var ok bool
if t, ok = args.([]interface{}); !ok {
if args != nil {
t = []interface{}{args}
}
}
body, err := EncodeMethodCall(method, t...)
if err != nil {
return nil, err
}
request, err := http.NewRequest("POST", url, bytes.NewReader(body))
if err != nil {
return nil, err
}
request.Header.Set("Content-Type", "text/xml")
request.Header.Set("Content-Length", fmt.Sprintf("%d", len(body)))
return request, nil
}
func EncodeMethodCall(method string, args ...interface{}) ([]byte, error) {
var b bytes.Buffer
b.WriteString(`<?xml version="1.0" encoding="UTF-8"?>`)
b.WriteString(fmt.Sprintf("<methodCall><methodName>%s</methodName>", method))
if args != nil {
b.WriteString("<params>")
for _, arg := range args {
p, err := marshal(arg)
if err != nil {
return nil, err
}
b.WriteString(fmt.Sprintf("<param>%s</param>", string(p)))
}
b.WriteString("</params>")
}
b.WriteString("</methodCall>")
return b.Bytes(), nil
}

View File

@ -1,42 +0,0 @@
package xmlrpc
import (
"fmt"
"regexp"
)
var (
faultRx = regexp.MustCompile(`<fault>(\s|\S)+</fault>`)
)
// FaultError is returned from the server when an invalid call is made
type FaultError struct {
Code int `xmlrpc:"faultCode"`
String string `xmlrpc:"faultString"`
}
// Error implements the error interface
func (e FaultError) Error() string {
return fmt.Sprintf("Fault(%d): %s", e.Code, e.String)
}
type Response []byte
func (r Response) Err() error {
if !faultRx.Match(r) {
return nil
}
var fault FaultError
if err := unmarshal(r, &fault); err != nil {
return err
}
return fault
}
func (r Response) Unmarshal(v interface{}) error {
if err := unmarshal(r, v); err != nil {
return err
}
return nil
}

View File

@ -1,25 +0,0 @@
# encoding: utf-8
require "xmlrpc/server"
class Service
def time
Time.now
end
def upcase(s)
s.upcase
end
def sum(x, y)
x + y
end
def error
raise XMLRPC::FaultException.new(500, "Server error")
end
end
server = XMLRPC::Server.new 5001, 'localhost'
server.add_handler "service", Service.new
server.serve

27
vendor/github.com/kr/fs/LICENSE generated vendored
View File

@ -1,27 +0,0 @@
Copyright (c) 2012 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.

3
vendor/github.com/kr/fs/Readme generated vendored
View File

@ -1,3 +0,0 @@
Filesystem Package
http://godoc.org/github.com/kr/fs

View File

@ -1,36 +0,0 @@
package fs
import (
"io/ioutil"
"os"
"path/filepath"
)
// FileSystem defines the methods of an abstract filesystem.
type FileSystem interface {
// ReadDir reads the directory named by dirname and returns a
// list of directory entries.
ReadDir(dirname string) ([]os.FileInfo, error)
// Lstat returns a FileInfo describing the named file. If the file is a
// symbolic link, the returned FileInfo describes the symbolic link. Lstat
// makes no attempt to follow the link.
Lstat(name string) (os.FileInfo, error)
// Join joins any number of path elements into a single path, adding a
// separator if necessary. The result is Cleaned; in particular, all
// empty strings are ignored.
//
// The separator is FileSystem specific.
Join(elem ...string) string
}
// fs represents a FileSystem provided by the os package.
type fs struct{}
func (f *fs) ReadDir(dirname string) ([]os.FileInfo, error) { return ioutil.ReadDir(dirname) }
func (f *fs) Lstat(name string) (os.FileInfo, error) { return os.Lstat(name) }
func (f *fs) Join(elem ...string) string { return filepath.Join(elem...) }

95
vendor/github.com/kr/fs/walk.go generated vendored
View File

@ -1,95 +0,0 @@
// Package fs provides filesystem-related functions.
package fs
import (
"os"
)
// Walker provides a convenient interface for iterating over the
// descendants of a filesystem path.
// Successive calls to the Step method will step through each
// file or directory in the tree, including the root. The files
// are walked in lexical order, which makes the output deterministic
// but means that for very large directories Walker can be inefficient.
// Walker does not follow symbolic links.
type Walker struct {
fs FileSystem
cur item
stack []item
descend bool
}
type item struct {
path string
info os.FileInfo
err error
}
// Walk returns a new Walker rooted at root.
func Walk(root string) *Walker {
return WalkFS(root, new(fs))
}
// WalkFS returns a new Walker rooted at root on the FileSystem fs.
func WalkFS(root string, fs FileSystem) *Walker {
info, err := fs.Lstat(root)
return &Walker{
fs: fs,
stack: []item{{root, info, err}},
}
}
// Step advances the Walker to the next file or directory,
// which will then be available through the Path, Stat,
// and Err methods.
// It returns false when the walk stops at the end of the tree.
func (w *Walker) Step() bool {
if w.descend && w.cur.err == nil && w.cur.info.IsDir() {
list, err := w.fs.ReadDir(w.cur.path)
if err != nil {
w.cur.err = err
w.stack = append(w.stack, w.cur)
} else {
for i := len(list) - 1; i >= 0; i-- {
path := w.fs.Join(w.cur.path, list[i].Name())
w.stack = append(w.stack, item{path, list[i], nil})
}
}
}
if len(w.stack) == 0 {
return false
}
i := len(w.stack) - 1
w.cur = w.stack[i]
w.stack = w.stack[:i]
w.descend = true
return true
}
// Path returns the path to the most recent file or directory
// visited by a call to Step. It contains the argument to Walk
// as a prefix; that is, if Walk is called with "dir", which is
// a directory containing the file "a", Path will return "dir/a".
func (w *Walker) Path() string {
return w.cur.path
}
// Stat returns info for the most recent file or directory
// visited by a call to Step.
func (w *Walker) Stat() os.FileInfo {
return w.cur.info
}
// Err returns the error, if any, for the most recent attempt
// by Step to visit a file or directory. If a directory has
// an error, w will not descend into that directory.
func (w *Walker) Err() error {
return w.cur.err
}
// SkipDir causes the currently visited directory to be skipped.
// If w is not on a directory, SkipDir has no effect.
func (w *Walker) SkipDir() {
w.descend = false
}

View File

@ -1,202 +0,0 @@
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

@ -1,35 +0,0 @@
package dom
import (
"bytes"
"fmt"
)
type Document struct {
root *Element
PrettyPrint bool
Indentation string
DocType bool
}
func CreateDocument() *Document {
return &Document{ PrettyPrint: false, Indentation: " ", DocType: true }
}
func (doc *Document) SetRoot(node *Element) {
node.parent = nil
doc.root = node
}
func (doc *Document) String() string {
var b bytes.Buffer
if doc.DocType {
fmt.Fprintln(&b, `<?xml version="1.0" encoding="utf-8" ?>`)
}
if doc.root != nil {
doc.root.Bytes(&b, doc.PrettyPrint, doc.Indentation, 0)
}
return string(b.Bytes())
}

View File

@ -1,200 +0,0 @@
package dom
import (
"encoding/xml"
"fmt"
"bytes"
)
type Attr struct {
Name xml.Name // Attribute namespace and name.
Value string // Attribute value.
}
type Element struct {
name xml.Name
children []*Element
parent *Element
content string
attributes []*Attr
namespaces []*Namespace
document *Document
}
func CreateElement(n string) *Element {
element := &Element { name: xml.Name { Local: n } }
element.children = make([]*Element, 0, 5)
element.attributes = make([]*Attr, 0, 10)
element.namespaces = make([]*Namespace, 0, 10)
return element
}
func (node *Element) AddChild(child *Element) *Element {
if child.parent != nil {
child.parent.RemoveChild(child)
}
child.SetParent(node)
node.children = append(node.children, child)
return node
}
func (node *Element) RemoveChild(child *Element) *Element {
p := -1
for i, v := range node.children {
if v == child {
p = i
break
}
}
if p == -1 {
return node
}
copy(node.children[p:], node.children[p+1:])
node.children = node.children[0 : len(node.children)-1]
child.parent = nil
return node
}
func (node *Element) SetAttr(name string, value string) *Element {
// namespaces?
attr := &Attr{ Name: xml.Name { Local: name }, Value: value }
node.attributes = append(node.attributes, attr)
return node
}
func (node *Element) SetParent(parent *Element) *Element {
node.parent = parent
return node
}
func (node *Element) SetContent(content string) *Element {
node.content = content
return node
}
// Add a namespace declaration to this node
func (node *Element) DeclareNamespace(ns Namespace) *Element {
// check if we already have it
prefix := node.namespacePrefix(ns.Uri)
if prefix == ns.Prefix {
return node
}
// add it
node.namespaces = append(node.namespaces, &ns)
return node
}
func (node *Element) DeclaredNamespaces() []*Namespace {
return node.namespaces
}
func (node *Element) SetNamespace(prefix string, uri string) {
resolved := node.namespacePrefix(uri)
if resolved == "" {
// we couldn't find the namespace, let's declare it at this node
node.namespaces = append(node.namespaces, &Namespace { Prefix: prefix, Uri: uri })
}
node.name.Space = uri
}
func (node *Element) Bytes(out *bytes.Buffer, indent bool, indentType string, level int) {
empty := len(node.children) == 0 && node.content == ""
content := node.content != ""
// children := len(node.children) > 0
// ns := len(node.namespaces) > 0
// attrs := len(node.attributes) > 0
indentStr := ""
nextLine := ""
if indent {
nextLine = "\n"
for i := 0; i < level; i++ {
indentStr += indentType
}
}
if node.name.Local != "" {
if len(node.name.Space) > 0 {
// first find if ns has been declared, otherwise
prefix := node.namespacePrefix(node.name.Space)
fmt.Fprintf(out, "%s<%s:%s", indentStr, prefix, node.name.Local)
} else {
fmt.Fprintf(out, "%s<%s", indentStr, node.name.Local)
}
}
// declared namespaces
for _, v := range node.namespaces {
prefix := node.namespacePrefix(v.Uri)
fmt.Fprintf(out, ` xmlns:%s="%s"`, prefix, v.Uri)
}
// attributes
for _, v := range node.attributes {
if len(v.Name.Space) > 0 {
prefix := node.namespacePrefix(v.Name.Space)
fmt.Fprintf(out, ` %s:%s="%s"`, prefix, v.Name.Local, v.Value)
} else {
fmt.Fprintf(out, ` %s="%s"`, v.Name.Local, v.Value)
}
}
// close tag
if empty {
fmt.Fprintf(out, "/>%s", nextLine)
} else {
if content {
out.WriteRune('>')
} else {
fmt.Fprintf(out, ">%s", nextLine)
}
}
if len(node.children) > 0 {
for _, child := range node.children {
child.Bytes(out, indent, indentType, level + 1)
}
} else if node.content != "" {
//val := []byte(node.content)
//xml.EscapeText(out, val)
out.WriteString(node.content)
}
if !empty && len(node.name.Local) > 0 {
var indentation string
if content {
indentation = ""
} else {
indentation = indentStr
}
if len(node.name.Space) > 0 {
prefix := node.namespacePrefix(node.name.Space)
fmt.Fprintf(out, "%s</%s:%s>\n", indentation, prefix, node.name.Local)
} else {
fmt.Fprintf(out, "%s</%s>\n", indentation, node.name.Local)
}
}
}
// Finds the prefix of the given namespace if it has been declared
// in this node or in one of its parent
func (node *Element) namespacePrefix(uri string) string {
for _, ns := range node.namespaces {
if ns.Uri == uri {
return ns.Prefix
}
}
if node.parent == nil {
return ""
}
return node.parent.namespacePrefix(uri)
}
func (node *Element) String() string {
var b bytes.Buffer
node.Bytes(&b, false, "", 0)
return string(b.Bytes())
}

View File

@ -1,10 +0,0 @@
package dom
type Namespace struct {
Prefix string
Uri string
}
func (ns *Namespace) SetTo(node *Element) {
node.SetNamespace(ns.Prefix, ns.Uri)
}

View File

@ -1,202 +0,0 @@
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

@ -1,181 +0,0 @@
package soap
import (
"github.com/masterzen/simplexml/dom"
"strconv"
)
type HeaderOption struct {
key string
value string
}
func NewHeaderOption(name string, value string) *HeaderOption {
return &HeaderOption{key: name, value: value}
}
type SoapHeader struct {
to string
replyTo string
maxEnvelopeSize string
timeout string
locale string
id string
action string
shellId string
resourceURI string
options []HeaderOption
message *SoapMessage
}
type HeaderBuilder interface {
To(string) *SoapHeader
ReplyTo(string) *SoapHeader
MaxEnvelopeSize(int) *SoapHeader
Timeout(string) *SoapHeader
Locale(string) *SoapHeader
Id(string) *SoapHeader
Action(string) *SoapHeader
ShellId(string) *SoapHeader
resourceURI(string) *SoapHeader
AddOption(*HeaderOption) *SoapHeader
Options([]HeaderOption) *SoapHeader
Build(*SoapMessage) *SoapMessage
}
func (self *SoapHeader) To(uri string) *SoapHeader {
self.to = uri
return self
}
func (self *SoapHeader) ReplyTo(uri string) *SoapHeader {
self.replyTo = uri
return self
}
func (self *SoapHeader) MaxEnvelopeSize(size int) *SoapHeader {
self.maxEnvelopeSize = strconv.Itoa(size)
return self
}
func (self *SoapHeader) Timeout(timeout string) *SoapHeader {
self.timeout = timeout
return self
}
func (self *SoapHeader) Id(id string) *SoapHeader {
self.id = id
return self
}
func (self *SoapHeader) Action(action string) *SoapHeader {
self.action = action
return self
}
func (self *SoapHeader) Locale(locale string) *SoapHeader {
self.locale = locale
return self
}
func (self *SoapHeader) ShellId(shellId string) *SoapHeader {
self.shellId = shellId
return self
}
func (self *SoapHeader) ResourceURI(resourceURI string) *SoapHeader {
self.resourceURI = resourceURI
return self
}
func (self *SoapHeader) AddOption(option *HeaderOption) *SoapHeader {
self.options = append(self.options, *option)
return self
}
func (self *SoapHeader) Options(options []HeaderOption) *SoapHeader {
self.options = options
return self
}
func (self *SoapHeader) Build() *SoapMessage {
header := self.createElement(self.message.envelope, "Header", NS_SOAP_ENV)
if self.to != "" {
to := self.createElement(header, "To", NS_ADDRESSING)
to.SetContent(self.to)
}
if self.replyTo != "" {
replyTo := self.createElement(header, "ReplyTo", NS_ADDRESSING)
a := self.createMUElement(replyTo, "Address", NS_ADDRESSING, true)
a.SetContent(self.replyTo)
}
if self.maxEnvelopeSize != "" {
envelope := self.createMUElement(header, "MaxEnvelopeSize", NS_WSMAN_DMTF, true)
envelope.SetContent(self.maxEnvelopeSize)
}
if self.timeout != "" {
timeout := self.createElement(header, "OperationTimeout", NS_WSMAN_DMTF)
timeout.SetContent(self.timeout)
}
if self.id != "" {
id := self.createElement(header, "MessageID", NS_ADDRESSING)
id.SetContent(self.id)
}
if self.locale != "" {
locale := self.createMUElement(header, "Locale", NS_WSMAN_DMTF, false)
locale.SetAttr("xml:lang", self.locale)
datalocale := self.createMUElement(header, "DataLocale", NS_WSMAN_MSFT, false)
datalocale.SetAttr("xml:lang", self.locale)
}
if self.action != "" {
action := self.createMUElement(header, "Action", NS_ADDRESSING, true)
action.SetContent(self.action)
}
if self.shellId != "" {
selectorSet := self.createElement(header, "SelectorSet", NS_WSMAN_DMTF)
selector := self.createElement(selectorSet, "Selector", NS_WSMAN_DMTF)
selector.SetAttr("Name", "ShellId")
selector.SetContent(self.shellId)
}
if self.resourceURI != "" {
resource := self.createMUElement(header, "ResourceURI", NS_WSMAN_DMTF, true)
resource.SetContent(self.resourceURI)
}
if len(self.options) > 0 {
set := self.createElement(header, "OptionSet", NS_WSMAN_DMTF)
for _, option := range self.options {
e := self.createElement(set, "Option", NS_WSMAN_DMTF)
e.SetAttr("Name", option.key)
e.SetContent(option.value)
}
}
return self.message
}
func (self *SoapHeader) createElement(parent *dom.Element, name string, ns dom.Namespace) (element *dom.Element) {
element = dom.CreateElement(name)
parent.AddChild(element)
ns.SetTo(element)
return
}
func (self *SoapHeader) createMUElement(parent *dom.Element, name string, ns dom.Namespace, mustUnderstand bool) (element *dom.Element) {
element = self.createElement(parent, name, ns)
value := "false"
if mustUnderstand {
value = "true"
}
element.SetAttr("mustUnderstand", value)
return
}

View File

@ -1,74 +0,0 @@
package soap
import (
"github.com/masterzen/simplexml/dom"
)
type SoapMessage struct {
document *dom.Document
envelope *dom.Element
header *SoapHeader
body *dom.Element
}
type MessageBuilder interface {
SetBody(*dom.Element)
NewBody() *dom.Element
CreateElement(*dom.Element, string, dom.Namespace) *dom.Element
CreateBodyElement(string, dom.Namespace) *dom.Element
Header() *SoapHeader
Doc() *dom.Document
Free()
String() string
}
func NewMessage() (message *SoapMessage) {
doc := dom.CreateDocument()
e := dom.CreateElement("Envelope")
doc.SetRoot(e)
AddUsualNamespaces(e)
NS_SOAP_ENV.SetTo(e)
message = &SoapMessage{document: doc, envelope: e}
return
}
func (message *SoapMessage) NewBody() (body *dom.Element) {
body = dom.CreateElement("Body")
message.envelope.AddChild(body)
NS_SOAP_ENV.SetTo(body)
return
}
func (message *SoapMessage) String() string {
return message.document.String()
}
func (message *SoapMessage) Doc() *dom.Document {
return message.document
}
func (message *SoapMessage) Free() {
}
func (message *SoapMessage) CreateElement(parent *dom.Element, name string, ns dom.Namespace) (element *dom.Element) {
element = dom.CreateElement(name)
parent.AddChild(element)
ns.SetTo(element)
return
}
func (message *SoapMessage) CreateBodyElement(name string, ns dom.Namespace) (element *dom.Element) {
if message.body == nil {
message.body = message.NewBody()
}
return message.CreateElement(message.body, name, ns)
}
func (message *SoapMessage) Header() *SoapHeader {
if message.header == nil {
message.header = &SoapHeader{message: message}
}
return message.header
}

View File

@ -1,37 +0,0 @@
package soap
import (
"github.com/masterzen/simplexml/dom"
"github.com/masterzen/xmlpath"
)
var (
NS_SOAP_ENV = dom.Namespace{"env", "http://www.w3.org/2003/05/soap-envelope"}
NS_ADDRESSING = dom.Namespace{"a", "http://schemas.xmlsoap.org/ws/2004/08/addressing"}
NS_CIMBINDING = dom.Namespace{"b", "http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd"}
NS_ENUM = dom.Namespace{"n", "http://schemas.xmlsoap.org/ws/2004/09/enumeration"}
NS_TRANSFER = dom.Namespace{"x", "http://schemas.xmlsoap.org/ws/2004/09/transfer"}
NS_WSMAN_DMTF = dom.Namespace{"w", "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"}
NS_WSMAN_MSFT = dom.Namespace{"p", "http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd"}
NS_SCHEMA_INST = dom.Namespace{"xsi", "http://www.w3.org/2001/XMLSchema-instance"}
NS_WIN_SHELL = dom.Namespace{"rsp", "http://schemas.microsoft.com/wbem/wsman/1/windows/shell"}
NS_WSMAN_FAULT = dom.Namespace{"f", "http://schemas.microsoft.com/wbem/wsman/1/wsmanfault"}
)
var MostUsed = [...]dom.Namespace{NS_SOAP_ENV, NS_ADDRESSING, NS_WIN_SHELL, NS_WSMAN_DMTF, NS_WSMAN_MSFT}
func AddUsualNamespaces(node *dom.Element) {
for _, ns := range MostUsed {
node.DeclareNamespace(ns)
}
}
func GetAllNamespaces() []xmlpath.Namespace {
var ns = []dom.Namespace{NS_WIN_SHELL, NS_ADDRESSING, NS_WSMAN_DMTF, NS_WSMAN_MSFT, NS_SOAP_ENV}
var xmlpathNs = make([]xmlpath.Namespace, 0, 4)
for _, namespace := range ns {
xmlpathNs = append(xmlpathNs, xmlpath.Namespace{Prefix: namespace.Prefix, Uri: namespace.Uri})
}
return xmlpathNs
}

View File

@ -1,166 +0,0 @@
package winrm
import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net/http"
"github.com/masterzen/winrm/soap"
)
type Client struct {
Parameters
username string
password string
useHTTPS bool
url string
http HttpPost
transport http.RoundTripper
}
// NewClient will create a new remote client on url, connecting with user and password
// This function doesn't connect (connection happens only when CreateShell is called)
func NewClient(endpoint *Endpoint, user, password string) (client *Client, err error) {
params := DefaultParameters()
client, err = NewClientWithParameters(endpoint, user, password, params)
return
}
// NewClient will create a new remote client on url, connecting with user and password
// This function doesn't connect (connection happens only when CreateShell is called)
func NewClientWithParameters(endpoint *Endpoint, user, password string, params *Parameters) (client *Client, err error) {
transport, err := newTransport(endpoint)
client = &Client{
Parameters: *params,
username: user,
password: password,
url: endpoint.url(),
http: Http_post,
useHTTPS: endpoint.HTTPS,
transport: transport,
}
if params.TransportDecorator != nil {
client.transport = params.TransportDecorator(transport)
}
return
}
// newTransport will create a new HTTP Transport, with options specified within the endpoint configuration
func newTransport(endpoint *Endpoint) (*http.Transport, error) {
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: endpoint.Insecure,
},
}
if endpoint.CACert != nil && len(*endpoint.CACert) > 0 {
certPool, err := readCACerts(endpoint.CACert)
if err != nil {
return nil, err
}
transport.TLSClientConfig.RootCAs = certPool
}
return transport, nil
}
func readCACerts(certs *[]byte) (*x509.CertPool, error) {
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(*certs) {
return nil, fmt.Errorf("Unable to read certificates")
}
return certPool, nil
}
// CreateShell will create a WinRM Shell, which is the prealable for running
// commands.
func (client *Client) CreateShell() (shell *Shell, err error) {
request := NewOpenShellRequest(client.url, &client.Parameters)
defer request.Free()
response, err := client.sendRequest(request)
if err == nil {
var shellId string
if shellId, err = ParseOpenShellResponse(response); err == nil {
shell = &Shell{client: client, ShellId: shellId}
}
}
return
}
func (client *Client) sendRequest(request *soap.SoapMessage) (response string, err error) {
return client.http(client, request)
}
// Run will run command on the the remote host, writing the process stdout and stderr to
// the given writers. Note with this method it isn't possible to inject stdin.
func (client *Client) Run(command string, stdout io.Writer, stderr io.Writer) (exitCode int, err error) {
shell, err := client.CreateShell()
if err != nil {
return 0, err
}
var cmd *Command
cmd, err = shell.Execute(command)
if err != nil {
return 0, err
}
go io.Copy(stdout, cmd.Stdout)
go io.Copy(stderr, cmd.Stderr)
cmd.Wait()
shell.Close()
return cmd.ExitCode(), cmd.err
}
// Run will run command on the the remote host, returning the process stdout and stderr
// as strings, and using the input stdin string as the process input
func (client *Client) RunWithString(command string, stdin string) (stdout string, stderr string, exitCode int, err error) {
shell, err := client.CreateShell()
if err != nil {
return "", "", 0, err
}
defer shell.Close()
var cmd *Command
cmd, err = shell.Execute(command)
if err != nil {
return "", "", 0, err
}
if len(stdin) > 0 {
cmd.Stdin.Write([]byte(stdin))
}
var outWriter, errWriter bytes.Buffer
go io.Copy(&outWriter, cmd.Stdout)
go io.Copy(&errWriter, cmd.Stderr)
cmd.Wait()
return outWriter.String(), errWriter.String(), cmd.ExitCode(), cmd.err
}
// Run will run command on the the remote host, writing the process stdout and stderr to
// the given writers, and injecting the process stdin with the stdin reader.
// Warning stdin (not stdout/stderr) are bufferized, which means reading only one byte in stdin will
// send a winrm http packet to the remote host. If stdin is a pipe, it might be better for
// performance reasons to buffer it.
func (client *Client) RunWithInput(command string, stdout io.Writer, stderr io.Writer, stdin io.Reader) (exitCode int, err error) {
shell, err := client.CreateShell()
if err != nil {
return 0, err
}
defer shell.Close()
var cmd *Command
cmd, err = shell.Execute(command)
if err != nil {
return 0, err
}
go io.Copy(cmd.Stdin, stdin)
go io.Copy(stdout, cmd.Stdout)
go io.Copy(stderr, cmd.Stderr)
cmd.Wait()
return cmd.ExitCode(), cmd.err
}

View File

@ -1,210 +0,0 @@
package winrm
import (
"bytes"
"errors"
"io"
"strings"
)
type commandWriter struct {
*Command
eof bool
}
type commandReader struct {
*Command
write *io.PipeWriter
read *io.PipeReader
stream string
}
// Command represents a given command running on a Shell. This structure allows to get access
// to the various stdout, stderr and stdin pipes.
type Command struct {
client *Client
shell *Shell
commandId string
exitCode int
finished bool
err error
Stdin *commandWriter
Stdout *commandReader
Stderr *commandReader
done chan struct{}
cancel chan struct{}
}
func newCommand(shell *Shell, commandId string) *Command {
command := &Command{shell: shell, client: shell.client, commandId: commandId, exitCode: 1, err: nil, done: make(chan struct{}), cancel: make(chan struct{})}
command.Stdin = &commandWriter{Command: command, eof: false}
command.Stdout = newCommandReader("stdout", command)
command.Stderr = newCommandReader("stderr", command)
go fetchOutput(command)
return command
}
func newCommandReader(stream string, command *Command) *commandReader {
read, write := io.Pipe()
return &commandReader{Command: command, stream: stream, write: write, read: read}
}
func fetchOutput(command *Command) {
for {
select {
case <-command.cancel:
close(command.done)
return
default:
finished, err := command.slurpAllOutput()
if finished {
command.err = err
close(command.done)
return
}
}
}
}
func (command *Command) check() (err error) {
if command.commandId == "" {
return errors.New("Command has already been closed")
}
if command.shell == nil {
return errors.New("Command has no associated shell")
}
if command.client == nil {
return errors.New("Command has no associated client")
}
return
}
// Close will terminate the running command
func (command *Command) Close() (err error) {
if err = command.check(); err != nil {
return err
}
select { // close cancel channel if it's still open
case <-command.cancel:
default:
close(command.cancel)
}
request := NewSignalRequest(command.client.url, command.shell.ShellId, command.commandId, &command.client.Parameters)
defer request.Free()
_, err = command.client.sendRequest(request)
return err
}
func (command *Command) slurpAllOutput() (finished bool, err error) {
if err = command.check(); err != nil {
command.Stderr.write.CloseWithError(err)
command.Stdout.write.CloseWithError(err)
return true, err
}
request := NewGetOutputRequest(command.client.url, command.shell.ShellId, command.commandId, "stdout stderr", &command.client.Parameters)
defer request.Free()
response, err := command.client.sendRequest(request)
if err != nil {
if strings.Contains(err.Error(), "OperationTimeout") {
// Operation timeout because there was no command output
return
}
command.Stderr.write.CloseWithError(err)
command.Stdout.write.CloseWithError(err)
return true, err
}
var exitCode int
var stdout, stderr bytes.Buffer
finished, exitCode, err = ParseSlurpOutputErrResponse(response, &stdout, &stderr)
if err != nil {
command.Stderr.write.CloseWithError(err)
command.Stdout.write.CloseWithError(err)
return true, err
}
if stdout.Len() > 0 {
command.Stdout.write.Write(stdout.Bytes())
}
if stderr.Len() > 0 {
command.Stderr.write.Write(stderr.Bytes())
}
if finished {
command.exitCode = exitCode
command.Stderr.write.Close()
command.Stdout.write.Close()
}
return
}
func (command *Command) sendInput(data []byte) (err error) {
if err = command.check(); err != nil {
return err
}
request := NewSendInputRequest(command.client.url, command.shell.ShellId, command.commandId, data, &command.client.Parameters)
defer request.Free()
_, err = command.client.sendRequest(request)
return
}
// ExitCode returns command exit code when it is finished. Before that the result is always 0.
func (command *Command) ExitCode() int {
return command.exitCode
}
// Calling this function will block the current goroutine until the remote command terminates.
func (command *Command) Wait() {
// block until finished
<-command.done
}
// Write data to this Pipe
func (w *commandWriter) Write(data []byte) (written int, err error) {
for len(data) > 0 {
if w.eof {
err = io.EOF
return
}
// never send more data than our EnvelopeSize.
n := min(w.client.Parameters.EnvelopeSize-1000, len(data))
if err = w.sendInput(data[:n]); err != nil {
break
}
data = data[n:]
written += int(n)
}
return
}
func min(a int, b int) int {
if a < b {
return a
}
return b
}
func (w *commandWriter) Close() error {
w.eof = true
return w.Close()
}
// Read data from this Pipe
func (r *commandReader) Read(buf []byte) (int, error) {
n, err := r.read.Read(buf)
if err != nil && err != io.EOF {
return 0, err
}
return n, err
}

View File

@ -1,22 +0,0 @@
package winrm
import "fmt"
type Endpoint struct {
Host string
Port int
HTTPS bool
Insecure bool
CACert *[]byte
}
func (ep *Endpoint) url() string {
var scheme string
if ep.HTTPS {
scheme = "https"
} else {
scheme = "http"
}
return fmt.Sprintf("%s://%s:%d/wsman", scheme, ep.Host, ep.Port)
}

View File

@ -1,60 +0,0 @@
package winrm
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/masterzen/winrm/soap"
)
var soapXML string = "application/soap+xml"
type HttpPost func(*Client, *soap.SoapMessage) (string, error)
func body(response *http.Response) (content string, err error) {
contentType := response.Header.Get("Content-Type")
if strings.HasPrefix(contentType, soapXML) {
var body []byte
body, err = ioutil.ReadAll(response.Body)
response.Body.Close()
if err != nil {
err = fmt.Errorf("error while reading request body %s", err)
return
}
content = string(body)
return
} else {
err = fmt.Errorf("invalid content-type: %s", contentType)
return
}
return
}
func Http_post(client *Client, request *soap.SoapMessage) (response string, err error) {
httpClient := &http.Client{Transport: client.transport}
req, err := http.NewRequest("POST", client.url, strings.NewReader(request.String()))
if err != nil {
err = fmt.Errorf("impossible to create http request %s", err)
return
}
req.Header.Set("Content-Type", soapXML+";charset=UTF-8")
req.SetBasicAuth(client.username, client.password)
resp, err := httpClient.Do(req)
if err != nil {
err = fmt.Errorf("unknown error %s", err)
return
}
if resp.StatusCode == 200 {
response, err = body(resp)
} else {
body, _ := ioutil.ReadAll(resp.Body)
err = fmt.Errorf("http error: %d - %s", resp.StatusCode, body)
}
return
}

View File

@ -1,20 +0,0 @@
package winrm
import (
"net/http"
)
type Parameters struct {
Timeout string
Locale string
EnvelopeSize int
TransportDecorator func(*http.Transport) http.RoundTripper
}
func DefaultParameters() *Parameters {
return NewParameters("PT60S", "en-US", 153600)
}
func NewParameters(timeout string, locale string, envelopeSize int) *Parameters {
return &Parameters{Timeout: timeout, Locale: locale, EnvelopeSize: envelopeSize}
}

View File

@ -1,22 +0,0 @@
package winrm
import (
"encoding/base64"
"fmt"
)
// Wraps a PowerShell script and prepares it for execution by the winrm client
func Powershell(psCmd string) string {
// 2 byte chars to make PowerShell happy
wideCmd := ""
for _, b := range []byte(psCmd) {
wideCmd += string(b) + "\x00"
}
// Base64 encode the command
input := []uint8(wideCmd)
encodedCmd := base64.StdEncoding.EncodeToString(input)
// Create the powershell.exe command line to execute the script
return fmt.Sprintf("powershell.exe -EncodedCommand %s", encodedCmd)
}

View File

@ -1,116 +0,0 @@
package winrm
import (
"encoding/base64"
"github.com/masterzen/winrm/soap"
"github.com/nu7hatch/gouuid"
)
func genUUID() string {
uuid, _ := uuid.NewV4()
return "uuid:" + uuid.String()
}
func defaultHeaders(message *soap.SoapMessage, url string, params *Parameters) (h *soap.SoapHeader) {
h = message.Header()
h.To(url).ReplyTo("http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous").MaxEnvelopeSize(params.EnvelopeSize).Id(genUUID()).Locale(params.Locale).Timeout(params.Timeout)
return
}
func NewOpenShellRequest(uri string, params *Parameters) (message *soap.SoapMessage) {
if params == nil {
params = DefaultParameters()
}
message = soap.NewMessage()
defaultHeaders(message, uri, params).Action("http://schemas.xmlsoap.org/ws/2004/09/transfer/Create").ResourceURI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd").AddOption(soap.NewHeaderOption("WINRS_NOPROFILE", "FALSE")).AddOption(soap.NewHeaderOption("WINRS_CODEPAGE", "65001")).Build()
body := message.CreateBodyElement("Shell", soap.NS_WIN_SHELL)
input := message.CreateElement(body, "InputStreams", soap.NS_WIN_SHELL)
input.SetContent("stdin")
output := message.CreateElement(body, "OutputStreams", soap.NS_WIN_SHELL)
output.SetContent("stdout stderr")
return
}
func NewDeleteShellRequest(uri string, shellId string, params *Parameters) (message *soap.SoapMessage) {
if params == nil {
params = DefaultParameters()
}
message = soap.NewMessage()
defaultHeaders(message, uri, params).Action("http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete").ShellId(shellId).ResourceURI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd").Build()
message.NewBody()
return
}
func NewExecuteCommandRequest(uri, shellId, command string, arguments []string, params *Parameters) (message *soap.SoapMessage) {
if params == nil {
params = DefaultParameters()
}
message = soap.NewMessage()
defaultHeaders(message, uri, params).Action("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command").ResourceURI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd").ShellId(shellId).AddOption(soap.NewHeaderOption("WINRS_CONSOLEMODE_STDIN", "TRUE")).AddOption(soap.NewHeaderOption("WINRS_SKIP_CMD_SHELL", "FALSE")).Build()
body := message.CreateBodyElement("CommandLine", soap.NS_WIN_SHELL)
// ensure special characters like & don't mangle the request XML
command = "<![CDATA[" + command + "]]>"
commandElement := message.CreateElement(body, "Command", soap.NS_WIN_SHELL)
commandElement.SetContent(command)
for _, arg := range arguments {
arg = "<![CDATA[" + arg + "]]>"
argumentsElement := message.CreateElement(body, "Arguments", soap.NS_WIN_SHELL)
argumentsElement.SetContent(arg)
}
return
}
func NewGetOutputRequest(uri string, shellId string, commandId string, streams string, params *Parameters) (message *soap.SoapMessage) {
if params == nil {
params = DefaultParameters()
}
message = soap.NewMessage()
defaultHeaders(message, uri, params).Action("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive").ResourceURI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd").ShellId(shellId).Build()
receive := message.CreateBodyElement("Receive", soap.NS_WIN_SHELL)
desiredStreams := message.CreateElement(receive, "DesiredStream", soap.NS_WIN_SHELL)
desiredStreams.SetAttr("CommandId", commandId)
desiredStreams.SetContent(streams)
return
}
func NewSendInputRequest(uri string, shellId string, commandId string, input []byte, params *Parameters) (message *soap.SoapMessage) {
if params == nil {
params = DefaultParameters()
}
message = soap.NewMessage()
defaultHeaders(message, uri, params).Action("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Send").ResourceURI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd").ShellId(shellId).Build()
content := base64.StdEncoding.EncodeToString(input)
send := message.CreateBodyElement("Send", soap.NS_WIN_SHELL)
streams := message.CreateElement(send, "Stream", soap.NS_WIN_SHELL)
streams.SetAttr("Name", "stdin")
streams.SetAttr("CommandId", commandId)
streams.SetContent(content)
return
}
func NewSignalRequest(uri string, shellId string, commandId string, params *Parameters) (message *soap.SoapMessage) {
if params == nil {
params = DefaultParameters()
}
message = soap.NewMessage()
defaultHeaders(message, uri, params).Action("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal").ResourceURI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd").ShellId(shellId).Build()
signal := message.CreateBodyElement("Signal", soap.NS_WIN_SHELL)
signal.SetAttr("CommandId", commandId)
code := message.CreateElement(signal, "Code", soap.NS_WIN_SHELL)
code.SetContent("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate")
return
}

View File

@ -1,111 +0,0 @@
package winrm
import (
"encoding/base64"
"fmt"
"github.com/masterzen/winrm/soap"
"github.com/masterzen/xmlpath"
"io"
"strconv"
"strings"
)
func first(node *xmlpath.Node, xpath string) (content string, err error) {
path, err := xmlpath.CompileWithNamespace(xpath, soap.GetAllNamespaces())
if err != nil {
return
}
content, _ = path.String(node)
return
}
func any(node *xmlpath.Node, xpath string) (found bool, err error) {
path, err := xmlpath.CompileWithNamespace(xpath, soap.GetAllNamespaces())
if err != nil {
return
}
found = path.Exists(node)
return
}
func xpath(node *xmlpath.Node, xpath string) (nodes []xmlpath.Node, err error) {
path, err := xmlpath.CompileWithNamespace(xpath, soap.GetAllNamespaces())
if err != nil {
return
}
nodes = make([]xmlpath.Node, 0, 1)
iter := path.Iter(node)
for iter.Next() {
nodes = append(nodes, *(iter.Node()))
}
return
}
func ParseOpenShellResponse(response string) (shellId string, err error) {
doc, err := xmlpath.Parse(strings.NewReader(response))
shellId, err = first(doc, "//w:Selector[@Name='ShellId']")
return
}
func ParseExecuteCommandResponse(response string) (commandId string, err error) {
doc, err := xmlpath.Parse(strings.NewReader(response))
commandId, err = first(doc, "//rsp:CommandId")
return
}
func ParseSlurpOutputErrResponse(response string, stdout io.Writer, stderr io.Writer) (finished bool, exitCode int, err error) {
doc, err := xmlpath.Parse(strings.NewReader(response))
stdouts, _ := xpath(doc, "//rsp:Stream[@Name='stdout']")
for _, node := range stdouts {
content, _ := base64.StdEncoding.DecodeString(node.String())
stdout.Write(content)
}
stderrs, _ := xpath(doc, "//rsp:Stream[@Name='stderr']")
for _, node := range stderrs {
content, _ := base64.StdEncoding.DecodeString(node.String())
stderr.Write(content)
}
ended, _ := any(doc, "//*[@State='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done']")
if ended {
finished = ended
if exitBool, _ := any(doc, "//rsp:ExitCode"); exitBool {
exit, _ := first(doc, "//rsp:ExitCode")
exitCode, _ = strconv.Atoi(exit)
}
} else {
finished = false
}
return
}
func ParseSlurpOutputResponse(response string, stream io.Writer, streamType string) (finished bool, exitCode int, err error) {
doc, err := xmlpath.Parse(strings.NewReader(response))
nodes, _ := xpath(doc, fmt.Sprintf("//rsp:Stream[@Name='%s']", streamType))
for _, node := range nodes {
content, _ := base64.StdEncoding.DecodeString(node.String())
stream.Write(content)
}
ended, _ := any(doc, "//*[@State='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done']")
if ended {
finished = ended
if exitBool, _ := any(doc, "//rsp:ExitCode"); exitBool {
exit, _ := first(doc, "//rsp:ExitCode")
exitCode, _ = strconv.Atoi(exit)
}
} else {
finished = false
}
return
}

View File

@ -1,31 +0,0 @@
package winrm
// Shell is the local view of a WinRM Shell of a given Client
type Shell struct {
client *Client
ShellId string
}
// Execute command on the given Shell, returning either an error or a Command
func (shell *Shell) Execute(command string, arguments ...string) (cmd *Command, err error) {
request := NewExecuteCommandRequest(shell.client.url, shell.ShellId, command, arguments, &shell.client.Parameters)
defer request.Free()
response, err := shell.client.sendRequest(request)
if err == nil {
var commandId string
if commandId, err = ParseExecuteCommandResponse(response); err == nil {
cmd = newCommand(shell, commandId)
}
}
return
}
// Close will terminate this shell. No commands can be issued once the shell is closed.
func (shell *Shell) Close() (err error) {
request := NewDeleteShellRequest(shell.client.url, shell.ShellId, &shell.client.Parameters)
defer request.Free()
_, err = shell.client.sendRequest(request)
return
}

View File

@ -1,185 +0,0 @@
This software is licensed under the LGPLv3, included below.
As a special exception to the GNU Lesser General Public License version 3
("LGPL3"), the copyright holders of this Library give you permission to
convey to a third party a Combined Work that links statically or dynamically
to this Library without providing any Minimal Corresponding Source or
Minimal Application Code as set out in 4d or providing the installation
information set out in section 4e, provided that you comply with the other
provisions of LGPL3 and provided that you meet, for the Application the
terms and conditions of the license(s) which apply to the Application.
Except as stated in this special exception, the provisions of LGPL3 will
continue to comply in full to this Library. If you modify this Library, you
may apply this exception to your version of this Library, but you are not
obliged to do so. If you do not wish to do so, delete this exception
statement from your version. This exception does not (and cannot) modify any
license terms which apply to the Application, with which you must still
comply.
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@ -1,95 +0,0 @@
// Package xmlpath implements a strict subset of the XPath specification for the Go language.
//
// The XPath specification is available at:
//
// http://www.w3.org/TR/xpath
//
// Path expressions supported by this package are in the following format,
// with all components being optional:
//
// /axis-name::node-test[predicate]/axis-name::node-test[predicate]
//
// At the moment, xmlpath is compatible with the XPath specification
// to the following extent:
//
// - All axes are supported ("child", "following-sibling", etc)
// - All abbreviated forms are supported (".", "//", etc)
// - All node types except for namespace are supported
// - Predicates are restricted to [N], [path], and [path=literal] forms
// - Only a single predicate is supported per path step
// - Namespaces are experimentally supported
// - Richer expressions
//
// For example, assuming the following document:
//
// <library>
// <!-- Great book. -->
// <book id="b0836217462" available="true">
// <isbn>0836217462</isbn>
// <title lang="en">Being a Dog Is a Full-Time Job</title>
// <quote>I'd dog paddle the deepest ocean.</quote>
// <author id="CMS">
// <?echo "go rocks"?>
// <name>Charles M Schulz</name>
// <born>1922-11-26</born>
// <dead>2000-02-12</dead>
// </author>
// <character id="PP">
// <name>Peppermint Patty</name>
// <born>1966-08-22</born>
// <qualification>bold, brash and tomboyish</qualification>
// </character>
// <character id="Snoopy">
// <name>Snoopy</name>
// <born>1950-10-04</born>
// <qualification>extroverted beagle</qualification>
// </character>
// </book>
// </library>
//
// The following examples are valid path expressions, and the first
// match has the indicated value:
//
// /library/book/isbn => "0836217462"
// library/*/isbn => "0836217462"
// /library/book/../book/./isbn => "0836217462"
// /library/book/character[2]/name => "Snoopy"
// /library/book/character[born='1950-10-04']/name => "Snoopy"
// /library/book//node()[@id='PP']/name => "Peppermint Patty"
// //book[author/@id='CMS']/title => "Being a Dog Is a Full-Time Job"},
// /library/book/preceding::comment() => " Great book. "
//
// To run an expression, compile it, and then apply the compiled path to any
// number of context nodes, from one or more parsed xml documents:
//
// path := xmlpath.MustCompile("/library/book/isbn")
// root, err := xmlpath.Parse(file)
// if err != nil {
// log.Fatal(err)
// }
// if value, ok := path.String(root); ok {
// fmt.Println("Found:", value)
// }
//
// To use xmlpath with namespaces, it is required to give the supported set of namespace
// when compiling:
//
//
// var namespaces = []xmlpath.Namespace {
// { "s", "http://www.w3.org/2003/05/soap-envelope" },
// { "a", "http://schemas.xmlsoap.org/ws/2004/08/addressing" },
// }
// path, err := xmlpath.CompileWithNamespace("/s:Header/a:To", namespaces)
// if err != nil {
// log.Fatal(err)
// }
// root, err := xmlpath.Parse(file)
// if err != nil {
// log.Fatal(err)
// }
// if value, ok := path.String(root); ok {
// fmt.Println("Found:", value)
// }
//
package xmlpath

View File

@ -1,233 +0,0 @@
package xmlpath
import (
"encoding/xml"
"io"
)
// Node is an item in an xml tree that was compiled to
// be processed via xml paths. A node may represent:
//
// - An element in the xml document (<body>)
// - An attribute of an element in the xml document (href="...")
// - A comment in the xml document (<!--...-->)
// - A processing instruction in the xml document (<?...?>)
// - Some text within the xml document
//
type Node struct {
kind nodeKind
name xml.Name
attr string
text []byte
nodes []Node
pos int
end int
up *Node
down []*Node
}
type nodeKind int
const (
anyNode nodeKind = iota
startNode
endNode
attrNode
textNode
commentNode
procInstNode
)
// String returns the string value of node.
//
// The string value of a node is:
//
// - For element nodes, the concatenation of all text nodes within the element.
// - For text nodes, the text itself.
// - For attribute nodes, the attribute value.
// - For comment nodes, the text within the comment delimiters.
// - For processing instruction nodes, the content of the instruction.
//
func (node *Node) String() string {
if node.kind == attrNode {
return node.attr
}
return string(node.Bytes())
}
// Bytes returns the string value of node as a byte slice.
// See Node.String for a description of what the string value of a node is.
func (node *Node) Bytes() []byte {
if node.kind == attrNode {
return []byte(node.attr)
}
if node.kind != startNode {
return node.text
}
var text []byte
for i := node.pos; i < node.end; i++ {
if node.nodes[i].kind == textNode {
text = append(text, node.nodes[i].text...)
}
}
return text
}
// equals returns whether the string value of node is equal to s,
// without allocating memory.
func (node *Node) equals(s string) bool {
if node.kind == attrNode {
return s == node.attr
}
if node.kind != startNode {
if len(s) != len(node.text) {
return false
}
for i := range s {
if s[i] != node.text[i] {
return false
}
}
return true
}
si := 0
for i := node.pos; i < node.end; i++ {
if node.nodes[i].kind == textNode {
for _, c := range node.nodes[i].text {
if si > len(s) {
return false
}
if s[si] != c {
return false
}
si++
}
}
}
return si == len(s)
}
// Parse reads an xml document from r, parses it, and returns its root node.
func Parse(r io.Reader) (*Node, error) {
return ParseDecoder(xml.NewDecoder(r))
}
// ParseHTML reads an HTML-like document from r, parses it, and returns
// its root node.
func ParseHTML(r io.Reader) (*Node, error) {
d := xml.NewDecoder(r)
d.Strict = false
d.AutoClose = xml.HTMLAutoClose
d.Entity = xml.HTMLEntity
return ParseDecoder(d)
}
// ParseDecoder parses the xml document being decoded by d and returns
// its root node.
func ParseDecoder(d *xml.Decoder) (*Node, error) {
var nodes []Node
var text []byte
// The root node.
nodes = append(nodes, Node{kind: startNode})
for {
t, err := d.Token()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
switch t := t.(type) {
case xml.EndElement:
nodes = append(nodes, Node{
kind: endNode,
})
case xml.StartElement:
nodes = append(nodes, Node{
kind: startNode,
name: t.Name,
})
for _, attr := range t.Attr {
nodes = append(nodes, Node{
kind: attrNode,
name: attr.Name,
attr: attr.Value,
})
}
case xml.CharData:
texti := len(text)
text = append(text, t...)
nodes = append(nodes, Node{
kind: textNode,
text: text[texti : texti+len(t)],
})
case xml.Comment:
texti := len(text)
text = append(text, t...)
nodes = append(nodes, Node{
kind: commentNode,
text: text[texti : texti+len(t)],
})
case xml.ProcInst:
texti := len(text)
text = append(text, t.Inst...)
nodes = append(nodes, Node{
kind: procInstNode,
name: xml.Name{Local: t.Target},
text: text[texti : texti+len(t.Inst)],
})
}
}
// Close the root node.
nodes = append(nodes, Node{kind: endNode})
stack := make([]*Node, 0, len(nodes))
downs := make([]*Node, len(nodes))
downCount := 0
for pos := range nodes {
switch nodes[pos].kind {
case startNode, attrNode, textNode, commentNode, procInstNode:
node := &nodes[pos]
node.nodes = nodes
node.pos = pos
if len(stack) > 0 {
node.up = stack[len(stack)-1]
}
if node.kind == startNode {
stack = append(stack, node)
} else {
node.end = pos + 1
}
case endNode:
node := stack[len(stack)-1]
node.end = pos
stack = stack[:len(stack)-1]
// Compute downs. Doing that here is what enables the
// use of a slice of a contiguous pre-allocated block.
node.down = downs[downCount:downCount]
for i := node.pos + 1; i < node.end; i++ {
if nodes[i].up == node {
switch nodes[i].kind {
case startNode, textNode, commentNode, procInstNode:
node.down = append(node.down, &nodes[i])
downCount++
}
}
}
if len(stack) == 0 {
return node, nil
}
}
}
return nil, io.EOF
}

View File

@ -1,642 +0,0 @@
package xmlpath
import (
"fmt"
"strconv"
"unicode/utf8"
)
// Namespace represents a given XML Namespace
type Namespace struct {
Prefix string
Uri string
}
// Path is a compiled path that can be applied to a context
// node to obtain a matching node set.
// A single Path can be applied concurrently to any number
// of context nodes.
type Path struct {
path string
steps []pathStep
}
// Iter returns an iterator that goes over the list of nodes
// that p matches on the given context.
func (p *Path) Iter(context *Node) *Iter {
iter := Iter{
make([]pathStepState, len(p.steps)),
make([]bool, len(context.nodes)),
}
for i := range p.steps {
iter.state[i].step = &p.steps[i]
}
iter.state[0].init(context)
return &iter
}
// Exists returns whether any nodes match p on the given context.
func (p *Path) Exists(context *Node) bool {
return p.Iter(context).Next()
}
// String returns the string value of the first node matched
// by p on the given context.
//
// See the documentation of Node.String.
func (p *Path) String(context *Node) (s string, ok bool) {
iter := p.Iter(context)
if iter.Next() {
return iter.Node().String(), true
}
return "", false
}
// Bytes returns as a byte slice the string value of the first
// node matched by p on the given context.
//
// See the documentation of Node.String.
func (p *Path) Bytes(node *Node) (b []byte, ok bool) {
iter := p.Iter(node)
if iter.Next() {
return iter.Node().Bytes(), true
}
return nil, false
}
// Iter iterates over node sets.
type Iter struct {
state []pathStepState
seen []bool
}
// Node returns the current node.
// Must only be called after Iter.Next returns true.
func (iter *Iter) Node() *Node {
state := iter.state[len(iter.state)-1]
if state.pos == 0 {
panic("Iter.Node called before Iter.Next")
}
if state.node == nil {
panic("Iter.Node called after Iter.Next false")
}
return state.node
}
// Next iterates to the next node in the set, if any, and
// returns whether there is a node available.
func (iter *Iter) Next() bool {
tip := len(iter.state) - 1
outer:
for {
for !iter.state[tip].next() {
tip--
if tip == -1 {
return false
}
}
for tip < len(iter.state)-1 {
tip++
iter.state[tip].init(iter.state[tip-1].node)
if !iter.state[tip].next() {
tip--
continue outer
}
}
if iter.seen[iter.state[tip].node.pos] {
continue
}
iter.seen[iter.state[tip].node.pos] = true
return true
}
panic("unreachable")
}
type pathStepState struct {
step *pathStep
node *Node
pos int
idx int
aux int
}
func (s *pathStepState) init(node *Node) {
s.node = node
s.pos = 0
s.idx = 0
s.aux = 0
}
func (s *pathStepState) next() bool {
for s._next() {
s.pos++
if s.step.pred == nil {
return true
}
if s.step.pred.bval {
if s.step.pred.path.Exists(s.node) {
return true
}
} else if s.step.pred.path != nil {
iter := s.step.pred.path.Iter(s.node)
for iter.Next() {
if iter.Node().equals(s.step.pred.sval) {
return true
}
}
} else {
if s.step.pred.ival == s.pos {
return true
}
}
}
return false
}
func (s *pathStepState) _next() bool {
if s.node == nil {
return false
}
if s.step.root && s.idx == 0 {
for s.node.up != nil {
s.node = s.node.up
}
}
switch s.step.axis {
case "self":
if s.idx == 0 && s.step.match(s.node) {
s.idx++
return true
}
case "parent":
if s.idx == 0 && s.node.up != nil && s.step.match(s.node.up) {
s.idx++
s.node = s.node.up
return true
}
case "ancestor", "ancestor-or-self":
if s.idx == 0 && s.step.axis == "ancestor-or-self" {
s.idx++
if s.step.match(s.node) {
return true
}
}
for s.node.up != nil {
s.node = s.node.up
s.idx++
if s.step.match(s.node) {
return true
}
}
case "child":
var down []*Node
if s.idx == 0 {
down = s.node.down
} else {
down = s.node.up.down
}
for s.idx < len(down) {
node := down[s.idx]
s.idx++
if s.step.match(node) {
s.node = node
return true
}
}
case "descendant", "descendant-or-self":
if s.idx == 0 {
s.idx = s.node.pos
s.aux = s.node.end
if s.step.axis == "descendant" {
s.idx++
}
}
for s.idx < s.aux {
node := &s.node.nodes[s.idx]
s.idx++
if node.kind == attrNode {
continue
}
if s.step.match(node) {
s.node = node
return true
}
}
case "following":
if s.idx == 0 {
s.idx = s.node.end
}
for s.idx < len(s.node.nodes) {
node := &s.node.nodes[s.idx]
s.idx++
if node.kind == attrNode {
continue
}
if s.step.match(node) {
s.node = node
return true
}
}
case "following-sibling":
var down []*Node
if s.node.up != nil {
down = s.node.up.down
if s.idx == 0 {
for s.idx < len(down) {
node := down[s.idx]
s.idx++
if node == s.node {
break
}
}
}
}
for s.idx < len(down) {
node := down[s.idx]
s.idx++
if s.step.match(node) {
s.node = node
return true
}
}
case "preceding":
if s.idx == 0 {
s.aux = s.node.pos // Detect ancestors.
s.idx = s.node.pos - 1
}
for s.idx >= 0 {
node := &s.node.nodes[s.idx]
s.idx--
if node.kind == attrNode {
continue
}
if node == s.node.nodes[s.aux].up {
s.aux = s.node.nodes[s.aux].up.pos
continue
}
if s.step.match(node) {
s.node = node
return true
}
}
case "preceding-sibling":
var down []*Node
if s.node.up != nil {
down = s.node.up.down
if s.aux == 0 {
s.aux = 1
for s.idx < len(down) {
node := down[s.idx]
s.idx++
if node == s.node {
s.idx--
break
}
}
}
}
for s.idx >= 0 {
node := down[s.idx]
s.idx--
if s.step.match(node) {
s.node = node
return true
}
}
case "attribute":
if s.idx == 0 {
s.idx = s.node.pos + 1
s.aux = s.node.end
}
for s.idx < s.aux {
node := &s.node.nodes[s.idx]
s.idx++
if node.kind != attrNode {
break
}
if s.step.match(node) {
s.node = node
return true
}
}
}
s.node = nil
return false
}
type pathPredicate struct {
path *Path
sval string
ival int
bval bool
}
type pathStep struct {
root bool
axis string
name string
prefix string
uri string
kind nodeKind
pred *pathPredicate
}
func (step *pathStep) match(node *Node) bool {
return node.kind != endNode &&
(step.kind == anyNode || step.kind == node.kind) &&
(step.name == "*" || (node.name.Local == step.name && (node.name.Space != "" && node.name.Space == step.uri || node.name.Space == "")))
}
// MustCompile returns the compiled path, and panics if
// there are any errors.
func MustCompile(path string) *Path {
e, err := Compile(path)
if err != nil {
panic(err)
}
return e
}
// Compile returns the compiled path.
func Compile(path string) (*Path, error) {
c := pathCompiler{path, 0, []Namespace{} }
if path == "" {
return nil, c.errorf("empty path")
}
p, err := c.parsePath()
if err != nil {
return nil, err
}
return p, nil
}
// Compile the path with the knowledge of the given namespaces
func CompileWithNamespace(path string, ns []Namespace) (*Path, error) {
c := pathCompiler{path, 0, ns}
if path == "" {
return nil, c.errorf("empty path")
}
p, err := c.parsePath()
if err != nil {
return nil, err
}
return p, nil
}
type pathCompiler struct {
path string
i int
ns []Namespace
}
func (c *pathCompiler) errorf(format string, args ...interface{}) error {
return fmt.Errorf("compiling xml path %q:%d: %s", c.path, c.i, fmt.Sprintf(format, args...))
}
func (c *pathCompiler) parsePath() (path *Path, err error) {
var steps []pathStep
var start = c.i
for {
step := pathStep{axis: "child"}
if c.i == 0 && c.skipByte('/') {
step.root = true
if len(c.path) == 1 {
step.name = "*"
}
}
if c.peekByte('/') {
step.axis = "descendant-or-self"
step.name = "*"
} else if c.skipByte('@') {
mark := c.i
if !c.skipName() {
return nil, c.errorf("missing name after @")
}
step.axis = "attribute"
step.name = c.path[mark:c.i]
step.kind = attrNode
} else {
mark := c.i
if c.skipName() {
step.name = c.path[mark:c.i]
}
if step.name == "" {
return nil, c.errorf("missing name")
} else if step.name == "*" {
step.kind = startNode
} else if step.name == "." {
step.axis = "self"
step.name = "*"
} else if step.name == ".." {
step.axis = "parent"
step.name = "*"
} else {
if c.skipByte(':') {
if !c.skipByte(':') {
mark = c.i
if c.skipName() {
step.prefix = step.name
step.name = c.path[mark:c.i]
// check prefix
found := false
for _, ns := range c.ns {
if ns.Prefix == step.prefix {
step.uri = ns.Uri
found = true
break
}
}
if !found {
return nil, c.errorf("unknown namespace prefix: %s", step.prefix)
}
} else {
return nil, c.errorf("missing name after namespace prefix")
}
} else {
switch step.name {
case "attribute":
step.kind = attrNode
case "self", "child", "parent":
case "descendant", "descendant-or-self":
case "ancestor", "ancestor-or-self":
case "following", "following-sibling":
case "preceding", "preceding-sibling":
default:
return nil, c.errorf("unsupported axis: %q", step.name)
}
step.axis = step.name
mark = c.i
if !c.skipName() {
return nil, c.errorf("missing name")
}
step.name = c.path[mark:c.i]
}
}
if c.skipByte('(') {
conflict := step.kind != anyNode
switch step.name {
case "node":
// must be anyNode
case "text":
step.kind = textNode
case "comment":
step.kind = commentNode
case "processing-instruction":
step.kind = procInstNode
default:
return nil, c.errorf("unsupported expression: %s()", step.name)
}
if conflict {
return nil, c.errorf("%s() cannot succeed on axis %q", step.name, step.axis)
}
literal, err := c.parseLiteral()
if err == errNoLiteral {
step.name = "*"
} else if err != nil {
return nil, c.errorf("%v", err)
} else if step.kind == procInstNode {
step.name = literal
} else {
return nil, c.errorf("%s() has no arguments", step.name)
}
if !c.skipByte(')') {
return nil, c.errorf("missing )")
}
} else if step.name == "*" && step.kind == anyNode {
step.kind = startNode
}
}
}
if c.skipByte('[') {
step.pred = &pathPredicate{}
if ival, ok := c.parseInt(); ok {
if ival == 0 {
return nil, c.errorf("positions start at 1")
}
step.pred.ival = ival
} else {
path, err := c.parsePath()
if err != nil {
return nil, err
}
if path.path[0] == '-' {
if _, err = strconv.Atoi(path.path); err == nil {
return nil, c.errorf("positions must be positive")
}
}
step.pred.path = path
if c.skipByte('=') {
sval, err := c.parseLiteral()
if err != nil {
return nil, c.errorf("%v", err)
}
step.pred.sval = sval
} else {
step.pred.bval = true
}
}
if !c.skipByte(']') {
return nil, c.errorf("expected ']'")
}
}
steps = append(steps, step)
//fmt.Printf("step: %#v\n", step)
if !c.skipByte('/') {
if (start == 0 || start == c.i) && c.i < len(c.path) {
return nil, c.errorf("unexpected %q", c.path[c.i])
}
return &Path{steps: steps, path: c.path[start:c.i]}, nil
}
}
panic("unreachable")
}
var errNoLiteral = fmt.Errorf("expected a literal string")
func (c *pathCompiler) parseLiteral() (string, error) {
if c.skipByte('"') {
mark := c.i
if !c.skipByteFind('"') {
return "", fmt.Errorf(`missing '"'`)
}
return c.path[mark:c.i-1], nil
}
if c.skipByte('\'') {
mark := c.i
if !c.skipByteFind('\'') {
return "", fmt.Errorf(`missing "'"`)
}
return c.path[mark:c.i-1], nil
}
return "", errNoLiteral
}
func (c *pathCompiler) parseInt() (v int, ok bool) {
mark := c.i
for c.i < len(c.path) && c.path[c.i] >= '0' && c.path[c.i] <= '9' {
v *= 10
v += int(c.path[c.i]) - '0'
c.i++
}
if c.i == mark {
return 0, false
}
return v, true
}
func (c *pathCompiler) skipByte(b byte) bool {
if c.i < len(c.path) && c.path[c.i] == b {
c.i++
return true
}
return false
}
func (c *pathCompiler) skipByteFind(b byte) bool {
for i := c.i; i < len(c.path); i++ {
if c.path[i] == b {
c.i = i+1
return true
}
}
return false
}
func (c *pathCompiler) peekByte(b byte) bool {
return c.i < len(c.path) && c.path[c.i] == b
}
func (c *pathCompiler) skipName() bool {
if c.i >= len(c.path) {
return false
}
if c.path[c.i] == '*' {
c.i++
return true
}
start := c.i
for c.i < len(c.path) && (c.path[c.i] >= utf8.RuneSelf || isNameByte(c.path[c.i])) {
c.i++
}
return c.i > start
}
func isNameByte(c byte) bool {
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c == '.' || c == '-'
}

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2013 Mitchell Hashimoto
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

@ -1,90 +0,0 @@
# FAT Filesystem Library for Go
This library implements the ability to create, read, and write
FAT filesystems using pure Go.
**WARNING:** While the implementation works (to some degree, see the
limitations section below), I highly recommend you **don't** use this
library, since it has many limitations and is generally a terrible
implementation of FAT. For educational purposes, however, this library
may be interesting.
In this library's current state, it is very good for _reading_ FAT
filesystems, and minimally useful for _creating_ FAT filesystems. See
the features and limitations below.
## Features & Limitations
Features:
* Format a brand new FAT filesystem on a file backed device
* Create files and directories
* Traverse filesystem
Limitations:
This library has several limitations. They're easily able to be overcome,
but because I didn't need them for my use case, I didn't bother:
* Files/directories cannot be deleted or renamed.
* Files never shrink in size.
* Deleted file/directory entries are never reclaimed, so fragmentation
grows towards infinity. Eventually, your "disk" will become full even
if you just create and delete a single file.
* There are some serious corruption possibilities in error cases. Cleanup
is not good.
* Incomplete FAT32 implementation (although FAT12 and FAT16 are complete).
## Usage
Here is some example usage where an existing disk image is read and
a file is created in the root directory:
```go
// Assume this file was created already with a FAT filesystem
f, err := os.OpenFile("FLOPPY.dmg", os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
panic(err)
}
defer f.Close()
// BlockDevice backed by a file
device, err := fs.NewFileDisk(f)
if err != nil {
panic(err)
}
filesys, err := fat.New(device)
if err != nil {
panic(err)
}
rootDir, err := filesys.RootDir()
if err != nil {
panic(err)
}
subEntry, err := rootDir.AddFile("HELLO_WORLD")
if err != nil {
panic(err)
}
file, err := subEntry.File()
if err != nil {
panic(err)
}
_, err = io.WriteString(file, "I am the contents of this file.")
if err != nil {
panic(err)
}
```
## Thanks
Thanks to the following resources which helped in the creation of this
library:
* [fat32-lib](https://code.google.com/p/fat32-lib/)
* [File Allocation Table on Wikipedia](http://en.wikipedia.org/wiki/File_Allocation_Table)
* Microsoft FAT filesystem specification

View File

@ -1,22 +0,0 @@
package fs
// A BlockDevice is the raw device that is meant to store a filesystem.
type BlockDevice interface {
// Closes this block device. No more methods may be called on a
// closed device.
Close() error
// Len returns the number of bytes in this block device.
Len() int64
// SectorSize returns the size of a single sector on this device.
SectorSize() int
// ReadAt reads data from the block device from the given
// offset. See io.ReaderAt for more information on this function.
ReadAt(p []byte, off int64) (n int, err error)
// WriteAt writes data to the block device at the given offset.
// See io.WriterAt for more information on this function.
WriteAt(p []byte, off int64) (n int, err error)
}

View File

@ -1,18 +0,0 @@
package fs
// Directory is an entry in a filesystem that stores files.
type Directory interface {
Entry(name string) DirectoryEntry
Entries() []DirectoryEntry
AddDirectory(name string) (DirectoryEntry, error)
AddFile(name string) (DirectoryEntry, error)
}
// DirectoryEntry represents a single entry within a directory,
// which can be either another Directory or a File.
type DirectoryEntry interface {
Name() string
IsDir() bool
Dir() (Directory, error)
File() (File, error)
}

View File

@ -1,347 +0,0 @@
package fat
import (
"encoding/binary"
"errors"
"fmt"
"github.com/mitchellh/go-fs"
"unicode"
)
type MediaType uint8
// The standard value for "fixed", non-removable media, directly
// from the FAT specification.
const MediaFixed MediaType = 0xF8
type BootSectorCommon struct {
OEMName string
BytesPerSector uint16
SectorsPerCluster uint8
ReservedSectorCount uint16
NumFATs uint8
RootEntryCount uint16
TotalSectors uint32
Media MediaType
SectorsPerFat uint32
SectorsPerTrack uint16
NumHeads uint16
}
// DecodeBootSector takes a BlockDevice and decodes the FAT boot sector
// from it.
func DecodeBootSector(device fs.BlockDevice) (*BootSectorCommon, error) {
var sector [512]byte
if _, err := device.ReadAt(sector[:], 0); err != nil {
return nil, err
}
if sector[510] != 0x55 || sector[511] != 0xAA {
return nil, errors.New("corrupt boot sector signature")
}
result := new(BootSectorCommon)
// BS_OEMName
result.OEMName = string(sector[3:11])
// BPB_BytsPerSec
result.BytesPerSector = binary.LittleEndian.Uint16(sector[11:13])
// BPB_SecPerClus
result.SectorsPerCluster = sector[13]
// BPB_RsvdSecCnt
result.ReservedSectorCount = binary.LittleEndian.Uint16(sector[14:16])
// BPB_NumFATs
result.NumFATs = sector[16]
// BPB_RootEntCnt
result.RootEntryCount = binary.LittleEndian.Uint16(sector[17:19])
// BPB_Media
result.Media = MediaType(sector[21])
// BPB_SecPerTrk
result.SectorsPerTrack = binary.LittleEndian.Uint16(sector[24:26])
// BPB_NumHeads
result.NumHeads = binary.LittleEndian.Uint16(sector[26:28])
// BPB_TotSec16 / BPB_TotSec32
result.TotalSectors = uint32(binary.LittleEndian.Uint16(sector[19:21]))
if result.TotalSectors == 0 {
result.TotalSectors = binary.LittleEndian.Uint32(sector[32:36])
}
// BPB_FATSz16 / BPB_FATSz32
result.SectorsPerFat = uint32(binary.LittleEndian.Uint16(sector[22:24]))
if result.SectorsPerFat == 0 {
result.SectorsPerFat = binary.LittleEndian.Uint32(sector[36:40])
}
return result, nil
}
func (b *BootSectorCommon) Bytes() ([]byte, error) {
var sector [512]byte
// BS_jmpBoot
sector[0] = 0xEB
sector[1] = 0x3C
sector[2] = 0x90
// BS_OEMName
if len(b.OEMName) > 8 {
return nil, errors.New("OEMName must be 8 bytes or less")
}
for i, r := range b.OEMName {
if r > unicode.MaxASCII {
return nil, fmt.Errorf("'%s' in OEM name not a valid ASCII char. Must be ASCII.", r)
}
sector[0x3+i] = byte(r)
}
// BPB_BytsPerSec
binary.LittleEndian.PutUint16(sector[11:13], b.BytesPerSector)
// BPB_SecPerClus
sector[13] = uint8(b.SectorsPerCluster)
// BPB_RsvdSecCnt
binary.LittleEndian.PutUint16(sector[14:16], b.ReservedSectorCount)
// BPB_NumFATs
sector[16] = b.NumFATs
// BPB_RootEntCnt
binary.LittleEndian.PutUint16(sector[17:19], b.RootEntryCount)
// BPB_Media
sector[21] = byte(b.Media)
// BPB_SecPerTrk
binary.LittleEndian.PutUint16(sector[24:26], b.SectorsPerTrack)
// BPB_Numheads
binary.LittleEndian.PutUint16(sector[26:28], b.NumHeads)
// BPB_Hiddsec
// sector[28:32] - it is always set to 0 because we don't partition drives yet.
// Important signature of every FAT boot sector
sector[510] = 0x55
sector[511] = 0xAA
return sector[:], nil
}
// BytesPerCluster returns the number of bytes per cluster.
func (b *BootSectorCommon) BytesPerCluster() uint32 {
return uint32(b.SectorsPerCluster) * uint32(b.BytesPerSector)
}
// ClusterOffset returns the offset of the data section of a particular
// cluster.
func (b *BootSectorCommon) ClusterOffset(n int) uint32 {
offset := b.DataOffset()
offset += (uint32(n) - FirstCluster) * b.BytesPerCluster()
return offset
}
// DataOffset returns the offset of the data section of the disk.
func (b *BootSectorCommon) DataOffset() uint32 {
offset := uint32(b.RootDirOffset())
offset += uint32(b.RootEntryCount * DirectoryEntrySize)
return offset
}
// FATOffset returns the offset in bytes for the given index of the FAT
func (b *BootSectorCommon) FATOffset(n int) int {
offset := uint32(b.ReservedSectorCount * b.BytesPerSector)
offset += b.SectorsPerFat * uint32(b.BytesPerSector) * uint32(n)
return int(offset)
}
// Calculates the FAT type that this boot sector represents.
func (b *BootSectorCommon) FATType() FATType {
var rootDirSectors uint32
rootDirSectors = (uint32(b.RootEntryCount) * 32) + (uint32(b.BytesPerSector) - 1)
rootDirSectors /= uint32(b.BytesPerSector)
dataSectors := b.SectorsPerFat * uint32(b.NumFATs)
dataSectors += uint32(b.ReservedSectorCount)
dataSectors += rootDirSectors
dataSectors = b.TotalSectors - dataSectors
countClusters := dataSectors / uint32(b.SectorsPerCluster)
switch {
case countClusters < 4085:
return FAT12
case countClusters < 65525:
return FAT16
default:
return FAT32
}
}
// RootDirOffset returns the byte offset when the root directory
// entries for FAT12/16 filesystems start. NOTE: This is absolutely useless
// for FAT32 because the root directory is just the beginning of the data
// region.
func (b *BootSectorCommon) RootDirOffset() int {
offset := b.FATOffset(0)
offset += int(uint32(b.NumFATs) * b.SectorsPerFat * uint32(b.BytesPerSector))
return offset
}
// BootSectorFat16 is the BootSector for FAT12 and FAT16 filesystems.
// It contains the common fields to all FAT filesystems and also some
// unique.
type BootSectorFat16 struct {
BootSectorCommon
DriveNumber uint8
VolumeID uint32
VolumeLabel string
FileSystemTypeLabel string
}
func (b *BootSectorFat16) Bytes() ([]byte, error) {
sector, err := b.BootSectorCommon.Bytes()
if err != nil {
return nil, err
}
// BPB_TotSec16 AND BPB_TotSec32
if b.TotalSectors < 0x10000 {
binary.LittleEndian.PutUint16(sector[19:21], uint16(b.TotalSectors))
} else {
binary.LittleEndian.PutUint32(sector[32:36], b.TotalSectors)
}
// BPB_FATSz16
if b.SectorsPerFat > 0x10000 {
return nil, fmt.Errorf("SectorsPerFat value too big for non-FAT32: %d", b.SectorsPerFat)
}
binary.LittleEndian.PutUint16(sector[22:24], uint16(b.SectorsPerFat))
// BS_DrvNum
sector[36] = b.DriveNumber
// BS_BootSig
sector[38] = 0x29
// BS_VolID
binary.LittleEndian.PutUint32(sector[39:43], b.VolumeID)
// BS_VolLab
if len(b.VolumeLabel) > 11 {
return nil, errors.New("VolumeLabel must be 11 bytes or less")
}
for i, r := range b.VolumeLabel {
if r > unicode.MaxASCII {
return nil, fmt.Errorf("'%s' in VolumeLabel not a valid ASCII char. Must be ASCII.", r)
}
sector[43+i] = byte(r)
}
// BS_FilSysType
if len(b.FileSystemTypeLabel) > 8 {
return nil, errors.New("FileSystemTypeLabel must be 8 bytes or less")
}
for i, r := range b.FileSystemTypeLabel {
if r > unicode.MaxASCII {
return nil, fmt.Errorf("'%s' in FileSystemTypeLabel not a valid ASCII char. Must be ASCII.", r)
}
sector[54+i] = byte(r)
}
return sector, nil
}
type BootSectorFat32 struct {
BootSectorCommon
RootCluster uint32
FSInfoSector uint16
BackupBootSector uint16
DriveNumber uint8
VolumeID uint32
VolumeLabel string
FileSystemTypeLabel string
}
func (b *BootSectorFat32) Bytes() ([]byte, error) {
sector, err := b.BootSectorCommon.Bytes()
if err != nil {
return nil, err
}
// BPB_RootEntCount - must be 0
sector[17] = 0
sector[18] = 0
// BPB_FATSz32
binary.LittleEndian.PutUint32(sector[36:40], b.SectorsPerFat)
// BPB_ExtFlags - Unused?
// BPB_FSVer. Explicitly set to 0 because that is really important
// to get correct.
sector[42] = 0
sector[43] = 0
// BPB_RootClus
binary.LittleEndian.PutUint32(sector[44:48], b.RootCluster)
// BPB_FSInfo
binary.LittleEndian.PutUint16(sector[48:50], b.FSInfoSector)
// BPB_BkBootSec
binary.LittleEndian.PutUint16(sector[50:52], b.BackupBootSector)
// BS_DrvNum
sector[64] = b.DriveNumber
// BS_BootSig
sector[66] = 0x29
// BS_VolID
binary.LittleEndian.PutUint32(sector[67:71], b.VolumeID)
// BS_VolLab
if len(b.VolumeLabel) > 11 {
return nil, errors.New("VolumeLabel must be 11 bytes or less")
}
for i, r := range b.VolumeLabel {
if r > unicode.MaxASCII {
return nil, fmt.Errorf("'%s' in VolumeLabel not a valid ASCII char. Must be ASCII.", r)
}
sector[71+i] = byte(r)
}
// BS_FilSysType
if len(b.FileSystemTypeLabel) > 8 {
return nil, errors.New("FileSystemTypeLabel must be 8 bytes or less")
}
for i, r := range b.FileSystemTypeLabel {
if r > unicode.MaxASCII {
return nil, fmt.Errorf("'%s' in FileSystemTypeLabel not a valid ASCII char. Must be ASCII.", r)
}
sector[82+i] = byte(r)
}
return sector, nil
}

View File

@ -1,91 +0,0 @@
package fat
import (
"github.com/mitchellh/go-fs"
"io"
"math"
)
type ClusterChain struct {
device fs.BlockDevice
fat *FAT
startCluster uint32
readOffset uint32
writeOffset uint32
}
func (c *ClusterChain) Read(p []byte) (n int, err error) {
bpc := c.fat.bs.BytesPerCluster()
chain := c.fat.Chain(c.startCluster)
dataOffset := uint32(0)
for dataOffset < uint32(len(p)) {
chainIdx := c.readOffset / bpc
if int(chainIdx) >= len(chain) {
err = io.EOF
return
}
clusterOffset := c.fat.bs.ClusterOffset(int(chain[chainIdx]))
clusterOffset += c.readOffset % bpc
dataOffsetEnd := dataOffset + bpc
dataOffsetEnd -= c.readOffset % bpc
dataOffsetEnd = uint32(math.Min(float64(dataOffsetEnd), float64(len(p))))
var nw int
nw, err = c.device.ReadAt(p[dataOffset:dataOffsetEnd], int64(clusterOffset))
if err != nil {
return
}
c.readOffset += uint32(nw)
dataOffset += uint32(nw)
n += nw
}
return
}
// Write will write to the cluster chain, expanding it if necessary.
func (c *ClusterChain) Write(p []byte) (n int, err error) {
bpc := c.fat.bs.BytesPerCluster()
chain := c.fat.Chain(c.startCluster)
chainLength := uint32(len(chain)) * bpc
if chainLength < c.writeOffset+uint32(len(p)) {
// We need to grow the chain
bytesNeeded := (c.writeOffset + uint32(len(p))) - chainLength
clustersNeeded := int(math.Ceil(float64(bytesNeeded) / float64(bpc)))
chain, err = c.fat.ResizeChain(c.startCluster, len(chain)+clustersNeeded)
if err != nil {
return
}
// Write the FAT out
if err = c.fat.WriteToDevice(c.device); err != nil {
return
}
}
dataOffset := uint32(0)
for dataOffset < uint32(len(p)) {
chainIdx := c.writeOffset / bpc
clusterOffset := c.fat.bs.ClusterOffset(int(chain[chainIdx]))
clusterOffset += c.writeOffset % bpc
dataOffsetEnd := dataOffset + bpc
dataOffsetEnd -= c.writeOffset % bpc
dataOffsetEnd = uint32(math.Min(float64(dataOffsetEnd), float64(len(p))))
var nw int
nw, err = c.device.WriteAt(p[dataOffset:dataOffsetEnd], int64(clusterOffset))
if err != nil {
return
}
c.writeOffset += uint32(nw)
dataOffset += uint32(nw)
n += nw
}
return
}

View File

@ -1,278 +0,0 @@
package fat
import (
"fmt"
"github.com/mitchellh/go-fs"
"strings"
"time"
)
// Directory implements fs.Directory and is used to interface with
// a directory on a FAT filesystem.
type Directory struct {
device fs.BlockDevice
dirCluster *DirectoryCluster
fat *FAT
}
// DirectoryEntry implements fs.DirectoryEntry and represents a single
// file/folder within a directory in a FAT filesystem. Note that the
// underlying directory entry data structures on the disk may be more
// than one to accomodate for long filenames.
type DirectoryEntry struct {
dir *Directory
lfnEntries []*DirectoryClusterEntry
entry *DirectoryClusterEntry
name string
}
// DecodeDirectoryEntry takes a list of entries, decodes the next full
// DirectoryEntry, and returns the newly created entry, the remaining
// entries, and an error, if there was one.
func DecodeDirectoryEntry(d *Directory, entries []*DirectoryClusterEntry) (*DirectoryEntry, []*DirectoryClusterEntry, error) {
var lfnEntries []*DirectoryClusterEntry
var entry *DirectoryClusterEntry
var name string
// Skip all the deleted entries
for len(entries) > 0 && entries[0].deleted {
entries = entries[1:]
}
// Skip the volume ID
if len(entries) > 0 && entries[0].IsVolumeId() {
entries = entries[1:]
}
if len(entries) == 0 {
return nil, entries, nil
}
// We have a long entry, so we have to traverse to the point where
// we're done. Also, calculate out the name and such.
if entries[0].IsLong() {
lfnEntries := make([]*DirectoryClusterEntry, 0, 3)
for entries[0].IsLong() {
lfnEntries = append(lfnEntries, entries[0])
entries = entries[1:]
}
var nameBytes []rune
nameBytes = make([]rune, 13*len(lfnEntries))
for i := len(lfnEntries) - 1; i >= 0; i-- {
for _, char := range lfnEntries[i].longName {
nameBytes = append(nameBytes, char)
}
}
name = string(nameBytes)
}
// Get the short entry
entry = entries[0]
entries = entries[1:]
// If the short entry is deleted, ignore everything
if entry.deleted {
return nil, entries, nil
}
if name == "" {
name = strings.TrimSpace(entry.name)
ext := strings.TrimSpace(entry.ext)
if ext != "" {
name = fmt.Sprintf("%s.%s", name, ext)
}
}
result := &DirectoryEntry{
dir: d,
lfnEntries: lfnEntries,
entry: entry,
name: name,
}
return result, entries, nil
}
func (d *DirectoryEntry) Dir() (fs.Directory, error) {
if !d.IsDir() {
panic("not a directory")
}
dirCluster, err := DecodeDirectoryCluster(
d.entry.cluster, d.dir.device, d.dir.fat)
if err != nil {
return nil, err
}
result := &Directory{
device: d.dir.device,
dirCluster: dirCluster,
fat: d.dir.fat,
}
return result, nil
}
func (d *DirectoryEntry) File() (fs.File, error) {
if d.IsDir() {
panic("not a file")
}
result := &File{
chain: &ClusterChain{
device: d.dir.device,
fat: d.dir.fat,
startCluster: d.entry.cluster,
},
dir: d.dir,
entry: d.entry,
}
return result, nil
}
func (d *DirectoryEntry) IsDir() bool {
return (d.entry.attr & AttrDirectory) == AttrDirectory
}
func (d *DirectoryEntry) Name() string {
return d.name
}
func (d *DirectoryEntry) ShortName() string {
if d.entry.name == "." || d.entry.name == ".." {
return d.entry.name
}
return fmt.Sprintf("%s.%s", d.entry.name, d.entry.ext)
}
func (d *Directory) AddDirectory(name string) (fs.DirectoryEntry, error) {
entry, err := d.addEntry(name, AttrDirectory)
if err != nil {
return nil, err
}
// Create the new directory cluster
newDirCluster := NewDirectoryCluster(
entry.entry.cluster, d.dirCluster.startCluster, entry.entry.createTime)
if err := newDirCluster.WriteToDevice(d.device, d.fat); err != nil {
return nil, err
}
return entry, nil
}
func (d *Directory) AddFile(name string) (fs.DirectoryEntry, error) {
entry, err := d.addEntry(name, DirectoryAttr(0))
if err != nil {
return nil, err
}
return entry, nil
}
func (d *Directory) Entries() []fs.DirectoryEntry {
entries := d.dirCluster.entries
result := make([]fs.DirectoryEntry, 0, len(entries)/2)
for len(entries) > 0 {
var entry *DirectoryEntry
entry, entries, _ = DecodeDirectoryEntry(d, entries)
if entry != nil {
result = append(result, entry)
}
}
return result
}
func (d *Directory) Entry(name string) fs.DirectoryEntry {
name = strings.ToUpper(name)
for _, entry := range d.Entries() {
if strings.ToUpper(entry.Name()) == name {
return entry
}
}
return nil
}
func (d *Directory) addEntry(name string, attr DirectoryAttr) (*DirectoryEntry, error) {
name = strings.TrimSpace(name)
entries := d.Entries()
usedNames := make([]string, 0, len(entries))
for _, entry := range entries {
if strings.ToUpper(entry.Name()) == strings.ToUpper(name) {
return nil, fmt.Errorf("name already exists: %s", name)
}
// Add it to the list of used names
dirEntry := entry.(*DirectoryEntry)
usedNames = append(usedNames, dirEntry.ShortName())
}
shortName, err := generateShortName(name, usedNames)
if err != nil {
return nil, err
}
var lfnEntries []*DirectoryClusterEntry
if shortName != strings.ToUpper(name) {
lfnEntries, err = NewLongDirectoryClusterEntry(name, shortName)
if err != nil {
return nil, err
}
}
// Allocate space for a cluster
startCluster, err := d.fat.AllocChain()
if err != nil {
return nil, err
}
createTime := time.Now()
// Create the entry for the short name
shortParts := strings.Split(shortName, ".")
if len(shortParts) == 1 {
shortParts = append(shortParts, "")
}
shortEntry := new(DirectoryClusterEntry)
shortEntry.attr = attr
shortEntry.name = shortParts[0]
shortEntry.ext = shortParts[1]
shortEntry.cluster = startCluster
shortEntry.accessTime = createTime
shortEntry.createTime = createTime
shortEntry.writeTime = createTime
// Write the new FAT out
if err := d.fat.WriteToDevice(d.device); err != nil {
return nil, err
}
// Write the entries out in this directory
if lfnEntries != nil {
d.dirCluster.entries = append(d.dirCluster.entries, lfnEntries...)
}
d.dirCluster.entries = append(d.dirCluster.entries, shortEntry)
if err := d.dirCluster.WriteToDevice(d.device, d.fat); err != nil {
return nil, err
}
newEntry := &DirectoryEntry{
dir: d,
lfnEntries: lfnEntries,
entry: shortEntry,
}
return newEntry, nil
}

View File

@ -1,438 +0,0 @@
package fat
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/mitchellh/go-fs"
"math"
"time"
"unicode/utf16"
)
type DirectoryAttr uint8
const (
AttrReadOnly DirectoryAttr = 0x01
AttrHidden = 0x02
AttrSystem = 0x04
AttrVolumeId = 0x08
AttrDirectory = 0x10
AttrArchive = 0x20
AttrLongName = AttrReadOnly | AttrHidden | AttrSystem | AttrVolumeId
)
// The size in bytes of a single directory entry.
const DirectoryEntrySize = 32
// Mask applied to the ord of the last long entry.
const LastLongEntryMask = 0x40
// DirectoryCluster represents a cluster on the disk that contains
// entries/contents.
type DirectoryCluster struct {
entries []*DirectoryClusterEntry
fat16Root bool
startCluster uint32
}
// DirectoryClusterEntry is a single 32-byte entry that is part of the
// chain of entries in a directory cluster.
type DirectoryClusterEntry struct {
name string
ext string
attr DirectoryAttr
createTime time.Time
accessTime time.Time
writeTime time.Time
cluster uint32
fileSize uint32
deleted bool
longOrd uint8
longName string
longChecksum uint8
}
func DecodeDirectoryCluster(startCluster uint32, device fs.BlockDevice, fat *FAT) (*DirectoryCluster, error) {
bs := fat.bs
chain := fat.Chain(startCluster)
data := make([]byte, uint32(len(chain))*bs.BytesPerCluster())
for i, clusterNumber := range chain {
dataOffset := uint32(i) * bs.BytesPerCluster()
devOffset := int64(bs.ClusterOffset(int(clusterNumber)))
chainData := data[dataOffset : dataOffset+bs.BytesPerCluster()]
if _, err := device.ReadAt(chainData, devOffset); err != nil {
return nil, err
}
}
result, err := decodeDirectoryCluster(data, bs)
if err != nil {
return nil, err
}
result.startCluster = startCluster
return result, nil
}
// DecodeFAT16RootDirectory decodes the FAT16 root directory structure
// from the device.
func DecodeFAT16RootDirectoryCluster(device fs.BlockDevice, bs *BootSectorCommon) (*DirectoryCluster, error) {
data := make([]byte, DirectoryEntrySize*bs.RootEntryCount)
if _, err := device.ReadAt(data, int64(bs.RootDirOffset())); err != nil {
return nil, err
}
result, err := decodeDirectoryCluster(data, bs)
if err != nil {
return nil, err
}
result.fat16Root = true
return result, nil
}
func decodeDirectoryCluster(data []byte, bs *BootSectorCommon) (*DirectoryCluster, error) {
entries := make([]*DirectoryClusterEntry, 0, bs.RootEntryCount)
for i := uint16(0); i < uint16(len(data)/DirectoryEntrySize); i++ {
offset := i * DirectoryEntrySize
entryData := data[offset : offset+DirectoryEntrySize]
if entryData[0] == 0 {
break
}
entry, err := DecodeDirectoryClusterEntry(entryData)
if err != nil {
return nil, err
}
entries = append(entries, entry)
}
result := &DirectoryCluster{
entries: entries,
}
return result, nil
}
func NewDirectoryCluster(start uint32, parent uint32, t time.Time) *DirectoryCluster {
cluster := new(DirectoryCluster)
cluster.startCluster = start
// Create the "." and ".." entries
cluster.entries = []*DirectoryClusterEntry{
&DirectoryClusterEntry{
accessTime: t,
attr: AttrDirectory,
cluster: start,
createTime: t,
name: ".",
writeTime: t,
},
&DirectoryClusterEntry{
accessTime: t,
attr: AttrDirectory,
cluster: parent,
createTime: t,
name: "..",
writeTime: t,
},
}
return cluster
}
// NewFat16RootDirectory creates a new DirectoryCluster that is meant only
// to be the root directory of a FAT12/FAT16 filesystem.
func NewFat16RootDirectoryCluster(bs *BootSectorCommon, label string) (*DirectoryCluster, error) {
if bs.RootEntryCount == 0 {
return nil, errors.New("root entry count is 0 in boot sector")
}
result := &DirectoryCluster{
entries: make([]*DirectoryClusterEntry, 1, bs.RootEntryCount),
}
// Create the volume ID entry
result.entries[0] = &DirectoryClusterEntry{
attr: AttrVolumeId,
name: label,
cluster: 0,
}
return result, nil
}
// Bytes returns the on-disk byte data for this directory structure.
func (d *DirectoryCluster) Bytes() []byte {
result := make([]byte, cap(d.entries)*DirectoryEntrySize)
for i, entry := range d.entries {
offset := i * DirectoryEntrySize
entryBytes := entry.Bytes()
copy(result[offset:offset+DirectoryEntrySize], entryBytes)
}
return result
}
// WriteToDevice writes the cluster to the device.
func (d *DirectoryCluster) WriteToDevice(device fs.BlockDevice, fat *FAT) error {
if d.fat16Root {
// Write the cluster to the FAT16 root directory location
offset := int64(fat.bs.RootDirOffset())
if _, err := device.WriteAt(d.Bytes(), offset); err != nil {
return err
}
} else {
chain := &ClusterChain{
device: device,
fat: fat,
startCluster: d.startCluster,
}
if _, err := chain.Write(d.Bytes()); err != nil {
return err
}
}
return nil
}
// Bytes returns the on-disk byte data for this directory entry.
func (d *DirectoryClusterEntry) Bytes() []byte {
var result [DirectoryEntrySize]byte
if d.longName != "" {
runes := bytes.Runes([]byte(d.longName))
// The name must be zero-terminated then padded with 0xFF
// up to 13 characters
if len(runes) < 13 {
runes = append(runes, 0)
for len(runes) < 13 {
runes = append(runes, 0xFFFF)
}
}
// LDIR_Ord
result[0] = d.longOrd
// LDIR_Name1
for i := 0; i < int(math.Min(float64(len(runes)), 5)); i++ {
offset := 1 + (i * 2)
data := result[offset : offset+2]
binary.LittleEndian.PutUint16(data, uint16(runes[i]))
}
// LDIR_Attr
result[11] = byte(AttrLongName)
// LDIR_Type
result[12] = 0
// LDIR_Chksum
result[13] = d.longChecksum
// LDIR_Name2
for i := 0; i < 6; i++ {
offset := 14 + (i * 2)
data := result[offset : offset+2]
binary.LittleEndian.PutUint16(data, uint16(runes[i+5]))
}
// LDIR_FstClusLO
result[26] = 0
result[27] = 0
// LDIR_Name3
for i := 0; i < 2; i++ {
offset := 28 + (i * 2)
data := result[offset : offset+2]
binary.LittleEndian.PutUint16(data, uint16(runes[i+11]))
}
} else {
// DIR_Name
var simpleName string
if d.name == "." || d.name == ".." {
simpleName = d.name
} else {
simpleName = fmt.Sprintf("%s.%s", d.name, d.ext)
}
copy(result[0:11], shortNameEntryValue(simpleName))
// DIR_Attr
result[11] = byte(d.attr)
// DIR_CrtTime
crtDate, crtTime, crtTenths := encodeDOSTime(d.createTime)
result[13] = crtTenths
binary.LittleEndian.PutUint16(result[14:16], crtTime)
binary.LittleEndian.PutUint16(result[16:18], crtDate)
// DIR_LstAccDate
accDate, _, _ := encodeDOSTime(d.accessTime)
binary.LittleEndian.PutUint16(result[18:20], accDate)
// DIR_FstClusHI
binary.LittleEndian.PutUint16(result[20:22], uint16(d.cluster>>16))
// DIR_WrtTime and DIR_WrtDate
wrtDate, wrtTime, _ := encodeDOSTime(d.writeTime)
binary.LittleEndian.PutUint16(result[22:24], wrtTime)
binary.LittleEndian.PutUint16(result[24:26], wrtDate)
// DIR_FstClusLO
binary.LittleEndian.PutUint16(result[26:28], uint16(d.cluster&0xFFFF))
// DIR_FileSize
binary.LittleEndian.PutUint32(result[28:32], d.fileSize)
}
return result[:]
}
// IsLong returns true if this is a long entry.
func (d *DirectoryClusterEntry) IsLong() bool {
return (d.attr & AttrLongName) == AttrLongName
}
func (d *DirectoryClusterEntry) IsVolumeId() bool {
return (d.attr & AttrVolumeId) == AttrVolumeId
}
// DecodeDirectoryClusterEntry decodes a single directory entry in the
// Directory structure.
func DecodeDirectoryClusterEntry(data []byte) (*DirectoryClusterEntry, error) {
var result DirectoryClusterEntry
// Do the attributes so we can determine if we're dealing with long names
result.attr = DirectoryAttr(data[11])
if (result.attr & AttrLongName) == AttrLongName {
result.longOrd = data[0]
chars := make([]uint16, 13)
for i := 0; i < 5; i++ {
offset := 1 + (i * 2)
chars[i] = binary.LittleEndian.Uint16(data[offset : offset+2])
}
for i := 0; i < 6; i++ {
offset := 14 + (i * 2)
chars[i+5] = binary.LittleEndian.Uint16(data[offset : offset+2])
}
for i := 0; i < 2; i++ {
offset := 28 + (i * 2)
chars[i+11] = binary.LittleEndian.Uint16(data[offset : offset+2])
}
result.longName = string(utf16.Decode(chars))
result.longChecksum = data[13]
} else {
result.deleted = data[0] == 0xE5
// Basic attributes
if data[0] == 0x05 {
data[0] = 0xE5
}
result.name = string(data[0:8])
result.ext = string(data[8:11])
// Creation time
createTimeTenths := data[13]
createTimeWord := binary.LittleEndian.Uint16(data[14:16])
createDateWord := binary.LittleEndian.Uint16(data[16:18])
result.createTime = decodeDOSTime(createDateWord, createTimeWord, createTimeTenths)
// Access time
accessDateWord := binary.LittleEndian.Uint16(data[18:20])
result.accessTime = decodeDOSTime(accessDateWord, 0, 0)
// Write time
writeTimeWord := binary.LittleEndian.Uint16(data[22:24])
writeDateWord := binary.LittleEndian.Uint16(data[24:26])
result.writeTime = decodeDOSTime(writeDateWord, writeTimeWord, 0)
// Cluster
result.cluster = uint32(binary.LittleEndian.Uint16(data[20:22]))
result.cluster <<= 4
result.cluster |= uint32(binary.LittleEndian.Uint16(data[26:28]))
// File size
result.fileSize = binary.LittleEndian.Uint32(data[28:32])
}
return &result, nil
}
// NewLongDirectoryClusterEntry returns the series of directory cluster
// entries that need to be written for a long directory entry. This list
// of entries does NOT contain the short name entry.
func NewLongDirectoryClusterEntry(name string, shortName string) ([]*DirectoryClusterEntry, error) {
// Split up the shortName properly
checksum := checksumShortName(shortNameEntryValue(shortName))
// Calcualte the number of entries we'll actually need to store
// the long name.
numLongEntries := len(name) / 13
if len(name)%13 != 0 {
numLongEntries++
}
entries := make([]*DirectoryClusterEntry, numLongEntries)
for i := 0; i < numLongEntries; i++ {
entries[i] = new(DirectoryClusterEntry)
entry := entries[i]
entry.attr = AttrLongName
entry.longOrd = uint8(numLongEntries - i)
if i == 0 {
entry.longOrd |= LastLongEntryMask
}
// Calculate the offsets of the string for this entry
j := (numLongEntries - i - 1) * 13
k := j + 13
if k > len(name) {
k = len(name)
}
entry.longChecksum = checksum
entry.longName = name[j:k]
}
return entries, nil
}
func decodeDOSTime(date, dosTime uint16, tenths uint8) time.Time {
return time.Date(
1980+int(date>>9),
time.Month((date>>5)&0x0F),
int(date&0x1F),
int(dosTime>>11),
int((dosTime>>5)&0x3F),
int((dosTime&0x1F)*2),
int(tenths)*10*int(time.Millisecond),
time.Local)
}
func encodeDOSTime(t time.Time) (uint16, uint16, uint8) {
var date uint16 = uint16((t.Year() - 1980) << 9)
date |= uint16(t.Month()) << 5
date += uint16(t.Day() & 0xFF)
var time uint16 = uint16(t.Hour() << 11)
time |= uint16(t.Minute() << 5)
time += uint16(t.Second() / 2)
var tenths uint8
// TODO(mitchellh): Do tenths
return date, time, tenths
}

View File

@ -1,268 +0,0 @@
package fat
import (
"errors"
"fmt"
"github.com/mitchellh/go-fs"
"math"
)
// The first cluster that can really hold user data is always 2
const FirstCluster = 2
// FAT is the actual file allocation table data structure that is
// stored on disk to describe the various clusters on the disk.
type FAT struct {
bs *BootSectorCommon
entries []uint32
}
func DecodeFAT(device fs.BlockDevice, bs *BootSectorCommon, n int) (*FAT, error) {
if n > int(bs.NumFATs) {
return nil, fmt.Errorf("FAT #%d greater than total FATs: %d", n, bs.NumFATs)
}
data := make([]byte, bs.SectorsPerFat*uint32(bs.BytesPerSector))
if _, err := device.ReadAt(data, int64(bs.FATOffset(n))); err != nil {
return nil, err
}
result := &FAT{
bs: bs,
entries: make([]uint32, FATEntryCount(bs)),
}
fatType := bs.FATType()
for i := 0; i < int(FATEntryCount(bs)); i++ {
var entryData uint32
switch fatType {
case FAT12:
entryData = fatReadEntry12(data, i)
case FAT16:
entryData = fatReadEntry16(data, i)
default:
entryData = fatReadEntry32(data, i)
}
result.entries[i] = entryData
}
return result, nil
}
// NewFAT creates a new FAT data structure, properly initialized.
func NewFAT(bs *BootSectorCommon) (*FAT, error) {
result := &FAT{
bs: bs,
entries: make([]uint32, FATEntryCount(bs)),
}
// Set the initial two entries according to spec
result.entries[0] = (uint32(bs.Media) & 0xFF) |
(0xFFFFFF00 & result.entryMask())
result.entries[1] = 0xFFFFFFFF & result.entryMask()
return result, nil
}
// Bytes returns the raw bytes for the FAT that should be written to
// the block device.
func (f *FAT) Bytes() []byte {
result := make([]byte, f.bs.SectorsPerFat*uint32(f.bs.BytesPerSector))
for i, entry := range f.entries {
switch f.bs.FATType() {
case FAT12:
f.writeEntry12(result, i, entry)
case FAT16:
f.writeEntry16(result, i, entry)
default:
f.writeEntry32(result, i, entry)
}
}
return result
}
func (f *FAT) AllocChain() (uint32, error) {
return f.allocNew()
}
func (f *FAT) allocNew() (uint32, error) {
dataSize := (f.bs.TotalSectors * uint32(f.bs.BytesPerSector))
dataSize -= f.bs.DataOffset()
clusterCount := dataSize / f.bs.BytesPerCluster()
lastClusterIndex := clusterCount + FirstCluster
var availIdx uint32
found := false
for i := uint32(FirstCluster); i < lastClusterIndex; i++ {
if f.entries[i] == 0 {
availIdx = i
found = true
break
}
}
if !found {
return 0, errors.New("FAT FULL")
}
// Mark that this is now in use
f.entries[availIdx] = 0xFFFFFFFF & f.entryMask()
return availIdx, nil
}
// Chain returns the chain of clusters starting at a certain cluster.
func (f *FAT) Chain(start uint32) []uint32 {
chain := make([]uint32, 0, 2)
cluster := start
for {
chain = append(chain, cluster)
cluster = f.entries[cluster]
if f.isEofCluster(cluster) || cluster == 0 {
break
}
}
return chain
}
// ResizeChain takes a given cluster number and resizes the chain
// to the given length. It returns the new chain of clusters.
func (f *FAT) ResizeChain(start uint32, length int) ([]uint32, error) {
chain := f.Chain(start)
if len(chain) == length {
return chain, nil
}
change := int(math.Abs(float64(length - len(chain))))
if length > len(chain) {
var lastCluster uint32
lastCluster = chain[0]
for i := 1; i < len(chain); i++ {
if f.isEofCluster(f.entries[lastCluster]) {
break
}
lastCluster = chain[i]
}
for i := 0; i < change; i++ {
newCluster, err := f.allocNew()
if err != nil {
return nil, err
}
f.entries[lastCluster] = newCluster
lastCluster = newCluster
}
} else {
panic("making chains smaller not implemented yet")
}
return f.Chain(start), nil
}
func (f *FAT) WriteToDevice(device fs.BlockDevice) error {
fatBytes := f.Bytes()
for i := 0; i < int(f.bs.NumFATs); i++ {
offset := int64(f.bs.FATOffset(i))
if _, err := device.WriteAt(fatBytes, offset); err != nil {
return err
}
}
return nil
}
func (f *FAT) entryMask() uint32 {
switch f.bs.FATType() {
case FAT12:
return 0x0FFF
case FAT16:
return 0xFFFF
default:
return 0x0FFFFFFF
}
}
func (f *FAT) isEofCluster(cluster uint32) bool {
return cluster >= (0xFFFFFF8 & f.entryMask())
}
func (f *FAT) writeEntry12(data []byte, idx int, entry uint32) {
dataIdx := idx + (idx / 2)
data = data[dataIdx : dataIdx+2]
if idx%2 == 1 {
// ODD
data[0] |= byte((entry & 0x0F) << 4)
data[1] = byte((entry >> 4) & 0xFF)
} else {
// Even
data[0] = byte(entry & 0xFF)
data[1] = byte((entry >> 8) & 0x0F)
}
}
func (f *FAT) writeEntry16(data []byte, idx int, entry uint32) {
idx <<= 1
data[idx] = byte(entry & 0xFF)
data[idx+1] = byte((entry >> 8) & 0xFF)
}
func (f *FAT) writeEntry32(data []byte, idx int, entry uint32) {
idx <<= 2
data[idx] = byte(entry & 0xFF)
data[idx+1] = byte((entry >> 8) & 0xFF)
data[idx+2] = byte((entry >> 16) & 0xFF)
data[idx+3] = byte((entry >> 24) & 0xFF)
}
// FATEntryCount returns the number of entries per fat for the given
// boot sector.
func FATEntryCount(bs *BootSectorCommon) uint32 {
// Determine the number of entries that'll go in the FAT.
var entryCount uint32 = bs.SectorsPerFat * uint32(bs.BytesPerSector)
switch bs.FATType() {
case FAT12:
entryCount = uint32((uint64(entryCount) * 8) / 12)
case FAT16:
entryCount /= 2
case FAT32:
entryCount /= 4
default:
panic("impossible fat type")
}
return entryCount
}
func fatReadEntry12(data []byte, idx int) uint32 {
idx += idx / 2
var result uint32 = (uint32(data[idx+1]) << 8) | uint32(data[idx])
if idx%2 == 0 {
return result & 0xFFF
} else {
return result >> 4
}
}
func fatReadEntry16(data []byte, idx int) uint32 {
idx <<= 1
return (uint32(data[idx+1]) << 8) | uint32(data[idx])
}
func fatReadEntry32(data []byte, idx int) uint32 {
idx <<= 2
return (uint32(data[idx+3]) << 24) |
(uint32(data[idx+2]) << 16) |
(uint32(data[idx+1]) << 8) |
uint32(data[idx+0])
}

View File

@ -1,26 +0,0 @@
package fat
type File struct {
chain *ClusterChain
dir *Directory
entry *DirectoryClusterEntry
}
func (f *File) Read(p []byte) (n int, err error) {
return f.chain.Read(p)
}
func (f *File) Write(p []byte) (n int, err error) {
lastByte := f.chain.writeOffset + uint32(len(p))
if lastByte > f.entry.fileSize {
// Increase the file size since we're writing past the end of the file
f.entry.fileSize = lastByte
// Write the entry out
if err := f.dir.dirCluster.WriteToDevice(f.dir.device, f.dir.fat); err != nil {
return 0, err
}
}
return f.chain.Write(p)
}

View File

@ -1,57 +0,0 @@
package fat
import (
"github.com/mitchellh/go-fs"
)
// FileSystem is the implementation of fs.FileSystem that can read a
// FAT filesystem.
type FileSystem struct {
bs *BootSectorCommon
device fs.BlockDevice
fat *FAT
rootDir *DirectoryCluster
}
// New returns a new FileSystem for accessing a previously created
// FAT filesystem.
func New(device fs.BlockDevice) (*FileSystem, error) {
bs, err := DecodeBootSector(device)
if err != nil {
return nil, err
}
fat, err := DecodeFAT(device, bs, 0)
if err != nil {
return nil, err
}
var rootDir *DirectoryCluster
if bs.FATType() == FAT32 {
panic("FAT32 not implemented yet")
} else {
rootDir, err = DecodeFAT16RootDirectoryCluster(device, bs)
if err != nil {
return nil, err
}
}
result := &FileSystem{
bs: bs,
device: device,
fat: fat,
rootDir: rootDir,
}
return result, nil
}
func (f *FileSystem) RootDir() (fs.Directory, error) {
dir := &Directory{
device: f.device,
dirCluster: f.rootDir,
fat: f.fat,
}
return dir, nil
}

View File

@ -1,166 +0,0 @@
package fat
import (
"bytes"
"fmt"
"strings"
)
// checksumShortName returns the checksum for the shortname that is used
// for the long name entries.
func checksumShortName(name string) uint8 {
var sum uint8 = name[0]
for i := uint8(1); i < 11; i++ {
sum = name[i] + (((sum & 1) << 7) + ((sum & 0xFE) >> 1))
}
return sum
}
// generateShortName takes a list of existing short names and a long
// name and generates the next valid short name. This process is done
// according to the MS specification.
func generateShortName(longName string, used []string) (string, error) {
longName = strings.ToUpper(longName)
// Split the string at the final "."
dotIdx := strings.LastIndex(longName, ".")
var ext string
if dotIdx == -1 {
dotIdx = len(longName)
} else {
ext = longName[dotIdx+1 : len(longName)]
}
ext = cleanShortString(ext)
ext = ext[0:len(ext)]
rawName := longName[0:dotIdx]
name := cleanShortString(rawName)
simpleName := fmt.Sprintf("%s.%s", name, ext)
if ext == "" {
simpleName = simpleName[0 : len(simpleName)-1]
}
doSuffix := name != rawName || len(name) > 8
if !doSuffix {
for _, usedSingle := range used {
if strings.ToUpper(usedSingle) == simpleName {
doSuffix = true
break
}
}
}
if doSuffix {
found := false
for i := 1; i < 99999; i++ {
serial := fmt.Sprintf("~%d", i)
nameOffset := 8 - len(serial)
if len(name) < nameOffset {
nameOffset = len(name)
}
serialName := fmt.Sprintf("%s%s", name[0:nameOffset], serial)
simpleName = fmt.Sprintf("%s.%s", serialName, ext)
exists := false
for _, usedSingle := range used {
if strings.ToUpper(usedSingle) == simpleName {
exists = true
break
}
}
if !exists {
found = true
break
}
}
if !found {
return "", fmt.Errorf("could not generate short name for %s", longName)
}
}
return simpleName, nil
}
// shortNameEntryValue returns the proper formatted short name value
// for the directory cluster entry.
func shortNameEntryValue(name string) string {
var shortParts []string
if name == "." || name == ".." {
shortParts = []string{name, ""}
} else {
shortParts = strings.Split(name, ".")
}
if len(shortParts) == 1 {
shortParts = append(shortParts, "")
}
if len(shortParts[0]) < 8 {
var temp bytes.Buffer
temp.WriteString(shortParts[0])
for i := 0; i < 8-len(shortParts[0]); i++ {
temp.WriteRune(' ')
}
shortParts[0] = temp.String()
}
if len(shortParts[1]) < 3 {
var temp bytes.Buffer
temp.WriteString(shortParts[1])
for i := 0; i < 3-len(shortParts[1]); i++ {
temp.WriteRune(' ')
}
shortParts[1] = temp.String()
}
return fmt.Sprintf("%s%s", shortParts[0], shortParts[1])
}
func cleanShortString(v string) string {
var result bytes.Buffer
for _, char := range v {
// We skip these chars
if char == '.' || char == ' ' {
continue
}
if !validShortChar(char) {
char = '_'
}
result.WriteRune(char)
}
return result.String()
}
func validShortChar(char rune) bool {
if char >= 'A' && char <= 'Z' {
return true
}
if char >= '0' && char <= '9' {
return true
}
validShortSymbols := []rune{
'_', '^', '$', '~', '!', '#', '%', '&', '-', '{', '}', '(',
')', '@', '\'', '`',
}
for _, valid := range validShortSymbols {
if char == valid {
return true
}
}
return false
}

View File

@ -1,250 +0,0 @@
package fat
import (
"errors"
"fmt"
"github.com/mitchellh/go-fs"
"time"
)
// SuperFloppyConfig is the configuration for various properties of
// a new super floppy formatted block device. Once this configuration is used
// to format a device, it must not be modified.
type SuperFloppyConfig struct {
// The type of FAT filesystem to use.
FATType FATType
// The label of the drive. Defaults to "NONAME"
Label string
// The OEM name for the FAT filesystem. Defaults to "gofs" if not set.
OEMName string
}
// Formats an fs.BlockDevice with the "super floppy" format according
// to the given configuration. The "super floppy" standard means that the
// device will be formatted so that it does not contain a partition table.
// Instead, the entire device holds a single FAT file system.
func FormatSuperFloppy(device fs.BlockDevice, config *SuperFloppyConfig) error {
formatter := &superFloppyFormatter{
config: config,
device: device,
}
return formatter.format()
}
// An internal struct that helps maintain state and perform calculations
// during a single formatting pass.
type superFloppyFormatter struct {
config *SuperFloppyConfig
device fs.BlockDevice
}
func (f *superFloppyFormatter) format() error {
// First, create the boot sector on the device. Start by configuring
// the common elements of the boot sector.
sectorsPerCluster, err := f.SectorsPerCluster()
if err != nil {
return err
}
bsCommon := BootSectorCommon{
BytesPerSector: uint16(f.device.SectorSize()),
Media: MediaFixed,
NumFATs: 2,
NumHeads: 16,
OEMName: f.config.OEMName,
ReservedSectorCount: f.ReservedSectorCount(),
SectorsPerCluster: sectorsPerCluster,
SectorsPerTrack: 32,
TotalSectors: uint32(f.device.Len() / int64(f.device.SectorSize())),
}
// Next, fill in the FAT-type specific boot sector information
switch f.config.FATType {
case FAT12, FAT16:
// Determine the filesystem type label, standard from the spec sheet
var label string
if f.config.FATType == FAT12 {
label = "FAT12 "
} else {
label = "FAT16 "
}
// Determine the number of root directory entries
if f.device.Len() > 512*5*32 {
bsCommon.RootEntryCount = 512
} else {
bsCommon.RootEntryCount = uint16(f.device.Len() / (5 * 32))
}
bsCommon.SectorsPerFat = f.sectorsPerFat(bsCommon.RootEntryCount, sectorsPerCluster)
bs := &BootSectorFat16{
BootSectorCommon: bsCommon,
FileSystemTypeLabel: label,
VolumeLabel: f.config.Label,
}
// Write the boot sector
bsBytes, err := bs.Bytes()
if err != nil {
return err
}
if _, err := f.device.WriteAt(bsBytes, 0); err != nil {
return err
}
case FAT32:
bsCommon.SectorsPerFat = f.sectorsPerFat(0, sectorsPerCluster)
bs := &BootSectorFat32{
BootSectorCommon: bsCommon,
FileSystemTypeLabel: "FAT32 ",
FSInfoSector: 1,
VolumeID: uint32(time.Now().Unix()),
VolumeLabel: f.config.Label,
}
// Write the boot sector
bsBytes, err := bs.Bytes()
if err != nil {
return err
}
if _, err := f.device.WriteAt(bsBytes, 0); err != nil {
return err
}
// TODO(mitchellh): Create the fsinfo structure
// TODO(mitchellh): write the boot sector copy
default:
return fmt.Errorf("Unknown FAT type: %d", f.config.FATType)
}
// Create the FATs
fat, err := NewFAT(&bsCommon)
if err != nil {
return err
}
// Write the FAT
if err := fat.WriteToDevice(f.device); err != nil {
return err
}
var rootDir *DirectoryCluster
if f.config.FATType == FAT32 {
panic("TODO")
} else {
rootDir, err = NewFat16RootDirectoryCluster(&bsCommon, f.config.Label)
if err != nil {
return err
}
offset := int64(bsCommon.RootDirOffset())
if _, err := f.device.WriteAt(rootDir.Bytes(), offset); err != nil {
return err
}
}
return nil
}
func (f *superFloppyFormatter) ReservedSectorCount() uint16 {
if f.config.FATType == FAT32 {
return 32
} else {
return 1
}
}
func (f *superFloppyFormatter) SectorsPerCluster() (uint8, error) {
if f.config.FATType == FAT12 {
return f.defaultSectorsPerCluster12()
} else if f.config.FATType == FAT16 {
return f.defaultSectorsPerCluster16()
} else {
return f.defaultSectorsPerCluster32()
}
}
func (f *superFloppyFormatter) defaultSectorsPerCluster12() (uint8, error) {
var result uint8 = 1
sectors := f.device.Len() / int64(f.device.SectorSize())
for (sectors / int64(result)) > 4084 {
result *= 2
if int(result)*f.device.SectorSize() > 4096 {
return 0, errors.New("disk too large for FAT12")
}
}
return result, nil
}
func (f *superFloppyFormatter) defaultSectorsPerCluster16() (uint8, error) {
sectors := f.device.Len() / int64(f.device.SectorSize())
if sectors <= 8400 {
return 0, errors.New("disk too small for FAT16")
} else if sectors > 4194304 {
return 0, errors.New("disk too large for FAT16")
}
switch {
case sectors > 2097152:
return 64, nil
case sectors > 1048576:
return 32, nil
case sectors > 524288:
return 16, nil
case sectors > 262144:
return 8, nil
case sectors > 32680:
return 4, nil
default:
return 2, nil
}
}
func (f *superFloppyFormatter) defaultSectorsPerCluster32() (uint8, error) {
sectors := f.device.Len() / int64(f.device.SectorSize())
if sectors <= 66600 {
return 0, errors.New("disk too small for FAT32")
}
switch {
case sectors > 67108864:
return 64, nil
case sectors > 33554432:
return 32, nil
case sectors > 16777216:
return 16, nil
case sectors > 532480:
return 8, nil
default:
return 1, nil
}
}
func (f *superFloppyFormatter) fatCount() uint8 {
return 2
}
func (f *superFloppyFormatter) sectorsPerFat(rootEntCount uint16, sectorsPerCluster uint8) uint32 {
bytesPerSec := f.device.SectorSize()
totalSectors := int(f.device.Len()) / bytesPerSec
rootDirSectors := ((int(rootEntCount) * 32) + (bytesPerSec - 1)) / bytesPerSec
tmp1 := totalSectors - (int(f.ReservedSectorCount()) + rootDirSectors)
tmp2 := (256 * int(sectorsPerCluster)) + int(f.fatCount())
if f.config.FATType == FAT32 {
tmp2 /= 2
}
return uint32((tmp1 + (tmp2 - 1)) / tmp2)
}

Some files were not shown because too many files have changed in this diff Show More