mirror of
https://github.com/appleboy/drone-ssh.git
synced 2026-06-16 14:49:25 +08:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b488f1203 | |||
| 8d0b62974a | |||
| 9b57f85e47 | |||
| 648e3c1321 | |||
| 9ef0a47bc0 | |||
| 530cb1df6e | |||
| 414ebba911 | |||
| ff6e63366c | |||
| 553894abe6 | |||
| 79584d6cfa | |||
| cacb93d984 | |||
| 160519dca6 | |||
| b8149fe18c | |||
| 5e47c61788 | |||
| bd8532afdc | |||
| 44cc1f409f | |||
| b83dae891d | |||
| 27b9f04a04 | |||
| f6128860ab | |||
| 3505455bb7 | |||
| 29018dba3a | |||
| e7fd9e9b89 | |||
| 56b67e1314 | |||
| e79f8934dc | |||
| 2fd7649e83 | |||
| 89fce5b1d3 | |||
| 0b241dd368 | |||
| a92fa054fe | |||
| 3342cdf59a | |||
| aa95c51e7f | |||
| 86a962988d | |||
| 2584920545 | |||
| d128f2fecf | |||
| 83f8486e15 | |||
| d32a2675d5 | |||
| 7ac526845d | |||
| eb33537e3f | |||
| 3a227c8854 | |||
| d3999bf6cc | |||
| f9cc37282c | |||
| 6e431b0c53 | |||
| 3499506089 | |||
| 6c0b475c15 | |||
| 60993a71e2 | |||
| 8bfc58f9d0 | |||
| 7f4cb1c1d0 | |||
| f92f762c9d | |||
| 84cb184039 | |||
| 31c084fd3e |
@@ -0,0 +1,16 @@
|
|||||||
|
local pipeline = import 'pipeline.libsonnet';
|
||||||
|
local name = 'drone-ssh';
|
||||||
|
|
||||||
|
[
|
||||||
|
pipeline.test,
|
||||||
|
pipeline.build(name, 'linux', 'amd64'),
|
||||||
|
pipeline.build(name, 'linux', 'arm64'),
|
||||||
|
pipeline.build(name, 'linux', 'arm'),
|
||||||
|
pipeline.release,
|
||||||
|
pipeline.notifications(depends_on=[
|
||||||
|
'linux-amd64',
|
||||||
|
'linux-arm64',
|
||||||
|
'linux-arm',
|
||||||
|
'release-binary',
|
||||||
|
]),
|
||||||
|
]
|
||||||
+353
-85
@@ -1,97 +1,365 @@
|
|||||||
workspace:
|
---
|
||||||
base: /go/src
|
kind: pipeline
|
||||||
path: github.com/appleboy/drone-ssh
|
name: testing
|
||||||
|
|
||||||
clone:
|
platform:
|
||||||
git:
|
os: linux
|
||||||
image: plugins/git
|
arch: amd64
|
||||||
depth: 50
|
|
||||||
tags: true
|
|
||||||
|
|
||||||
pipeline:
|
steps:
|
||||||
lint:
|
- name: vet
|
||||||
image: appleboy/golang-testing
|
pull: always
|
||||||
pull: true
|
image: golang:1.13
|
||||||
group: golang
|
commands:
|
||||||
commands:
|
- make vet
|
||||||
- make vet
|
volumes:
|
||||||
- make lint
|
- name: gopath
|
||||||
- make test-vendor
|
path: /go
|
||||||
|
|
||||||
linux_amd64:
|
- name: lint
|
||||||
image: appleboy/golang-testing
|
pull: always
|
||||||
pull: true
|
image: golang:1.13
|
||||||
group: golang
|
commands:
|
||||||
commands:
|
- make lint
|
||||||
- make linux_amd64
|
volumes:
|
||||||
|
- name: gopath
|
||||||
|
path: /go
|
||||||
|
|
||||||
linux_arm64:
|
- name: misspell
|
||||||
image: appleboy/golang-testing
|
pull: always
|
||||||
pull: true
|
image: golang:1.13
|
||||||
group: golang
|
commands:
|
||||||
commands:
|
- make misspell-check
|
||||||
- make linux_arm64
|
volumes:
|
||||||
|
- name: gopath
|
||||||
|
path: /go
|
||||||
|
|
||||||
linux_arm:
|
- name: test
|
||||||
image: appleboy/golang-testing
|
pull: always
|
||||||
pull: true
|
image: golang:1.13-alpine
|
||||||
group: golang
|
commands:
|
||||||
commands:
|
- apk add git make curl perl bash build-base zlib-dev ucl-dev
|
||||||
- make linux_arm
|
- make ssh-server
|
||||||
|
- make test
|
||||||
|
- make coverage
|
||||||
|
volumes:
|
||||||
|
- name: gopath
|
||||||
|
path: /go
|
||||||
|
|
||||||
test:
|
- name: codecov
|
||||||
image: appleboy/golang-testing
|
pull: always
|
||||||
pull: true
|
image: robertstettner/drone-codecov
|
||||||
group: golang
|
settings:
|
||||||
commands:
|
token:
|
||||||
- make ssh-server
|
from_secret: codecov_token
|
||||||
- make test
|
|
||||||
- make coverage
|
|
||||||
|
|
||||||
release:
|
volumes:
|
||||||
image: appleboy/golang-testing
|
- name: gopath
|
||||||
pull: true
|
temp: {}
|
||||||
commands:
|
|
||||||
- make release
|
|
||||||
when:
|
|
||||||
event: [ tag ]
|
|
||||||
local: false
|
|
||||||
|
|
||||||
codecov:
|
---
|
||||||
image: robertstettner/drone-codecov
|
kind: pipeline
|
||||||
secrets: [ codecov_token ]
|
name: linux-amd64
|
||||||
|
|
||||||
|
platform:
|
||||||
|
os: linux
|
||||||
|
arch: amd64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build-push
|
||||||
|
pull: always
|
||||||
|
image: golang:1.13
|
||||||
|
commands:
|
||||||
|
- go build -v -ldflags '-X main.build=${DRONE_BUILD_NUMBER}' -a -o release/linux/amd64/drone-ssh
|
||||||
|
environment:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
exclude:
|
||||||
|
- tag
|
||||||
|
|
||||||
|
- name: build-tag
|
||||||
|
pull: always
|
||||||
|
image: golang:1.13
|
||||||
|
commands:
|
||||||
|
- go build -v -ldflags '-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}' -a -o release/linux/amd64/drone-ssh
|
||||||
|
environment:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- tag
|
||||||
|
|
||||||
|
- name: executable
|
||||||
|
pull: always
|
||||||
|
image: golang:1.13
|
||||||
|
commands:
|
||||||
|
- ./release/linux/amd64/drone-ssh --help
|
||||||
|
|
||||||
|
- name: dryrun
|
||||||
|
pull: always
|
||||||
|
image: plugins/docker:linux-amd64
|
||||||
|
settings:
|
||||||
|
cache_from: appleboy/drone-ssh
|
||||||
|
dockerfile: docker/Dockerfile.linux.amd64
|
||||||
|
dry_run: true
|
||||||
|
repo: appleboy/drone-ssh
|
||||||
|
tags: linux-amd64
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
- name: publish
|
||||||
|
pull: always
|
||||||
|
image: plugins/docker:linux-amd64
|
||||||
|
settings:
|
||||||
|
auto_tag: true
|
||||||
|
auto_tag_suffix: linux-amd64
|
||||||
|
cache_from: appleboy/drone-ssh
|
||||||
|
daemon_off: false
|
||||||
|
dockerfile: docker/Dockerfile.linux.amd64
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
|
repo: appleboy/drone-ssh
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
exclude:
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
ref:
|
||||||
|
- refs/heads/master
|
||||||
|
- refs/pull/**
|
||||||
|
- refs/tags/**
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- testing
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
name: linux-arm64
|
||||||
|
|
||||||
|
platform:
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build-push
|
||||||
|
pull: always
|
||||||
|
image: golang:1.13
|
||||||
|
commands:
|
||||||
|
- go build -v -ldflags '-X main.build=${DRONE_BUILD_NUMBER}' -a -o release/linux/arm64/drone-ssh
|
||||||
|
environment:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
exclude:
|
||||||
|
- tag
|
||||||
|
|
||||||
|
- name: build-tag
|
||||||
|
pull: always
|
||||||
|
image: golang:1.13
|
||||||
|
commands:
|
||||||
|
- go build -v -ldflags '-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}' -a -o release/linux/arm64/drone-ssh
|
||||||
|
environment:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- tag
|
||||||
|
|
||||||
|
- name: executable
|
||||||
|
pull: always
|
||||||
|
image: golang:1.13
|
||||||
|
commands:
|
||||||
|
- ./release/linux/arm64/drone-ssh --help
|
||||||
|
|
||||||
|
- name: dryrun
|
||||||
|
pull: always
|
||||||
|
image: plugins/docker:linux-arm64
|
||||||
|
settings:
|
||||||
|
cache_from: appleboy/drone-ssh
|
||||||
|
dockerfile: docker/Dockerfile.linux.arm64
|
||||||
|
dry_run: true
|
||||||
|
repo: appleboy/drone-ssh
|
||||||
|
tags: linux-arm64
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
- name: publish
|
||||||
|
pull: always
|
||||||
|
image: plugins/docker:linux-arm64
|
||||||
|
settings:
|
||||||
|
auto_tag: true
|
||||||
|
auto_tag_suffix: linux-arm64
|
||||||
|
cache_from: appleboy/drone-ssh
|
||||||
|
daemon_off: false
|
||||||
|
dockerfile: docker/Dockerfile.linux.arm64
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
|
repo: appleboy/drone-ssh
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
exclude:
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
ref:
|
||||||
|
- refs/heads/master
|
||||||
|
- refs/pull/**
|
||||||
|
- refs/tags/**
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- testing
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
name: linux-arm
|
||||||
|
|
||||||
|
platform:
|
||||||
|
os: linux
|
||||||
|
arch: arm
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build-push
|
||||||
|
pull: always
|
||||||
|
image: golang:1.13
|
||||||
|
commands:
|
||||||
|
- go build -v -ldflags '-X main.build=${DRONE_BUILD_NUMBER}' -a -o release/linux/arm/drone-ssh
|
||||||
|
environment:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
exclude:
|
||||||
|
- tag
|
||||||
|
|
||||||
|
- name: build-tag
|
||||||
|
pull: always
|
||||||
|
image: golang:1.13
|
||||||
|
commands:
|
||||||
|
- go build -v -ldflags '-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}' -a -o release/linux/arm/drone-ssh
|
||||||
|
environment:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- tag
|
||||||
|
|
||||||
|
- name: executable
|
||||||
|
pull: always
|
||||||
|
image: golang:1.13
|
||||||
|
commands:
|
||||||
|
- ./release/linux/arm/drone-ssh --help
|
||||||
|
|
||||||
|
- name: dryrun
|
||||||
|
pull: always
|
||||||
|
image: plugins/docker:linux-arm
|
||||||
|
settings:
|
||||||
|
cache_from: appleboy/drone-ssh
|
||||||
|
dockerfile: docker/Dockerfile.linux.arm
|
||||||
|
dry_run: true
|
||||||
|
repo: appleboy/drone-ssh
|
||||||
|
tags: linux-arm
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
- name: publish
|
||||||
|
pull: always
|
||||||
|
image: plugins/docker:linux-arm
|
||||||
|
settings:
|
||||||
|
auto_tag: true
|
||||||
|
auto_tag_suffix: linux-arm
|
||||||
|
cache_from: appleboy/drone-ssh
|
||||||
|
daemon_off: false
|
||||||
|
dockerfile: docker/Dockerfile.linux.arm
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
|
repo: appleboy/drone-ssh
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
exclude:
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
ref:
|
||||||
|
- refs/heads/master
|
||||||
|
- refs/pull/**
|
||||||
|
- refs/tags/**
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- testing
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
name: release-binary
|
||||||
|
|
||||||
|
platform:
|
||||||
|
os: linux
|
||||||
|
arch: amd64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build-all-binary
|
||||||
|
pull: always
|
||||||
|
image: golang:1.13
|
||||||
|
commands:
|
||||||
|
- make release
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- tag
|
||||||
|
|
||||||
|
- name: deploy-all-binary
|
||||||
|
pull: always
|
||||||
|
image: plugins/github-release
|
||||||
|
settings:
|
||||||
|
api_key:
|
||||||
|
from_secret: github_release_api_key
|
||||||
files:
|
files:
|
||||||
- .cover/coverage.txt
|
- dist/release/*
|
||||||
when:
|
when:
|
||||||
event: [ push, pull_request ]
|
event:
|
||||||
status: [ success ]
|
- tag
|
||||||
|
|
||||||
publish:
|
trigger:
|
||||||
image: plugins/docker
|
ref:
|
||||||
pull: true
|
- refs/tags/**
|
||||||
repo: ${DRONE_REPO}
|
|
||||||
default_tags: true
|
|
||||||
secrets: [ docker_username, docker_password ]
|
|
||||||
group: release
|
|
||||||
when:
|
|
||||||
event: [ push, tag ]
|
|
||||||
local: false
|
|
||||||
|
|
||||||
release_tag:
|
depends_on:
|
||||||
image: plugins/github-release
|
- testing
|
||||||
pull: true
|
|
||||||
secrets: [ github_release_api_key ]
|
|
||||||
group: release
|
|
||||||
files:
|
|
||||||
- dist/release/*
|
|
||||||
when:
|
|
||||||
event: [ tag ]
|
|
||||||
local: false
|
|
||||||
|
|
||||||
facebook:
|
---
|
||||||
image: appleboy/drone-facebook
|
kind: pipeline
|
||||||
secrets: [ fb_page_token, fb_verify_token ]
|
name: notifications
|
||||||
pull: true
|
|
||||||
to: 1234973386524610
|
platform:
|
||||||
when:
|
os: linux
|
||||||
status: [ changed, failure ]
|
arch: amd64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: manifest
|
||||||
|
pull: always
|
||||||
|
image: plugins/manifest
|
||||||
|
settings:
|
||||||
|
ignore_missing: true
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
|
spec: docker/manifest.tmpl
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
ref:
|
||||||
|
- refs/heads/master
|
||||||
|
- refs/tags/**
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- linux-amd64
|
||||||
|
- linux-arm64
|
||||||
|
- linux-arm
|
||||||
|
- release-binary
|
||||||
|
|
||||||
|
...
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
ignoreGeneratedHeader = false
|
||||||
|
severity = "warning"
|
||||||
|
confidence = 0.8
|
||||||
|
errorCode = 1
|
||||||
|
warningCode = 1
|
||||||
|
|
||||||
|
[rule.blank-imports]
|
||||||
|
[rule.context-as-argument]
|
||||||
|
[rule.context-keys-type]
|
||||||
|
[rule.dot-imports]
|
||||||
|
[rule.error-return]
|
||||||
|
[rule.error-strings]
|
||||||
|
[rule.error-naming]
|
||||||
|
[rule.exported]
|
||||||
|
[rule.if-return]
|
||||||
|
[rule.increment-decrement]
|
||||||
|
[rule.var-naming]
|
||||||
|
[rule.var-declaration]
|
||||||
|
[rule.package-comments]
|
||||||
|
[rule.range]
|
||||||
|
[rule.receiver-naming]
|
||||||
|
[rule.time-naming]
|
||||||
|
[rule.unexported-return]
|
||||||
|
[rule.indent-error-flow]
|
||||||
|
[rule.errorf]
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
date: 2017-01-29T00:00:00+00:00
|
date: 2019-08-04T00:00:00+00:00
|
||||||
title: SSH
|
title: SSH
|
||||||
author: appleboy
|
author: appleboy
|
||||||
tags: [ publish, ssh ]
|
tags: [ deploy, publish, ssh ]
|
||||||
repo: appleboy/drone-ssh
|
repo: appleboy/drone-ssh
|
||||||
logo: term.svg
|
logo: term.svg
|
||||||
image: appleboy/drone-ssh
|
image: appleboy/drone-ssh
|
||||||
@@ -11,9 +11,9 @@ image: appleboy/drone-ssh
|
|||||||
Use the SSH plugin to execute commands on a remote server. The below pipeline configuration demonstrates simple usage:
|
Use the SSH plugin to execute commands on a remote server. The below pipeline configuration demonstrates simple usage:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
pipeline:
|
- name: ssh commands
|
||||||
ssh:
|
image: appleboy/drone-ssh
|
||||||
image: appleboy/drone-ssh
|
settings:
|
||||||
host: foo.com
|
host: foo.com
|
||||||
username: root
|
username: root
|
||||||
password: 1234
|
password: 1234
|
||||||
@@ -26,137 +26,120 @@ pipeline:
|
|||||||
Example configuration in your `.drone.yml` file for multiple hosts:
|
Example configuration in your `.drone.yml` file for multiple hosts:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
pipeline:
|
- name: ssh commands
|
||||||
ssh:
|
|
||||||
image: appleboy/drone-ssh
|
image: appleboy/drone-ssh
|
||||||
host:
|
settings:
|
||||||
+ - foo.com
|
host:
|
||||||
+ - bar.com
|
+ - foo.com
|
||||||
username: root
|
+ - bar.com
|
||||||
password: 1234
|
username: root
|
||||||
port: 22
|
password: 1234
|
||||||
script:
|
port: 22
|
||||||
|
script:
|
||||||
- echo hello
|
- echo hello
|
||||||
- echo world
|
- echo world
|
||||||
```
|
```
|
||||||
|
|
||||||
Example configuration for command timeout (unit: second), default value is 60 seconds:
|
Example configuration for command timeout, default value is 60 seconds:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
pipeline:
|
- name: ssh commands
|
||||||
ssh:
|
|
||||||
image: appleboy/drone-ssh
|
image: appleboy/drone-ssh
|
||||||
host: foo.com
|
settings:
|
||||||
username: root
|
host: foo.com
|
||||||
password: 1234
|
username: root
|
||||||
port: 22
|
password: 1234
|
||||||
+ command_timeout: 120
|
port: 22
|
||||||
script:
|
+ command_timeout: 2m
|
||||||
- echo hello
|
script:
|
||||||
- echo world
|
- echo hello
|
||||||
|
- echo world
|
||||||
```
|
```
|
||||||
|
|
||||||
Example configuration for execute commands on a remote server using `SSHProxyCommand`:
|
Example configuration for execute commands on a remote server using `SSHProxyCommand`:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
pipeline:
|
- name: ssh commands
|
||||||
ssh:
|
|
||||||
image: appleboy/drone-ssh
|
image: appleboy/drone-ssh
|
||||||
host: foo.com
|
settings:
|
||||||
username: root
|
host: foo.com
|
||||||
password: 1234
|
username: root
|
||||||
port: 22
|
password: 1234
|
||||||
script:
|
port: 22
|
||||||
- echo hello
|
script:
|
||||||
- echo world
|
- echo hello
|
||||||
+ proxy_host: 10.130.33.145
|
- echo world
|
||||||
+ proxy_user: ubuntu
|
+ proxy_host: 10.130.33.145
|
||||||
+ proxy_port: 22
|
+ proxy_user: ubuntu
|
||||||
+ proxy_password: 1234
|
+ proxy_port: 22
|
||||||
```
|
+ proxy_password: 1234
|
||||||
|
|
||||||
Example configuration for `master` branch:
|
|
||||||
|
|
||||||
```diff
|
|
||||||
pipeline:
|
|
||||||
ssh:
|
|
||||||
image: appleboy/drone-ssh
|
|
||||||
host: foo.com
|
|
||||||
username: root
|
|
||||||
password: 1234
|
|
||||||
port: 22
|
|
||||||
script:
|
|
||||||
- echo hello
|
|
||||||
- echo world
|
|
||||||
+ when:
|
|
||||||
+ branch: master
|
|
||||||
```
|
|
||||||
|
|
||||||
Example configuration for `tag` event:
|
|
||||||
|
|
||||||
```diff
|
|
||||||
pipeline:
|
|
||||||
ssh:
|
|
||||||
image: appleboy/drone-ssh
|
|
||||||
host: foo.com
|
|
||||||
username: root
|
|
||||||
password: 1234
|
|
||||||
port: 22
|
|
||||||
script:
|
|
||||||
- echo hello
|
|
||||||
- echo world
|
|
||||||
+ when:
|
|
||||||
+ event: tag
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Example configuration using password from secrets:
|
Example configuration using password from secrets:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
pipeline:
|
- name: ssh commands
|
||||||
ssh:
|
|
||||||
image: appleboy/drone-ssh
|
image: appleboy/drone-ssh
|
||||||
host: foo.com
|
settings:
|
||||||
username: root
|
host: foo.com
|
||||||
- password: 1234
|
username: root
|
||||||
port: 22
|
+ password:
|
||||||
+ secrets: [ ssh_password ]
|
+ from_secret: ssh_password
|
||||||
script:
|
port: 22
|
||||||
- echo hello
|
script:
|
||||||
- echo world
|
- echo hello
|
||||||
|
- echo world
|
||||||
```
|
```
|
||||||
|
|
||||||
Example configuration using ssh key from secrets:
|
Example configuration using ssh key from secrets:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
pipeline:
|
- name: ssh commands
|
||||||
ssh:
|
|
||||||
image: appleboy/drone-ssh
|
image: appleboy/drone-ssh
|
||||||
host: foo.com
|
settings:
|
||||||
username: root
|
host: foo.com
|
||||||
port: 22
|
username: root
|
||||||
+ secrets: [ ssh_key ]
|
port: 22
|
||||||
script:
|
+ key:
|
||||||
- echo hello
|
+ from_secret: ssh_key
|
||||||
- echo world
|
script:
|
||||||
|
- echo hello
|
||||||
|
- echo world
|
||||||
```
|
```
|
||||||
|
|
||||||
Example configuration for exporting custom secrets:
|
Example configuration for exporting custom secrets:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
pipeline:
|
- name: ssh commands
|
||||||
ssh:
|
|
||||||
image: appleboy/drone-ssh
|
image: appleboy/drone-ssh
|
||||||
host: foo.com
|
settings:
|
||||||
username: root
|
host: foo.com
|
||||||
password: 1234
|
username: root
|
||||||
port: 22
|
password: 1234
|
||||||
+ secrets: [ aws_access_key_id ]
|
port: 22
|
||||||
+ envs: [ aws_access_key_id ]
|
+ envs:
|
||||||
script:
|
- aws_access_key_id
|
||||||
- export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
|
script:
|
||||||
|
- export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
|
||||||
```
|
```
|
||||||
|
|
||||||
# Secret Reference
|
Example configuration for stoping script after first failure:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- name: ssh commands
|
||||||
|
image: appleboy/drone-ssh
|
||||||
|
settings:
|
||||||
|
host: foo.com
|
||||||
|
username: root
|
||||||
|
password: 1234
|
||||||
|
port: 22
|
||||||
|
+ script_stop: true
|
||||||
|
script:
|
||||||
|
- mkdir abc/def/efg
|
||||||
|
- echo "you can't see the steps."
|
||||||
|
```
|
||||||
|
|
||||||
|
## Secret Reference
|
||||||
|
|
||||||
ssh_username
|
ssh_username
|
||||||
: account for target host user
|
: account for target host user
|
||||||
@@ -176,7 +159,7 @@ proxy_ssh_password
|
|||||||
proxy_ssh_key
|
proxy_ssh_key
|
||||||
: plain text of user private key for proxy server
|
: plain text of user private key for proxy server
|
||||||
|
|
||||||
# Parameter Reference
|
## Parameter Reference
|
||||||
|
|
||||||
host
|
host
|
||||||
: target hostname or IP
|
: target hostname or IP
|
||||||
@@ -202,11 +185,14 @@ envs
|
|||||||
script
|
script
|
||||||
: execute commands on a remote server
|
: execute commands on a remote server
|
||||||
|
|
||||||
|
script_stop
|
||||||
|
: stop script after first failure
|
||||||
|
|
||||||
timeout
|
timeout
|
||||||
: Timeout is the maximum amount of time for the TCP connection to establish.
|
: Timeout is the maximum amount of time for the ssh connection to establish, default is 30 seconds.
|
||||||
|
|
||||||
command_timeout
|
command_timeout
|
||||||
: Command timeout is the maximum amount of time for the execute commands, default is 60 secs.
|
: Command timeout is the maximum amount of time for the execute commands, default is 10 minutes.
|
||||||
|
|
||||||
proxy_host
|
proxy_host
|
||||||
: proxy hostname or IP
|
: proxy hostname or IP
|
||||||
|
|||||||
-16
@@ -1,16 +0,0 @@
|
|||||||
FROM alpine:3.4
|
|
||||||
|
|
||||||
RUN apk update && \
|
|
||||||
apk add -U --no-cache \
|
|
||||||
ca-certificates \
|
|
||||||
openssh-client && \
|
|
||||||
rm -rf /var/cache/apk/*
|
|
||||||
|
|
||||||
LABEL org.label-schema.version=latest
|
|
||||||
LABEL org.label-schema.vcs-url="https://github.com/appleboy/drone-ssh.git"
|
|
||||||
LABEL org.label-schema.name="drone-ssh"
|
|
||||||
LABEL org.label-schema.vendor="Bo-Yi Wu"
|
|
||||||
LABEL org.label-schema.schema-version="1.0"
|
|
||||||
|
|
||||||
ADD release/linux/amd64/drone-ssh /bin/
|
|
||||||
ENTRYPOINT ["/bin/drone-ssh"]
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
FROM armhfbuild/alpine:3.4
|
|
||||||
|
|
||||||
RUN apk update && \
|
|
||||||
apk add \
|
|
||||||
ca-certificates \
|
|
||||||
openssh-client && \
|
|
||||||
rm -rf /var/cache/apk/*
|
|
||||||
|
|
||||||
ADD drone-ssh /bin/
|
|
||||||
ENTRYPOINT ["/bin/drone-ssh"]
|
|
||||||
@@ -1,21 +1,18 @@
|
|||||||
.PHONY: test drone-ssh build fmt vet errcheck lint install update release-dirs release-build release-copy release-check release coverage
|
|
||||||
|
|
||||||
DIST := dist
|
DIST := dist
|
||||||
EXECUTABLE := drone-ssh
|
EXECUTABLE := drone-ssh
|
||||||
|
GOFMT ?= gofmt "-s"
|
||||||
GO ?= go
|
GO ?= go
|
||||||
|
|
||||||
# for dockerhub
|
# for dockerhub
|
||||||
DEPLOY_ACCOUNT := appleboy
|
DEPLOY_ACCOUNT := appleboy
|
||||||
DEPLOY_IMAGE := $(EXECUTABLE)
|
DEPLOY_IMAGE := $(EXECUTABLE)
|
||||||
GOFMT ?= gofmt "-s"
|
|
||||||
|
|
||||||
TARGETS ?= linux darwin windows
|
TARGETS ?= linux darwin windows
|
||||||
PACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/)
|
ARCHS ?= amd64 386
|
||||||
GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*")
|
PACKAGES ?= $(shell $(GO) list ./...)
|
||||||
SOURCES ?= $(shell find . -name "*.go" -type f)
|
SOURCES ?= $(shell find . -name "*.go" -type f)
|
||||||
TAGS ?=
|
TAGS ?=
|
||||||
LDFLAGS ?= -X 'main.Version=$(VERSION)' -X 'main.build=$(NUMBER)'
|
LDFLAGS ?= -X 'main.Version=$(VERSION)'
|
||||||
TMPDIR := $(shell mktemp -d 2>/dev/null || mktemp -d -t 'tempdir')
|
|
||||||
|
|
||||||
ifneq ($(shell uname), Darwin)
|
ifneq ($(shell uname), Darwin)
|
||||||
EXTLDFLAGS = -extldflags "-static" $(null)
|
EXTLDFLAGS = -extldflags "-static" $(null)
|
||||||
@@ -25,75 +22,57 @@ endif
|
|||||||
|
|
||||||
ifneq ($(DRONE_TAG),)
|
ifneq ($(DRONE_TAG),)
|
||||||
VERSION ?= $(DRONE_TAG)
|
VERSION ?= $(DRONE_TAG)
|
||||||
endif
|
|
||||||
|
|
||||||
ifneq ($(DRONE_BUILD_NUMBER),)
|
|
||||||
NUMBER ?= $(DRONE_BUILD_NUMBER)
|
|
||||||
else
|
else
|
||||||
VERSION ?= $(shell git describe --tags --always || git rev-parse --short HEAD)
|
VERSION ?= $(shell git describe --tags --always || git rev-parse --short HEAD)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
$(GOFMT) -w $(SOURCES)
|
||||||
|
|
||||||
|
vet:
|
||||||
|
$(GO) vet $(PACKAGES)
|
||||||
|
|
||||||
|
lint:
|
||||||
|
@hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
$(GO) get -u github.com/mgechev/revive; \
|
||||||
|
fi
|
||||||
|
revive -config .revive.toml ./... || exit 1
|
||||||
|
|
||||||
|
.PHONY: misspell-check
|
||||||
|
misspell-check:
|
||||||
|
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
||||||
|
fi
|
||||||
|
misspell -error $(SOURCES)
|
||||||
|
|
||||||
|
.PHONY: misspell
|
||||||
|
misspell:
|
||||||
|
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
||||||
|
fi
|
||||||
|
misspell -w $(SOURCES)
|
||||||
|
|
||||||
.PHONY: fmt-check
|
.PHONY: fmt-check
|
||||||
fmt-check:
|
fmt-check:
|
||||||
@diff=$$($(GOFMT) -d $(GOFILES)); \
|
@diff=$$($(GOFMT) -d $(SOURCES)); \
|
||||||
if [ -n "$$diff" ]; then \
|
if [ -n "$$diff" ]; then \
|
||||||
echo "Please run 'make fmt' and commit the result:"; \
|
echo "Please run 'make fmt' and commit the result:"; \
|
||||||
echo "$${diff}"; \
|
echo "$${diff}"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
.PHONY: test-vendor
|
|
||||||
test-vendor:
|
|
||||||
@hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
|
||||||
$(GO) get -u github.com/kardianos/govendor; \
|
|
||||||
fi
|
|
||||||
govendor list +unused | tee "$(TMPDIR)/wc-gitea-unused"
|
|
||||||
[ $$(cat "$(TMPDIR)/wc-gitea-unused" | wc -l) -eq 0 ] || echo "Warning: /!\\ Some vendor are not used /!\\"
|
|
||||||
|
|
||||||
govendor list +outside | tee "$(TMPDIR)/wc-gitea-outside"
|
|
||||||
[ $$(cat "$(TMPDIR)/wc-gitea-outside" | wc -l) -eq 0 ] || exit 1
|
|
||||||
|
|
||||||
govendor status || exit 1
|
|
||||||
|
|
||||||
fmt:
|
|
||||||
$(GOFMT) -w $(GOFILES)
|
|
||||||
|
|
||||||
vet:
|
|
||||||
$(GO) vet $(PACKAGES)
|
|
||||||
|
|
||||||
errcheck:
|
|
||||||
@hash errcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
|
||||||
$(GO) get -u github.com/kisielk/errcheck; \
|
|
||||||
fi
|
|
||||||
errcheck $(PACKAGES)
|
|
||||||
|
|
||||||
lint:
|
|
||||||
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
|
||||||
$(GO) get -u github.com/golang/lint/golint; \
|
|
||||||
fi
|
|
||||||
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
|
|
||||||
|
|
||||||
unconvert:
|
|
||||||
@hash unconvert > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
|
||||||
$(GO) get -u github.com/mdempsky/unconvert; \
|
|
||||||
fi
|
|
||||||
for PKG in $(PACKAGES); do unconvert -v $$PKG || exit 1; done;
|
|
||||||
|
|
||||||
test: fmt-check
|
test: fmt-check
|
||||||
for PKG in $(PACKAGES); do $(GO) test -v -cover -coverprofile $$GOPATH/src/$$PKG/coverage.txt $$PKG || exit 1; done;
|
@$(GO) test -v -cover -coverprofile coverage.txt $(PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
|
||||||
|
|
||||||
html:
|
|
||||||
$(GO) tool cover -html=coverage.txt
|
|
||||||
|
|
||||||
install: $(SOURCES)
|
install: $(SOURCES)
|
||||||
$(GO) install -v -tags '$(TAGS)' -ldflags "$(EXTLDFLAGS)-s -w $(LDFLAGS)"
|
$(GO) install -v -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)'
|
||||||
|
|
||||||
build: $(EXECUTABLE)
|
build: $(EXECUTABLE)
|
||||||
|
|
||||||
$(EXECUTABLE): $(SOURCES)
|
$(EXECUTABLE): $(SOURCES)
|
||||||
$(GO) build -v -tags '$(TAGS)' -ldflags "$(EXTLDFLAGS)-s -w $(LDFLAGS)" -o $@
|
$(GO) build -v -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o $@
|
||||||
|
|
||||||
release: release-dirs release-build release-copy release-check
|
release: release-dirs release-build release-copy release-check
|
||||||
|
|
||||||
@@ -101,10 +80,10 @@ release-dirs:
|
|||||||
mkdir -p $(DIST)/binaries $(DIST)/release
|
mkdir -p $(DIST)/binaries $(DIST)/release
|
||||||
|
|
||||||
release-build:
|
release-build:
|
||||||
@hash gox > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@which gox > /dev/null; if [ $$? -ne 0 ]; then \
|
||||||
$(GO) get -u github.com/mitchellh/gox; \
|
$(GO) get -u github.com/mitchellh/gox; \
|
||||||
fi
|
fi
|
||||||
gox -os="$(TARGETS)" -arch="amd64 386" -tags="$(TAGS)" -ldflags="-s -w $(LDFLAGS)" -output="$(DIST)/binaries/$(EXECUTABLE)-$(VERSION)-{{.OS}}-{{.Arch}}"
|
gox -os="$(TARGETS)" -arch="$(ARCHS)" -tags="$(TAGS)" -ldflags="-s -w $(LDFLAGS)" -output="$(DIST)/binaries/$(EXECUTABLE)-$(VERSION)-{{.OS}}-{{.Arch}}"
|
||||||
|
|
||||||
release-copy:
|
release-copy:
|
||||||
$(foreach file,$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*),cp $(file) $(DIST)/release/$(notdir $(file));)
|
$(foreach file,$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*),cp $(file) $(DIST)/release/$(notdir $(file));)
|
||||||
@@ -112,18 +91,23 @@ release-copy:
|
|||||||
release-check:
|
release-check:
|
||||||
cd $(DIST)/release; $(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;)
|
cd $(DIST)/release; $(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;)
|
||||||
|
|
||||||
linux_amd64:
|
build_linux_amd64:
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build -a -tags '$(TAGS)' -ldflags "$(EXTLDFLAGS)-s -w $(LDFLAGS)" -o release/linux/amd64/$(EXECUTABLE)
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/linux/amd64/$(DEPLOY_IMAGE)
|
||||||
|
|
||||||
linux_arm64:
|
build_linux_i386:
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build -a -tags '$(TAGS)' -ldflags "$(EXTLDFLAGS)-s -w $(LDFLAGS)" -o release/linux/arm64/$(EXECUTABLE)
|
CGO_ENABLED=0 GOOS=linux GOARCH=386 $(GO) build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/linux/i386/$(DEPLOY_IMAGE)
|
||||||
|
|
||||||
linux_arm:
|
build_linux_arm64:
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 $(GO) build -a -tags '$(TAGS)' -ldflags "$(EXTLDFLAGS)-s -w $(LDFLAGS)" -o release/arm/amd64/$(EXECUTABLE)
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/linux/arm64/$(DEPLOY_IMAGE)
|
||||||
|
|
||||||
|
build_linux_arm:
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 $(GO) build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/linux/arm/$(DEPLOY_IMAGE)
|
||||||
|
|
||||||
docker_image:
|
docker_image:
|
||||||
docker build -t $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE) .
|
docker build -t $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE) .
|
||||||
|
|
||||||
|
docker: docker_image
|
||||||
|
|
||||||
docker_deploy:
|
docker_deploy:
|
||||||
ifeq ($(tag),)
|
ifeq ($(tag),)
|
||||||
@echo "Usage: make $@ tag=<tag>"
|
@echo "Usage: make $@ tag=<tag>"
|
||||||
@@ -133,29 +117,26 @@ endif
|
|||||||
docker tag $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE):latest $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE):$(tag)
|
docker tag $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE):latest $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE):$(tag)
|
||||||
docker push $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE):$(tag)
|
docker push $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE):$(tag)
|
||||||
|
|
||||||
|
ssh-server:
|
||||||
|
adduser -h /home/drone-scp -s /bin/bash -D -S drone-scp
|
||||||
|
echo drone-scp:1234 | chpasswd
|
||||||
|
mkdir -p /home/drone-scp/.ssh
|
||||||
|
chmod 700 /home/drone-scp/.ssh
|
||||||
|
cat tests/.ssh/id_rsa.pub >> /home/drone-scp/.ssh/authorized_keys
|
||||||
|
cat tests/.ssh/test.pub >> /home/drone-scp/.ssh/authorized_keys
|
||||||
|
chown -R drone-scp /home/drone-scp/.ssh
|
||||||
|
# install ssh and start server
|
||||||
|
apk add --update openssh openrc
|
||||||
|
rm -rf /etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_dsa_key
|
||||||
|
sed -i 's/AllowTcpForwarding no/AllowTcpForwarding yes/g' /etc/ssh/sshd_config
|
||||||
|
./tests/entrypoint.sh /usr/sbin/sshd -D &
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
sed -i '/main.go/d' coverage.txt
|
sed -i '/main.go/d' coverage.txt
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
$(GO) clean -x -i ./...
|
$(GO) clean -x -i ./...
|
||||||
rm -rf coverage.txt $(EXECUTABLE) $(DIST) vendor
|
rm -rf coverage.txt $(EXECUTABLE) $(DIST)
|
||||||
|
|
||||||
ssh-server:
|
|
||||||
adduser -h /home/drone-scp -s /bin/bash -D -S drone-scp
|
|
||||||
passwd -d drone-scp
|
|
||||||
mkdir -p /home/drone-scp/.ssh
|
|
||||||
chmod 700 /home/drone-scp/.ssh
|
|
||||||
cp tests/.ssh/id_rsa.pub /home/drone-scp/.ssh/authorized_keys
|
|
||||||
chown -R drone-scp /home/drone-scp/.ssh
|
|
||||||
# install ssh and start server
|
|
||||||
apk update && apk add openssh openrc
|
|
||||||
rm -rf /etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_dsa_key
|
|
||||||
./tests/entrypoint.sh /usr/sbin/sshd -D &
|
|
||||||
|
|
||||||
# Show source statistics.
|
|
||||||
cloc:
|
|
||||||
@cloc -exclude-dir=vendor,node_modules .
|
|
||||||
.PHONY: cloc
|
|
||||||
|
|
||||||
version:
|
version:
|
||||||
@echo $(VERSION)
|
@echo $(VERSION)
|
||||||
|
|||||||
@@ -1,39 +1,77 @@
|
|||||||
<img src="ssh.png">
|
|
||||||
|
|
||||||
# drone-ssh
|
# drone-ssh
|
||||||
|
|
||||||
[](https://github.com/appleboy/drone-ssh/releases) [](https://godoc.org/github.com/appleboy/drone-ssh) [](http://drone.wu-boy.com/appleboy/drone-ssh) [](https://codecov.io/gh/appleboy/drone-ssh) [](https://goreportcard.com/report/github.com/appleboy/drone-ssh) [](https://hub.docker.com/r/appleboy/drone-ssh/) [](https://microbadger.com/images/appleboy/drone-ssh "Get your own image badge on microbadger.com")
|

|
||||||
|
|
||||||
|
[](https://github.com/appleboy/drone-ssh/releases)
|
||||||
|
[](https://godoc.org/github.com/appleboy/drone-ssh)
|
||||||
|
[](https://cloud.drone.io/appleboy/drone-ssh)
|
||||||
|
[](https://codecov.io/gh/appleboy/drone-ssh)
|
||||||
|
[](https://goreportcard.com/report/github.com/appleboy/drone-ssh)
|
||||||
|
[](https://hub.docker.com/r/appleboy/drone-ssh/)
|
||||||
|
[](https://microbadger.com/images/appleboy/drone-ssh "Get your own image badge on microbadger.com")
|
||||||
|
|
||||||
Drone plugin to execute commands on a remote host through SSH. For the usage
|
Drone plugin to execute commands on a remote host through SSH. For the usage
|
||||||
information and a listing of the available options please take a look at [the docs](http://plugins.drone.io/appleboy/drone-ssh/).
|
information and a listing of the available options please take a look at [the docs](http://plugins.drone.io/appleboy/drone-ssh/).
|
||||||
|
|
||||||
**Note: Please update your image config path to `appleboy/drone-ssh` for drone. `plugins/ssh` is no longer maintained.**
|
**Note: Please update your image config path to `appleboy/drone-ssh` for drone. `plugins/ssh` is no longer maintained.**
|
||||||
|
|
||||||

|

|
||||||
## Build
|
|
||||||
|
|
||||||
Build the binary with the following commands:
|
## Breaking changes
|
||||||
|
|
||||||
|
`v1.5.0`: change command timeout flag to `Duration`. See the following setting:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
pipeline:
|
||||||
|
scp:
|
||||||
|
image: appleboy/drone-scp
|
||||||
|
settings:
|
||||||
|
host:
|
||||||
|
- example1.com
|
||||||
|
- example2.com
|
||||||
|
username: ubuntu
|
||||||
|
password:
|
||||||
|
from_secret: ssh_password
|
||||||
|
port: 22
|
||||||
|
- command_timeout: 120
|
||||||
|
+ command_timeout: 2m
|
||||||
|
script:
|
||||||
|
- echo "Hello World"
|
||||||
```
|
```
|
||||||
go build
|
|
||||||
go test
|
## Build or Download a binary
|
||||||
|
|
||||||
|
The pre-compiled binaries can be downloaded from [release page](https://github.com/appleboy/drone-ssh/releases). Support the following OS type.
|
||||||
|
|
||||||
|
* Windows amd64/386
|
||||||
|
* Linux arm/amd64/386
|
||||||
|
* Darwin amd64/386
|
||||||
|
|
||||||
|
With `Go` installed
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get -u -v github.com/appleboy/drone-ssh
|
||||||
|
```
|
||||||
|
|
||||||
|
or build the binary with the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export GOOS=linux
|
||||||
|
export GOARCH=amd64
|
||||||
|
export CGO_ENABLED=0
|
||||||
|
export GO111MODULE=on
|
||||||
|
|
||||||
|
go test -cover ./...
|
||||||
|
|
||||||
|
go build -v -a -tags netgo -o release/linux/amd64/drone-ssh .
|
||||||
```
|
```
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
Build the docker image with the following commands:
|
Build the docker image with the following commands:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags netgo
|
make docker
|
||||||
docker build -t appleboy/drone-ssh .
|
|
||||||
```
|
|
||||||
|
|
||||||
Please note incorrectly building the image for the correct x64 linux and with
|
|
||||||
GCO disabled will result in an error when running the Docker image:
|
|
||||||
|
|
||||||
```
|
|
||||||
docker: Error response from daemon: Container command
|
|
||||||
'/bin/drone-ssh' not found or does not exist..
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@@ -53,9 +91,9 @@ docker run --rm \
|
|||||||
|
|
||||||
## Mount key from file path
|
## Mount key from file path
|
||||||
|
|
||||||
Please make sure that enable the `trusted` mode in project setting.
|
Please make sure that enable the `trusted` mode in project setting for [drone 0.8 version](https://0-8-0.docs.drone.io/).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Mount private key in `volumes` setting of `.drone.yml` config
|
Mount private key in `volumes` setting of `.drone.yml` config
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
FROM plugins/base:linux-amd64
|
||||||
|
|
||||||
|
LABEL maintainer="Bo-Yi Wu <appleboy.tw@gmail.com>" \
|
||||||
|
org.label-schema.name="Drone SSH" \
|
||||||
|
org.label-schema.vendor="Bo-Yi Wu" \
|
||||||
|
org.label-schema.schema-version="1.0"
|
||||||
|
|
||||||
|
RUN apk add --no-cache ca-certificates && \
|
||||||
|
rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
ADD release/linux/amd64/drone-ssh /bin/
|
||||||
|
ENTRYPOINT ["/bin/drone-ssh"]
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
FROM plugins/base:linux-arm
|
||||||
|
|
||||||
|
LABEL maintainer="Bo-Yi Wu <appleboy.tw@gmail.com>" \
|
||||||
|
org.label-schema.name="Drone SSH" \
|
||||||
|
org.label-schema.vendor="Bo-Yi Wu" \
|
||||||
|
org.label-schema.schema-version="1.0"
|
||||||
|
|
||||||
|
RUN apk add --no-cache ca-certificates && \
|
||||||
|
rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
ADD release/linux/arm/drone-ssh /bin/
|
||||||
|
ENTRYPOINT ["/bin/drone-ssh"]
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
FROM plugins/base:linux-arm64
|
||||||
|
|
||||||
|
LABEL maintainer="Bo-Yi Wu <appleboy.tw@gmail.com>" \
|
||||||
|
org.label-schema.name="Drone SSH" \
|
||||||
|
org.label-schema.vendor="Bo-Yi Wu" \
|
||||||
|
org.label-schema.schema-version="1.0"
|
||||||
|
|
||||||
|
RUN apk add --no-cache ca-certificates && \
|
||||||
|
rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
ADD release/linux/arm64/drone-ssh /bin/
|
||||||
|
ENTRYPOINT ["/bin/drone-ssh"]
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
FROM microsoft/nanoserver:10.0.14393.1884
|
||||||
|
|
||||||
|
LABEL maintainer="Bo-Yi Wu <appleboy.tw@gmail.com>" \
|
||||||
|
org.label-schema.name="Drone SSH" \
|
||||||
|
org.label-schema.vendor="Bo-Yi Wu" \
|
||||||
|
org.label-schema.schema-version="1.0"
|
||||||
|
|
||||||
|
ADD drone-ssh.exe /drone-ssh.exe
|
||||||
|
ENTRYPOINT [ "\\drone-ssh.exe" ]
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
image: appleboy/drone-ssh:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
||||||
|
{{#if build.tags}}
|
||||||
|
tags:
|
||||||
|
{{#each build.tags}}
|
||||||
|
- {{this}}
|
||||||
|
{{/each}}
|
||||||
|
{{/if}}
|
||||||
|
manifests:
|
||||||
|
-
|
||||||
|
image: appleboy/drone-ssh:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
||||||
|
platform:
|
||||||
|
architecture: amd64
|
||||||
|
os: linux
|
||||||
|
-
|
||||||
|
image: appleboy/drone-ssh:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
||||||
|
platform:
|
||||||
|
architecture: arm64
|
||||||
|
os: linux
|
||||||
|
variant: v8
|
||||||
|
-
|
||||||
|
image: appleboy/drone-ssh:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
||||||
|
platform:
|
||||||
|
architecture: arm
|
||||||
|
os: linux
|
||||||
|
variant: v7
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
module github.com/appleboy/drone-ssh
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/appleboy/easyssh-proxy v1.3.0
|
||||||
|
github.com/joho/godotenv v1.3.0
|
||||||
|
github.com/stretchr/testify v1.4.0
|
||||||
|
github.com/urfave/cli v1.22.1
|
||||||
|
)
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/ScaleFT/sshkeys v0.0.0-20181112160850-82451a803681 h1:JS2rl38kZmHgWa0xINSaSYH0Whtvem64/4+Ef0+Y5pE=
|
||||||
|
github.com/ScaleFT/sshkeys v0.0.0-20181112160850-82451a803681/go.mod h1:WfDateMPQ/55dPbZRp5Zxrux5WiEaHsjk9puUhz0KgY=
|
||||||
|
github.com/appleboy/easyssh-proxy v1.3.0 h1:ToH+hZDPWP9/9E58lwxDLJQSHvgGgDAQ9ZVx6x5oofI=
|
||||||
|
github.com/appleboy/easyssh-proxy v1.3.0/go.mod h1:Kk57I3w7OCafOjp5kgZFvxk2fO8Tca5CriBTOsbSbjY=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
|
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/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU=
|
||||||
|
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0=
|
||||||
|
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||||
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
|
||||||
|
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc=
|
||||||
|
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
Before Width: | Height: | Size: 3.9 MiB After Width: | Height: | Size: 3.9 MiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
@@ -1,8 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/appleboy/easyssh-proxy"
|
"github.com/appleboy/easyssh-proxy"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
@@ -10,21 +11,19 @@ import (
|
|||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// build number set at compile-time
|
|
||||||
var build = "0"
|
|
||||||
|
|
||||||
// Version set at compile-time
|
// Version set at compile-time
|
||||||
var Version string
|
var Version string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if Version == "" {
|
// Load env-file if it exists first
|
||||||
Version = fmt.Sprintf("1.3.1+%s", build)
|
if filename, found := os.LookupEnv("PLUGIN_ENV_FILE"); found {
|
||||||
|
_ = godotenv.Load(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
app.Name = "Drone SSH"
|
app.Name = "Drone SSH"
|
||||||
app.Usage = "Executing remote ssh commands"
|
app.Usage = "Executing remote ssh commands"
|
||||||
app.Copyright = "Copyright (c) 2017 Bo-Yi Wu"
|
app.Copyright = "Copyright (c) 2019 Bo-Yi Wu"
|
||||||
app.Authors = []cli.Author{
|
app.Authors = []cli.Author{
|
||||||
{
|
{
|
||||||
Name: "Bo-Yi Wu",
|
Name: "Bo-Yi Wu",
|
||||||
@@ -37,111 +36,123 @@ func main() {
|
|||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "ssh-key",
|
Name: "ssh-key",
|
||||||
Usage: "private ssh key",
|
Usage: "private ssh key",
|
||||||
EnvVar: "PLUGIN_SSH_KEY,PLUGIN_KEY,SSH_KEY",
|
EnvVar: "PLUGIN_SSH_KEY,PLUGIN_KEY,SSH_KEY,KEY,INPUT_KEY",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "ssh-passphrase",
|
||||||
|
Usage: "ssh passphrase",
|
||||||
|
EnvVar: "PLUGIN_SSH_PASSPHRASE,PLUGIN_PASSPHRASE,SSH_PASSPHRASE,PASSPHRASE,INPUT_PASSPHRASE",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "key-path,i",
|
Name: "key-path,i",
|
||||||
Usage: "ssh private key path",
|
Usage: "ssh private key path",
|
||||||
EnvVar: "PLUGIN_KEY_PATH,SSH_KEY_PATH",
|
EnvVar: "PLUGIN_KEY_PATH,SSH_KEY_PATH,INPUT_KEY_PATH",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "username,user,u",
|
Name: "username,user,u",
|
||||||
Usage: "connect as user",
|
Usage: "connect as user",
|
||||||
EnvVar: "PLUGIN_USERNAME,PLUGIN_USER,SSH_USERNAME",
|
EnvVar: "PLUGIN_USERNAME,PLUGIN_USER,SSH_USERNAME,USERNAME,INPUT_USERNAME",
|
||||||
Value: "root",
|
Value: "root",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "password,P",
|
Name: "password,P",
|
||||||
Usage: "user password",
|
Usage: "user password",
|
||||||
EnvVar: "PLUGIN_PASSWORD,SSH_PASSWORD",
|
EnvVar: "PLUGIN_PASSWORD,SSH_PASSWORD,PASSWORD,INPUT_PASSWORD",
|
||||||
},
|
},
|
||||||
cli.StringSliceFlag{
|
cli.StringSliceFlag{
|
||||||
Name: "host,H",
|
Name: "host,H",
|
||||||
Usage: "connect to host",
|
Usage: "connect to host",
|
||||||
EnvVar: "PLUGIN_HOST,SSH_HOST",
|
EnvVar: "PLUGIN_HOST,SSH_HOST,HOST,INPUT_HOST",
|
||||||
},
|
},
|
||||||
cli.IntFlag{
|
cli.IntFlag{
|
||||||
Name: "port,p",
|
Name: "port,p",
|
||||||
Usage: "connect to port",
|
Usage: "connect to port",
|
||||||
EnvVar: "PLUGIN_PORT,SSH_PORT",
|
EnvVar: "PLUGIN_PORT,SSH_PORT,PORT,INPUT_PORT",
|
||||||
Value: 22,
|
Value: 22,
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "sync",
|
Name: "sync",
|
||||||
Usage: "sync mode",
|
Usage: "sync mode",
|
||||||
EnvVar: "PLUGIN_SYNC",
|
EnvVar: "PLUGIN_SYNC,SYNC,INPUT_SYNC",
|
||||||
},
|
},
|
||||||
cli.DurationFlag{
|
cli.DurationFlag{
|
||||||
Name: "timeout,t",
|
Name: "timeout,t",
|
||||||
Usage: "connection timeout",
|
Usage: "connection timeout",
|
||||||
EnvVar: "PLUGIN_TIMEOUT,SSH_TIMEOUT",
|
EnvVar: "PLUGIN_TIMEOUT,SSH_TIMEOUT,TIMEOUT,INPUT_TIMEOUT",
|
||||||
|
Value: 30 * time.Second,
|
||||||
},
|
},
|
||||||
cli.IntFlag{
|
cli.DurationFlag{
|
||||||
Name: "command.timeout,T",
|
Name: "command.timeout,T",
|
||||||
Usage: "command timeout",
|
Usage: "command timeout",
|
||||||
EnvVar: "PLUGIN_COMMAND_TIMEOUT,SSH_COMMAND_TIMEOUT",
|
EnvVar: "PLUGIN_COMMAND_TIMEOUT,SSH_COMMAND_TIMEOUT,COMMAND_TIMEOUT,INPUT_COMMAND_TIMEOUT",
|
||||||
Value: 60,
|
Value: 10 * time.Minute,
|
||||||
},
|
},
|
||||||
cli.StringSliceFlag{
|
cli.StringSliceFlag{
|
||||||
Name: "script,s",
|
Name: "script,s",
|
||||||
Usage: "execute commands",
|
Usage: "execute commands",
|
||||||
EnvVar: "PLUGIN_SCRIPT,SSH_SCRIPT",
|
EnvVar: "PLUGIN_SCRIPT,SSH_SCRIPT,SCRIPT",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "env-file",
|
Name: "script.string",
|
||||||
Usage: "source env file",
|
Usage: "execute single commands for github action",
|
||||||
|
EnvVar: "INPUT_SCRIPT",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "script.stop",
|
||||||
|
Usage: "stop script after first failure",
|
||||||
|
EnvVar: "PLUGIN_SCRIPT_STOP,STOP,INPUT_SCRIPT_STOP",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "proxy.ssh-key",
|
Name: "proxy.ssh-key",
|
||||||
Usage: "private ssh key of proxy",
|
Usage: "private ssh key of proxy",
|
||||||
EnvVar: "PLUGIN_PROXY_SSH_KEY,PLUGIN_PROXY_KEY,PROXY_SSH_KEY",
|
EnvVar: "PLUGIN_PROXY_SSH_KEY,PLUGIN_PROXY_KEY,PROXY_SSH_KEY,INPUT_PROXY_KEY",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "proxy.ssh-passphrase",
|
||||||
|
Usage: "proxy ssh passphrase",
|
||||||
|
EnvVar: "PLUGIN_PROXY_SSH_PASSPHRASE,PLUGIN_PROXY_PASSPHRASE,PROXY_SSH_PASSPHRASE,PROXY_PASSPHRASE,INPUT_PROXY_PASSPHRASE",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "proxy.key-path",
|
Name: "proxy.key-path",
|
||||||
Usage: "ssh private key path of proxy",
|
Usage: "ssh private key path of proxy",
|
||||||
EnvVar: "PLUGIN_PROXY_KEY_PATH,PROXY_SSH_KEY_PATH",
|
EnvVar: "PLUGIN_PROXY_KEY_PATH,PROXY_SSH_KEY_PATH,INPUT_PROXY_KEY_PATH",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "proxy.username",
|
Name: "proxy.username",
|
||||||
Usage: "connect as user of proxy",
|
Usage: "connect as user of proxy",
|
||||||
EnvVar: "PLUGIN_PROXY_USERNAME,PLUGIN_PROXY_USER,PROXY_SSH_USERNAME",
|
EnvVar: "PLUGIN_PROXY_USERNAME,PLUGIN_PROXY_USER,PROXY_SSH_USERNAME,INPUT_PROXY_USERNAME",
|
||||||
Value: "root",
|
Value: "root",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "proxy.password",
|
Name: "proxy.password",
|
||||||
Usage: "user password of proxy",
|
Usage: "user password of proxy",
|
||||||
EnvVar: "PLUGIN_PROXY_PASSWORD,PROXY_SSH_PASSWORD",
|
EnvVar: "PLUGIN_PROXY_PASSWORD,PROXY_SSH_PASSWORD,INPUT_PROXY_PASSWORD",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "proxy.host",
|
Name: "proxy.host",
|
||||||
Usage: "connect to host of proxy",
|
Usage: "connect to host of proxy",
|
||||||
EnvVar: "PLUGIN_PROXY_HOST,PROXY_SSH_HOST",
|
EnvVar: "PLUGIN_PROXY_HOST,PROXY_SSH_HOST,INPUT_PROXY_HOST",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "proxy.port",
|
Name: "proxy.port",
|
||||||
Usage: "connect to port of proxy",
|
Usage: "connect to port of proxy",
|
||||||
EnvVar: "PLUGIN_PROXY_PORT,PROXY_SSH_PORT",
|
EnvVar: "PLUGIN_PROXY_PORT,PROXY_SSH_PORT,INPUT_PROXY_PORT",
|
||||||
Value: "22",
|
Value: "22",
|
||||||
},
|
},
|
||||||
cli.DurationFlag{
|
cli.DurationFlag{
|
||||||
Name: "proxy.timeout",
|
Name: "proxy.timeout",
|
||||||
Usage: "proxy connection timeout",
|
Usage: "proxy connection timeout",
|
||||||
EnvVar: "PLUGIN_PROXY_TIMEOUT,PROXY_SSH_TIMEOUT",
|
EnvVar: "PLUGIN_PROXY_TIMEOUT,PROXY_SSH_TIMEOUT,INPUT_PROXY_TIMEOUT",
|
||||||
},
|
|
||||||
cli.StringSliceFlag{
|
|
||||||
Name: "secrets",
|
|
||||||
Usage: "plugin secret",
|
|
||||||
EnvVar: "PLUGIN_SECRETS",
|
|
||||||
},
|
},
|
||||||
cli.StringSliceFlag{
|
cli.StringSliceFlag{
|
||||||
Name: "envs",
|
Name: "envs",
|
||||||
Usage: "Pass envs",
|
Usage: "pass environment variable to shell script",
|
||||||
EnvVar: "PLUGIN_ENVS",
|
EnvVar: "PLUGIN_ENVS,INPUT_ENVS",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "debug",
|
Name: "debug",
|
||||||
Usage: "debug mode",
|
Usage: "debug mode",
|
||||||
EnvVar: "PLUGIN_DEBUG",
|
EnvVar: "PLUGIN_DEBUG,DEBUG,INPUT_DEBUG",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,41 +190,43 @@ REPOSITORY:
|
|||||||
`
|
`
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
if err := app.Run(os.Args); err != nil {
|
||||||
fmt.Println("drone-ssh error: ", err)
|
log.Fatal(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(c *cli.Context) error {
|
func run(c *cli.Context) error {
|
||||||
if c.String("env-file") != "" {
|
scripts := c.StringSlice("script")
|
||||||
_ = godotenv.Load(c.String("env-file"))
|
if s := c.String("script.string"); s != "" {
|
||||||
|
scripts = append(scripts, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
Config: Config{
|
Config: Config{
|
||||||
Key: c.String("ssh-key"),
|
Key: c.String("ssh-key"),
|
||||||
KeyPath: c.String("key-path"),
|
KeyPath: c.String("key-path"),
|
||||||
UserName: c.String("user"),
|
Username: c.String("user"),
|
||||||
Password: c.String("password"),
|
Password: c.String("password"),
|
||||||
|
Passphrase: c.String("ssh-passphrase"),
|
||||||
Host: c.StringSlice("host"),
|
Host: c.StringSlice("host"),
|
||||||
Port: c.Int("port"),
|
Port: c.Int("port"),
|
||||||
Timeout: c.Duration("timeout"),
|
Timeout: c.Duration("timeout"),
|
||||||
CommandTimeout: c.Int("command.timeout"),
|
CommandTimeout: c.Duration("command.timeout"),
|
||||||
Script: c.StringSlice("script"),
|
Script: scripts,
|
||||||
Secrets: c.StringSlice("secrets"),
|
ScriptStop: c.Bool("script.stop"),
|
||||||
Envs: c.StringSlice("envs"),
|
Envs: c.StringSlice("envs"),
|
||||||
Debug: c.Bool("debug"),
|
Debug: c.Bool("debug"),
|
||||||
Sync: c.Bool("sync"),
|
Sync: c.Bool("sync"),
|
||||||
Proxy: easyssh.DefaultConfig{
|
Proxy: easyssh.DefaultConfig{
|
||||||
Key: c.String("proxy.ssh-key"),
|
Key: c.String("proxy.ssh-key"),
|
||||||
KeyPath: c.String("proxy.key-path"),
|
KeyPath: c.String("proxy.key-path"),
|
||||||
User: c.String("proxy.username"),
|
User: c.String("proxy.username"),
|
||||||
Password: c.String("proxy.password"),
|
Password: c.String("proxy.password"),
|
||||||
Server: c.String("proxy.host"),
|
Passphrase: c.String("proxy.ssh-passphrase"),
|
||||||
Port: c.String("proxy.port"),
|
Server: c.String("proxy.host"),
|
||||||
Timeout: c.Duration("proxy.timeout"),
|
Port: c.String("proxy.port"),
|
||||||
|
Timeout: c.Duration("proxy.timeout"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Writer: os.Stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
return plugin.Exec()
|
return plugin.Exec()
|
||||||
|
|||||||
@@ -0,0 +1,256 @@
|
|||||||
|
{
|
||||||
|
test:: {
|
||||||
|
kind: 'pipeline',
|
||||||
|
name: 'testing',
|
||||||
|
platform: {
|
||||||
|
os: 'linux',
|
||||||
|
arch: 'amd64',
|
||||||
|
},
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
name: 'vet',
|
||||||
|
image: 'golang:1.13',
|
||||||
|
pull: 'always',
|
||||||
|
commands: [
|
||||||
|
'make vet',
|
||||||
|
],
|
||||||
|
volumes: [
|
||||||
|
{
|
||||||
|
name: 'gopath',
|
||||||
|
path: '/go',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lint',
|
||||||
|
image: 'golang:1.13',
|
||||||
|
pull: 'always',
|
||||||
|
commands: [
|
||||||
|
'make lint',
|
||||||
|
],
|
||||||
|
volumes: [
|
||||||
|
{
|
||||||
|
name: 'gopath',
|
||||||
|
path: '/go',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'misspell',
|
||||||
|
image: 'golang:1.13',
|
||||||
|
pull: 'always',
|
||||||
|
commands: [
|
||||||
|
'make misspell-check',
|
||||||
|
],
|
||||||
|
volumes: [
|
||||||
|
{
|
||||||
|
name: 'gopath',
|
||||||
|
path: '/go',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'test',
|
||||||
|
image: 'golang:1.13-alpine',
|
||||||
|
pull: 'always',
|
||||||
|
commands: [
|
||||||
|
'apk add git make curl perl bash build-base zlib-dev ucl-dev',
|
||||||
|
'make ssh-server',
|
||||||
|
'make test',
|
||||||
|
'make coverage',
|
||||||
|
],
|
||||||
|
volumes: [
|
||||||
|
{
|
||||||
|
name: 'gopath',
|
||||||
|
path: '/go',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'codecov',
|
||||||
|
image: 'robertstettner/drone-codecov',
|
||||||
|
pull: 'always',
|
||||||
|
settings: {
|
||||||
|
token: { 'from_secret': 'codecov_token' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
volumes: [
|
||||||
|
{
|
||||||
|
name: 'gopath',
|
||||||
|
temp: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
build(name, os='linux', arch='amd64'):: {
|
||||||
|
kind: 'pipeline',
|
||||||
|
name: os + '-' + arch,
|
||||||
|
platform: {
|
||||||
|
os: os,
|
||||||
|
arch: arch,
|
||||||
|
},
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
name: 'build-push',
|
||||||
|
image: 'golang:1.13',
|
||||||
|
pull: 'always',
|
||||||
|
environment: {
|
||||||
|
CGO_ENABLED: '0',
|
||||||
|
},
|
||||||
|
commands: [
|
||||||
|
'go build -v -ldflags \'-X main.build=${DRONE_BUILD_NUMBER}\' -a -o release/' + os + '/' + arch + '/' + name,
|
||||||
|
],
|
||||||
|
when: {
|
||||||
|
event: {
|
||||||
|
exclude: [ 'tag' ],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'build-tag',
|
||||||
|
image: 'golang:1.13',
|
||||||
|
pull: 'always',
|
||||||
|
environment: {
|
||||||
|
CGO_ENABLED: '0',
|
||||||
|
},
|
||||||
|
commands: [
|
||||||
|
'go build -v -ldflags \'-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}\' -a -o release/' + os + '/' + arch + '/' + name,
|
||||||
|
],
|
||||||
|
when: {
|
||||||
|
event: [ 'tag' ],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'executable',
|
||||||
|
image: 'golang:1.13',
|
||||||
|
pull: 'always',
|
||||||
|
commands: [
|
||||||
|
'./release/' + os + '/' + arch + '/' + name + ' --help',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dryrun',
|
||||||
|
image: 'plugins/docker:' + os + '-' + arch,
|
||||||
|
pull: 'always',
|
||||||
|
settings: {
|
||||||
|
daemon_off: false,
|
||||||
|
dry_run: true,
|
||||||
|
tags: os + '-' + arch,
|
||||||
|
dockerfile: 'docker/Dockerfile.' + os + '.' + arch,
|
||||||
|
repo: 'appleboy/' + name,
|
||||||
|
cache_from: 'appleboy/' + name,
|
||||||
|
},
|
||||||
|
when: {
|
||||||
|
event: [ 'pull_request' ],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'publish',
|
||||||
|
image: 'plugins/docker:' + os + '-' + arch,
|
||||||
|
pull: 'always',
|
||||||
|
settings: {
|
||||||
|
daemon_off: 'false',
|
||||||
|
auto_tag: true,
|
||||||
|
auto_tag_suffix: os + '-' + arch,
|
||||||
|
dockerfile: 'docker/Dockerfile.' + os + '.' + arch,
|
||||||
|
repo: 'appleboy/' + name,
|
||||||
|
cache_from: 'appleboy/' + name,
|
||||||
|
username: { 'from_secret': 'docker_username' },
|
||||||
|
password: { 'from_secret': 'docker_password' },
|
||||||
|
},
|
||||||
|
when: {
|
||||||
|
event: {
|
||||||
|
exclude: [ 'pull_request' ],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
depends_on: [
|
||||||
|
'testing',
|
||||||
|
],
|
||||||
|
trigger: {
|
||||||
|
ref: [
|
||||||
|
'refs/heads/master',
|
||||||
|
'refs/pull/**',
|
||||||
|
'refs/tags/**',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
release:: {
|
||||||
|
kind: 'pipeline',
|
||||||
|
name: 'release-binary',
|
||||||
|
platform: {
|
||||||
|
os: 'linux',
|
||||||
|
arch: 'amd64',
|
||||||
|
},
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
name: 'build-all-binary',
|
||||||
|
image: 'golang:1.13',
|
||||||
|
pull: 'always',
|
||||||
|
commands: [
|
||||||
|
'make release'
|
||||||
|
],
|
||||||
|
when: {
|
||||||
|
event: [ 'tag' ],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'deploy-all-binary',
|
||||||
|
image: 'plugins/github-release',
|
||||||
|
pull: 'always',
|
||||||
|
settings: {
|
||||||
|
files: [ 'dist/release/*' ],
|
||||||
|
api_key: { 'from_secret': 'github_release_api_key' },
|
||||||
|
},
|
||||||
|
when: {
|
||||||
|
event: [ 'tag' ],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
depends_on: [
|
||||||
|
'testing',
|
||||||
|
],
|
||||||
|
trigger: {
|
||||||
|
ref: [
|
||||||
|
'refs/tags/**',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
notifications(os='linux', arch='amd64', depends_on=[]):: {
|
||||||
|
kind: 'pipeline',
|
||||||
|
name: 'notifications',
|
||||||
|
platform: {
|
||||||
|
os: os,
|
||||||
|
arch: arch,
|
||||||
|
},
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
name: 'manifest',
|
||||||
|
image: 'plugins/manifest',
|
||||||
|
pull: 'always',
|
||||||
|
settings: {
|
||||||
|
username: { from_secret: 'docker_username' },
|
||||||
|
password: { from_secret: 'docker_password' },
|
||||||
|
spec: 'docker/manifest.tmpl',
|
||||||
|
ignore_missing: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
depends_on: depends_on,
|
||||||
|
trigger: {
|
||||||
|
ref: [
|
||||||
|
'refs/heads/master',
|
||||||
|
'refs/tags/**',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
signature(key):: {
|
||||||
|
kind: 'signature',
|
||||||
|
hmac: key,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -11,26 +13,27 @@ import (
|
|||||||
"github.com/appleboy/easyssh-proxy"
|
"github.com/appleboy/easyssh-proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var (
|
||||||
missingHostOrUser = "Error: missing server host or user"
|
errMissingHost = errors.New("Error: missing server host")
|
||||||
missingPasswordOrKey = "Error: can't connect without a private SSH key or password"
|
errMissingPasswordOrKey = errors.New("Error: can't connect without a private SSH key or password")
|
||||||
commandTimeOut = "Error: command timeout"
|
errCommandTimeOut = errors.New("Error: command timeout")
|
||||||
setPasswordandKey = "can't set password and key at the same time"
|
errSetPasswordandKey = errors.New("can't set password and key at the same time")
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// Config for the plugin.
|
// Config for the plugin.
|
||||||
Config struct {
|
Config struct {
|
||||||
Key string
|
Key string
|
||||||
|
Passphrase string
|
||||||
KeyPath string
|
KeyPath string
|
||||||
UserName string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
Host []string
|
Host []string
|
||||||
Port int
|
Port int
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
CommandTimeout int
|
CommandTimeout time.Duration
|
||||||
Script []string
|
Script []string
|
||||||
Secrets []string
|
ScriptStop bool
|
||||||
Envs []string
|
Envs []string
|
||||||
Proxy easyssh.DefaultConfig
|
Proxy easyssh.DefaultConfig
|
||||||
Debug bool
|
Debug bool
|
||||||
@@ -40,27 +43,34 @@ type (
|
|||||||
// Plugin structure
|
// Plugin structure
|
||||||
Plugin struct {
|
Plugin struct {
|
||||||
Config Config
|
Config Config
|
||||||
|
Writer io.Writer
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func escapeArg(arg string) string {
|
||||||
|
return "'" + strings.Replace(arg, "'", `'\''`, -1) + "'"
|
||||||
|
}
|
||||||
|
|
||||||
func (p Plugin) exec(host string, wg *sync.WaitGroup, errChannel chan error) {
|
func (p Plugin) exec(host string, wg *sync.WaitGroup, errChannel chan error) {
|
||||||
// Create MakeConfig instance with remote username, server address and path to private key.
|
// Create MakeConfig instance with remote username, server address and path to private key.
|
||||||
ssh := &easyssh.MakeConfig{
|
ssh := &easyssh.MakeConfig{
|
||||||
Server: host,
|
Server: host,
|
||||||
User: p.Config.UserName,
|
User: p.Config.Username,
|
||||||
Password: p.Config.Password,
|
Password: p.Config.Password,
|
||||||
Port: strconv.Itoa(p.Config.Port),
|
Port: strconv.Itoa(p.Config.Port),
|
||||||
Key: p.Config.Key,
|
Key: p.Config.Key,
|
||||||
KeyPath: p.Config.KeyPath,
|
KeyPath: p.Config.KeyPath,
|
||||||
Timeout: p.Config.Timeout,
|
Passphrase: p.Config.Passphrase,
|
||||||
|
Timeout: p.Config.Timeout,
|
||||||
Proxy: easyssh.DefaultConfig{
|
Proxy: easyssh.DefaultConfig{
|
||||||
Server: p.Config.Proxy.Server,
|
Server: p.Config.Proxy.Server,
|
||||||
User: p.Config.Proxy.User,
|
User: p.Config.Proxy.User,
|
||||||
Password: p.Config.Proxy.Password,
|
Password: p.Config.Proxy.Password,
|
||||||
Port: p.Config.Proxy.Port,
|
Port: p.Config.Proxy.Port,
|
||||||
Key: p.Config.Proxy.Key,
|
Key: p.Config.Proxy.Key,
|
||||||
KeyPath: p.Config.Proxy.KeyPath,
|
KeyPath: p.Config.Proxy.KeyPath,
|
||||||
Timeout: p.Config.Proxy.Timeout,
|
Passphrase: p.Config.Proxy.Passphrase,
|
||||||
|
Timeout: p.Config.Proxy.Timeout,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,12 +81,12 @@ func (p Plugin) exec(host string, wg *sync.WaitGroup, errChannel chan error) {
|
|||||||
env := []string{}
|
env := []string{}
|
||||||
for _, key := range p.Config.Envs {
|
for _, key := range p.Config.Envs {
|
||||||
key = strings.ToUpper(key)
|
key = strings.ToUpper(key)
|
||||||
val := os.Getenv(key)
|
if val, found := os.LookupEnv(key); found {
|
||||||
val = strings.Replace(val, " ", "", -1)
|
env = append(env, key+"="+escapeArg(val))
|
||||||
env = append(env, key+"='"+val+"'")
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Config.Script = append(env, p.Config.Script...)
|
p.Config.Script = append(env, p.scriptCommands()...)
|
||||||
|
|
||||||
if p.Config.Debug {
|
if p.Config.Debug {
|
||||||
p.log(host, "======ENV======")
|
p.log(host, "======ENV======")
|
||||||
@@ -110,7 +120,7 @@ func (p Plugin) exec(host string, wg *sync.WaitGroup, errChannel chan error) {
|
|||||||
|
|
||||||
// command time out
|
// command time out
|
||||||
if !isTimeout {
|
if !isTimeout {
|
||||||
errChannel <- fmt.Errorf(commandTimeOut)
|
errChannel <- errCommandTimeOut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,31 +128,34 @@ func (p Plugin) exec(host string, wg *sync.WaitGroup, errChannel chan error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p Plugin) log(host string, message ...interface{}) {
|
func (p Plugin) log(host string, message ...interface{}) {
|
||||||
|
if p.Writer == nil {
|
||||||
|
p.Writer = os.Stdout
|
||||||
|
}
|
||||||
if count := len(p.Config.Host); count == 1 {
|
if count := len(p.Config.Host); count == 1 {
|
||||||
fmt.Printf("%s", fmt.Sprintln(message...))
|
fmt.Fprintf(p.Writer, "%s", fmt.Sprintln(message...))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%s: %s", host, fmt.Sprintln(message...))
|
fmt.Fprintf(p.Writer, "%s: %s", host, fmt.Sprintln(message...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the plugin.
|
// Exec executes the plugin.
|
||||||
func (p Plugin) Exec() error {
|
func (p Plugin) Exec() error {
|
||||||
if len(p.Config.Host) == 0 && len(p.Config.UserName) == 0 {
|
if len(p.Config.Host) == 0 {
|
||||||
return fmt.Errorf(missingHostOrUser)
|
return errMissingHost
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(p.Config.Key) == 0 && len(p.Config.Password) == 0 && len(p.Config.KeyPath) == 0 {
|
if len(p.Config.Key) == 0 && len(p.Config.Password) == 0 && len(p.Config.KeyPath) == 0 {
|
||||||
return fmt.Errorf(missingPasswordOrKey)
|
return errMissingPasswordOrKey
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(p.Config.Key) != 0 && len(p.Config.Password) != 0 {
|
if len(p.Config.Key) != 0 && len(p.Config.Password) != 0 {
|
||||||
return fmt.Errorf(setPasswordandKey)
|
return errSetPasswordandKey
|
||||||
}
|
}
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(len(p.Config.Host))
|
wg.Add(len(p.Config.Host))
|
||||||
errChannel := make(chan error, 1)
|
errChannel := make(chan error)
|
||||||
finished := make(chan bool, 1)
|
finished := make(chan struct{})
|
||||||
for _, host := range p.Config.Host {
|
for _, host := range p.Config.Host {
|
||||||
if p.Config.Sync {
|
if p.Config.Sync {
|
||||||
p.exec(host, &wg, errChannel)
|
p.exec(host, &wg, errChannel)
|
||||||
@@ -164,9 +177,35 @@ func (p Plugin) Exec() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("==========================================")
|
fmt.Println("==============================================")
|
||||||
fmt.Println("Successfully executed commands to all host.")
|
fmt.Println("✅ Successfully executed commands to all host.")
|
||||||
fmt.Println("==========================================")
|
fmt.Println("==============================================")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p Plugin) scriptCommands() []string {
|
||||||
|
scripts := []string{}
|
||||||
|
|
||||||
|
for _, cmd := range p.Config.Script {
|
||||||
|
if p.Config.ScriptStop {
|
||||||
|
scripts = append(scripts, strings.Split(cmd, "\n")...)
|
||||||
|
} else {
|
||||||
|
scripts = append(scripts, cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commands := make([]string, 0)
|
||||||
|
|
||||||
|
for _, cmd := range scripts {
|
||||||
|
if strings.TrimSpace(cmd) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
commands = append(commands, cmd)
|
||||||
|
if p.Config.ScriptStop {
|
||||||
|
commands = append(commands, "DRONE_SSH_PREV_COMMAND_EXIT_CODE=$? ; if [ $DRONE_SSH_PREV_COMMAND_EXIT_CODE -ne 0 ]; then exit $DRONE_SSH_PREV_COMMAND_EXIT_CODE; fi;")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return commands
|
||||||
|
}
|
||||||
|
|||||||
+349
-29
@@ -1,8 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/appleboy/easyssh-proxy"
|
"github.com/appleboy/easyssh-proxy"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -14,48 +19,50 @@ func TestMissingHostOrUser(t *testing.T) {
|
|||||||
err := plugin.Exec()
|
err := plugin.Exec()
|
||||||
|
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
assert.Equal(t, missingHostOrUser, err.Error())
|
assert.Equal(t, errMissingHost, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMissingKeyOrPassword(t *testing.T) {
|
func TestMissingKeyOrPassword(t *testing.T) {
|
||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
Config{
|
Config{
|
||||||
Host: []string{"localhost"},
|
Host: []string{"localhost"},
|
||||||
UserName: "ubuntu",
|
Username: "ubuntu",
|
||||||
},
|
},
|
||||||
|
os.Stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := plugin.Exec()
|
err := plugin.Exec()
|
||||||
|
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
assert.Equal(t, missingPasswordOrKey, err.Error())
|
assert.Equal(t, errMissingPasswordOrKey, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetPasswordAndKey(t *testing.T) {
|
func TestSetPasswordAndKey(t *testing.T) {
|
||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
Config{
|
Config{
|
||||||
Host: []string{"localhost"},
|
Host: []string{"localhost"},
|
||||||
UserName: "ubuntu",
|
Username: "ubuntu",
|
||||||
Password: "1234",
|
Password: "1234",
|
||||||
Key: "1234",
|
Key: "1234",
|
||||||
},
|
},
|
||||||
|
os.Stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := plugin.Exec()
|
err := plugin.Exec()
|
||||||
|
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
assert.Equal(t, setPasswordandKey, err.Error())
|
assert.Equal(t, errSetPasswordandKey, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIncorrectPassword(t *testing.T) {
|
func TestIncorrectPassword(t *testing.T) {
|
||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
Config: Config{
|
Config: Config{
|
||||||
Host: []string{"localhost"},
|
Host: []string{"localhost"},
|
||||||
UserName: "drone-scp",
|
Username: "drone-scp",
|
||||||
Port: 22,
|
Port: 22,
|
||||||
Password: "123456",
|
Password: "123456",
|
||||||
Script: []string{"whoami"},
|
Script: []string{"whoami"},
|
||||||
CommandTimeout: 60,
|
CommandTimeout: 60 * time.Second,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,9 +74,9 @@ func TestSSHScriptFromRawKey(t *testing.T) {
|
|||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
Config: Config{
|
Config: Config{
|
||||||
Host: []string{"localhost"},
|
Host: []string{"localhost"},
|
||||||
UserName: "drone-scp",
|
Username: "drone-scp",
|
||||||
Port: 22,
|
Port: 22,
|
||||||
CommandTimeout: 60,
|
CommandTimeout: 60 * time.Second,
|
||||||
Key: `-----BEGIN RSA PRIVATE KEY-----
|
Key: `-----BEGIN RSA PRIVATE KEY-----
|
||||||
MIIEpAIBAAKCAQEA4e2D/qPN08pzTac+a8ZmlP1ziJOXk45CynMPtva0rtK/RB26
|
MIIEpAIBAAKCAQEA4e2D/qPN08pzTac+a8ZmlP1ziJOXk45CynMPtva0rtK/RB26
|
||||||
VbfAF0hIJji7ltvnYnqCU9oFfvEM33cTn7T96+od8ib/Vz25YU8ZbstqtIskPuwC
|
VbfAF0hIJji7ltvnYnqCU9oFfvEM33cTn7T96+od8ib/Vz25YU8ZbstqtIskPuwC
|
||||||
@@ -110,11 +117,11 @@ func TestSSHScriptFromKeyFile(t *testing.T) {
|
|||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
Config: Config{
|
Config: Config{
|
||||||
Host: []string{"localhost", "127.0.0.1"},
|
Host: []string{"localhost", "127.0.0.1"},
|
||||||
UserName: "drone-scp",
|
Username: "drone-scp",
|
||||||
Port: 22,
|
Port: 22,
|
||||||
KeyPath: "./tests/.ssh/id_rsa",
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
Script: []string{"whoami", "ls -al"},
|
Script: []string{"whoami", "ls -al"},
|
||||||
CommandTimeout: 60,
|
CommandTimeout: 60 * time.Second,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,11 +133,11 @@ func TestStreamFromSSHCommand(t *testing.T) {
|
|||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
Config: Config{
|
Config: Config{
|
||||||
Host: []string{"localhost", "127.0.0.1"},
|
Host: []string{"localhost", "127.0.0.1"},
|
||||||
UserName: "drone-scp",
|
Username: "drone-scp",
|
||||||
Port: 22,
|
Port: 22,
|
||||||
KeyPath: "./tests/.ssh/id_rsa",
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
Script: []string{"whoami", "for i in {1..5}; do echo ${i}; sleep 1; done", "echo 'done'"},
|
Script: []string{"whoami", "for i in {1..5}; do echo ${i}; sleep 1; done", "echo 'done'"},
|
||||||
CommandTimeout: 60,
|
CommandTimeout: 60 * time.Second,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,11 +149,11 @@ func TestSSHScriptWithError(t *testing.T) {
|
|||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
Config: Config{
|
Config: Config{
|
||||||
Host: []string{"localhost", "127.0.0.1"},
|
Host: []string{"localhost", "127.0.0.1"},
|
||||||
UserName: "drone-scp",
|
Username: "drone-scp",
|
||||||
Port: 22,
|
Port: 22,
|
||||||
KeyPath: "./tests/.ssh/id_rsa",
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
Script: []string{"exit 1"},
|
Script: []string{"exit 1"},
|
||||||
CommandTimeout: 60,
|
CommandTimeout: 60 * time.Second,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,11 +166,11 @@ func TestSSHCommandTimeOut(t *testing.T) {
|
|||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
Config: Config{
|
Config: Config{
|
||||||
Host: []string{"localhost"},
|
Host: []string{"localhost"},
|
||||||
UserName: "drone-scp",
|
Username: "drone-scp",
|
||||||
Port: 22,
|
Port: 22,
|
||||||
KeyPath: "./tests/.ssh/id_rsa",
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
Script: []string{"sleep 5"},
|
Script: []string{"sleep 5"},
|
||||||
CommandTimeout: 1,
|
CommandTimeout: 1 * time.Second,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,11 +182,11 @@ func TestProxyCommand(t *testing.T) {
|
|||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
Config: Config{
|
Config: Config{
|
||||||
Host: []string{"localhost"},
|
Host: []string{"localhost"},
|
||||||
UserName: "drone-scp",
|
Username: "drone-scp",
|
||||||
Port: 22,
|
Port: 22,
|
||||||
KeyPath: "./tests/.ssh/id_rsa",
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
Script: []string{"whoami"},
|
Script: []string{"whoami"},
|
||||||
CommandTimeout: 1,
|
CommandTimeout: 1 * time.Second,
|
||||||
Proxy: easyssh.DefaultConfig{
|
Proxy: easyssh.DefaultConfig{
|
||||||
Server: "localhost",
|
Server: "localhost",
|
||||||
User: "drone-scp",
|
User: "drone-scp",
|
||||||
@@ -197,11 +204,11 @@ func TestSSHCommandError(t *testing.T) {
|
|||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
Config: Config{
|
Config: Config{
|
||||||
Host: []string{"localhost"},
|
Host: []string{"localhost"},
|
||||||
UserName: "drone-scp",
|
Username: "drone-scp",
|
||||||
Port: 22,
|
Port: 22,
|
||||||
KeyPath: "./tests/.ssh/id_rsa",
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
Script: []string{"mkdir a", "mkdir a"},
|
Script: []string{"mkdir a", "mkdir a"},
|
||||||
CommandTimeout: 60,
|
CommandTimeout: 60 * time.Second,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +220,7 @@ func TestSSHCommandExitCodeError(t *testing.T) {
|
|||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
Config: Config{
|
Config: Config{
|
||||||
Host: []string{"localhost"},
|
Host: []string{"localhost"},
|
||||||
UserName: "drone-scp",
|
Username: "drone-scp",
|
||||||
Port: 22,
|
Port: 22,
|
||||||
KeyPath: "./tests/.ssh/id_rsa",
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
Script: []string{
|
Script: []string{
|
||||||
@@ -223,7 +230,7 @@ func TestSSHCommandExitCodeError(t *testing.T) {
|
|||||||
"mkdir a",
|
"mkdir a",
|
||||||
"echo 2",
|
"echo 2",
|
||||||
},
|
},
|
||||||
CommandTimeout: 60,
|
CommandTimeout: 60 * time.Second,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,18 +239,43 @@ func TestSSHCommandExitCodeError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSetENV(t *testing.T) {
|
func TestSetENV(t *testing.T) {
|
||||||
os.Setenv("FOO", "1)")
|
os.Setenv("FOO", `' 1) '`)
|
||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
Config: Config{
|
Config: Config{
|
||||||
Host: []string{"localhost"},
|
Host: []string{"localhost"},
|
||||||
UserName: "drone-scp",
|
Username: "drone-scp",
|
||||||
Port: 22,
|
Port: 22,
|
||||||
KeyPath: "./tests/.ssh/id_rsa",
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
Secrets: []string{"FOO"},
|
|
||||||
Envs: []string{"foo"},
|
Envs: []string{"foo"},
|
||||||
Debug: true,
|
Debug: true,
|
||||||
Script: []string{"whoami; echo $FOO"},
|
Script: []string{"whoami; echo $FOO"},
|
||||||
CommandTimeout: 1,
|
CommandTimeout: 1 * time.Second,
|
||||||
|
Proxy: easyssh.DefaultConfig{
|
||||||
|
Server: "localhost",
|
||||||
|
User: "drone-scp",
|
||||||
|
Port: "22",
|
||||||
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := plugin.Exec()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetExistingENV(t *testing.T) {
|
||||||
|
os.Setenv("FOO", "Value for foo")
|
||||||
|
os.Setenv("BAR", "")
|
||||||
|
plugin := Plugin{
|
||||||
|
Config: Config{
|
||||||
|
Host: []string{"localhost"},
|
||||||
|
Username: "drone-scp",
|
||||||
|
Port: 22,
|
||||||
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
|
Envs: []string{"foo", "bar", "baz"},
|
||||||
|
Debug: true,
|
||||||
|
Script: []string{"export FOO", "export BAR", "export BAZ", "env | grep -q '^FOO=Value for foo$'", "env | grep -q '^BAR=$'", "if env | grep -q BAZ; then false; else true; fi"},
|
||||||
|
CommandTimeout: 1 * time.Second,
|
||||||
Proxy: easyssh.DefaultConfig{
|
Proxy: easyssh.DefaultConfig{
|
||||||
Server: "localhost",
|
Server: "localhost",
|
||||||
User: "drone-scp",
|
User: "drone-scp",
|
||||||
@@ -261,11 +293,11 @@ func TestSyncMode(t *testing.T) {
|
|||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
Config: Config{
|
Config: Config{
|
||||||
Host: []string{"localhost", "127.0.0.1"},
|
Host: []string{"localhost", "127.0.0.1"},
|
||||||
UserName: "drone-scp",
|
Username: "drone-scp",
|
||||||
Port: 22,
|
Port: 22,
|
||||||
KeyPath: "./tests/.ssh/id_rsa",
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
Script: []string{"whoami", "for i in {1..3}; do echo ${i}; sleep 1; done", "echo 'done'"},
|
Script: []string{"whoami", "for i in {1..3}; do echo ${i}; sleep 1; done", "echo 'done'"},
|
||||||
CommandTimeout: 60,
|
CommandTimeout: 60 * time.Second,
|
||||||
Sync: true,
|
Sync: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -273,3 +305,291 @@ func TestSyncMode(t *testing.T) {
|
|||||||
err := plugin.Exec()
|
err := plugin.Exec()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_escapeArg(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
arg string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "escape nothing",
|
||||||
|
args: args{
|
||||||
|
arg: "Hi I am appleboy",
|
||||||
|
},
|
||||||
|
want: `'Hi I am appleboy'`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "escape single quote",
|
||||||
|
args: args{
|
||||||
|
arg: "Hi I am 'appleboy'",
|
||||||
|
},
|
||||||
|
want: `'Hi I am '\''appleboy'\'''`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := escapeArg(tt.args.arg)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandOutput(t *testing.T) {
|
||||||
|
var (
|
||||||
|
buffer bytes.Buffer
|
||||||
|
expected = `
|
||||||
|
localhost: ======CMD======
|
||||||
|
localhost: pwd
|
||||||
|
whoami
|
||||||
|
uname
|
||||||
|
localhost: ======END======
|
||||||
|
localhost: out: /home/drone-scp
|
||||||
|
localhost: out: drone-scp
|
||||||
|
localhost: out: Linux
|
||||||
|
127.0.0.1: ======CMD======
|
||||||
|
127.0.0.1: pwd
|
||||||
|
whoami
|
||||||
|
uname
|
||||||
|
127.0.0.1: ======END======
|
||||||
|
127.0.0.1: out: /home/drone-scp
|
||||||
|
127.0.0.1: out: drone-scp
|
||||||
|
127.0.0.1: out: Linux
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
plugin := Plugin{
|
||||||
|
Config: Config{
|
||||||
|
Host: []string{"localhost", "127.0.0.1"},
|
||||||
|
Username: "drone-scp",
|
||||||
|
Port: 22,
|
||||||
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
|
Script: []string{
|
||||||
|
"pwd",
|
||||||
|
"whoami",
|
||||||
|
"uname",
|
||||||
|
},
|
||||||
|
CommandTimeout: 60 * time.Second,
|
||||||
|
Sync: true,
|
||||||
|
},
|
||||||
|
Writer: &buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := plugin.Exec()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, unindent(expected), unindent(buffer.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScriptStop(t *testing.T) {
|
||||||
|
var (
|
||||||
|
buffer bytes.Buffer
|
||||||
|
expected = `
|
||||||
|
======CMD======
|
||||||
|
mkdir a/b/c
|
||||||
|
mkdir d/e/f
|
||||||
|
======END======
|
||||||
|
err: mkdir: can't create directory 'a/b/c': No such file or directory
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
plugin := Plugin{
|
||||||
|
Config: Config{
|
||||||
|
Host: []string{"localhost"},
|
||||||
|
Username: "drone-scp",
|
||||||
|
Port: 22,
|
||||||
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
|
Script: []string{
|
||||||
|
"mkdir a/b/c",
|
||||||
|
"mkdir d/e/f",
|
||||||
|
},
|
||||||
|
CommandTimeout: 10 * time.Second,
|
||||||
|
ScriptStop: true,
|
||||||
|
},
|
||||||
|
Writer: &buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := plugin.Exec()
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, unindent(expected), unindent(buffer.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoneScriptStop(t *testing.T) {
|
||||||
|
var (
|
||||||
|
buffer bytes.Buffer
|
||||||
|
expected = `
|
||||||
|
======CMD======
|
||||||
|
mkdir a/b/c
|
||||||
|
mkdir d/e/f
|
||||||
|
======END======
|
||||||
|
err: mkdir: can't create directory 'a/b/c': No such file or directory
|
||||||
|
err: mkdir: can't create directory 'd/e/f': No such file or directory
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
plugin := Plugin{
|
||||||
|
Config: Config{
|
||||||
|
Host: []string{"localhost"},
|
||||||
|
Username: "drone-scp",
|
||||||
|
Port: 22,
|
||||||
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
|
Script: []string{
|
||||||
|
"mkdir a/b/c",
|
||||||
|
"mkdir d/e/f",
|
||||||
|
},
|
||||||
|
CommandTimeout: 10 * time.Second,
|
||||||
|
},
|
||||||
|
Writer: &buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := plugin.Exec()
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, unindent(expected), unindent(buffer.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvOutput(t *testing.T) {
|
||||||
|
var (
|
||||||
|
buffer bytes.Buffer
|
||||||
|
expected = `
|
||||||
|
======CMD======
|
||||||
|
echo "[${ENV_1}]"
|
||||||
|
echo "[${ENV_2}]"
|
||||||
|
echo "[${ENV_3}]"
|
||||||
|
echo "[${ENV_4}]"
|
||||||
|
echo "[${ENV_5}]"
|
||||||
|
echo "[${ENV_6}]"
|
||||||
|
echo "[${ENV_7}]"
|
||||||
|
======END======
|
||||||
|
======ENV======
|
||||||
|
ENV_1='test'
|
||||||
|
ENV_2='test test'
|
||||||
|
ENV_3='test '
|
||||||
|
ENV_4=' test test '
|
||||||
|
ENV_5='test'\'''
|
||||||
|
ENV_6='test"'
|
||||||
|
ENV_7='test,!#;?.@$~'\''"'
|
||||||
|
======END======
|
||||||
|
out: [test]
|
||||||
|
out: [test test]
|
||||||
|
out: [test ]
|
||||||
|
out: [ test test ]
|
||||||
|
out: [test']
|
||||||
|
out: [test"]
|
||||||
|
out: [test,!#;?.@$~'"]
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
os.Setenv("ENV_1", `test`)
|
||||||
|
os.Setenv("ENV_2", `test test`)
|
||||||
|
os.Setenv("ENV_3", `test `)
|
||||||
|
os.Setenv("ENV_4", ` test test `)
|
||||||
|
os.Setenv("ENV_5", `test'`)
|
||||||
|
os.Setenv("ENV_6", `test"`)
|
||||||
|
os.Setenv("ENV_7", `test,!#;?.@$~'"`)
|
||||||
|
|
||||||
|
plugin := Plugin{
|
||||||
|
Config: Config{
|
||||||
|
Host: []string{"localhost"},
|
||||||
|
Username: "drone-scp",
|
||||||
|
Port: 22,
|
||||||
|
KeyPath: "./tests/.ssh/test",
|
||||||
|
Passphrase: "1234",
|
||||||
|
Envs: []string{"env_1", "env_2", "env_3", "env_4", "env_5", "env_6", "env_7"},
|
||||||
|
Debug: true,
|
||||||
|
Script: []string{
|
||||||
|
`echo "[${ENV_1}]"`,
|
||||||
|
`echo "[${ENV_2}]"`,
|
||||||
|
`echo "[${ENV_3}]"`,
|
||||||
|
`echo "[${ENV_4}]"`,
|
||||||
|
`echo "[${ENV_5}]"`,
|
||||||
|
`echo "[${ENV_6}]"`,
|
||||||
|
`echo "[${ENV_7}]"`,
|
||||||
|
},
|
||||||
|
CommandTimeout: 10 * time.Second,
|
||||||
|
Proxy: easyssh.DefaultConfig{
|
||||||
|
Server: "localhost",
|
||||||
|
User: "drone-scp",
|
||||||
|
Port: "22",
|
||||||
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Writer: &buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := plugin.Exec()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, unindent(expected), unindent(buffer.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func unindent(text string) string {
|
||||||
|
return strings.TrimSpace(strings.Replace(text, "\t", "", -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlugin_scriptCommands(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
Config Config
|
||||||
|
Writer io.Writer
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "normal testing",
|
||||||
|
fields: fields{
|
||||||
|
Config: Config{
|
||||||
|
Script: []string{"mkdir a", "mkdir b"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []string{"mkdir a", "mkdir b"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "script stop",
|
||||||
|
fields: fields{
|
||||||
|
Config: Config{
|
||||||
|
Script: []string{"mkdir a", "mkdir b"},
|
||||||
|
ScriptStop: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []string{"mkdir a", "DRONE_SSH_PREV_COMMAND_EXIT_CODE=$? ; if [ $DRONE_SSH_PREV_COMMAND_EXIT_CODE -ne 0 ]; then exit $DRONE_SSH_PREV_COMMAND_EXIT_CODE; fi;", "mkdir b", "DRONE_SSH_PREV_COMMAND_EXIT_CODE=$? ; if [ $DRONE_SSH_PREV_COMMAND_EXIT_CODE -ne 0 ]; then exit $DRONE_SSH_PREV_COMMAND_EXIT_CODE; fi;"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "normal testing 2",
|
||||||
|
fields: fields{
|
||||||
|
Config: Config{
|
||||||
|
Script: []string{"mkdir a\nmkdir c", "mkdir b"},
|
||||||
|
ScriptStop: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []string{"mkdir a", "DRONE_SSH_PREV_COMMAND_EXIT_CODE=$? ; if [ $DRONE_SSH_PREV_COMMAND_EXIT_CODE -ne 0 ]; then exit $DRONE_SSH_PREV_COMMAND_EXIT_CODE; fi;", "mkdir c", "DRONE_SSH_PREV_COMMAND_EXIT_CODE=$? ; if [ $DRONE_SSH_PREV_COMMAND_EXIT_CODE -ne 0 ]; then exit $DRONE_SSH_PREV_COMMAND_EXIT_CODE; fi;", "mkdir b", "DRONE_SSH_PREV_COMMAND_EXIT_CODE=$? ; if [ $DRONE_SSH_PREV_COMMAND_EXIT_CODE -ne 0 ]; then exit $DRONE_SSH_PREV_COMMAND_EXIT_CODE; fi;"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "trim space",
|
||||||
|
fields: fields{
|
||||||
|
Config: Config{
|
||||||
|
Script: []string{"mkdir a", "mkdir b", "\t", " "},
|
||||||
|
ScriptStop: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []string{"mkdir a", "mkdir b"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
p := Plugin{
|
||||||
|
Config: tt.fields.Config,
|
||||||
|
Writer: tt.fields.Writer,
|
||||||
|
}
|
||||||
|
if got := p.scriptCommands(); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Plugin.scriptCommands() = %#v, want %#v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAZka7A7i
|
||||||
|
FscMeJBPyPteclAAAAEAAAAAEAAAIXAAAAB3NzaC1yc2EAAAADAQABAAACAQDz6aZ1jY2o
|
||||||
|
nnuj2YNHJ/HhfvIu0B973v/+pFFOavnTUOhEEKEy3TASu+s9CkHrYZAtRc+QYIkNZI31mh
|
||||||
|
HBhotdeP/7GoO2UirkFtrzyQKPNJxEcv0RBoG9ssN8jex0PyK6DHIYYFnIWadVBEEOh/H+
|
||||||
|
rK7j7u2/big3oTzYBuFrCwmYFcz5na99MzFeAUhazF44gVBma+zO+1quGeqF51UDIg1SMG
|
||||||
|
vX8I7LNEqrKEBaIUQJKFQcxlOWlRLQsjJCymrOujsXsRrXHAQWcnxDcNevv2ZMOUl0ybvv
|
||||||
|
9yH0BiGbRBd1Hy8/QPILbAQaqu0oQE7fubN8Q8lqb3Jg0loID4x/5GPhSY8WAXpuLcXTOr
|
||||||
|
b93SnCw1JsAgJDNqpuuRFy3BSZ7wBOr1jfeIoo7xk14OHiUjJ0uXDL9cLMkcw6ElWz81mr
|
||||||
|
D2VCkXUz+qFyjJ+G7aGWRtctZoOzKln4yfNfUmwW8/8ra3QnmrMZ2xW2Ylw3ZhO+tLi7jI
|
||||||
|
NHYFb54bAdLVPUU1ctIuJns2qkWnjJCxxMiynIqCif20/OU1n8CTJuOWiURmRdmvKOH4PE
|
||||||
|
3JxC2Qnk/3tV3Cf8hp1CH5VjBZ9AjGj5MDMHXyu34VY2WvYo5QyzfS3ySPoT8kCO0G0xpv
|
||||||
|
jwCMHOK+G2RP4kqb/KKZguiKdgintBXuskTlJmD7kcMQAAB1CnEMQGwAKZbd3F1DJqwfPf
|
||||||
|
KWjoUJKbTRiav6h5pQr65JaqDe/7YE2ZHYo5917AC2vPLwPxAnoHFMsbObd5mWcmpATg/0
|
||||||
|
K/qkN5Z4Ml5U3bwr51wfSPh1MiAP21Aickt09BDstIJzNNwwgcY31O3k/d6VBjqyM6Ezop
|
||||||
|
66LI4s/IIni1BI+cALyEfzE4Qu16GfzIeM+JVxildP4VImhvNBESmmbBL8rNmSzlQ+FTuF
|
||||||
|
JVmowUbcon1O0CppM1MRVPeG805XDwjxHXKwOp5O7MdTz7H8JeORoe8D6+4rNfJE0eQGY7
|
||||||
|
Nm4+Wa97HzAFbT9IS433rxoGx9Qps3LAySFONso2JWSOEfo8rxnqO04DrfVHQhY3DkkwQt
|
||||||
|
FsDnMtkthJa+ZzUYc75fnS0DBPGuF9DZUCqrev5oAUHP6C4Vc4b33JJQD4FZJ+ehk3Xsci
|
||||||
|
cwJQsmgLyc5Jdh543Dm7kZoM9ku7HDNrB4H/1p45Vo6aBZMAY50x+fTdBeTgCzzhzzTbf+
|
||||||
|
0IF8W3yW3/BYD+S2Byo3JKp6NH0Q8cgPJrGTl6GltGfpVuc6kLjMZ5zvxRbyWaqtIygM46
|
||||||
|
W1izbA+9jwbHhitCtOk42e/ff6iEB1MVC13LqPty3gPNR8Pv0rDUDjJS4KiVwXqUY+bMr0
|
||||||
|
C8l/hx93euHjLUJ49Ru6uy/2fBlHZEj6GmEAJhu/i6t2c1Rq0HBLis9X356oQT+YZnIai2
|
||||||
|
ym0MknPxjeYBAItOV3zhRd1cYnk7CDcl1XALcnh0tqP712x24IJ+Ytqg7nvB2NZV8T469I
|
||||||
|
8Fp254Nr89HOMAXaZD0UcIPm7D2rfWV+YJFI3ZcJ/8DM99H3tpXe2j4oHMdmAbBd++09sx
|
||||||
|
KBRdFLcvnBfd1lqwxpA7hbxzrxi/yehYCqzh5KQGaf2UXej6TPiVzBWVYbp34cMZtsT6mF
|
||||||
|
K8SS3l5TXoNK2DNEk30o8K3q+vngQpfC9GZ/id4B7LS/3ybellxemZHXQoU4PxDkLKt7jd
|
||||||
|
AAsd5WO13dv3n/qgyu8iBRiFU+W66NX0RJGkp+lZMnta0YzukafM2n6GDn/r/Cx/y21PAi
|
||||||
|
ah8i41ByI1QLI4m1r+bRHdUxAarS/XJw4tTSFiZu3zddMYrlzeG9O3VUX9zBvBtfQbSmeJ
|
||||||
|
omml0zlr/qD7TMsORiujy7XIn7sMW+Ls/NA8TvX8oRnACjXe/MYNEZ8WDu2rkZuY/Dfc+o
|
||||||
|
NyYWO7kZ3kcejQZ1NusJSA7MG0FFGYSIaC9T9CWqYd5IcRSJW4dZnCt9z8CIJ6TSUFqMb/
|
||||||
|
H1Y5Rmi0IIX+8qbGGXVBDIBk5y9xtS43+nz1nsdXwDmkTiXN9+ZX+GDsLxCWoHGryrWDbk
|
||||||
|
EuOAlqpvxFKzEkNsx+AC5wae6i/hBeiEce9bm4nZp+hFv1ic1Z9WS8B37YOFgJ4utGeOjB
|
||||||
|
6hnywUUJ3aH0LnCQNB3UzeFR7BmEaxmYD/phJodmjA5SD3CWpeizdXfrUjtqXGhYlr2jzq
|
||||||
|
vBAeeYEO4uaHIGxg8GqoqtaseqVcIdtouHxrVAxxXkjShV2ji7oJ/AtrLZNlkKYxMk0TpX
|
||||||
|
fFiKqL/uKfS78FfvVOhOkHZTD6ZeMgmdL/uOghEAtrf08ChyRvdp7QLjA802aio9eUVIQm
|
||||||
|
lHb1ltPEbIZNuvQ5kTIwk2eM6EAkOh0MBMoAYOxOpIb00XHNRDGJYuLewByjMQa8EoT6VM
|
||||||
|
NoiFIzJU9lLAXE6yz6JswctpTpLHK9Aq5vY7ObaOvrmpCQqsXfOuVUo2nR/FyEes97zuXG
|
||||||
|
E4aKaHK4IAW4UY/oGYk7pU/yRpudhiNRMXzmcQXfVmBEHuvDrh2chg8lDYn++07F7RWqkI
|
||||||
|
nfMAOWR8UEl4xp4zJtThDjRxNW6QLl8E1ADjndA9wVaKNSzv2i1TLXKBr5luFqY9MSJ2rm
|
||||||
|
yBR5EwairH/Qn9TUxaDD+0p6J+E9iz1l8UPTJa/cjtwiySljahY/6tHHnr9YQVnox92yfU
|
||||||
|
UXpfINGjYrpqh6EFwmyRw9fryIMvMhgZYo6ZoCRBCK2GfGAB0VTzJy2FGs4GecZK5ptXKu
|
||||||
|
sOX8BgGX/Q/nAJ7PWf9hgYlX2YyjmLjQZDMWECp05VFx9znEETNKlwF1FX5/E/37ISyz4d
|
||||||
|
I1LVSKOEccJX7jCR32LzvRW1UBX47Z+q3LVE4sa0QAV/JoISq6Qn6zAsVIV0yEPmVbd/xx
|
||||||
|
aX2uBUGHhmd99YJDh81xJIoYEMRzoGVfp0JjfYcDUc+2I6JdrOMF9/KmMA5wsZl4OKiu/F
|
||||||
|
cTRGjUkgw/cF2EFRGWknee2esYRB7tOr4y56qZ4gxqw8q9rYXhyB42jbdTvt5xcCm/ynid
|
||||||
|
sn4InokRRoIiMIPL5Ur7FZQHOP+915MWUBsrTJtkCWQuqJheYUi3mCzh/7NadAKplRpaKb
|
||||||
|
rS/DJIOOkjnGni/sDxJzPq7STDBVy4WStwQl6NI5hq+/c+JvN9GI4Vu/kz0z8qUcdShLaH
|
||||||
|
l4njcaMpg4tpQMHtCBOicGyV0=
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDz6aZ1jY2onnuj2YNHJ/HhfvIu0B973v/+pFFOavnTUOhEEKEy3TASu+s9CkHrYZAtRc+QYIkNZI31mhHBhotdeP/7GoO2UirkFtrzyQKPNJxEcv0RBoG9ssN8jex0PyK6DHIYYFnIWadVBEEOh/H+rK7j7u2/big3oTzYBuFrCwmYFcz5na99MzFeAUhazF44gVBma+zO+1quGeqF51UDIg1SMGvX8I7LNEqrKEBaIUQJKFQcxlOWlRLQsjJCymrOujsXsRrXHAQWcnxDcNevv2ZMOUl0ybvv9yH0BiGbRBd1Hy8/QPILbAQaqu0oQE7fubN8Q8lqb3Jg0loID4x/5GPhSY8WAXpuLcXTOrb93SnCw1JsAgJDNqpuuRFy3BSZ7wBOr1jfeIoo7xk14OHiUjJ0uXDL9cLMkcw6ElWz81mrD2VCkXUz+qFyjJ+G7aGWRtctZoOzKln4yfNfUmwW8/8ra3QnmrMZ2xW2Ylw3ZhO+tLi7jINHYFb54bAdLVPUU1ctIuJns2qkWnjJCxxMiynIqCif20/OU1n8CTJuOWiURmRdmvKOH4PE3JxC2Qnk/3tV3Cf8hp1CH5VjBZ9AjGj5MDMHXyu34VY2WvYo5QyzfS3ySPoT8kCO0G0xpvjwCMHOK+G2RP4kqb/KKZguiKdgintBXuskTlJmD7kcMQ== deploy@easyssh
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2017 Bo-Yi Wu
|
|
||||||
|
|
||||||
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.
|
|
||||||
-84
@@ -1,84 +0,0 @@
|
|||||||
.PHONY: test drone-ssh fmt vet errcheck lint install update coverage embedmd
|
|
||||||
|
|
||||||
GOFMT ?= gofmt "-s"
|
|
||||||
GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*")
|
|
||||||
PACKAGES ?= $(shell go list ./... | grep -v /vendor/)
|
|
||||||
|
|
||||||
all: install lint
|
|
||||||
|
|
||||||
install:
|
|
||||||
@hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
|
||||||
go get -u github.com/kardianos/govendor; \
|
|
||||||
fi
|
|
||||||
govendor sync
|
|
||||||
|
|
||||||
fmt:
|
|
||||||
$(GOFMT) -w $(GOFILES)
|
|
||||||
|
|
||||||
.PHONY: fmt-check
|
|
||||||
fmt-check:
|
|
||||||
# get all go files and run go fmt on them
|
|
||||||
@diff=$$($(GOFMT) -d $(GOFILES)); \
|
|
||||||
if [ -n "$$diff" ]; then \
|
|
||||||
echo "Please run 'make fmt' and commit the result:"; \
|
|
||||||
echo "$${diff}"; \
|
|
||||||
exit 1; \
|
|
||||||
fi;
|
|
||||||
|
|
||||||
vet:
|
|
||||||
go vet $(PACKAGES)
|
|
||||||
|
|
||||||
errcheck:
|
|
||||||
@hash errcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
|
||||||
go get -u github.com/kisielk/errcheck; \
|
|
||||||
fi
|
|
||||||
errcheck $(PACKAGES)
|
|
||||||
|
|
||||||
lint:
|
|
||||||
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
|
||||||
go get -u github.com/golang/lint/golint; \
|
|
||||||
fi
|
|
||||||
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
|
|
||||||
|
|
||||||
unconvert:
|
|
||||||
@hash unconvert > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
|
||||||
go get -u github.com/mdempsky/unconvert; \
|
|
||||||
fi
|
|
||||||
for PKG in $(PACKAGES); do unconvert -v $$PKG || exit 1; done;
|
|
||||||
|
|
||||||
embedmd:
|
|
||||||
@hash embedmd > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
|
||||||
go get -u github.com/campoy/embedmd; \
|
|
||||||
fi
|
|
||||||
embedmd -d *.md
|
|
||||||
|
|
||||||
test: fmt-check
|
|
||||||
for PKG in $(PACKAGES); do go test -v -cover -coverprofile $$GOPATH/src/$$PKG/coverage.txt $$PKG || exit 1; done;
|
|
||||||
|
|
||||||
html:
|
|
||||||
go tool cover -html=coverage.txt
|
|
||||||
|
|
||||||
coverage:
|
|
||||||
sed -i '/main.go/d' .cover/coverage.txt
|
|
||||||
curl -s https://codecov.io/bash > .codecov && \
|
|
||||||
chmod +x .codecov && \
|
|
||||||
./.codecov -f .cover/coverage.txt
|
|
||||||
|
|
||||||
clean:
|
|
||||||
go clean -x -i ./...
|
|
||||||
rm -rf coverage.txt $(EXECUTABLE) $(DIST) vendor
|
|
||||||
|
|
||||||
ssh-server:
|
|
||||||
adduser -h /home/drone-scp -s /bin/bash -D -S drone-scp
|
|
||||||
echo drone-scp:1234 | chpasswd
|
|
||||||
mkdir -p /home/drone-scp/.ssh
|
|
||||||
chmod 700 /home/drone-scp/.ssh
|
|
||||||
cp tests/.ssh/id_rsa.pub /home/drone-scp/.ssh/authorized_keys
|
|
||||||
chown -R drone-scp /home/drone-scp/.ssh
|
|
||||||
# install ssh and start server
|
|
||||||
apk add --update openssh openrc
|
|
||||||
rm -rf /etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_dsa_key
|
|
||||||
./tests/entrypoint.sh /usr/sbin/sshd -D &
|
|
||||||
|
|
||||||
version:
|
|
||||||
@echo $(VERSION)
|
|
||||||
-181
@@ -1,181 +0,0 @@
|
|||||||
# easyssh-proxy
|
|
||||||
|
|
||||||
[](https://godoc.org/github.com/appleboy/easyssh-proxy)
|
|
||||||
[](http://drone.wu-boy.com/appleboy/easyssh-proxy)
|
|
||||||
[](https://codecov.io/gh/appleboy/easyssh-proxy)
|
|
||||||
[](https://goreportcard.com/report/github.com/appleboy/easyssh-proxy)
|
|
||||||
[](https://sourcegraph.com/github.com/appleboy/easyssh-proxy?badge)
|
|
||||||
[](https://github.com/appleboy/easyssh-proxy/releases/latest)
|
|
||||||
|
|
||||||
easyssh-proxy provides a simple implementation of some SSH protocol features in Go.
|
|
||||||
|
|
||||||
## Feature
|
|
||||||
|
|
||||||
This project is forked from [easyssh](https://github.com/hypersleep/easyssh) but add some features as the following.
|
|
||||||
|
|
||||||
* [x] Support plain text of user private key.
|
|
||||||
* [x] Support key path of user private key.
|
|
||||||
* [x] Support Timeout for the TCP connection to establish.
|
|
||||||
* [x] Support SSH ProxyCommand.
|
|
||||||
|
|
||||||
```
|
|
||||||
+--------+ +----------+ +-----------+
|
|
||||||
| Laptop | <--> | Jumphost | <--> | FooServer |
|
|
||||||
+--------+ +----------+ +-----------+
|
|
||||||
|
|
||||||
OR
|
|
||||||
|
|
||||||
+--------+ +----------+ +-----------+
|
|
||||||
| Laptop | <--> | Firewall | <--> | FooServer |
|
|
||||||
+--------+ +----------+ +-----------+
|
|
||||||
192.168.1.5 121.1.2.3 10.10.29.68
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage:
|
|
||||||
|
|
||||||
You can see `ssh`, `scp`, `ProxyCommand` on `examples` folder.
|
|
||||||
|
|
||||||
### ssh
|
|
||||||
|
|
||||||
See [example/ssh/ssh.go](./example/ssh/ssh.go)
|
|
||||||
|
|
||||||
[embedmd]:# (example/ssh/ssh.go go)
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/appleboy/easyssh-proxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Create MakeConfig instance with remote username, server address and path to private key.
|
|
||||||
ssh := &easyssh.MakeConfig{
|
|
||||||
User: "appleboy",
|
|
||||||
Server: "example.com",
|
|
||||||
// Optional key or Password without either we try to contact your agent SOCKET
|
|
||||||
//Password: "password",
|
|
||||||
Key: "/.ssh/id_rsa",
|
|
||||||
Port: "22",
|
|
||||||
Timeout: 60 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call Run method with command you want to run on remote server.
|
|
||||||
stdout, stderr, done, err := ssh.Run("ls -al", 60)
|
|
||||||
// Handle errors
|
|
||||||
if err != nil {
|
|
||||||
panic("Can't run remote command: " + err.Error())
|
|
||||||
} else {
|
|
||||||
fmt.Println("don is :", done, "stdout is :", stdout, "; stderr is :", stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### scp
|
|
||||||
|
|
||||||
See [example/scp/scp.go](./example/scp/scp.go)
|
|
||||||
|
|
||||||
[embedmd]:# (example/scp/scp.go go)
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/appleboy/easyssh-proxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Create MakeConfig instance with remote username, server address and path to private key.
|
|
||||||
ssh := &easyssh.MakeConfig{
|
|
||||||
User: "appleboy",
|
|
||||||
Server: "example.com",
|
|
||||||
Password: "123qwe",
|
|
||||||
Port: "22",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call Scp method with file you want to upload to remote server.
|
|
||||||
// Please make sure the `tmp` floder exists.
|
|
||||||
err := ssh.Scp("/root/source.csv", "/tmp/target.csv")
|
|
||||||
|
|
||||||
// Handle errors
|
|
||||||
if err != nil {
|
|
||||||
panic("Can't run remote command: " + err.Error())
|
|
||||||
} else {
|
|
||||||
fmt.Println("success")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### SSH ProxyCommand
|
|
||||||
|
|
||||||
See [example/proxy/proxy.go](./example/proxy/proxy.go)
|
|
||||||
|
|
||||||
[embedmd]:# (example/proxy/proxy.go go /\tssh :=/ /\t}$/)
|
|
||||||
```go
|
|
||||||
ssh := &easyssh.MakeConfig{
|
|
||||||
User: "drone-scp",
|
|
||||||
Server: "localhost",
|
|
||||||
Port: "22",
|
|
||||||
KeyPath: "./tests/.ssh/id_rsa",
|
|
||||||
Proxy: easyssh.DefaultConfig{
|
|
||||||
User: "drone-scp",
|
|
||||||
Server: "localhost",
|
|
||||||
Port: "22",
|
|
||||||
KeyPath: "./tests/.ssh/id_rsa",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### SSH Stream Log
|
|
||||||
|
|
||||||
See [example/stream/stream.go](./example/stream/stream.go)
|
|
||||||
|
|
||||||
[embedmd]:# (example/stream/stream.go go /func/ /^}$/)
|
|
||||||
```go
|
|
||||||
func main() {
|
|
||||||
// Create MakeConfig instance with remote username, server address and path to private key.
|
|
||||||
ssh := &easyssh.MakeConfig{
|
|
||||||
Server: "localhost",
|
|
||||||
User: "drone-scp",
|
|
||||||
KeyPath: "./tests/.ssh/id_rsa",
|
|
||||||
Port: "22",
|
|
||||||
Timeout: 60 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call Run method with command you want to run on remote server.
|
|
||||||
stdoutChan, stderrChan, doneChan, errChan, err := ssh.Stream("for i in {1..5}; do echo ${i}; sleep 1; done; exit 2;", 60)
|
|
||||||
// Handle errors
|
|
||||||
if err != nil {
|
|
||||||
panic("Can't run remote command: " + err.Error())
|
|
||||||
} else {
|
|
||||||
// read from the output channel until the done signal is passed
|
|
||||||
isTimeout := true
|
|
||||||
loop:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case isTimeout = <-doneChan:
|
|
||||||
break loop
|
|
||||||
case outline := <-stdoutChan:
|
|
||||||
fmt.Println("out:", outline)
|
|
||||||
case errline := <-stderrChan:
|
|
||||||
fmt.Println("err:", errline)
|
|
||||||
case err = <-errChan:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get exit code or command error.
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("err: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// command time out
|
|
||||||
if !isTimeout {
|
|
||||||
fmt.Println("Error: command timeout")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
-300
@@ -1,300 +0,0 @@
|
|||||||
// Package easyssh provides a simple implementation of some SSH protocol
|
|
||||||
// features in Go. You can simply run a command on a remote server or get a file
|
|
||||||
// even simpler than native console SSH client. You don't need to think about
|
|
||||||
// Dials, sessions, defers, or public keys... Let easyssh think about it!
|
|
||||||
package easyssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
"golang.org/x/crypto/ssh/agent"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// MakeConfig Contains main authority information.
|
|
||||||
// User field should be a name of user on remote server (ex. john in ssh john@example.com).
|
|
||||||
// Server field should be a remote machine address (ex. example.com in ssh john@example.com)
|
|
||||||
// Key is a path to private key on your local machine.
|
|
||||||
// Port is SSH server port on remote machine.
|
|
||||||
// Note: easyssh looking for private key in user's home directory (ex. /home/john + Key).
|
|
||||||
// Then ensure your Key begins from '/' (ex. /.ssh/id_rsa)
|
|
||||||
MakeConfig struct {
|
|
||||||
User string
|
|
||||||
Server string
|
|
||||||
Key string
|
|
||||||
KeyPath string
|
|
||||||
Port string
|
|
||||||
Password string
|
|
||||||
Timeout time.Duration
|
|
||||||
Proxy DefaultConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultConfig for ssh proxy config
|
|
||||||
DefaultConfig struct {
|
|
||||||
User string
|
|
||||||
Server string
|
|
||||||
Key string
|
|
||||||
KeyPath string
|
|
||||||
Port string
|
|
||||||
Password string
|
|
||||||
Timeout time.Duration
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// returns ssh.Signer from user you running app home path + cutted key path.
|
|
||||||
// (ex. pubkey,err := getKeyFile("/.ssh/id_rsa") )
|
|
||||||
func getKeyFile(keypath string) (ssh.Signer, error) {
|
|
||||||
buf, err := ioutil.ReadFile(keypath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pubkey, err := ssh.ParsePrivateKey(buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return pubkey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSSHConfig(config DefaultConfig) *ssh.ClientConfig {
|
|
||||||
// auths holds the detected ssh auth methods
|
|
||||||
auths := []ssh.AuthMethod{}
|
|
||||||
|
|
||||||
// figure out what auths are requested, what is supported
|
|
||||||
if config.Password != "" {
|
|
||||||
auths = append(auths, ssh.Password(config.Password))
|
|
||||||
}
|
|
||||||
|
|
||||||
if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
|
||||||
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers))
|
|
||||||
defer sshAgent.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.KeyPath != "" {
|
|
||||||
if pubkey, err := getKeyFile(config.KeyPath); err == nil {
|
|
||||||
auths = append(auths, ssh.PublicKeys(pubkey))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Key != "" {
|
|
||||||
if signer, err := ssh.ParsePrivateKey([]byte(config.Key)); err == nil {
|
|
||||||
auths = append(auths, ssh.PublicKeys(signer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ssh.ClientConfig{
|
|
||||||
Timeout: config.Timeout,
|
|
||||||
User: config.User,
|
|
||||||
Auth: auths,
|
|
||||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// connect to remote server using MakeConfig struct and returns *ssh.Session
|
|
||||||
func (ssh_conf *MakeConfig) connect() (*ssh.Session, error) {
|
|
||||||
var client *ssh.Client
|
|
||||||
var err error
|
|
||||||
|
|
||||||
targetConfig := getSSHConfig(DefaultConfig{
|
|
||||||
User: ssh_conf.User,
|
|
||||||
Key: ssh_conf.Key,
|
|
||||||
KeyPath: ssh_conf.KeyPath,
|
|
||||||
Password: ssh_conf.Password,
|
|
||||||
Timeout: ssh_conf.Timeout,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Enable proxy command
|
|
||||||
if ssh_conf.Proxy.Server != "" {
|
|
||||||
proxyConfig := getSSHConfig(DefaultConfig{
|
|
||||||
User: ssh_conf.Proxy.User,
|
|
||||||
Key: ssh_conf.Proxy.Key,
|
|
||||||
KeyPath: ssh_conf.Proxy.KeyPath,
|
|
||||||
Password: ssh_conf.Proxy.Password,
|
|
||||||
Timeout: ssh_conf.Proxy.Timeout,
|
|
||||||
})
|
|
||||||
|
|
||||||
proxyClient, err := ssh.Dial("tcp", net.JoinHostPort(ssh_conf.Proxy.Server, ssh_conf.Proxy.Port), proxyConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := proxyClient.Dial("tcp", net.JoinHostPort(ssh_conf.Server, ssh_conf.Port))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ncc, chans, reqs, err := ssh.NewClientConn(conn, net.JoinHostPort(ssh_conf.Server, ssh_conf.Port), targetConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client = ssh.NewClient(ncc, chans, reqs)
|
|
||||||
} else {
|
|
||||||
client, err = ssh.Dial("tcp", net.JoinHostPort(ssh_conf.Server, ssh_conf.Port), targetConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
session, err := client.NewSession()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return session, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream returns one channel that combines the stdout and stderr of the command
|
|
||||||
// as it is run on the remote machine, and another that sends true when the
|
|
||||||
// command is done. The sessions and channels will then be closed.
|
|
||||||
func (ssh_conf *MakeConfig) Stream(command string, timeout int) (<-chan string, <-chan string, <-chan bool, <-chan error, error) {
|
|
||||||
// continuously send the command's output over the channel
|
|
||||||
stdoutChan := make(chan string)
|
|
||||||
stderrChan := make(chan string)
|
|
||||||
doneChan := make(chan bool)
|
|
||||||
errChan := make(chan error)
|
|
||||||
|
|
||||||
// connect to remote host
|
|
||||||
session, err := ssh_conf.connect()
|
|
||||||
if err != nil {
|
|
||||||
return stdoutChan, stderrChan, doneChan, errChan, err
|
|
||||||
}
|
|
||||||
// defer session.Close()
|
|
||||||
// connect to both outputs (they are of type io.Reader)
|
|
||||||
outReader, err := session.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
return stdoutChan, stderrChan, doneChan, errChan, err
|
|
||||||
}
|
|
||||||
errReader, err := session.StderrPipe()
|
|
||||||
if err != nil {
|
|
||||||
return stdoutChan, stderrChan, doneChan, errChan, err
|
|
||||||
}
|
|
||||||
err = session.Start(command)
|
|
||||||
if err != nil {
|
|
||||||
return stdoutChan, stderrChan, doneChan, errChan, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// combine outputs, create a line-by-line scanner
|
|
||||||
stdoutReader := io.MultiReader(outReader)
|
|
||||||
stderrReader := io.MultiReader(errReader)
|
|
||||||
stdoutScanner := bufio.NewScanner(stdoutReader)
|
|
||||||
stderrScanner := bufio.NewScanner(stderrReader)
|
|
||||||
|
|
||||||
go func(stdoutScanner, stderrScanner *bufio.Scanner, stdoutChan, stderrChan chan string, doneChan chan bool, errChan chan error) {
|
|
||||||
defer close(stdoutChan)
|
|
||||||
defer close(stderrChan)
|
|
||||||
defer close(doneChan)
|
|
||||||
defer close(errChan)
|
|
||||||
defer session.Close()
|
|
||||||
|
|
||||||
timeoutChan := time.After(time.Duration(timeout) * time.Second)
|
|
||||||
res := make(chan bool, 1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for stdoutScanner.Scan() {
|
|
||||||
stdoutChan <- stdoutScanner.Text()
|
|
||||||
}
|
|
||||||
for stderrScanner.Scan() {
|
|
||||||
stderrChan <- stderrScanner.Text()
|
|
||||||
}
|
|
||||||
// close all of our open resources
|
|
||||||
res <- true
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-res:
|
|
||||||
errChan <- session.Wait()
|
|
||||||
doneChan <- true
|
|
||||||
case <-timeoutChan:
|
|
||||||
stderrChan <- "Run Command Timeout!"
|
|
||||||
errChan <- nil
|
|
||||||
doneChan <- false
|
|
||||||
}
|
|
||||||
}(stdoutScanner, stderrScanner, stdoutChan, stderrChan, doneChan, errChan)
|
|
||||||
|
|
||||||
return stdoutChan, stderrChan, doneChan, errChan, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run command on remote machine and returns its stdout as a string
|
|
||||||
func (ssh_conf *MakeConfig) Run(command string, timeout int) (outStr string, errStr string, isTimeout bool, err error) {
|
|
||||||
stdoutChan, stderrChan, doneChan, errChan, err := ssh_conf.Stream(command, timeout)
|
|
||||||
if err != nil {
|
|
||||||
return outStr, errStr, isTimeout, err
|
|
||||||
}
|
|
||||||
// read from the output channel until the done signal is passed
|
|
||||||
loop:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case isTimeout = <-doneChan:
|
|
||||||
break loop
|
|
||||||
case outline := <-stdoutChan:
|
|
||||||
if outline != "" {
|
|
||||||
outStr += outline + "\n"
|
|
||||||
}
|
|
||||||
case errline := <-stderrChan:
|
|
||||||
if errline != "" {
|
|
||||||
errStr += errline + "\n"
|
|
||||||
}
|
|
||||||
case err = <-errChan:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// return the concatenation of all signals from the output channel
|
|
||||||
return outStr, errStr, isTimeout, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scp uploads sourceFile to remote machine like native scp console app.
|
|
||||||
func (ssh_conf *MakeConfig) Scp(sourceFile string, etargetFile string) error {
|
|
||||||
session, err := ssh_conf.connect()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer session.Close()
|
|
||||||
|
|
||||||
targetFile := filepath.Base(etargetFile)
|
|
||||||
|
|
||||||
src, srcErr := os.Open(sourceFile)
|
|
||||||
|
|
||||||
if srcErr != nil {
|
|
||||||
return srcErr
|
|
||||||
}
|
|
||||||
|
|
||||||
srcStat, statErr := src.Stat()
|
|
||||||
|
|
||||||
if statErr != nil {
|
|
||||||
return statErr
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
w, err := session.StdinPipe()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer w.Close()
|
|
||||||
|
|
||||||
fmt.Fprintln(w, "C0644", srcStat.Size(), targetFile)
|
|
||||||
|
|
||||||
if srcStat.Size() > 0 {
|
|
||||||
io.Copy(w, src)
|
|
||||||
fmt.Fprint(w, "\x00")
|
|
||||||
} else {
|
|
||||||
fmt.Fprint(w, "\x00")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := session.Run(fmt.Sprintf("scp -tr %s", etargetFile)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
ISC License
|
|
||||||
|
|
||||||
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and distribute this software for any
|
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
|
||||||
copyright notice and this permission notice appear in all copies.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
-152
@@ -1,152 +0,0 @@
|
|||||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
|
||||||
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
|
||||||
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
|
||||||
// tag is deprecated and thus should not be used.
|
|
||||||
// +build !js,!appengine,!safe,!disableunsafe
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
|
||||||
// not access to the unsafe package is available.
|
|
||||||
UnsafeDisabled = false
|
|
||||||
|
|
||||||
// ptrSize is the size of a pointer on the current arch.
|
|
||||||
ptrSize = unsafe.Sizeof((*byte)(nil))
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
|
|
||||||
// internal reflect.Value fields. These values are valid before golang
|
|
||||||
// commit ecccf07e7f9d which changed the format. The are also valid
|
|
||||||
// after commit 82f48826c6c7 which changed the format again to mirror
|
|
||||||
// the original format. Code in the init function updates these offsets
|
|
||||||
// as necessary.
|
|
||||||
offsetPtr = uintptr(ptrSize)
|
|
||||||
offsetScalar = uintptr(0)
|
|
||||||
offsetFlag = uintptr(ptrSize * 2)
|
|
||||||
|
|
||||||
// flagKindWidth and flagKindShift indicate various bits that the
|
|
||||||
// reflect package uses internally to track kind information.
|
|
||||||
//
|
|
||||||
// flagRO indicates whether or not the value field of a reflect.Value is
|
|
||||||
// read-only.
|
|
||||||
//
|
|
||||||
// flagIndir indicates whether the value field of a reflect.Value is
|
|
||||||
// the actual data or a pointer to the data.
|
|
||||||
//
|
|
||||||
// These values are valid before golang commit 90a7c3c86944 which
|
|
||||||
// changed their positions. Code in the init function updates these
|
|
||||||
// flags as necessary.
|
|
||||||
flagKindWidth = uintptr(5)
|
|
||||||
flagKindShift = uintptr(flagKindWidth - 1)
|
|
||||||
flagRO = uintptr(1 << 0)
|
|
||||||
flagIndir = uintptr(1 << 1)
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Older versions of reflect.Value stored small integers directly in the
|
|
||||||
// ptr field (which is named val in the older versions). Versions
|
|
||||||
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
|
|
||||||
// scalar for this purpose which unfortunately came before the flag
|
|
||||||
// field, so the offset of the flag field is different for those
|
|
||||||
// versions.
|
|
||||||
//
|
|
||||||
// This code constructs a new reflect.Value from a known small integer
|
|
||||||
// and checks if the size of the reflect.Value struct indicates it has
|
|
||||||
// the scalar field. When it does, the offsets are updated accordingly.
|
|
||||||
vv := reflect.ValueOf(0xf00)
|
|
||||||
if unsafe.Sizeof(vv) == (ptrSize * 4) {
|
|
||||||
offsetScalar = ptrSize * 2
|
|
||||||
offsetFlag = ptrSize * 3
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit 90a7c3c86944 changed the flag positions such that the low
|
|
||||||
// order bits are the kind. This code extracts the kind from the flags
|
|
||||||
// field and ensures it's the correct type. When it's not, the flag
|
|
||||||
// order has been changed to the newer format, so the flags are updated
|
|
||||||
// accordingly.
|
|
||||||
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
|
|
||||||
upfv := *(*uintptr)(upf)
|
|
||||||
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
|
|
||||||
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
|
|
||||||
flagKindShift = 0
|
|
||||||
flagRO = 1 << 5
|
|
||||||
flagIndir = 1 << 6
|
|
||||||
|
|
||||||
// Commit adf9b30e5594 modified the flags to separate the
|
|
||||||
// flagRO flag into two bits which specifies whether or not the
|
|
||||||
// field is embedded. This causes flagIndir to move over a bit
|
|
||||||
// and means that flagRO is the combination of either of the
|
|
||||||
// original flagRO bit and the new bit.
|
|
||||||
//
|
|
||||||
// This code detects the change by extracting what used to be
|
|
||||||
// the indirect bit to ensure it's set. When it's not, the flag
|
|
||||||
// order has been changed to the newer format, so the flags are
|
|
||||||
// updated accordingly.
|
|
||||||
if upfv&flagIndir == 0 {
|
|
||||||
flagRO = 3 << 5
|
|
||||||
flagIndir = 1 << 7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
|
||||||
// the typical safety restrictions preventing access to unaddressable and
|
|
||||||
// unexported data. It works by digging the raw pointer to the underlying
|
|
||||||
// value out of the protected value and generating a new unprotected (unsafe)
|
|
||||||
// reflect.Value to it.
|
|
||||||
//
|
|
||||||
// This allows us to check for implementations of the Stringer and error
|
|
||||||
// interfaces to be used for pretty printing ordinarily unaddressable and
|
|
||||||
// inaccessible values such as unexported struct fields.
|
|
||||||
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
|
|
||||||
indirects := 1
|
|
||||||
vt := v.Type()
|
|
||||||
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
|
|
||||||
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
|
|
||||||
if rvf&flagIndir != 0 {
|
|
||||||
vt = reflect.PtrTo(v.Type())
|
|
||||||
indirects++
|
|
||||||
} else if offsetScalar != 0 {
|
|
||||||
// The value is in the scalar field when it's not one of the
|
|
||||||
// reference types.
|
|
||||||
switch vt.Kind() {
|
|
||||||
case reflect.Uintptr:
|
|
||||||
case reflect.Chan:
|
|
||||||
case reflect.Func:
|
|
||||||
case reflect.Map:
|
|
||||||
case reflect.Ptr:
|
|
||||||
case reflect.UnsafePointer:
|
|
||||||
default:
|
|
||||||
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
|
|
||||||
offsetScalar)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pv := reflect.NewAt(vt, upv)
|
|
||||||
rv = pv
|
|
||||||
for i := 0; i < indirects; i++ {
|
|
||||||
rv = rv.Elem()
|
|
||||||
}
|
|
||||||
return rv
|
|
||||||
}
|
|
||||||
-38
@@ -1,38 +0,0 @@
|
|||||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
|
||||||
// when the code is running on Google App Engine, compiled by GopherJS, or
|
|
||||||
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
|
||||||
// tag is deprecated and thus should not be used.
|
|
||||||
// +build js appengine safe disableunsafe
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import "reflect"
|
|
||||||
|
|
||||||
const (
|
|
||||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
|
||||||
// not access to the unsafe package is available.
|
|
||||||
UnsafeDisabled = true
|
|
||||||
)
|
|
||||||
|
|
||||||
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
|
||||||
// that bypasses the typical safety restrictions preventing access to
|
|
||||||
// unaddressable and unexported data. However, doing this relies on access to
|
|
||||||
// the unsafe package. This is a stub version which simply returns the passed
|
|
||||||
// reflect.Value when the unsafe package is not available.
|
|
||||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
-341
@@ -1,341 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
|
||||||
// the technique used in the fmt package.
|
|
||||||
var (
|
|
||||||
panicBytes = []byte("(PANIC=")
|
|
||||||
plusBytes = []byte("+")
|
|
||||||
iBytes = []byte("i")
|
|
||||||
trueBytes = []byte("true")
|
|
||||||
falseBytes = []byte("false")
|
|
||||||
interfaceBytes = []byte("(interface {})")
|
|
||||||
commaNewlineBytes = []byte(",\n")
|
|
||||||
newlineBytes = []byte("\n")
|
|
||||||
openBraceBytes = []byte("{")
|
|
||||||
openBraceNewlineBytes = []byte("{\n")
|
|
||||||
closeBraceBytes = []byte("}")
|
|
||||||
asteriskBytes = []byte("*")
|
|
||||||
colonBytes = []byte(":")
|
|
||||||
colonSpaceBytes = []byte(": ")
|
|
||||||
openParenBytes = []byte("(")
|
|
||||||
closeParenBytes = []byte(")")
|
|
||||||
spaceBytes = []byte(" ")
|
|
||||||
pointerChainBytes = []byte("->")
|
|
||||||
nilAngleBytes = []byte("<nil>")
|
|
||||||
maxNewlineBytes = []byte("<max depth reached>\n")
|
|
||||||
maxShortBytes = []byte("<max>")
|
|
||||||
circularBytes = []byte("<already shown>")
|
|
||||||
circularShortBytes = []byte("<shown>")
|
|
||||||
invalidAngleBytes = []byte("<invalid>")
|
|
||||||
openBracketBytes = []byte("[")
|
|
||||||
closeBracketBytes = []byte("]")
|
|
||||||
percentBytes = []byte("%")
|
|
||||||
precisionBytes = []byte(".")
|
|
||||||
openAngleBytes = []byte("<")
|
|
||||||
closeAngleBytes = []byte(">")
|
|
||||||
openMapBytes = []byte("map[")
|
|
||||||
closeMapBytes = []byte("]")
|
|
||||||
lenEqualsBytes = []byte("len=")
|
|
||||||
capEqualsBytes = []byte("cap=")
|
|
||||||
)
|
|
||||||
|
|
||||||
// hexDigits is used to map a decimal value to a hex digit.
|
|
||||||
var hexDigits = "0123456789abcdef"
|
|
||||||
|
|
||||||
// catchPanic handles any panics that might occur during the handleMethods
|
|
||||||
// calls.
|
|
||||||
func catchPanic(w io.Writer, v reflect.Value) {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
w.Write(panicBytes)
|
|
||||||
fmt.Fprintf(w, "%v", err)
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleMethods attempts to call the Error and String methods on the underlying
|
|
||||||
// type the passed reflect.Value represents and outputes the result to Writer w.
|
|
||||||
//
|
|
||||||
// It handles panics in any called methods by catching and displaying the error
|
|
||||||
// as the formatted value.
|
|
||||||
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
|
||||||
// We need an interface to check if the type implements the error or
|
|
||||||
// Stringer interface. However, the reflect package won't give us an
|
|
||||||
// interface on certain things like unexported struct fields in order
|
|
||||||
// to enforce visibility rules. We use unsafe, when it's available,
|
|
||||||
// to bypass these restrictions since this package does not mutate the
|
|
||||||
// values.
|
|
||||||
if !v.CanInterface() {
|
|
||||||
if UnsafeDisabled {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
v = unsafeReflectValue(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Choose whether or not to do error and Stringer interface lookups against
|
|
||||||
// the base type or a pointer to the base type depending on settings.
|
|
||||||
// Technically calling one of these methods with a pointer receiver can
|
|
||||||
// mutate the value, however, types which choose to satisify an error or
|
|
||||||
// Stringer interface with a pointer receiver should not be mutating their
|
|
||||||
// state inside these interface methods.
|
|
||||||
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
|
||||||
v = unsafeReflectValue(v)
|
|
||||||
}
|
|
||||||
if v.CanAddr() {
|
|
||||||
v = v.Addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is it an error or Stringer?
|
|
||||||
switch iface := v.Interface().(type) {
|
|
||||||
case error:
|
|
||||||
defer catchPanic(w, v)
|
|
||||||
if cs.ContinueOnMethod {
|
|
||||||
w.Write(openParenBytes)
|
|
||||||
w.Write([]byte(iface.Error()))
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
w.Write(spaceBytes)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write([]byte(iface.Error()))
|
|
||||||
return true
|
|
||||||
|
|
||||||
case fmt.Stringer:
|
|
||||||
defer catchPanic(w, v)
|
|
||||||
if cs.ContinueOnMethod {
|
|
||||||
w.Write(openParenBytes)
|
|
||||||
w.Write([]byte(iface.String()))
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
w.Write(spaceBytes)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
w.Write([]byte(iface.String()))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// printBool outputs a boolean value as true or false to Writer w.
|
|
||||||
func printBool(w io.Writer, val bool) {
|
|
||||||
if val {
|
|
||||||
w.Write(trueBytes)
|
|
||||||
} else {
|
|
||||||
w.Write(falseBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// printInt outputs a signed integer value to Writer w.
|
|
||||||
func printInt(w io.Writer, val int64, base int) {
|
|
||||||
w.Write([]byte(strconv.FormatInt(val, base)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// printUint outputs an unsigned integer value to Writer w.
|
|
||||||
func printUint(w io.Writer, val uint64, base int) {
|
|
||||||
w.Write([]byte(strconv.FormatUint(val, base)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// printFloat outputs a floating point value using the specified precision,
|
|
||||||
// which is expected to be 32 or 64bit, to Writer w.
|
|
||||||
func printFloat(w io.Writer, val float64, precision int) {
|
|
||||||
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// printComplex outputs a complex value using the specified float precision
|
|
||||||
// for the real and imaginary parts to Writer w.
|
|
||||||
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
|
||||||
r := real(c)
|
|
||||||
w.Write(openParenBytes)
|
|
||||||
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
|
||||||
i := imag(c)
|
|
||||||
if i >= 0 {
|
|
||||||
w.Write(plusBytes)
|
|
||||||
}
|
|
||||||
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
|
||||||
w.Write(iBytes)
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
|
|
||||||
// prefix to Writer w.
|
|
||||||
func printHexPtr(w io.Writer, p uintptr) {
|
|
||||||
// Null pointer.
|
|
||||||
num := uint64(p)
|
|
||||||
if num == 0 {
|
|
||||||
w.Write(nilAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
|
||||||
buf := make([]byte, 18)
|
|
||||||
|
|
||||||
// It's simpler to construct the hex string right to left.
|
|
||||||
base := uint64(16)
|
|
||||||
i := len(buf) - 1
|
|
||||||
for num >= base {
|
|
||||||
buf[i] = hexDigits[num%base]
|
|
||||||
num /= base
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
buf[i] = hexDigits[num]
|
|
||||||
|
|
||||||
// Add '0x' prefix.
|
|
||||||
i--
|
|
||||||
buf[i] = 'x'
|
|
||||||
i--
|
|
||||||
buf[i] = '0'
|
|
||||||
|
|
||||||
// Strip unused leading bytes.
|
|
||||||
buf = buf[i:]
|
|
||||||
w.Write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
|
||||||
// elements to be sorted.
|
|
||||||
type valuesSorter struct {
|
|
||||||
values []reflect.Value
|
|
||||||
strings []string // either nil or same len and values
|
|
||||||
cs *ConfigState
|
|
||||||
}
|
|
||||||
|
|
||||||
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
|
||||||
// surrogate keys on which the data should be sorted. It uses flags in
|
|
||||||
// ConfigState to decide if and how to populate those surrogate keys.
|
|
||||||
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
|
||||||
vs := &valuesSorter{values: values, cs: cs}
|
|
||||||
if canSortSimply(vs.values[0].Kind()) {
|
|
||||||
return vs
|
|
||||||
}
|
|
||||||
if !cs.DisableMethods {
|
|
||||||
vs.strings = make([]string, len(values))
|
|
||||||
for i := range vs.values {
|
|
||||||
b := bytes.Buffer{}
|
|
||||||
if !handleMethods(cs, &b, vs.values[i]) {
|
|
||||||
vs.strings = nil
|
|
||||||
break
|
|
||||||
}
|
|
||||||
vs.strings[i] = b.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if vs.strings == nil && cs.SpewKeys {
|
|
||||||
vs.strings = make([]string, len(values))
|
|
||||||
for i := range vs.values {
|
|
||||||
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vs
|
|
||||||
}
|
|
||||||
|
|
||||||
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
|
||||||
// directly, or whether it should be considered for sorting by surrogate keys
|
|
||||||
// (if the ConfigState allows it).
|
|
||||||
func canSortSimply(kind reflect.Kind) bool {
|
|
||||||
// This switch parallels valueSortLess, except for the default case.
|
|
||||||
switch kind {
|
|
||||||
case reflect.Bool:
|
|
||||||
return true
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
return true
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
return true
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return true
|
|
||||||
case reflect.String:
|
|
||||||
return true
|
|
||||||
case reflect.Uintptr:
|
|
||||||
return true
|
|
||||||
case reflect.Array:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of values in the slice. It is part of the
|
|
||||||
// sort.Interface implementation.
|
|
||||||
func (s *valuesSorter) Len() int {
|
|
||||||
return len(s.values)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap swaps the values at the passed indices. It is part of the
|
|
||||||
// sort.Interface implementation.
|
|
||||||
func (s *valuesSorter) Swap(i, j int) {
|
|
||||||
s.values[i], s.values[j] = s.values[j], s.values[i]
|
|
||||||
if s.strings != nil {
|
|
||||||
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// valueSortLess returns whether the first value should sort before the second
|
|
||||||
// value. It is used by valueSorter.Less as part of the sort.Interface
|
|
||||||
// implementation.
|
|
||||||
func valueSortLess(a, b reflect.Value) bool {
|
|
||||||
switch a.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
return !a.Bool() && b.Bool()
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
return a.Int() < b.Int()
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
return a.Uint() < b.Uint()
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return a.Float() < b.Float()
|
|
||||||
case reflect.String:
|
|
||||||
return a.String() < b.String()
|
|
||||||
case reflect.Uintptr:
|
|
||||||
return a.Uint() < b.Uint()
|
|
||||||
case reflect.Array:
|
|
||||||
// Compare the contents of both arrays.
|
|
||||||
l := a.Len()
|
|
||||||
for i := 0; i < l; i++ {
|
|
||||||
av := a.Index(i)
|
|
||||||
bv := b.Index(i)
|
|
||||||
if av.Interface() == bv.Interface() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return valueSortLess(av, bv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return a.String() < b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Less returns whether the value at index i should sort before the
|
|
||||||
// value at index j. It is part of the sort.Interface implementation.
|
|
||||||
func (s *valuesSorter) Less(i, j int) bool {
|
|
||||||
if s.strings == nil {
|
|
||||||
return valueSortLess(s.values[i], s.values[j])
|
|
||||||
}
|
|
||||||
return s.strings[i] < s.strings[j]
|
|
||||||
}
|
|
||||||
|
|
||||||
// sortValues is a sort function that handles both native types and any type that
|
|
||||||
// can be converted to error or Stringer. Other inputs are sorted according to
|
|
||||||
// their Value.String() value to ensure display stability.
|
|
||||||
func sortValues(values []reflect.Value, cs *ConfigState) {
|
|
||||||
if len(values) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sort.Sort(newValuesSorter(values, cs))
|
|
||||||
}
|
|
||||||
-306
@@ -1,306 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConfigState houses the configuration options used by spew to format and
|
|
||||||
// display values. There is a global instance, Config, that is used to control
|
|
||||||
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
|
||||||
// provides methods equivalent to the top-level functions.
|
|
||||||
//
|
|
||||||
// The zero value for ConfigState provides no indentation. You would typically
|
|
||||||
// want to set it to a space or a tab.
|
|
||||||
//
|
|
||||||
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
|
||||||
// with default settings. See the documentation of NewDefaultConfig for default
|
|
||||||
// values.
|
|
||||||
type ConfigState struct {
|
|
||||||
// Indent specifies the string to use for each indentation level. The
|
|
||||||
// global config instance that all top-level functions use set this to a
|
|
||||||
// single space by default. If you would like more indentation, you might
|
|
||||||
// set this to a tab with "\t" or perhaps two spaces with " ".
|
|
||||||
Indent string
|
|
||||||
|
|
||||||
// MaxDepth controls the maximum number of levels to descend into nested
|
|
||||||
// data structures. The default, 0, means there is no limit.
|
|
||||||
//
|
|
||||||
// NOTE: Circular data structures are properly detected, so it is not
|
|
||||||
// necessary to set this value unless you specifically want to limit deeply
|
|
||||||
// nested data structures.
|
|
||||||
MaxDepth int
|
|
||||||
|
|
||||||
// DisableMethods specifies whether or not error and Stringer interfaces are
|
|
||||||
// invoked for types that implement them.
|
|
||||||
DisableMethods bool
|
|
||||||
|
|
||||||
// DisablePointerMethods specifies whether or not to check for and invoke
|
|
||||||
// error and Stringer interfaces on types which only accept a pointer
|
|
||||||
// receiver when the current type is not a pointer.
|
|
||||||
//
|
|
||||||
// NOTE: This might be an unsafe action since calling one of these methods
|
|
||||||
// with a pointer receiver could technically mutate the value, however,
|
|
||||||
// in practice, types which choose to satisify an error or Stringer
|
|
||||||
// interface with a pointer receiver should not be mutating their state
|
|
||||||
// inside these interface methods. As a result, this option relies on
|
|
||||||
// access to the unsafe package, so it will not have any effect when
|
|
||||||
// running in environments without access to the unsafe package such as
|
|
||||||
// Google App Engine or with the "safe" build tag specified.
|
|
||||||
DisablePointerMethods bool
|
|
||||||
|
|
||||||
// DisablePointerAddresses specifies whether to disable the printing of
|
|
||||||
// pointer addresses. This is useful when diffing data structures in tests.
|
|
||||||
DisablePointerAddresses bool
|
|
||||||
|
|
||||||
// DisableCapacities specifies whether to disable the printing of capacities
|
|
||||||
// for arrays, slices, maps and channels. This is useful when diffing
|
|
||||||
// data structures in tests.
|
|
||||||
DisableCapacities bool
|
|
||||||
|
|
||||||
// ContinueOnMethod specifies whether or not recursion should continue once
|
|
||||||
// a custom error or Stringer interface is invoked. The default, false,
|
|
||||||
// means it will print the results of invoking the custom error or Stringer
|
|
||||||
// interface and return immediately instead of continuing to recurse into
|
|
||||||
// the internals of the data type.
|
|
||||||
//
|
|
||||||
// NOTE: This flag does not have any effect if method invocation is disabled
|
|
||||||
// via the DisableMethods or DisablePointerMethods options.
|
|
||||||
ContinueOnMethod bool
|
|
||||||
|
|
||||||
// SortKeys specifies map keys should be sorted before being printed. Use
|
|
||||||
// this to have a more deterministic, diffable output. Note that only
|
|
||||||
// native types (bool, int, uint, floats, uintptr and string) and types
|
|
||||||
// that support the error or Stringer interfaces (if methods are
|
|
||||||
// enabled) are supported, with other types sorted according to the
|
|
||||||
// reflect.Value.String() output which guarantees display stability.
|
|
||||||
SortKeys bool
|
|
||||||
|
|
||||||
// SpewKeys specifies that, as a last resort attempt, map keys should
|
|
||||||
// be spewed to strings and sorted by those strings. This is only
|
|
||||||
// considered if SortKeys is true.
|
|
||||||
SpewKeys bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config is the active configuration of the top-level functions.
|
|
||||||
// The configuration can be changed by modifying the contents of spew.Config.
|
|
||||||
var Config = ConfigState{Indent: " "}
|
|
||||||
|
|
||||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the formatted string as a value that satisfies error. See NewFormatter
|
|
||||||
// for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
|
||||||
return fmt.Errorf(format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprint(w, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintln(w, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Print(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Printf(format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Println(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Sprint(a ...interface{}) string {
|
|
||||||
return fmt.Sprint(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
|
||||||
return fmt.Sprintf(format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
|
||||||
// were passed with a Formatter interface returned by c.NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
|
||||||
return fmt.Sprintln(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
|
||||||
interface. As a result, it integrates cleanly with standard fmt package
|
|
||||||
printing functions. The formatter is useful for inline printing of smaller data
|
|
||||||
types similar to the standard %v format specifier.
|
|
||||||
|
|
||||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
|
||||||
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
|
||||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
|
||||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
|
||||||
the width and precision arguments (however they will still work on the format
|
|
||||||
specifiers not handled by the custom formatter).
|
|
||||||
|
|
||||||
Typically this function shouldn't be called directly. It is much easier to make
|
|
||||||
use of the custom formatter by calling one of the convenience functions such as
|
|
||||||
c.Printf, c.Println, or c.Printf.
|
|
||||||
*/
|
|
||||||
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
|
||||||
return newFormatter(c, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
|
||||||
// exactly the same as Dump.
|
|
||||||
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
|
||||||
fdump(c, w, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Dump displays the passed parameters to standard out with newlines, customizable
|
|
||||||
indentation, and additional debug information such as complete types and all
|
|
||||||
pointer addresses used to indirect to the final value. It provides the
|
|
||||||
following features over the built-in printing facilities provided by the fmt
|
|
||||||
package:
|
|
||||||
|
|
||||||
* Pointers are dereferenced and followed
|
|
||||||
* Circular data structures are detected and handled properly
|
|
||||||
* Custom Stringer/error interfaces are optionally invoked, including
|
|
||||||
on unexported types
|
|
||||||
* Custom types which only implement the Stringer/error interfaces via
|
|
||||||
a pointer receiver are optionally invoked when passing non-pointer
|
|
||||||
variables
|
|
||||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
|
||||||
includes offsets, byte values in hex, and ASCII output
|
|
||||||
|
|
||||||
The configuration options are controlled by modifying the public members
|
|
||||||
of c. See ConfigState for options documentation.
|
|
||||||
|
|
||||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
|
||||||
get the formatted result as a string.
|
|
||||||
*/
|
|
||||||
func (c *ConfigState) Dump(a ...interface{}) {
|
|
||||||
fdump(c, os.Stdout, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
|
||||||
// as Dump.
|
|
||||||
func (c *ConfigState) Sdump(a ...interface{}) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fdump(c, &buf, a...)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
|
||||||
// length with each argument converted to a spew Formatter interface using
|
|
||||||
// the ConfigState associated with s.
|
|
||||||
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
|
||||||
formatters = make([]interface{}, len(args))
|
|
||||||
for index, arg := range args {
|
|
||||||
formatters[index] = newFormatter(c, arg)
|
|
||||||
}
|
|
||||||
return formatters
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDefaultConfig returns a ConfigState with the following default settings.
|
|
||||||
//
|
|
||||||
// Indent: " "
|
|
||||||
// MaxDepth: 0
|
|
||||||
// DisableMethods: false
|
|
||||||
// DisablePointerMethods: false
|
|
||||||
// ContinueOnMethod: false
|
|
||||||
// SortKeys: false
|
|
||||||
func NewDefaultConfig() *ConfigState {
|
|
||||||
return &ConfigState{Indent: " "}
|
|
||||||
}
|
|
||||||
-211
@@ -1,211 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package spew implements a deep pretty printer for Go data structures to aid in
|
|
||||||
debugging.
|
|
||||||
|
|
||||||
A quick overview of the additional features spew provides over the built-in
|
|
||||||
printing facilities for Go data types are as follows:
|
|
||||||
|
|
||||||
* Pointers are dereferenced and followed
|
|
||||||
* Circular data structures are detected and handled properly
|
|
||||||
* Custom Stringer/error interfaces are optionally invoked, including
|
|
||||||
on unexported types
|
|
||||||
* Custom types which only implement the Stringer/error interfaces via
|
|
||||||
a pointer receiver are optionally invoked when passing non-pointer
|
|
||||||
variables
|
|
||||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
|
||||||
includes offsets, byte values in hex, and ASCII output (only when using
|
|
||||||
Dump style)
|
|
||||||
|
|
||||||
There are two different approaches spew allows for dumping Go data structures:
|
|
||||||
|
|
||||||
* Dump style which prints with newlines, customizable indentation,
|
|
||||||
and additional debug information such as types and all pointer addresses
|
|
||||||
used to indirect to the final value
|
|
||||||
* A custom Formatter interface that integrates cleanly with the standard fmt
|
|
||||||
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
|
||||||
similar to the default %v while providing the additional functionality
|
|
||||||
outlined above and passing unsupported format verbs such as %x and %q
|
|
||||||
along to fmt
|
|
||||||
|
|
||||||
Quick Start
|
|
||||||
|
|
||||||
This section demonstrates how to quickly get started with spew. See the
|
|
||||||
sections below for further details on formatting and configuration options.
|
|
||||||
|
|
||||||
To dump a variable with full newlines, indentation, type, and pointer
|
|
||||||
information use Dump, Fdump, or Sdump:
|
|
||||||
spew.Dump(myVar1, myVar2, ...)
|
|
||||||
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
|
||||||
str := spew.Sdump(myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
Alternatively, if you would prefer to use format strings with a compacted inline
|
|
||||||
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
|
||||||
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
|
||||||
%#+v (adds types and pointer addresses):
|
|
||||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
|
|
||||||
Configuration Options
|
|
||||||
|
|
||||||
Configuration of spew is handled by fields in the ConfigState type. For
|
|
||||||
convenience, all of the top-level functions use a global state available
|
|
||||||
via the spew.Config global.
|
|
||||||
|
|
||||||
It is also possible to create a ConfigState instance that provides methods
|
|
||||||
equivalent to the top-level functions. This allows concurrent configuration
|
|
||||||
options. See the ConfigState documentation for more details.
|
|
||||||
|
|
||||||
The following configuration options are available:
|
|
||||||
* Indent
|
|
||||||
String to use for each indentation level for Dump functions.
|
|
||||||
It is a single space by default. A popular alternative is "\t".
|
|
||||||
|
|
||||||
* MaxDepth
|
|
||||||
Maximum number of levels to descend into nested data structures.
|
|
||||||
There is no limit by default.
|
|
||||||
|
|
||||||
* DisableMethods
|
|
||||||
Disables invocation of error and Stringer interface methods.
|
|
||||||
Method invocation is enabled by default.
|
|
||||||
|
|
||||||
* DisablePointerMethods
|
|
||||||
Disables invocation of error and Stringer interface methods on types
|
|
||||||
which only accept pointer receivers from non-pointer variables.
|
|
||||||
Pointer method invocation is enabled by default.
|
|
||||||
|
|
||||||
* DisablePointerAddresses
|
|
||||||
DisablePointerAddresses specifies whether to disable the printing of
|
|
||||||
pointer addresses. This is useful when diffing data structures in tests.
|
|
||||||
|
|
||||||
* DisableCapacities
|
|
||||||
DisableCapacities specifies whether to disable the printing of
|
|
||||||
capacities for arrays, slices, maps and channels. This is useful when
|
|
||||||
diffing data structures in tests.
|
|
||||||
|
|
||||||
* ContinueOnMethod
|
|
||||||
Enables recursion into types after invoking error and Stringer interface
|
|
||||||
methods. Recursion after method invocation is disabled by default.
|
|
||||||
|
|
||||||
* SortKeys
|
|
||||||
Specifies map keys should be sorted before being printed. Use
|
|
||||||
this to have a more deterministic, diffable output. Note that
|
|
||||||
only native types (bool, int, uint, floats, uintptr and string)
|
|
||||||
and types which implement error or Stringer interfaces are
|
|
||||||
supported with other types sorted according to the
|
|
||||||
reflect.Value.String() output which guarantees display
|
|
||||||
stability. Natural map order is used by default.
|
|
||||||
|
|
||||||
* SpewKeys
|
|
||||||
Specifies that, as a last resort attempt, map keys should be
|
|
||||||
spewed to strings and sorted by those strings. This is only
|
|
||||||
considered if SortKeys is true.
|
|
||||||
|
|
||||||
Dump Usage
|
|
||||||
|
|
||||||
Simply call spew.Dump with a list of variables you want to dump:
|
|
||||||
|
|
||||||
spew.Dump(myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
|
||||||
io.Writer. For example, to dump to standard error:
|
|
||||||
|
|
||||||
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
A third option is to call spew.Sdump to get the formatted output as a string:
|
|
||||||
|
|
||||||
str := spew.Sdump(myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
Sample Dump Output
|
|
||||||
|
|
||||||
See the Dump example for details on the setup of the types and variables being
|
|
||||||
shown here.
|
|
||||||
|
|
||||||
(main.Foo) {
|
|
||||||
unexportedField: (*main.Bar)(0xf84002e210)({
|
|
||||||
flag: (main.Flag) flagTwo,
|
|
||||||
data: (uintptr) <nil>
|
|
||||||
}),
|
|
||||||
ExportedField: (map[interface {}]interface {}) (len=1) {
|
|
||||||
(string) (len=3) "one": (bool) true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
|
||||||
command as shown.
|
|
||||||
([]uint8) (len=32 cap=32) {
|
|
||||||
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
|
||||||
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
|
||||||
00000020 31 32 |12|
|
|
||||||
}
|
|
||||||
|
|
||||||
Custom Formatter
|
|
||||||
|
|
||||||
Spew provides a custom formatter that implements the fmt.Formatter interface
|
|
||||||
so that it integrates cleanly with standard fmt package printing functions. The
|
|
||||||
formatter is useful for inline printing of smaller data types similar to the
|
|
||||||
standard %v format specifier.
|
|
||||||
|
|
||||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
|
||||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
|
||||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
|
||||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
|
||||||
the width and precision arguments (however they will still work on the format
|
|
||||||
specifiers not handled by the custom formatter).
|
|
||||||
|
|
||||||
Custom Formatter Usage
|
|
||||||
|
|
||||||
The simplest way to make use of the spew custom formatter is to call one of the
|
|
||||||
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
|
||||||
functions have syntax you are most likely already familiar with:
|
|
||||||
|
|
||||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
spew.Println(myVar, myVar2)
|
|
||||||
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
|
|
||||||
See the Index for the full list convenience functions.
|
|
||||||
|
|
||||||
Sample Formatter Output
|
|
||||||
|
|
||||||
Double pointer to a uint8:
|
|
||||||
%v: <**>5
|
|
||||||
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
|
||||||
%#v: (**uint8)5
|
|
||||||
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
|
||||||
|
|
||||||
Pointer to circular struct with a uint8 field and a pointer to itself:
|
|
||||||
%v: <*>{1 <*><shown>}
|
|
||||||
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
|
||||||
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
|
||||||
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
|
||||||
|
|
||||||
See the Printf example for details on the setup of variables being shown
|
|
||||||
here.
|
|
||||||
|
|
||||||
Errors
|
|
||||||
|
|
||||||
Since it is possible for custom Stringer/error interfaces to panic, spew
|
|
||||||
detects them and handles them internally by printing the panic information
|
|
||||||
inline with the output. Since spew is intended to provide deep pretty printing
|
|
||||||
capabilities on structures, it intentionally does not return any errors.
|
|
||||||
*/
|
|
||||||
package spew
|
|
||||||
-509
@@ -1,509 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// uint8Type is a reflect.Type representing a uint8. It is used to
|
|
||||||
// convert cgo types to uint8 slices for hexdumping.
|
|
||||||
uint8Type = reflect.TypeOf(uint8(0))
|
|
||||||
|
|
||||||
// cCharRE is a regular expression that matches a cgo char.
|
|
||||||
// It is used to detect character arrays to hexdump them.
|
|
||||||
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
|
|
||||||
|
|
||||||
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
|
||||||
// char. It is used to detect unsigned character arrays to hexdump
|
|
||||||
// them.
|
|
||||||
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
|
|
||||||
|
|
||||||
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
|
||||||
// It is used to detect uint8_t arrays to hexdump them.
|
|
||||||
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
|
|
||||||
)
|
|
||||||
|
|
||||||
// dumpState contains information about the state of a dump operation.
|
|
||||||
type dumpState struct {
|
|
||||||
w io.Writer
|
|
||||||
depth int
|
|
||||||
pointers map[uintptr]int
|
|
||||||
ignoreNextType bool
|
|
||||||
ignoreNextIndent bool
|
|
||||||
cs *ConfigState
|
|
||||||
}
|
|
||||||
|
|
||||||
// indent performs indentation according to the depth level and cs.Indent
|
|
||||||
// option.
|
|
||||||
func (d *dumpState) indent() {
|
|
||||||
if d.ignoreNextIndent {
|
|
||||||
d.ignoreNextIndent = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpackValue returns values inside of non-nil interfaces when possible.
|
|
||||||
// This is useful for data types like structs, arrays, slices, and maps which
|
|
||||||
// can contain varying types packed inside an interface.
|
|
||||||
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
|
||||||
if v.Kind() == reflect.Interface && !v.IsNil() {
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
|
||||||
func (d *dumpState) dumpPtr(v reflect.Value) {
|
|
||||||
// Remove pointers at or below the current depth from map used to detect
|
|
||||||
// circular refs.
|
|
||||||
for k, depth := range d.pointers {
|
|
||||||
if depth >= d.depth {
|
|
||||||
delete(d.pointers, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep list of all dereferenced pointers to show later.
|
|
||||||
pointerChain := make([]uintptr, 0)
|
|
||||||
|
|
||||||
// Figure out how many levels of indirection there are by dereferencing
|
|
||||||
// pointers and unpacking interfaces down the chain while detecting circular
|
|
||||||
// references.
|
|
||||||
nilFound := false
|
|
||||||
cycleFound := false
|
|
||||||
indirects := 0
|
|
||||||
ve := v
|
|
||||||
for ve.Kind() == reflect.Ptr {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
indirects++
|
|
||||||
addr := ve.Pointer()
|
|
||||||
pointerChain = append(pointerChain, addr)
|
|
||||||
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
|
||||||
cycleFound = true
|
|
||||||
indirects--
|
|
||||||
break
|
|
||||||
}
|
|
||||||
d.pointers[addr] = d.depth
|
|
||||||
|
|
||||||
ve = ve.Elem()
|
|
||||||
if ve.Kind() == reflect.Interface {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ve = ve.Elem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display type information.
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
|
||||||
d.w.Write([]byte(ve.Type().String()))
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
|
|
||||||
// Display pointer information.
|
|
||||||
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
for i, addr := range pointerChain {
|
|
||||||
if i > 0 {
|
|
||||||
d.w.Write(pointerChainBytes)
|
|
||||||
}
|
|
||||||
printHexPtr(d.w, addr)
|
|
||||||
}
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display dereferenced value.
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
switch {
|
|
||||||
case nilFound == true:
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
|
|
||||||
case cycleFound == true:
|
|
||||||
d.w.Write(circularBytes)
|
|
||||||
|
|
||||||
default:
|
|
||||||
d.ignoreNextType = true
|
|
||||||
d.dump(ve)
|
|
||||||
}
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
|
||||||
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
|
||||||
func (d *dumpState) dumpSlice(v reflect.Value) {
|
|
||||||
// Determine whether this type should be hex dumped or not. Also,
|
|
||||||
// for types which should be hexdumped, try to use the underlying data
|
|
||||||
// first, then fall back to trying to convert them to a uint8 slice.
|
|
||||||
var buf []uint8
|
|
||||||
doConvert := false
|
|
||||||
doHexDump := false
|
|
||||||
numEntries := v.Len()
|
|
||||||
if numEntries > 0 {
|
|
||||||
vt := v.Index(0).Type()
|
|
||||||
vts := vt.String()
|
|
||||||
switch {
|
|
||||||
// C types that need to be converted.
|
|
||||||
case cCharRE.MatchString(vts):
|
|
||||||
fallthrough
|
|
||||||
case cUnsignedCharRE.MatchString(vts):
|
|
||||||
fallthrough
|
|
||||||
case cUint8tCharRE.MatchString(vts):
|
|
||||||
doConvert = true
|
|
||||||
|
|
||||||
// Try to use existing uint8 slices and fall back to converting
|
|
||||||
// and copying if that fails.
|
|
||||||
case vt.Kind() == reflect.Uint8:
|
|
||||||
// We need an addressable interface to convert the type
|
|
||||||
// to a byte slice. However, the reflect package won't
|
|
||||||
// give us an interface on certain things like
|
|
||||||
// unexported struct fields in order to enforce
|
|
||||||
// visibility rules. We use unsafe, when available, to
|
|
||||||
// bypass these restrictions since this package does not
|
|
||||||
// mutate the values.
|
|
||||||
vs := v
|
|
||||||
if !vs.CanInterface() || !vs.CanAddr() {
|
|
||||||
vs = unsafeReflectValue(vs)
|
|
||||||
}
|
|
||||||
if !UnsafeDisabled {
|
|
||||||
vs = vs.Slice(0, numEntries)
|
|
||||||
|
|
||||||
// Use the existing uint8 slice if it can be
|
|
||||||
// type asserted.
|
|
||||||
iface := vs.Interface()
|
|
||||||
if slice, ok := iface.([]uint8); ok {
|
|
||||||
buf = slice
|
|
||||||
doHexDump = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The underlying data needs to be converted if it can't
|
|
||||||
// be type asserted to a uint8 slice.
|
|
||||||
doConvert = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy and convert the underlying type if needed.
|
|
||||||
if doConvert && vt.ConvertibleTo(uint8Type) {
|
|
||||||
// Convert and copy each element into a uint8 byte
|
|
||||||
// slice.
|
|
||||||
buf = make([]uint8, numEntries)
|
|
||||||
for i := 0; i < numEntries; i++ {
|
|
||||||
vv := v.Index(i)
|
|
||||||
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
|
||||||
}
|
|
||||||
doHexDump = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hexdump the entire slice as needed.
|
|
||||||
if doHexDump {
|
|
||||||
indent := strings.Repeat(d.cs.Indent, d.depth)
|
|
||||||
str := indent + hex.Dump(buf)
|
|
||||||
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
|
||||||
str = strings.TrimRight(str, d.cs.Indent)
|
|
||||||
d.w.Write([]byte(str))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recursively call dump for each item.
|
|
||||||
for i := 0; i < numEntries; i++ {
|
|
||||||
d.dump(d.unpackValue(v.Index(i)))
|
|
||||||
if i < (numEntries - 1) {
|
|
||||||
d.w.Write(commaNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
|
||||||
// value to figure out what kind of object we are dealing with and formats it
|
|
||||||
// appropriately. It is a recursive function, however circular data structures
|
|
||||||
// are detected and handled properly.
|
|
||||||
func (d *dumpState) dump(v reflect.Value) {
|
|
||||||
// Handle invalid reflect values immediately.
|
|
||||||
kind := v.Kind()
|
|
||||||
if kind == reflect.Invalid {
|
|
||||||
d.w.Write(invalidAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle pointers specially.
|
|
||||||
if kind == reflect.Ptr {
|
|
||||||
d.indent()
|
|
||||||
d.dumpPtr(v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print type information unless already handled elsewhere.
|
|
||||||
if !d.ignoreNextType {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
d.w.Write([]byte(v.Type().String()))
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
d.w.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
d.ignoreNextType = false
|
|
||||||
|
|
||||||
// Display length and capacity if the built-in len and cap functions
|
|
||||||
// work with the value's kind and the len/cap itself is non-zero.
|
|
||||||
valueLen, valueCap := 0, 0
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Array, reflect.Slice, reflect.Chan:
|
|
||||||
valueLen, valueCap = v.Len(), v.Cap()
|
|
||||||
case reflect.Map, reflect.String:
|
|
||||||
valueLen = v.Len()
|
|
||||||
}
|
|
||||||
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
if valueLen != 0 {
|
|
||||||
d.w.Write(lenEqualsBytes)
|
|
||||||
printInt(d.w, int64(valueLen), 10)
|
|
||||||
}
|
|
||||||
if !d.cs.DisableCapacities && valueCap != 0 {
|
|
||||||
if valueLen != 0 {
|
|
||||||
d.w.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
d.w.Write(capEqualsBytes)
|
|
||||||
printInt(d.w, int64(valueCap), 10)
|
|
||||||
}
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
d.w.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call Stringer/error interfaces if they exist and the handle methods flag
|
|
||||||
// is enabled
|
|
||||||
if !d.cs.DisableMethods {
|
|
||||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
|
||||||
if handled := handleMethods(d.cs, d.w, v); handled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case reflect.Invalid:
|
|
||||||
// Do nothing. We should never get here since invalid has already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Bool:
|
|
||||||
printBool(d.w, v.Bool())
|
|
||||||
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
printInt(d.w, v.Int(), 10)
|
|
||||||
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
printUint(d.w, v.Uint(), 10)
|
|
||||||
|
|
||||||
case reflect.Float32:
|
|
||||||
printFloat(d.w, v.Float(), 32)
|
|
||||||
|
|
||||||
case reflect.Float64:
|
|
||||||
printFloat(d.w, v.Float(), 64)
|
|
||||||
|
|
||||||
case reflect.Complex64:
|
|
||||||
printComplex(d.w, v.Complex(), 32)
|
|
||||||
|
|
||||||
case reflect.Complex128:
|
|
||||||
printComplex(d.w, v.Complex(), 64)
|
|
||||||
|
|
||||||
case reflect.Slice:
|
|
||||||
if v.IsNil() {
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
|
|
||||||
case reflect.Array:
|
|
||||||
d.w.Write(openBraceNewlineBytes)
|
|
||||||
d.depth++
|
|
||||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(maxNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.dumpSlice(v)
|
|
||||||
}
|
|
||||||
d.depth--
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.String:
|
|
||||||
d.w.Write([]byte(strconv.Quote(v.String())))
|
|
||||||
|
|
||||||
case reflect.Interface:
|
|
||||||
// The only time we should get here is for nil interfaces due to
|
|
||||||
// unpackValue calls.
|
|
||||||
if v.IsNil() {
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Ptr:
|
|
||||||
// Do nothing. We should never get here since pointers have already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Map:
|
|
||||||
// nil maps should be indicated as different than empty maps
|
|
||||||
if v.IsNil() {
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
d.w.Write(openBraceNewlineBytes)
|
|
||||||
d.depth++
|
|
||||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(maxNewlineBytes)
|
|
||||||
} else {
|
|
||||||
numEntries := v.Len()
|
|
||||||
keys := v.MapKeys()
|
|
||||||
if d.cs.SortKeys {
|
|
||||||
sortValues(keys, d.cs)
|
|
||||||
}
|
|
||||||
for i, key := range keys {
|
|
||||||
d.dump(d.unpackValue(key))
|
|
||||||
d.w.Write(colonSpaceBytes)
|
|
||||||
d.ignoreNextIndent = true
|
|
||||||
d.dump(d.unpackValue(v.MapIndex(key)))
|
|
||||||
if i < (numEntries - 1) {
|
|
||||||
d.w.Write(commaNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.depth--
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
d.w.Write(openBraceNewlineBytes)
|
|
||||||
d.depth++
|
|
||||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(maxNewlineBytes)
|
|
||||||
} else {
|
|
||||||
vt := v.Type()
|
|
||||||
numFields := v.NumField()
|
|
||||||
for i := 0; i < numFields; i++ {
|
|
||||||
d.indent()
|
|
||||||
vtf := vt.Field(i)
|
|
||||||
d.w.Write([]byte(vtf.Name))
|
|
||||||
d.w.Write(colonSpaceBytes)
|
|
||||||
d.ignoreNextIndent = true
|
|
||||||
d.dump(d.unpackValue(v.Field(i)))
|
|
||||||
if i < (numFields - 1) {
|
|
||||||
d.w.Write(commaNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.depth--
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.Uintptr:
|
|
||||||
printHexPtr(d.w, uintptr(v.Uint()))
|
|
||||||
|
|
||||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
|
||||||
printHexPtr(d.w, v.Pointer())
|
|
||||||
|
|
||||||
// There were not any other types at the time this code was written, but
|
|
||||||
// fall back to letting the default fmt package handle it in case any new
|
|
||||||
// types are added.
|
|
||||||
default:
|
|
||||||
if v.CanInterface() {
|
|
||||||
fmt.Fprintf(d.w, "%v", v.Interface())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(d.w, "%v", v.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fdump is a helper function to consolidate the logic from the various public
|
|
||||||
// methods which take varying writers and config states.
|
|
||||||
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
|
||||||
for _, arg := range a {
|
|
||||||
if arg == nil {
|
|
||||||
w.Write(interfaceBytes)
|
|
||||||
w.Write(spaceBytes)
|
|
||||||
w.Write(nilAngleBytes)
|
|
||||||
w.Write(newlineBytes)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
d := dumpState{w: w, cs: cs}
|
|
||||||
d.pointers = make(map[uintptr]int)
|
|
||||||
d.dump(reflect.ValueOf(arg))
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
|
||||||
// exactly the same as Dump.
|
|
||||||
func Fdump(w io.Writer, a ...interface{}) {
|
|
||||||
fdump(&Config, w, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
|
||||||
// as Dump.
|
|
||||||
func Sdump(a ...interface{}) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fdump(&Config, &buf, a...)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Dump displays the passed parameters to standard out with newlines, customizable
|
|
||||||
indentation, and additional debug information such as complete types and all
|
|
||||||
pointer addresses used to indirect to the final value. It provides the
|
|
||||||
following features over the built-in printing facilities provided by the fmt
|
|
||||||
package:
|
|
||||||
|
|
||||||
* Pointers are dereferenced and followed
|
|
||||||
* Circular data structures are detected and handled properly
|
|
||||||
* Custom Stringer/error interfaces are optionally invoked, including
|
|
||||||
on unexported types
|
|
||||||
* Custom types which only implement the Stringer/error interfaces via
|
|
||||||
a pointer receiver are optionally invoked when passing non-pointer
|
|
||||||
variables
|
|
||||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
|
||||||
includes offsets, byte values in hex, and ASCII output
|
|
||||||
|
|
||||||
The configuration options are controlled by an exported package global,
|
|
||||||
spew.Config. See ConfigState for options documentation.
|
|
||||||
|
|
||||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
|
||||||
get the formatted result as a string.
|
|
||||||
*/
|
|
||||||
func Dump(a ...interface{}) {
|
|
||||||
fdump(&Config, os.Stdout, a...)
|
|
||||||
}
|
|
||||||
-419
@@ -1,419 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// supportedFlags is a list of all the character flags supported by fmt package.
|
|
||||||
const supportedFlags = "0-+# "
|
|
||||||
|
|
||||||
// formatState implements the fmt.Formatter interface and contains information
|
|
||||||
// about the state of a formatting operation. The NewFormatter function can
|
|
||||||
// be used to get a new Formatter which can be used directly as arguments
|
|
||||||
// in standard fmt package printing calls.
|
|
||||||
type formatState struct {
|
|
||||||
value interface{}
|
|
||||||
fs fmt.State
|
|
||||||
depth int
|
|
||||||
pointers map[uintptr]int
|
|
||||||
ignoreNextType bool
|
|
||||||
cs *ConfigState
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildDefaultFormat recreates the original format string without precision
|
|
||||||
// and width information to pass in to fmt.Sprintf in the case of an
|
|
||||||
// unrecognized type. Unless new types are added to the language, this
|
|
||||||
// function won't ever be called.
|
|
||||||
func (f *formatState) buildDefaultFormat() (format string) {
|
|
||||||
buf := bytes.NewBuffer(percentBytes)
|
|
||||||
|
|
||||||
for _, flag := range supportedFlags {
|
|
||||||
if f.fs.Flag(int(flag)) {
|
|
||||||
buf.WriteRune(flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteRune('v')
|
|
||||||
|
|
||||||
format = buf.String()
|
|
||||||
return format
|
|
||||||
}
|
|
||||||
|
|
||||||
// constructOrigFormat recreates the original format string including precision
|
|
||||||
// and width information to pass along to the standard fmt package. This allows
|
|
||||||
// automatic deferral of all format strings this package doesn't support.
|
|
||||||
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
|
||||||
buf := bytes.NewBuffer(percentBytes)
|
|
||||||
|
|
||||||
for _, flag := range supportedFlags {
|
|
||||||
if f.fs.Flag(int(flag)) {
|
|
||||||
buf.WriteRune(flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if width, ok := f.fs.Width(); ok {
|
|
||||||
buf.WriteString(strconv.Itoa(width))
|
|
||||||
}
|
|
||||||
|
|
||||||
if precision, ok := f.fs.Precision(); ok {
|
|
||||||
buf.Write(precisionBytes)
|
|
||||||
buf.WriteString(strconv.Itoa(precision))
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteRune(verb)
|
|
||||||
|
|
||||||
format = buf.String()
|
|
||||||
return format
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpackValue returns values inside of non-nil interfaces when possible and
|
|
||||||
// ensures that types for values which have been unpacked from an interface
|
|
||||||
// are displayed when the show types flag is also set.
|
|
||||||
// This is useful for data types like structs, arrays, slices, and maps which
|
|
||||||
// can contain varying types packed inside an interface.
|
|
||||||
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
|
||||||
if v.Kind() == reflect.Interface {
|
|
||||||
f.ignoreNextType = false
|
|
||||||
if !v.IsNil() {
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
|
||||||
func (f *formatState) formatPtr(v reflect.Value) {
|
|
||||||
// Display nil if top level pointer is nil.
|
|
||||||
showTypes := f.fs.Flag('#')
|
|
||||||
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove pointers at or below the current depth from map used to detect
|
|
||||||
// circular refs.
|
|
||||||
for k, depth := range f.pointers {
|
|
||||||
if depth >= f.depth {
|
|
||||||
delete(f.pointers, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep list of all dereferenced pointers to possibly show later.
|
|
||||||
pointerChain := make([]uintptr, 0)
|
|
||||||
|
|
||||||
// Figure out how many levels of indirection there are by derferencing
|
|
||||||
// pointers and unpacking interfaces down the chain while detecting circular
|
|
||||||
// references.
|
|
||||||
nilFound := false
|
|
||||||
cycleFound := false
|
|
||||||
indirects := 0
|
|
||||||
ve := v
|
|
||||||
for ve.Kind() == reflect.Ptr {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
indirects++
|
|
||||||
addr := ve.Pointer()
|
|
||||||
pointerChain = append(pointerChain, addr)
|
|
||||||
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
|
||||||
cycleFound = true
|
|
||||||
indirects--
|
|
||||||
break
|
|
||||||
}
|
|
||||||
f.pointers[addr] = f.depth
|
|
||||||
|
|
||||||
ve = ve.Elem()
|
|
||||||
if ve.Kind() == reflect.Interface {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ve = ve.Elem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display type or indirection level depending on flags.
|
|
||||||
if showTypes && !f.ignoreNextType {
|
|
||||||
f.fs.Write(openParenBytes)
|
|
||||||
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
|
||||||
f.fs.Write([]byte(ve.Type().String()))
|
|
||||||
f.fs.Write(closeParenBytes)
|
|
||||||
} else {
|
|
||||||
if nilFound || cycleFound {
|
|
||||||
indirects += strings.Count(ve.Type().String(), "*")
|
|
||||||
}
|
|
||||||
f.fs.Write(openAngleBytes)
|
|
||||||
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
|
||||||
f.fs.Write(closeAngleBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display pointer information depending on flags.
|
|
||||||
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
|
||||||
f.fs.Write(openParenBytes)
|
|
||||||
for i, addr := range pointerChain {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(pointerChainBytes)
|
|
||||||
}
|
|
||||||
printHexPtr(f.fs, addr)
|
|
||||||
}
|
|
||||||
f.fs.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display dereferenced value.
|
|
||||||
switch {
|
|
||||||
case nilFound == true:
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
|
|
||||||
case cycleFound == true:
|
|
||||||
f.fs.Write(circularShortBytes)
|
|
||||||
|
|
||||||
default:
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(ve)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// format is the main workhorse for providing the Formatter interface. It
|
|
||||||
// uses the passed reflect value to figure out what kind of object we are
|
|
||||||
// dealing with and formats it appropriately. It is a recursive function,
|
|
||||||
// however circular data structures are detected and handled properly.
|
|
||||||
func (f *formatState) format(v reflect.Value) {
|
|
||||||
// Handle invalid reflect values immediately.
|
|
||||||
kind := v.Kind()
|
|
||||||
if kind == reflect.Invalid {
|
|
||||||
f.fs.Write(invalidAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle pointers specially.
|
|
||||||
if kind == reflect.Ptr {
|
|
||||||
f.formatPtr(v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print type information unless already handled elsewhere.
|
|
||||||
if !f.ignoreNextType && f.fs.Flag('#') {
|
|
||||||
f.fs.Write(openParenBytes)
|
|
||||||
f.fs.Write([]byte(v.Type().String()))
|
|
||||||
f.fs.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
f.ignoreNextType = false
|
|
||||||
|
|
||||||
// Call Stringer/error interfaces if they exist and the handle methods
|
|
||||||
// flag is enabled.
|
|
||||||
if !f.cs.DisableMethods {
|
|
||||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
|
||||||
if handled := handleMethods(f.cs, f.fs, v); handled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case reflect.Invalid:
|
|
||||||
// Do nothing. We should never get here since invalid has already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Bool:
|
|
||||||
printBool(f.fs, v.Bool())
|
|
||||||
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
printInt(f.fs, v.Int(), 10)
|
|
||||||
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
printUint(f.fs, v.Uint(), 10)
|
|
||||||
|
|
||||||
case reflect.Float32:
|
|
||||||
printFloat(f.fs, v.Float(), 32)
|
|
||||||
|
|
||||||
case reflect.Float64:
|
|
||||||
printFloat(f.fs, v.Float(), 64)
|
|
||||||
|
|
||||||
case reflect.Complex64:
|
|
||||||
printComplex(f.fs, v.Complex(), 32)
|
|
||||||
|
|
||||||
case reflect.Complex128:
|
|
||||||
printComplex(f.fs, v.Complex(), 64)
|
|
||||||
|
|
||||||
case reflect.Slice:
|
|
||||||
if v.IsNil() {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
|
|
||||||
case reflect.Array:
|
|
||||||
f.fs.Write(openBracketBytes)
|
|
||||||
f.depth++
|
|
||||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
|
||||||
f.fs.Write(maxShortBytes)
|
|
||||||
} else {
|
|
||||||
numEntries := v.Len()
|
|
||||||
for i := 0; i < numEntries; i++ {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(f.unpackValue(v.Index(i)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.depth--
|
|
||||||
f.fs.Write(closeBracketBytes)
|
|
||||||
|
|
||||||
case reflect.String:
|
|
||||||
f.fs.Write([]byte(v.String()))
|
|
||||||
|
|
||||||
case reflect.Interface:
|
|
||||||
// The only time we should get here is for nil interfaces due to
|
|
||||||
// unpackValue calls.
|
|
||||||
if v.IsNil() {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Ptr:
|
|
||||||
// Do nothing. We should never get here since pointers have already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Map:
|
|
||||||
// nil maps should be indicated as different than empty maps
|
|
||||||
if v.IsNil() {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
f.fs.Write(openMapBytes)
|
|
||||||
f.depth++
|
|
||||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
|
||||||
f.fs.Write(maxShortBytes)
|
|
||||||
} else {
|
|
||||||
keys := v.MapKeys()
|
|
||||||
if f.cs.SortKeys {
|
|
||||||
sortValues(keys, f.cs)
|
|
||||||
}
|
|
||||||
for i, key := range keys {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(f.unpackValue(key))
|
|
||||||
f.fs.Write(colonBytes)
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(f.unpackValue(v.MapIndex(key)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.depth--
|
|
||||||
f.fs.Write(closeMapBytes)
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
numFields := v.NumField()
|
|
||||||
f.fs.Write(openBraceBytes)
|
|
||||||
f.depth++
|
|
||||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
|
||||||
f.fs.Write(maxShortBytes)
|
|
||||||
} else {
|
|
||||||
vt := v.Type()
|
|
||||||
for i := 0; i < numFields; i++ {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
vtf := vt.Field(i)
|
|
||||||
if f.fs.Flag('+') || f.fs.Flag('#') {
|
|
||||||
f.fs.Write([]byte(vtf.Name))
|
|
||||||
f.fs.Write(colonBytes)
|
|
||||||
}
|
|
||||||
f.format(f.unpackValue(v.Field(i)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.depth--
|
|
||||||
f.fs.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.Uintptr:
|
|
||||||
printHexPtr(f.fs, uintptr(v.Uint()))
|
|
||||||
|
|
||||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
|
||||||
printHexPtr(f.fs, v.Pointer())
|
|
||||||
|
|
||||||
// There were not any other types at the time this code was written, but
|
|
||||||
// fall back to letting the default fmt package handle it if any get added.
|
|
||||||
default:
|
|
||||||
format := f.buildDefaultFormat()
|
|
||||||
if v.CanInterface() {
|
|
||||||
fmt.Fprintf(f.fs, format, v.Interface())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(f.fs, format, v.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
|
||||||
// details.
|
|
||||||
func (f *formatState) Format(fs fmt.State, verb rune) {
|
|
||||||
f.fs = fs
|
|
||||||
|
|
||||||
// Use standard formatting for verbs that are not v.
|
|
||||||
if verb != 'v' {
|
|
||||||
format := f.constructOrigFormat(verb)
|
|
||||||
fmt.Fprintf(fs, format, f.value)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.value == nil {
|
|
||||||
if fs.Flag('#') {
|
|
||||||
fs.Write(interfaceBytes)
|
|
||||||
}
|
|
||||||
fs.Write(nilAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f.format(reflect.ValueOf(f.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
// newFormatter is a helper function to consolidate the logic from the various
|
|
||||||
// public methods which take varying config states.
|
|
||||||
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
|
||||||
fs := &formatState{value: v, cs: cs}
|
|
||||||
fs.pointers = make(map[uintptr]int)
|
|
||||||
return fs
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
|
||||||
interface. As a result, it integrates cleanly with standard fmt package
|
|
||||||
printing functions. The formatter is useful for inline printing of smaller data
|
|
||||||
types similar to the standard %v format specifier.
|
|
||||||
|
|
||||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
|
||||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
|
||||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
|
||||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
|
||||||
the width and precision arguments (however they will still work on the format
|
|
||||||
specifiers not handled by the custom formatter).
|
|
||||||
|
|
||||||
Typically this function shouldn't be called directly. It is much easier to make
|
|
||||||
use of the custom formatter by calling one of the convenience functions such as
|
|
||||||
Printf, Println, or Fprintf.
|
|
||||||
*/
|
|
||||||
func NewFormatter(v interface{}) fmt.Formatter {
|
|
||||||
return newFormatter(&Config, v)
|
|
||||||
}
|
|
||||||
-148
@@ -1,148 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the formatted string as a value that satisfies error. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Errorf(format string, a ...interface{}) (err error) {
|
|
||||||
return fmt.Errorf(format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprint(w, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintf(w, format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintln(w, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Print(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Print(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Printf(format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Println(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Println(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Sprint(a ...interface{}) string {
|
|
||||||
return fmt.Sprint(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Sprintf(format string, a ...interface{}) string {
|
|
||||||
return fmt.Sprintf(format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
|
||||||
// were passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Sprintln(a ...interface{}) string {
|
|
||||||
return fmt.Sprintln(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
|
||||||
// length with each argument converted to a default spew Formatter interface.
|
|
||||||
func convertArgs(args []interface{}) (formatters []interface{}) {
|
|
||||||
formatters = make([]interface{}, len(args))
|
|
||||||
for index, arg := range args {
|
|
||||||
formatters[index] = NewFormatter(arg)
|
|
||||||
}
|
|
||||||
return formatters
|
|
||||||
}
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
Copyright (c) 2013 John Barton
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
-127
@@ -1,127 +0,0 @@
|
|||||||
# GoDotEnv [](https://app.wercker.com/project/bykey/507594c2ec7e60f19403a568dfea0f78)
|
|
||||||
|
|
||||||
A Go (golang) port of the Ruby dotenv project (which loads env vars from a .env file)
|
|
||||||
|
|
||||||
From the original Library:
|
|
||||||
|
|
||||||
> Storing configuration in the environment is one of the tenets of a twelve-factor app. Anything that is likely to change between deployment environments–such as resource handles for databases or credentials for external services–should be extracted from the code into environment variables.
|
|
||||||
>
|
|
||||||
> But it is not always practical to set environment variables on development machines or continuous integration servers where multiple projects are run. Dotenv load variables from a .env file into ENV when the environment is bootstrapped.
|
|
||||||
|
|
||||||
It can be used as a library (for loading in env for your own daemons etc) or as a bin command.
|
|
||||||
|
|
||||||
There is test coverage and CI for both linuxish and windows environments, but I make no guarantees about the bin version working on windows.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
As a library
|
|
||||||
|
|
||||||
```shell
|
|
||||||
go get github.com/joho/godotenv
|
|
||||||
```
|
|
||||||
|
|
||||||
or if you want to use it as a bin command
|
|
||||||
```shell
|
|
||||||
go get github.com/joho/godotenv/cmd/godotenv
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Add your application configuration to your `.env` file in the root of your project:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
S3_BUCKET=YOURS3BUCKET
|
|
||||||
SECRET_KEY=YOURSECRETKEYGOESHERE
|
|
||||||
```
|
|
||||||
|
|
||||||
Then in your Go app you can do something like
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := godotenv.Load()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error loading .env file")
|
|
||||||
}
|
|
||||||
|
|
||||||
s3Bucket := os.Getenv("S3_BUCKET")
|
|
||||||
secretKey := os.Getenv("SECRET_KEY")
|
|
||||||
|
|
||||||
// now do something with s3 or whatever
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're even lazier than that, you can just take advantage of the autoload package which will read in `.env` on import
|
|
||||||
|
|
||||||
```go
|
|
||||||
import _ "github.com/joho/godotenv/autoload"
|
|
||||||
```
|
|
||||||
|
|
||||||
While `.env` in the project root is the default, you don't have to be constrained, both examples below are 100% legit
|
|
||||||
|
|
||||||
```go
|
|
||||||
_ = godotenv.Load("somerandomfile")
|
|
||||||
_ = godotenv.Load("filenumberone.env", "filenumbertwo.env")
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to be really fancy with your env file you can do comments and exports (below is a valid env file)
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# I am a comment and that is OK
|
|
||||||
SOME_VAR=someval
|
|
||||||
FOO=BAR # comments at line end are OK too
|
|
||||||
export BAR=BAZ
|
|
||||||
```
|
|
||||||
|
|
||||||
Or finally you can do YAML(ish) style
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
FOO: bar
|
|
||||||
BAR: baz
|
|
||||||
```
|
|
||||||
|
|
||||||
as a final aside, if you don't want godotenv munging your env you can just get a map back instead
|
|
||||||
|
|
||||||
```go
|
|
||||||
var myEnv map[string]string
|
|
||||||
myEnv, err := godotenv.Read()
|
|
||||||
|
|
||||||
s3Bucket := myEnv["S3_BUCKET"]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Command Mode
|
|
||||||
|
|
||||||
Assuming you've installed the command as above and you've got `$GOPATH/bin` in your `$PATH`
|
|
||||||
|
|
||||||
```
|
|
||||||
godotenv -f /some/path/to/.env some_command with some args
|
|
||||||
```
|
|
||||||
|
|
||||||
If you don't specify `-f` it will fall back on the default of loading `.env` in `PWD`
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Contributions are most welcome! The parser itself is pretty stupidly naive and I wouldn't be surprised if it breaks with edge cases.
|
|
||||||
|
|
||||||
*code changes without tests will not be accepted*
|
|
||||||
|
|
||||||
1. Fork it
|
|
||||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
||||||
3. Commit your changes (`git commit -am 'Added some feature'`)
|
|
||||||
4. Push to the branch (`git push origin my-new-feature`)
|
|
||||||
5. Create new Pull Request
|
|
||||||
|
|
||||||
## CI
|
|
||||||
|
|
||||||
Linux: [](https://app.wercker.com/project/bykey/507594c2ec7e60f19403a568dfea0f78) Windows: [](https://ci.appveyor.com/project/joho/godotenv)
|
|
||||||
|
|
||||||
## Who?
|
|
||||||
|
|
||||||
The original library [dotenv](https://github.com/bkeepers/dotenv) was written by [Brandon Keepers](http://opensoul.org/), and this port was done by [John Barton](http://whoisjohnbarton.com) based off the tests/fixtures in the original library.
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
package autoload
|
|
||||||
|
|
||||||
/*
|
|
||||||
You can just read the .env file on import just by doing
|
|
||||||
|
|
||||||
import _ "github.com/joho/godotenv/autoload"
|
|
||||||
|
|
||||||
And bob's your mother's brother
|
|
||||||
*/
|
|
||||||
|
|
||||||
import "github.com/joho/godotenv"
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
godotenv.Load()
|
|
||||||
}
|
|
||||||
-235
@@ -1,235 +0,0 @@
|
|||||||
// Package godotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv)
|
|
||||||
//
|
|
||||||
// Examples/readme can be found on the github page at https://github.com/joho/godotenv
|
|
||||||
//
|
|
||||||
// The TL;DR is that you make a .env file that looks something like
|
|
||||||
//
|
|
||||||
// SOME_ENV_VAR=somevalue
|
|
||||||
//
|
|
||||||
// and then in your go code you can call
|
|
||||||
//
|
|
||||||
// godotenv.Load()
|
|
||||||
//
|
|
||||||
// and all the env vars declared in .env will be avaiable through os.Getenv("SOME_ENV_VAR")
|
|
||||||
package godotenv
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Load will read your env file(s) and load them into ENV for this process.
|
|
||||||
//
|
|
||||||
// Call this function as close as possible to the start of your program (ideally in main)
|
|
||||||
//
|
|
||||||
// If you call Load without any args it will default to loading .env in the current path
|
|
||||||
//
|
|
||||||
// You can otherwise tell it which files to load (there can be more than one) like
|
|
||||||
//
|
|
||||||
// godotenv.Load("fileone", "filetwo")
|
|
||||||
//
|
|
||||||
// It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults
|
|
||||||
func Load(filenames ...string) (err error) {
|
|
||||||
filenames = filenamesOrDefault(filenames)
|
|
||||||
|
|
||||||
for _, filename := range filenames {
|
|
||||||
err = loadFile(filename, false)
|
|
||||||
if err != nil {
|
|
||||||
return // return early on a spazout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overload will read your env file(s) and load them into ENV for this process.
|
|
||||||
//
|
|
||||||
// Call this function as close as possible to the start of your program (ideally in main)
|
|
||||||
//
|
|
||||||
// If you call Overload without any args it will default to loading .env in the current path
|
|
||||||
//
|
|
||||||
// You can otherwise tell it which files to load (there can be more than one) like
|
|
||||||
//
|
|
||||||
// godotenv.Overload("fileone", "filetwo")
|
|
||||||
//
|
|
||||||
// It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefilly set all vars.
|
|
||||||
func Overload(filenames ...string) (err error) {
|
|
||||||
filenames = filenamesOrDefault(filenames)
|
|
||||||
|
|
||||||
for _, filename := range filenames {
|
|
||||||
err = loadFile(filename, true)
|
|
||||||
if err != nil {
|
|
||||||
return // return early on a spazout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read all env (with same file loading semantics as Load) but return values as
|
|
||||||
// a map rather than automatically writing values into env
|
|
||||||
func Read(filenames ...string) (envMap map[string]string, err error) {
|
|
||||||
filenames = filenamesOrDefault(filenames)
|
|
||||||
envMap = make(map[string]string)
|
|
||||||
|
|
||||||
for _, filename := range filenames {
|
|
||||||
individualEnvMap, individualErr := readFile(filename)
|
|
||||||
|
|
||||||
if individualErr != nil {
|
|
||||||
err = individualErr
|
|
||||||
return // return early on a spazout
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value := range individualEnvMap {
|
|
||||||
envMap[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exec loads env vars from the specified filenames (empty map falls back to default)
|
|
||||||
// then executes the cmd specified.
|
|
||||||
//
|
|
||||||
// Simply hooks up os.Stdin/err/out to the command and calls Run()
|
|
||||||
//
|
|
||||||
// If you want more fine grained control over your command it's recommended
|
|
||||||
// that you use `Load()` or `Read()` and the `os/exec` package yourself.
|
|
||||||
func Exec(filenames []string, cmd string, cmdArgs []string) error {
|
|
||||||
Load(filenames...)
|
|
||||||
|
|
||||||
command := exec.Command(cmd, cmdArgs...)
|
|
||||||
command.Stdin = os.Stdin
|
|
||||||
command.Stdout = os.Stdout
|
|
||||||
command.Stderr = os.Stderr
|
|
||||||
return command.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func filenamesOrDefault(filenames []string) []string {
|
|
||||||
if len(filenames) == 0 {
|
|
||||||
return []string{".env"}
|
|
||||||
}
|
|
||||||
return filenames
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadFile(filename string, overload bool) error {
|
|
||||||
envMap, err := readFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value := range envMap {
|
|
||||||
if os.Getenv(key) == "" || overload {
|
|
||||||
os.Setenv(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readFile(filename string) (envMap map[string]string, err error) {
|
|
||||||
file, err := os.Open(filename)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
envMap = make(map[string]string)
|
|
||||||
|
|
||||||
var lines []string
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
for scanner.Scan() {
|
|
||||||
lines = append(lines, scanner.Text())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = scanner.Err(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fullLine := range lines {
|
|
||||||
if !isIgnoredLine(fullLine) {
|
|
||||||
var key, value string
|
|
||||||
key, value, err = parseLine(fullLine)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
envMap[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseLine(line string) (key string, value string, err error) {
|
|
||||||
if len(line) == 0 {
|
|
||||||
err = errors.New("zero length string")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ditch the comments (but keep quoted hashes)
|
|
||||||
if strings.Contains(line, "#") {
|
|
||||||
segmentsBetweenHashes := strings.Split(line, "#")
|
|
||||||
quotesAreOpen := false
|
|
||||||
var segmentsToKeep []string
|
|
||||||
for _, segment := range segmentsBetweenHashes {
|
|
||||||
if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 {
|
|
||||||
if quotesAreOpen {
|
|
||||||
quotesAreOpen = false
|
|
||||||
segmentsToKeep = append(segmentsToKeep, segment)
|
|
||||||
} else {
|
|
||||||
quotesAreOpen = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(segmentsToKeep) == 0 || quotesAreOpen {
|
|
||||||
segmentsToKeep = append(segmentsToKeep, segment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
line = strings.Join(segmentsToKeep, "#")
|
|
||||||
}
|
|
||||||
|
|
||||||
// now split key from value
|
|
||||||
splitString := strings.SplitN(line, "=", 2)
|
|
||||||
|
|
||||||
if len(splitString) != 2 {
|
|
||||||
// try yaml mode!
|
|
||||||
splitString = strings.SplitN(line, ":", 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(splitString) != 2 {
|
|
||||||
err = errors.New("Can't separate key from value")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the key
|
|
||||||
key = splitString[0]
|
|
||||||
if strings.HasPrefix(key, "export") {
|
|
||||||
key = strings.TrimPrefix(key, "export")
|
|
||||||
}
|
|
||||||
key = strings.Trim(key, " ")
|
|
||||||
|
|
||||||
// Parse the value
|
|
||||||
value = splitString[1]
|
|
||||||
// trim
|
|
||||||
value = strings.Trim(value, " ")
|
|
||||||
|
|
||||||
// check if we've got quoted values
|
|
||||||
if strings.Count(value, "\"") == 2 || strings.Count(value, "'") == 2 {
|
|
||||||
// pull the quotes off the edges
|
|
||||||
value = strings.Trim(value, "\"'")
|
|
||||||
|
|
||||||
// expand quotes
|
|
||||||
value = strings.Replace(value, "\\\"", "\"", -1)
|
|
||||||
// expand newlines
|
|
||||||
value = strings.Replace(value, "\\n", "\n", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func isIgnoredLine(line string) bool {
|
|
||||||
trimmedLine := strings.Trim(line, " \n\t")
|
|
||||||
return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#")
|
|
||||||
}
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
box: pjvds/golang
|
|
||||||
-27
@@ -1,27 +0,0 @@
|
|||||||
Copyright (c) 2013, Patrick Mezard
|
|
||||||
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.
|
|
||||||
The names of its contributors may not 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
|
|
||||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
||||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
-772
@@ -1,772 +0,0 @@
|
|||||||
// Package difflib is a partial port of Python difflib module.
|
|
||||||
//
|
|
||||||
// It provides tools to compare sequences of strings and generate textual diffs.
|
|
||||||
//
|
|
||||||
// The following class and functions have been ported:
|
|
||||||
//
|
|
||||||
// - SequenceMatcher
|
|
||||||
//
|
|
||||||
// - unified_diff
|
|
||||||
//
|
|
||||||
// - context_diff
|
|
||||||
//
|
|
||||||
// Getting unified diffs was the main goal of the port. Keep in mind this code
|
|
||||||
// is mostly suitable to output text differences in a human friendly way, there
|
|
||||||
// are no guarantees generated diffs are consumable by patch(1).
|
|
||||||
package difflib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func min(a, b int) int {
|
|
||||||
if a < b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func max(a, b int) int {
|
|
||||||
if a > b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateRatio(matches, length int) float64 {
|
|
||||||
if length > 0 {
|
|
||||||
return 2.0 * float64(matches) / float64(length)
|
|
||||||
}
|
|
||||||
return 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
type Match struct {
|
|
||||||
A int
|
|
||||||
B int
|
|
||||||
Size int
|
|
||||||
}
|
|
||||||
|
|
||||||
type OpCode struct {
|
|
||||||
Tag byte
|
|
||||||
I1 int
|
|
||||||
I2 int
|
|
||||||
J1 int
|
|
||||||
J2 int
|
|
||||||
}
|
|
||||||
|
|
||||||
// SequenceMatcher compares sequence of strings. The basic
|
|
||||||
// algorithm predates, and is a little fancier than, an algorithm
|
|
||||||
// published in the late 1980's by Ratcliff and Obershelp under the
|
|
||||||
// hyperbolic name "gestalt pattern matching". The basic idea is to find
|
|
||||||
// the longest contiguous matching subsequence that contains no "junk"
|
|
||||||
// elements (R-O doesn't address junk). The same idea is then applied
|
|
||||||
// recursively to the pieces of the sequences to the left and to the right
|
|
||||||
// of the matching subsequence. This does not yield minimal edit
|
|
||||||
// sequences, but does tend to yield matches that "look right" to people.
|
|
||||||
//
|
|
||||||
// SequenceMatcher tries to compute a "human-friendly diff" between two
|
|
||||||
// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the
|
|
||||||
// longest *contiguous* & junk-free matching subsequence. That's what
|
|
||||||
// catches peoples' eyes. The Windows(tm) windiff has another interesting
|
|
||||||
// notion, pairing up elements that appear uniquely in each sequence.
|
|
||||||
// That, and the method here, appear to yield more intuitive difference
|
|
||||||
// reports than does diff. This method appears to be the least vulnerable
|
|
||||||
// to synching up on blocks of "junk lines", though (like blank lines in
|
|
||||||
// ordinary text files, or maybe "<P>" lines in HTML files). That may be
|
|
||||||
// because this is the only method of the 3 that has a *concept* of
|
|
||||||
// "junk" <wink>.
|
|
||||||
//
|
|
||||||
// Timing: Basic R-O is cubic time worst case and quadratic time expected
|
|
||||||
// case. SequenceMatcher is quadratic time for the worst case and has
|
|
||||||
// expected-case behavior dependent in a complicated way on how many
|
|
||||||
// elements the sequences have in common; best case time is linear.
|
|
||||||
type SequenceMatcher struct {
|
|
||||||
a []string
|
|
||||||
b []string
|
|
||||||
b2j map[string][]int
|
|
||||||
IsJunk func(string) bool
|
|
||||||
autoJunk bool
|
|
||||||
bJunk map[string]struct{}
|
|
||||||
matchingBlocks []Match
|
|
||||||
fullBCount map[string]int
|
|
||||||
bPopular map[string]struct{}
|
|
||||||
opCodes []OpCode
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMatcher(a, b []string) *SequenceMatcher {
|
|
||||||
m := SequenceMatcher{autoJunk: true}
|
|
||||||
m.SetSeqs(a, b)
|
|
||||||
return &m
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMatcherWithJunk(a, b []string, autoJunk bool,
|
|
||||||
isJunk func(string) bool) *SequenceMatcher {
|
|
||||||
|
|
||||||
m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk}
|
|
||||||
m.SetSeqs(a, b)
|
|
||||||
return &m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set two sequences to be compared.
|
|
||||||
func (m *SequenceMatcher) SetSeqs(a, b []string) {
|
|
||||||
m.SetSeq1(a)
|
|
||||||
m.SetSeq2(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the first sequence to be compared. The second sequence to be compared is
|
|
||||||
// not changed.
|
|
||||||
//
|
|
||||||
// SequenceMatcher computes and caches detailed information about the second
|
|
||||||
// sequence, so if you want to compare one sequence S against many sequences,
|
|
||||||
// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other
|
|
||||||
// sequences.
|
|
||||||
//
|
|
||||||
// See also SetSeqs() and SetSeq2().
|
|
||||||
func (m *SequenceMatcher) SetSeq1(a []string) {
|
|
||||||
if &a == &m.a {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.a = a
|
|
||||||
m.matchingBlocks = nil
|
|
||||||
m.opCodes = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the second sequence to be compared. The first sequence to be compared is
|
|
||||||
// not changed.
|
|
||||||
func (m *SequenceMatcher) SetSeq2(b []string) {
|
|
||||||
if &b == &m.b {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.b = b
|
|
||||||
m.matchingBlocks = nil
|
|
||||||
m.opCodes = nil
|
|
||||||
m.fullBCount = nil
|
|
||||||
m.chainB()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *SequenceMatcher) chainB() {
|
|
||||||
// Populate line -> index mapping
|
|
||||||
b2j := map[string][]int{}
|
|
||||||
for i, s := range m.b {
|
|
||||||
indices := b2j[s]
|
|
||||||
indices = append(indices, i)
|
|
||||||
b2j[s] = indices
|
|
||||||
}
|
|
||||||
|
|
||||||
// Purge junk elements
|
|
||||||
m.bJunk = map[string]struct{}{}
|
|
||||||
if m.IsJunk != nil {
|
|
||||||
junk := m.bJunk
|
|
||||||
for s, _ := range b2j {
|
|
||||||
if m.IsJunk(s) {
|
|
||||||
junk[s] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for s, _ := range junk {
|
|
||||||
delete(b2j, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Purge remaining popular elements
|
|
||||||
popular := map[string]struct{}{}
|
|
||||||
n := len(m.b)
|
|
||||||
if m.autoJunk && n >= 200 {
|
|
||||||
ntest := n/100 + 1
|
|
||||||
for s, indices := range b2j {
|
|
||||||
if len(indices) > ntest {
|
|
||||||
popular[s] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for s, _ := range popular {
|
|
||||||
delete(b2j, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.bPopular = popular
|
|
||||||
m.b2j = b2j
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *SequenceMatcher) isBJunk(s string) bool {
|
|
||||||
_, ok := m.bJunk[s]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find longest matching block in a[alo:ahi] and b[blo:bhi].
|
|
||||||
//
|
|
||||||
// If IsJunk is not defined:
|
|
||||||
//
|
|
||||||
// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
|
|
||||||
// alo <= i <= i+k <= ahi
|
|
||||||
// blo <= j <= j+k <= bhi
|
|
||||||
// and for all (i',j',k') meeting those conditions,
|
|
||||||
// k >= k'
|
|
||||||
// i <= i'
|
|
||||||
// and if i == i', j <= j'
|
|
||||||
//
|
|
||||||
// In other words, of all maximal matching blocks, return one that
|
|
||||||
// starts earliest in a, and of all those maximal matching blocks that
|
|
||||||
// start earliest in a, return the one that starts earliest in b.
|
|
||||||
//
|
|
||||||
// If IsJunk is defined, first the longest matching block is
|
|
||||||
// determined as above, but with the additional restriction that no
|
|
||||||
// junk element appears in the block. Then that block is extended as
|
|
||||||
// far as possible by matching (only) junk elements on both sides. So
|
|
||||||
// the resulting block never matches on junk except as identical junk
|
|
||||||
// happens to be adjacent to an "interesting" match.
|
|
||||||
//
|
|
||||||
// If no blocks match, return (alo, blo, 0).
|
|
||||||
func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match {
|
|
||||||
// CAUTION: stripping common prefix or suffix would be incorrect.
|
|
||||||
// E.g.,
|
|
||||||
// ab
|
|
||||||
// acab
|
|
||||||
// Longest matching block is "ab", but if common prefix is
|
|
||||||
// stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
|
|
||||||
// strip, so ends up claiming that ab is changed to acab by
|
|
||||||
// inserting "ca" in the middle. That's minimal but unintuitive:
|
|
||||||
// "it's obvious" that someone inserted "ac" at the front.
|
|
||||||
// Windiff ends up at the same place as diff, but by pairing up
|
|
||||||
// the unique 'b's and then matching the first two 'a's.
|
|
||||||
besti, bestj, bestsize := alo, blo, 0
|
|
||||||
|
|
||||||
// find longest junk-free match
|
|
||||||
// during an iteration of the loop, j2len[j] = length of longest
|
|
||||||
// junk-free match ending with a[i-1] and b[j]
|
|
||||||
j2len := map[int]int{}
|
|
||||||
for i := alo; i != ahi; i++ {
|
|
||||||
// look at all instances of a[i] in b; note that because
|
|
||||||
// b2j has no junk keys, the loop is skipped if a[i] is junk
|
|
||||||
newj2len := map[int]int{}
|
|
||||||
for _, j := range m.b2j[m.a[i]] {
|
|
||||||
// a[i] matches b[j]
|
|
||||||
if j < blo {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if j >= bhi {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
k := j2len[j-1] + 1
|
|
||||||
newj2len[j] = k
|
|
||||||
if k > bestsize {
|
|
||||||
besti, bestj, bestsize = i-k+1, j-k+1, k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
j2len = newj2len
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extend the best by non-junk elements on each end. In particular,
|
|
||||||
// "popular" non-junk elements aren't in b2j, which greatly speeds
|
|
||||||
// the inner loop above, but also means "the best" match so far
|
|
||||||
// doesn't contain any junk *or* popular non-junk elements.
|
|
||||||
for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) &&
|
|
||||||
m.a[besti-1] == m.b[bestj-1] {
|
|
||||||
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
|
||||||
}
|
|
||||||
for besti+bestsize < ahi && bestj+bestsize < bhi &&
|
|
||||||
!m.isBJunk(m.b[bestj+bestsize]) &&
|
|
||||||
m.a[besti+bestsize] == m.b[bestj+bestsize] {
|
|
||||||
bestsize += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now that we have a wholly interesting match (albeit possibly
|
|
||||||
// empty!), we may as well suck up the matching junk on each
|
|
||||||
// side of it too. Can't think of a good reason not to, and it
|
|
||||||
// saves post-processing the (possibly considerable) expense of
|
|
||||||
// figuring out what to do with it. In the case of an empty
|
|
||||||
// interesting match, this is clearly the right thing to do,
|
|
||||||
// because no other kind of match is possible in the regions.
|
|
||||||
for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) &&
|
|
||||||
m.a[besti-1] == m.b[bestj-1] {
|
|
||||||
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
|
||||||
}
|
|
||||||
for besti+bestsize < ahi && bestj+bestsize < bhi &&
|
|
||||||
m.isBJunk(m.b[bestj+bestsize]) &&
|
|
||||||
m.a[besti+bestsize] == m.b[bestj+bestsize] {
|
|
||||||
bestsize += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return Match{A: besti, B: bestj, Size: bestsize}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return list of triples describing matching subsequences.
|
|
||||||
//
|
|
||||||
// Each triple is of the form (i, j, n), and means that
|
|
||||||
// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
|
|
||||||
// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are
|
|
||||||
// adjacent triples in the list, and the second is not the last triple in the
|
|
||||||
// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe
|
|
||||||
// adjacent equal blocks.
|
|
||||||
//
|
|
||||||
// The last triple is a dummy, (len(a), len(b), 0), and is the only
|
|
||||||
// triple with n==0.
|
|
||||||
func (m *SequenceMatcher) GetMatchingBlocks() []Match {
|
|
||||||
if m.matchingBlocks != nil {
|
|
||||||
return m.matchingBlocks
|
|
||||||
}
|
|
||||||
|
|
||||||
var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match
|
|
||||||
matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match {
|
|
||||||
match := m.findLongestMatch(alo, ahi, blo, bhi)
|
|
||||||
i, j, k := match.A, match.B, match.Size
|
|
||||||
if match.Size > 0 {
|
|
||||||
if alo < i && blo < j {
|
|
||||||
matched = matchBlocks(alo, i, blo, j, matched)
|
|
||||||
}
|
|
||||||
matched = append(matched, match)
|
|
||||||
if i+k < ahi && j+k < bhi {
|
|
||||||
matched = matchBlocks(i+k, ahi, j+k, bhi, matched)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matched
|
|
||||||
}
|
|
||||||
matched := matchBlocks(0, len(m.a), 0, len(m.b), nil)
|
|
||||||
|
|
||||||
// It's possible that we have adjacent equal blocks in the
|
|
||||||
// matching_blocks list now.
|
|
||||||
nonAdjacent := []Match{}
|
|
||||||
i1, j1, k1 := 0, 0, 0
|
|
||||||
for _, b := range matched {
|
|
||||||
// Is this block adjacent to i1, j1, k1?
|
|
||||||
i2, j2, k2 := b.A, b.B, b.Size
|
|
||||||
if i1+k1 == i2 && j1+k1 == j2 {
|
|
||||||
// Yes, so collapse them -- this just increases the length of
|
|
||||||
// the first block by the length of the second, and the first
|
|
||||||
// block so lengthened remains the block to compare against.
|
|
||||||
k1 += k2
|
|
||||||
} else {
|
|
||||||
// Not adjacent. Remember the first block (k1==0 means it's
|
|
||||||
// the dummy we started with), and make the second block the
|
|
||||||
// new block to compare against.
|
|
||||||
if k1 > 0 {
|
|
||||||
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
|
|
||||||
}
|
|
||||||
i1, j1, k1 = i2, j2, k2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if k1 > 0 {
|
|
||||||
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
|
|
||||||
}
|
|
||||||
|
|
||||||
nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0})
|
|
||||||
m.matchingBlocks = nonAdjacent
|
|
||||||
return m.matchingBlocks
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return list of 5-tuples describing how to turn a into b.
|
|
||||||
//
|
|
||||||
// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
|
|
||||||
// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
|
|
||||||
// tuple preceding it, and likewise for j1 == the previous j2.
|
|
||||||
//
|
|
||||||
// The tags are characters, with these meanings:
|
|
||||||
//
|
|
||||||
// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2]
|
|
||||||
//
|
|
||||||
// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case.
|
|
||||||
//
|
|
||||||
// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case.
|
|
||||||
//
|
|
||||||
// 'e' (equal): a[i1:i2] == b[j1:j2]
|
|
||||||
func (m *SequenceMatcher) GetOpCodes() []OpCode {
|
|
||||||
if m.opCodes != nil {
|
|
||||||
return m.opCodes
|
|
||||||
}
|
|
||||||
i, j := 0, 0
|
|
||||||
matching := m.GetMatchingBlocks()
|
|
||||||
opCodes := make([]OpCode, 0, len(matching))
|
|
||||||
for _, m := range matching {
|
|
||||||
// invariant: we've pumped out correct diffs to change
|
|
||||||
// a[:i] into b[:j], and the next matching block is
|
|
||||||
// a[ai:ai+size] == b[bj:bj+size]. So we need to pump
|
|
||||||
// out a diff to change a[i:ai] into b[j:bj], pump out
|
|
||||||
// the matching block, and move (i,j) beyond the match
|
|
||||||
ai, bj, size := m.A, m.B, m.Size
|
|
||||||
tag := byte(0)
|
|
||||||
if i < ai && j < bj {
|
|
||||||
tag = 'r'
|
|
||||||
} else if i < ai {
|
|
||||||
tag = 'd'
|
|
||||||
} else if j < bj {
|
|
||||||
tag = 'i'
|
|
||||||
}
|
|
||||||
if tag > 0 {
|
|
||||||
opCodes = append(opCodes, OpCode{tag, i, ai, j, bj})
|
|
||||||
}
|
|
||||||
i, j = ai+size, bj+size
|
|
||||||
// the list of matching blocks is terminated by a
|
|
||||||
// sentinel with size 0
|
|
||||||
if size > 0 {
|
|
||||||
opCodes = append(opCodes, OpCode{'e', ai, i, bj, j})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.opCodes = opCodes
|
|
||||||
return m.opCodes
|
|
||||||
}
|
|
||||||
|
|
||||||
// Isolate change clusters by eliminating ranges with no changes.
|
|
||||||
//
|
|
||||||
// Return a generator of groups with up to n lines of context.
|
|
||||||
// Each group is in the same format as returned by GetOpCodes().
|
|
||||||
func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
|
|
||||||
if n < 0 {
|
|
||||||
n = 3
|
|
||||||
}
|
|
||||||
codes := m.GetOpCodes()
|
|
||||||
if len(codes) == 0 {
|
|
||||||
codes = []OpCode{OpCode{'e', 0, 1, 0, 1}}
|
|
||||||
}
|
|
||||||
// Fixup leading and trailing groups if they show no changes.
|
|
||||||
if codes[0].Tag == 'e' {
|
|
||||||
c := codes[0]
|
|
||||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
|
||||||
codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2}
|
|
||||||
}
|
|
||||||
if codes[len(codes)-1].Tag == 'e' {
|
|
||||||
c := codes[len(codes)-1]
|
|
||||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
|
||||||
codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)}
|
|
||||||
}
|
|
||||||
nn := n + n
|
|
||||||
groups := [][]OpCode{}
|
|
||||||
group := []OpCode{}
|
|
||||||
for _, c := range codes {
|
|
||||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
|
||||||
// End the current group and start a new one whenever
|
|
||||||
// there is a large range with no changes.
|
|
||||||
if c.Tag == 'e' && i2-i1 > nn {
|
|
||||||
group = append(group, OpCode{c.Tag, i1, min(i2, i1+n),
|
|
||||||
j1, min(j2, j1+n)})
|
|
||||||
groups = append(groups, group)
|
|
||||||
group = []OpCode{}
|
|
||||||
i1, j1 = max(i1, i2-n), max(j1, j2-n)
|
|
||||||
}
|
|
||||||
group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
|
|
||||||
}
|
|
||||||
if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') {
|
|
||||||
groups = append(groups, group)
|
|
||||||
}
|
|
||||||
return groups
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a measure of the sequences' similarity (float in [0,1]).
|
|
||||||
//
|
|
||||||
// Where T is the total number of elements in both sequences, and
|
|
||||||
// M is the number of matches, this is 2.0*M / T.
|
|
||||||
// Note that this is 1 if the sequences are identical, and 0 if
|
|
||||||
// they have nothing in common.
|
|
||||||
//
|
|
||||||
// .Ratio() is expensive to compute if you haven't already computed
|
|
||||||
// .GetMatchingBlocks() or .GetOpCodes(), in which case you may
|
|
||||||
// want to try .QuickRatio() or .RealQuickRation() first to get an
|
|
||||||
// upper bound.
|
|
||||||
func (m *SequenceMatcher) Ratio() float64 {
|
|
||||||
matches := 0
|
|
||||||
for _, m := range m.GetMatchingBlocks() {
|
|
||||||
matches += m.Size
|
|
||||||
}
|
|
||||||
return calculateRatio(matches, len(m.a)+len(m.b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return an upper bound on ratio() relatively quickly.
|
|
||||||
//
|
|
||||||
// This isn't defined beyond that it is an upper bound on .Ratio(), and
|
|
||||||
// is faster to compute.
|
|
||||||
func (m *SequenceMatcher) QuickRatio() float64 {
|
|
||||||
// viewing a and b as multisets, set matches to the cardinality
|
|
||||||
// of their intersection; this counts the number of matches
|
|
||||||
// without regard to order, so is clearly an upper bound
|
|
||||||
if m.fullBCount == nil {
|
|
||||||
m.fullBCount = map[string]int{}
|
|
||||||
for _, s := range m.b {
|
|
||||||
m.fullBCount[s] = m.fullBCount[s] + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// avail[x] is the number of times x appears in 'b' less the
|
|
||||||
// number of times we've seen it in 'a' so far ... kinda
|
|
||||||
avail := map[string]int{}
|
|
||||||
matches := 0
|
|
||||||
for _, s := range m.a {
|
|
||||||
n, ok := avail[s]
|
|
||||||
if !ok {
|
|
||||||
n = m.fullBCount[s]
|
|
||||||
}
|
|
||||||
avail[s] = n - 1
|
|
||||||
if n > 0 {
|
|
||||||
matches += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return calculateRatio(matches, len(m.a)+len(m.b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return an upper bound on ratio() very quickly.
|
|
||||||
//
|
|
||||||
// This isn't defined beyond that it is an upper bound on .Ratio(), and
|
|
||||||
// is faster to compute than either .Ratio() or .QuickRatio().
|
|
||||||
func (m *SequenceMatcher) RealQuickRatio() float64 {
|
|
||||||
la, lb := len(m.a), len(m.b)
|
|
||||||
return calculateRatio(min(la, lb), la+lb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert range to the "ed" format
|
|
||||||
func formatRangeUnified(start, stop int) string {
|
|
||||||
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
|
||||||
beginning := start + 1 // lines start numbering with one
|
|
||||||
length := stop - start
|
|
||||||
if length == 1 {
|
|
||||||
return fmt.Sprintf("%d", beginning)
|
|
||||||
}
|
|
||||||
if length == 0 {
|
|
||||||
beginning -= 1 // empty ranges begin at line just before the range
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%d,%d", beginning, length)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unified diff parameters
|
|
||||||
type UnifiedDiff struct {
|
|
||||||
A []string // First sequence lines
|
|
||||||
FromFile string // First file name
|
|
||||||
FromDate string // First file time
|
|
||||||
B []string // Second sequence lines
|
|
||||||
ToFile string // Second file name
|
|
||||||
ToDate string // Second file time
|
|
||||||
Eol string // Headers end of line, defaults to LF
|
|
||||||
Context int // Number of context lines
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare two sequences of lines; generate the delta as a unified diff.
|
|
||||||
//
|
|
||||||
// Unified diffs are a compact way of showing line changes and a few
|
|
||||||
// lines of context. The number of context lines is set by 'n' which
|
|
||||||
// defaults to three.
|
|
||||||
//
|
|
||||||
// By default, the diff control lines (those with ---, +++, or @@) are
|
|
||||||
// created with a trailing newline. This is helpful so that inputs
|
|
||||||
// created from file.readlines() result in diffs that are suitable for
|
|
||||||
// file.writelines() since both the inputs and outputs have trailing
|
|
||||||
// newlines.
|
|
||||||
//
|
|
||||||
// For inputs that do not have trailing newlines, set the lineterm
|
|
||||||
// argument to "" so that the output will be uniformly newline free.
|
|
||||||
//
|
|
||||||
// The unidiff format normally has a header for filenames and modification
|
|
||||||
// times. Any or all of these may be specified using strings for
|
|
||||||
// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
|
|
||||||
// The modification times are normally expressed in the ISO 8601 format.
|
|
||||||
func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
|
|
||||||
buf := bufio.NewWriter(writer)
|
|
||||||
defer buf.Flush()
|
|
||||||
wf := func(format string, args ...interface{}) error {
|
|
||||||
_, err := buf.WriteString(fmt.Sprintf(format, args...))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ws := func(s string) error {
|
|
||||||
_, err := buf.WriteString(s)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(diff.Eol) == 0 {
|
|
||||||
diff.Eol = "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
started := false
|
|
||||||
m := NewMatcher(diff.A, diff.B)
|
|
||||||
for _, g := range m.GetGroupedOpCodes(diff.Context) {
|
|
||||||
if !started {
|
|
||||||
started = true
|
|
||||||
fromDate := ""
|
|
||||||
if len(diff.FromDate) > 0 {
|
|
||||||
fromDate = "\t" + diff.FromDate
|
|
||||||
}
|
|
||||||
toDate := ""
|
|
||||||
if len(diff.ToDate) > 0 {
|
|
||||||
toDate = "\t" + diff.ToDate
|
|
||||||
}
|
|
||||||
if diff.FromFile != "" || diff.ToFile != "" {
|
|
||||||
err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
first, last := g[0], g[len(g)-1]
|
|
||||||
range1 := formatRangeUnified(first.I1, last.I2)
|
|
||||||
range2 := formatRangeUnified(first.J1, last.J2)
|
|
||||||
if err := wf("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, c := range g {
|
|
||||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
|
||||||
if c.Tag == 'e' {
|
|
||||||
for _, line := range diff.A[i1:i2] {
|
|
||||||
if err := ws(" " + line); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if c.Tag == 'r' || c.Tag == 'd' {
|
|
||||||
for _, line := range diff.A[i1:i2] {
|
|
||||||
if err := ws("-" + line); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.Tag == 'r' || c.Tag == 'i' {
|
|
||||||
for _, line := range diff.B[j1:j2] {
|
|
||||||
if err := ws("+" + line); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Like WriteUnifiedDiff but returns the diff a string.
|
|
||||||
func GetUnifiedDiffString(diff UnifiedDiff) (string, error) {
|
|
||||||
w := &bytes.Buffer{}
|
|
||||||
err := WriteUnifiedDiff(w, diff)
|
|
||||||
return string(w.Bytes()), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert range to the "ed" format.
|
|
||||||
func formatRangeContext(start, stop int) string {
|
|
||||||
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
|
||||||
beginning := start + 1 // lines start numbering with one
|
|
||||||
length := stop - start
|
|
||||||
if length == 0 {
|
|
||||||
beginning -= 1 // empty ranges begin at line just before the range
|
|
||||||
}
|
|
||||||
if length <= 1 {
|
|
||||||
return fmt.Sprintf("%d", beginning)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%d,%d", beginning, beginning+length-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContextDiff UnifiedDiff
|
|
||||||
|
|
||||||
// Compare two sequences of lines; generate the delta as a context diff.
|
|
||||||
//
|
|
||||||
// Context diffs are a compact way of showing line changes and a few
|
|
||||||
// lines of context. The number of context lines is set by diff.Context
|
|
||||||
// which defaults to three.
|
|
||||||
//
|
|
||||||
// By default, the diff control lines (those with *** or ---) are
|
|
||||||
// created with a trailing newline.
|
|
||||||
//
|
|
||||||
// For inputs that do not have trailing newlines, set the diff.Eol
|
|
||||||
// argument to "" so that the output will be uniformly newline free.
|
|
||||||
//
|
|
||||||
// The context diff format normally has a header for filenames and
|
|
||||||
// modification times. Any or all of these may be specified using
|
|
||||||
// strings for diff.FromFile, diff.ToFile, diff.FromDate, diff.ToDate.
|
|
||||||
// The modification times are normally expressed in the ISO 8601 format.
|
|
||||||
// If not specified, the strings default to blanks.
|
|
||||||
func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
|
|
||||||
buf := bufio.NewWriter(writer)
|
|
||||||
defer buf.Flush()
|
|
||||||
var diffErr error
|
|
||||||
wf := func(format string, args ...interface{}) {
|
|
||||||
_, err := buf.WriteString(fmt.Sprintf(format, args...))
|
|
||||||
if diffErr == nil && err != nil {
|
|
||||||
diffErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ws := func(s string) {
|
|
||||||
_, err := buf.WriteString(s)
|
|
||||||
if diffErr == nil && err != nil {
|
|
||||||
diffErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(diff.Eol) == 0 {
|
|
||||||
diff.Eol = "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix := map[byte]string{
|
|
||||||
'i': "+ ",
|
|
||||||
'd': "- ",
|
|
||||||
'r': "! ",
|
|
||||||
'e': " ",
|
|
||||||
}
|
|
||||||
|
|
||||||
started := false
|
|
||||||
m := NewMatcher(diff.A, diff.B)
|
|
||||||
for _, g := range m.GetGroupedOpCodes(diff.Context) {
|
|
||||||
if !started {
|
|
||||||
started = true
|
|
||||||
fromDate := ""
|
|
||||||
if len(diff.FromDate) > 0 {
|
|
||||||
fromDate = "\t" + diff.FromDate
|
|
||||||
}
|
|
||||||
toDate := ""
|
|
||||||
if len(diff.ToDate) > 0 {
|
|
||||||
toDate = "\t" + diff.ToDate
|
|
||||||
}
|
|
||||||
if diff.FromFile != "" || diff.ToFile != "" {
|
|
||||||
wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol)
|
|
||||||
wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
first, last := g[0], g[len(g)-1]
|
|
||||||
ws("***************" + diff.Eol)
|
|
||||||
|
|
||||||
range1 := formatRangeContext(first.I1, last.I2)
|
|
||||||
wf("*** %s ****%s", range1, diff.Eol)
|
|
||||||
for _, c := range g {
|
|
||||||
if c.Tag == 'r' || c.Tag == 'd' {
|
|
||||||
for _, cc := range g {
|
|
||||||
if cc.Tag == 'i' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, line := range diff.A[cc.I1:cc.I2] {
|
|
||||||
ws(prefix[cc.Tag] + line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
range2 := formatRangeContext(first.J1, last.J2)
|
|
||||||
wf("--- %s ----%s", range2, diff.Eol)
|
|
||||||
for _, c := range g {
|
|
||||||
if c.Tag == 'r' || c.Tag == 'i' {
|
|
||||||
for _, cc := range g {
|
|
||||||
if cc.Tag == 'd' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, line := range diff.B[cc.J1:cc.J2] {
|
|
||||||
ws(prefix[cc.Tag] + line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return diffErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Like WriteContextDiff but returns the diff a string.
|
|
||||||
func GetContextDiffString(diff ContextDiff) (string, error) {
|
|
||||||
w := &bytes.Buffer{}
|
|
||||||
err := WriteContextDiff(w, diff)
|
|
||||||
return string(w.Bytes()), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split a string on "\n" while preserving them. The output can be used
|
|
||||||
// as input for UnifiedDiff and ContextDiff structures.
|
|
||||||
func SplitLines(s string) []string {
|
|
||||||
lines := strings.SplitAfter(s, "\n")
|
|
||||||
lines[len(lines)-1] += "\n"
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
-22
@@ -1,22 +0,0 @@
|
|||||||
Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell
|
|
||||||
|
|
||||||
Please consider promoting this project if you find it useful.
|
|
||||||
|
|
||||||
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.
|
|
||||||
-352
@@ -1,352 +0,0 @@
|
|||||||
/*
|
|
||||||
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
|
|
||||||
* THIS FILE MUST NOT BE EDITED BY HAND
|
|
||||||
*/
|
|
||||||
|
|
||||||
package assert
|
|
||||||
|
|
||||||
import (
|
|
||||||
http "net/http"
|
|
||||||
url "net/url"
|
|
||||||
time "time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Condition uses a Comparison to assert a complex condition.
|
|
||||||
func (a *Assertions) Condition(comp Comparison, msgAndArgs ...interface{}) bool {
|
|
||||||
return Condition(a.t, comp, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contains asserts that the specified string, list(array, slice...) or map contains the
|
|
||||||
// specified substring or element.
|
|
||||||
//
|
|
||||||
// a.Contains("Hello World", "World", "But 'Hello World' does contain 'World'")
|
|
||||||
// a.Contains(["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'")
|
|
||||||
// a.Contains({"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool {
|
|
||||||
return Contains(a.t, s, contains, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
|
||||||
// a slice or a channel with len == 0.
|
|
||||||
//
|
|
||||||
// a.Empty(obj)
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool {
|
|
||||||
return Empty(a.t, object, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal asserts that two objects are equal.
|
|
||||||
//
|
|
||||||
// a.Equal(123, 123, "123 and 123 should be equal")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
//
|
|
||||||
// Pointer variable equality is determined based on the equality of the
|
|
||||||
// referenced values (as opposed to the memory addresses).
|
|
||||||
func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
|
|
||||||
return Equal(a.t, expected, actual, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EqualError asserts that a function returned an error (i.e. not `nil`)
|
|
||||||
// and that it is equal to the provided error.
|
|
||||||
//
|
|
||||||
// actualObj, err := SomeFunction()
|
|
||||||
// a.EqualError(err, expectedErrorString, "An error was expected")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) bool {
|
|
||||||
return EqualError(a.t, theError, errString, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EqualValues asserts that two objects are equal or convertable to the same types
|
|
||||||
// and equal.
|
|
||||||
//
|
|
||||||
// a.EqualValues(uint32(123), int32(123), "123 and 123 should be equal")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
|
|
||||||
return EqualValues(a.t, expected, actual, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error asserts that a function returned an error (i.e. not `nil`).
|
|
||||||
//
|
|
||||||
// actualObj, err := SomeFunction()
|
|
||||||
// if a.Error(err, "An error was expected") {
|
|
||||||
// assert.Equal(t, err, expectedError)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool {
|
|
||||||
return Error(a.t, err, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exactly asserts that two objects are equal is value and type.
|
|
||||||
//
|
|
||||||
// a.Exactly(int32(123), int64(123), "123 and 123 should NOT be equal")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
|
|
||||||
return Exactly(a.t, expected, actual, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail reports a failure through
|
|
||||||
func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) bool {
|
|
||||||
return Fail(a.t, failureMessage, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FailNow fails test
|
|
||||||
func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) bool {
|
|
||||||
return FailNow(a.t, failureMessage, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// False asserts that the specified value is false.
|
|
||||||
//
|
|
||||||
// a.False(myBool, "myBool should be false")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool {
|
|
||||||
return False(a.t, value, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPBodyContains asserts that a specified handler returns a
|
|
||||||
// body that contains a string.
|
|
||||||
//
|
|
||||||
// a.HTTPBodyContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool {
|
|
||||||
return HTTPBodyContains(a.t, handler, method, url, values, str)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPBodyNotContains asserts that a specified handler returns a
|
|
||||||
// body that does not contain a string.
|
|
||||||
//
|
|
||||||
// a.HTTPBodyNotContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool {
|
|
||||||
return HTTPBodyNotContains(a.t, handler, method, url, values, str)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPError asserts that a specified handler returns an error status code.
|
|
||||||
//
|
|
||||||
// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values) bool {
|
|
||||||
return HTTPError(a.t, handler, method, url, values)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPRedirect asserts that a specified handler returns a redirect status code.
|
|
||||||
//
|
|
||||||
// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values) bool {
|
|
||||||
return HTTPRedirect(a.t, handler, method, url, values)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPSuccess asserts that a specified handler returns a success status code.
|
|
||||||
//
|
|
||||||
// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil)
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values) bool {
|
|
||||||
return HTTPSuccess(a.t, handler, method, url, values)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implements asserts that an object is implemented by the specified interface.
|
|
||||||
//
|
|
||||||
// a.Implements((*MyInterface)(nil), new(MyObject), "MyObject")
|
|
||||||
func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool {
|
|
||||||
return Implements(a.t, interfaceObject, object, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InDelta asserts that the two numerals are within delta of each other.
|
|
||||||
//
|
|
||||||
// a.InDelta(math.Pi, (22 / 7.0), 0.01)
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
|
|
||||||
return InDelta(a.t, expected, actual, delta, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InDeltaSlice is the same as InDelta, except it compares two slices.
|
|
||||||
func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
|
|
||||||
return InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InEpsilon asserts that expected and actual have a relative error less than epsilon
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool {
|
|
||||||
return InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices.
|
|
||||||
func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool {
|
|
||||||
return InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsType asserts that the specified objects are of the same type.
|
|
||||||
func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool {
|
|
||||||
return IsType(a.t, expectedType, object, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONEq asserts that two JSON strings are equivalent.
|
|
||||||
//
|
|
||||||
// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool {
|
|
||||||
return JSONEq(a.t, expected, actual, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len asserts that the specified object has specific length.
|
|
||||||
// Len also fails if the object has a type that len() not accept.
|
|
||||||
//
|
|
||||||
// a.Len(mySlice, 3, "The size of slice is not 3")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) bool {
|
|
||||||
return Len(a.t, object, length, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nil asserts that the specified object is nil.
|
|
||||||
//
|
|
||||||
// a.Nil(err, "err should be nothing")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool {
|
|
||||||
return Nil(a.t, object, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoError asserts that a function returned no error (i.e. `nil`).
|
|
||||||
//
|
|
||||||
// actualObj, err := SomeFunction()
|
|
||||||
// if a.NoError(err) {
|
|
||||||
// assert.Equal(t, actualObj, expectedObj)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool {
|
|
||||||
return NoError(a.t, err, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the
|
|
||||||
// specified substring or element.
|
|
||||||
//
|
|
||||||
// a.NotContains("Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'")
|
|
||||||
// a.NotContains(["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'")
|
|
||||||
// a.NotContains({"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool {
|
|
||||||
return NotContains(a.t, s, contains, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
|
||||||
// a slice or a channel with len == 0.
|
|
||||||
//
|
|
||||||
// if a.NotEmpty(obj) {
|
|
||||||
// assert.Equal(t, "two", obj[1])
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) bool {
|
|
||||||
return NotEmpty(a.t, object, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotEqual asserts that the specified values are NOT equal.
|
|
||||||
//
|
|
||||||
// a.NotEqual(obj1, obj2, "two objects shouldn't be equal")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
//
|
|
||||||
// Pointer variable equality is determined based on the equality of the
|
|
||||||
// referenced values (as opposed to the memory addresses).
|
|
||||||
func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
|
|
||||||
return NotEqual(a.t, expected, actual, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotNil asserts that the specified object is not nil.
|
|
||||||
//
|
|
||||||
// a.NotNil(err, "err should be something")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool {
|
|
||||||
return NotNil(a.t, object, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic.
|
|
||||||
//
|
|
||||||
// a.NotPanics(func(){
|
|
||||||
// RemainCalm()
|
|
||||||
// }, "Calling RemainCalm() should NOT panic")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
|
||||||
return NotPanics(a.t, f, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotRegexp asserts that a specified regexp does not match a string.
|
|
||||||
//
|
|
||||||
// a.NotRegexp(regexp.MustCompile("starts"), "it's starting")
|
|
||||||
// a.NotRegexp("^start", "it's not starting")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
|
|
||||||
return NotRegexp(a.t, rx, str, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotZero asserts that i is not the zero value for its type and returns the truth.
|
|
||||||
func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) bool {
|
|
||||||
return NotZero(a.t, i, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panics asserts that the code inside the specified PanicTestFunc panics.
|
|
||||||
//
|
|
||||||
// a.Panics(func(){
|
|
||||||
// GoCrazy()
|
|
||||||
// }, "Calling GoCrazy() should panic")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
|
||||||
return Panics(a.t, f, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regexp asserts that a specified regexp matches a string.
|
|
||||||
//
|
|
||||||
// a.Regexp(regexp.MustCompile("start"), "it's starting")
|
|
||||||
// a.Regexp("start...$", "it's not starting")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
|
|
||||||
return Regexp(a.t, rx, str, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// True asserts that the specified value is true.
|
|
||||||
//
|
|
||||||
// a.True(myBool, "myBool should be true")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool {
|
|
||||||
return True(a.t, value, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithinDuration asserts that the two times are within duration delta of each other.
|
|
||||||
//
|
|
||||||
// a.WithinDuration(time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool {
|
|
||||||
return WithinDuration(a.t, expected, actual, delta, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zero asserts that i is the zero value for its type and returns the truth.
|
|
||||||
func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) bool {
|
|
||||||
return Zero(a.t, i, msgAndArgs...)
|
|
||||||
}
|
|
||||||
-4
@@ -1,4 +0,0 @@
|
|||||||
{{.CommentWithoutT "a"}}
|
|
||||||
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool {
|
|
||||||
return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
|
|
||||||
}
|
|
||||||
-1069
File diff suppressed because it is too large
Load Diff
-45
@@ -1,45 +0,0 @@
|
|||||||
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
|
|
||||||
//
|
|
||||||
// Example Usage
|
|
||||||
//
|
|
||||||
// The following is a complete example using assert in a standard test function:
|
|
||||||
// import (
|
|
||||||
// "testing"
|
|
||||||
// "github.com/stretchr/testify/assert"
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// func TestSomething(t *testing.T) {
|
|
||||||
//
|
|
||||||
// var a string = "Hello"
|
|
||||||
// var b string = "Hello"
|
|
||||||
//
|
|
||||||
// assert.Equal(t, a, b, "The two words should be the same.")
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if you assert many times, use the format below:
|
|
||||||
//
|
|
||||||
// import (
|
|
||||||
// "testing"
|
|
||||||
// "github.com/stretchr/testify/assert"
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// func TestSomething(t *testing.T) {
|
|
||||||
// assert := assert.New(t)
|
|
||||||
//
|
|
||||||
// var a string = "Hello"
|
|
||||||
// var b string = "Hello"
|
|
||||||
//
|
|
||||||
// assert.Equal(a, b, "The two words should be the same.")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Assertions
|
|
||||||
//
|
|
||||||
// Assertions allow you to easily write test code, and are global funcs in the `assert` package.
|
|
||||||
// All assertion functions take, as the first argument, the `*testing.T` object provided by the
|
|
||||||
// testing framework. This allows the assertion funcs to write the failings and other details to
|
|
||||||
// the correct place.
|
|
||||||
//
|
|
||||||
// Every assertion function also takes an optional string message as the final argument,
|
|
||||||
// allowing custom error messages to be appended to the message the assertion method outputs.
|
|
||||||
package assert
|
|
||||||
-10
@@ -1,10 +0,0 @@
|
|||||||
package assert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AnError is an error instance useful for testing. If the code does not care
|
|
||||||
// about error specifics, and only needs to return the error for example, this
|
|
||||||
// error should be used to make the test code more readable.
|
|
||||||
var AnError = errors.New("assert.AnError general error for testing")
|
|
||||||
-16
@@ -1,16 +0,0 @@
|
|||||||
package assert
|
|
||||||
|
|
||||||
// Assertions provides assertion methods around the
|
|
||||||
// TestingT interface.
|
|
||||||
type Assertions struct {
|
|
||||||
t TestingT
|
|
||||||
}
|
|
||||||
|
|
||||||
// New makes a new Assertions object for the specified TestingT.
|
|
||||||
func New(t TestingT) *Assertions {
|
|
||||||
return &Assertions{
|
|
||||||
t: t,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl
|
|
||||||
-106
@@ -1,106 +0,0 @@
|
|||||||
package assert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// httpCode is a helper that returns HTTP code of the response. It returns -1
|
|
||||||
// if building a new request fails.
|
|
||||||
func httpCode(handler http.HandlerFunc, method, url string, values url.Values) int {
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
req, err := http.NewRequest(method, url+"?"+values.Encode(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
handler(w, req)
|
|
||||||
return w.Code
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPSuccess asserts that a specified handler returns a success status code.
|
|
||||||
//
|
|
||||||
// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil)
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool {
|
|
||||||
code := httpCode(handler, method, url, values)
|
|
||||||
if code == -1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return code >= http.StatusOK && code <= http.StatusPartialContent
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPRedirect asserts that a specified handler returns a redirect status code.
|
|
||||||
//
|
|
||||||
// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool {
|
|
||||||
code := httpCode(handler, method, url, values)
|
|
||||||
if code == -1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPError asserts that a specified handler returns an error status code.
|
|
||||||
//
|
|
||||||
// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool {
|
|
||||||
code := httpCode(handler, method, url, values)
|
|
||||||
if code == -1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return code >= http.StatusBadRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPBody is a helper that returns HTTP body of the response. It returns
|
|
||||||
// empty string if building a new request fails.
|
|
||||||
func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string {
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
req, err := http.NewRequest(method, url+"?"+values.Encode(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
handler(w, req)
|
|
||||||
return w.Body.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPBodyContains asserts that a specified handler returns a
|
|
||||||
// body that contains a string.
|
|
||||||
//
|
|
||||||
// assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool {
|
|
||||||
body := HTTPBody(handler, method, url, values)
|
|
||||||
|
|
||||||
contains := strings.Contains(body, fmt.Sprint(str))
|
|
||||||
if !contains {
|
|
||||||
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
|
|
||||||
}
|
|
||||||
|
|
||||||
return contains
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPBodyNotContains asserts that a specified handler returns a
|
|
||||||
// body that does not contain a string.
|
|
||||||
//
|
|
||||||
// assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky")
|
|
||||||
//
|
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
|
||||||
func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool {
|
|
||||||
body := HTTPBody(handler, method, url, values)
|
|
||||||
|
|
||||||
contains := strings.Contains(body, fmt.Sprint(str))
|
|
||||||
if contains {
|
|
||||||
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
|
|
||||||
}
|
|
||||||
|
|
||||||
return !contains
|
|
||||||
}
|
|
||||||
-392
@@ -1,392 +0,0 @@
|
|||||||
# Change Log
|
|
||||||
|
|
||||||
**ATTN**: This project uses [semantic versioning](http://semver.org/).
|
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
|
|
||||||
## [1.19.1] - 2016-11-21
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fixes regression introduced in 1.19.0 where using an `ActionFunc` as
|
|
||||||
the `Action` for a command would cause it to error rather than calling the
|
|
||||||
function. Should not have a affected declarative cases using `func(c
|
|
||||||
*cli.Context) err)`.
|
|
||||||
- Shell completion now handles the case where the user specifies
|
|
||||||
`--generate-bash-completion` immediately after a flag that takes an argument.
|
|
||||||
Previously it call the application with `--generate-bash-completion` as the
|
|
||||||
flag value.
|
|
||||||
|
|
||||||
## [1.19.0] - 2016-11-19
|
|
||||||
### Added
|
|
||||||
- `FlagsByName` was added to make it easy to sort flags (e.g. `sort.Sort(cli.FlagsByName(app.Flags))`)
|
|
||||||
- A `Description` field was added to `App` for a more detailed description of
|
|
||||||
the application (similar to the existing `Description` field on `Command`)
|
|
||||||
- Flag type code generation via `go generate`
|
|
||||||
- Write to stderr and exit 1 if action returns non-nil error
|
|
||||||
- Added support for TOML to the `altsrc` loader
|
|
||||||
- `SkipArgReorder` was added to allow users to skip the argument reordering.
|
|
||||||
This is useful if you want to consider all "flags" after an argument as
|
|
||||||
arguments rather than flags (the default behavior of the stdlib `flag`
|
|
||||||
library). This is backported functionality from the [removal of the flag
|
|
||||||
reordering](https://github.com/urfave/cli/pull/398) in the unreleased version
|
|
||||||
2
|
|
||||||
- For formatted errors (those implementing `ErrorFormatter`), the errors will
|
|
||||||
be formatted during output. Compatible with `pkg/errors`.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Raise minimum tested/supported Go version to 1.2+
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Consider empty environment variables as set (previously environment variables
|
|
||||||
with the equivalent of `""` would be skipped rather than their value used).
|
|
||||||
- Return an error if the value in a given environment variable cannot be parsed
|
|
||||||
as the flag type. Previously these errors were silently swallowed.
|
|
||||||
- Print full error when an invalid flag is specified (which includes the invalid flag)
|
|
||||||
- `App.Writer` defaults to `stdout` when `nil`
|
|
||||||
- If no action is specified on a command or app, the help is now printed instead of `panic`ing
|
|
||||||
- `App.Metadata` is initialized automatically now (previously was `nil` unless initialized)
|
|
||||||
- Correctly show help message if `-h` is provided to a subcommand
|
|
||||||
- `context.(Global)IsSet` now respects environment variables. Previously it
|
|
||||||
would return `false` if a flag was specified in the environment rather than
|
|
||||||
as an argument
|
|
||||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
|
|
||||||
- `altsrc`s import paths were updated to use `gopkg.in/urfave/cli.v1`. This
|
|
||||||
fixes issues that occurred when `gopkg.in/urfave/cli.v1` was imported as well
|
|
||||||
as `altsrc` where Go would complain that the types didn't match
|
|
||||||
|
|
||||||
## [1.18.1] - 2016-08-28
|
|
||||||
### Fixed
|
|
||||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user (backported)
|
|
||||||
|
|
||||||
## [1.18.0] - 2016-06-27
|
|
||||||
### Added
|
|
||||||
- `./runtests` test runner with coverage tracking by default
|
|
||||||
- testing on OS X
|
|
||||||
- testing on Windows
|
|
||||||
- `UintFlag`, `Uint64Flag`, and `Int64Flag` types and supporting code
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Use spaces for alignment in help/usage output instead of tabs, making the
|
|
||||||
output alignment consistent regardless of tab width
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Printing of command aliases in help text
|
|
||||||
- Printing of visible flags for both struct and struct pointer flags
|
|
||||||
- Display the `help` subcommand when using `CommandCategories`
|
|
||||||
- No longer swallows `panic`s that occur within the `Action`s themselves when
|
|
||||||
detecting the signature of the `Action` field
|
|
||||||
|
|
||||||
## [1.17.1] - 2016-08-28
|
|
||||||
### Fixed
|
|
||||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
|
|
||||||
|
|
||||||
## [1.17.0] - 2016-05-09
|
|
||||||
### Added
|
|
||||||
- Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc`
|
|
||||||
- `context.GlobalBoolT` was added as an analogue to `context.GlobalBool`
|
|
||||||
- Support for hiding commands by setting `Hidden: true` -- this will hide the
|
|
||||||
commands in help output
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer
|
|
||||||
quoted in help text output.
|
|
||||||
- All flag types now include `(default: {value})` strings following usage when a
|
|
||||||
default value can be (reasonably) detected.
|
|
||||||
- `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent
|
|
||||||
with non-slice flag types
|
|
||||||
- Apps now exit with a code of 3 if an unknown subcommand is specified
|
|
||||||
(previously they printed "No help topic for...", but still exited 0. This
|
|
||||||
makes it easier to script around apps built using `cli` since they can trust
|
|
||||||
that a 0 exit code indicated a successful execution.
|
|
||||||
- cleanups based on [Go Report Card
|
|
||||||
feedback](https://goreportcard.com/report/github.com/urfave/cli)
|
|
||||||
|
|
||||||
## [1.16.1] - 2016-08-28
|
|
||||||
### Fixed
|
|
||||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
|
|
||||||
|
|
||||||
## [1.16.0] - 2016-05-02
|
|
||||||
### Added
|
|
||||||
- `Hidden` field on all flag struct types to omit from generated help text
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- `BashCompletionFlag` (`--enable-bash-completion`) is now omitted from
|
|
||||||
generated help text via the `Hidden` field
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- handling of error values in `HandleAction` and `HandleExitCoder`
|
|
||||||
|
|
||||||
## [1.15.0] - 2016-04-30
|
|
||||||
### Added
|
|
||||||
- This file!
|
|
||||||
- Support for placeholders in flag usage strings
|
|
||||||
- `App.Metadata` map for arbitrary data/state management
|
|
||||||
- `Set` and `GlobalSet` methods on `*cli.Context` for altering values after
|
|
||||||
parsing.
|
|
||||||
- Support for nested lookup of dot-delimited keys in structures loaded from
|
|
||||||
YAML.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- The `App.Action` and `Command.Action` now prefer a return signature of
|
|
||||||
`func(*cli.Context) error`, as defined by `cli.ActionFunc`. If a non-nil
|
|
||||||
`error` is returned, there may be two outcomes:
|
|
||||||
- If the error fulfills `cli.ExitCoder`, then `os.Exit` will be called
|
|
||||||
automatically
|
|
||||||
- Else the error is bubbled up and returned from `App.Run`
|
|
||||||
- Specifying an `Action` with the legacy return signature of
|
|
||||||
`func(*cli.Context)` will produce a deprecation message to stderr
|
|
||||||
- Specifying an `Action` that is not a `func` type will produce a non-zero exit
|
|
||||||
from `App.Run`
|
|
||||||
- Specifying an `Action` func that has an invalid (input) signature will
|
|
||||||
produce a non-zero exit from `App.Run`
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
- <a name="deprecated-cli-app-runandexitonerror"></a>
|
|
||||||
`cli.App.RunAndExitOnError`, which should now be done by returning an error
|
|
||||||
that fulfills `cli.ExitCoder` to `cli.App.Run`.
|
|
||||||
- <a name="deprecated-cli-app-action-signature"></a> the legacy signature for
|
|
||||||
`cli.App.Action` of `func(*cli.Context)`, which should now have a return
|
|
||||||
signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Added missing `*cli.Context.GlobalFloat64` method
|
|
||||||
|
|
||||||
## [1.14.0] - 2016-04-03 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Codebeat badge
|
|
||||||
- Support for categorization via `CategorizedHelp` and `Categories` on app.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Use `filepath.Base` instead of `path.Base` in `Name` and `HelpName`.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Ensure version is not shown in help text when `HideVersion` set.
|
|
||||||
|
|
||||||
## [1.13.0] - 2016-03-06 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- YAML file input support.
|
|
||||||
- `NArg` method on context.
|
|
||||||
|
|
||||||
## [1.12.0] - 2016-02-17 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Custom usage error handling.
|
|
||||||
- Custom text support in `USAGE` section of help output.
|
|
||||||
- Improved help messages for empty strings.
|
|
||||||
- AppVeyor CI configuration.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Removed `panic` from default help printer func.
|
|
||||||
- De-duping and optimizations.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Correctly handle `Before`/`After` at command level when no subcommands.
|
|
||||||
- Case of literal `-` argument causing flag reordering.
|
|
||||||
- Environment variable hints on Windows.
|
|
||||||
- Docs updates.
|
|
||||||
|
|
||||||
## [1.11.1] - 2015-12-21 (backfilled 2016-04-25)
|
|
||||||
### Changed
|
|
||||||
- Use `path.Base` in `Name` and `HelpName`
|
|
||||||
- Export `GetName` on flag types.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Flag parsing when skipping is enabled.
|
|
||||||
- Test output cleanup.
|
|
||||||
- Move completion check to account for empty input case.
|
|
||||||
|
|
||||||
## [1.11.0] - 2015-11-15 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Destination scan support for flags.
|
|
||||||
- Testing against `tip` in Travis CI config.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Go version in Travis CI config.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Removed redundant tests.
|
|
||||||
- Use correct example naming in tests.
|
|
||||||
|
|
||||||
## [1.10.2] - 2015-10-29 (backfilled 2016-04-25)
|
|
||||||
### Fixed
|
|
||||||
- Remove unused var in bash completion.
|
|
||||||
|
|
||||||
## [1.10.1] - 2015-10-21 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Coverage and reference logos in README.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Use specified values in help and version parsing.
|
|
||||||
- Only display app version and help message once.
|
|
||||||
|
|
||||||
## [1.10.0] - 2015-10-06 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- More tests for existing functionality.
|
|
||||||
- `ArgsUsage` at app and command level for help text flexibility.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Honor `HideHelp` and `HideVersion` in `App.Run`.
|
|
||||||
- Remove juvenile word from README.
|
|
||||||
|
|
||||||
## [1.9.0] - 2015-09-08 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- `FullName` on command with accompanying help output update.
|
|
||||||
- Set default `$PROG` in bash completion.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Docs formatting.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Removed self-referential imports in tests.
|
|
||||||
|
|
||||||
## [1.8.0] - 2015-06-30 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Support for `Copyright` at app level.
|
|
||||||
- `Parent` func at context level to walk up context lineage.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Global flag processing at top level.
|
|
||||||
|
|
||||||
## [1.7.1] - 2015-06-11 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Aggregate errors from `Before`/`After` funcs.
|
|
||||||
- Doc comments on flag structs.
|
|
||||||
- Include non-global flags when checking version and help.
|
|
||||||
- Travis CI config updates.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Ensure slice type flags have non-nil values.
|
|
||||||
- Collect global flags from the full command hierarchy.
|
|
||||||
- Docs prose.
|
|
||||||
|
|
||||||
## [1.7.0] - 2015-05-03 (backfilled 2016-04-25)
|
|
||||||
### Changed
|
|
||||||
- `HelpPrinter` signature includes output writer.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Specify go 1.1+ in docs.
|
|
||||||
- Set `Writer` when running command as app.
|
|
||||||
|
|
||||||
## [1.6.0] - 2015-03-23 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Multiple author support.
|
|
||||||
- `NumFlags` at context level.
|
|
||||||
- `Aliases` at command level.
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
- `ShortName` at command level.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Subcommand help output.
|
|
||||||
- Backward compatible support for deprecated `Author` and `Email` fields.
|
|
||||||
- Docs regarding `Names`/`Aliases`.
|
|
||||||
|
|
||||||
## [1.5.0] - 2015-02-20 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- `After` hook func support at app and command level.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Use parsed context when running command as subcommand.
|
|
||||||
- Docs prose.
|
|
||||||
|
|
||||||
## [1.4.1] - 2015-01-09 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Support for hiding `-h / --help` flags, but not `help` subcommand.
|
|
||||||
- Stop flag parsing after `--`.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Help text for generic flags to specify single value.
|
|
||||||
- Use double quotes in output for defaults.
|
|
||||||
- Use `ParseInt` instead of `ParseUint` for int environment var values.
|
|
||||||
- Use `0` as base when parsing int environment var values.
|
|
||||||
|
|
||||||
## [1.4.0] - 2014-12-12 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Support for environment variable lookup "cascade".
|
|
||||||
- Support for `Stdout` on app for output redirection.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Print command help instead of app help in `ShowCommandHelp`.
|
|
||||||
|
|
||||||
## [1.3.1] - 2014-11-13 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Docs and example code updates.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Default `-v / --version` flag made optional.
|
|
||||||
|
|
||||||
## [1.3.0] - 2014-08-10 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- `FlagNames` at context level.
|
|
||||||
- Exposed `VersionPrinter` var for more control over version output.
|
|
||||||
- Zsh completion hook.
|
|
||||||
- `AUTHOR` section in default app help template.
|
|
||||||
- Contribution guidelines.
|
|
||||||
- `DurationFlag` type.
|
|
||||||
|
|
||||||
## [1.2.0] - 2014-08-02
|
|
||||||
### Added
|
|
||||||
- Support for environment variable defaults on flags plus tests.
|
|
||||||
|
|
||||||
## [1.1.0] - 2014-07-15
|
|
||||||
### Added
|
|
||||||
- Bash completion.
|
|
||||||
- Optional hiding of built-in help command.
|
|
||||||
- Optional skipping of flag parsing at command level.
|
|
||||||
- `Author`, `Email`, and `Compiled` metadata on app.
|
|
||||||
- `Before` hook func support at app and command level.
|
|
||||||
- `CommandNotFound` func support at app level.
|
|
||||||
- Command reference available on context.
|
|
||||||
- `GenericFlag` type.
|
|
||||||
- `Float64Flag` type.
|
|
||||||
- `BoolTFlag` type.
|
|
||||||
- `IsSet` flag helper on context.
|
|
||||||
- More flag lookup funcs at context level.
|
|
||||||
- More tests & docs.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Help template updates to account for presence/absence of flags.
|
|
||||||
- Separated subcommand help template.
|
|
||||||
- Exposed `HelpPrinter` var for more control over help output.
|
|
||||||
|
|
||||||
## [1.0.0] - 2013-11-01
|
|
||||||
### Added
|
|
||||||
- `help` flag in default app flag set and each command flag set.
|
|
||||||
- Custom handling of argument parsing errors.
|
|
||||||
- Command lookup by name at app level.
|
|
||||||
- `StringSliceFlag` type and supporting `StringSlice` type.
|
|
||||||
- `IntSliceFlag` type and supporting `IntSlice` type.
|
|
||||||
- Slice type flag lookups by name at context level.
|
|
||||||
- Export of app and command help functions.
|
|
||||||
- More tests & docs.
|
|
||||||
|
|
||||||
## 0.1.0 - 2013-07-22
|
|
||||||
### Added
|
|
||||||
- Initial implementation.
|
|
||||||
|
|
||||||
[Unreleased]: https://github.com/urfave/cli/compare/v1.18.0...HEAD
|
|
||||||
[1.18.0]: https://github.com/urfave/cli/compare/v1.17.0...v1.18.0
|
|
||||||
[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0
|
|
||||||
[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0
|
|
||||||
[1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0
|
|
||||||
[1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0
|
|
||||||
[1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0
|
|
||||||
[1.12.0]: https://github.com/urfave/cli/compare/v1.11.1...v1.12.0
|
|
||||||
[1.11.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1
|
|
||||||
[1.11.0]: https://github.com/urfave/cli/compare/v1.10.2...v1.11.0
|
|
||||||
[1.10.2]: https://github.com/urfave/cli/compare/v1.10.1...v1.10.2
|
|
||||||
[1.10.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1
|
|
||||||
[1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0
|
|
||||||
[1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0
|
|
||||||
[1.8.0]: https://github.com/urfave/cli/compare/v1.7.1...v1.8.0
|
|
||||||
[1.7.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1
|
|
||||||
[1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0
|
|
||||||
[1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0
|
|
||||||
[1.5.0]: https://github.com/urfave/cli/compare/v1.4.1...v1.5.0
|
|
||||||
[1.4.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1
|
|
||||||
[1.4.0]: https://github.com/urfave/cli/compare/v1.3.1...v1.4.0
|
|
||||||
[1.3.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1
|
|
||||||
[1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0
|
|
||||||
[1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0
|
|
||||||
[1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0
|
|
||||||
[1.0.0]: https://github.com/urfave/cli/compare/v0.1.0...v1.0.0
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2016 Jeremy Saenz & Contributors
|
|
||||||
|
|
||||||
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.
|
|
||||||
-1381
File diff suppressed because it is too large
Load Diff
-492
@@ -1,492 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md"
|
|
||||||
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
|
|
||||||
runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL)
|
|
||||||
|
|
||||||
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
|
|
||||||
|
|
||||||
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
|
|
||||||
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
|
|
||||||
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
|
|
||||||
)
|
|
||||||
|
|
||||||
// App is the main structure of a cli application. It is recommended that
|
|
||||||
// an app be created with the cli.NewApp() function
|
|
||||||
type App struct {
|
|
||||||
// The name of the program. Defaults to path.Base(os.Args[0])
|
|
||||||
Name string
|
|
||||||
// Full name of command for help, defaults to Name
|
|
||||||
HelpName string
|
|
||||||
// Description of the program.
|
|
||||||
Usage string
|
|
||||||
// Text to override the USAGE section of help
|
|
||||||
UsageText string
|
|
||||||
// Description of the program argument format.
|
|
||||||
ArgsUsage string
|
|
||||||
// Version of the program
|
|
||||||
Version string
|
|
||||||
// Description of the program
|
|
||||||
Description string
|
|
||||||
// List of commands to execute
|
|
||||||
Commands []Command
|
|
||||||
// List of flags to parse
|
|
||||||
Flags []Flag
|
|
||||||
// Boolean to enable bash completion commands
|
|
||||||
EnableBashCompletion bool
|
|
||||||
// Boolean to hide built-in help command
|
|
||||||
HideHelp bool
|
|
||||||
// Boolean to hide built-in version flag and the VERSION section of help
|
|
||||||
HideVersion bool
|
|
||||||
// Populate on app startup, only gettable through method Categories()
|
|
||||||
categories CommandCategories
|
|
||||||
// An action to execute when the bash-completion flag is set
|
|
||||||
BashComplete BashCompleteFunc
|
|
||||||
// An action to execute before any subcommands are run, but after the context is ready
|
|
||||||
// If a non-nil error is returned, no subcommands are run
|
|
||||||
Before BeforeFunc
|
|
||||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
|
||||||
// It is run even if Action() panics
|
|
||||||
After AfterFunc
|
|
||||||
|
|
||||||
// The action to execute when no subcommands are specified
|
|
||||||
// Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}`
|
|
||||||
// *Note*: support for the deprecated `Action` signature will be removed in a future version
|
|
||||||
Action interface{}
|
|
||||||
|
|
||||||
// Execute this function if the proper command cannot be found
|
|
||||||
CommandNotFound CommandNotFoundFunc
|
|
||||||
// Execute this function if an usage error occurs
|
|
||||||
OnUsageError OnUsageErrorFunc
|
|
||||||
// Compilation date
|
|
||||||
Compiled time.Time
|
|
||||||
// List of all authors who contributed
|
|
||||||
Authors []Author
|
|
||||||
// Copyright of the binary if any
|
|
||||||
Copyright string
|
|
||||||
// Name of Author (Note: Use App.Authors, this is deprecated)
|
|
||||||
Author string
|
|
||||||
// Email of Author (Note: Use App.Authors, this is deprecated)
|
|
||||||
Email string
|
|
||||||
// Writer writer to write output to
|
|
||||||
Writer io.Writer
|
|
||||||
// ErrWriter writes error output
|
|
||||||
ErrWriter io.Writer
|
|
||||||
// Other custom info
|
|
||||||
Metadata map[string]interface{}
|
|
||||||
|
|
||||||
didSetup bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tries to find out when this binary was compiled.
|
|
||||||
// Returns the current time if it fails to find it.
|
|
||||||
func compileTime() time.Time {
|
|
||||||
info, err := os.Stat(os.Args[0])
|
|
||||||
if err != nil {
|
|
||||||
return time.Now()
|
|
||||||
}
|
|
||||||
return info.ModTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewApp creates a new cli Application with some reasonable defaults for Name,
|
|
||||||
// Usage, Version and Action.
|
|
||||||
func NewApp() *App {
|
|
||||||
return &App{
|
|
||||||
Name: filepath.Base(os.Args[0]),
|
|
||||||
HelpName: filepath.Base(os.Args[0]),
|
|
||||||
Usage: "A new cli application",
|
|
||||||
UsageText: "",
|
|
||||||
Version: "0.0.0",
|
|
||||||
BashComplete: DefaultAppComplete,
|
|
||||||
Action: helpCommand.Action,
|
|
||||||
Compiled: compileTime(),
|
|
||||||
Writer: os.Stdout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup runs initialization code to ensure all data structures are ready for
|
|
||||||
// `Run` or inspection prior to `Run`. It is internally called by `Run`, but
|
|
||||||
// will return early if setup has already happened.
|
|
||||||
func (a *App) Setup() {
|
|
||||||
if a.didSetup {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
a.didSetup = true
|
|
||||||
|
|
||||||
if a.Author != "" || a.Email != "" {
|
|
||||||
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
|
|
||||||
}
|
|
||||||
|
|
||||||
newCmds := []Command{}
|
|
||||||
for _, c := range a.Commands {
|
|
||||||
if c.HelpName == "" {
|
|
||||||
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
|
|
||||||
}
|
|
||||||
newCmds = append(newCmds, c)
|
|
||||||
}
|
|
||||||
a.Commands = newCmds
|
|
||||||
|
|
||||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
|
||||||
a.Commands = append(a.Commands, helpCommand)
|
|
||||||
if (HelpFlag != BoolFlag{}) {
|
|
||||||
a.appendFlag(HelpFlag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !a.HideVersion {
|
|
||||||
a.appendFlag(VersionFlag)
|
|
||||||
}
|
|
||||||
|
|
||||||
a.categories = CommandCategories{}
|
|
||||||
for _, command := range a.Commands {
|
|
||||||
a.categories = a.categories.AddCommand(command.Category, command)
|
|
||||||
}
|
|
||||||
sort.Sort(a.categories)
|
|
||||||
|
|
||||||
if a.Metadata == nil {
|
|
||||||
a.Metadata = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Writer == nil {
|
|
||||||
a.Writer = os.Stdout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run is the entry point to the cli app. Parses the arguments slice and routes
|
|
||||||
// to the proper flag/args combination
|
|
||||||
func (a *App) Run(arguments []string) (err error) {
|
|
||||||
a.Setup()
|
|
||||||
|
|
||||||
// handle the completion flag separately from the flagset since
|
|
||||||
// completion could be attempted after a flag, but before its value was put
|
|
||||||
// on the command line. this causes the flagset to interpret the completion
|
|
||||||
// flag name as the value of the flag before it which is undesirable
|
|
||||||
// note that we can only do this because the shell autocomplete function
|
|
||||||
// always appends the completion flag at the end of the command
|
|
||||||
shellComplete, arguments := checkShellCompleteFlag(a, arguments)
|
|
||||||
|
|
||||||
// parse flags
|
|
||||||
set, err := flagSet(a.Name, a.Flags)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
set.SetOutput(ioutil.Discard)
|
|
||||||
err = set.Parse(arguments[1:])
|
|
||||||
nerr := normalizeFlags(a.Flags, set)
|
|
||||||
context := NewContext(a, set, nil)
|
|
||||||
if nerr != nil {
|
|
||||||
fmt.Fprintln(a.Writer, nerr)
|
|
||||||
ShowAppHelp(context)
|
|
||||||
return nerr
|
|
||||||
}
|
|
||||||
context.shellComplete = shellComplete
|
|
||||||
|
|
||||||
if checkCompletions(context) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if a.OnUsageError != nil {
|
|
||||||
err := a.OnUsageError(context, err, false)
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
|
||||||
ShowAppHelp(context)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !a.HideHelp && checkHelp(context) {
|
|
||||||
ShowAppHelp(context)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !a.HideVersion && checkVersion(context) {
|
|
||||||
ShowVersion(context)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.After != nil {
|
|
||||||
defer func() {
|
|
||||||
if afterErr := a.After(context); afterErr != nil {
|
|
||||||
if err != nil {
|
|
||||||
err = NewMultiError(err, afterErr)
|
|
||||||
} else {
|
|
||||||
err = afterErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Before != nil {
|
|
||||||
beforeErr := a.Before(context)
|
|
||||||
if beforeErr != nil {
|
|
||||||
fmt.Fprintf(a.Writer, "%v\n\n", beforeErr)
|
|
||||||
ShowAppHelp(context)
|
|
||||||
HandleExitCoder(beforeErr)
|
|
||||||
err = beforeErr
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
args := context.Args()
|
|
||||||
if args.Present() {
|
|
||||||
name := args.First()
|
|
||||||
c := a.Command(name)
|
|
||||||
if c != nil {
|
|
||||||
return c.Run(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Action == nil {
|
|
||||||
a.Action = helpCommand.Action
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run default Action
|
|
||||||
err = HandleAction(a.Action, context)
|
|
||||||
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunAndExitOnError calls .Run() and exits non-zero if an error was returned
|
|
||||||
//
|
|
||||||
// Deprecated: instead you should return an error that fulfills cli.ExitCoder
|
|
||||||
// to cli.App.Run. This will cause the application to exit with the given eror
|
|
||||||
// code in the cli.ExitCoder
|
|
||||||
func (a *App) RunAndExitOnError() {
|
|
||||||
if err := a.Run(os.Args); err != nil {
|
|
||||||
fmt.Fprintln(a.errWriter(), err)
|
|
||||||
OsExiter(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
|
|
||||||
// generate command-specific flags
|
|
||||||
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
|
||||||
// append help to commands
|
|
||||||
if len(a.Commands) > 0 {
|
|
||||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
|
||||||
a.Commands = append(a.Commands, helpCommand)
|
|
||||||
if (HelpFlag != BoolFlag{}) {
|
|
||||||
a.appendFlag(HelpFlag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newCmds := []Command{}
|
|
||||||
for _, c := range a.Commands {
|
|
||||||
if c.HelpName == "" {
|
|
||||||
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
|
|
||||||
}
|
|
||||||
newCmds = append(newCmds, c)
|
|
||||||
}
|
|
||||||
a.Commands = newCmds
|
|
||||||
|
|
||||||
// parse flags
|
|
||||||
set, err := flagSet(a.Name, a.Flags)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
set.SetOutput(ioutil.Discard)
|
|
||||||
err = set.Parse(ctx.Args().Tail())
|
|
||||||
nerr := normalizeFlags(a.Flags, set)
|
|
||||||
context := NewContext(a, set, ctx)
|
|
||||||
|
|
||||||
if nerr != nil {
|
|
||||||
fmt.Fprintln(a.Writer, nerr)
|
|
||||||
fmt.Fprintln(a.Writer)
|
|
||||||
if len(a.Commands) > 0 {
|
|
||||||
ShowSubcommandHelp(context)
|
|
||||||
} else {
|
|
||||||
ShowCommandHelp(ctx, context.Args().First())
|
|
||||||
}
|
|
||||||
return nerr
|
|
||||||
}
|
|
||||||
|
|
||||||
if checkCompletions(context) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if a.OnUsageError != nil {
|
|
||||||
err = a.OnUsageError(context, err, true)
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
|
||||||
ShowSubcommandHelp(context)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(a.Commands) > 0 {
|
|
||||||
if checkSubcommandHelp(context) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if checkCommandHelp(ctx, context.Args().First()) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.After != nil {
|
|
||||||
defer func() {
|
|
||||||
afterErr := a.After(context)
|
|
||||||
if afterErr != nil {
|
|
||||||
HandleExitCoder(err)
|
|
||||||
if err != nil {
|
|
||||||
err = NewMultiError(err, afterErr)
|
|
||||||
} else {
|
|
||||||
err = afterErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Before != nil {
|
|
||||||
beforeErr := a.Before(context)
|
|
||||||
if beforeErr != nil {
|
|
||||||
HandleExitCoder(beforeErr)
|
|
||||||
err = beforeErr
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
args := context.Args()
|
|
||||||
if args.Present() {
|
|
||||||
name := args.First()
|
|
||||||
c := a.Command(name)
|
|
||||||
if c != nil {
|
|
||||||
return c.Run(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run default Action
|
|
||||||
err = HandleAction(a.Action, context)
|
|
||||||
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the named command on App. Returns nil if the command does not exist
|
|
||||||
func (a *App) Command(name string) *Command {
|
|
||||||
for _, c := range a.Commands {
|
|
||||||
if c.HasName(name) {
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Categories returns a slice containing all the categories with the commands they contain
|
|
||||||
func (a *App) Categories() CommandCategories {
|
|
||||||
return a.categories
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleCategories returns a slice of categories and commands that are
|
|
||||||
// Hidden=false
|
|
||||||
func (a *App) VisibleCategories() []*CommandCategory {
|
|
||||||
ret := []*CommandCategory{}
|
|
||||||
for _, category := range a.categories {
|
|
||||||
if visible := func() *CommandCategory {
|
|
||||||
for _, command := range category.Commands {
|
|
||||||
if !command.Hidden {
|
|
||||||
return category
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}(); visible != nil {
|
|
||||||
ret = append(ret, visible)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
|
||||||
func (a *App) VisibleCommands() []Command {
|
|
||||||
ret := []Command{}
|
|
||||||
for _, command := range a.Commands {
|
|
||||||
if !command.Hidden {
|
|
||||||
ret = append(ret, command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
|
||||||
func (a *App) VisibleFlags() []Flag {
|
|
||||||
return visibleFlags(a.Flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) hasFlag(flag Flag) bool {
|
|
||||||
for _, f := range a.Flags {
|
|
||||||
if flag == f {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) errWriter() io.Writer {
|
|
||||||
|
|
||||||
// When the app ErrWriter is nil use the package level one.
|
|
||||||
if a.ErrWriter == nil {
|
|
||||||
return ErrWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.ErrWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) appendFlag(flag Flag) {
|
|
||||||
if !a.hasFlag(flag) {
|
|
||||||
a.Flags = append(a.Flags, flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Author represents someone who has contributed to a cli project.
|
|
||||||
type Author struct {
|
|
||||||
Name string // The Authors name
|
|
||||||
Email string // The Authors email
|
|
||||||
}
|
|
||||||
|
|
||||||
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
|
|
||||||
func (a Author) String() string {
|
|
||||||
e := ""
|
|
||||||
if a.Email != "" {
|
|
||||||
e = " <" + a.Email + ">"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%v%v", a.Name, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleAction attempts to figure out which Action signature was used. If
|
|
||||||
// it's an ActionFunc or a func with the legacy signature for Action, the func
|
|
||||||
// is run!
|
|
||||||
func HandleAction(action interface{}, context *Context) (err error) {
|
|
||||||
if a, ok := action.(ActionFunc); ok {
|
|
||||||
return a(context)
|
|
||||||
} else if a, ok := action.(func(*Context) error); ok {
|
|
||||||
return a(context)
|
|
||||||
} else if a, ok := action.(func(*Context)); ok { // deprecated function signature
|
|
||||||
a(context)
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return errInvalidActionType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-24
@@ -1,24 +0,0 @@
|
|||||||
version: "{build}"
|
|
||||||
|
|
||||||
os: Windows Server 2012 R2
|
|
||||||
|
|
||||||
clone_folder: c:\gopath\src\github.com\urfave\cli
|
|
||||||
|
|
||||||
environment:
|
|
||||||
GOPATH: C:\gopath
|
|
||||||
GOVERSION: 1.6
|
|
||||||
PYTHON: C:\Python27-x64
|
|
||||||
PYTHON_VERSION: 2.7.x
|
|
||||||
PYTHON_ARCH: 64
|
|
||||||
|
|
||||||
install:
|
|
||||||
- set PATH=%GOPATH%\bin;C:\go\bin;%PATH%
|
|
||||||
- go version
|
|
||||||
- go env
|
|
||||||
- go get github.com/urfave/gfmrun/...
|
|
||||||
- go get -v -t ./...
|
|
||||||
|
|
||||||
build_script:
|
|
||||||
- python runtests vet
|
|
||||||
- python runtests test
|
|
||||||
- python runtests gfmrun
|
|
||||||
-44
@@ -1,44 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
// CommandCategories is a slice of *CommandCategory.
|
|
||||||
type CommandCategories []*CommandCategory
|
|
||||||
|
|
||||||
// CommandCategory is a category containing commands.
|
|
||||||
type CommandCategory struct {
|
|
||||||
Name string
|
|
||||||
Commands Commands
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CommandCategories) Less(i, j int) bool {
|
|
||||||
return c[i].Name < c[j].Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CommandCategories) Len() int {
|
|
||||||
return len(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CommandCategories) Swap(i, j int) {
|
|
||||||
c[i], c[j] = c[j], c[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddCommand adds a command to a category.
|
|
||||||
func (c CommandCategories) AddCommand(category string, command Command) CommandCategories {
|
|
||||||
for _, commandCategory := range c {
|
|
||||||
if commandCategory.Name == category {
|
|
||||||
commandCategory.Commands = append(commandCategory.Commands, command)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return append(c, &CommandCategory{Name: category, Commands: []Command{command}})
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
|
||||||
func (c *CommandCategory) VisibleCommands() []Command {
|
|
||||||
ret := []Command{}
|
|
||||||
for _, command := range c.Commands {
|
|
||||||
if !command.Hidden {
|
|
||||||
ret = append(ret, command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
// Package cli provides a minimal framework for creating and organizing command line
|
|
||||||
// Go applications. cli is designed to be easy to understand and write, the most simple
|
|
||||||
// cli application can be written as follows:
|
|
||||||
// func main() {
|
|
||||||
// cli.NewApp().Run(os.Args)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Of course this application does not do much, so let's make this an actual application:
|
|
||||||
// func main() {
|
|
||||||
// app := cli.NewApp()
|
|
||||||
// app.Name = "greet"
|
|
||||||
// app.Usage = "say a greeting"
|
|
||||||
// app.Action = func(c *cli.Context) error {
|
|
||||||
// println("Greetings")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// app.Run(os.Args)
|
|
||||||
// }
|
|
||||||
package cli
|
|
||||||
|
|
||||||
//go:generate python ./generate-flag-types cli -i flag-types.json -o flag_generated.go
|
|
||||||
-299
@@ -1,299 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Command is a subcommand for a cli.App.
|
|
||||||
type Command struct {
|
|
||||||
// The name of the command
|
|
||||||
Name string
|
|
||||||
// short name of the command. Typically one character (deprecated, use `Aliases`)
|
|
||||||
ShortName string
|
|
||||||
// A list of aliases for the command
|
|
||||||
Aliases []string
|
|
||||||
// A short description of the usage of this command
|
|
||||||
Usage string
|
|
||||||
// Custom text to show on USAGE section of help
|
|
||||||
UsageText string
|
|
||||||
// A longer explanation of how the command works
|
|
||||||
Description string
|
|
||||||
// A short description of the arguments of this command
|
|
||||||
ArgsUsage string
|
|
||||||
// The category the command is part of
|
|
||||||
Category string
|
|
||||||
// The function to call when checking for bash command completions
|
|
||||||
BashComplete BashCompleteFunc
|
|
||||||
// An action to execute before any sub-subcommands are run, but after the context is ready
|
|
||||||
// If a non-nil error is returned, no sub-subcommands are run
|
|
||||||
Before BeforeFunc
|
|
||||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
|
||||||
// It is run even if Action() panics
|
|
||||||
After AfterFunc
|
|
||||||
// The function to call when this command is invoked
|
|
||||||
Action interface{}
|
|
||||||
// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
|
|
||||||
// of deprecation period has passed, maybe?
|
|
||||||
|
|
||||||
// Execute this function if a usage error occurs.
|
|
||||||
OnUsageError OnUsageErrorFunc
|
|
||||||
// List of child commands
|
|
||||||
Subcommands Commands
|
|
||||||
// List of flags to parse
|
|
||||||
Flags []Flag
|
|
||||||
// Treat all flags as normal arguments if true
|
|
||||||
SkipFlagParsing bool
|
|
||||||
// Skip argument reordering which attempts to move flags before arguments,
|
|
||||||
// but only works if all flags appear after all arguments. This behavior was
|
|
||||||
// removed n version 2 since it only works under specific conditions so we
|
|
||||||
// backport here by exposing it as an option for compatibility.
|
|
||||||
SkipArgReorder bool
|
|
||||||
// Boolean to hide built-in help command
|
|
||||||
HideHelp bool
|
|
||||||
// Boolean to hide this command from help or completion
|
|
||||||
Hidden bool
|
|
||||||
|
|
||||||
// Full name of command for help, defaults to full command name, including parent commands.
|
|
||||||
HelpName string
|
|
||||||
commandNamePath []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandsByName []Command
|
|
||||||
|
|
||||||
func (c CommandsByName) Len() int {
|
|
||||||
return len(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CommandsByName) Less(i, j int) bool {
|
|
||||||
return c[i].Name < c[j].Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CommandsByName) Swap(i, j int) {
|
|
||||||
c[i], c[j] = c[j], c[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullName returns the full name of the command.
|
|
||||||
// For subcommands this ensures that parent commands are part of the command path
|
|
||||||
func (c Command) FullName() string {
|
|
||||||
if c.commandNamePath == nil {
|
|
||||||
return c.Name
|
|
||||||
}
|
|
||||||
return strings.Join(c.commandNamePath, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commands is a slice of Command
|
|
||||||
type Commands []Command
|
|
||||||
|
|
||||||
// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
|
||||||
func (c Command) Run(ctx *Context) (err error) {
|
|
||||||
if len(c.Subcommands) > 0 {
|
|
||||||
return c.startApp(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
|
|
||||||
// append help to flags
|
|
||||||
c.Flags = append(
|
|
||||||
c.Flags,
|
|
||||||
HelpFlag,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
set, err := flagSet(c.Name, c.Flags)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
set.SetOutput(ioutil.Discard)
|
|
||||||
|
|
||||||
if c.SkipFlagParsing {
|
|
||||||
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
|
|
||||||
} else if !c.SkipArgReorder {
|
|
||||||
firstFlagIndex := -1
|
|
||||||
terminatorIndex := -1
|
|
||||||
for index, arg := range ctx.Args() {
|
|
||||||
if arg == "--" {
|
|
||||||
terminatorIndex = index
|
|
||||||
break
|
|
||||||
} else if arg == "-" {
|
|
||||||
// Do nothing. A dash alone is not really a flag.
|
|
||||||
continue
|
|
||||||
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
|
|
||||||
firstFlagIndex = index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if firstFlagIndex > -1 {
|
|
||||||
args := ctx.Args()
|
|
||||||
regularArgs := make([]string, len(args[1:firstFlagIndex]))
|
|
||||||
copy(regularArgs, args[1:firstFlagIndex])
|
|
||||||
|
|
||||||
var flagArgs []string
|
|
||||||
if terminatorIndex > -1 {
|
|
||||||
flagArgs = args[firstFlagIndex:terminatorIndex]
|
|
||||||
regularArgs = append(regularArgs, args[terminatorIndex:]...)
|
|
||||||
} else {
|
|
||||||
flagArgs = args[firstFlagIndex:]
|
|
||||||
}
|
|
||||||
|
|
||||||
err = set.Parse(append(flagArgs, regularArgs...))
|
|
||||||
} else {
|
|
||||||
err = set.Parse(ctx.Args().Tail())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = set.Parse(ctx.Args().Tail())
|
|
||||||
}
|
|
||||||
|
|
||||||
nerr := normalizeFlags(c.Flags, set)
|
|
||||||
if nerr != nil {
|
|
||||||
fmt.Fprintln(ctx.App.Writer, nerr)
|
|
||||||
fmt.Fprintln(ctx.App.Writer)
|
|
||||||
ShowCommandHelp(ctx, c.Name)
|
|
||||||
return nerr
|
|
||||||
}
|
|
||||||
|
|
||||||
context := NewContext(ctx.App, set, ctx)
|
|
||||||
if checkCommandCompletions(context, c.Name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if c.OnUsageError != nil {
|
|
||||||
err := c.OnUsageError(ctx, err, false)
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage:", err.Error())
|
|
||||||
fmt.Fprintln(ctx.App.Writer)
|
|
||||||
ShowCommandHelp(ctx, c.Name)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if checkCommandHelp(context, c.Name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.After != nil {
|
|
||||||
defer func() {
|
|
||||||
afterErr := c.After(context)
|
|
||||||
if afterErr != nil {
|
|
||||||
HandleExitCoder(err)
|
|
||||||
if err != nil {
|
|
||||||
err = NewMultiError(err, afterErr)
|
|
||||||
} else {
|
|
||||||
err = afterErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Before != nil {
|
|
||||||
err = c.Before(context)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(ctx.App.Writer, err)
|
|
||||||
fmt.Fprintln(ctx.App.Writer)
|
|
||||||
ShowCommandHelp(ctx, c.Name)
|
|
||||||
HandleExitCoder(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Action == nil {
|
|
||||||
c.Action = helpSubcommand.Action
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Command = c
|
|
||||||
err = HandleAction(c.Action, context)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
HandleExitCoder(err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Names returns the names including short names and aliases.
|
|
||||||
func (c Command) Names() []string {
|
|
||||||
names := []string{c.Name}
|
|
||||||
|
|
||||||
if c.ShortName != "" {
|
|
||||||
names = append(names, c.ShortName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(names, c.Aliases...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasName returns true if Command.Name or Command.ShortName matches given name
|
|
||||||
func (c Command) HasName(name string) bool {
|
|
||||||
for _, n := range c.Names() {
|
|
||||||
if n == name {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Command) startApp(ctx *Context) error {
|
|
||||||
app := NewApp()
|
|
||||||
app.Metadata = ctx.App.Metadata
|
|
||||||
// set the name and usage
|
|
||||||
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
|
|
||||||
if c.HelpName == "" {
|
|
||||||
app.HelpName = c.HelpName
|
|
||||||
} else {
|
|
||||||
app.HelpName = app.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
app.Usage = c.Usage
|
|
||||||
app.Description = c.Description
|
|
||||||
app.ArgsUsage = c.ArgsUsage
|
|
||||||
|
|
||||||
// set CommandNotFound
|
|
||||||
app.CommandNotFound = ctx.App.CommandNotFound
|
|
||||||
|
|
||||||
// set the flags and commands
|
|
||||||
app.Commands = c.Subcommands
|
|
||||||
app.Flags = c.Flags
|
|
||||||
app.HideHelp = c.HideHelp
|
|
||||||
|
|
||||||
app.Version = ctx.App.Version
|
|
||||||
app.HideVersion = ctx.App.HideVersion
|
|
||||||
app.Compiled = ctx.App.Compiled
|
|
||||||
app.Author = ctx.App.Author
|
|
||||||
app.Email = ctx.App.Email
|
|
||||||
app.Writer = ctx.App.Writer
|
|
||||||
app.ErrWriter = ctx.App.ErrWriter
|
|
||||||
|
|
||||||
app.categories = CommandCategories{}
|
|
||||||
for _, command := range c.Subcommands {
|
|
||||||
app.categories = app.categories.AddCommand(command.Category, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(app.categories)
|
|
||||||
|
|
||||||
// bash completion
|
|
||||||
app.EnableBashCompletion = ctx.App.EnableBashCompletion
|
|
||||||
if c.BashComplete != nil {
|
|
||||||
app.BashComplete = c.BashComplete
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the actions
|
|
||||||
app.Before = c.Before
|
|
||||||
app.After = c.After
|
|
||||||
if c.Action != nil {
|
|
||||||
app.Action = c.Action
|
|
||||||
} else {
|
|
||||||
app.Action = helpSubcommand.Action
|
|
||||||
}
|
|
||||||
|
|
||||||
for index, cc := range app.Commands {
|
|
||||||
app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
|
|
||||||
}
|
|
||||||
|
|
||||||
return app.RunAsSubcommand(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
|
||||||
func (c Command) VisibleFlags() []Flag {
|
|
||||||
return visibleFlags(c.Flags)
|
|
||||||
}
|
|
||||||
-276
@@ -1,276 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"flag"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Context is a type that is passed through to
|
|
||||||
// each Handler action in a cli application. Context
|
|
||||||
// can be used to retrieve context-specific Args and
|
|
||||||
// parsed command-line options.
|
|
||||||
type Context struct {
|
|
||||||
App *App
|
|
||||||
Command Command
|
|
||||||
shellComplete bool
|
|
||||||
flagSet *flag.FlagSet
|
|
||||||
setFlags map[string]bool
|
|
||||||
parentContext *Context
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewContext creates a new context. For use in when invoking an App or Command action.
|
|
||||||
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
|
|
||||||
c := &Context{App: app, flagSet: set, parentContext: parentCtx}
|
|
||||||
|
|
||||||
if parentCtx != nil {
|
|
||||||
c.shellComplete = parentCtx.shellComplete
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumFlags returns the number of flags set
|
|
||||||
func (c *Context) NumFlags() int {
|
|
||||||
return c.flagSet.NFlag()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set sets a context flag to a value.
|
|
||||||
func (c *Context) Set(name, value string) error {
|
|
||||||
return c.flagSet.Set(name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalSet sets a context flag to a value on the global flagset
|
|
||||||
func (c *Context) GlobalSet(name, value string) error {
|
|
||||||
return globalContext(c).flagSet.Set(name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet determines if the flag was actually set
|
|
||||||
func (c *Context) IsSet(name string) bool {
|
|
||||||
if c.setFlags == nil {
|
|
||||||
c.setFlags = make(map[string]bool)
|
|
||||||
|
|
||||||
c.flagSet.Visit(func(f *flag.Flag) {
|
|
||||||
c.setFlags[f.Name] = true
|
|
||||||
})
|
|
||||||
|
|
||||||
c.flagSet.VisitAll(func(f *flag.Flag) {
|
|
||||||
if _, ok := c.setFlags[f.Name]; ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.setFlags[f.Name] = false
|
|
||||||
})
|
|
||||||
|
|
||||||
// XXX hack to support IsSet for flags with EnvVar
|
|
||||||
//
|
|
||||||
// There isn't an easy way to do this with the current implementation since
|
|
||||||
// whether a flag was set via an environment variable is very difficult to
|
|
||||||
// determine here. Instead, we intend to introduce a backwards incompatible
|
|
||||||
// change in version 2 to add `IsSet` to the Flag interface to push the
|
|
||||||
// responsibility closer to where the information required to determine
|
|
||||||
// whether a flag is set by non-standard means such as environment
|
|
||||||
// variables is avaliable.
|
|
||||||
//
|
|
||||||
// See https://github.com/urfave/cli/issues/294 for additional discussion
|
|
||||||
flags := c.Command.Flags
|
|
||||||
if c.Command.Name == "" { // cannot == Command{} since it contains slice types
|
|
||||||
if c.App != nil {
|
|
||||||
flags = c.App.Flags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, f := range flags {
|
|
||||||
eachName(f.GetName(), func(name string) {
|
|
||||||
if isSet, ok := c.setFlags[name]; isSet || !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val := reflect.ValueOf(f)
|
|
||||||
if val.Kind() == reflect.Ptr {
|
|
||||||
val = val.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
envVarValue := val.FieldByName("EnvVar")
|
|
||||||
if !envVarValue.IsValid() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(envVarValue.String(), func(envVar string) {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if _, ok := syscall.Getenv(envVar); ok {
|
|
||||||
c.setFlags[name] = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.setFlags[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalIsSet determines if the global flag was actually set
|
|
||||||
func (c *Context) GlobalIsSet(name string) bool {
|
|
||||||
ctx := c
|
|
||||||
if ctx.parentContext != nil {
|
|
||||||
ctx = ctx.parentContext
|
|
||||||
}
|
|
||||||
|
|
||||||
for ; ctx != nil; ctx = ctx.parentContext {
|
|
||||||
if ctx.IsSet(name) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlagNames returns a slice of flag names used in this context.
|
|
||||||
func (c *Context) FlagNames() (names []string) {
|
|
||||||
for _, flag := range c.Command.Flags {
|
|
||||||
name := strings.Split(flag.GetName(), ",")[0]
|
|
||||||
if name == "help" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalFlagNames returns a slice of global flag names used by the app.
|
|
||||||
func (c *Context) GlobalFlagNames() (names []string) {
|
|
||||||
for _, flag := range c.App.Flags {
|
|
||||||
name := strings.Split(flag.GetName(), ",")[0]
|
|
||||||
if name == "help" || name == "version" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parent returns the parent context, if any
|
|
||||||
func (c *Context) Parent() *Context {
|
|
||||||
return c.parentContext
|
|
||||||
}
|
|
||||||
|
|
||||||
// value returns the value of the flag coressponding to `name`
|
|
||||||
func (c *Context) value(name string) interface{} {
|
|
||||||
return c.flagSet.Lookup(name).Value.(flag.Getter).Get()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Args contains apps console arguments
|
|
||||||
type Args []string
|
|
||||||
|
|
||||||
// Args returns the command line arguments associated with the context.
|
|
||||||
func (c *Context) Args() Args {
|
|
||||||
args := Args(c.flagSet.Args())
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
// NArg returns the number of the command line arguments.
|
|
||||||
func (c *Context) NArg() int {
|
|
||||||
return len(c.Args())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the nth argument, or else a blank string
|
|
||||||
func (a Args) Get(n int) string {
|
|
||||||
if len(a) > n {
|
|
||||||
return a[n]
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// First returns the first argument, or else a blank string
|
|
||||||
func (a Args) First() string {
|
|
||||||
return a.Get(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tail returns the rest of the arguments (not the first one)
|
|
||||||
// or else an empty string slice
|
|
||||||
func (a Args) Tail() []string {
|
|
||||||
if len(a) >= 2 {
|
|
||||||
return []string(a)[1:]
|
|
||||||
}
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Present checks if there are any arguments present
|
|
||||||
func (a Args) Present() bool {
|
|
||||||
return len(a) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap swaps arguments at the given indexes
|
|
||||||
func (a Args) Swap(from, to int) error {
|
|
||||||
if from >= len(a) || to >= len(a) {
|
|
||||||
return errors.New("index out of range")
|
|
||||||
}
|
|
||||||
a[from], a[to] = a[to], a[from]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func globalContext(ctx *Context) *Context {
|
|
||||||
if ctx == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
if ctx.parentContext == nil {
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
ctx = ctx.parentContext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
|
|
||||||
if ctx.parentContext != nil {
|
|
||||||
ctx = ctx.parentContext
|
|
||||||
}
|
|
||||||
for ; ctx != nil; ctx = ctx.parentContext {
|
|
||||||
if f := ctx.flagSet.Lookup(name); f != nil {
|
|
||||||
return ctx.flagSet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
|
|
||||||
switch ff.Value.(type) {
|
|
||||||
case *StringSlice:
|
|
||||||
default:
|
|
||||||
set.Set(name, ff.Value.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
|
|
||||||
visited := make(map[string]bool)
|
|
||||||
set.Visit(func(f *flag.Flag) {
|
|
||||||
visited[f.Name] = true
|
|
||||||
})
|
|
||||||
for _, f := range flags {
|
|
||||||
parts := strings.Split(f.GetName(), ",")
|
|
||||||
if len(parts) == 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var ff *flag.Flag
|
|
||||||
for _, name := range parts {
|
|
||||||
name = strings.Trim(name, " ")
|
|
||||||
if visited[name] {
|
|
||||||
if ff != nil {
|
|
||||||
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
|
|
||||||
}
|
|
||||||
ff = set.Lookup(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ff == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, name := range parts {
|
|
||||||
name = strings.Trim(name, " ")
|
|
||||||
if !visited[name] {
|
|
||||||
copyFlag(name, ff, set)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-124
@@ -1,124 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OsExiter is the function used when the app exits. If not set defaults to os.Exit.
|
|
||||||
var OsExiter = os.Exit
|
|
||||||
|
|
||||||
// ErrWriter is used to write errors to the user. This can be anything
|
|
||||||
// implementing the io.Writer interface and defaults to os.Stderr.
|
|
||||||
var ErrWriter io.Writer = os.Stderr
|
|
||||||
|
|
||||||
// MultiError is an error that wraps multiple errors.
|
|
||||||
type MultiError struct {
|
|
||||||
Errors []error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMultiError creates a new MultiError. Pass in one or more errors.
|
|
||||||
func NewMultiError(err ...error) MultiError {
|
|
||||||
return MultiError{Errors: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error implements the error interface.
|
|
||||||
func (m MultiError) Error() string {
|
|
||||||
errs := make([]string, len(m.Errors))
|
|
||||||
for i, err := range m.Errors {
|
|
||||||
errs[i] = err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(errs, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
type ErrorFormatter interface {
|
|
||||||
Format(s fmt.State, verb rune)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExitCoder is the interface checked by `App` and `Command` for a custom exit
|
|
||||||
// code
|
|
||||||
type ExitCoder interface {
|
|
||||||
error
|
|
||||||
ExitCode() int
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExitError fulfills both the builtin `error` interface and `ExitCoder`
|
|
||||||
type ExitError struct {
|
|
||||||
exitCode int
|
|
||||||
message interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewExitError makes a new *ExitError
|
|
||||||
func NewExitError(message interface{}, exitCode int) *ExitError {
|
|
||||||
return &ExitError{
|
|
||||||
exitCode: exitCode,
|
|
||||||
message: message,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns the string message, fulfilling the interface required by
|
|
||||||
// `error`
|
|
||||||
func (ee *ExitError) Error() string {
|
|
||||||
return fmt.Sprintf("%v", ee.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExitCode returns the exit code, fulfilling the interface required by
|
|
||||||
// `ExitCoder`
|
|
||||||
func (ee *ExitError) ExitCode() int {
|
|
||||||
return ee.exitCode
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
|
|
||||||
// so prints the error to stderr (if it is non-empty) and calls OsExiter with the
|
|
||||||
// given exit code. If the given error is a MultiError, then this func is
|
|
||||||
// called on all members of the Errors slice and calls OsExiter with the last exit code.
|
|
||||||
func HandleExitCoder(err error) {
|
|
||||||
if err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if exitErr, ok := err.(ExitCoder); ok {
|
|
||||||
if err.Error() != "" {
|
|
||||||
if _, ok := exitErr.(ErrorFormatter); ok {
|
|
||||||
fmt.Fprintf(ErrWriter, "%+v\n", err)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintln(ErrWriter, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OsExiter(exitErr.ExitCode())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if multiErr, ok := err.(MultiError); ok {
|
|
||||||
code := handleMultiError(multiErr)
|
|
||||||
OsExiter(code)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err.Error() != "" {
|
|
||||||
if _, ok := err.(ErrorFormatter); ok {
|
|
||||||
fmt.Fprintf(ErrWriter, "%+v\n", err)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintln(ErrWriter, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OsExiter(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleMultiError(multiErr MultiError) int {
|
|
||||||
code := 1
|
|
||||||
for _, merr := range multiErr.Errors {
|
|
||||||
if multiErr2, ok := merr.(MultiError); ok {
|
|
||||||
code = handleMultiError(multiErr2)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintln(ErrWriter, merr)
|
|
||||||
if exitErr, ok := merr.(ExitCoder); ok {
|
|
||||||
code = exitErr.ExitCode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
-93
@@ -1,93 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"name": "Bool",
|
|
||||||
"type": "bool",
|
|
||||||
"value": false,
|
|
||||||
"context_default": "false",
|
|
||||||
"parser": "strconv.ParseBool(f.Value.String())"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "BoolT",
|
|
||||||
"type": "bool",
|
|
||||||
"value": false,
|
|
||||||
"doctail": " that is true by default",
|
|
||||||
"context_default": "false",
|
|
||||||
"parser": "strconv.ParseBool(f.Value.String())"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Duration",
|
|
||||||
"type": "time.Duration",
|
|
||||||
"doctail": " (see https://golang.org/pkg/time/#ParseDuration)",
|
|
||||||
"context_default": "0",
|
|
||||||
"parser": "time.ParseDuration(f.Value.String())"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Float64",
|
|
||||||
"type": "float64",
|
|
||||||
"context_default": "0",
|
|
||||||
"parser": "strconv.ParseFloat(f.Value.String(), 64)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Generic",
|
|
||||||
"type": "Generic",
|
|
||||||
"dest": false,
|
|
||||||
"context_default": "nil",
|
|
||||||
"context_type": "interface{}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Int64",
|
|
||||||
"type": "int64",
|
|
||||||
"context_default": "0",
|
|
||||||
"parser": "strconv.ParseInt(f.Value.String(), 0, 64)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Int",
|
|
||||||
"type": "int",
|
|
||||||
"context_default": "0",
|
|
||||||
"parser": "strconv.ParseInt(f.Value.String(), 0, 64)",
|
|
||||||
"parser_cast": "int(parsed)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "IntSlice",
|
|
||||||
"type": "*IntSlice",
|
|
||||||
"dest": false,
|
|
||||||
"context_default": "nil",
|
|
||||||
"context_type": "[]int",
|
|
||||||
"parser": "(f.Value.(*IntSlice)).Value(), error(nil)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Int64Slice",
|
|
||||||
"type": "*Int64Slice",
|
|
||||||
"dest": false,
|
|
||||||
"context_default": "nil",
|
|
||||||
"context_type": "[]int64",
|
|
||||||
"parser": "(f.Value.(*Int64Slice)).Value(), error(nil)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "String",
|
|
||||||
"type": "string",
|
|
||||||
"context_default": "\"\"",
|
|
||||||
"parser": "f.Value.String(), error(nil)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "StringSlice",
|
|
||||||
"type": "*StringSlice",
|
|
||||||
"dest": false,
|
|
||||||
"context_default": "nil",
|
|
||||||
"context_type": "[]string",
|
|
||||||
"parser": "(f.Value.(*StringSlice)).Value(), error(nil)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Uint64",
|
|
||||||
"type": "uint64",
|
|
||||||
"context_default": "0",
|
|
||||||
"parser": "strconv.ParseUint(f.Value.String(), 0, 64)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Uint",
|
|
||||||
"type": "uint",
|
|
||||||
"context_default": "0",
|
|
||||||
"parser": "strconv.ParseUint(f.Value.String(), 0, 64)",
|
|
||||||
"parser_cast": "uint(parsed)"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
-799
@@ -1,799 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultPlaceholder = "value"
|
|
||||||
|
|
||||||
// BashCompletionFlag enables bash-completion for all commands and subcommands
|
|
||||||
var BashCompletionFlag = BoolFlag{
|
|
||||||
Name: "generate-bash-completion",
|
|
||||||
Hidden: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// VersionFlag prints the version for the application
|
|
||||||
var VersionFlag = BoolFlag{
|
|
||||||
Name: "version, v",
|
|
||||||
Usage: "print the version",
|
|
||||||
}
|
|
||||||
|
|
||||||
// HelpFlag prints the help for all commands and subcommands
|
|
||||||
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
|
|
||||||
// unless HideHelp is set to true)
|
|
||||||
var HelpFlag = BoolFlag{
|
|
||||||
Name: "help, h",
|
|
||||||
Usage: "show help",
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlagStringer converts a flag definition to a string. This is used by help
|
|
||||||
// to display a flag.
|
|
||||||
var FlagStringer FlagStringFunc = stringifyFlag
|
|
||||||
|
|
||||||
// FlagsByName is a slice of Flag.
|
|
||||||
type FlagsByName []Flag
|
|
||||||
|
|
||||||
func (f FlagsByName) Len() int {
|
|
||||||
return len(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f FlagsByName) Less(i, j int) bool {
|
|
||||||
return f[i].GetName() < f[j].GetName()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f FlagsByName) Swap(i, j int) {
|
|
||||||
f[i], f[j] = f[j], f[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flag is a common interface related to parsing flags in cli.
|
|
||||||
// For more advanced flag parsing techniques, it is recommended that
|
|
||||||
// this interface be implemented.
|
|
||||||
type Flag interface {
|
|
||||||
fmt.Stringer
|
|
||||||
// Apply Flag settings to the given flag set
|
|
||||||
Apply(*flag.FlagSet)
|
|
||||||
GetName() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// errorableFlag is an interface that allows us to return errors during apply
|
|
||||||
// it allows flags defined in this library to return errors in a fashion backwards compatible
|
|
||||||
// TODO remove in v2 and modify the existing Flag interface to return errors
|
|
||||||
type errorableFlag interface {
|
|
||||||
Flag
|
|
||||||
|
|
||||||
ApplyWithError(*flag.FlagSet) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
|
|
||||||
set := flag.NewFlagSet(name, flag.ContinueOnError)
|
|
||||||
|
|
||||||
for _, f := range flags {
|
|
||||||
//TODO remove in v2 when errorableFlag is removed
|
|
||||||
if ef, ok := f.(errorableFlag); ok {
|
|
||||||
if err := ef.ApplyWithError(set); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
f.Apply(set)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return set, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func eachName(longName string, fn func(string)) {
|
|
||||||
parts := strings.Split(longName, ",")
|
|
||||||
for _, name := range parts {
|
|
||||||
name = strings.Trim(name, " ")
|
|
||||||
fn(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic is a generic parseable type identified by a specific flag
|
|
||||||
type Generic interface {
|
|
||||||
Set(value string) error
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply takes the flagset and calls Set on the generic flag with the value
|
|
||||||
// provided by the user for parsing by the flag
|
|
||||||
// Ignores parsing errors
|
|
||||||
func (f GenericFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError takes the flagset and calls Set on the generic flag with the value
|
|
||||||
// provided by the user for parsing by the flag
|
|
||||||
func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
val := f.Value
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
if err := val.Set(envVal); err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
set.Var(f.Value, name, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter
|
|
||||||
type StringSlice []string
|
|
||||||
|
|
||||||
// Set appends the string value to the list of values
|
|
||||||
func (f *StringSlice) Set(value string) error {
|
|
||||||
*f = append(*f, value)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value (for usage defaults)
|
|
||||||
func (f *StringSlice) String() string {
|
|
||||||
return fmt.Sprintf("%s", *f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the slice of strings set by this flag
|
|
||||||
func (f *StringSlice) Value() []string {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the slice of strings set by this flag
|
|
||||||
func (f *StringSlice) Get() interface{} {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
newVal := &StringSlice{}
|
|
||||||
for _, s := range strings.Split(envVal, ",") {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if err := newVal.Set(s); err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.Value = newVal
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Value == nil {
|
|
||||||
f.Value = &StringSlice{}
|
|
||||||
}
|
|
||||||
set.Var(f.Value, name, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter
|
|
||||||
type IntSlice []int
|
|
||||||
|
|
||||||
// Set parses the value into an integer and appends it to the list of values
|
|
||||||
func (f *IntSlice) Set(value string) error {
|
|
||||||
tmp, err := strconv.Atoi(value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*f = append(*f, tmp)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value (for usage defaults)
|
|
||||||
func (f *IntSlice) String() string {
|
|
||||||
return fmt.Sprintf("%#v", *f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the slice of ints set by this flag
|
|
||||||
func (f *IntSlice) Value() []int {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the slice of ints set by this flag
|
|
||||||
func (f *IntSlice) Get() interface{} {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
newVal := &IntSlice{}
|
|
||||||
for _, s := range strings.Split(envVal, ",") {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if err := newVal.Set(s); err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.Value = newVal
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Value == nil {
|
|
||||||
f.Value = &IntSlice{}
|
|
||||||
}
|
|
||||||
set.Var(f.Value, name, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter
|
|
||||||
type Int64Slice []int64
|
|
||||||
|
|
||||||
// Set parses the value into an integer and appends it to the list of values
|
|
||||||
func (f *Int64Slice) Set(value string) error {
|
|
||||||
tmp, err := strconv.ParseInt(value, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*f = append(*f, tmp)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value (for usage defaults)
|
|
||||||
func (f *Int64Slice) String() string {
|
|
||||||
return fmt.Sprintf("%#v", *f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the slice of ints set by this flag
|
|
||||||
func (f *Int64Slice) Value() []int64 {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the slice of ints set by this flag
|
|
||||||
func (f *Int64Slice) Get() interface{} {
|
|
||||||
return *f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
newVal := &Int64Slice{}
|
|
||||||
for _, s := range strings.Split(envVal, ",") {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if err := newVal.Set(s); err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.Value = newVal
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Value == nil {
|
|
||||||
f.Value = &Int64Slice{}
|
|
||||||
}
|
|
||||||
set.Var(f.Value, name, f.Usage)
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f BoolFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
val := false
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
if envVal == "" {
|
|
||||||
val = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
envValBool, err := strconv.ParseBool(envVal)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
val = envValBool
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.BoolVar(f.Destination, name, val, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Bool(name, val, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f BoolTFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
val := true
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
if envVal == "" {
|
|
||||||
val = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
envValBool, err := strconv.ParseBool(envVal)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
val = envValBool
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.BoolVar(f.Destination, name, val, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Bool(name, val, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f StringFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f StringFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
f.Value = envVal
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.StringVar(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.String(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f IntFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f IntFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
f.Value = int(envValInt)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.IntVar(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Int(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f Int64Flag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Value = envValInt
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.Int64Var(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Int64(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f UintFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f UintFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValInt, err := strconv.ParseUint(envVal, 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Value = uint(envValInt)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.UintVar(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Uint(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f Uint64Flag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValInt, err := strconv.ParseUint(envVal, 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Value = uint64(envValInt)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.Uint64Var(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Uint64(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f DurationFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValDuration, err := time.ParseDuration(envVal)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Value = envValDuration
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.DurationVar(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Duration(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
|
||||||
// Ignores errors
|
|
||||||
func (f Float64Flag) Apply(set *flag.FlagSet) {
|
|
||||||
f.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||||
func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
if f.EnvVar != "" {
|
|
||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
|
||||||
envValFloat, err := strconv.ParseFloat(envVal, 10)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Value = float64(envValFloat)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
if f.Destination != nil {
|
|
||||||
set.Float64Var(f.Destination, name, f.Value, f.Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set.Float64(name, f.Value, f.Usage)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func visibleFlags(fl []Flag) []Flag {
|
|
||||||
visible := []Flag{}
|
|
||||||
for _, flag := range fl {
|
|
||||||
if !flagValue(flag).FieldByName("Hidden").Bool() {
|
|
||||||
visible = append(visible, flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return visible
|
|
||||||
}
|
|
||||||
|
|
||||||
func prefixFor(name string) (prefix string) {
|
|
||||||
if len(name) == 1 {
|
|
||||||
prefix = "-"
|
|
||||||
} else {
|
|
||||||
prefix = "--"
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the placeholder, if any, and the unquoted usage string.
|
|
||||||
func unquoteUsage(usage string) (string, string) {
|
|
||||||
for i := 0; i < len(usage); i++ {
|
|
||||||
if usage[i] == '`' {
|
|
||||||
for j := i + 1; j < len(usage); j++ {
|
|
||||||
if usage[j] == '`' {
|
|
||||||
name := usage[i+1 : j]
|
|
||||||
usage = usage[:i] + name + usage[j+1:]
|
|
||||||
return name, usage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", usage
|
|
||||||
}
|
|
||||||
|
|
||||||
func prefixedNames(fullName, placeholder string) string {
|
|
||||||
var prefixed string
|
|
||||||
parts := strings.Split(fullName, ",")
|
|
||||||
for i, name := range parts {
|
|
||||||
name = strings.Trim(name, " ")
|
|
||||||
prefixed += prefixFor(name) + name
|
|
||||||
if placeholder != "" {
|
|
||||||
prefixed += " " + placeholder
|
|
||||||
}
|
|
||||||
if i < len(parts)-1 {
|
|
||||||
prefixed += ", "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return prefixed
|
|
||||||
}
|
|
||||||
|
|
||||||
func withEnvHint(envVar, str string) string {
|
|
||||||
envText := ""
|
|
||||||
if envVar != "" {
|
|
||||||
prefix := "$"
|
|
||||||
suffix := ""
|
|
||||||
sep := ", $"
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
prefix = "%"
|
|
||||||
suffix = "%"
|
|
||||||
sep = "%, %"
|
|
||||||
}
|
|
||||||
envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix)
|
|
||||||
}
|
|
||||||
return str + envText
|
|
||||||
}
|
|
||||||
|
|
||||||
func flagValue(f Flag) reflect.Value {
|
|
||||||
fv := reflect.ValueOf(f)
|
|
||||||
for fv.Kind() == reflect.Ptr {
|
|
||||||
fv = reflect.Indirect(fv)
|
|
||||||
}
|
|
||||||
return fv
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifyFlag(f Flag) string {
|
|
||||||
fv := flagValue(f)
|
|
||||||
|
|
||||||
switch f.(type) {
|
|
||||||
case IntSliceFlag:
|
|
||||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
|
||||||
stringifyIntSliceFlag(f.(IntSliceFlag)))
|
|
||||||
case Int64SliceFlag:
|
|
||||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
|
||||||
stringifyInt64SliceFlag(f.(Int64SliceFlag)))
|
|
||||||
case StringSliceFlag:
|
|
||||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
|
||||||
stringifyStringSliceFlag(f.(StringSliceFlag)))
|
|
||||||
}
|
|
||||||
|
|
||||||
placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String())
|
|
||||||
|
|
||||||
needsPlaceholder := false
|
|
||||||
defaultValueString := ""
|
|
||||||
val := fv.FieldByName("Value")
|
|
||||||
|
|
||||||
if val.IsValid() {
|
|
||||||
needsPlaceholder = true
|
|
||||||
defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface())
|
|
||||||
|
|
||||||
if val.Kind() == reflect.String && val.String() != "" {
|
|
||||||
defaultValueString = fmt.Sprintf(" (default: %q)", val.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if defaultValueString == " (default: )" {
|
|
||||||
defaultValueString = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if needsPlaceholder && placeholder == "" {
|
|
||||||
placeholder = defaultPlaceholder
|
|
||||||
}
|
|
||||||
|
|
||||||
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString))
|
|
||||||
|
|
||||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
|
||||||
fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault))
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifyIntSliceFlag(f IntSliceFlag) string {
|
|
||||||
defaultVals := []string{}
|
|
||||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
|
||||||
for _, i := range f.Value.Value() {
|
|
||||||
defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifyInt64SliceFlag(f Int64SliceFlag) string {
|
|
||||||
defaultVals := []string{}
|
|
||||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
|
||||||
for _, i := range f.Value.Value() {
|
|
||||||
defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifyStringSliceFlag(f StringSliceFlag) string {
|
|
||||||
defaultVals := []string{}
|
|
||||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
|
||||||
for _, s := range f.Value.Value() {
|
|
||||||
if len(s) > 0 {
|
|
||||||
defaultVals = append(defaultVals, fmt.Sprintf("%q", s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifySliceFlag(usage, name string, defaultVals []string) string {
|
|
||||||
placeholder, usage := unquoteUsage(usage)
|
|
||||||
if placeholder == "" {
|
|
||||||
placeholder = defaultPlaceholder
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultVal := ""
|
|
||||||
if len(defaultVals) > 0 {
|
|
||||||
defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal))
|
|
||||||
return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault)
|
|
||||||
}
|
|
||||||
-627
@@ -1,627 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WARNING: This file is generated!
|
|
||||||
|
|
||||||
// BoolFlag is a flag with type bool
|
|
||||||
type BoolFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Destination *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f BoolFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f BoolFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bool looks up the value of a local BoolFlag, returns
|
|
||||||
// false if not found
|
|
||||||
func (c *Context) Bool(name string) bool {
|
|
||||||
return lookupBool(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalBool looks up the value of a global BoolFlag, returns
|
|
||||||
// false if not found
|
|
||||||
func (c *Context) GlobalBool(name string) bool {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupBool(name, fs)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupBool(name string, set *flag.FlagSet) bool {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseBool(f.Value.String())
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolTFlag is a flag with type bool that is true by default
|
|
||||||
type BoolTFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Destination *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f BoolTFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f BoolTFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolT looks up the value of a local BoolTFlag, returns
|
|
||||||
// false if not found
|
|
||||||
func (c *Context) BoolT(name string) bool {
|
|
||||||
return lookupBoolT(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalBoolT looks up the value of a global BoolTFlag, returns
|
|
||||||
// false if not found
|
|
||||||
func (c *Context) GlobalBoolT(name string) bool {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupBoolT(name, fs)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupBoolT(name string, set *flag.FlagSet) bool {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseBool(f.Value.String())
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
|
|
||||||
type DurationFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value time.Duration
|
|
||||||
Destination *time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f DurationFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f DurationFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duration looks up the value of a local DurationFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Duration(name string) time.Duration {
|
|
||||||
return lookupDuration(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalDuration looks up the value of a global DurationFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalDuration(name string) time.Duration {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupDuration(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := time.ParseDuration(f.Value.String())
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64Flag is a flag with type float64
|
|
||||||
type Float64Flag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value float64
|
|
||||||
Destination *float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f Float64Flag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f Float64Flag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64 looks up the value of a local Float64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Float64(name string) float64 {
|
|
||||||
return lookupFloat64(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalFloat64 looks up the value of a global Float64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalFloat64(name string) float64 {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupFloat64(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupFloat64(name string, set *flag.FlagSet) float64 {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseFloat(f.Value.String(), 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenericFlag is a flag with type Generic
|
|
||||||
type GenericFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value Generic
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f GenericFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f GenericFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic looks up the value of a local GenericFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) Generic(name string) interface{} {
|
|
||||||
return lookupGeneric(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalGeneric looks up the value of a global GenericFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) GlobalGeneric(name string) interface{} {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupGeneric(name, fs)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := f.Value, error(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64Flag is a flag with type int64
|
|
||||||
type Int64Flag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value int64
|
|
||||||
Destination *int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f Int64Flag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f Int64Flag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64 looks up the value of a local Int64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Int64(name string) int64 {
|
|
||||||
return lookupInt64(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalInt64 looks up the value of a global Int64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalInt64(name string) int64 {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupInt64(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupInt64(name string, set *flag.FlagSet) int64 {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntFlag is a flag with type int
|
|
||||||
type IntFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value int
|
|
||||||
Destination *int
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f IntFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f IntFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int looks up the value of a local IntFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Int(name string) int {
|
|
||||||
return lookupInt(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalInt looks up the value of a global IntFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalInt(name string) int {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupInt(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupInt(name string, set *flag.FlagSet) int {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(parsed)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntSliceFlag is a flag with type *IntSlice
|
|
||||||
type IntSliceFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value *IntSlice
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f IntSliceFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f IntSliceFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntSlice looks up the value of a local IntSliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) IntSlice(name string) []int {
|
|
||||||
return lookupIntSlice(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalIntSlice looks up the value of a global IntSliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) GlobalIntSlice(name string) []int {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupIntSlice(name, fs)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupIntSlice(name string, set *flag.FlagSet) []int {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := (f.Value.(*IntSlice)).Value(), error(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64SliceFlag is a flag with type *Int64Slice
|
|
||||||
type Int64SliceFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value *Int64Slice
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f Int64SliceFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f Int64SliceFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64Slice looks up the value of a local Int64SliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) Int64Slice(name string) []int64 {
|
|
||||||
return lookupInt64Slice(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) GlobalInt64Slice(name string) []int64 {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupInt64Slice(name, fs)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringFlag is a flag with type string
|
|
||||||
type StringFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value string
|
|
||||||
Destination *string
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f StringFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f StringFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// String looks up the value of a local StringFlag, returns
|
|
||||||
// "" if not found
|
|
||||||
func (c *Context) String(name string) string {
|
|
||||||
return lookupString(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalString looks up the value of a global StringFlag, returns
|
|
||||||
// "" if not found
|
|
||||||
func (c *Context) GlobalString(name string) string {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupString(name, fs)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupString(name string, set *flag.FlagSet) string {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := f.Value.String(), error(nil)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringSliceFlag is a flag with type *StringSlice
|
|
||||||
type StringSliceFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value *StringSlice
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f StringSliceFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f StringSliceFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringSlice looks up the value of a local StringSliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) StringSlice(name string) []string {
|
|
||||||
return lookupStringSlice(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalStringSlice looks up the value of a global StringSliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) GlobalStringSlice(name string) []string {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupStringSlice(name, fs)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupStringSlice(name string, set *flag.FlagSet) []string {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := (f.Value.(*StringSlice)).Value(), error(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint64Flag is a flag with type uint64
|
|
||||||
type Uint64Flag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value uint64
|
|
||||||
Destination *uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f Uint64Flag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f Uint64Flag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint64 looks up the value of a local Uint64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Uint64(name string) uint64 {
|
|
||||||
return lookupUint64(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalUint64 looks up the value of a global Uint64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalUint64(name string) uint64 {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupUint64(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupUint64(name string, set *flag.FlagSet) uint64 {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// UintFlag is a flag with type uint
|
|
||||||
type UintFlag struct {
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
Value uint
|
|
||||||
Destination *uint
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f UintFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f UintFlag) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint looks up the value of a local UintFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Uint(name string) uint {
|
|
||||||
return lookupUint(name, c.flagSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalUint looks up the value of a global UintFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalUint(name string) uint {
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
|
||||||
return lookupUint(name, fs)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupUint(name string, set *flag.FlagSet) uint {
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {
|
|
||||||
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return uint(parsed)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
// BashCompleteFunc is an action to execute when the bash-completion flag is set
|
|
||||||
type BashCompleteFunc func(*Context)
|
|
||||||
|
|
||||||
// BeforeFunc is an action to execute before any subcommands are run, but after
|
|
||||||
// the context is ready if a non-nil error is returned, no subcommands are run
|
|
||||||
type BeforeFunc func(*Context) error
|
|
||||||
|
|
||||||
// AfterFunc is an action to execute after any subcommands are run, but after the
|
|
||||||
// subcommand has finished it is run even if Action() panics
|
|
||||||
type AfterFunc func(*Context) error
|
|
||||||
|
|
||||||
// ActionFunc is the action to execute when no subcommands are specified
|
|
||||||
type ActionFunc func(*Context) error
|
|
||||||
|
|
||||||
// CommandNotFoundFunc is executed if the proper command cannot be found
|
|
||||||
type CommandNotFoundFunc func(*Context, string)
|
|
||||||
|
|
||||||
// OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying
|
|
||||||
// customized usage error messages. This function is able to replace the
|
|
||||||
// original error messages. If this function is not set, the "Incorrect usage"
|
|
||||||
// is displayed and the execution is interrupted.
|
|
||||||
type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error
|
|
||||||
|
|
||||||
// FlagStringFunc is used by the help generation to display a flag, which is
|
|
||||||
// expected to be a single line.
|
|
||||||
type FlagStringFunc func(Flag) string
|
|
||||||
-255
@@ -1,255 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
"""
|
|
||||||
The flag types that ship with the cli library have many things in common, and
|
|
||||||
so we can take advantage of the `go generate` command to create much of the
|
|
||||||
source code from a list of definitions. These definitions attempt to cover
|
|
||||||
the parts that vary between flag types, and should evolve as needed.
|
|
||||||
|
|
||||||
An example of the minimum definition needed is:
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "SomeType",
|
|
||||||
"type": "sometype",
|
|
||||||
"context_default": "nil"
|
|
||||||
}
|
|
||||||
|
|
||||||
In this example, the code generated for the `cli` package will include a type
|
|
||||||
named `SomeTypeFlag` that is expected to wrap a value of type `sometype`.
|
|
||||||
Fetching values by name via `*cli.Context` will default to a value of `nil`.
|
|
||||||
|
|
||||||
A more complete, albeit somewhat redundant, example showing all available
|
|
||||||
definition keys is:
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "VeryMuchType",
|
|
||||||
"type": "*VeryMuchType",
|
|
||||||
"value": true,
|
|
||||||
"dest": false,
|
|
||||||
"doctail": " which really only wraps a []float64, oh well!",
|
|
||||||
"context_type": "[]float64",
|
|
||||||
"context_default": "nil",
|
|
||||||
"parser": "parseVeryMuchType(f.Value.String())",
|
|
||||||
"parser_cast": "[]float64(parsed)"
|
|
||||||
}
|
|
||||||
|
|
||||||
The meaning of each field is as follows:
|
|
||||||
|
|
||||||
name (string) - The type "name", which will be suffixed with
|
|
||||||
`Flag` when generating the type definition
|
|
||||||
for `cli` and the wrapper type for `altsrc`
|
|
||||||
type (string) - The type that the generated `Flag` type for `cli`
|
|
||||||
is expected to "contain" as its `.Value` member
|
|
||||||
value (bool) - Should the generated `cli` type have a `Value`
|
|
||||||
member?
|
|
||||||
dest (bool) - Should the generated `cli` type support a
|
|
||||||
destination pointer?
|
|
||||||
doctail (string) - Additional docs for the `cli` flag type comment
|
|
||||||
context_type (string) - The literal type used in the `*cli.Context`
|
|
||||||
reader func signature
|
|
||||||
context_default (string) - The literal value used as the default by the
|
|
||||||
`*cli.Context` reader funcs when no value is
|
|
||||||
present
|
|
||||||
parser (string) - Literal code used to parse the flag `f`,
|
|
||||||
expected to have a return signature of
|
|
||||||
(value, error)
|
|
||||||
parser_cast (string) - Literal code used to cast the `parsed` value
|
|
||||||
returned from the `parser` code
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import print_function, unicode_literals
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import textwrap
|
|
||||||
|
|
||||||
|
|
||||||
class _FancyFormatter(argparse.ArgumentDefaultsHelpFormatter,
|
|
||||||
argparse.RawDescriptionHelpFormatter):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def main(sysargs=sys.argv[:]):
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description='Generate flag type code!',
|
|
||||||
formatter_class=_FancyFormatter)
|
|
||||||
parser.add_argument(
|
|
||||||
'package',
|
|
||||||
type=str, default='cli', choices=_WRITEFUNCS.keys(),
|
|
||||||
help='Package for which flag types will be generated'
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-i', '--in-json',
|
|
||||||
type=argparse.FileType('r'),
|
|
||||||
default=sys.stdin,
|
|
||||||
help='Input JSON file which defines each type to be generated'
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-o', '--out-go',
|
|
||||||
type=argparse.FileType('w'),
|
|
||||||
default=sys.stdout,
|
|
||||||
help='Output file/stream to which generated source will be written'
|
|
||||||
)
|
|
||||||
parser.epilog = __doc__
|
|
||||||
|
|
||||||
args = parser.parse_args(sysargs[1:])
|
|
||||||
_generate_flag_types(_WRITEFUNCS[args.package], args.out_go, args.in_json)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def _generate_flag_types(writefunc, output_go, input_json):
|
|
||||||
types = json.load(input_json)
|
|
||||||
|
|
||||||
tmp = tempfile.NamedTemporaryFile(suffix='.go', delete=False)
|
|
||||||
writefunc(tmp, types)
|
|
||||||
tmp.close()
|
|
||||||
|
|
||||||
new_content = subprocess.check_output(
|
|
||||||
['goimports', tmp.name]
|
|
||||||
).decode('utf-8')
|
|
||||||
|
|
||||||
print(new_content, file=output_go, end='')
|
|
||||||
output_go.flush()
|
|
||||||
os.remove(tmp.name)
|
|
||||||
|
|
||||||
|
|
||||||
def _set_typedef_defaults(typedef):
|
|
||||||
typedef.setdefault('doctail', '')
|
|
||||||
typedef.setdefault('context_type', typedef['type'])
|
|
||||||
typedef.setdefault('dest', True)
|
|
||||||
typedef.setdefault('value', True)
|
|
||||||
typedef.setdefault('parser', 'f.Value, error(nil)')
|
|
||||||
typedef.setdefault('parser_cast', 'parsed')
|
|
||||||
|
|
||||||
|
|
||||||
def _write_cli_flag_types(outfile, types):
|
|
||||||
_fwrite(outfile, """\
|
|
||||||
package cli
|
|
||||||
|
|
||||||
// WARNING: This file is generated!
|
|
||||||
|
|
||||||
""")
|
|
||||||
|
|
||||||
for typedef in types:
|
|
||||||
_set_typedef_defaults(typedef)
|
|
||||||
|
|
||||||
_fwrite(outfile, """\
|
|
||||||
// {name}Flag is a flag with type {type}{doctail}
|
|
||||||
type {name}Flag struct {{
|
|
||||||
Name string
|
|
||||||
Usage string
|
|
||||||
EnvVar string
|
|
||||||
Hidden bool
|
|
||||||
""".format(**typedef))
|
|
||||||
|
|
||||||
if typedef['value']:
|
|
||||||
_fwrite(outfile, """\
|
|
||||||
Value {type}
|
|
||||||
""".format(**typedef))
|
|
||||||
|
|
||||||
if typedef['dest']:
|
|
||||||
_fwrite(outfile, """\
|
|
||||||
Destination *{type}
|
|
||||||
""".format(**typedef))
|
|
||||||
|
|
||||||
_fwrite(outfile, "\n}\n\n")
|
|
||||||
|
|
||||||
_fwrite(outfile, """\
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f {name}Flag) String() string {{
|
|
||||||
return FlagStringer(f)
|
|
||||||
}}
|
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f {name}Flag) GetName() string {{
|
|
||||||
return f.Name
|
|
||||||
}}
|
|
||||||
|
|
||||||
// {name} looks up the value of a local {name}Flag, returns
|
|
||||||
// {context_default} if not found
|
|
||||||
func (c *Context) {name}(name string) {context_type} {{
|
|
||||||
return lookup{name}(name, c.flagSet)
|
|
||||||
}}
|
|
||||||
|
|
||||||
// Global{name} looks up the value of a global {name}Flag, returns
|
|
||||||
// {context_default} if not found
|
|
||||||
func (c *Context) Global{name}(name string) {context_type} {{
|
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {{
|
|
||||||
return lookup{name}(name, fs)
|
|
||||||
}}
|
|
||||||
return {context_default}
|
|
||||||
}}
|
|
||||||
|
|
||||||
func lookup{name}(name string, set *flag.FlagSet) {context_type} {{
|
|
||||||
f := set.Lookup(name)
|
|
||||||
if f != nil {{
|
|
||||||
parsed, err := {parser}
|
|
||||||
if err != nil {{
|
|
||||||
return {context_default}
|
|
||||||
}}
|
|
||||||
return {parser_cast}
|
|
||||||
}}
|
|
||||||
return {context_default}
|
|
||||||
}}
|
|
||||||
""".format(**typedef))
|
|
||||||
|
|
||||||
|
|
||||||
def _write_altsrc_flag_types(outfile, types):
|
|
||||||
_fwrite(outfile, """\
|
|
||||||
package altsrc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gopkg.in/urfave/cli.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WARNING: This file is generated!
|
|
||||||
|
|
||||||
""")
|
|
||||||
|
|
||||||
for typedef in types:
|
|
||||||
_set_typedef_defaults(typedef)
|
|
||||||
|
|
||||||
_fwrite(outfile, """\
|
|
||||||
// {name}Flag is the flag type that wraps cli.{name}Flag to allow
|
|
||||||
// for other values to be specified
|
|
||||||
type {name}Flag struct {{
|
|
||||||
cli.{name}Flag
|
|
||||||
set *flag.FlagSet
|
|
||||||
}}
|
|
||||||
|
|
||||||
// New{name}Flag creates a new {name}Flag
|
|
||||||
func New{name}Flag(fl cli.{name}Flag) *{name}Flag {{
|
|
||||||
return &{name}Flag{{{name}Flag: fl, set: nil}}
|
|
||||||
}}
|
|
||||||
|
|
||||||
// Apply saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped {name}Flag.Apply
|
|
||||||
func (f *{name}Flag) Apply(set *flag.FlagSet) {{
|
|
||||||
f.set = set
|
|
||||||
f.{name}Flag.Apply(set)
|
|
||||||
}}
|
|
||||||
|
|
||||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped {name}Flag.ApplyWithError
|
|
||||||
func (f *{name}Flag) ApplyWithError(set *flag.FlagSet) error {{
|
|
||||||
f.set = set
|
|
||||||
return f.{name}Flag.ApplyWithError(set)
|
|
||||||
}}
|
|
||||||
""".format(**typedef))
|
|
||||||
|
|
||||||
|
|
||||||
def _fwrite(outfile, text):
|
|
||||||
print(textwrap.dedent(text), end='', file=outfile)
|
|
||||||
|
|
||||||
|
|
||||||
_WRITEFUNCS = {
|
|
||||||
'cli': _write_cli_flag_types,
|
|
||||||
'altsrc': _write_altsrc_flag_types
|
|
||||||
}
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main())
|
|
||||||
-294
@@ -1,294 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AppHelpTemplate is the text template for the Default help topic.
|
|
||||||
// cli.go uses text/template to render templates. You can
|
|
||||||
// render custom help text by setting this variable.
|
|
||||||
var AppHelpTemplate = `NAME:
|
|
||||||
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
|
|
||||||
|
|
||||||
USAGE:
|
|
||||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
|
|
||||||
|
|
||||||
VERSION:
|
|
||||||
{{.Version}}{{end}}{{end}}{{if .Description}}
|
|
||||||
|
|
||||||
DESCRIPTION:
|
|
||||||
{{.Description}}{{end}}{{if len .Authors}}
|
|
||||||
|
|
||||||
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
|
|
||||||
{{range $index, $author := .Authors}}{{if $index}}
|
|
||||||
{{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
|
|
||||||
|
|
||||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
|
||||||
{{.Name}}:{{end}}{{range .VisibleCommands}}
|
|
||||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
|
|
||||||
|
|
||||||
GLOBAL OPTIONS:
|
|
||||||
{{range $index, $option := .VisibleFlags}}{{if $index}}
|
|
||||||
{{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
|
|
||||||
|
|
||||||
COPYRIGHT:
|
|
||||||
{{.Copyright}}{{end}}
|
|
||||||
`
|
|
||||||
|
|
||||||
// CommandHelpTemplate is the text template for the command help topic.
|
|
||||||
// cli.go uses text/template to render templates. You can
|
|
||||||
// render custom help text by setting this variable.
|
|
||||||
var CommandHelpTemplate = `NAME:
|
|
||||||
{{.HelpName}} - {{.Usage}}
|
|
||||||
|
|
||||||
USAGE:
|
|
||||||
{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}}
|
|
||||||
|
|
||||||
CATEGORY:
|
|
||||||
{{.Category}}{{end}}{{if .Description}}
|
|
||||||
|
|
||||||
DESCRIPTION:
|
|
||||||
{{.Description}}{{end}}{{if .VisibleFlags}}
|
|
||||||
|
|
||||||
OPTIONS:
|
|
||||||
{{range .VisibleFlags}}{{.}}
|
|
||||||
{{end}}{{end}}
|
|
||||||
`
|
|
||||||
|
|
||||||
// SubcommandHelpTemplate is the text template for the subcommand help topic.
|
|
||||||
// cli.go uses text/template to render templates. You can
|
|
||||||
// render custom help text by setting this variable.
|
|
||||||
var SubcommandHelpTemplate = `NAME:
|
|
||||||
{{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}}
|
|
||||||
|
|
||||||
USAGE:
|
|
||||||
{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
|
||||||
|
|
||||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
|
||||||
{{.Name}}:{{end}}{{range .VisibleCommands}}
|
|
||||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}
|
|
||||||
{{end}}{{if .VisibleFlags}}
|
|
||||||
OPTIONS:
|
|
||||||
{{range .VisibleFlags}}{{.}}
|
|
||||||
{{end}}{{end}}
|
|
||||||
`
|
|
||||||
|
|
||||||
var helpCommand = Command{
|
|
||||||
Name: "help",
|
|
||||||
Aliases: []string{"h"},
|
|
||||||
Usage: "Shows a list of commands or help for one command",
|
|
||||||
ArgsUsage: "[command]",
|
|
||||||
Action: func(c *Context) error {
|
|
||||||
args := c.Args()
|
|
||||||
if args.Present() {
|
|
||||||
return ShowCommandHelp(c, args.First())
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowAppHelp(c)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var helpSubcommand = Command{
|
|
||||||
Name: "help",
|
|
||||||
Aliases: []string{"h"},
|
|
||||||
Usage: "Shows a list of commands or help for one command",
|
|
||||||
ArgsUsage: "[command]",
|
|
||||||
Action: func(c *Context) error {
|
|
||||||
args := c.Args()
|
|
||||||
if args.Present() {
|
|
||||||
return ShowCommandHelp(c, args.First())
|
|
||||||
}
|
|
||||||
|
|
||||||
return ShowSubcommandHelp(c)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prints help for the App or Command
|
|
||||||
type helpPrinter func(w io.Writer, templ string, data interface{})
|
|
||||||
|
|
||||||
// HelpPrinter is a function that writes the help output. If not set a default
|
|
||||||
// is used. The function signature is:
|
|
||||||
// func(w io.Writer, templ string, data interface{})
|
|
||||||
var HelpPrinter helpPrinter = printHelp
|
|
||||||
|
|
||||||
// VersionPrinter prints the version for the App
|
|
||||||
var VersionPrinter = printVersion
|
|
||||||
|
|
||||||
// ShowAppHelp is an action that displays the help.
|
|
||||||
func ShowAppHelp(c *Context) error {
|
|
||||||
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultAppComplete prints the list of subcommands as the default app completion method
|
|
||||||
func DefaultAppComplete(c *Context) {
|
|
||||||
for _, command := range c.App.Commands {
|
|
||||||
if command.Hidden {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, name := range command.Names() {
|
|
||||||
fmt.Fprintln(c.App.Writer, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowCommandHelp prints help for the given command
|
|
||||||
func ShowCommandHelp(ctx *Context, command string) error {
|
|
||||||
// show the subcommand help for a command with subcommands
|
|
||||||
if command == "" {
|
|
||||||
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range ctx.App.Commands {
|
|
||||||
if c.HasName(command) {
|
|
||||||
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.App.CommandNotFound == nil {
|
|
||||||
return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.App.CommandNotFound(ctx, command)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowSubcommandHelp prints help for the given subcommand
|
|
||||||
func ShowSubcommandHelp(c *Context) error {
|
|
||||||
return ShowCommandHelp(c, c.Command.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowVersion prints the version number of the App
|
|
||||||
func ShowVersion(c *Context) {
|
|
||||||
VersionPrinter(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func printVersion(c *Context) {
|
|
||||||
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowCompletions prints the lists of commands within a given context
|
|
||||||
func ShowCompletions(c *Context) {
|
|
||||||
a := c.App
|
|
||||||
if a != nil && a.BashComplete != nil {
|
|
||||||
a.BashComplete(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowCommandCompletions prints the custom completions for a given command
|
|
||||||
func ShowCommandCompletions(ctx *Context, command string) {
|
|
||||||
c := ctx.App.Command(command)
|
|
||||||
if c != nil && c.BashComplete != nil {
|
|
||||||
c.BashComplete(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printHelp(out io.Writer, templ string, data interface{}) {
|
|
||||||
funcMap := template.FuncMap{
|
|
||||||
"join": strings.Join,
|
|
||||||
}
|
|
||||||
|
|
||||||
w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
|
|
||||||
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
|
|
||||||
err := t.Execute(w, data)
|
|
||||||
if err != nil {
|
|
||||||
// If the writer is closed, t.Execute will fail, and there's nothing
|
|
||||||
// we can do to recover.
|
|
||||||
if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
|
|
||||||
fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkVersion(c *Context) bool {
|
|
||||||
found := false
|
|
||||||
if VersionFlag.Name != "" {
|
|
||||||
eachName(VersionFlag.Name, func(name string) {
|
|
||||||
if c.GlobalBool(name) || c.Bool(name) {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkHelp(c *Context) bool {
|
|
||||||
found := false
|
|
||||||
if HelpFlag.Name != "" {
|
|
||||||
eachName(HelpFlag.Name, func(name string) {
|
|
||||||
if c.GlobalBool(name) || c.Bool(name) {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCommandHelp(c *Context, name string) bool {
|
|
||||||
if c.Bool("h") || c.Bool("help") {
|
|
||||||
ShowCommandHelp(c, name)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkSubcommandHelp(c *Context) bool {
|
|
||||||
if c.Bool("h") || c.Bool("help") {
|
|
||||||
ShowSubcommandHelp(c)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) {
|
|
||||||
if !a.EnableBashCompletion {
|
|
||||||
return false, arguments
|
|
||||||
}
|
|
||||||
|
|
||||||
pos := len(arguments) - 1
|
|
||||||
lastArg := arguments[pos]
|
|
||||||
|
|
||||||
if lastArg != "--"+BashCompletionFlag.Name {
|
|
||||||
return false, arguments
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, arguments[:pos]
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCompletions(c *Context) bool {
|
|
||||||
if !c.shellComplete {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if args := c.Args(); args.Present() {
|
|
||||||
name := args.First()
|
|
||||||
if cmd := c.App.Command(name); cmd != nil {
|
|
||||||
// let the command handle the completion
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowCompletions(c)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCommandCompletions(c *Context, name string) bool {
|
|
||||||
if !c.shellComplete {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowCommandCompletions(c, name)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
-122
@@ -1,122 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
from subprocess import check_call, check_output
|
|
||||||
|
|
||||||
|
|
||||||
PACKAGE_NAME = os.environ.get(
|
|
||||||
'CLI_PACKAGE_NAME', 'github.com/urfave/cli'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def main(sysargs=sys.argv[:]):
|
|
||||||
targets = {
|
|
||||||
'vet': _vet,
|
|
||||||
'test': _test,
|
|
||||||
'gfmrun': _gfmrun,
|
|
||||||
'toc': _toc,
|
|
||||||
'gen': _gen,
|
|
||||||
}
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument(
|
|
||||||
'target', nargs='?', choices=tuple(targets.keys()), default='test'
|
|
||||||
)
|
|
||||||
args = parser.parse_args(sysargs[1:])
|
|
||||||
|
|
||||||
targets[args.target]()
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def _test():
|
|
||||||
if check_output('go version'.split()).split()[2] < 'go1.2':
|
|
||||||
_run('go test -v .')
|
|
||||||
return
|
|
||||||
|
|
||||||
coverprofiles = []
|
|
||||||
for subpackage in ['', 'altsrc']:
|
|
||||||
coverprofile = 'cli.coverprofile'
|
|
||||||
if subpackage != '':
|
|
||||||
coverprofile = '{}.coverprofile'.format(subpackage)
|
|
||||||
|
|
||||||
coverprofiles.append(coverprofile)
|
|
||||||
|
|
||||||
_run('go test -v'.split() + [
|
|
||||||
'-coverprofile={}'.format(coverprofile),
|
|
||||||
('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/')
|
|
||||||
])
|
|
||||||
|
|
||||||
combined_name = _combine_coverprofiles(coverprofiles)
|
|
||||||
_run('go tool cover -func={}'.format(combined_name))
|
|
||||||
os.remove(combined_name)
|
|
||||||
|
|
||||||
|
|
||||||
def _gfmrun():
|
|
||||||
go_version = check_output('go version'.split()).split()[2]
|
|
||||||
if go_version < 'go1.3':
|
|
||||||
print('runtests: skip on {}'.format(go_version), file=sys.stderr)
|
|
||||||
return
|
|
||||||
_run(['gfmrun', '-c', str(_gfmrun_count()), '-s', 'README.md'])
|
|
||||||
|
|
||||||
|
|
||||||
def _vet():
|
|
||||||
_run('go vet ./...')
|
|
||||||
|
|
||||||
|
|
||||||
def _toc():
|
|
||||||
_run('node_modules/.bin/markdown-toc -i README.md')
|
|
||||||
_run('git diff --exit-code')
|
|
||||||
|
|
||||||
|
|
||||||
def _gen():
|
|
||||||
go_version = check_output('go version'.split()).split()[2]
|
|
||||||
if go_version < 'go1.5':
|
|
||||||
print('runtests: skip on {}'.format(go_version), file=sys.stderr)
|
|
||||||
return
|
|
||||||
|
|
||||||
_run('go generate ./...')
|
|
||||||
_run('git diff --exit-code')
|
|
||||||
|
|
||||||
|
|
||||||
def _run(command):
|
|
||||||
if hasattr(command, 'split'):
|
|
||||||
command = command.split()
|
|
||||||
print('runtests: {}'.format(' '.join(command)), file=sys.stderr)
|
|
||||||
check_call(command)
|
|
||||||
|
|
||||||
|
|
||||||
def _gfmrun_count():
|
|
||||||
with open('README.md') as infile:
|
|
||||||
lines = infile.read().splitlines()
|
|
||||||
return len(filter(_is_go_runnable, lines))
|
|
||||||
|
|
||||||
|
|
||||||
def _is_go_runnable(line):
|
|
||||||
return line.startswith('package main')
|
|
||||||
|
|
||||||
|
|
||||||
def _combine_coverprofiles(coverprofiles):
|
|
||||||
combined = tempfile.NamedTemporaryFile(
|
|
||||||
suffix='.coverprofile', delete=False
|
|
||||||
)
|
|
||||||
combined.write('mode: set\n')
|
|
||||||
|
|
||||||
for coverprofile in coverprofiles:
|
|
||||||
with open(coverprofile, 'r') as infile:
|
|
||||||
for line in infile.readlines():
|
|
||||||
if not line.startswith('mode: '):
|
|
||||||
combined.write(line)
|
|
||||||
|
|
||||||
combined.flush()
|
|
||||||
name = combined.name
|
|
||||||
combined.close()
|
|
||||||
return name
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main())
|
|
||||||
-27
@@ -1,27 +0,0 @@
|
|||||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
-22
@@ -1,22 +0,0 @@
|
|||||||
Additional IP Rights Grant (Patents)
|
|
||||||
|
|
||||||
"This implementation" means the copyrightable works distributed by
|
|
||||||
Google as part of the Go project.
|
|
||||||
|
|
||||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
|
||||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
|
||||||
patent license to make, have made, use, offer to sell, sell, import,
|
|
||||||
transfer and otherwise run, modify and propagate the contents of this
|
|
||||||
implementation of Go, where such license applies only to those patent
|
|
||||||
claims, both currently owned or controlled by Google and acquired in
|
|
||||||
the future, licensable by Google that are necessarily infringed by this
|
|
||||||
implementation of Go. This grant does not include claims that would be
|
|
||||||
infringed only as a consequence of further modification of this
|
|
||||||
implementation. If you or your agent or exclusive licensee institute or
|
|
||||||
order or agree to the institution of patent litigation against any
|
|
||||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
|
||||||
that this implementation of Go or any code incorporated within this
|
|
||||||
implementation of Go constitutes direct or contributory patent
|
|
||||||
infringement, or inducement of patent infringement, then any patent
|
|
||||||
rights granted to you under this License for this implementation of Go
|
|
||||||
shall terminate as of the date such litigation is filed.
|
|
||||||
-8
@@ -1,8 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This code was translated into a form compatible with 6a from the public
|
|
||||||
// domain sources in SUPERCOP: http://bench.cr.yp.to/supercop.html
|
|
||||||
|
|
||||||
#define REDMASK51 0x0007FFFFFFFFFFFF
|
|
||||||
-20
@@ -1,20 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This code was translated into a form compatible with 6a from the public
|
|
||||||
// domain sources in SUPERCOP: http://bench.cr.yp.to/supercop.html
|
|
||||||
|
|
||||||
// +build amd64,!gccgo,!appengine
|
|
||||||
|
|
||||||
// These constants cannot be encoded in non-MOVQ immediates.
|
|
||||||
// We access them directly from memory instead.
|
|
||||||
|
|
||||||
DATA ·_121666_213(SB)/8, $996687872
|
|
||||||
GLOBL ·_121666_213(SB), 8, $8
|
|
||||||
|
|
||||||
DATA ·_2P0(SB)/8, $0xFFFFFFFFFFFDA
|
|
||||||
GLOBL ·_2P0(SB), 8, $8
|
|
||||||
|
|
||||||
DATA ·_2P1234(SB)/8, $0xFFFFFFFFFFFFE
|
|
||||||
GLOBL ·_2P1234(SB), 8, $8
|
|
||||||
-88
@@ -1,88 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This code was translated into a form compatible with 6a from the public
|
|
||||||
// domain sources in SUPERCOP: http://bench.cr.yp.to/supercop.html
|
|
||||||
|
|
||||||
// +build amd64,!gccgo,!appengine
|
|
||||||
|
|
||||||
// func cswap(inout *[5]uint64, v uint64)
|
|
||||||
TEXT ·cswap(SB),7,$0
|
|
||||||
MOVQ inout+0(FP),DI
|
|
||||||
MOVQ v+8(FP),SI
|
|
||||||
|
|
||||||
CMPQ SI,$1
|
|
||||||
MOVQ 0(DI),SI
|
|
||||||
MOVQ 80(DI),DX
|
|
||||||
MOVQ 8(DI),CX
|
|
||||||
MOVQ 88(DI),R8
|
|
||||||
MOVQ SI,R9
|
|
||||||
CMOVQEQ DX,SI
|
|
||||||
CMOVQEQ R9,DX
|
|
||||||
MOVQ CX,R9
|
|
||||||
CMOVQEQ R8,CX
|
|
||||||
CMOVQEQ R9,R8
|
|
||||||
MOVQ SI,0(DI)
|
|
||||||
MOVQ DX,80(DI)
|
|
||||||
MOVQ CX,8(DI)
|
|
||||||
MOVQ R8,88(DI)
|
|
||||||
MOVQ 16(DI),SI
|
|
||||||
MOVQ 96(DI),DX
|
|
||||||
MOVQ 24(DI),CX
|
|
||||||
MOVQ 104(DI),R8
|
|
||||||
MOVQ SI,R9
|
|
||||||
CMOVQEQ DX,SI
|
|
||||||
CMOVQEQ R9,DX
|
|
||||||
MOVQ CX,R9
|
|
||||||
CMOVQEQ R8,CX
|
|
||||||
CMOVQEQ R9,R8
|
|
||||||
MOVQ SI,16(DI)
|
|
||||||
MOVQ DX,96(DI)
|
|
||||||
MOVQ CX,24(DI)
|
|
||||||
MOVQ R8,104(DI)
|
|
||||||
MOVQ 32(DI),SI
|
|
||||||
MOVQ 112(DI),DX
|
|
||||||
MOVQ 40(DI),CX
|
|
||||||
MOVQ 120(DI),R8
|
|
||||||
MOVQ SI,R9
|
|
||||||
CMOVQEQ DX,SI
|
|
||||||
CMOVQEQ R9,DX
|
|
||||||
MOVQ CX,R9
|
|
||||||
CMOVQEQ R8,CX
|
|
||||||
CMOVQEQ R9,R8
|
|
||||||
MOVQ SI,32(DI)
|
|
||||||
MOVQ DX,112(DI)
|
|
||||||
MOVQ CX,40(DI)
|
|
||||||
MOVQ R8,120(DI)
|
|
||||||
MOVQ 48(DI),SI
|
|
||||||
MOVQ 128(DI),DX
|
|
||||||
MOVQ 56(DI),CX
|
|
||||||
MOVQ 136(DI),R8
|
|
||||||
MOVQ SI,R9
|
|
||||||
CMOVQEQ DX,SI
|
|
||||||
CMOVQEQ R9,DX
|
|
||||||
MOVQ CX,R9
|
|
||||||
CMOVQEQ R8,CX
|
|
||||||
CMOVQEQ R9,R8
|
|
||||||
MOVQ SI,48(DI)
|
|
||||||
MOVQ DX,128(DI)
|
|
||||||
MOVQ CX,56(DI)
|
|
||||||
MOVQ R8,136(DI)
|
|
||||||
MOVQ 64(DI),SI
|
|
||||||
MOVQ 144(DI),DX
|
|
||||||
MOVQ 72(DI),CX
|
|
||||||
MOVQ 152(DI),R8
|
|
||||||
MOVQ SI,R9
|
|
||||||
CMOVQEQ DX,SI
|
|
||||||
CMOVQEQ R9,DX
|
|
||||||
MOVQ CX,R9
|
|
||||||
CMOVQEQ R8,CX
|
|
||||||
CMOVQEQ R9,R8
|
|
||||||
MOVQ SI,64(DI)
|
|
||||||
MOVQ DX,144(DI)
|
|
||||||
MOVQ CX,72(DI)
|
|
||||||
MOVQ R8,152(DI)
|
|
||||||
MOVQ DI,AX
|
|
||||||
MOVQ SI,DX
|
|
||||||
RET
|
|
||||||
-841
@@ -1,841 +0,0 @@
|
|||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// We have a implementation in amd64 assembly so this code is only run on
|
|
||||||
// non-amd64 platforms. The amd64 assembly does not support gccgo.
|
|
||||||
// +build !amd64 gccgo appengine
|
|
||||||
|
|
||||||
package curve25519
|
|
||||||
|
|
||||||
// This code is a port of the public domain, "ref10" implementation of
|
|
||||||
// curve25519 from SUPERCOP 20130419 by D. J. Bernstein.
|
|
||||||
|
|
||||||
// fieldElement represents an element of the field GF(2^255 - 19). An element
|
|
||||||
// t, entries t[0]...t[9], represents the integer t[0]+2^26 t[1]+2^51 t[2]+2^77
|
|
||||||
// t[3]+2^102 t[4]+...+2^230 t[9]. Bounds on each t[i] vary depending on
|
|
||||||
// context.
|
|
||||||
type fieldElement [10]int32
|
|
||||||
|
|
||||||
func feZero(fe *fieldElement) {
|
|
||||||
for i := range fe {
|
|
||||||
fe[i] = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func feOne(fe *fieldElement) {
|
|
||||||
feZero(fe)
|
|
||||||
fe[0] = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func feAdd(dst, a, b *fieldElement) {
|
|
||||||
for i := range dst {
|
|
||||||
dst[i] = a[i] + b[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func feSub(dst, a, b *fieldElement) {
|
|
||||||
for i := range dst {
|
|
||||||
dst[i] = a[i] - b[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func feCopy(dst, src *fieldElement) {
|
|
||||||
for i := range dst {
|
|
||||||
dst[i] = src[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// feCSwap replaces (f,g) with (g,f) if b == 1; replaces (f,g) with (f,g) if b == 0.
|
|
||||||
//
|
|
||||||
// Preconditions: b in {0,1}.
|
|
||||||
func feCSwap(f, g *fieldElement, b int32) {
|
|
||||||
var x fieldElement
|
|
||||||
b = -b
|
|
||||||
for i := range x {
|
|
||||||
x[i] = b & (f[i] ^ g[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range f {
|
|
||||||
f[i] ^= x[i]
|
|
||||||
}
|
|
||||||
for i := range g {
|
|
||||||
g[i] ^= x[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// load3 reads a 24-bit, little-endian value from in.
|
|
||||||
func load3(in []byte) int64 {
|
|
||||||
var r int64
|
|
||||||
r = int64(in[0])
|
|
||||||
r |= int64(in[1]) << 8
|
|
||||||
r |= int64(in[2]) << 16
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// load4 reads a 32-bit, little-endian value from in.
|
|
||||||
func load4(in []byte) int64 {
|
|
||||||
var r int64
|
|
||||||
r = int64(in[0])
|
|
||||||
r |= int64(in[1]) << 8
|
|
||||||
r |= int64(in[2]) << 16
|
|
||||||
r |= int64(in[3]) << 24
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func feFromBytes(dst *fieldElement, src *[32]byte) {
|
|
||||||
h0 := load4(src[:])
|
|
||||||
h1 := load3(src[4:]) << 6
|
|
||||||
h2 := load3(src[7:]) << 5
|
|
||||||
h3 := load3(src[10:]) << 3
|
|
||||||
h4 := load3(src[13:]) << 2
|
|
||||||
h5 := load4(src[16:])
|
|
||||||
h6 := load3(src[20:]) << 7
|
|
||||||
h7 := load3(src[23:]) << 5
|
|
||||||
h8 := load3(src[26:]) << 4
|
|
||||||
h9 := load3(src[29:]) << 2
|
|
||||||
|
|
||||||
var carry [10]int64
|
|
||||||
carry[9] = (h9 + 1<<24) >> 25
|
|
||||||
h0 += carry[9] * 19
|
|
||||||
h9 -= carry[9] << 25
|
|
||||||
carry[1] = (h1 + 1<<24) >> 25
|
|
||||||
h2 += carry[1]
|
|
||||||
h1 -= carry[1] << 25
|
|
||||||
carry[3] = (h3 + 1<<24) >> 25
|
|
||||||
h4 += carry[3]
|
|
||||||
h3 -= carry[3] << 25
|
|
||||||
carry[5] = (h5 + 1<<24) >> 25
|
|
||||||
h6 += carry[5]
|
|
||||||
h5 -= carry[5] << 25
|
|
||||||
carry[7] = (h7 + 1<<24) >> 25
|
|
||||||
h8 += carry[7]
|
|
||||||
h7 -= carry[7] << 25
|
|
||||||
|
|
||||||
carry[0] = (h0 + 1<<25) >> 26
|
|
||||||
h1 += carry[0]
|
|
||||||
h0 -= carry[0] << 26
|
|
||||||
carry[2] = (h2 + 1<<25) >> 26
|
|
||||||
h3 += carry[2]
|
|
||||||
h2 -= carry[2] << 26
|
|
||||||
carry[4] = (h4 + 1<<25) >> 26
|
|
||||||
h5 += carry[4]
|
|
||||||
h4 -= carry[4] << 26
|
|
||||||
carry[6] = (h6 + 1<<25) >> 26
|
|
||||||
h7 += carry[6]
|
|
||||||
h6 -= carry[6] << 26
|
|
||||||
carry[8] = (h8 + 1<<25) >> 26
|
|
||||||
h9 += carry[8]
|
|
||||||
h8 -= carry[8] << 26
|
|
||||||
|
|
||||||
dst[0] = int32(h0)
|
|
||||||
dst[1] = int32(h1)
|
|
||||||
dst[2] = int32(h2)
|
|
||||||
dst[3] = int32(h3)
|
|
||||||
dst[4] = int32(h4)
|
|
||||||
dst[5] = int32(h5)
|
|
||||||
dst[6] = int32(h6)
|
|
||||||
dst[7] = int32(h7)
|
|
||||||
dst[8] = int32(h8)
|
|
||||||
dst[9] = int32(h9)
|
|
||||||
}
|
|
||||||
|
|
||||||
// feToBytes marshals h to s.
|
|
||||||
// Preconditions:
|
|
||||||
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|
|
||||||
//
|
|
||||||
// Write p=2^255-19; q=floor(h/p).
|
|
||||||
// Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))).
|
|
||||||
//
|
|
||||||
// Proof:
|
|
||||||
// Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4.
|
|
||||||
// Also have |h-2^230 h9|<2^230 so |19 2^(-255)(h-2^230 h9)|<1/4.
|
|
||||||
//
|
|
||||||
// Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9).
|
|
||||||
// Then 0<y<1.
|
|
||||||
//
|
|
||||||
// Write r=h-pq.
|
|
||||||
// Have 0<=r<=p-1=2^255-20.
|
|
||||||
// Thus 0<=r+19(2^-255)r<r+19(2^-255)2^255<=2^255-1.
|
|
||||||
//
|
|
||||||
// Write x=r+19(2^-255)r+y.
|
|
||||||
// Then 0<x<2^255 so floor(2^(-255)x) = 0 so floor(q+2^(-255)x) = q.
|
|
||||||
//
|
|
||||||
// Have q+2^(-255)x = 2^(-255)(h + 19 2^(-25) h9 + 2^(-1))
|
|
||||||
// so floor(2^(-255)(h + 19 2^(-25) h9 + 2^(-1))) = q.
|
|
||||||
func feToBytes(s *[32]byte, h *fieldElement) {
|
|
||||||
var carry [10]int32
|
|
||||||
|
|
||||||
q := (19*h[9] + (1 << 24)) >> 25
|
|
||||||
q = (h[0] + q) >> 26
|
|
||||||
q = (h[1] + q) >> 25
|
|
||||||
q = (h[2] + q) >> 26
|
|
||||||
q = (h[3] + q) >> 25
|
|
||||||
q = (h[4] + q) >> 26
|
|
||||||
q = (h[5] + q) >> 25
|
|
||||||
q = (h[6] + q) >> 26
|
|
||||||
q = (h[7] + q) >> 25
|
|
||||||
q = (h[8] + q) >> 26
|
|
||||||
q = (h[9] + q) >> 25
|
|
||||||
|
|
||||||
// Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20.
|
|
||||||
h[0] += 19 * q
|
|
||||||
// Goal: Output h-2^255 q, which is between 0 and 2^255-20.
|
|
||||||
|
|
||||||
carry[0] = h[0] >> 26
|
|
||||||
h[1] += carry[0]
|
|
||||||
h[0] -= carry[0] << 26
|
|
||||||
carry[1] = h[1] >> 25
|
|
||||||
h[2] += carry[1]
|
|
||||||
h[1] -= carry[1] << 25
|
|
||||||
carry[2] = h[2] >> 26
|
|
||||||
h[3] += carry[2]
|
|
||||||
h[2] -= carry[2] << 26
|
|
||||||
carry[3] = h[3] >> 25
|
|
||||||
h[4] += carry[3]
|
|
||||||
h[3] -= carry[3] << 25
|
|
||||||
carry[4] = h[4] >> 26
|
|
||||||
h[5] += carry[4]
|
|
||||||
h[4] -= carry[4] << 26
|
|
||||||
carry[5] = h[5] >> 25
|
|
||||||
h[6] += carry[5]
|
|
||||||
h[5] -= carry[5] << 25
|
|
||||||
carry[6] = h[6] >> 26
|
|
||||||
h[7] += carry[6]
|
|
||||||
h[6] -= carry[6] << 26
|
|
||||||
carry[7] = h[7] >> 25
|
|
||||||
h[8] += carry[7]
|
|
||||||
h[7] -= carry[7] << 25
|
|
||||||
carry[8] = h[8] >> 26
|
|
||||||
h[9] += carry[8]
|
|
||||||
h[8] -= carry[8] << 26
|
|
||||||
carry[9] = h[9] >> 25
|
|
||||||
h[9] -= carry[9] << 25
|
|
||||||
// h10 = carry9
|
|
||||||
|
|
||||||
// Goal: Output h[0]+...+2^255 h10-2^255 q, which is between 0 and 2^255-20.
|
|
||||||
// Have h[0]+...+2^230 h[9] between 0 and 2^255-1;
|
|
||||||
// evidently 2^255 h10-2^255 q = 0.
|
|
||||||
// Goal: Output h[0]+...+2^230 h[9].
|
|
||||||
|
|
||||||
s[0] = byte(h[0] >> 0)
|
|
||||||
s[1] = byte(h[0] >> 8)
|
|
||||||
s[2] = byte(h[0] >> 16)
|
|
||||||
s[3] = byte((h[0] >> 24) | (h[1] << 2))
|
|
||||||
s[4] = byte(h[1] >> 6)
|
|
||||||
s[5] = byte(h[1] >> 14)
|
|
||||||
s[6] = byte((h[1] >> 22) | (h[2] << 3))
|
|
||||||
s[7] = byte(h[2] >> 5)
|
|
||||||
s[8] = byte(h[2] >> 13)
|
|
||||||
s[9] = byte((h[2] >> 21) | (h[3] << 5))
|
|
||||||
s[10] = byte(h[3] >> 3)
|
|
||||||
s[11] = byte(h[3] >> 11)
|
|
||||||
s[12] = byte((h[3] >> 19) | (h[4] << 6))
|
|
||||||
s[13] = byte(h[4] >> 2)
|
|
||||||
s[14] = byte(h[4] >> 10)
|
|
||||||
s[15] = byte(h[4] >> 18)
|
|
||||||
s[16] = byte(h[5] >> 0)
|
|
||||||
s[17] = byte(h[5] >> 8)
|
|
||||||
s[18] = byte(h[5] >> 16)
|
|
||||||
s[19] = byte((h[5] >> 24) | (h[6] << 1))
|
|
||||||
s[20] = byte(h[6] >> 7)
|
|
||||||
s[21] = byte(h[6] >> 15)
|
|
||||||
s[22] = byte((h[6] >> 23) | (h[7] << 3))
|
|
||||||
s[23] = byte(h[7] >> 5)
|
|
||||||
s[24] = byte(h[7] >> 13)
|
|
||||||
s[25] = byte((h[7] >> 21) | (h[8] << 4))
|
|
||||||
s[26] = byte(h[8] >> 4)
|
|
||||||
s[27] = byte(h[8] >> 12)
|
|
||||||
s[28] = byte((h[8] >> 20) | (h[9] << 6))
|
|
||||||
s[29] = byte(h[9] >> 2)
|
|
||||||
s[30] = byte(h[9] >> 10)
|
|
||||||
s[31] = byte(h[9] >> 18)
|
|
||||||
}
|
|
||||||
|
|
||||||
// feMul calculates h = f * g
|
|
||||||
// Can overlap h with f or g.
|
|
||||||
//
|
|
||||||
// Preconditions:
|
|
||||||
// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
|
|
||||||
// |g| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
|
|
||||||
//
|
|
||||||
// Postconditions:
|
|
||||||
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|
|
||||||
//
|
|
||||||
// Notes on implementation strategy:
|
|
||||||
//
|
|
||||||
// Using schoolbook multiplication.
|
|
||||||
// Karatsuba would save a little in some cost models.
|
|
||||||
//
|
|
||||||
// Most multiplications by 2 and 19 are 32-bit precomputations;
|
|
||||||
// cheaper than 64-bit postcomputations.
|
|
||||||
//
|
|
||||||
// There is one remaining multiplication by 19 in the carry chain;
|
|
||||||
// one *19 precomputation can be merged into this,
|
|
||||||
// but the resulting data flow is considerably less clean.
|
|
||||||
//
|
|
||||||
// There are 12 carries below.
|
|
||||||
// 10 of them are 2-way parallelizable and vectorizable.
|
|
||||||
// Can get away with 11 carries, but then data flow is much deeper.
|
|
||||||
//
|
|
||||||
// With tighter constraints on inputs can squeeze carries into int32.
|
|
||||||
func feMul(h, f, g *fieldElement) {
|
|
||||||
f0 := f[0]
|
|
||||||
f1 := f[1]
|
|
||||||
f2 := f[2]
|
|
||||||
f3 := f[3]
|
|
||||||
f4 := f[4]
|
|
||||||
f5 := f[5]
|
|
||||||
f6 := f[6]
|
|
||||||
f7 := f[7]
|
|
||||||
f8 := f[8]
|
|
||||||
f9 := f[9]
|
|
||||||
g0 := g[0]
|
|
||||||
g1 := g[1]
|
|
||||||
g2 := g[2]
|
|
||||||
g3 := g[3]
|
|
||||||
g4 := g[4]
|
|
||||||
g5 := g[5]
|
|
||||||
g6 := g[6]
|
|
||||||
g7 := g[7]
|
|
||||||
g8 := g[8]
|
|
||||||
g9 := g[9]
|
|
||||||
g1_19 := 19 * g1 // 1.4*2^29
|
|
||||||
g2_19 := 19 * g2 // 1.4*2^30; still ok
|
|
||||||
g3_19 := 19 * g3
|
|
||||||
g4_19 := 19 * g4
|
|
||||||
g5_19 := 19 * g5
|
|
||||||
g6_19 := 19 * g6
|
|
||||||
g7_19 := 19 * g7
|
|
||||||
g8_19 := 19 * g8
|
|
||||||
g9_19 := 19 * g9
|
|
||||||
f1_2 := 2 * f1
|
|
||||||
f3_2 := 2 * f3
|
|
||||||
f5_2 := 2 * f5
|
|
||||||
f7_2 := 2 * f7
|
|
||||||
f9_2 := 2 * f9
|
|
||||||
f0g0 := int64(f0) * int64(g0)
|
|
||||||
f0g1 := int64(f0) * int64(g1)
|
|
||||||
f0g2 := int64(f0) * int64(g2)
|
|
||||||
f0g3 := int64(f0) * int64(g3)
|
|
||||||
f0g4 := int64(f0) * int64(g4)
|
|
||||||
f0g5 := int64(f0) * int64(g5)
|
|
||||||
f0g6 := int64(f0) * int64(g6)
|
|
||||||
f0g7 := int64(f0) * int64(g7)
|
|
||||||
f0g8 := int64(f0) * int64(g8)
|
|
||||||
f0g9 := int64(f0) * int64(g9)
|
|
||||||
f1g0 := int64(f1) * int64(g0)
|
|
||||||
f1g1_2 := int64(f1_2) * int64(g1)
|
|
||||||
f1g2 := int64(f1) * int64(g2)
|
|
||||||
f1g3_2 := int64(f1_2) * int64(g3)
|
|
||||||
f1g4 := int64(f1) * int64(g4)
|
|
||||||
f1g5_2 := int64(f1_2) * int64(g5)
|
|
||||||
f1g6 := int64(f1) * int64(g6)
|
|
||||||
f1g7_2 := int64(f1_2) * int64(g7)
|
|
||||||
f1g8 := int64(f1) * int64(g8)
|
|
||||||
f1g9_38 := int64(f1_2) * int64(g9_19)
|
|
||||||
f2g0 := int64(f2) * int64(g0)
|
|
||||||
f2g1 := int64(f2) * int64(g1)
|
|
||||||
f2g2 := int64(f2) * int64(g2)
|
|
||||||
f2g3 := int64(f2) * int64(g3)
|
|
||||||
f2g4 := int64(f2) * int64(g4)
|
|
||||||
f2g5 := int64(f2) * int64(g5)
|
|
||||||
f2g6 := int64(f2) * int64(g6)
|
|
||||||
f2g7 := int64(f2) * int64(g7)
|
|
||||||
f2g8_19 := int64(f2) * int64(g8_19)
|
|
||||||
f2g9_19 := int64(f2) * int64(g9_19)
|
|
||||||
f3g0 := int64(f3) * int64(g0)
|
|
||||||
f3g1_2 := int64(f3_2) * int64(g1)
|
|
||||||
f3g2 := int64(f3) * int64(g2)
|
|
||||||
f3g3_2 := int64(f3_2) * int64(g3)
|
|
||||||
f3g4 := int64(f3) * int64(g4)
|
|
||||||
f3g5_2 := int64(f3_2) * int64(g5)
|
|
||||||
f3g6 := int64(f3) * int64(g6)
|
|
||||||
f3g7_38 := int64(f3_2) * int64(g7_19)
|
|
||||||
f3g8_19 := int64(f3) * int64(g8_19)
|
|
||||||
f3g9_38 := int64(f3_2) * int64(g9_19)
|
|
||||||
f4g0 := int64(f4) * int64(g0)
|
|
||||||
f4g1 := int64(f4) * int64(g1)
|
|
||||||
f4g2 := int64(f4) * int64(g2)
|
|
||||||
f4g3 := int64(f4) * int64(g3)
|
|
||||||
f4g4 := int64(f4) * int64(g4)
|
|
||||||
f4g5 := int64(f4) * int64(g5)
|
|
||||||
f4g6_19 := int64(f4) * int64(g6_19)
|
|
||||||
f4g7_19 := int64(f4) * int64(g7_19)
|
|
||||||
f4g8_19 := int64(f4) * int64(g8_19)
|
|
||||||
f4g9_19 := int64(f4) * int64(g9_19)
|
|
||||||
f5g0 := int64(f5) * int64(g0)
|
|
||||||
f5g1_2 := int64(f5_2) * int64(g1)
|
|
||||||
f5g2 := int64(f5) * int64(g2)
|
|
||||||
f5g3_2 := int64(f5_2) * int64(g3)
|
|
||||||
f5g4 := int64(f5) * int64(g4)
|
|
||||||
f5g5_38 := int64(f5_2) * int64(g5_19)
|
|
||||||
f5g6_19 := int64(f5) * int64(g6_19)
|
|
||||||
f5g7_38 := int64(f5_2) * int64(g7_19)
|
|
||||||
f5g8_19 := int64(f5) * int64(g8_19)
|
|
||||||
f5g9_38 := int64(f5_2) * int64(g9_19)
|
|
||||||
f6g0 := int64(f6) * int64(g0)
|
|
||||||
f6g1 := int64(f6) * int64(g1)
|
|
||||||
f6g2 := int64(f6) * int64(g2)
|
|
||||||
f6g3 := int64(f6) * int64(g3)
|
|
||||||
f6g4_19 := int64(f6) * int64(g4_19)
|
|
||||||
f6g5_19 := int64(f6) * int64(g5_19)
|
|
||||||
f6g6_19 := int64(f6) * int64(g6_19)
|
|
||||||
f6g7_19 := int64(f6) * int64(g7_19)
|
|
||||||
f6g8_19 := int64(f6) * int64(g8_19)
|
|
||||||
f6g9_19 := int64(f6) * int64(g9_19)
|
|
||||||
f7g0 := int64(f7) * int64(g0)
|
|
||||||
f7g1_2 := int64(f7_2) * int64(g1)
|
|
||||||
f7g2 := int64(f7) * int64(g2)
|
|
||||||
f7g3_38 := int64(f7_2) * int64(g3_19)
|
|
||||||
f7g4_19 := int64(f7) * int64(g4_19)
|
|
||||||
f7g5_38 := int64(f7_2) * int64(g5_19)
|
|
||||||
f7g6_19 := int64(f7) * int64(g6_19)
|
|
||||||
f7g7_38 := int64(f7_2) * int64(g7_19)
|
|
||||||
f7g8_19 := int64(f7) * int64(g8_19)
|
|
||||||
f7g9_38 := int64(f7_2) * int64(g9_19)
|
|
||||||
f8g0 := int64(f8) * int64(g0)
|
|
||||||
f8g1 := int64(f8) * int64(g1)
|
|
||||||
f8g2_19 := int64(f8) * int64(g2_19)
|
|
||||||
f8g3_19 := int64(f8) * int64(g3_19)
|
|
||||||
f8g4_19 := int64(f8) * int64(g4_19)
|
|
||||||
f8g5_19 := int64(f8) * int64(g5_19)
|
|
||||||
f8g6_19 := int64(f8) * int64(g6_19)
|
|
||||||
f8g7_19 := int64(f8) * int64(g7_19)
|
|
||||||
f8g8_19 := int64(f8) * int64(g8_19)
|
|
||||||
f8g9_19 := int64(f8) * int64(g9_19)
|
|
||||||
f9g0 := int64(f9) * int64(g0)
|
|
||||||
f9g1_38 := int64(f9_2) * int64(g1_19)
|
|
||||||
f9g2_19 := int64(f9) * int64(g2_19)
|
|
||||||
f9g3_38 := int64(f9_2) * int64(g3_19)
|
|
||||||
f9g4_19 := int64(f9) * int64(g4_19)
|
|
||||||
f9g5_38 := int64(f9_2) * int64(g5_19)
|
|
||||||
f9g6_19 := int64(f9) * int64(g6_19)
|
|
||||||
f9g7_38 := int64(f9_2) * int64(g7_19)
|
|
||||||
f9g8_19 := int64(f9) * int64(g8_19)
|
|
||||||
f9g9_38 := int64(f9_2) * int64(g9_19)
|
|
||||||
h0 := f0g0 + f1g9_38 + f2g8_19 + f3g7_38 + f4g6_19 + f5g5_38 + f6g4_19 + f7g3_38 + f8g2_19 + f9g1_38
|
|
||||||
h1 := f0g1 + f1g0 + f2g9_19 + f3g8_19 + f4g7_19 + f5g6_19 + f6g5_19 + f7g4_19 + f8g3_19 + f9g2_19
|
|
||||||
h2 := f0g2 + f1g1_2 + f2g0 + f3g9_38 + f4g8_19 + f5g7_38 + f6g6_19 + f7g5_38 + f8g4_19 + f9g3_38
|
|
||||||
h3 := f0g3 + f1g2 + f2g1 + f3g0 + f4g9_19 + f5g8_19 + f6g7_19 + f7g6_19 + f8g5_19 + f9g4_19
|
|
||||||
h4 := f0g4 + f1g3_2 + f2g2 + f3g1_2 + f4g0 + f5g9_38 + f6g8_19 + f7g7_38 + f8g6_19 + f9g5_38
|
|
||||||
h5 := f0g5 + f1g4 + f2g3 + f3g2 + f4g1 + f5g0 + f6g9_19 + f7g8_19 + f8g7_19 + f9g6_19
|
|
||||||
h6 := f0g6 + f1g5_2 + f2g4 + f3g3_2 + f4g2 + f5g1_2 + f6g0 + f7g9_38 + f8g8_19 + f9g7_38
|
|
||||||
h7 := f0g7 + f1g6 + f2g5 + f3g4 + f4g3 + f5g2 + f6g1 + f7g0 + f8g9_19 + f9g8_19
|
|
||||||
h8 := f0g8 + f1g7_2 + f2g6 + f3g5_2 + f4g4 + f5g3_2 + f6g2 + f7g1_2 + f8g0 + f9g9_38
|
|
||||||
h9 := f0g9 + f1g8 + f2g7 + f3g6 + f4g5 + f5g4 + f6g3 + f7g2 + f8g1 + f9g0
|
|
||||||
var carry [10]int64
|
|
||||||
|
|
||||||
// |h0| <= (1.1*1.1*2^52*(1+19+19+19+19)+1.1*1.1*2^50*(38+38+38+38+38))
|
|
||||||
// i.e. |h0| <= 1.2*2^59; narrower ranges for h2, h4, h6, h8
|
|
||||||
// |h1| <= (1.1*1.1*2^51*(1+1+19+19+19+19+19+19+19+19))
|
|
||||||
// i.e. |h1| <= 1.5*2^58; narrower ranges for h3, h5, h7, h9
|
|
||||||
|
|
||||||
carry[0] = (h0 + (1 << 25)) >> 26
|
|
||||||
h1 += carry[0]
|
|
||||||
h0 -= carry[0] << 26
|
|
||||||
carry[4] = (h4 + (1 << 25)) >> 26
|
|
||||||
h5 += carry[4]
|
|
||||||
h4 -= carry[4] << 26
|
|
||||||
// |h0| <= 2^25
|
|
||||||
// |h4| <= 2^25
|
|
||||||
// |h1| <= 1.51*2^58
|
|
||||||
// |h5| <= 1.51*2^58
|
|
||||||
|
|
||||||
carry[1] = (h1 + (1 << 24)) >> 25
|
|
||||||
h2 += carry[1]
|
|
||||||
h1 -= carry[1] << 25
|
|
||||||
carry[5] = (h5 + (1 << 24)) >> 25
|
|
||||||
h6 += carry[5]
|
|
||||||
h5 -= carry[5] << 25
|
|
||||||
// |h1| <= 2^24; from now on fits into int32
|
|
||||||
// |h5| <= 2^24; from now on fits into int32
|
|
||||||
// |h2| <= 1.21*2^59
|
|
||||||
// |h6| <= 1.21*2^59
|
|
||||||
|
|
||||||
carry[2] = (h2 + (1 << 25)) >> 26
|
|
||||||
h3 += carry[2]
|
|
||||||
h2 -= carry[2] << 26
|
|
||||||
carry[6] = (h6 + (1 << 25)) >> 26
|
|
||||||
h7 += carry[6]
|
|
||||||
h6 -= carry[6] << 26
|
|
||||||
// |h2| <= 2^25; from now on fits into int32 unchanged
|
|
||||||
// |h6| <= 2^25; from now on fits into int32 unchanged
|
|
||||||
// |h3| <= 1.51*2^58
|
|
||||||
// |h7| <= 1.51*2^58
|
|
||||||
|
|
||||||
carry[3] = (h3 + (1 << 24)) >> 25
|
|
||||||
h4 += carry[3]
|
|
||||||
h3 -= carry[3] << 25
|
|
||||||
carry[7] = (h7 + (1 << 24)) >> 25
|
|
||||||
h8 += carry[7]
|
|
||||||
h7 -= carry[7] << 25
|
|
||||||
// |h3| <= 2^24; from now on fits into int32 unchanged
|
|
||||||
// |h7| <= 2^24; from now on fits into int32 unchanged
|
|
||||||
// |h4| <= 1.52*2^33
|
|
||||||
// |h8| <= 1.52*2^33
|
|
||||||
|
|
||||||
carry[4] = (h4 + (1 << 25)) >> 26
|
|
||||||
h5 += carry[4]
|
|
||||||
h4 -= carry[4] << 26
|
|
||||||
carry[8] = (h8 + (1 << 25)) >> 26
|
|
||||||
h9 += carry[8]
|
|
||||||
h8 -= carry[8] << 26
|
|
||||||
// |h4| <= 2^25; from now on fits into int32 unchanged
|
|
||||||
// |h8| <= 2^25; from now on fits into int32 unchanged
|
|
||||||
// |h5| <= 1.01*2^24
|
|
||||||
// |h9| <= 1.51*2^58
|
|
||||||
|
|
||||||
carry[9] = (h9 + (1 << 24)) >> 25
|
|
||||||
h0 += carry[9] * 19
|
|
||||||
h9 -= carry[9] << 25
|
|
||||||
// |h9| <= 2^24; from now on fits into int32 unchanged
|
|
||||||
// |h0| <= 1.8*2^37
|
|
||||||
|
|
||||||
carry[0] = (h0 + (1 << 25)) >> 26
|
|
||||||
h1 += carry[0]
|
|
||||||
h0 -= carry[0] << 26
|
|
||||||
// |h0| <= 2^25; from now on fits into int32 unchanged
|
|
||||||
// |h1| <= 1.01*2^24
|
|
||||||
|
|
||||||
h[0] = int32(h0)
|
|
||||||
h[1] = int32(h1)
|
|
||||||
h[2] = int32(h2)
|
|
||||||
h[3] = int32(h3)
|
|
||||||
h[4] = int32(h4)
|
|
||||||
h[5] = int32(h5)
|
|
||||||
h[6] = int32(h6)
|
|
||||||
h[7] = int32(h7)
|
|
||||||
h[8] = int32(h8)
|
|
||||||
h[9] = int32(h9)
|
|
||||||
}
|
|
||||||
|
|
||||||
// feSquare calculates h = f*f. Can overlap h with f.
|
|
||||||
//
|
|
||||||
// Preconditions:
|
|
||||||
// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
|
|
||||||
//
|
|
||||||
// Postconditions:
|
|
||||||
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|
|
||||||
func feSquare(h, f *fieldElement) {
|
|
||||||
f0 := f[0]
|
|
||||||
f1 := f[1]
|
|
||||||
f2 := f[2]
|
|
||||||
f3 := f[3]
|
|
||||||
f4 := f[4]
|
|
||||||
f5 := f[5]
|
|
||||||
f6 := f[6]
|
|
||||||
f7 := f[7]
|
|
||||||
f8 := f[8]
|
|
||||||
f9 := f[9]
|
|
||||||
f0_2 := 2 * f0
|
|
||||||
f1_2 := 2 * f1
|
|
||||||
f2_2 := 2 * f2
|
|
||||||
f3_2 := 2 * f3
|
|
||||||
f4_2 := 2 * f4
|
|
||||||
f5_2 := 2 * f5
|
|
||||||
f6_2 := 2 * f6
|
|
||||||
f7_2 := 2 * f7
|
|
||||||
f5_38 := 38 * f5 // 1.31*2^30
|
|
||||||
f6_19 := 19 * f6 // 1.31*2^30
|
|
||||||
f7_38 := 38 * f7 // 1.31*2^30
|
|
||||||
f8_19 := 19 * f8 // 1.31*2^30
|
|
||||||
f9_38 := 38 * f9 // 1.31*2^30
|
|
||||||
f0f0 := int64(f0) * int64(f0)
|
|
||||||
f0f1_2 := int64(f0_2) * int64(f1)
|
|
||||||
f0f2_2 := int64(f0_2) * int64(f2)
|
|
||||||
f0f3_2 := int64(f0_2) * int64(f3)
|
|
||||||
f0f4_2 := int64(f0_2) * int64(f4)
|
|
||||||
f0f5_2 := int64(f0_2) * int64(f5)
|
|
||||||
f0f6_2 := int64(f0_2) * int64(f6)
|
|
||||||
f0f7_2 := int64(f0_2) * int64(f7)
|
|
||||||
f0f8_2 := int64(f0_2) * int64(f8)
|
|
||||||
f0f9_2 := int64(f0_2) * int64(f9)
|
|
||||||
f1f1_2 := int64(f1_2) * int64(f1)
|
|
||||||
f1f2_2 := int64(f1_2) * int64(f2)
|
|
||||||
f1f3_4 := int64(f1_2) * int64(f3_2)
|
|
||||||
f1f4_2 := int64(f1_2) * int64(f4)
|
|
||||||
f1f5_4 := int64(f1_2) * int64(f5_2)
|
|
||||||
f1f6_2 := int64(f1_2) * int64(f6)
|
|
||||||
f1f7_4 := int64(f1_2) * int64(f7_2)
|
|
||||||
f1f8_2 := int64(f1_2) * int64(f8)
|
|
||||||
f1f9_76 := int64(f1_2) * int64(f9_38)
|
|
||||||
f2f2 := int64(f2) * int64(f2)
|
|
||||||
f2f3_2 := int64(f2_2) * int64(f3)
|
|
||||||
f2f4_2 := int64(f2_2) * int64(f4)
|
|
||||||
f2f5_2 := int64(f2_2) * int64(f5)
|
|
||||||
f2f6_2 := int64(f2_2) * int64(f6)
|
|
||||||
f2f7_2 := int64(f2_2) * int64(f7)
|
|
||||||
f2f8_38 := int64(f2_2) * int64(f8_19)
|
|
||||||
f2f9_38 := int64(f2) * int64(f9_38)
|
|
||||||
f3f3_2 := int64(f3_2) * int64(f3)
|
|
||||||
f3f4_2 := int64(f3_2) * int64(f4)
|
|
||||||
f3f5_4 := int64(f3_2) * int64(f5_2)
|
|
||||||
f3f6_2 := int64(f3_2) * int64(f6)
|
|
||||||
f3f7_76 := int64(f3_2) * int64(f7_38)
|
|
||||||
f3f8_38 := int64(f3_2) * int64(f8_19)
|
|
||||||
f3f9_76 := int64(f3_2) * int64(f9_38)
|
|
||||||
f4f4 := int64(f4) * int64(f4)
|
|
||||||
f4f5_2 := int64(f4_2) * int64(f5)
|
|
||||||
f4f6_38 := int64(f4_2) * int64(f6_19)
|
|
||||||
f4f7_38 := int64(f4) * int64(f7_38)
|
|
||||||
f4f8_38 := int64(f4_2) * int64(f8_19)
|
|
||||||
f4f9_38 := int64(f4) * int64(f9_38)
|
|
||||||
f5f5_38 := int64(f5) * int64(f5_38)
|
|
||||||
f5f6_38 := int64(f5_2) * int64(f6_19)
|
|
||||||
f5f7_76 := int64(f5_2) * int64(f7_38)
|
|
||||||
f5f8_38 := int64(f5_2) * int64(f8_19)
|
|
||||||
f5f9_76 := int64(f5_2) * int64(f9_38)
|
|
||||||
f6f6_19 := int64(f6) * int64(f6_19)
|
|
||||||
f6f7_38 := int64(f6) * int64(f7_38)
|
|
||||||
f6f8_38 := int64(f6_2) * int64(f8_19)
|
|
||||||
f6f9_38 := int64(f6) * int64(f9_38)
|
|
||||||
f7f7_38 := int64(f7) * int64(f7_38)
|
|
||||||
f7f8_38 := int64(f7_2) * int64(f8_19)
|
|
||||||
f7f9_76 := int64(f7_2) * int64(f9_38)
|
|
||||||
f8f8_19 := int64(f8) * int64(f8_19)
|
|
||||||
f8f9_38 := int64(f8) * int64(f9_38)
|
|
||||||
f9f9_38 := int64(f9) * int64(f9_38)
|
|
||||||
h0 := f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38
|
|
||||||
h1 := f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38
|
|
||||||
h2 := f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19
|
|
||||||
h3 := f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38
|
|
||||||
h4 := f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38
|
|
||||||
h5 := f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38
|
|
||||||
h6 := f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19
|
|
||||||
h7 := f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38
|
|
||||||
h8 := f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38
|
|
||||||
h9 := f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2
|
|
||||||
var carry [10]int64
|
|
||||||
|
|
||||||
carry[0] = (h0 + (1 << 25)) >> 26
|
|
||||||
h1 += carry[0]
|
|
||||||
h0 -= carry[0] << 26
|
|
||||||
carry[4] = (h4 + (1 << 25)) >> 26
|
|
||||||
h5 += carry[4]
|
|
||||||
h4 -= carry[4] << 26
|
|
||||||
|
|
||||||
carry[1] = (h1 + (1 << 24)) >> 25
|
|
||||||
h2 += carry[1]
|
|
||||||
h1 -= carry[1] << 25
|
|
||||||
carry[5] = (h5 + (1 << 24)) >> 25
|
|
||||||
h6 += carry[5]
|
|
||||||
h5 -= carry[5] << 25
|
|
||||||
|
|
||||||
carry[2] = (h2 + (1 << 25)) >> 26
|
|
||||||
h3 += carry[2]
|
|
||||||
h2 -= carry[2] << 26
|
|
||||||
carry[6] = (h6 + (1 << 25)) >> 26
|
|
||||||
h7 += carry[6]
|
|
||||||
h6 -= carry[6] << 26
|
|
||||||
|
|
||||||
carry[3] = (h3 + (1 << 24)) >> 25
|
|
||||||
h4 += carry[3]
|
|
||||||
h3 -= carry[3] << 25
|
|
||||||
carry[7] = (h7 + (1 << 24)) >> 25
|
|
||||||
h8 += carry[7]
|
|
||||||
h7 -= carry[7] << 25
|
|
||||||
|
|
||||||
carry[4] = (h4 + (1 << 25)) >> 26
|
|
||||||
h5 += carry[4]
|
|
||||||
h4 -= carry[4] << 26
|
|
||||||
carry[8] = (h8 + (1 << 25)) >> 26
|
|
||||||
h9 += carry[8]
|
|
||||||
h8 -= carry[8] << 26
|
|
||||||
|
|
||||||
carry[9] = (h9 + (1 << 24)) >> 25
|
|
||||||
h0 += carry[9] * 19
|
|
||||||
h9 -= carry[9] << 25
|
|
||||||
|
|
||||||
carry[0] = (h0 + (1 << 25)) >> 26
|
|
||||||
h1 += carry[0]
|
|
||||||
h0 -= carry[0] << 26
|
|
||||||
|
|
||||||
h[0] = int32(h0)
|
|
||||||
h[1] = int32(h1)
|
|
||||||
h[2] = int32(h2)
|
|
||||||
h[3] = int32(h3)
|
|
||||||
h[4] = int32(h4)
|
|
||||||
h[5] = int32(h5)
|
|
||||||
h[6] = int32(h6)
|
|
||||||
h[7] = int32(h7)
|
|
||||||
h[8] = int32(h8)
|
|
||||||
h[9] = int32(h9)
|
|
||||||
}
|
|
||||||
|
|
||||||
// feMul121666 calculates h = f * 121666. Can overlap h with f.
|
|
||||||
//
|
|
||||||
// Preconditions:
|
|
||||||
// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
|
|
||||||
//
|
|
||||||
// Postconditions:
|
|
||||||
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|
|
||||||
func feMul121666(h, f *fieldElement) {
|
|
||||||
h0 := int64(f[0]) * 121666
|
|
||||||
h1 := int64(f[1]) * 121666
|
|
||||||
h2 := int64(f[2]) * 121666
|
|
||||||
h3 := int64(f[3]) * 121666
|
|
||||||
h4 := int64(f[4]) * 121666
|
|
||||||
h5 := int64(f[5]) * 121666
|
|
||||||
h6 := int64(f[6]) * 121666
|
|
||||||
h7 := int64(f[7]) * 121666
|
|
||||||
h8 := int64(f[8]) * 121666
|
|
||||||
h9 := int64(f[9]) * 121666
|
|
||||||
var carry [10]int64
|
|
||||||
|
|
||||||
carry[9] = (h9 + (1 << 24)) >> 25
|
|
||||||
h0 += carry[9] * 19
|
|
||||||
h9 -= carry[9] << 25
|
|
||||||
carry[1] = (h1 + (1 << 24)) >> 25
|
|
||||||
h2 += carry[1]
|
|
||||||
h1 -= carry[1] << 25
|
|
||||||
carry[3] = (h3 + (1 << 24)) >> 25
|
|
||||||
h4 += carry[3]
|
|
||||||
h3 -= carry[3] << 25
|
|
||||||
carry[5] = (h5 + (1 << 24)) >> 25
|
|
||||||
h6 += carry[5]
|
|
||||||
h5 -= carry[5] << 25
|
|
||||||
carry[7] = (h7 + (1 << 24)) >> 25
|
|
||||||
h8 += carry[7]
|
|
||||||
h7 -= carry[7] << 25
|
|
||||||
|
|
||||||
carry[0] = (h0 + (1 << 25)) >> 26
|
|
||||||
h1 += carry[0]
|
|
||||||
h0 -= carry[0] << 26
|
|
||||||
carry[2] = (h2 + (1 << 25)) >> 26
|
|
||||||
h3 += carry[2]
|
|
||||||
h2 -= carry[2] << 26
|
|
||||||
carry[4] = (h4 + (1 << 25)) >> 26
|
|
||||||
h5 += carry[4]
|
|
||||||
h4 -= carry[4] << 26
|
|
||||||
carry[6] = (h6 + (1 << 25)) >> 26
|
|
||||||
h7 += carry[6]
|
|
||||||
h6 -= carry[6] << 26
|
|
||||||
carry[8] = (h8 + (1 << 25)) >> 26
|
|
||||||
h9 += carry[8]
|
|
||||||
h8 -= carry[8] << 26
|
|
||||||
|
|
||||||
h[0] = int32(h0)
|
|
||||||
h[1] = int32(h1)
|
|
||||||
h[2] = int32(h2)
|
|
||||||
h[3] = int32(h3)
|
|
||||||
h[4] = int32(h4)
|
|
||||||
h[5] = int32(h5)
|
|
||||||
h[6] = int32(h6)
|
|
||||||
h[7] = int32(h7)
|
|
||||||
h[8] = int32(h8)
|
|
||||||
h[9] = int32(h9)
|
|
||||||
}
|
|
||||||
|
|
||||||
// feInvert sets out = z^-1.
|
|
||||||
func feInvert(out, z *fieldElement) {
|
|
||||||
var t0, t1, t2, t3 fieldElement
|
|
||||||
var i int
|
|
||||||
|
|
||||||
feSquare(&t0, z)
|
|
||||||
for i = 1; i < 1; i++ {
|
|
||||||
feSquare(&t0, &t0)
|
|
||||||
}
|
|
||||||
feSquare(&t1, &t0)
|
|
||||||
for i = 1; i < 2; i++ {
|
|
||||||
feSquare(&t1, &t1)
|
|
||||||
}
|
|
||||||
feMul(&t1, z, &t1)
|
|
||||||
feMul(&t0, &t0, &t1)
|
|
||||||
feSquare(&t2, &t0)
|
|
||||||
for i = 1; i < 1; i++ {
|
|
||||||
feSquare(&t2, &t2)
|
|
||||||
}
|
|
||||||
feMul(&t1, &t1, &t2)
|
|
||||||
feSquare(&t2, &t1)
|
|
||||||
for i = 1; i < 5; i++ {
|
|
||||||
feSquare(&t2, &t2)
|
|
||||||
}
|
|
||||||
feMul(&t1, &t2, &t1)
|
|
||||||
feSquare(&t2, &t1)
|
|
||||||
for i = 1; i < 10; i++ {
|
|
||||||
feSquare(&t2, &t2)
|
|
||||||
}
|
|
||||||
feMul(&t2, &t2, &t1)
|
|
||||||
feSquare(&t3, &t2)
|
|
||||||
for i = 1; i < 20; i++ {
|
|
||||||
feSquare(&t3, &t3)
|
|
||||||
}
|
|
||||||
feMul(&t2, &t3, &t2)
|
|
||||||
feSquare(&t2, &t2)
|
|
||||||
for i = 1; i < 10; i++ {
|
|
||||||
feSquare(&t2, &t2)
|
|
||||||
}
|
|
||||||
feMul(&t1, &t2, &t1)
|
|
||||||
feSquare(&t2, &t1)
|
|
||||||
for i = 1; i < 50; i++ {
|
|
||||||
feSquare(&t2, &t2)
|
|
||||||
}
|
|
||||||
feMul(&t2, &t2, &t1)
|
|
||||||
feSquare(&t3, &t2)
|
|
||||||
for i = 1; i < 100; i++ {
|
|
||||||
feSquare(&t3, &t3)
|
|
||||||
}
|
|
||||||
feMul(&t2, &t3, &t2)
|
|
||||||
feSquare(&t2, &t2)
|
|
||||||
for i = 1; i < 50; i++ {
|
|
||||||
feSquare(&t2, &t2)
|
|
||||||
}
|
|
||||||
feMul(&t1, &t2, &t1)
|
|
||||||
feSquare(&t1, &t1)
|
|
||||||
for i = 1; i < 5; i++ {
|
|
||||||
feSquare(&t1, &t1)
|
|
||||||
}
|
|
||||||
feMul(out, &t1, &t0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func scalarMult(out, in, base *[32]byte) {
|
|
||||||
var e [32]byte
|
|
||||||
|
|
||||||
copy(e[:], in[:])
|
|
||||||
e[0] &= 248
|
|
||||||
e[31] &= 127
|
|
||||||
e[31] |= 64
|
|
||||||
|
|
||||||
var x1, x2, z2, x3, z3, tmp0, tmp1 fieldElement
|
|
||||||
feFromBytes(&x1, base)
|
|
||||||
feOne(&x2)
|
|
||||||
feCopy(&x3, &x1)
|
|
||||||
feOne(&z3)
|
|
||||||
|
|
||||||
swap := int32(0)
|
|
||||||
for pos := 254; pos >= 0; pos-- {
|
|
||||||
b := e[pos/8] >> uint(pos&7)
|
|
||||||
b &= 1
|
|
||||||
swap ^= int32(b)
|
|
||||||
feCSwap(&x2, &x3, swap)
|
|
||||||
feCSwap(&z2, &z3, swap)
|
|
||||||
swap = int32(b)
|
|
||||||
|
|
||||||
feSub(&tmp0, &x3, &z3)
|
|
||||||
feSub(&tmp1, &x2, &z2)
|
|
||||||
feAdd(&x2, &x2, &z2)
|
|
||||||
feAdd(&z2, &x3, &z3)
|
|
||||||
feMul(&z3, &tmp0, &x2)
|
|
||||||
feMul(&z2, &z2, &tmp1)
|
|
||||||
feSquare(&tmp0, &tmp1)
|
|
||||||
feSquare(&tmp1, &x2)
|
|
||||||
feAdd(&x3, &z3, &z2)
|
|
||||||
feSub(&z2, &z3, &z2)
|
|
||||||
feMul(&x2, &tmp1, &tmp0)
|
|
||||||
feSub(&tmp1, &tmp1, &tmp0)
|
|
||||||
feSquare(&z2, &z2)
|
|
||||||
feMul121666(&z3, &tmp1)
|
|
||||||
feSquare(&x3, &x3)
|
|
||||||
feAdd(&tmp0, &tmp0, &z3)
|
|
||||||
feMul(&z3, &x1, &z2)
|
|
||||||
feMul(&z2, &tmp1, &tmp0)
|
|
||||||
}
|
|
||||||
|
|
||||||
feCSwap(&x2, &x3, swap)
|
|
||||||
feCSwap(&z2, &z3, swap)
|
|
||||||
|
|
||||||
feInvert(&z2, &z2)
|
|
||||||
feMul(&x2, &x2, &z2)
|
|
||||||
feToBytes(out, &x2)
|
|
||||||
}
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package curve25519 provides an implementation of scalar multiplication on
|
|
||||||
// the elliptic curve known as curve25519. See http://cr.yp.to/ecdh.html
|
|
||||||
package curve25519 // import "golang.org/x/crypto/curve25519"
|
|
||||||
|
|
||||||
// basePoint is the x coordinate of the generator of the curve.
|
|
||||||
var basePoint = [32]byte{9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
|
||||||
|
|
||||||
// ScalarMult sets dst to the product in*base where dst and base are the x
|
|
||||||
// coordinates of group points and all values are in little-endian form.
|
|
||||||
func ScalarMult(dst, in, base *[32]byte) {
|
|
||||||
scalarMult(dst, in, base)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ScalarBaseMult sets dst to the product in*base where dst and base are the x
|
|
||||||
// coordinates of group points, base is the standard generator and all values
|
|
||||||
// are in little-endian form.
|
|
||||||
func ScalarBaseMult(dst, in *[32]byte) {
|
|
||||||
ScalarMult(dst, in, &basePoint)
|
|
||||||
}
|
|
||||||
-73
@@ -1,73 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This code was translated into a form compatible with 6a from the public
|
|
||||||
// domain sources in SUPERCOP: http://bench.cr.yp.to/supercop.html
|
|
||||||
|
|
||||||
// +build amd64,!gccgo,!appengine
|
|
||||||
|
|
||||||
#include "const_amd64.h"
|
|
||||||
|
|
||||||
// func freeze(inout *[5]uint64)
|
|
||||||
TEXT ·freeze(SB),7,$0-8
|
|
||||||
MOVQ inout+0(FP), DI
|
|
||||||
|
|
||||||
MOVQ 0(DI),SI
|
|
||||||
MOVQ 8(DI),DX
|
|
||||||
MOVQ 16(DI),CX
|
|
||||||
MOVQ 24(DI),R8
|
|
||||||
MOVQ 32(DI),R9
|
|
||||||
MOVQ $REDMASK51,AX
|
|
||||||
MOVQ AX,R10
|
|
||||||
SUBQ $18,R10
|
|
||||||
MOVQ $3,R11
|
|
||||||
REDUCELOOP:
|
|
||||||
MOVQ SI,R12
|
|
||||||
SHRQ $51,R12
|
|
||||||
ANDQ AX,SI
|
|
||||||
ADDQ R12,DX
|
|
||||||
MOVQ DX,R12
|
|
||||||
SHRQ $51,R12
|
|
||||||
ANDQ AX,DX
|
|
||||||
ADDQ R12,CX
|
|
||||||
MOVQ CX,R12
|
|
||||||
SHRQ $51,R12
|
|
||||||
ANDQ AX,CX
|
|
||||||
ADDQ R12,R8
|
|
||||||
MOVQ R8,R12
|
|
||||||
SHRQ $51,R12
|
|
||||||
ANDQ AX,R8
|
|
||||||
ADDQ R12,R9
|
|
||||||
MOVQ R9,R12
|
|
||||||
SHRQ $51,R12
|
|
||||||
ANDQ AX,R9
|
|
||||||
IMUL3Q $19,R12,R12
|
|
||||||
ADDQ R12,SI
|
|
||||||
SUBQ $1,R11
|
|
||||||
JA REDUCELOOP
|
|
||||||
MOVQ $1,R12
|
|
||||||
CMPQ R10,SI
|
|
||||||
CMOVQLT R11,R12
|
|
||||||
CMPQ AX,DX
|
|
||||||
CMOVQNE R11,R12
|
|
||||||
CMPQ AX,CX
|
|
||||||
CMOVQNE R11,R12
|
|
||||||
CMPQ AX,R8
|
|
||||||
CMOVQNE R11,R12
|
|
||||||
CMPQ AX,R9
|
|
||||||
CMOVQNE R11,R12
|
|
||||||
NEGQ R12
|
|
||||||
ANDQ R12,AX
|
|
||||||
ANDQ R12,R10
|
|
||||||
SUBQ R10,SI
|
|
||||||
SUBQ AX,DX
|
|
||||||
SUBQ AX,CX
|
|
||||||
SUBQ AX,R8
|
|
||||||
SUBQ AX,R9
|
|
||||||
MOVQ SI,0(DI)
|
|
||||||
MOVQ DX,8(DI)
|
|
||||||
MOVQ CX,16(DI)
|
|
||||||
MOVQ R8,24(DI)
|
|
||||||
MOVQ R9,32(DI)
|
|
||||||
RET
|
|
||||||
-1377
File diff suppressed because it is too large
Load Diff
-240
@@ -1,240 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build amd64,!gccgo,!appengine
|
|
||||||
|
|
||||||
package curve25519
|
|
||||||
|
|
||||||
// These functions are implemented in the .s files. The names of the functions
|
|
||||||
// in the rest of the file are also taken from the SUPERCOP sources to help
|
|
||||||
// people following along.
|
|
||||||
|
|
||||||
//go:noescape
|
|
||||||
|
|
||||||
func cswap(inout *[5]uint64, v uint64)
|
|
||||||
|
|
||||||
//go:noescape
|
|
||||||
|
|
||||||
func ladderstep(inout *[5][5]uint64)
|
|
||||||
|
|
||||||
//go:noescape
|
|
||||||
|
|
||||||
func freeze(inout *[5]uint64)
|
|
||||||
|
|
||||||
//go:noescape
|
|
||||||
|
|
||||||
func mul(dest, a, b *[5]uint64)
|
|
||||||
|
|
||||||
//go:noescape
|
|
||||||
|
|
||||||
func square(out, in *[5]uint64)
|
|
||||||
|
|
||||||
// mladder uses a Montgomery ladder to calculate (xr/zr) *= s.
|
|
||||||
func mladder(xr, zr *[5]uint64, s *[32]byte) {
|
|
||||||
var work [5][5]uint64
|
|
||||||
|
|
||||||
work[0] = *xr
|
|
||||||
setint(&work[1], 1)
|
|
||||||
setint(&work[2], 0)
|
|
||||||
work[3] = *xr
|
|
||||||
setint(&work[4], 1)
|
|
||||||
|
|
||||||
j := uint(6)
|
|
||||||
var prevbit byte
|
|
||||||
|
|
||||||
for i := 31; i >= 0; i-- {
|
|
||||||
for j < 8 {
|
|
||||||
bit := ((*s)[i] >> j) & 1
|
|
||||||
swap := bit ^ prevbit
|
|
||||||
prevbit = bit
|
|
||||||
cswap(&work[1], uint64(swap))
|
|
||||||
ladderstep(&work)
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
j = 7
|
|
||||||
}
|
|
||||||
|
|
||||||
*xr = work[1]
|
|
||||||
*zr = work[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
func scalarMult(out, in, base *[32]byte) {
|
|
||||||
var e [32]byte
|
|
||||||
copy(e[:], (*in)[:])
|
|
||||||
e[0] &= 248
|
|
||||||
e[31] &= 127
|
|
||||||
e[31] |= 64
|
|
||||||
|
|
||||||
var t, z [5]uint64
|
|
||||||
unpack(&t, base)
|
|
||||||
mladder(&t, &z, &e)
|
|
||||||
invert(&z, &z)
|
|
||||||
mul(&t, &t, &z)
|
|
||||||
pack(out, &t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setint(r *[5]uint64, v uint64) {
|
|
||||||
r[0] = v
|
|
||||||
r[1] = 0
|
|
||||||
r[2] = 0
|
|
||||||
r[3] = 0
|
|
||||||
r[4] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpack sets r = x where r consists of 5, 51-bit limbs in little-endian
|
|
||||||
// order.
|
|
||||||
func unpack(r *[5]uint64, x *[32]byte) {
|
|
||||||
r[0] = uint64(x[0]) |
|
|
||||||
uint64(x[1])<<8 |
|
|
||||||
uint64(x[2])<<16 |
|
|
||||||
uint64(x[3])<<24 |
|
|
||||||
uint64(x[4])<<32 |
|
|
||||||
uint64(x[5])<<40 |
|
|
||||||
uint64(x[6]&7)<<48
|
|
||||||
|
|
||||||
r[1] = uint64(x[6])>>3 |
|
|
||||||
uint64(x[7])<<5 |
|
|
||||||
uint64(x[8])<<13 |
|
|
||||||
uint64(x[9])<<21 |
|
|
||||||
uint64(x[10])<<29 |
|
|
||||||
uint64(x[11])<<37 |
|
|
||||||
uint64(x[12]&63)<<45
|
|
||||||
|
|
||||||
r[2] = uint64(x[12])>>6 |
|
|
||||||
uint64(x[13])<<2 |
|
|
||||||
uint64(x[14])<<10 |
|
|
||||||
uint64(x[15])<<18 |
|
|
||||||
uint64(x[16])<<26 |
|
|
||||||
uint64(x[17])<<34 |
|
|
||||||
uint64(x[18])<<42 |
|
|
||||||
uint64(x[19]&1)<<50
|
|
||||||
|
|
||||||
r[3] = uint64(x[19])>>1 |
|
|
||||||
uint64(x[20])<<7 |
|
|
||||||
uint64(x[21])<<15 |
|
|
||||||
uint64(x[22])<<23 |
|
|
||||||
uint64(x[23])<<31 |
|
|
||||||
uint64(x[24])<<39 |
|
|
||||||
uint64(x[25]&15)<<47
|
|
||||||
|
|
||||||
r[4] = uint64(x[25])>>4 |
|
|
||||||
uint64(x[26])<<4 |
|
|
||||||
uint64(x[27])<<12 |
|
|
||||||
uint64(x[28])<<20 |
|
|
||||||
uint64(x[29])<<28 |
|
|
||||||
uint64(x[30])<<36 |
|
|
||||||
uint64(x[31]&127)<<44
|
|
||||||
}
|
|
||||||
|
|
||||||
// pack sets out = x where out is the usual, little-endian form of the 5,
|
|
||||||
// 51-bit limbs in x.
|
|
||||||
func pack(out *[32]byte, x *[5]uint64) {
|
|
||||||
t := *x
|
|
||||||
freeze(&t)
|
|
||||||
|
|
||||||
out[0] = byte(t[0])
|
|
||||||
out[1] = byte(t[0] >> 8)
|
|
||||||
out[2] = byte(t[0] >> 16)
|
|
||||||
out[3] = byte(t[0] >> 24)
|
|
||||||
out[4] = byte(t[0] >> 32)
|
|
||||||
out[5] = byte(t[0] >> 40)
|
|
||||||
out[6] = byte(t[0] >> 48)
|
|
||||||
|
|
||||||
out[6] ^= byte(t[1]<<3) & 0xf8
|
|
||||||
out[7] = byte(t[1] >> 5)
|
|
||||||
out[8] = byte(t[1] >> 13)
|
|
||||||
out[9] = byte(t[1] >> 21)
|
|
||||||
out[10] = byte(t[1] >> 29)
|
|
||||||
out[11] = byte(t[1] >> 37)
|
|
||||||
out[12] = byte(t[1] >> 45)
|
|
||||||
|
|
||||||
out[12] ^= byte(t[2]<<6) & 0xc0
|
|
||||||
out[13] = byte(t[2] >> 2)
|
|
||||||
out[14] = byte(t[2] >> 10)
|
|
||||||
out[15] = byte(t[2] >> 18)
|
|
||||||
out[16] = byte(t[2] >> 26)
|
|
||||||
out[17] = byte(t[2] >> 34)
|
|
||||||
out[18] = byte(t[2] >> 42)
|
|
||||||
out[19] = byte(t[2] >> 50)
|
|
||||||
|
|
||||||
out[19] ^= byte(t[3]<<1) & 0xfe
|
|
||||||
out[20] = byte(t[3] >> 7)
|
|
||||||
out[21] = byte(t[3] >> 15)
|
|
||||||
out[22] = byte(t[3] >> 23)
|
|
||||||
out[23] = byte(t[3] >> 31)
|
|
||||||
out[24] = byte(t[3] >> 39)
|
|
||||||
out[25] = byte(t[3] >> 47)
|
|
||||||
|
|
||||||
out[25] ^= byte(t[4]<<4) & 0xf0
|
|
||||||
out[26] = byte(t[4] >> 4)
|
|
||||||
out[27] = byte(t[4] >> 12)
|
|
||||||
out[28] = byte(t[4] >> 20)
|
|
||||||
out[29] = byte(t[4] >> 28)
|
|
||||||
out[30] = byte(t[4] >> 36)
|
|
||||||
out[31] = byte(t[4] >> 44)
|
|
||||||
}
|
|
||||||
|
|
||||||
// invert calculates r = x^-1 mod p using Fermat's little theorem.
|
|
||||||
func invert(r *[5]uint64, x *[5]uint64) {
|
|
||||||
var z2, z9, z11, z2_5_0, z2_10_0, z2_20_0, z2_50_0, z2_100_0, t [5]uint64
|
|
||||||
|
|
||||||
square(&z2, x) /* 2 */
|
|
||||||
square(&t, &z2) /* 4 */
|
|
||||||
square(&t, &t) /* 8 */
|
|
||||||
mul(&z9, &t, x) /* 9 */
|
|
||||||
mul(&z11, &z9, &z2) /* 11 */
|
|
||||||
square(&t, &z11) /* 22 */
|
|
||||||
mul(&z2_5_0, &t, &z9) /* 2^5 - 2^0 = 31 */
|
|
||||||
|
|
||||||
square(&t, &z2_5_0) /* 2^6 - 2^1 */
|
|
||||||
for i := 1; i < 5; i++ { /* 2^20 - 2^10 */
|
|
||||||
square(&t, &t)
|
|
||||||
}
|
|
||||||
mul(&z2_10_0, &t, &z2_5_0) /* 2^10 - 2^0 */
|
|
||||||
|
|
||||||
square(&t, &z2_10_0) /* 2^11 - 2^1 */
|
|
||||||
for i := 1; i < 10; i++ { /* 2^20 - 2^10 */
|
|
||||||
square(&t, &t)
|
|
||||||
}
|
|
||||||
mul(&z2_20_0, &t, &z2_10_0) /* 2^20 - 2^0 */
|
|
||||||
|
|
||||||
square(&t, &z2_20_0) /* 2^21 - 2^1 */
|
|
||||||
for i := 1; i < 20; i++ { /* 2^40 - 2^20 */
|
|
||||||
square(&t, &t)
|
|
||||||
}
|
|
||||||
mul(&t, &t, &z2_20_0) /* 2^40 - 2^0 */
|
|
||||||
|
|
||||||
square(&t, &t) /* 2^41 - 2^1 */
|
|
||||||
for i := 1; i < 10; i++ { /* 2^50 - 2^10 */
|
|
||||||
square(&t, &t)
|
|
||||||
}
|
|
||||||
mul(&z2_50_0, &t, &z2_10_0) /* 2^50 - 2^0 */
|
|
||||||
|
|
||||||
square(&t, &z2_50_0) /* 2^51 - 2^1 */
|
|
||||||
for i := 1; i < 50; i++ { /* 2^100 - 2^50 */
|
|
||||||
square(&t, &t)
|
|
||||||
}
|
|
||||||
mul(&z2_100_0, &t, &z2_50_0) /* 2^100 - 2^0 */
|
|
||||||
|
|
||||||
square(&t, &z2_100_0) /* 2^101 - 2^1 */
|
|
||||||
for i := 1; i < 100; i++ { /* 2^200 - 2^100 */
|
|
||||||
square(&t, &t)
|
|
||||||
}
|
|
||||||
mul(&t, &t, &z2_100_0) /* 2^200 - 2^0 */
|
|
||||||
|
|
||||||
square(&t, &t) /* 2^201 - 2^1 */
|
|
||||||
for i := 1; i < 50; i++ { /* 2^250 - 2^50 */
|
|
||||||
square(&t, &t)
|
|
||||||
}
|
|
||||||
mul(&t, &t, &z2_50_0) /* 2^250 - 2^0 */
|
|
||||||
|
|
||||||
square(&t, &t) /* 2^251 - 2^1 */
|
|
||||||
square(&t, &t) /* 2^252 - 2^2 */
|
|
||||||
square(&t, &t) /* 2^253 - 2^3 */
|
|
||||||
|
|
||||||
square(&t, &t) /* 2^254 - 2^4 */
|
|
||||||
|
|
||||||
square(&t, &t) /* 2^255 - 2^5 */
|
|
||||||
mul(r, &t, &z11) /* 2^255 - 21 */
|
|
||||||
}
|
|
||||||
-169
@@ -1,169 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This code was translated into a form compatible with 6a from the public
|
|
||||||
// domain sources in SUPERCOP: http://bench.cr.yp.to/supercop.html
|
|
||||||
|
|
||||||
// +build amd64,!gccgo,!appengine
|
|
||||||
|
|
||||||
#include "const_amd64.h"
|
|
||||||
|
|
||||||
// func mul(dest, a, b *[5]uint64)
|
|
||||||
TEXT ·mul(SB),0,$16-24
|
|
||||||
MOVQ dest+0(FP), DI
|
|
||||||
MOVQ a+8(FP), SI
|
|
||||||
MOVQ b+16(FP), DX
|
|
||||||
|
|
||||||
MOVQ DX,CX
|
|
||||||
MOVQ 24(SI),DX
|
|
||||||
IMUL3Q $19,DX,AX
|
|
||||||
MOVQ AX,0(SP)
|
|
||||||
MULQ 16(CX)
|
|
||||||
MOVQ AX,R8
|
|
||||||
MOVQ DX,R9
|
|
||||||
MOVQ 32(SI),DX
|
|
||||||
IMUL3Q $19,DX,AX
|
|
||||||
MOVQ AX,8(SP)
|
|
||||||
MULQ 8(CX)
|
|
||||||
ADDQ AX,R8
|
|
||||||
ADCQ DX,R9
|
|
||||||
MOVQ 0(SI),AX
|
|
||||||
MULQ 0(CX)
|
|
||||||
ADDQ AX,R8
|
|
||||||
ADCQ DX,R9
|
|
||||||
MOVQ 0(SI),AX
|
|
||||||
MULQ 8(CX)
|
|
||||||
MOVQ AX,R10
|
|
||||||
MOVQ DX,R11
|
|
||||||
MOVQ 0(SI),AX
|
|
||||||
MULQ 16(CX)
|
|
||||||
MOVQ AX,R12
|
|
||||||
MOVQ DX,R13
|
|
||||||
MOVQ 0(SI),AX
|
|
||||||
MULQ 24(CX)
|
|
||||||
MOVQ AX,R14
|
|
||||||
MOVQ DX,R15
|
|
||||||
MOVQ 0(SI),AX
|
|
||||||
MULQ 32(CX)
|
|
||||||
MOVQ AX,BX
|
|
||||||
MOVQ DX,BP
|
|
||||||
MOVQ 8(SI),AX
|
|
||||||
MULQ 0(CX)
|
|
||||||
ADDQ AX,R10
|
|
||||||
ADCQ DX,R11
|
|
||||||
MOVQ 8(SI),AX
|
|
||||||
MULQ 8(CX)
|
|
||||||
ADDQ AX,R12
|
|
||||||
ADCQ DX,R13
|
|
||||||
MOVQ 8(SI),AX
|
|
||||||
MULQ 16(CX)
|
|
||||||
ADDQ AX,R14
|
|
||||||
ADCQ DX,R15
|
|
||||||
MOVQ 8(SI),AX
|
|
||||||
MULQ 24(CX)
|
|
||||||
ADDQ AX,BX
|
|
||||||
ADCQ DX,BP
|
|
||||||
MOVQ 8(SI),DX
|
|
||||||
IMUL3Q $19,DX,AX
|
|
||||||
MULQ 32(CX)
|
|
||||||
ADDQ AX,R8
|
|
||||||
ADCQ DX,R9
|
|
||||||
MOVQ 16(SI),AX
|
|
||||||
MULQ 0(CX)
|
|
||||||
ADDQ AX,R12
|
|
||||||
ADCQ DX,R13
|
|
||||||
MOVQ 16(SI),AX
|
|
||||||
MULQ 8(CX)
|
|
||||||
ADDQ AX,R14
|
|
||||||
ADCQ DX,R15
|
|
||||||
MOVQ 16(SI),AX
|
|
||||||
MULQ 16(CX)
|
|
||||||
ADDQ AX,BX
|
|
||||||
ADCQ DX,BP
|
|
||||||
MOVQ 16(SI),DX
|
|
||||||
IMUL3Q $19,DX,AX
|
|
||||||
MULQ 24(CX)
|
|
||||||
ADDQ AX,R8
|
|
||||||
ADCQ DX,R9
|
|
||||||
MOVQ 16(SI),DX
|
|
||||||
IMUL3Q $19,DX,AX
|
|
||||||
MULQ 32(CX)
|
|
||||||
ADDQ AX,R10
|
|
||||||
ADCQ DX,R11
|
|
||||||
MOVQ 24(SI),AX
|
|
||||||
MULQ 0(CX)
|
|
||||||
ADDQ AX,R14
|
|
||||||
ADCQ DX,R15
|
|
||||||
MOVQ 24(SI),AX
|
|
||||||
MULQ 8(CX)
|
|
||||||
ADDQ AX,BX
|
|
||||||
ADCQ DX,BP
|
|
||||||
MOVQ 0(SP),AX
|
|
||||||
MULQ 24(CX)
|
|
||||||
ADDQ AX,R10
|
|
||||||
ADCQ DX,R11
|
|
||||||
MOVQ 0(SP),AX
|
|
||||||
MULQ 32(CX)
|
|
||||||
ADDQ AX,R12
|
|
||||||
ADCQ DX,R13
|
|
||||||
MOVQ 32(SI),AX
|
|
||||||
MULQ 0(CX)
|
|
||||||
ADDQ AX,BX
|
|
||||||
ADCQ DX,BP
|
|
||||||
MOVQ 8(SP),AX
|
|
||||||
MULQ 16(CX)
|
|
||||||
ADDQ AX,R10
|
|
||||||
ADCQ DX,R11
|
|
||||||
MOVQ 8(SP),AX
|
|
||||||
MULQ 24(CX)
|
|
||||||
ADDQ AX,R12
|
|
||||||
ADCQ DX,R13
|
|
||||||
MOVQ 8(SP),AX
|
|
||||||
MULQ 32(CX)
|
|
||||||
ADDQ AX,R14
|
|
||||||
ADCQ DX,R15
|
|
||||||
MOVQ $REDMASK51,SI
|
|
||||||
SHLQ $13,R9:R8
|
|
||||||
ANDQ SI,R8
|
|
||||||
SHLQ $13,R11:R10
|
|
||||||
ANDQ SI,R10
|
|
||||||
ADDQ R9,R10
|
|
||||||
SHLQ $13,R13:R12
|
|
||||||
ANDQ SI,R12
|
|
||||||
ADDQ R11,R12
|
|
||||||
SHLQ $13,R15:R14
|
|
||||||
ANDQ SI,R14
|
|
||||||
ADDQ R13,R14
|
|
||||||
SHLQ $13,BP:BX
|
|
||||||
ANDQ SI,BX
|
|
||||||
ADDQ R15,BX
|
|
||||||
IMUL3Q $19,BP,DX
|
|
||||||
ADDQ DX,R8
|
|
||||||
MOVQ R8,DX
|
|
||||||
SHRQ $51,DX
|
|
||||||
ADDQ R10,DX
|
|
||||||
MOVQ DX,CX
|
|
||||||
SHRQ $51,DX
|
|
||||||
ANDQ SI,R8
|
|
||||||
ADDQ R12,DX
|
|
||||||
MOVQ DX,R9
|
|
||||||
SHRQ $51,DX
|
|
||||||
ANDQ SI,CX
|
|
||||||
ADDQ R14,DX
|
|
||||||
MOVQ DX,AX
|
|
||||||
SHRQ $51,DX
|
|
||||||
ANDQ SI,R9
|
|
||||||
ADDQ BX,DX
|
|
||||||
MOVQ DX,R10
|
|
||||||
SHRQ $51,DX
|
|
||||||
ANDQ SI,AX
|
|
||||||
IMUL3Q $19,DX,DX
|
|
||||||
ADDQ DX,R8
|
|
||||||
ANDQ SI,R10
|
|
||||||
MOVQ R8,0(DI)
|
|
||||||
MOVQ CX,8(DI)
|
|
||||||
MOVQ R9,16(DI)
|
|
||||||
MOVQ AX,24(DI)
|
|
||||||
MOVQ R10,32(DI)
|
|
||||||
RET
|
|
||||||
-132
@@ -1,132 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This code was translated into a form compatible with 6a from the public
|
|
||||||
// domain sources in SUPERCOP: http://bench.cr.yp.to/supercop.html
|
|
||||||
|
|
||||||
// +build amd64,!gccgo,!appengine
|
|
||||||
|
|
||||||
#include "const_amd64.h"
|
|
||||||
|
|
||||||
// func square(out, in *[5]uint64)
|
|
||||||
TEXT ·square(SB),7,$0-16
|
|
||||||
MOVQ out+0(FP), DI
|
|
||||||
MOVQ in+8(FP), SI
|
|
||||||
|
|
||||||
MOVQ 0(SI),AX
|
|
||||||
MULQ 0(SI)
|
|
||||||
MOVQ AX,CX
|
|
||||||
MOVQ DX,R8
|
|
||||||
MOVQ 0(SI),AX
|
|
||||||
SHLQ $1,AX
|
|
||||||
MULQ 8(SI)
|
|
||||||
MOVQ AX,R9
|
|
||||||
MOVQ DX,R10
|
|
||||||
MOVQ 0(SI),AX
|
|
||||||
SHLQ $1,AX
|
|
||||||
MULQ 16(SI)
|
|
||||||
MOVQ AX,R11
|
|
||||||
MOVQ DX,R12
|
|
||||||
MOVQ 0(SI),AX
|
|
||||||
SHLQ $1,AX
|
|
||||||
MULQ 24(SI)
|
|
||||||
MOVQ AX,R13
|
|
||||||
MOVQ DX,R14
|
|
||||||
MOVQ 0(SI),AX
|
|
||||||
SHLQ $1,AX
|
|
||||||
MULQ 32(SI)
|
|
||||||
MOVQ AX,R15
|
|
||||||
MOVQ DX,BX
|
|
||||||
MOVQ 8(SI),AX
|
|
||||||
MULQ 8(SI)
|
|
||||||
ADDQ AX,R11
|
|
||||||
ADCQ DX,R12
|
|
||||||
MOVQ 8(SI),AX
|
|
||||||
SHLQ $1,AX
|
|
||||||
MULQ 16(SI)
|
|
||||||
ADDQ AX,R13
|
|
||||||
ADCQ DX,R14
|
|
||||||
MOVQ 8(SI),AX
|
|
||||||
SHLQ $1,AX
|
|
||||||
MULQ 24(SI)
|
|
||||||
ADDQ AX,R15
|
|
||||||
ADCQ DX,BX
|
|
||||||
MOVQ 8(SI),DX
|
|
||||||
IMUL3Q $38,DX,AX
|
|
||||||
MULQ 32(SI)
|
|
||||||
ADDQ AX,CX
|
|
||||||
ADCQ DX,R8
|
|
||||||
MOVQ 16(SI),AX
|
|
||||||
MULQ 16(SI)
|
|
||||||
ADDQ AX,R15
|
|
||||||
ADCQ DX,BX
|
|
||||||
MOVQ 16(SI),DX
|
|
||||||
IMUL3Q $38,DX,AX
|
|
||||||
MULQ 24(SI)
|
|
||||||
ADDQ AX,CX
|
|
||||||
ADCQ DX,R8
|
|
||||||
MOVQ 16(SI),DX
|
|
||||||
IMUL3Q $38,DX,AX
|
|
||||||
MULQ 32(SI)
|
|
||||||
ADDQ AX,R9
|
|
||||||
ADCQ DX,R10
|
|
||||||
MOVQ 24(SI),DX
|
|
||||||
IMUL3Q $19,DX,AX
|
|
||||||
MULQ 24(SI)
|
|
||||||
ADDQ AX,R9
|
|
||||||
ADCQ DX,R10
|
|
||||||
MOVQ 24(SI),DX
|
|
||||||
IMUL3Q $38,DX,AX
|
|
||||||
MULQ 32(SI)
|
|
||||||
ADDQ AX,R11
|
|
||||||
ADCQ DX,R12
|
|
||||||
MOVQ 32(SI),DX
|
|
||||||
IMUL3Q $19,DX,AX
|
|
||||||
MULQ 32(SI)
|
|
||||||
ADDQ AX,R13
|
|
||||||
ADCQ DX,R14
|
|
||||||
MOVQ $REDMASK51,SI
|
|
||||||
SHLQ $13,R8:CX
|
|
||||||
ANDQ SI,CX
|
|
||||||
SHLQ $13,R10:R9
|
|
||||||
ANDQ SI,R9
|
|
||||||
ADDQ R8,R9
|
|
||||||
SHLQ $13,R12:R11
|
|
||||||
ANDQ SI,R11
|
|
||||||
ADDQ R10,R11
|
|
||||||
SHLQ $13,R14:R13
|
|
||||||
ANDQ SI,R13
|
|
||||||
ADDQ R12,R13
|
|
||||||
SHLQ $13,BX:R15
|
|
||||||
ANDQ SI,R15
|
|
||||||
ADDQ R14,R15
|
|
||||||
IMUL3Q $19,BX,DX
|
|
||||||
ADDQ DX,CX
|
|
||||||
MOVQ CX,DX
|
|
||||||
SHRQ $51,DX
|
|
||||||
ADDQ R9,DX
|
|
||||||
ANDQ SI,CX
|
|
||||||
MOVQ DX,R8
|
|
||||||
SHRQ $51,DX
|
|
||||||
ADDQ R11,DX
|
|
||||||
ANDQ SI,R8
|
|
||||||
MOVQ DX,R9
|
|
||||||
SHRQ $51,DX
|
|
||||||
ADDQ R13,DX
|
|
||||||
ANDQ SI,R9
|
|
||||||
MOVQ DX,AX
|
|
||||||
SHRQ $51,DX
|
|
||||||
ADDQ R15,DX
|
|
||||||
ANDQ SI,AX
|
|
||||||
MOVQ DX,R10
|
|
||||||
SHRQ $51,DX
|
|
||||||
IMUL3Q $19,DX,DX
|
|
||||||
ADDQ DX,CX
|
|
||||||
ANDQ SI,R10
|
|
||||||
MOVQ CX,0(DI)
|
|
||||||
MOVQ R8,8(DI)
|
|
||||||
MOVQ R9,16(DI)
|
|
||||||
MOVQ AX,24(DI)
|
|
||||||
MOVQ R10,32(DI)
|
|
||||||
RET
|
|
||||||
-181
@@ -1,181 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package ed25519 implements the Ed25519 signature algorithm. See
|
|
||||||
// http://ed25519.cr.yp.to/.
|
|
||||||
//
|
|
||||||
// These functions are also compatible with the “Ed25519” function defined in
|
|
||||||
// https://tools.ietf.org/html/draft-irtf-cfrg-eddsa-05.
|
|
||||||
package ed25519
|
|
||||||
|
|
||||||
// This code is a port of the public domain, “ref10” implementation of ed25519
|
|
||||||
// from SUPERCOP.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
cryptorand "crypto/rand"
|
|
||||||
"crypto/sha512"
|
|
||||||
"crypto/subtle"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ed25519/internal/edwards25519"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// PublicKeySize is the size, in bytes, of public keys as used in this package.
|
|
||||||
PublicKeySize = 32
|
|
||||||
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
|
|
||||||
PrivateKeySize = 64
|
|
||||||
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
|
|
||||||
SignatureSize = 64
|
|
||||||
)
|
|
||||||
|
|
||||||
// PublicKey is the type of Ed25519 public keys.
|
|
||||||
type PublicKey []byte
|
|
||||||
|
|
||||||
// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer.
|
|
||||||
type PrivateKey []byte
|
|
||||||
|
|
||||||
// Public returns the PublicKey corresponding to priv.
|
|
||||||
func (priv PrivateKey) Public() crypto.PublicKey {
|
|
||||||
publicKey := make([]byte, PublicKeySize)
|
|
||||||
copy(publicKey, priv[32:])
|
|
||||||
return PublicKey(publicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign signs the given message with priv.
|
|
||||||
// Ed25519 performs two passes over messages to be signed and therefore cannot
|
|
||||||
// handle pre-hashed messages. Thus opts.HashFunc() must return zero to
|
|
||||||
// indicate the message hasn't been hashed. This can be achieved by passing
|
|
||||||
// crypto.Hash(0) as the value for opts.
|
|
||||||
func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
|
||||||
if opts.HashFunc() != crypto.Hash(0) {
|
|
||||||
return nil, errors.New("ed25519: cannot sign hashed message")
|
|
||||||
}
|
|
||||||
|
|
||||||
return Sign(priv, message), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateKey generates a public/private key pair using entropy from rand.
|
|
||||||
// If rand is nil, crypto/rand.Reader will be used.
|
|
||||||
func GenerateKey(rand io.Reader) (publicKey PublicKey, privateKey PrivateKey, err error) {
|
|
||||||
if rand == nil {
|
|
||||||
rand = cryptorand.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
privateKey = make([]byte, PrivateKeySize)
|
|
||||||
publicKey = make([]byte, PublicKeySize)
|
|
||||||
_, err = io.ReadFull(rand, privateKey[:32])
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
digest := sha512.Sum512(privateKey[:32])
|
|
||||||
digest[0] &= 248
|
|
||||||
digest[31] &= 127
|
|
||||||
digest[31] |= 64
|
|
||||||
|
|
||||||
var A edwards25519.ExtendedGroupElement
|
|
||||||
var hBytes [32]byte
|
|
||||||
copy(hBytes[:], digest[:])
|
|
||||||
edwards25519.GeScalarMultBase(&A, &hBytes)
|
|
||||||
var publicKeyBytes [32]byte
|
|
||||||
A.ToBytes(&publicKeyBytes)
|
|
||||||
|
|
||||||
copy(privateKey[32:], publicKeyBytes[:])
|
|
||||||
copy(publicKey, publicKeyBytes[:])
|
|
||||||
|
|
||||||
return publicKey, privateKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign signs the message with privateKey and returns a signature. It will
|
|
||||||
// panic if len(privateKey) is not PrivateKeySize.
|
|
||||||
func Sign(privateKey PrivateKey, message []byte) []byte {
|
|
||||||
if l := len(privateKey); l != PrivateKeySize {
|
|
||||||
panic("ed25519: bad private key length: " + strconv.Itoa(l))
|
|
||||||
}
|
|
||||||
|
|
||||||
h := sha512.New()
|
|
||||||
h.Write(privateKey[:32])
|
|
||||||
|
|
||||||
var digest1, messageDigest, hramDigest [64]byte
|
|
||||||
var expandedSecretKey [32]byte
|
|
||||||
h.Sum(digest1[:0])
|
|
||||||
copy(expandedSecretKey[:], digest1[:])
|
|
||||||
expandedSecretKey[0] &= 248
|
|
||||||
expandedSecretKey[31] &= 63
|
|
||||||
expandedSecretKey[31] |= 64
|
|
||||||
|
|
||||||
h.Reset()
|
|
||||||
h.Write(digest1[32:])
|
|
||||||
h.Write(message)
|
|
||||||
h.Sum(messageDigest[:0])
|
|
||||||
|
|
||||||
var messageDigestReduced [32]byte
|
|
||||||
edwards25519.ScReduce(&messageDigestReduced, &messageDigest)
|
|
||||||
var R edwards25519.ExtendedGroupElement
|
|
||||||
edwards25519.GeScalarMultBase(&R, &messageDigestReduced)
|
|
||||||
|
|
||||||
var encodedR [32]byte
|
|
||||||
R.ToBytes(&encodedR)
|
|
||||||
|
|
||||||
h.Reset()
|
|
||||||
h.Write(encodedR[:])
|
|
||||||
h.Write(privateKey[32:])
|
|
||||||
h.Write(message)
|
|
||||||
h.Sum(hramDigest[:0])
|
|
||||||
var hramDigestReduced [32]byte
|
|
||||||
edwards25519.ScReduce(&hramDigestReduced, &hramDigest)
|
|
||||||
|
|
||||||
var s [32]byte
|
|
||||||
edwards25519.ScMulAdd(&s, &hramDigestReduced, &expandedSecretKey, &messageDigestReduced)
|
|
||||||
|
|
||||||
signature := make([]byte, SignatureSize)
|
|
||||||
copy(signature[:], encodedR[:])
|
|
||||||
copy(signature[32:], s[:])
|
|
||||||
|
|
||||||
return signature
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify reports whether sig is a valid signature of message by publicKey. It
|
|
||||||
// will panic if len(publicKey) is not PublicKeySize.
|
|
||||||
func Verify(publicKey PublicKey, message, sig []byte) bool {
|
|
||||||
if l := len(publicKey); l != PublicKeySize {
|
|
||||||
panic("ed25519: bad public key length: " + strconv.Itoa(l))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sig) != SignatureSize || sig[63]&224 != 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var A edwards25519.ExtendedGroupElement
|
|
||||||
var publicKeyBytes [32]byte
|
|
||||||
copy(publicKeyBytes[:], publicKey)
|
|
||||||
if !A.FromBytes(&publicKeyBytes) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
edwards25519.FeNeg(&A.X, &A.X)
|
|
||||||
edwards25519.FeNeg(&A.T, &A.T)
|
|
||||||
|
|
||||||
h := sha512.New()
|
|
||||||
h.Write(sig[:32])
|
|
||||||
h.Write(publicKey[:])
|
|
||||||
h.Write(message)
|
|
||||||
var digest [64]byte
|
|
||||||
h.Sum(digest[:0])
|
|
||||||
|
|
||||||
var hReduced [32]byte
|
|
||||||
edwards25519.ScReduce(&hReduced, &digest)
|
|
||||||
|
|
||||||
var R edwards25519.ProjectiveGroupElement
|
|
||||||
var b [32]byte
|
|
||||||
copy(b[:], sig[32:])
|
|
||||||
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &b)
|
|
||||||
|
|
||||||
var checkR [32]byte
|
|
||||||
R.ToBytes(&checkR)
|
|
||||||
return subtle.ConstantTimeCompare(sig[:32], checkR[:]) == 1
|
|
||||||
}
|
|
||||||
-1422
File diff suppressed because it is too large
Load Diff
-1771
File diff suppressed because it is too large
Load Diff
-659
@@ -1,659 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package agent implements the ssh-agent protocol, and provides both
|
|
||||||
// a client and a server. The client can talk to a standard ssh-agent
|
|
||||||
// that uses UNIX sockets, and one could implement an alternative
|
|
||||||
// ssh-agent process using the sample server.
|
|
||||||
//
|
|
||||||
// References:
|
|
||||||
// [PROTOCOL.agent]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent?rev=HEAD
|
|
||||||
package agent // import "golang.org/x/crypto/ssh/agent"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/dsa"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rsa"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ed25519"
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Agent represents the capabilities of an ssh-agent.
|
|
||||||
type Agent interface {
|
|
||||||
// List returns the identities known to the agent.
|
|
||||||
List() ([]*Key, error)
|
|
||||||
|
|
||||||
// Sign has the agent sign the data using a protocol 2 key as defined
|
|
||||||
// in [PROTOCOL.agent] section 2.6.2.
|
|
||||||
Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error)
|
|
||||||
|
|
||||||
// Add adds a private key to the agent.
|
|
||||||
Add(key AddedKey) error
|
|
||||||
|
|
||||||
// Remove removes all identities with the given public key.
|
|
||||||
Remove(key ssh.PublicKey) error
|
|
||||||
|
|
||||||
// RemoveAll removes all identities.
|
|
||||||
RemoveAll() error
|
|
||||||
|
|
||||||
// Lock locks the agent. Sign and Remove will fail, and List will empty an empty list.
|
|
||||||
Lock(passphrase []byte) error
|
|
||||||
|
|
||||||
// Unlock undoes the effect of Lock
|
|
||||||
Unlock(passphrase []byte) error
|
|
||||||
|
|
||||||
// Signers returns signers for all the known keys.
|
|
||||||
Signers() ([]ssh.Signer, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddedKey describes an SSH key to be added to an Agent.
|
|
||||||
type AddedKey struct {
|
|
||||||
// PrivateKey must be a *rsa.PrivateKey, *dsa.PrivateKey or
|
|
||||||
// *ecdsa.PrivateKey, which will be inserted into the agent.
|
|
||||||
PrivateKey interface{}
|
|
||||||
// Certificate, if not nil, is communicated to the agent and will be
|
|
||||||
// stored with the key.
|
|
||||||
Certificate *ssh.Certificate
|
|
||||||
// Comment is an optional, free-form string.
|
|
||||||
Comment string
|
|
||||||
// LifetimeSecs, if not zero, is the number of seconds that the
|
|
||||||
// agent will store the key for.
|
|
||||||
LifetimeSecs uint32
|
|
||||||
// ConfirmBeforeUse, if true, requests that the agent confirm with the
|
|
||||||
// user before each use of this key.
|
|
||||||
ConfirmBeforeUse bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// See [PROTOCOL.agent], section 3.
|
|
||||||
const (
|
|
||||||
agentRequestV1Identities = 1
|
|
||||||
agentRemoveAllV1Identities = 9
|
|
||||||
|
|
||||||
// 3.2 Requests from client to agent for protocol 2 key operations
|
|
||||||
agentAddIdentity = 17
|
|
||||||
agentRemoveIdentity = 18
|
|
||||||
agentRemoveAllIdentities = 19
|
|
||||||
agentAddIdConstrained = 25
|
|
||||||
|
|
||||||
// 3.3 Key-type independent requests from client to agent
|
|
||||||
agentAddSmartcardKey = 20
|
|
||||||
agentRemoveSmartcardKey = 21
|
|
||||||
agentLock = 22
|
|
||||||
agentUnlock = 23
|
|
||||||
agentAddSmartcardKeyConstrained = 26
|
|
||||||
|
|
||||||
// 3.7 Key constraint identifiers
|
|
||||||
agentConstrainLifetime = 1
|
|
||||||
agentConstrainConfirm = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// maxAgentResponseBytes is the maximum agent reply size that is accepted. This
|
|
||||||
// is a sanity check, not a limit in the spec.
|
|
||||||
const maxAgentResponseBytes = 16 << 20
|
|
||||||
|
|
||||||
// Agent messages:
|
|
||||||
// These structures mirror the wire format of the corresponding ssh agent
|
|
||||||
// messages found in [PROTOCOL.agent].
|
|
||||||
|
|
||||||
// 3.4 Generic replies from agent to client
|
|
||||||
const agentFailure = 5
|
|
||||||
|
|
||||||
type failureAgentMsg struct{}
|
|
||||||
|
|
||||||
const agentSuccess = 6
|
|
||||||
|
|
||||||
type successAgentMsg struct{}
|
|
||||||
|
|
||||||
// See [PROTOCOL.agent], section 2.5.2.
|
|
||||||
const agentRequestIdentities = 11
|
|
||||||
|
|
||||||
type requestIdentitiesAgentMsg struct{}
|
|
||||||
|
|
||||||
// See [PROTOCOL.agent], section 2.5.2.
|
|
||||||
const agentIdentitiesAnswer = 12
|
|
||||||
|
|
||||||
type identitiesAnswerAgentMsg struct {
|
|
||||||
NumKeys uint32 `sshtype:"12"`
|
|
||||||
Keys []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// See [PROTOCOL.agent], section 2.6.2.
|
|
||||||
const agentSignRequest = 13
|
|
||||||
|
|
||||||
type signRequestAgentMsg struct {
|
|
||||||
KeyBlob []byte `sshtype:"13"`
|
|
||||||
Data []byte
|
|
||||||
Flags uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// See [PROTOCOL.agent], section 2.6.2.
|
|
||||||
|
|
||||||
// 3.6 Replies from agent to client for protocol 2 key operations
|
|
||||||
const agentSignResponse = 14
|
|
||||||
|
|
||||||
type signResponseAgentMsg struct {
|
|
||||||
SigBlob []byte `sshtype:"14"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type publicKey struct {
|
|
||||||
Format string
|
|
||||||
Rest []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key represents a protocol 2 public key as defined in
|
|
||||||
// [PROTOCOL.agent], section 2.5.2.
|
|
||||||
type Key struct {
|
|
||||||
Format string
|
|
||||||
Blob []byte
|
|
||||||
Comment string
|
|
||||||
}
|
|
||||||
|
|
||||||
func clientErr(err error) error {
|
|
||||||
return fmt.Errorf("agent: client error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the storage form of an agent key with the format, base64
|
|
||||||
// encoded serialized key, and the comment if it is not empty.
|
|
||||||
func (k *Key) String() string {
|
|
||||||
s := string(k.Format) + " " + base64.StdEncoding.EncodeToString(k.Blob)
|
|
||||||
|
|
||||||
if k.Comment != "" {
|
|
||||||
s += " " + k.Comment
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the public key type.
|
|
||||||
func (k *Key) Type() string {
|
|
||||||
return k.Format
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal returns key blob to satisfy the ssh.PublicKey interface.
|
|
||||||
func (k *Key) Marshal() []byte {
|
|
||||||
return k.Blob
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify satisfies the ssh.PublicKey interface.
|
|
||||||
func (k *Key) Verify(data []byte, sig *ssh.Signature) error {
|
|
||||||
pubKey, err := ssh.ParsePublicKey(k.Blob)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("agent: bad public key: %v", err)
|
|
||||||
}
|
|
||||||
return pubKey.Verify(data, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
type wireKey struct {
|
|
||||||
Format string
|
|
||||||
Rest []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseKey(in []byte) (out *Key, rest []byte, err error) {
|
|
||||||
var record struct {
|
|
||||||
Blob []byte
|
|
||||||
Comment string
|
|
||||||
Rest []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ssh.Unmarshal(in, &record); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var wk wireKey
|
|
||||||
if err := ssh.Unmarshal(record.Blob, &wk); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Key{
|
|
||||||
Format: wk.Format,
|
|
||||||
Blob: record.Blob,
|
|
||||||
Comment: record.Comment,
|
|
||||||
}, record.Rest, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// client is a client for an ssh-agent process.
|
|
||||||
type client struct {
|
|
||||||
// conn is typically a *net.UnixConn
|
|
||||||
conn io.ReadWriter
|
|
||||||
// mu is used to prevent concurrent access to the agent
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient returns an Agent that talks to an ssh-agent process over
|
|
||||||
// the given connection.
|
|
||||||
func NewClient(rw io.ReadWriter) Agent {
|
|
||||||
return &client{conn: rw}
|
|
||||||
}
|
|
||||||
|
|
||||||
// call sends an RPC to the agent. On success, the reply is
|
|
||||||
// unmarshaled into reply and replyType is set to the first byte of
|
|
||||||
// the reply, which contains the type of the message.
|
|
||||||
func (c *client) call(req []byte) (reply interface{}, err error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
msg := make([]byte, 4+len(req))
|
|
||||||
binary.BigEndian.PutUint32(msg, uint32(len(req)))
|
|
||||||
copy(msg[4:], req)
|
|
||||||
if _, err = c.conn.Write(msg); err != nil {
|
|
||||||
return nil, clientErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var respSizeBuf [4]byte
|
|
||||||
if _, err = io.ReadFull(c.conn, respSizeBuf[:]); err != nil {
|
|
||||||
return nil, clientErr(err)
|
|
||||||
}
|
|
||||||
respSize := binary.BigEndian.Uint32(respSizeBuf[:])
|
|
||||||
if respSize > maxAgentResponseBytes {
|
|
||||||
return nil, clientErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, respSize)
|
|
||||||
if _, err = io.ReadFull(c.conn, buf); err != nil {
|
|
||||||
return nil, clientErr(err)
|
|
||||||
}
|
|
||||||
reply, err = unmarshal(buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, clientErr(err)
|
|
||||||
}
|
|
||||||
return reply, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *client) simpleCall(req []byte) error {
|
|
||||||
resp, err := c.call(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, ok := resp.(*successAgentMsg); ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New("agent: failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *client) RemoveAll() error {
|
|
||||||
return c.simpleCall([]byte{agentRemoveAllIdentities})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *client) Remove(key ssh.PublicKey) error {
|
|
||||||
req := ssh.Marshal(&agentRemoveIdentityMsg{
|
|
||||||
KeyBlob: key.Marshal(),
|
|
||||||
})
|
|
||||||
return c.simpleCall(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *client) Lock(passphrase []byte) error {
|
|
||||||
req := ssh.Marshal(&agentLockMsg{
|
|
||||||
Passphrase: passphrase,
|
|
||||||
})
|
|
||||||
return c.simpleCall(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *client) Unlock(passphrase []byte) error {
|
|
||||||
req := ssh.Marshal(&agentUnlockMsg{
|
|
||||||
Passphrase: passphrase,
|
|
||||||
})
|
|
||||||
return c.simpleCall(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns the identities known to the agent.
|
|
||||||
func (c *client) List() ([]*Key, error) {
|
|
||||||
// see [PROTOCOL.agent] section 2.5.2.
|
|
||||||
req := []byte{agentRequestIdentities}
|
|
||||||
|
|
||||||
msg, err := c.call(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case *identitiesAnswerAgentMsg:
|
|
||||||
if msg.NumKeys > maxAgentResponseBytes/8 {
|
|
||||||
return nil, errors.New("agent: too many keys in agent reply")
|
|
||||||
}
|
|
||||||
keys := make([]*Key, msg.NumKeys)
|
|
||||||
data := msg.Keys
|
|
||||||
for i := uint32(0); i < msg.NumKeys; i++ {
|
|
||||||
var key *Key
|
|
||||||
var err error
|
|
||||||
if key, data, err = parseKey(data); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
keys[i] = key
|
|
||||||
}
|
|
||||||
return keys, nil
|
|
||||||
case *failureAgentMsg:
|
|
||||||
return nil, errors.New("agent: failed to list keys")
|
|
||||||
}
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign has the agent sign the data using a protocol 2 key as defined
|
|
||||||
// in [PROTOCOL.agent] section 2.6.2.
|
|
||||||
func (c *client) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
|
|
||||||
req := ssh.Marshal(signRequestAgentMsg{
|
|
||||||
KeyBlob: key.Marshal(),
|
|
||||||
Data: data,
|
|
||||||
})
|
|
||||||
|
|
||||||
msg, err := c.call(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case *signResponseAgentMsg:
|
|
||||||
var sig ssh.Signature
|
|
||||||
if err := ssh.Unmarshal(msg.SigBlob, &sig); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &sig, nil
|
|
||||||
case *failureAgentMsg:
|
|
||||||
return nil, errors.New("agent: failed to sign challenge")
|
|
||||||
}
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
// unmarshal parses an agent message in packet, returning the parsed
|
|
||||||
// form and the message type of packet.
|
|
||||||
func unmarshal(packet []byte) (interface{}, error) {
|
|
||||||
if len(packet) < 1 {
|
|
||||||
return nil, errors.New("agent: empty packet")
|
|
||||||
}
|
|
||||||
var msg interface{}
|
|
||||||
switch packet[0] {
|
|
||||||
case agentFailure:
|
|
||||||
return new(failureAgentMsg), nil
|
|
||||||
case agentSuccess:
|
|
||||||
return new(successAgentMsg), nil
|
|
||||||
case agentIdentitiesAnswer:
|
|
||||||
msg = new(identitiesAnswerAgentMsg)
|
|
||||||
case agentSignResponse:
|
|
||||||
msg = new(signResponseAgentMsg)
|
|
||||||
case agentV1IdentitiesAnswer:
|
|
||||||
msg = new(agentV1IdentityMsg)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("agent: unknown type tag %d", packet[0])
|
|
||||||
}
|
|
||||||
if err := ssh.Unmarshal(packet, msg); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return msg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type rsaKeyMsg struct {
|
|
||||||
Type string `sshtype:"17|25"`
|
|
||||||
N *big.Int
|
|
||||||
E *big.Int
|
|
||||||
D *big.Int
|
|
||||||
Iqmp *big.Int // IQMP = Inverse Q Mod P
|
|
||||||
P *big.Int
|
|
||||||
Q *big.Int
|
|
||||||
Comments string
|
|
||||||
Constraints []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type dsaKeyMsg struct {
|
|
||||||
Type string `sshtype:"17|25"`
|
|
||||||
P *big.Int
|
|
||||||
Q *big.Int
|
|
||||||
G *big.Int
|
|
||||||
Y *big.Int
|
|
||||||
X *big.Int
|
|
||||||
Comments string
|
|
||||||
Constraints []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ecdsaKeyMsg struct {
|
|
||||||
Type string `sshtype:"17|25"`
|
|
||||||
Curve string
|
|
||||||
KeyBytes []byte
|
|
||||||
D *big.Int
|
|
||||||
Comments string
|
|
||||||
Constraints []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ed25519KeyMsg struct {
|
|
||||||
Type string `sshtype:"17|25"`
|
|
||||||
Pub []byte
|
|
||||||
Priv []byte
|
|
||||||
Comments string
|
|
||||||
Constraints []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert adds a private key to the agent.
|
|
||||||
func (c *client) insertKey(s interface{}, comment string, constraints []byte) error {
|
|
||||||
var req []byte
|
|
||||||
switch k := s.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
if len(k.Primes) != 2 {
|
|
||||||
return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes))
|
|
||||||
}
|
|
||||||
k.Precompute()
|
|
||||||
req = ssh.Marshal(rsaKeyMsg{
|
|
||||||
Type: ssh.KeyAlgoRSA,
|
|
||||||
N: k.N,
|
|
||||||
E: big.NewInt(int64(k.E)),
|
|
||||||
D: k.D,
|
|
||||||
Iqmp: k.Precomputed.Qinv,
|
|
||||||
P: k.Primes[0],
|
|
||||||
Q: k.Primes[1],
|
|
||||||
Comments: comment,
|
|
||||||
Constraints: constraints,
|
|
||||||
})
|
|
||||||
case *dsa.PrivateKey:
|
|
||||||
req = ssh.Marshal(dsaKeyMsg{
|
|
||||||
Type: ssh.KeyAlgoDSA,
|
|
||||||
P: k.P,
|
|
||||||
Q: k.Q,
|
|
||||||
G: k.G,
|
|
||||||
Y: k.Y,
|
|
||||||
X: k.X,
|
|
||||||
Comments: comment,
|
|
||||||
Constraints: constraints,
|
|
||||||
})
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
nistID := fmt.Sprintf("nistp%d", k.Params().BitSize)
|
|
||||||
req = ssh.Marshal(ecdsaKeyMsg{
|
|
||||||
Type: "ecdsa-sha2-" + nistID,
|
|
||||||
Curve: nistID,
|
|
||||||
KeyBytes: elliptic.Marshal(k.Curve, k.X, k.Y),
|
|
||||||
D: k.D,
|
|
||||||
Comments: comment,
|
|
||||||
Constraints: constraints,
|
|
||||||
})
|
|
||||||
case *ed25519.PrivateKey:
|
|
||||||
req = ssh.Marshal(ed25519KeyMsg{
|
|
||||||
Type: ssh.KeyAlgoED25519,
|
|
||||||
Pub: []byte(*k)[32:],
|
|
||||||
Priv: []byte(*k),
|
|
||||||
Comments: comment,
|
|
||||||
Constraints: constraints,
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("agent: unsupported key type %T", s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if constraints are present then the message type needs to be changed.
|
|
||||||
if len(constraints) != 0 {
|
|
||||||
req[0] = agentAddIdConstrained
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := c.call(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, ok := resp.(*successAgentMsg); ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New("agent: failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
type rsaCertMsg struct {
|
|
||||||
Type string `sshtype:"17|25"`
|
|
||||||
CertBytes []byte
|
|
||||||
D *big.Int
|
|
||||||
Iqmp *big.Int // IQMP = Inverse Q Mod P
|
|
||||||
P *big.Int
|
|
||||||
Q *big.Int
|
|
||||||
Comments string
|
|
||||||
Constraints []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type dsaCertMsg struct {
|
|
||||||
Type string `sshtype:"17|25"`
|
|
||||||
CertBytes []byte
|
|
||||||
X *big.Int
|
|
||||||
Comments string
|
|
||||||
Constraints []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ecdsaCertMsg struct {
|
|
||||||
Type string `sshtype:"17|25"`
|
|
||||||
CertBytes []byte
|
|
||||||
D *big.Int
|
|
||||||
Comments string
|
|
||||||
Constraints []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ed25519CertMsg struct {
|
|
||||||
Type string `sshtype:"17|25"`
|
|
||||||
CertBytes []byte
|
|
||||||
Pub []byte
|
|
||||||
Priv []byte
|
|
||||||
Comments string
|
|
||||||
Constraints []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds a private key to the agent. If a certificate is given,
|
|
||||||
// that certificate is added instead as public key.
|
|
||||||
func (c *client) Add(key AddedKey) error {
|
|
||||||
var constraints []byte
|
|
||||||
|
|
||||||
if secs := key.LifetimeSecs; secs != 0 {
|
|
||||||
constraints = append(constraints, agentConstrainLifetime)
|
|
||||||
|
|
||||||
var secsBytes [4]byte
|
|
||||||
binary.BigEndian.PutUint32(secsBytes[:], secs)
|
|
||||||
constraints = append(constraints, secsBytes[:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.ConfirmBeforeUse {
|
|
||||||
constraints = append(constraints, agentConstrainConfirm)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cert := key.Certificate; cert == nil {
|
|
||||||
return c.insertKey(key.PrivateKey, key.Comment, constraints)
|
|
||||||
} else {
|
|
||||||
return c.insertCert(key.PrivateKey, cert, key.Comment, constraints)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string, constraints []byte) error {
|
|
||||||
var req []byte
|
|
||||||
switch k := s.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
if len(k.Primes) != 2 {
|
|
||||||
return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes))
|
|
||||||
}
|
|
||||||
k.Precompute()
|
|
||||||
req = ssh.Marshal(rsaCertMsg{
|
|
||||||
Type: cert.Type(),
|
|
||||||
CertBytes: cert.Marshal(),
|
|
||||||
D: k.D,
|
|
||||||
Iqmp: k.Precomputed.Qinv,
|
|
||||||
P: k.Primes[0],
|
|
||||||
Q: k.Primes[1],
|
|
||||||
Comments: comment,
|
|
||||||
Constraints: constraints,
|
|
||||||
})
|
|
||||||
case *dsa.PrivateKey:
|
|
||||||
req = ssh.Marshal(dsaCertMsg{
|
|
||||||
Type: cert.Type(),
|
|
||||||
CertBytes: cert.Marshal(),
|
|
||||||
X: k.X,
|
|
||||||
Comments: comment,
|
|
||||||
Constraints: constraints,
|
|
||||||
})
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
req = ssh.Marshal(ecdsaCertMsg{
|
|
||||||
Type: cert.Type(),
|
|
||||||
CertBytes: cert.Marshal(),
|
|
||||||
D: k.D,
|
|
||||||
Comments: comment,
|
|
||||||
Constraints: constraints,
|
|
||||||
})
|
|
||||||
case *ed25519.PrivateKey:
|
|
||||||
req = ssh.Marshal(ed25519CertMsg{
|
|
||||||
Type: cert.Type(),
|
|
||||||
CertBytes: cert.Marshal(),
|
|
||||||
Pub: []byte(*k)[32:],
|
|
||||||
Priv: []byte(*k),
|
|
||||||
Comments: comment,
|
|
||||||
Constraints: constraints,
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("agent: unsupported key type %T", s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if constraints are present then the message type needs to be changed.
|
|
||||||
if len(constraints) != 0 {
|
|
||||||
req[0] = agentAddIdConstrained
|
|
||||||
}
|
|
||||||
|
|
||||||
signer, err := ssh.NewSignerFromKey(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 {
|
|
||||||
return errors.New("agent: signer and cert have different public key")
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := c.call(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, ok := resp.(*successAgentMsg); ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New("agent: failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signers provides a callback for client authentication.
|
|
||||||
func (c *client) Signers() ([]ssh.Signer, error) {
|
|
||||||
keys, err := c.List()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var result []ssh.Signer
|
|
||||||
for _, k := range keys {
|
|
||||||
result = append(result, &agentKeyringSigner{c, k})
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type agentKeyringSigner struct {
|
|
||||||
agent *client
|
|
||||||
pub ssh.PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *agentKeyringSigner) PublicKey() ssh.PublicKey {
|
|
||||||
return s.pub
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *agentKeyringSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
|
|
||||||
// The agent has its own entropy source, so the rand argument is ignored.
|
|
||||||
return s.agent.Sign(s.pub, data)
|
|
||||||
}
|
|
||||||
-103
@@ -1,103 +0,0 @@
|
|||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RequestAgentForwarding sets up agent forwarding for the session.
|
|
||||||
// ForwardToAgent or ForwardToRemote should be called to route
|
|
||||||
// the authentication requests.
|
|
||||||
func RequestAgentForwarding(session *ssh.Session) error {
|
|
||||||
ok, err := session.SendRequest("auth-agent-req@openssh.com", true, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return errors.New("forwarding request denied")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForwardToAgent routes authentication requests to the given keyring.
|
|
||||||
func ForwardToAgent(client *ssh.Client, keyring Agent) error {
|
|
||||||
channels := client.HandleChannelOpen(channelType)
|
|
||||||
if channels == nil {
|
|
||||||
return errors.New("agent: already have handler for " + channelType)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for ch := range channels {
|
|
||||||
channel, reqs, err := ch.Accept()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
go ssh.DiscardRequests(reqs)
|
|
||||||
go func() {
|
|
||||||
ServeAgent(keyring, channel)
|
|
||||||
channel.Close()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const channelType = "auth-agent@openssh.com"
|
|
||||||
|
|
||||||
// ForwardToRemote routes authentication requests to the ssh-agent
|
|
||||||
// process serving on the given unix socket.
|
|
||||||
func ForwardToRemote(client *ssh.Client, addr string) error {
|
|
||||||
channels := client.HandleChannelOpen(channelType)
|
|
||||||
if channels == nil {
|
|
||||||
return errors.New("agent: already have handler for " + channelType)
|
|
||||||
}
|
|
||||||
conn, err := net.Dial("unix", addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
conn.Close()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for ch := range channels {
|
|
||||||
channel, reqs, err := ch.Accept()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
go ssh.DiscardRequests(reqs)
|
|
||||||
go forwardUnixSocket(channel, addr)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func forwardUnixSocket(channel ssh.Channel, addr string) {
|
|
||||||
conn, err := net.Dial("unix", addr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(2)
|
|
||||||
go func() {
|
|
||||||
io.Copy(conn, channel)
|
|
||||||
conn.(*net.UnixConn).CloseWrite()
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
io.Copy(channel, conn)
|
|
||||||
channel.CloseWrite()
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
conn.Close()
|
|
||||||
channel.Close()
|
|
||||||
}
|
|
||||||
-215
@@ -1,215 +0,0 @@
|
|||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/subtle"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
type privKey struct {
|
|
||||||
signer ssh.Signer
|
|
||||||
comment string
|
|
||||||
expire *time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type keyring struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
keys []privKey
|
|
||||||
|
|
||||||
locked bool
|
|
||||||
passphrase []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
var errLocked = errors.New("agent: locked")
|
|
||||||
|
|
||||||
// NewKeyring returns an Agent that holds keys in memory. It is safe
|
|
||||||
// for concurrent use by multiple goroutines.
|
|
||||||
func NewKeyring() Agent {
|
|
||||||
return &keyring{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveAll removes all identities.
|
|
||||||
func (r *keyring) RemoveAll() error {
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
if r.locked {
|
|
||||||
return errLocked
|
|
||||||
}
|
|
||||||
|
|
||||||
r.keys = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeLocked does the actual key removal. The caller must already be holding the
|
|
||||||
// keyring mutex.
|
|
||||||
func (r *keyring) removeLocked(want []byte) error {
|
|
||||||
found := false
|
|
||||||
for i := 0; i < len(r.keys); {
|
|
||||||
if bytes.Equal(r.keys[i].signer.PublicKey().Marshal(), want) {
|
|
||||||
found = true
|
|
||||||
r.keys[i] = r.keys[len(r.keys)-1]
|
|
||||||
r.keys = r.keys[:len(r.keys)-1]
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
return errors.New("agent: key not found")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove removes all identities with the given public key.
|
|
||||||
func (r *keyring) Remove(key ssh.PublicKey) error {
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
if r.locked {
|
|
||||||
return errLocked
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.removeLocked(key.Marshal())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock locks the agent. Sign and Remove will fail, and List will return an empty list.
|
|
||||||
func (r *keyring) Lock(passphrase []byte) error {
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
if r.locked {
|
|
||||||
return errLocked
|
|
||||||
}
|
|
||||||
|
|
||||||
r.locked = true
|
|
||||||
r.passphrase = passphrase
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlock undoes the effect of Lock
|
|
||||||
func (r *keyring) Unlock(passphrase []byte) error {
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
if !r.locked {
|
|
||||||
return errors.New("agent: not locked")
|
|
||||||
}
|
|
||||||
if len(passphrase) != len(r.passphrase) || 1 != subtle.ConstantTimeCompare(passphrase, r.passphrase) {
|
|
||||||
return fmt.Errorf("agent: incorrect passphrase")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.locked = false
|
|
||||||
r.passphrase = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// expireKeysLocked removes expired keys from the keyring. If a key was added
|
|
||||||
// with a lifetimesecs contraint and seconds >= lifetimesecs seconds have
|
|
||||||
// ellapsed, it is removed. The caller *must* be holding the keyring mutex.
|
|
||||||
func (r *keyring) expireKeysLocked() {
|
|
||||||
for _, k := range r.keys {
|
|
||||||
if k.expire != nil && time.Now().After(*k.expire) {
|
|
||||||
r.removeLocked(k.signer.PublicKey().Marshal())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns the identities known to the agent.
|
|
||||||
func (r *keyring) List() ([]*Key, error) {
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
if r.locked {
|
|
||||||
// section 2.7: locked agents return empty.
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r.expireKeysLocked()
|
|
||||||
var ids []*Key
|
|
||||||
for _, k := range r.keys {
|
|
||||||
pub := k.signer.PublicKey()
|
|
||||||
ids = append(ids, &Key{
|
|
||||||
Format: pub.Type(),
|
|
||||||
Blob: pub.Marshal(),
|
|
||||||
Comment: k.comment})
|
|
||||||
}
|
|
||||||
return ids, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert adds a private key to the keyring. If a certificate
|
|
||||||
// is given, that certificate is added as public key. Note that
|
|
||||||
// any constraints given are ignored.
|
|
||||||
func (r *keyring) Add(key AddedKey) error {
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
if r.locked {
|
|
||||||
return errLocked
|
|
||||||
}
|
|
||||||
signer, err := ssh.NewSignerFromKey(key.PrivateKey)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if cert := key.Certificate; cert != nil {
|
|
||||||
signer, err = ssh.NewCertSigner(cert, signer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p := privKey{
|
|
||||||
signer: signer,
|
|
||||||
comment: key.Comment,
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.LifetimeSecs > 0 {
|
|
||||||
t := time.Now().Add(time.Duration(key.LifetimeSecs) * time.Second)
|
|
||||||
p.expire = &t
|
|
||||||
}
|
|
||||||
|
|
||||||
r.keys = append(r.keys, p)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign returns a signature for the data.
|
|
||||||
func (r *keyring) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
if r.locked {
|
|
||||||
return nil, errLocked
|
|
||||||
}
|
|
||||||
|
|
||||||
r.expireKeysLocked()
|
|
||||||
wanted := key.Marshal()
|
|
||||||
for _, k := range r.keys {
|
|
||||||
if bytes.Equal(k.signer.PublicKey().Marshal(), wanted) {
|
|
||||||
return k.signer.Sign(rand.Reader, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signers returns signers for all the known keys.
|
|
||||||
func (r *keyring) Signers() ([]ssh.Signer, error) {
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
if r.locked {
|
|
||||||
return nil, errLocked
|
|
||||||
}
|
|
||||||
|
|
||||||
r.expireKeysLocked()
|
|
||||||
s := make([]ssh.Signer, 0, len(r.keys))
|
|
||||||
for _, k := range r.keys {
|
|
||||||
s = append(s, k.signer)
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
-451
@@ -1,451 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/dsa"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rsa"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ed25519"
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Server wraps an Agent and uses it to implement the agent side of
|
|
||||||
// the SSH-agent, wire protocol.
|
|
||||||
type server struct {
|
|
||||||
agent Agent
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) processRequestBytes(reqData []byte) []byte {
|
|
||||||
rep, err := s.processRequest(reqData)
|
|
||||||
if err != nil {
|
|
||||||
if err != errLocked {
|
|
||||||
// TODO(hanwen): provide better logging interface?
|
|
||||||
log.Printf("agent %d: %v", reqData[0], err)
|
|
||||||
}
|
|
||||||
return []byte{agentFailure}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil && rep == nil {
|
|
||||||
return []byte{agentSuccess}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ssh.Marshal(rep)
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalKey(k *Key) []byte {
|
|
||||||
var record struct {
|
|
||||||
Blob []byte
|
|
||||||
Comment string
|
|
||||||
}
|
|
||||||
record.Blob = k.Marshal()
|
|
||||||
record.Comment = k.Comment
|
|
||||||
|
|
||||||
return ssh.Marshal(&record)
|
|
||||||
}
|
|
||||||
|
|
||||||
// See [PROTOCOL.agent], section 2.5.1.
|
|
||||||
const agentV1IdentitiesAnswer = 2
|
|
||||||
|
|
||||||
type agentV1IdentityMsg struct {
|
|
||||||
Numkeys uint32 `sshtype:"2"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type agentRemoveIdentityMsg struct {
|
|
||||||
KeyBlob []byte `sshtype:"18"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type agentLockMsg struct {
|
|
||||||
Passphrase []byte `sshtype:"22"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type agentUnlockMsg struct {
|
|
||||||
Passphrase []byte `sshtype:"23"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) processRequest(data []byte) (interface{}, error) {
|
|
||||||
switch data[0] {
|
|
||||||
case agentRequestV1Identities:
|
|
||||||
return &agentV1IdentityMsg{0}, nil
|
|
||||||
|
|
||||||
case agentRemoveAllV1Identities:
|
|
||||||
return nil, nil
|
|
||||||
|
|
||||||
case agentRemoveIdentity:
|
|
||||||
var req agentRemoveIdentityMsg
|
|
||||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var wk wireKey
|
|
||||||
if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, s.agent.Remove(&Key{Format: wk.Format, Blob: req.KeyBlob})
|
|
||||||
|
|
||||||
case agentRemoveAllIdentities:
|
|
||||||
return nil, s.agent.RemoveAll()
|
|
||||||
|
|
||||||
case agentLock:
|
|
||||||
var req agentLockMsg
|
|
||||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, s.agent.Lock(req.Passphrase)
|
|
||||||
|
|
||||||
case agentUnlock:
|
|
||||||
var req agentLockMsg
|
|
||||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return nil, s.agent.Unlock(req.Passphrase)
|
|
||||||
|
|
||||||
case agentSignRequest:
|
|
||||||
var req signRequestAgentMsg
|
|
||||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var wk wireKey
|
|
||||||
if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
k := &Key{
|
|
||||||
Format: wk.Format,
|
|
||||||
Blob: req.KeyBlob,
|
|
||||||
}
|
|
||||||
|
|
||||||
sig, err := s.agent.Sign(k, req.Data) // TODO(hanwen): flags.
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &signResponseAgentMsg{SigBlob: ssh.Marshal(sig)}, nil
|
|
||||||
|
|
||||||
case agentRequestIdentities:
|
|
||||||
keys, err := s.agent.List()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rep := identitiesAnswerAgentMsg{
|
|
||||||
NumKeys: uint32(len(keys)),
|
|
||||||
}
|
|
||||||
for _, k := range keys {
|
|
||||||
rep.Keys = append(rep.Keys, marshalKey(k)...)
|
|
||||||
}
|
|
||||||
return rep, nil
|
|
||||||
|
|
||||||
case agentAddIdConstrained, agentAddIdentity:
|
|
||||||
return nil, s.insertIdentity(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("unknown opcode %d", data[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRSAKey(req []byte) (*AddedKey, error) {
|
|
||||||
var k rsaKeyMsg
|
|
||||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if k.E.BitLen() > 30 {
|
|
||||||
return nil, errors.New("agent: RSA public exponent too large")
|
|
||||||
}
|
|
||||||
priv := &rsa.PrivateKey{
|
|
||||||
PublicKey: rsa.PublicKey{
|
|
||||||
E: int(k.E.Int64()),
|
|
||||||
N: k.N,
|
|
||||||
},
|
|
||||||
D: k.D,
|
|
||||||
Primes: []*big.Int{k.P, k.Q},
|
|
||||||
}
|
|
||||||
priv.Precompute()
|
|
||||||
|
|
||||||
return &AddedKey{PrivateKey: priv, Comment: k.Comments}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseEd25519Key(req []byte) (*AddedKey, error) {
|
|
||||||
var k ed25519KeyMsg
|
|
||||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
priv := ed25519.PrivateKey(k.Priv)
|
|
||||||
return &AddedKey{PrivateKey: &priv, Comment: k.Comments}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDSAKey(req []byte) (*AddedKey, error) {
|
|
||||||
var k dsaKeyMsg
|
|
||||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
priv := &dsa.PrivateKey{
|
|
||||||
PublicKey: dsa.PublicKey{
|
|
||||||
Parameters: dsa.Parameters{
|
|
||||||
P: k.P,
|
|
||||||
Q: k.Q,
|
|
||||||
G: k.G,
|
|
||||||
},
|
|
||||||
Y: k.Y,
|
|
||||||
},
|
|
||||||
X: k.X,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AddedKey{PrivateKey: priv, Comment: k.Comments}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalECDSA(curveName string, keyBytes []byte, privScalar *big.Int) (priv *ecdsa.PrivateKey, err error) {
|
|
||||||
priv = &ecdsa.PrivateKey{
|
|
||||||
D: privScalar,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch curveName {
|
|
||||||
case "nistp256":
|
|
||||||
priv.Curve = elliptic.P256()
|
|
||||||
case "nistp384":
|
|
||||||
priv.Curve = elliptic.P384()
|
|
||||||
case "nistp521":
|
|
||||||
priv.Curve = elliptic.P521()
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("agent: unknown curve %q", curveName)
|
|
||||||
}
|
|
||||||
|
|
||||||
priv.X, priv.Y = elliptic.Unmarshal(priv.Curve, keyBytes)
|
|
||||||
if priv.X == nil || priv.Y == nil {
|
|
||||||
return nil, errors.New("agent: point not on curve")
|
|
||||||
}
|
|
||||||
|
|
||||||
return priv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseEd25519Cert(req []byte) (*AddedKey, error) {
|
|
||||||
var k ed25519CertMsg
|
|
||||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
priv := ed25519.PrivateKey(k.Priv)
|
|
||||||
cert, ok := pubKey.(*ssh.Certificate)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("agent: bad ED25519 certificate")
|
|
||||||
}
|
|
||||||
return &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseECDSAKey(req []byte) (*AddedKey, error) {
|
|
||||||
var k ecdsaKeyMsg
|
|
||||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
priv, err := unmarshalECDSA(k.Curve, k.KeyBytes, k.D)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AddedKey{PrivateKey: priv, Comment: k.Comments}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRSACert(req []byte) (*AddedKey, error) {
|
|
||||||
var k rsaCertMsg
|
|
||||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, ok := pubKey.(*ssh.Certificate)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("agent: bad RSA certificate")
|
|
||||||
}
|
|
||||||
|
|
||||||
// An RSA publickey as marshaled by rsaPublicKey.Marshal() in keys.go
|
|
||||||
var rsaPub struct {
|
|
||||||
Name string
|
|
||||||
E *big.Int
|
|
||||||
N *big.Int
|
|
||||||
}
|
|
||||||
if err := ssh.Unmarshal(cert.Key.Marshal(), &rsaPub); err != nil {
|
|
||||||
return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rsaPub.E.BitLen() > 30 {
|
|
||||||
return nil, errors.New("agent: RSA public exponent too large")
|
|
||||||
}
|
|
||||||
|
|
||||||
priv := rsa.PrivateKey{
|
|
||||||
PublicKey: rsa.PublicKey{
|
|
||||||
E: int(rsaPub.E.Int64()),
|
|
||||||
N: rsaPub.N,
|
|
||||||
},
|
|
||||||
D: k.D,
|
|
||||||
Primes: []*big.Int{k.Q, k.P},
|
|
||||||
}
|
|
||||||
priv.Precompute()
|
|
||||||
|
|
||||||
return &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDSACert(req []byte) (*AddedKey, error) {
|
|
||||||
var k dsaCertMsg
|
|
||||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cert, ok := pubKey.(*ssh.Certificate)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("agent: bad DSA certificate")
|
|
||||||
}
|
|
||||||
|
|
||||||
// A DSA publickey as marshaled by dsaPublicKey.Marshal() in keys.go
|
|
||||||
var w struct {
|
|
||||||
Name string
|
|
||||||
P, Q, G, Y *big.Int
|
|
||||||
}
|
|
||||||
if err := ssh.Unmarshal(cert.Key.Marshal(), &w); err != nil {
|
|
||||||
return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
priv := &dsa.PrivateKey{
|
|
||||||
PublicKey: dsa.PublicKey{
|
|
||||||
Parameters: dsa.Parameters{
|
|
||||||
P: w.P,
|
|
||||||
Q: w.Q,
|
|
||||||
G: w.G,
|
|
||||||
},
|
|
||||||
Y: w.Y,
|
|
||||||
},
|
|
||||||
X: k.X,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseECDSACert(req []byte) (*AddedKey, error) {
|
|
||||||
var k ecdsaCertMsg
|
|
||||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cert, ok := pubKey.(*ssh.Certificate)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("agent: bad ECDSA certificate")
|
|
||||||
}
|
|
||||||
|
|
||||||
// An ECDSA publickey as marshaled by ecdsaPublicKey.Marshal() in keys.go
|
|
||||||
var ecdsaPub struct {
|
|
||||||
Name string
|
|
||||||
ID string
|
|
||||||
Key []byte
|
|
||||||
}
|
|
||||||
if err := ssh.Unmarshal(cert.Key.Marshal(), &ecdsaPub); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
priv, err := unmarshalECDSA(ecdsaPub.ID, ecdsaPub.Key, k.D)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) insertIdentity(req []byte) error {
|
|
||||||
var record struct {
|
|
||||||
Type string `sshtype:"17|25"`
|
|
||||||
Rest []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ssh.Unmarshal(req, &record); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var addedKey *AddedKey
|
|
||||||
var err error
|
|
||||||
|
|
||||||
switch record.Type {
|
|
||||||
case ssh.KeyAlgoRSA:
|
|
||||||
addedKey, err = parseRSAKey(req)
|
|
||||||
case ssh.KeyAlgoDSA:
|
|
||||||
addedKey, err = parseDSAKey(req)
|
|
||||||
case ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521:
|
|
||||||
addedKey, err = parseECDSAKey(req)
|
|
||||||
case ssh.KeyAlgoED25519:
|
|
||||||
addedKey, err = parseEd25519Key(req)
|
|
||||||
case ssh.CertAlgoRSAv01:
|
|
||||||
addedKey, err = parseRSACert(req)
|
|
||||||
case ssh.CertAlgoDSAv01:
|
|
||||||
addedKey, err = parseDSACert(req)
|
|
||||||
case ssh.CertAlgoECDSA256v01, ssh.CertAlgoECDSA384v01, ssh.CertAlgoECDSA521v01:
|
|
||||||
addedKey, err = parseECDSACert(req)
|
|
||||||
case ssh.CertAlgoED25519v01:
|
|
||||||
addedKey, err = parseEd25519Cert(req)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("agent: not implemented: %q", record.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.agent.Add(*addedKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeAgent serves the agent protocol on the given connection. It
|
|
||||||
// returns when an I/O error occurs.
|
|
||||||
func ServeAgent(agent Agent, c io.ReadWriter) error {
|
|
||||||
s := &server{agent}
|
|
||||||
|
|
||||||
var length [4]byte
|
|
||||||
for {
|
|
||||||
if _, err := io.ReadFull(c, length[:]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
l := binary.BigEndian.Uint32(length[:])
|
|
||||||
if l > maxAgentResponseBytes {
|
|
||||||
// We also cap requests.
|
|
||||||
return fmt.Errorf("agent: request too large: %d", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
req := make([]byte, l)
|
|
||||||
if _, err := io.ReadFull(c, req); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
repData := s.processRequestBytes(req)
|
|
||||||
if len(repData) > maxAgentResponseBytes {
|
|
||||||
return fmt.Errorf("agent: reply too large: %d bytes", len(repData))
|
|
||||||
}
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint32(length[:], uint32(len(repData)))
|
|
||||||
if _, err := c.Write(length[:]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := c.Write(repData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-98
@@ -1,98 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// buffer provides a linked list buffer for data exchange
|
|
||||||
// between producer and consumer. Theoretically the buffer is
|
|
||||||
// of unlimited capacity as it does no allocation of its own.
|
|
||||||
type buffer struct {
|
|
||||||
// protects concurrent access to head, tail and closed
|
|
||||||
*sync.Cond
|
|
||||||
|
|
||||||
head *element // the buffer that will be read first
|
|
||||||
tail *element // the buffer that will be read last
|
|
||||||
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// An element represents a single link in a linked list.
|
|
||||||
type element struct {
|
|
||||||
buf []byte
|
|
||||||
next *element
|
|
||||||
}
|
|
||||||
|
|
||||||
// newBuffer returns an empty buffer that is not closed.
|
|
||||||
func newBuffer() *buffer {
|
|
||||||
e := new(element)
|
|
||||||
b := &buffer{
|
|
||||||
Cond: newCond(),
|
|
||||||
head: e,
|
|
||||||
tail: e,
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// write makes buf available for Read to receive.
|
|
||||||
// buf must not be modified after the call to write.
|
|
||||||
func (b *buffer) write(buf []byte) {
|
|
||||||
b.Cond.L.Lock()
|
|
||||||
e := &element{buf: buf}
|
|
||||||
b.tail.next = e
|
|
||||||
b.tail = e
|
|
||||||
b.Cond.Signal()
|
|
||||||
b.Cond.L.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// eof closes the buffer. Reads from the buffer once all
|
|
||||||
// the data has been consumed will receive os.EOF.
|
|
||||||
func (b *buffer) eof() error {
|
|
||||||
b.Cond.L.Lock()
|
|
||||||
b.closed = true
|
|
||||||
b.Cond.Signal()
|
|
||||||
b.Cond.L.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads data from the internal buffer in buf. Reads will block
|
|
||||||
// if no data is available, or until the buffer is closed.
|
|
||||||
func (b *buffer) Read(buf []byte) (n int, err error) {
|
|
||||||
b.Cond.L.Lock()
|
|
||||||
defer b.Cond.L.Unlock()
|
|
||||||
|
|
||||||
for len(buf) > 0 {
|
|
||||||
// if there is data in b.head, copy it
|
|
||||||
if len(b.head.buf) > 0 {
|
|
||||||
r := copy(buf, b.head.buf)
|
|
||||||
buf, b.head.buf = buf[r:], b.head.buf[r:]
|
|
||||||
n += r
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// if there is a next buffer, make it the head
|
|
||||||
if len(b.head.buf) == 0 && b.head != b.tail {
|
|
||||||
b.head = b.head.next
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// if at least one byte has been copied, return
|
|
||||||
if n > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// if nothing was read, and there is nothing outstanding
|
|
||||||
// check to see if the buffer is closed.
|
|
||||||
if b.closed {
|
|
||||||
err = io.EOF
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// out of buffers, wait for producer
|
|
||||||
b.Cond.Wait()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
-503
@@ -1,503 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// These constants from [PROTOCOL.certkeys] represent the algorithm names
|
|
||||||
// for certificate types supported by this package.
|
|
||||||
const (
|
|
||||||
CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com"
|
|
||||||
CertAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com"
|
|
||||||
CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com"
|
|
||||||
CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com"
|
|
||||||
CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com"
|
|
||||||
CertAlgoED25519v01 = "ssh-ed25519-cert-v01@openssh.com"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Certificate types distinguish between host and user
|
|
||||||
// certificates. The values can be set in the CertType field of
|
|
||||||
// Certificate.
|
|
||||||
const (
|
|
||||||
UserCert = 1
|
|
||||||
HostCert = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// Signature represents a cryptographic signature.
|
|
||||||
type Signature struct {
|
|
||||||
Format string
|
|
||||||
Blob []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// CertTimeInfinity can be used for OpenSSHCertV01.ValidBefore to indicate that
|
|
||||||
// a certificate does not expire.
|
|
||||||
const CertTimeInfinity = 1<<64 - 1
|
|
||||||
|
|
||||||
// An Certificate represents an OpenSSH certificate as defined in
|
|
||||||
// [PROTOCOL.certkeys]?rev=1.8.
|
|
||||||
type Certificate struct {
|
|
||||||
Nonce []byte
|
|
||||||
Key PublicKey
|
|
||||||
Serial uint64
|
|
||||||
CertType uint32
|
|
||||||
KeyId string
|
|
||||||
ValidPrincipals []string
|
|
||||||
ValidAfter uint64
|
|
||||||
ValidBefore uint64
|
|
||||||
Permissions
|
|
||||||
Reserved []byte
|
|
||||||
SignatureKey PublicKey
|
|
||||||
Signature *Signature
|
|
||||||
}
|
|
||||||
|
|
||||||
// genericCertData holds the key-independent part of the certificate data.
|
|
||||||
// Overall, certificates contain an nonce, public key fields and
|
|
||||||
// key-independent fields.
|
|
||||||
type genericCertData struct {
|
|
||||||
Serial uint64
|
|
||||||
CertType uint32
|
|
||||||
KeyId string
|
|
||||||
ValidPrincipals []byte
|
|
||||||
ValidAfter uint64
|
|
||||||
ValidBefore uint64
|
|
||||||
CriticalOptions []byte
|
|
||||||
Extensions []byte
|
|
||||||
Reserved []byte
|
|
||||||
SignatureKey []byte
|
|
||||||
Signature []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalStringList(namelist []string) []byte {
|
|
||||||
var to []byte
|
|
||||||
for _, name := range namelist {
|
|
||||||
s := struct{ N string }{name}
|
|
||||||
to = append(to, Marshal(&s)...)
|
|
||||||
}
|
|
||||||
return to
|
|
||||||
}
|
|
||||||
|
|
||||||
type optionsTuple struct {
|
|
||||||
Key string
|
|
||||||
Value []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type optionsTupleValue struct {
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
// serialize a map of critical options or extensions
|
|
||||||
// issue #10569 - per [PROTOCOL.certkeys] and SSH implementation,
|
|
||||||
// we need two length prefixes for a non-empty string value
|
|
||||||
func marshalTuples(tups map[string]string) []byte {
|
|
||||||
keys := make([]string, 0, len(tups))
|
|
||||||
for key := range tups {
|
|
||||||
keys = append(keys, key)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
|
|
||||||
var ret []byte
|
|
||||||
for _, key := range keys {
|
|
||||||
s := optionsTuple{Key: key}
|
|
||||||
if value := tups[key]; len(value) > 0 {
|
|
||||||
s.Value = Marshal(&optionsTupleValue{value})
|
|
||||||
}
|
|
||||||
ret = append(ret, Marshal(&s)...)
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// issue #10569 - per [PROTOCOL.certkeys] and SSH implementation,
|
|
||||||
// we need two length prefixes for a non-empty option value
|
|
||||||
func parseTuples(in []byte) (map[string]string, error) {
|
|
||||||
tups := map[string]string{}
|
|
||||||
var lastKey string
|
|
||||||
var haveLastKey bool
|
|
||||||
|
|
||||||
for len(in) > 0 {
|
|
||||||
var key, val, extra []byte
|
|
||||||
var ok bool
|
|
||||||
|
|
||||||
if key, in, ok = parseString(in); !ok {
|
|
||||||
return nil, errShortRead
|
|
||||||
}
|
|
||||||
keyStr := string(key)
|
|
||||||
// according to [PROTOCOL.certkeys], the names must be in
|
|
||||||
// lexical order.
|
|
||||||
if haveLastKey && keyStr <= lastKey {
|
|
||||||
return nil, fmt.Errorf("ssh: certificate options are not in lexical order")
|
|
||||||
}
|
|
||||||
lastKey, haveLastKey = keyStr, true
|
|
||||||
// the next field is a data field, which if non-empty has a string embedded
|
|
||||||
if val, in, ok = parseString(in); !ok {
|
|
||||||
return nil, errShortRead
|
|
||||||
}
|
|
||||||
if len(val) > 0 {
|
|
||||||
val, extra, ok = parseString(val)
|
|
||||||
if !ok {
|
|
||||||
return nil, errShortRead
|
|
||||||
}
|
|
||||||
if len(extra) > 0 {
|
|
||||||
return nil, fmt.Errorf("ssh: unexpected trailing data after certificate option value")
|
|
||||||
}
|
|
||||||
tups[keyStr] = string(val)
|
|
||||||
} else {
|
|
||||||
tups[keyStr] = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tups, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseCert(in []byte, privAlgo string) (*Certificate, error) {
|
|
||||||
nonce, rest, ok := parseString(in)
|
|
||||||
if !ok {
|
|
||||||
return nil, errShortRead
|
|
||||||
}
|
|
||||||
|
|
||||||
key, rest, err := parsePubKey(rest, privAlgo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var g genericCertData
|
|
||||||
if err := Unmarshal(rest, &g); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &Certificate{
|
|
||||||
Nonce: nonce,
|
|
||||||
Key: key,
|
|
||||||
Serial: g.Serial,
|
|
||||||
CertType: g.CertType,
|
|
||||||
KeyId: g.KeyId,
|
|
||||||
ValidAfter: g.ValidAfter,
|
|
||||||
ValidBefore: g.ValidBefore,
|
|
||||||
}
|
|
||||||
|
|
||||||
for principals := g.ValidPrincipals; len(principals) > 0; {
|
|
||||||
principal, rest, ok := parseString(principals)
|
|
||||||
if !ok {
|
|
||||||
return nil, errShortRead
|
|
||||||
}
|
|
||||||
c.ValidPrincipals = append(c.ValidPrincipals, string(principal))
|
|
||||||
principals = rest
|
|
||||||
}
|
|
||||||
|
|
||||||
c.CriticalOptions, err = parseTuples(g.CriticalOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.Extensions, err = parseTuples(g.Extensions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.Reserved = g.Reserved
|
|
||||||
k, err := ParsePublicKey(g.SignatureKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.SignatureKey = k
|
|
||||||
c.Signature, rest, ok = parseSignatureBody(g.Signature)
|
|
||||||
if !ok || len(rest) > 0 {
|
|
||||||
return nil, errors.New("ssh: signature parse error")
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type openSSHCertSigner struct {
|
|
||||||
pub *Certificate
|
|
||||||
signer Signer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCertSigner returns a Signer that signs with the given Certificate, whose
|
|
||||||
// private key is held by signer. It returns an error if the public key in cert
|
|
||||||
// doesn't match the key used by signer.
|
|
||||||
func NewCertSigner(cert *Certificate, signer Signer) (Signer, error) {
|
|
||||||
if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 {
|
|
||||||
return nil, errors.New("ssh: signer and cert have different public key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &openSSHCertSigner{cert, signer}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *openSSHCertSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
|
||||||
return s.signer.Sign(rand, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *openSSHCertSigner) PublicKey() PublicKey {
|
|
||||||
return s.pub
|
|
||||||
}
|
|
||||||
|
|
||||||
const sourceAddressCriticalOption = "source-address"
|
|
||||||
|
|
||||||
// CertChecker does the work of verifying a certificate. Its methods
|
|
||||||
// can be plugged into ClientConfig.HostKeyCallback and
|
|
||||||
// ServerConfig.PublicKeyCallback. For the CertChecker to work,
|
|
||||||
// minimally, the IsAuthority callback should be set.
|
|
||||||
type CertChecker struct {
|
|
||||||
// SupportedCriticalOptions lists the CriticalOptions that the
|
|
||||||
// server application layer understands. These are only used
|
|
||||||
// for user certificates.
|
|
||||||
SupportedCriticalOptions []string
|
|
||||||
|
|
||||||
// IsAuthority should return true if the key is recognized as
|
|
||||||
// an authority. This allows for certificates to be signed by other
|
|
||||||
// certificates.
|
|
||||||
IsAuthority func(auth PublicKey) bool
|
|
||||||
|
|
||||||
// Clock is used for verifying time stamps. If nil, time.Now
|
|
||||||
// is used.
|
|
||||||
Clock func() time.Time
|
|
||||||
|
|
||||||
// UserKeyFallback is called when CertChecker.Authenticate encounters a
|
|
||||||
// public key that is not a certificate. It must implement validation
|
|
||||||
// of user keys or else, if nil, all such keys are rejected.
|
|
||||||
UserKeyFallback func(conn ConnMetadata, key PublicKey) (*Permissions, error)
|
|
||||||
|
|
||||||
// HostKeyFallback is called when CertChecker.CheckHostKey encounters a
|
|
||||||
// public key that is not a certificate. It must implement host key
|
|
||||||
// validation or else, if nil, all such keys are rejected.
|
|
||||||
HostKeyFallback HostKeyCallback
|
|
||||||
|
|
||||||
// IsRevoked is called for each certificate so that revocation checking
|
|
||||||
// can be implemented. It should return true if the given certificate
|
|
||||||
// is revoked and false otherwise. If nil, no certificates are
|
|
||||||
// considered to have been revoked.
|
|
||||||
IsRevoked func(cert *Certificate) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckHostKey checks a host key certificate. This method can be
|
|
||||||
// plugged into ClientConfig.HostKeyCallback.
|
|
||||||
func (c *CertChecker) CheckHostKey(addr string, remote net.Addr, key PublicKey) error {
|
|
||||||
cert, ok := key.(*Certificate)
|
|
||||||
if !ok {
|
|
||||||
if c.HostKeyFallback != nil {
|
|
||||||
return c.HostKeyFallback(addr, remote, key)
|
|
||||||
}
|
|
||||||
return errors.New("ssh: non-certificate host key")
|
|
||||||
}
|
|
||||||
if cert.CertType != HostCert {
|
|
||||||
return fmt.Errorf("ssh: certificate presented as a host key has type %d", cert.CertType)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.CheckCert(addr, cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authenticate checks a user certificate. Authenticate can be used as
|
|
||||||
// a value for ServerConfig.PublicKeyCallback.
|
|
||||||
func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permissions, error) {
|
|
||||||
cert, ok := pubKey.(*Certificate)
|
|
||||||
if !ok {
|
|
||||||
if c.UserKeyFallback != nil {
|
|
||||||
return c.UserKeyFallback(conn, pubKey)
|
|
||||||
}
|
|
||||||
return nil, errors.New("ssh: normal key pairs not accepted")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cert.CertType != UserCert {
|
|
||||||
return nil, fmt.Errorf("ssh: cert has type %d", cert.CertType)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.CheckCert(conn.User(), cert); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &cert.Permissions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckCert checks CriticalOptions, ValidPrincipals, revocation, timestamp and
|
|
||||||
// the signature of the certificate.
|
|
||||||
func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
|
||||||
if c.IsRevoked != nil && c.IsRevoked(cert) {
|
|
||||||
return fmt.Errorf("ssh: certicate serial %d revoked", cert.Serial)
|
|
||||||
}
|
|
||||||
|
|
||||||
for opt, _ := range cert.CriticalOptions {
|
|
||||||
// sourceAddressCriticalOption will be enforced by
|
|
||||||
// serverAuthenticate
|
|
||||||
if opt == sourceAddressCriticalOption {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
found := false
|
|
||||||
for _, supp := range c.SupportedCriticalOptions {
|
|
||||||
if supp == opt {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return fmt.Errorf("ssh: unsupported critical option %q in certificate", opt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cert.ValidPrincipals) > 0 {
|
|
||||||
// By default, certs are valid for all users/hosts.
|
|
||||||
found := false
|
|
||||||
for _, p := range cert.ValidPrincipals {
|
|
||||||
if p == principal {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return fmt.Errorf("ssh: principal %q not in the set of valid principals for given certificate: %q", principal, cert.ValidPrincipals)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.IsAuthority(cert.SignatureKey) {
|
|
||||||
return fmt.Errorf("ssh: certificate signed by unrecognized authority")
|
|
||||||
}
|
|
||||||
|
|
||||||
clock := c.Clock
|
|
||||||
if clock == nil {
|
|
||||||
clock = time.Now
|
|
||||||
}
|
|
||||||
|
|
||||||
unixNow := clock().Unix()
|
|
||||||
if after := int64(cert.ValidAfter); after < 0 || unixNow < int64(cert.ValidAfter) {
|
|
||||||
return fmt.Errorf("ssh: cert is not yet valid")
|
|
||||||
}
|
|
||||||
if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(CertTimeInfinity) && (unixNow >= before || before < 0) {
|
|
||||||
return fmt.Errorf("ssh: cert has expired")
|
|
||||||
}
|
|
||||||
if err := cert.SignatureKey.Verify(cert.bytesForSigning(), cert.Signature); err != nil {
|
|
||||||
return fmt.Errorf("ssh: certificate signature does not verify")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignCert sets c.SignatureKey to the authority's public key and stores a
|
|
||||||
// Signature, by authority, in the certificate.
|
|
||||||
func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
|
|
||||||
c.Nonce = make([]byte, 32)
|
|
||||||
if _, err := io.ReadFull(rand, c.Nonce); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.SignatureKey = authority.PublicKey()
|
|
||||||
|
|
||||||
sig, err := authority.Sign(rand, c.bytesForSigning())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.Signature = sig
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var certAlgoNames = map[string]string{
|
|
||||||
KeyAlgoRSA: CertAlgoRSAv01,
|
|
||||||
KeyAlgoDSA: CertAlgoDSAv01,
|
|
||||||
KeyAlgoECDSA256: CertAlgoECDSA256v01,
|
|
||||||
KeyAlgoECDSA384: CertAlgoECDSA384v01,
|
|
||||||
KeyAlgoECDSA521: CertAlgoECDSA521v01,
|
|
||||||
KeyAlgoED25519: CertAlgoED25519v01,
|
|
||||||
}
|
|
||||||
|
|
||||||
// certToPrivAlgo returns the underlying algorithm for a certificate algorithm.
|
|
||||||
// Panics if a non-certificate algorithm is passed.
|
|
||||||
func certToPrivAlgo(algo string) string {
|
|
||||||
for privAlgo, pubAlgo := range certAlgoNames {
|
|
||||||
if pubAlgo == algo {
|
|
||||||
return privAlgo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic("unknown cert algorithm")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cert *Certificate) bytesForSigning() []byte {
|
|
||||||
c2 := *cert
|
|
||||||
c2.Signature = nil
|
|
||||||
out := c2.Marshal()
|
|
||||||
// Drop trailing signature length.
|
|
||||||
return out[:len(out)-4]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal serializes c into OpenSSH's wire format. It is part of the
|
|
||||||
// PublicKey interface.
|
|
||||||
func (c *Certificate) Marshal() []byte {
|
|
||||||
generic := genericCertData{
|
|
||||||
Serial: c.Serial,
|
|
||||||
CertType: c.CertType,
|
|
||||||
KeyId: c.KeyId,
|
|
||||||
ValidPrincipals: marshalStringList(c.ValidPrincipals),
|
|
||||||
ValidAfter: uint64(c.ValidAfter),
|
|
||||||
ValidBefore: uint64(c.ValidBefore),
|
|
||||||
CriticalOptions: marshalTuples(c.CriticalOptions),
|
|
||||||
Extensions: marshalTuples(c.Extensions),
|
|
||||||
Reserved: c.Reserved,
|
|
||||||
SignatureKey: c.SignatureKey.Marshal(),
|
|
||||||
}
|
|
||||||
if c.Signature != nil {
|
|
||||||
generic.Signature = Marshal(c.Signature)
|
|
||||||
}
|
|
||||||
genericBytes := Marshal(&generic)
|
|
||||||
keyBytes := c.Key.Marshal()
|
|
||||||
_, keyBytes, _ = parseString(keyBytes)
|
|
||||||
prefix := Marshal(&struct {
|
|
||||||
Name string
|
|
||||||
Nonce []byte
|
|
||||||
Key []byte `ssh:"rest"`
|
|
||||||
}{c.Type(), c.Nonce, keyBytes})
|
|
||||||
|
|
||||||
result := make([]byte, 0, len(prefix)+len(genericBytes))
|
|
||||||
result = append(result, prefix...)
|
|
||||||
result = append(result, genericBytes...)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the key name. It is part of the PublicKey interface.
|
|
||||||
func (c *Certificate) Type() string {
|
|
||||||
algo, ok := certAlgoNames[c.Key.Type()]
|
|
||||||
if !ok {
|
|
||||||
panic("unknown cert key type " + c.Key.Type())
|
|
||||||
}
|
|
||||||
return algo
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify verifies a signature against the certificate's public
|
|
||||||
// key. It is part of the PublicKey interface.
|
|
||||||
func (c *Certificate) Verify(data []byte, sig *Signature) error {
|
|
||||||
return c.Key.Verify(data, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSignatureBody(in []byte) (out *Signature, rest []byte, ok bool) {
|
|
||||||
format, in, ok := parseString(in)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
out = &Signature{
|
|
||||||
Format: string(format),
|
|
||||||
}
|
|
||||||
|
|
||||||
if out.Blob, in, ok = parseString(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, in, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSignature(in []byte) (out *Signature, rest []byte, ok bool) {
|
|
||||||
sigBytes, rest, ok := parseString(in)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
out, trailing, ok := parseSignatureBody(sigBytes)
|
|
||||||
if !ok || len(trailing) > 0 {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
-633
@@ -1,633 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
minPacketLength = 9
|
|
||||||
// channelMaxPacket contains the maximum number of bytes that will be
|
|
||||||
// sent in a single packet. As per RFC 4253, section 6.1, 32k is also
|
|
||||||
// the minimum.
|
|
||||||
channelMaxPacket = 1 << 15
|
|
||||||
// We follow OpenSSH here.
|
|
||||||
channelWindowSize = 64 * channelMaxPacket
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewChannel represents an incoming request to a channel. It must either be
|
|
||||||
// accepted for use by calling Accept, or rejected by calling Reject.
|
|
||||||
type NewChannel interface {
|
|
||||||
// Accept accepts the channel creation request. It returns the Channel
|
|
||||||
// and a Go channel containing SSH requests. The Go channel must be
|
|
||||||
// serviced otherwise the Channel will hang.
|
|
||||||
Accept() (Channel, <-chan *Request, error)
|
|
||||||
|
|
||||||
// Reject rejects the channel creation request. After calling
|
|
||||||
// this, no other methods on the Channel may be called.
|
|
||||||
Reject(reason RejectionReason, message string) error
|
|
||||||
|
|
||||||
// ChannelType returns the type of the channel, as supplied by the
|
|
||||||
// client.
|
|
||||||
ChannelType() string
|
|
||||||
|
|
||||||
// ExtraData returns the arbitrary payload for this channel, as supplied
|
|
||||||
// by the client. This data is specific to the channel type.
|
|
||||||
ExtraData() []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Channel is an ordered, reliable, flow-controlled, duplex stream
|
|
||||||
// that is multiplexed over an SSH connection.
|
|
||||||
type Channel interface {
|
|
||||||
// Read reads up to len(data) bytes from the channel.
|
|
||||||
Read(data []byte) (int, error)
|
|
||||||
|
|
||||||
// Write writes len(data) bytes to the channel.
|
|
||||||
Write(data []byte) (int, error)
|
|
||||||
|
|
||||||
// Close signals end of channel use. No data may be sent after this
|
|
||||||
// call.
|
|
||||||
Close() error
|
|
||||||
|
|
||||||
// CloseWrite signals the end of sending in-band
|
|
||||||
// data. Requests may still be sent, and the other side may
|
|
||||||
// still send data
|
|
||||||
CloseWrite() error
|
|
||||||
|
|
||||||
// SendRequest sends a channel request. If wantReply is true,
|
|
||||||
// it will wait for a reply and return the result as a
|
|
||||||
// boolean, otherwise the return value will be false. Channel
|
|
||||||
// requests are out-of-band messages so they may be sent even
|
|
||||||
// if the data stream is closed or blocked by flow control.
|
|
||||||
// If the channel is closed before a reply is returned, io.EOF
|
|
||||||
// is returned.
|
|
||||||
SendRequest(name string, wantReply bool, payload []byte) (bool, error)
|
|
||||||
|
|
||||||
// Stderr returns an io.ReadWriter that writes to this channel
|
|
||||||
// with the extended data type set to stderr. Stderr may
|
|
||||||
// safely be read and written from a different goroutine than
|
|
||||||
// Read and Write respectively.
|
|
||||||
Stderr() io.ReadWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request is a request sent outside of the normal stream of
|
|
||||||
// data. Requests can either be specific to an SSH channel, or they
|
|
||||||
// can be global.
|
|
||||||
type Request struct {
|
|
||||||
Type string
|
|
||||||
WantReply bool
|
|
||||||
Payload []byte
|
|
||||||
|
|
||||||
ch *channel
|
|
||||||
mux *mux
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reply sends a response to a request. It must be called for all requests
|
|
||||||
// where WantReply is true and is a no-op otherwise. The payload argument is
|
|
||||||
// ignored for replies to channel-specific requests.
|
|
||||||
func (r *Request) Reply(ok bool, payload []byte) error {
|
|
||||||
if !r.WantReply {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.ch == nil {
|
|
||||||
return r.mux.ackRequest(ok, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.ch.ackRequest(ok)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RejectionReason is an enumeration used when rejecting channel creation
|
|
||||||
// requests. See RFC 4254, section 5.1.
|
|
||||||
type RejectionReason uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
Prohibited RejectionReason = iota + 1
|
|
||||||
ConnectionFailed
|
|
||||||
UnknownChannelType
|
|
||||||
ResourceShortage
|
|
||||||
)
|
|
||||||
|
|
||||||
// String converts the rejection reason to human readable form.
|
|
||||||
func (r RejectionReason) String() string {
|
|
||||||
switch r {
|
|
||||||
case Prohibited:
|
|
||||||
return "administratively prohibited"
|
|
||||||
case ConnectionFailed:
|
|
||||||
return "connect failed"
|
|
||||||
case UnknownChannelType:
|
|
||||||
return "unknown channel type"
|
|
||||||
case ResourceShortage:
|
|
||||||
return "resource shortage"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("unknown reason %d", int(r))
|
|
||||||
}
|
|
||||||
|
|
||||||
func min(a uint32, b int) uint32 {
|
|
||||||
if a < uint32(b) {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return uint32(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
type channelDirection uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
channelInbound channelDirection = iota
|
|
||||||
channelOutbound
|
|
||||||
)
|
|
||||||
|
|
||||||
// channel is an implementation of the Channel interface that works
|
|
||||||
// with the mux class.
|
|
||||||
type channel struct {
|
|
||||||
// R/O after creation
|
|
||||||
chanType string
|
|
||||||
extraData []byte
|
|
||||||
localId, remoteId uint32
|
|
||||||
|
|
||||||
// maxIncomingPayload and maxRemotePayload are the maximum
|
|
||||||
// payload sizes of normal and extended data packets for
|
|
||||||
// receiving and sending, respectively. The wire packet will
|
|
||||||
// be 9 or 13 bytes larger (excluding encryption overhead).
|
|
||||||
maxIncomingPayload uint32
|
|
||||||
maxRemotePayload uint32
|
|
||||||
|
|
||||||
mux *mux
|
|
||||||
|
|
||||||
// decided is set to true if an accept or reject message has been sent
|
|
||||||
// (for outbound channels) or received (for inbound channels).
|
|
||||||
decided bool
|
|
||||||
|
|
||||||
// direction contains either channelOutbound, for channels created
|
|
||||||
// locally, or channelInbound, for channels created by the peer.
|
|
||||||
direction channelDirection
|
|
||||||
|
|
||||||
// Pending internal channel messages.
|
|
||||||
msg chan interface{}
|
|
||||||
|
|
||||||
// Since requests have no ID, there can be only one request
|
|
||||||
// with WantReply=true outstanding. This lock is held by a
|
|
||||||
// goroutine that has such an outgoing request pending.
|
|
||||||
sentRequestMu sync.Mutex
|
|
||||||
|
|
||||||
incomingRequests chan *Request
|
|
||||||
|
|
||||||
sentEOF bool
|
|
||||||
|
|
||||||
// thread-safe data
|
|
||||||
remoteWin window
|
|
||||||
pending *buffer
|
|
||||||
extPending *buffer
|
|
||||||
|
|
||||||
// windowMu protects myWindow, the flow-control window.
|
|
||||||
windowMu sync.Mutex
|
|
||||||
myWindow uint32
|
|
||||||
|
|
||||||
// writeMu serializes calls to mux.conn.writePacket() and
|
|
||||||
// protects sentClose and packetPool. This mutex must be
|
|
||||||
// different from windowMu, as writePacket can block if there
|
|
||||||
// is a key exchange pending.
|
|
||||||
writeMu sync.Mutex
|
|
||||||
sentClose bool
|
|
||||||
|
|
||||||
// packetPool has a buffer for each extended channel ID to
|
|
||||||
// save allocations during writes.
|
|
||||||
packetPool map[uint32][]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// writePacket sends a packet. If the packet is a channel close, it updates
|
|
||||||
// sentClose. This method takes the lock c.writeMu.
|
|
||||||
func (c *channel) writePacket(packet []byte) error {
|
|
||||||
c.writeMu.Lock()
|
|
||||||
if c.sentClose {
|
|
||||||
c.writeMu.Unlock()
|
|
||||||
return io.EOF
|
|
||||||
}
|
|
||||||
c.sentClose = (packet[0] == msgChannelClose)
|
|
||||||
err := c.mux.conn.writePacket(packet)
|
|
||||||
c.writeMu.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *channel) sendMessage(msg interface{}) error {
|
|
||||||
if debugMux {
|
|
||||||
log.Printf("send(%d): %#v", c.mux.chanList.offset, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
p := Marshal(msg)
|
|
||||||
binary.BigEndian.PutUint32(p[1:], c.remoteId)
|
|
||||||
return c.writePacket(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteExtended writes data to a specific extended stream. These streams are
|
|
||||||
// used, for example, for stderr.
|
|
||||||
func (c *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err error) {
|
|
||||||
if c.sentEOF {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
// 1 byte message type, 4 bytes remoteId, 4 bytes data length
|
|
||||||
opCode := byte(msgChannelData)
|
|
||||||
headerLength := uint32(9)
|
|
||||||
if extendedCode > 0 {
|
|
||||||
headerLength += 4
|
|
||||||
opCode = msgChannelExtendedData
|
|
||||||
}
|
|
||||||
|
|
||||||
c.writeMu.Lock()
|
|
||||||
packet := c.packetPool[extendedCode]
|
|
||||||
// We don't remove the buffer from packetPool, so
|
|
||||||
// WriteExtended calls from different goroutines will be
|
|
||||||
// flagged as errors by the race detector.
|
|
||||||
c.writeMu.Unlock()
|
|
||||||
|
|
||||||
for len(data) > 0 {
|
|
||||||
space := min(c.maxRemotePayload, len(data))
|
|
||||||
if space, err = c.remoteWin.reserve(space); err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
if want := headerLength + space; uint32(cap(packet)) < want {
|
|
||||||
packet = make([]byte, want)
|
|
||||||
} else {
|
|
||||||
packet = packet[:want]
|
|
||||||
}
|
|
||||||
|
|
||||||
todo := data[:space]
|
|
||||||
|
|
||||||
packet[0] = opCode
|
|
||||||
binary.BigEndian.PutUint32(packet[1:], c.remoteId)
|
|
||||||
if extendedCode > 0 {
|
|
||||||
binary.BigEndian.PutUint32(packet[5:], uint32(extendedCode))
|
|
||||||
}
|
|
||||||
binary.BigEndian.PutUint32(packet[headerLength-4:], uint32(len(todo)))
|
|
||||||
copy(packet[headerLength:], todo)
|
|
||||||
if err = c.writePacket(packet); err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
n += len(todo)
|
|
||||||
data = data[len(todo):]
|
|
||||||
}
|
|
||||||
|
|
||||||
c.writeMu.Lock()
|
|
||||||
c.packetPool[extendedCode] = packet
|
|
||||||
c.writeMu.Unlock()
|
|
||||||
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *channel) handleData(packet []byte) error {
|
|
||||||
headerLen := 9
|
|
||||||
isExtendedData := packet[0] == msgChannelExtendedData
|
|
||||||
if isExtendedData {
|
|
||||||
headerLen = 13
|
|
||||||
}
|
|
||||||
if len(packet) < headerLen {
|
|
||||||
// malformed data packet
|
|
||||||
return parseError(packet[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
var extended uint32
|
|
||||||
if isExtendedData {
|
|
||||||
extended = binary.BigEndian.Uint32(packet[5:])
|
|
||||||
}
|
|
||||||
|
|
||||||
length := binary.BigEndian.Uint32(packet[headerLen-4 : headerLen])
|
|
||||||
if length == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if length > c.maxIncomingPayload {
|
|
||||||
// TODO(hanwen): should send Disconnect?
|
|
||||||
return errors.New("ssh: incoming packet exceeds maximum payload size")
|
|
||||||
}
|
|
||||||
|
|
||||||
data := packet[headerLen:]
|
|
||||||
if length != uint32(len(data)) {
|
|
||||||
return errors.New("ssh: wrong packet length")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.windowMu.Lock()
|
|
||||||
if c.myWindow < length {
|
|
||||||
c.windowMu.Unlock()
|
|
||||||
// TODO(hanwen): should send Disconnect with reason?
|
|
||||||
return errors.New("ssh: remote side wrote too much")
|
|
||||||
}
|
|
||||||
c.myWindow -= length
|
|
||||||
c.windowMu.Unlock()
|
|
||||||
|
|
||||||
if extended == 1 {
|
|
||||||
c.extPending.write(data)
|
|
||||||
} else if extended > 0 {
|
|
||||||
// discard other extended data.
|
|
||||||
} else {
|
|
||||||
c.pending.write(data)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *channel) adjustWindow(n uint32) error {
|
|
||||||
c.windowMu.Lock()
|
|
||||||
// Since myWindow is managed on our side, and can never exceed
|
|
||||||
// the initial window setting, we don't worry about overflow.
|
|
||||||
c.myWindow += uint32(n)
|
|
||||||
c.windowMu.Unlock()
|
|
||||||
return c.sendMessage(windowAdjustMsg{
|
|
||||||
AdditionalBytes: uint32(n),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *channel) ReadExtended(data []byte, extended uint32) (n int, err error) {
|
|
||||||
switch extended {
|
|
||||||
case 1:
|
|
||||||
n, err = c.extPending.Read(data)
|
|
||||||
case 0:
|
|
||||||
n, err = c.pending.Read(data)
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("ssh: extended code %d unimplemented", extended)
|
|
||||||
}
|
|
||||||
|
|
||||||
if n > 0 {
|
|
||||||
err = c.adjustWindow(uint32(n))
|
|
||||||
// sendWindowAdjust can return io.EOF if the remote
|
|
||||||
// peer has closed the connection, however we want to
|
|
||||||
// defer forwarding io.EOF to the caller of Read until
|
|
||||||
// the buffer has been drained.
|
|
||||||
if n > 0 && err == io.EOF {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *channel) close() {
|
|
||||||
c.pending.eof()
|
|
||||||
c.extPending.eof()
|
|
||||||
close(c.msg)
|
|
||||||
close(c.incomingRequests)
|
|
||||||
c.writeMu.Lock()
|
|
||||||
// This is not necessary for a normal channel teardown, but if
|
|
||||||
// there was another error, it is.
|
|
||||||
c.sentClose = true
|
|
||||||
c.writeMu.Unlock()
|
|
||||||
// Unblock writers.
|
|
||||||
c.remoteWin.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// responseMessageReceived is called when a success or failure message is
|
|
||||||
// received on a channel to check that such a message is reasonable for the
|
|
||||||
// given channel.
|
|
||||||
func (c *channel) responseMessageReceived() error {
|
|
||||||
if c.direction == channelInbound {
|
|
||||||
return errors.New("ssh: channel response message received on inbound channel")
|
|
||||||
}
|
|
||||||
if c.decided {
|
|
||||||
return errors.New("ssh: duplicate response received for channel")
|
|
||||||
}
|
|
||||||
c.decided = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *channel) handlePacket(packet []byte) error {
|
|
||||||
switch packet[0] {
|
|
||||||
case msgChannelData, msgChannelExtendedData:
|
|
||||||
return c.handleData(packet)
|
|
||||||
case msgChannelClose:
|
|
||||||
c.sendMessage(channelCloseMsg{PeersId: c.remoteId})
|
|
||||||
c.mux.chanList.remove(c.localId)
|
|
||||||
c.close()
|
|
||||||
return nil
|
|
||||||
case msgChannelEOF:
|
|
||||||
// RFC 4254 is mute on how EOF affects dataExt messages but
|
|
||||||
// it is logical to signal EOF at the same time.
|
|
||||||
c.extPending.eof()
|
|
||||||
c.pending.eof()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
decoded, err := decode(packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch msg := decoded.(type) {
|
|
||||||
case *channelOpenFailureMsg:
|
|
||||||
if err := c.responseMessageReceived(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.mux.chanList.remove(msg.PeersId)
|
|
||||||
c.msg <- msg
|
|
||||||
case *channelOpenConfirmMsg:
|
|
||||||
if err := c.responseMessageReceived(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
|
|
||||||
return fmt.Errorf("ssh: invalid MaxPacketSize %d from peer", msg.MaxPacketSize)
|
|
||||||
}
|
|
||||||
c.remoteId = msg.MyId
|
|
||||||
c.maxRemotePayload = msg.MaxPacketSize
|
|
||||||
c.remoteWin.add(msg.MyWindow)
|
|
||||||
c.msg <- msg
|
|
||||||
case *windowAdjustMsg:
|
|
||||||
if !c.remoteWin.add(msg.AdditionalBytes) {
|
|
||||||
return fmt.Errorf("ssh: invalid window update for %d bytes", msg.AdditionalBytes)
|
|
||||||
}
|
|
||||||
case *channelRequestMsg:
|
|
||||||
req := Request{
|
|
||||||
Type: msg.Request,
|
|
||||||
WantReply: msg.WantReply,
|
|
||||||
Payload: msg.RequestSpecificData,
|
|
||||||
ch: c,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.incomingRequests <- &req
|
|
||||||
default:
|
|
||||||
c.msg <- msg
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mux) newChannel(chanType string, direction channelDirection, extraData []byte) *channel {
|
|
||||||
ch := &channel{
|
|
||||||
remoteWin: window{Cond: newCond()},
|
|
||||||
myWindow: channelWindowSize,
|
|
||||||
pending: newBuffer(),
|
|
||||||
extPending: newBuffer(),
|
|
||||||
direction: direction,
|
|
||||||
incomingRequests: make(chan *Request, chanSize),
|
|
||||||
msg: make(chan interface{}, chanSize),
|
|
||||||
chanType: chanType,
|
|
||||||
extraData: extraData,
|
|
||||||
mux: m,
|
|
||||||
packetPool: make(map[uint32][]byte),
|
|
||||||
}
|
|
||||||
ch.localId = m.chanList.add(ch)
|
|
||||||
return ch
|
|
||||||
}
|
|
||||||
|
|
||||||
var errUndecided = errors.New("ssh: must Accept or Reject channel")
|
|
||||||
var errDecidedAlready = errors.New("ssh: can call Accept or Reject only once")
|
|
||||||
|
|
||||||
type extChannel struct {
|
|
||||||
code uint32
|
|
||||||
ch *channel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *extChannel) Write(data []byte) (n int, err error) {
|
|
||||||
return e.ch.WriteExtended(data, e.code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *extChannel) Read(data []byte) (n int, err error) {
|
|
||||||
return e.ch.ReadExtended(data, e.code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *channel) Accept() (Channel, <-chan *Request, error) {
|
|
||||||
if c.decided {
|
|
||||||
return nil, nil, errDecidedAlready
|
|
||||||
}
|
|
||||||
c.maxIncomingPayload = channelMaxPacket
|
|
||||||
confirm := channelOpenConfirmMsg{
|
|
||||||
PeersId: c.remoteId,
|
|
||||||
MyId: c.localId,
|
|
||||||
MyWindow: c.myWindow,
|
|
||||||
MaxPacketSize: c.maxIncomingPayload,
|
|
||||||
}
|
|
||||||
c.decided = true
|
|
||||||
if err := c.sendMessage(confirm); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, c.incomingRequests, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch *channel) Reject(reason RejectionReason, message string) error {
|
|
||||||
if ch.decided {
|
|
||||||
return errDecidedAlready
|
|
||||||
}
|
|
||||||
reject := channelOpenFailureMsg{
|
|
||||||
PeersId: ch.remoteId,
|
|
||||||
Reason: reason,
|
|
||||||
Message: message,
|
|
||||||
Language: "en",
|
|
||||||
}
|
|
||||||
ch.decided = true
|
|
||||||
return ch.sendMessage(reject)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch *channel) Read(data []byte) (int, error) {
|
|
||||||
if !ch.decided {
|
|
||||||
return 0, errUndecided
|
|
||||||
}
|
|
||||||
return ch.ReadExtended(data, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch *channel) Write(data []byte) (int, error) {
|
|
||||||
if !ch.decided {
|
|
||||||
return 0, errUndecided
|
|
||||||
}
|
|
||||||
return ch.WriteExtended(data, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch *channel) CloseWrite() error {
|
|
||||||
if !ch.decided {
|
|
||||||
return errUndecided
|
|
||||||
}
|
|
||||||
ch.sentEOF = true
|
|
||||||
return ch.sendMessage(channelEOFMsg{
|
|
||||||
PeersId: ch.remoteId})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch *channel) Close() error {
|
|
||||||
if !ch.decided {
|
|
||||||
return errUndecided
|
|
||||||
}
|
|
||||||
|
|
||||||
return ch.sendMessage(channelCloseMsg{
|
|
||||||
PeersId: ch.remoteId})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extended returns an io.ReadWriter that sends and receives data on the given,
|
|
||||||
// SSH extended stream. Such streams are used, for example, for stderr.
|
|
||||||
func (ch *channel) Extended(code uint32) io.ReadWriter {
|
|
||||||
if !ch.decided {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &extChannel{code, ch}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch *channel) Stderr() io.ReadWriter {
|
|
||||||
return ch.Extended(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch *channel) SendRequest(name string, wantReply bool, payload []byte) (bool, error) {
|
|
||||||
if !ch.decided {
|
|
||||||
return false, errUndecided
|
|
||||||
}
|
|
||||||
|
|
||||||
if wantReply {
|
|
||||||
ch.sentRequestMu.Lock()
|
|
||||||
defer ch.sentRequestMu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := channelRequestMsg{
|
|
||||||
PeersId: ch.remoteId,
|
|
||||||
Request: name,
|
|
||||||
WantReply: wantReply,
|
|
||||||
RequestSpecificData: payload,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ch.sendMessage(msg); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if wantReply {
|
|
||||||
m, ok := (<-ch.msg)
|
|
||||||
if !ok {
|
|
||||||
return false, io.EOF
|
|
||||||
}
|
|
||||||
switch m.(type) {
|
|
||||||
case *channelRequestFailureMsg:
|
|
||||||
return false, nil
|
|
||||||
case *channelRequestSuccessMsg:
|
|
||||||
return true, nil
|
|
||||||
default:
|
|
||||||
return false, fmt.Errorf("ssh: unexpected response to channel request: %#v", m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ackRequest either sends an ack or nack to the channel request.
|
|
||||||
func (ch *channel) ackRequest(ok bool) error {
|
|
||||||
if !ch.decided {
|
|
||||||
return errUndecided
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg interface{}
|
|
||||||
if !ok {
|
|
||||||
msg = channelRequestFailureMsg{
|
|
||||||
PeersId: ch.remoteId,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
msg = channelRequestSuccessMsg{
|
|
||||||
PeersId: ch.remoteId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ch.sendMessage(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch *channel) ChannelType() string {
|
|
||||||
return ch.chanType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch *channel) ExtraData() []byte {
|
|
||||||
return ch.extraData
|
|
||||||
}
|
|
||||||
-627
@@ -1,627 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/des"
|
|
||||||
"crypto/rc4"
|
|
||||||
"crypto/subtle"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher.
|
|
||||||
|
|
||||||
// RFC 4253 section 6.1 defines a minimum packet size of 32768 that implementations
|
|
||||||
// MUST be able to process (plus a few more kilobytes for padding and mac). The RFC
|
|
||||||
// indicates implementations SHOULD be able to handle larger packet sizes, but then
|
|
||||||
// waffles on about reasonable limits.
|
|
||||||
//
|
|
||||||
// OpenSSH caps their maxPacket at 256kB so we choose to do
|
|
||||||
// the same. maxPacket is also used to ensure that uint32
|
|
||||||
// length fields do not overflow, so it should remain well
|
|
||||||
// below 4G.
|
|
||||||
maxPacket = 256 * 1024
|
|
||||||
)
|
|
||||||
|
|
||||||
// noneCipher implements cipher.Stream and provides no encryption. It is used
|
|
||||||
// by the transport before the first key-exchange.
|
|
||||||
type noneCipher struct{}
|
|
||||||
|
|
||||||
func (c noneCipher) XORKeyStream(dst, src []byte) {
|
|
||||||
copy(dst, src)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAESCTR(key, iv []byte) (cipher.Stream, error) {
|
|
||||||
c, err := aes.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return cipher.NewCTR(c, iv), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRC4(key, iv []byte) (cipher.Stream, error) {
|
|
||||||
return rc4.NewCipher(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
type streamCipherMode struct {
|
|
||||||
keySize int
|
|
||||||
ivSize int
|
|
||||||
skip int
|
|
||||||
createFunc func(key, iv []byte) (cipher.Stream, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *streamCipherMode) createStream(key, iv []byte) (cipher.Stream, error) {
|
|
||||||
if len(key) < c.keySize {
|
|
||||||
panic("ssh: key length too small for cipher")
|
|
||||||
}
|
|
||||||
if len(iv) < c.ivSize {
|
|
||||||
panic("ssh: iv too small for cipher")
|
|
||||||
}
|
|
||||||
|
|
||||||
stream, err := c.createFunc(key[:c.keySize], iv[:c.ivSize])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var streamDump []byte
|
|
||||||
if c.skip > 0 {
|
|
||||||
streamDump = make([]byte, 512)
|
|
||||||
}
|
|
||||||
|
|
||||||
for remainingToDump := c.skip; remainingToDump > 0; {
|
|
||||||
dumpThisTime := remainingToDump
|
|
||||||
if dumpThisTime > len(streamDump) {
|
|
||||||
dumpThisTime = len(streamDump)
|
|
||||||
}
|
|
||||||
stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
|
|
||||||
remainingToDump -= dumpThisTime
|
|
||||||
}
|
|
||||||
|
|
||||||
return stream, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cipherModes documents properties of supported ciphers. Ciphers not included
|
|
||||||
// are not supported and will not be negotiated, even if explicitly requested in
|
|
||||||
// ClientConfig.Crypto.Ciphers.
|
|
||||||
var cipherModes = map[string]*streamCipherMode{
|
|
||||||
// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
|
|
||||||
// are defined in the order specified in the RFC.
|
|
||||||
"aes128-ctr": {16, aes.BlockSize, 0, newAESCTR},
|
|
||||||
"aes192-ctr": {24, aes.BlockSize, 0, newAESCTR},
|
|
||||||
"aes256-ctr": {32, aes.BlockSize, 0, newAESCTR},
|
|
||||||
|
|
||||||
// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
|
|
||||||
// They are defined in the order specified in the RFC.
|
|
||||||
"arcfour128": {16, 0, 1536, newRC4},
|
|
||||||
"arcfour256": {32, 0, 1536, newRC4},
|
|
||||||
|
|
||||||
// Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol.
|
|
||||||
// Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and
|
|
||||||
// RC4) has problems with weak keys, and should be used with caution."
|
|
||||||
// RFC4345 introduces improved versions of Arcfour.
|
|
||||||
"arcfour": {16, 0, 0, newRC4},
|
|
||||||
|
|
||||||
// AES-GCM is not a stream cipher, so it is constructed with a
|
|
||||||
// special case. If we add any more non-stream ciphers, we
|
|
||||||
// should invest a cleaner way to do this.
|
|
||||||
gcmCipherID: {16, 12, 0, nil},
|
|
||||||
|
|
||||||
// CBC mode is insecure and so is not included in the default config.
|
|
||||||
// (See http://www.isg.rhul.ac.uk/~kp/SandPfinal.pdf). If absolutely
|
|
||||||
// needed, it's possible to specify a custom Config to enable it.
|
|
||||||
// You should expect that an active attacker can recover plaintext if
|
|
||||||
// you do.
|
|
||||||
aes128cbcID: {16, aes.BlockSize, 0, nil},
|
|
||||||
|
|
||||||
// 3des-cbc is insecure and is disabled by default.
|
|
||||||
tripledescbcID: {24, des.BlockSize, 0, nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
// prefixLen is the length of the packet prefix that contains the packet length
|
|
||||||
// and number of padding bytes.
|
|
||||||
const prefixLen = 5
|
|
||||||
|
|
||||||
// streamPacketCipher is a packetCipher using a stream cipher.
|
|
||||||
type streamPacketCipher struct {
|
|
||||||
mac hash.Hash
|
|
||||||
cipher cipher.Stream
|
|
||||||
etm bool
|
|
||||||
|
|
||||||
// The following members are to avoid per-packet allocations.
|
|
||||||
prefix [prefixLen]byte
|
|
||||||
seqNumBytes [4]byte
|
|
||||||
padding [2 * packetSizeMultiple]byte
|
|
||||||
packetData []byte
|
|
||||||
macResult []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// readPacket reads and decrypt a single packet from the reader argument.
|
|
||||||
func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
|
||||||
if _, err := io.ReadFull(r, s.prefix[:]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var encryptedPaddingLength [1]byte
|
|
||||||
if s.mac != nil && s.etm {
|
|
||||||
copy(encryptedPaddingLength[:], s.prefix[4:5])
|
|
||||||
s.cipher.XORKeyStream(s.prefix[4:5], s.prefix[4:5])
|
|
||||||
} else {
|
|
||||||
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
length := binary.BigEndian.Uint32(s.prefix[0:4])
|
|
||||||
paddingLength := uint32(s.prefix[4])
|
|
||||||
|
|
||||||
var macSize uint32
|
|
||||||
if s.mac != nil {
|
|
||||||
s.mac.Reset()
|
|
||||||
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
|
|
||||||
s.mac.Write(s.seqNumBytes[:])
|
|
||||||
if s.etm {
|
|
||||||
s.mac.Write(s.prefix[:4])
|
|
||||||
s.mac.Write(encryptedPaddingLength[:])
|
|
||||||
} else {
|
|
||||||
s.mac.Write(s.prefix[:])
|
|
||||||
}
|
|
||||||
macSize = uint32(s.mac.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
if length <= paddingLength+1 {
|
|
||||||
return nil, errors.New("ssh: invalid packet length, packet too small")
|
|
||||||
}
|
|
||||||
|
|
||||||
if length > maxPacket {
|
|
||||||
return nil, errors.New("ssh: invalid packet length, packet too large")
|
|
||||||
}
|
|
||||||
|
|
||||||
// the maxPacket check above ensures that length-1+macSize
|
|
||||||
// does not overflow.
|
|
||||||
if uint32(cap(s.packetData)) < length-1+macSize {
|
|
||||||
s.packetData = make([]byte, length-1+macSize)
|
|
||||||
} else {
|
|
||||||
s.packetData = s.packetData[:length-1+macSize]
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(r, s.packetData); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mac := s.packetData[length-1:]
|
|
||||||
data := s.packetData[:length-1]
|
|
||||||
|
|
||||||
if s.mac != nil && s.etm {
|
|
||||||
s.mac.Write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.cipher.XORKeyStream(data, data)
|
|
||||||
|
|
||||||
if s.mac != nil {
|
|
||||||
if !s.etm {
|
|
||||||
s.mac.Write(data)
|
|
||||||
}
|
|
||||||
s.macResult = s.mac.Sum(s.macResult[:0])
|
|
||||||
if subtle.ConstantTimeCompare(s.macResult, mac) != 1 {
|
|
||||||
return nil, errors.New("ssh: MAC failure")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.packetData[:length-paddingLength-1], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// writePacket encrypts and sends a packet of data to the writer argument
|
|
||||||
func (s *streamPacketCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
|
|
||||||
if len(packet) > maxPacket {
|
|
||||||
return errors.New("ssh: packet too large")
|
|
||||||
}
|
|
||||||
|
|
||||||
aadlen := 0
|
|
||||||
if s.mac != nil && s.etm {
|
|
||||||
// packet length is not encrypted for EtM modes
|
|
||||||
aadlen = 4
|
|
||||||
}
|
|
||||||
|
|
||||||
paddingLength := packetSizeMultiple - (prefixLen+len(packet)-aadlen)%packetSizeMultiple
|
|
||||||
if paddingLength < 4 {
|
|
||||||
paddingLength += packetSizeMultiple
|
|
||||||
}
|
|
||||||
|
|
||||||
length := len(packet) + 1 + paddingLength
|
|
||||||
binary.BigEndian.PutUint32(s.prefix[:], uint32(length))
|
|
||||||
s.prefix[4] = byte(paddingLength)
|
|
||||||
padding := s.padding[:paddingLength]
|
|
||||||
if _, err := io.ReadFull(rand, padding); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.mac != nil {
|
|
||||||
s.mac.Reset()
|
|
||||||
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
|
|
||||||
s.mac.Write(s.seqNumBytes[:])
|
|
||||||
|
|
||||||
if s.etm {
|
|
||||||
// For EtM algorithms, the packet length must stay unencrypted,
|
|
||||||
// but the following data (padding length) must be encrypted
|
|
||||||
s.cipher.XORKeyStream(s.prefix[4:5], s.prefix[4:5])
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mac.Write(s.prefix[:])
|
|
||||||
|
|
||||||
if !s.etm {
|
|
||||||
// For non-EtM algorithms, the algorithm is applied on unencrypted data
|
|
||||||
s.mac.Write(packet)
|
|
||||||
s.mac.Write(padding)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(s.mac != nil && s.etm) {
|
|
||||||
// For EtM algorithms, the padding length has already been encrypted
|
|
||||||
// and the packet length must remain unencrypted
|
|
||||||
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
s.cipher.XORKeyStream(packet, packet)
|
|
||||||
s.cipher.XORKeyStream(padding, padding)
|
|
||||||
|
|
||||||
if s.mac != nil && s.etm {
|
|
||||||
// For EtM algorithms, packet and padding must be encrypted
|
|
||||||
s.mac.Write(packet)
|
|
||||||
s.mac.Write(padding)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := w.Write(s.prefix[:]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := w.Write(packet); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := w.Write(padding); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.mac != nil {
|
|
||||||
s.macResult = s.mac.Sum(s.macResult[:0])
|
|
||||||
if _, err := w.Write(s.macResult); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type gcmCipher struct {
|
|
||||||
aead cipher.AEAD
|
|
||||||
prefix [4]byte
|
|
||||||
iv []byte
|
|
||||||
buf []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGCMCipher(iv, key, macKey []byte) (packetCipher, error) {
|
|
||||||
c, err := aes.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
aead, err := cipher.NewGCM(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &gcmCipher{
|
|
||||||
aead: aead,
|
|
||||||
iv: iv,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const gcmTagSize = 16
|
|
||||||
|
|
||||||
func (c *gcmCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
|
|
||||||
// Pad out to multiple of 16 bytes. This is different from the
|
|
||||||
// stream cipher because that encrypts the length too.
|
|
||||||
padding := byte(packetSizeMultiple - (1+len(packet))%packetSizeMultiple)
|
|
||||||
if padding < 4 {
|
|
||||||
padding += packetSizeMultiple
|
|
||||||
}
|
|
||||||
|
|
||||||
length := uint32(len(packet) + int(padding) + 1)
|
|
||||||
binary.BigEndian.PutUint32(c.prefix[:], length)
|
|
||||||
if _, err := w.Write(c.prefix[:]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if cap(c.buf) < int(length) {
|
|
||||||
c.buf = make([]byte, length)
|
|
||||||
} else {
|
|
||||||
c.buf = c.buf[:length]
|
|
||||||
}
|
|
||||||
|
|
||||||
c.buf[0] = padding
|
|
||||||
copy(c.buf[1:], packet)
|
|
||||||
if _, err := io.ReadFull(rand, c.buf[1+len(packet):]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.buf = c.aead.Seal(c.buf[:0], c.iv, c.buf, c.prefix[:])
|
|
||||||
if _, err := w.Write(c.buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.incIV()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *gcmCipher) incIV() {
|
|
||||||
for i := 4 + 7; i >= 4; i-- {
|
|
||||||
c.iv[i]++
|
|
||||||
if c.iv[i] != 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *gcmCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
|
||||||
if _, err := io.ReadFull(r, c.prefix[:]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
length := binary.BigEndian.Uint32(c.prefix[:])
|
|
||||||
if length > maxPacket {
|
|
||||||
return nil, errors.New("ssh: max packet length exceeded.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cap(c.buf) < int(length+gcmTagSize) {
|
|
||||||
c.buf = make([]byte, length+gcmTagSize)
|
|
||||||
} else {
|
|
||||||
c.buf = c.buf[:length+gcmTagSize]
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(r, c.buf); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
plain, err := c.aead.Open(c.buf[:0], c.iv, c.buf, c.prefix[:])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.incIV()
|
|
||||||
|
|
||||||
padding := plain[0]
|
|
||||||
if padding < 4 || padding >= 20 {
|
|
||||||
return nil, fmt.Errorf("ssh: illegal padding %d", padding)
|
|
||||||
}
|
|
||||||
|
|
||||||
if int(padding+1) >= len(plain) {
|
|
||||||
return nil, fmt.Errorf("ssh: padding %d too large", padding)
|
|
||||||
}
|
|
||||||
plain = plain[1 : length-uint32(padding)]
|
|
||||||
return plain, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cbcCipher implements aes128-cbc cipher defined in RFC 4253 section 6.1
|
|
||||||
type cbcCipher struct {
|
|
||||||
mac hash.Hash
|
|
||||||
macSize uint32
|
|
||||||
decrypter cipher.BlockMode
|
|
||||||
encrypter cipher.BlockMode
|
|
||||||
|
|
||||||
// The following members are to avoid per-packet allocations.
|
|
||||||
seqNumBytes [4]byte
|
|
||||||
packetData []byte
|
|
||||||
macResult []byte
|
|
||||||
|
|
||||||
// Amount of data we should still read to hide which
|
|
||||||
// verification error triggered.
|
|
||||||
oracleCamouflage uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCBCCipher(c cipher.Block, iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
|
||||||
cbc := &cbcCipher{
|
|
||||||
mac: macModes[algs.MAC].new(macKey),
|
|
||||||
decrypter: cipher.NewCBCDecrypter(c, iv),
|
|
||||||
encrypter: cipher.NewCBCEncrypter(c, iv),
|
|
||||||
packetData: make([]byte, 1024),
|
|
||||||
}
|
|
||||||
if cbc.mac != nil {
|
|
||||||
cbc.macSize = uint32(cbc.mac.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
return cbc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
|
||||||
c, err := aes.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cbc, err := newCBCCipher(c, iv, key, macKey, algs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cbc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTripleDESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
|
||||||
c, err := des.NewTripleDESCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cbc, err := newCBCCipher(c, iv, key, macKey, algs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cbc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func maxUInt32(a, b int) uint32 {
|
|
||||||
if a > b {
|
|
||||||
return uint32(a)
|
|
||||||
}
|
|
||||||
return uint32(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
cbcMinPacketSizeMultiple = 8
|
|
||||||
cbcMinPacketSize = 16
|
|
||||||
cbcMinPaddingSize = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
// cbcError represents a verification error that may leak information.
|
|
||||||
type cbcError string
|
|
||||||
|
|
||||||
func (e cbcError) Error() string { return string(e) }
|
|
||||||
|
|
||||||
func (c *cbcCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
|
||||||
p, err := c.readPacketLeaky(seqNum, r)
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(cbcError); ok {
|
|
||||||
// Verification error: read a fixed amount of
|
|
||||||
// data, to make distinguishing between
|
|
||||||
// failing MAC and failing length check more
|
|
||||||
// difficult.
|
|
||||||
io.CopyN(ioutil.Discard, r, int64(c.oracleCamouflage))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cbcCipher) readPacketLeaky(seqNum uint32, r io.Reader) ([]byte, error) {
|
|
||||||
blockSize := c.decrypter.BlockSize()
|
|
||||||
|
|
||||||
// Read the header, which will include some of the subsequent data in the
|
|
||||||
// case of block ciphers - this is copied back to the payload later.
|
|
||||||
// How many bytes of payload/padding will be read with this first read.
|
|
||||||
firstBlockLength := uint32((prefixLen + blockSize - 1) / blockSize * blockSize)
|
|
||||||
firstBlock := c.packetData[:firstBlockLength]
|
|
||||||
if _, err := io.ReadFull(r, firstBlock); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.oracleCamouflage = maxPacket + 4 + c.macSize - firstBlockLength
|
|
||||||
|
|
||||||
c.decrypter.CryptBlocks(firstBlock, firstBlock)
|
|
||||||
length := binary.BigEndian.Uint32(firstBlock[:4])
|
|
||||||
if length > maxPacket {
|
|
||||||
return nil, cbcError("ssh: packet too large")
|
|
||||||
}
|
|
||||||
if length+4 < maxUInt32(cbcMinPacketSize, blockSize) {
|
|
||||||
// The minimum size of a packet is 16 (or the cipher block size, whichever
|
|
||||||
// is larger) bytes.
|
|
||||||
return nil, cbcError("ssh: packet too small")
|
|
||||||
}
|
|
||||||
// The length of the packet (including the length field but not the MAC) must
|
|
||||||
// be a multiple of the block size or 8, whichever is larger.
|
|
||||||
if (length+4)%maxUInt32(cbcMinPacketSizeMultiple, blockSize) != 0 {
|
|
||||||
return nil, cbcError("ssh: invalid packet length multiple")
|
|
||||||
}
|
|
||||||
|
|
||||||
paddingLength := uint32(firstBlock[4])
|
|
||||||
if paddingLength < cbcMinPaddingSize || length <= paddingLength+1 {
|
|
||||||
return nil, cbcError("ssh: invalid packet length")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Positions within the c.packetData buffer:
|
|
||||||
macStart := 4 + length
|
|
||||||
paddingStart := macStart - paddingLength
|
|
||||||
|
|
||||||
// Entire packet size, starting before length, ending at end of mac.
|
|
||||||
entirePacketSize := macStart + c.macSize
|
|
||||||
|
|
||||||
// Ensure c.packetData is large enough for the entire packet data.
|
|
||||||
if uint32(cap(c.packetData)) < entirePacketSize {
|
|
||||||
// Still need to upsize and copy, but this should be rare at runtime, only
|
|
||||||
// on upsizing the packetData buffer.
|
|
||||||
c.packetData = make([]byte, entirePacketSize)
|
|
||||||
copy(c.packetData, firstBlock)
|
|
||||||
} else {
|
|
||||||
c.packetData = c.packetData[:entirePacketSize]
|
|
||||||
}
|
|
||||||
|
|
||||||
if n, err := io.ReadFull(r, c.packetData[firstBlockLength:]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
c.oracleCamouflage -= uint32(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
remainingCrypted := c.packetData[firstBlockLength:macStart]
|
|
||||||
c.decrypter.CryptBlocks(remainingCrypted, remainingCrypted)
|
|
||||||
|
|
||||||
mac := c.packetData[macStart:]
|
|
||||||
if c.mac != nil {
|
|
||||||
c.mac.Reset()
|
|
||||||
binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum)
|
|
||||||
c.mac.Write(c.seqNumBytes[:])
|
|
||||||
c.mac.Write(c.packetData[:macStart])
|
|
||||||
c.macResult = c.mac.Sum(c.macResult[:0])
|
|
||||||
if subtle.ConstantTimeCompare(c.macResult, mac) != 1 {
|
|
||||||
return nil, cbcError("ssh: MAC failure")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.packetData[prefixLen:paddingStart], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cbcCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
|
|
||||||
effectiveBlockSize := maxUInt32(cbcMinPacketSizeMultiple, c.encrypter.BlockSize())
|
|
||||||
|
|
||||||
// Length of encrypted portion of the packet (header, payload, padding).
|
|
||||||
// Enforce minimum padding and packet size.
|
|
||||||
encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPaddingSize)
|
|
||||||
// Enforce block size.
|
|
||||||
encLength = (encLength + effectiveBlockSize - 1) / effectiveBlockSize * effectiveBlockSize
|
|
||||||
|
|
||||||
length := encLength - 4
|
|
||||||
paddingLength := int(length) - (1 + len(packet))
|
|
||||||
|
|
||||||
// Overall buffer contains: header, payload, padding, mac.
|
|
||||||
// Space for the MAC is reserved in the capacity but not the slice length.
|
|
||||||
bufferSize := encLength + c.macSize
|
|
||||||
if uint32(cap(c.packetData)) < bufferSize {
|
|
||||||
c.packetData = make([]byte, encLength, bufferSize)
|
|
||||||
} else {
|
|
||||||
c.packetData = c.packetData[:encLength]
|
|
||||||
}
|
|
||||||
|
|
||||||
p := c.packetData
|
|
||||||
|
|
||||||
// Packet header.
|
|
||||||
binary.BigEndian.PutUint32(p, length)
|
|
||||||
p = p[4:]
|
|
||||||
p[0] = byte(paddingLength)
|
|
||||||
|
|
||||||
// Payload.
|
|
||||||
p = p[1:]
|
|
||||||
copy(p, packet)
|
|
||||||
|
|
||||||
// Padding.
|
|
||||||
p = p[len(packet):]
|
|
||||||
if _, err := io.ReadFull(rand, p); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.mac != nil {
|
|
||||||
c.mac.Reset()
|
|
||||||
binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum)
|
|
||||||
c.mac.Write(c.seqNumBytes[:])
|
|
||||||
c.mac.Write(c.packetData)
|
|
||||||
// The MAC is now appended into the capacity reserved for it earlier.
|
|
||||||
c.packetData = c.mac.Sum(c.packetData)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.encrypter.CryptBlocks(c.packetData[:encLength], c.packetData[:encLength])
|
|
||||||
|
|
||||||
if _, err := w.Write(c.packetData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-257
@@ -1,257 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client implements a traditional SSH client that supports shells,
|
|
||||||
// subprocesses, TCP port/streamlocal forwarding and tunneled dialing.
|
|
||||||
type Client struct {
|
|
||||||
Conn
|
|
||||||
|
|
||||||
forwards forwardList // forwarded tcpip connections from the remote side
|
|
||||||
mu sync.Mutex
|
|
||||||
channelHandlers map[string]chan NewChannel
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleChannelOpen returns a channel on which NewChannel requests
|
|
||||||
// for the given type are sent. If the type already is being handled,
|
|
||||||
// nil is returned. The channel is closed when the connection is closed.
|
|
||||||
func (c *Client) HandleChannelOpen(channelType string) <-chan NewChannel {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
if c.channelHandlers == nil {
|
|
||||||
// The SSH channel has been closed.
|
|
||||||
c := make(chan NewChannel)
|
|
||||||
close(c)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
ch := c.channelHandlers[channelType]
|
|
||||||
if ch != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ch = make(chan NewChannel, chanSize)
|
|
||||||
c.channelHandlers[channelType] = ch
|
|
||||||
return ch
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient creates a Client on top of the given connection.
|
|
||||||
func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
|
|
||||||
conn := &Client{
|
|
||||||
Conn: c,
|
|
||||||
channelHandlers: make(map[string]chan NewChannel, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
go conn.handleGlobalRequests(reqs)
|
|
||||||
go conn.handleChannelOpens(chans)
|
|
||||||
go func() {
|
|
||||||
conn.Wait()
|
|
||||||
conn.forwards.closeAll()
|
|
||||||
}()
|
|
||||||
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip"))
|
|
||||||
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-streamlocal@openssh.com"))
|
|
||||||
return conn
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClientConn establishes an authenticated SSH connection using c
|
|
||||||
// as the underlying transport. The Request and NewChannel channels
|
|
||||||
// must be serviced or the connection will hang.
|
|
||||||
func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) {
|
|
||||||
fullConf := *config
|
|
||||||
fullConf.SetDefaults()
|
|
||||||
if fullConf.HostKeyCallback == nil {
|
|
||||||
c.Close()
|
|
||||||
return nil, nil, nil, errors.New("ssh: must specify HostKeyCallback")
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := &connection{
|
|
||||||
sshConn: sshConn{conn: c},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := conn.clientHandshake(addr, &fullConf); err != nil {
|
|
||||||
c.Close()
|
|
||||||
return nil, nil, nil, fmt.Errorf("ssh: handshake failed: %v", err)
|
|
||||||
}
|
|
||||||
conn.mux = newMux(conn.transport)
|
|
||||||
return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// clientHandshake performs the client side key exchange. See RFC 4253 Section
|
|
||||||
// 7.
|
|
||||||
func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) error {
|
|
||||||
if config.ClientVersion != "" {
|
|
||||||
c.clientVersion = []byte(config.ClientVersion)
|
|
||||||
} else {
|
|
||||||
c.clientVersion = []byte(packageVersion)
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.transport = newClientTransport(
|
|
||||||
newTransport(c.sshConn.conn, config.Rand, true /* is client */),
|
|
||||||
c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr())
|
|
||||||
if err := c.transport.waitSession(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.sessionID = c.transport.getSessionID()
|
|
||||||
return c.clientAuthenticate(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyHostKeySignature verifies the host key obtained in the key
|
|
||||||
// exchange.
|
|
||||||
func verifyHostKeySignature(hostKey PublicKey, result *kexResult) error {
|
|
||||||
sig, rest, ok := parseSignatureBody(result.Signature)
|
|
||||||
if len(rest) > 0 || !ok {
|
|
||||||
return errors.New("ssh: signature parse error")
|
|
||||||
}
|
|
||||||
|
|
||||||
return hostKey.Verify(result.H, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSession opens a new Session for this client. (A session is a remote
|
|
||||||
// execution of a program.)
|
|
||||||
func (c *Client) NewSession() (*Session, error) {
|
|
||||||
ch, in, err := c.OpenChannel("session", nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return newSession(ch, in)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) handleGlobalRequests(incoming <-chan *Request) {
|
|
||||||
for r := range incoming {
|
|
||||||
// This handles keepalive messages and matches
|
|
||||||
// the behaviour of OpenSSH.
|
|
||||||
r.Reply(false, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleChannelOpens channel open messages from the remote side.
|
|
||||||
func (c *Client) handleChannelOpens(in <-chan NewChannel) {
|
|
||||||
for ch := range in {
|
|
||||||
c.mu.Lock()
|
|
||||||
handler := c.channelHandlers[ch.ChannelType()]
|
|
||||||
c.mu.Unlock()
|
|
||||||
|
|
||||||
if handler != nil {
|
|
||||||
handler <- ch
|
|
||||||
} else {
|
|
||||||
ch.Reject(UnknownChannelType, fmt.Sprintf("unknown channel type: %v", ch.ChannelType()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.mu.Lock()
|
|
||||||
for _, ch := range c.channelHandlers {
|
|
||||||
close(ch)
|
|
||||||
}
|
|
||||||
c.channelHandlers = nil
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial starts a client connection to the given SSH server. It is a
|
|
||||||
// convenience function that connects to the given network address,
|
|
||||||
// initiates the SSH handshake, and then sets up a Client. For access
|
|
||||||
// to incoming channels and requests, use net.Dial with NewClientConn
|
|
||||||
// instead.
|
|
||||||
func Dial(network, addr string, config *ClientConfig) (*Client, error) {
|
|
||||||
conn, err := net.DialTimeout(network, addr, config.Timeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c, chans, reqs, err := NewClientConn(conn, addr, config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return NewClient(c, chans, reqs), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HostKeyCallback is the function type used for verifying server
|
|
||||||
// keys. A HostKeyCallback must return nil if the host key is OK, or
|
|
||||||
// an error to reject it. It receives the hostname as passed to Dial
|
|
||||||
// or NewClientConn. The remote address is the RemoteAddr of the
|
|
||||||
// net.Conn underlying the the SSH connection.
|
|
||||||
type HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
|
|
||||||
|
|
||||||
// A ClientConfig structure is used to configure a Client. It must not be
|
|
||||||
// modified after having been passed to an SSH function.
|
|
||||||
type ClientConfig struct {
|
|
||||||
// Config contains configuration that is shared between clients and
|
|
||||||
// servers.
|
|
||||||
Config
|
|
||||||
|
|
||||||
// User contains the username to authenticate as.
|
|
||||||
User string
|
|
||||||
|
|
||||||
// Auth contains possible authentication methods to use with the
|
|
||||||
// server. Only the first instance of a particular RFC 4252 method will
|
|
||||||
// be used during authentication.
|
|
||||||
Auth []AuthMethod
|
|
||||||
|
|
||||||
// HostKeyCallback is called during the cryptographic
|
|
||||||
// handshake to validate the server's host key. The client
|
|
||||||
// configuration must supply this callback for the connection
|
|
||||||
// to succeed. The functions InsecureIgnoreHostKey or
|
|
||||||
// FixedHostKey can be used for simplistic host key checks.
|
|
||||||
HostKeyCallback HostKeyCallback
|
|
||||||
|
|
||||||
// ClientVersion contains the version identification string that will
|
|
||||||
// be used for the connection. If empty, a reasonable default is used.
|
|
||||||
ClientVersion string
|
|
||||||
|
|
||||||
// HostKeyAlgorithms lists the key types that the client will
|
|
||||||
// accept from the server as host key, in order of
|
|
||||||
// preference. If empty, a reasonable default is used. Any
|
|
||||||
// string returned from PublicKey.Type method may be used, or
|
|
||||||
// any of the CertAlgoXxxx and KeyAlgoXxxx constants.
|
|
||||||
HostKeyAlgorithms []string
|
|
||||||
|
|
||||||
// Timeout is the maximum amount of time for the TCP connection to establish.
|
|
||||||
//
|
|
||||||
// A Timeout of zero means no timeout.
|
|
||||||
Timeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsecureIgnoreHostKey returns a function that can be used for
|
|
||||||
// ClientConfig.HostKeyCallback to accept any host key. It should
|
|
||||||
// not be used for production code.
|
|
||||||
func InsecureIgnoreHostKey() HostKeyCallback {
|
|
||||||
return func(hostname string, remote net.Addr, key PublicKey) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type fixedHostKey struct {
|
|
||||||
key PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fixedHostKey) check(hostname string, remote net.Addr, key PublicKey) error {
|
|
||||||
if f.key == nil {
|
|
||||||
return fmt.Errorf("ssh: required host key was nil")
|
|
||||||
}
|
|
||||||
if !bytes.Equal(key.Marshal(), f.key.Marshal()) {
|
|
||||||
return fmt.Errorf("ssh: host key mismatch")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FixedHostKey returns a function for use in
|
|
||||||
// ClientConfig.HostKeyCallback to accept only a specific host key.
|
|
||||||
func FixedHostKey(key PublicKey) HostKeyCallback {
|
|
||||||
hk := &fixedHostKey{key}
|
|
||||||
return hk.check
|
|
||||||
}
|
|
||||||
-486
@@ -1,486 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// clientAuthenticate authenticates with the remote server. See RFC 4252.
|
|
||||||
func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
|
||||||
// initiate user auth session
|
|
||||||
if err := c.transport.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
packet, err := c.transport.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var serviceAccept serviceAcceptMsg
|
|
||||||
if err := Unmarshal(packet, &serviceAccept); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// during the authentication phase the client first attempts the "none" method
|
|
||||||
// then any untried methods suggested by the server.
|
|
||||||
tried := make(map[string]bool)
|
|
||||||
var lastMethods []string
|
|
||||||
|
|
||||||
sessionID := c.transport.getSessionID()
|
|
||||||
for auth := AuthMethod(new(noneAuth)); auth != nil; {
|
|
||||||
ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
// success
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
tried[auth.method()] = true
|
|
||||||
if methods == nil {
|
|
||||||
methods = lastMethods
|
|
||||||
}
|
|
||||||
lastMethods = methods
|
|
||||||
|
|
||||||
auth = nil
|
|
||||||
|
|
||||||
findNext:
|
|
||||||
for _, a := range config.Auth {
|
|
||||||
candidateMethod := a.method()
|
|
||||||
if tried[candidateMethod] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, meth := range methods {
|
|
||||||
if meth == candidateMethod {
|
|
||||||
auth = a
|
|
||||||
break findNext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried))
|
|
||||||
}
|
|
||||||
|
|
||||||
func keys(m map[string]bool) []string {
|
|
||||||
s := make([]string, 0, len(m))
|
|
||||||
|
|
||||||
for key := range m {
|
|
||||||
s = append(s, key)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// An AuthMethod represents an instance of an RFC 4252 authentication method.
|
|
||||||
type AuthMethod interface {
|
|
||||||
// auth authenticates user over transport t.
|
|
||||||
// Returns true if authentication is successful.
|
|
||||||
// If authentication is not successful, a []string of alternative
|
|
||||||
// method names is returned. If the slice is nil, it will be ignored
|
|
||||||
// and the previous set of possible methods will be reused.
|
|
||||||
auth(session []byte, user string, p packetConn, rand io.Reader) (bool, []string, error)
|
|
||||||
|
|
||||||
// method returns the RFC 4252 method name.
|
|
||||||
method() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// "none" authentication, RFC 4252 section 5.2.
|
|
||||||
type noneAuth int
|
|
||||||
|
|
||||||
func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
|
||||||
if err := c.writePacket(Marshal(&userAuthRequestMsg{
|
|
||||||
User: user,
|
|
||||||
Service: serviceSSH,
|
|
||||||
Method: "none",
|
|
||||||
})); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return handleAuthResponse(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *noneAuth) method() string {
|
|
||||||
return "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
// passwordCallback is an AuthMethod that fetches the password through
|
|
||||||
// a function call, e.g. by prompting the user.
|
|
||||||
type passwordCallback func() (password string, err error)
|
|
||||||
|
|
||||||
func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
|
||||||
type passwordAuthMsg struct {
|
|
||||||
User string `sshtype:"50"`
|
|
||||||
Service string
|
|
||||||
Method string
|
|
||||||
Reply bool
|
|
||||||
Password string
|
|
||||||
}
|
|
||||||
|
|
||||||
pw, err := cb()
|
|
||||||
// REVIEW NOTE: is there a need to support skipping a password attempt?
|
|
||||||
// The program may only find out that the user doesn't have a password
|
|
||||||
// when prompting.
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.writePacket(Marshal(&passwordAuthMsg{
|
|
||||||
User: user,
|
|
||||||
Service: serviceSSH,
|
|
||||||
Method: cb.method(),
|
|
||||||
Reply: false,
|
|
||||||
Password: pw,
|
|
||||||
})); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return handleAuthResponse(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cb passwordCallback) method() string {
|
|
||||||
return "password"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Password returns an AuthMethod using the given password.
|
|
||||||
func Password(secret string) AuthMethod {
|
|
||||||
return passwordCallback(func() (string, error) { return secret, nil })
|
|
||||||
}
|
|
||||||
|
|
||||||
// PasswordCallback returns an AuthMethod that uses a callback for
|
|
||||||
// fetching a password.
|
|
||||||
func PasswordCallback(prompt func() (secret string, err error)) AuthMethod {
|
|
||||||
return passwordCallback(prompt)
|
|
||||||
}
|
|
||||||
|
|
||||||
type publickeyAuthMsg struct {
|
|
||||||
User string `sshtype:"50"`
|
|
||||||
Service string
|
|
||||||
Method string
|
|
||||||
// HasSig indicates to the receiver packet that the auth request is signed and
|
|
||||||
// should be used for authentication of the request.
|
|
||||||
HasSig bool
|
|
||||||
Algoname string
|
|
||||||
PubKey []byte
|
|
||||||
// Sig is tagged with "rest" so Marshal will exclude it during
|
|
||||||
// validateKey
|
|
||||||
Sig []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// publicKeyCallback is an AuthMethod that uses a set of key
|
|
||||||
// pairs for authentication.
|
|
||||||
type publicKeyCallback func() ([]Signer, error)
|
|
||||||
|
|
||||||
func (cb publicKeyCallback) method() string {
|
|
||||||
return "publickey"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
|
||||||
// Authentication is performed by sending an enquiry to test if a key is
|
|
||||||
// acceptable to the remote. If the key is acceptable, the client will
|
|
||||||
// attempt to authenticate with the valid key. If not the client will repeat
|
|
||||||
// the process with the remaining keys.
|
|
||||||
|
|
||||||
signers, err := cb()
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
var methods []string
|
|
||||||
for _, signer := range signers {
|
|
||||||
ok, err := validateKey(signer.PublicKey(), user, c)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pub := signer.PublicKey()
|
|
||||||
pubKey := pub.Marshal()
|
|
||||||
sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{
|
|
||||||
User: user,
|
|
||||||
Service: serviceSSH,
|
|
||||||
Method: cb.method(),
|
|
||||||
}, []byte(pub.Type()), pubKey))
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// manually wrap the serialized signature in a string
|
|
||||||
s := Marshal(sign)
|
|
||||||
sig := make([]byte, stringLength(len(s)))
|
|
||||||
marshalString(sig, s)
|
|
||||||
msg := publickeyAuthMsg{
|
|
||||||
User: user,
|
|
||||||
Service: serviceSSH,
|
|
||||||
Method: cb.method(),
|
|
||||||
HasSig: true,
|
|
||||||
Algoname: pub.Type(),
|
|
||||||
PubKey: pubKey,
|
|
||||||
Sig: sig,
|
|
||||||
}
|
|
||||||
p := Marshal(&msg)
|
|
||||||
if err := c.writePacket(p); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
var success bool
|
|
||||||
success, methods, err = handleAuthResponse(c)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If authentication succeeds or the list of available methods does not
|
|
||||||
// contain the "publickey" method, do not attempt to authenticate with any
|
|
||||||
// other keys. According to RFC 4252 Section 7, the latter can occur when
|
|
||||||
// additional authentication methods are required.
|
|
||||||
if success || !containsMethod(methods, cb.method()) {
|
|
||||||
return success, methods, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, methods, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func containsMethod(methods []string, method string) bool {
|
|
||||||
for _, m := range methods {
|
|
||||||
if m == method {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateKey validates the key provided is acceptable to the server.
|
|
||||||
func validateKey(key PublicKey, user string, c packetConn) (bool, error) {
|
|
||||||
pubKey := key.Marshal()
|
|
||||||
msg := publickeyAuthMsg{
|
|
||||||
User: user,
|
|
||||||
Service: serviceSSH,
|
|
||||||
Method: "publickey",
|
|
||||||
HasSig: false,
|
|
||||||
Algoname: key.Type(),
|
|
||||||
PubKey: pubKey,
|
|
||||||
}
|
|
||||||
if err := c.writePacket(Marshal(&msg)); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return confirmKeyAck(key, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
|
|
||||||
pubKey := key.Marshal()
|
|
||||||
algoname := key.Type()
|
|
||||||
|
|
||||||
for {
|
|
||||||
packet, err := c.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
switch packet[0] {
|
|
||||||
case msgUserAuthBanner:
|
|
||||||
// TODO(gpaul): add callback to present the banner to the user
|
|
||||||
case msgUserAuthPubKeyOk:
|
|
||||||
var msg userAuthPubKeyOkMsg
|
|
||||||
if err := Unmarshal(packet, &msg); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if msg.Algo != algoname || !bytes.Equal(msg.PubKey, pubKey) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
case msgUserAuthFailure:
|
|
||||||
return false, nil
|
|
||||||
default:
|
|
||||||
return false, unexpectedMessageError(msgUserAuthSuccess, packet[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PublicKeys returns an AuthMethod that uses the given key
|
|
||||||
// pairs.
|
|
||||||
func PublicKeys(signers ...Signer) AuthMethod {
|
|
||||||
return publicKeyCallback(func() ([]Signer, error) { return signers, nil })
|
|
||||||
}
|
|
||||||
|
|
||||||
// PublicKeysCallback returns an AuthMethod that runs the given
|
|
||||||
// function to obtain a list of key pairs.
|
|
||||||
func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMethod {
|
|
||||||
return publicKeyCallback(getSigners)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleAuthResponse returns whether the preceding authentication request succeeded
|
|
||||||
// along with a list of remaining authentication methods to try next and
|
|
||||||
// an error if an unexpected response was received.
|
|
||||||
func handleAuthResponse(c packetConn) (bool, []string, error) {
|
|
||||||
for {
|
|
||||||
packet, err := c.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch packet[0] {
|
|
||||||
case msgUserAuthBanner:
|
|
||||||
// TODO: add callback to present the banner to the user
|
|
||||||
case msgUserAuthFailure:
|
|
||||||
var msg userAuthFailureMsg
|
|
||||||
if err := Unmarshal(packet, &msg); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
return false, msg.Methods, nil
|
|
||||||
case msgUserAuthSuccess:
|
|
||||||
return true, nil, nil
|
|
||||||
default:
|
|
||||||
return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyboardInteractiveChallenge should print questions, optionally
|
|
||||||
// disabling echoing (e.g. for passwords), and return all the answers.
|
|
||||||
// Challenge may be called multiple times in a single session. After
|
|
||||||
// successful authentication, the server may send a challenge with no
|
|
||||||
// questions, for which the user and instruction messages should be
|
|
||||||
// printed. RFC 4256 section 3.3 details how the UI should behave for
|
|
||||||
// both CLI and GUI environments.
|
|
||||||
type KeyboardInteractiveChallenge func(user, instruction string, questions []string, echos []bool) (answers []string, err error)
|
|
||||||
|
|
||||||
// KeyboardInteractive returns a AuthMethod using a prompt/response
|
|
||||||
// sequence controlled by the server.
|
|
||||||
func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod {
|
|
||||||
return challenge
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cb KeyboardInteractiveChallenge) method() string {
|
|
||||||
return "keyboard-interactive"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
|
||||||
type initiateMsg struct {
|
|
||||||
User string `sshtype:"50"`
|
|
||||||
Service string
|
|
||||||
Method string
|
|
||||||
Language string
|
|
||||||
Submethods string
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.writePacket(Marshal(&initiateMsg{
|
|
||||||
User: user,
|
|
||||||
Service: serviceSSH,
|
|
||||||
Method: "keyboard-interactive",
|
|
||||||
})); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
packet, err := c.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// like handleAuthResponse, but with less options.
|
|
||||||
switch packet[0] {
|
|
||||||
case msgUserAuthBanner:
|
|
||||||
// TODO: Print banners during userauth.
|
|
||||||
continue
|
|
||||||
case msgUserAuthInfoRequest:
|
|
||||||
// OK
|
|
||||||
case msgUserAuthFailure:
|
|
||||||
var msg userAuthFailureMsg
|
|
||||||
if err := Unmarshal(packet, &msg); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
return false, msg.Methods, nil
|
|
||||||
case msgUserAuthSuccess:
|
|
||||||
return true, nil, nil
|
|
||||||
default:
|
|
||||||
return false, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg userAuthInfoRequestMsg
|
|
||||||
if err := Unmarshal(packet, &msg); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manually unpack the prompt/echo pairs.
|
|
||||||
rest := msg.Prompts
|
|
||||||
var prompts []string
|
|
||||||
var echos []bool
|
|
||||||
for i := 0; i < int(msg.NumPrompts); i++ {
|
|
||||||
prompt, r, ok := parseString(rest)
|
|
||||||
if !ok || len(r) == 0 {
|
|
||||||
return false, nil, errors.New("ssh: prompt format error")
|
|
||||||
}
|
|
||||||
prompts = append(prompts, string(prompt))
|
|
||||||
echos = append(echos, r[0] != 0)
|
|
||||||
rest = r[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(rest) != 0 {
|
|
||||||
return false, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
|
|
||||||
}
|
|
||||||
|
|
||||||
answers, err := cb(msg.User, msg.Instruction, prompts, echos)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(answers) != len(prompts) {
|
|
||||||
return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
|
|
||||||
}
|
|
||||||
responseLength := 1 + 4
|
|
||||||
for _, a := range answers {
|
|
||||||
responseLength += stringLength(len(a))
|
|
||||||
}
|
|
||||||
serialized := make([]byte, responseLength)
|
|
||||||
p := serialized
|
|
||||||
p[0] = msgUserAuthInfoResponse
|
|
||||||
p = p[1:]
|
|
||||||
p = marshalUint32(p, uint32(len(answers)))
|
|
||||||
for _, a := range answers {
|
|
||||||
p = marshalString(p, []byte(a))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.writePacket(serialized); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type retryableAuthMethod struct {
|
|
||||||
authMethod AuthMethod
|
|
||||||
maxTries int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok bool, methods []string, err error) {
|
|
||||||
for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ {
|
|
||||||
ok, methods, err = r.authMethod.auth(session, user, c, rand)
|
|
||||||
if ok || err != nil { // either success or error terminate
|
|
||||||
return ok, methods, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ok, methods, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *retryableAuthMethod) method() string {
|
|
||||||
return r.authMethod.method()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RetryableAuthMethod is a decorator for other auth methods enabling them to
|
|
||||||
// be retried up to maxTries before considering that AuthMethod itself failed.
|
|
||||||
// If maxTries is <= 0, will retry indefinitely
|
|
||||||
//
|
|
||||||
// This is useful for interactive clients using challenge/response type
|
|
||||||
// authentication (e.g. Keyboard-Interactive, Password, etc) where the user
|
|
||||||
// could mistype their response resulting in the server issuing a
|
|
||||||
// SSH_MSG_USERAUTH_FAILURE (rfc4252 #8 [password] and rfc4256 #3.4
|
|
||||||
// [keyboard-interactive]); Without this decorator, the non-retryable
|
|
||||||
// AuthMethod would be removed from future consideration, and never tried again
|
|
||||||
// (and so the user would never be able to retry their entry).
|
|
||||||
func RetryableAuthMethod(auth AuthMethod, maxTries int) AuthMethod {
|
|
||||||
return &retryableAuthMethod{authMethod: auth, maxTries: maxTries}
|
|
||||||
}
|
|
||||||
-373
@@ -1,373 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/rand"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
_ "crypto/sha1"
|
|
||||||
_ "crypto/sha256"
|
|
||||||
_ "crypto/sha512"
|
|
||||||
)
|
|
||||||
|
|
||||||
// These are string constants in the SSH protocol.
|
|
||||||
const (
|
|
||||||
compressionNone = "none"
|
|
||||||
serviceUserAuth = "ssh-userauth"
|
|
||||||
serviceSSH = "ssh-connection"
|
|
||||||
)
|
|
||||||
|
|
||||||
// supportedCiphers specifies the supported ciphers in preference order.
|
|
||||||
var supportedCiphers = []string{
|
|
||||||
"aes128-ctr", "aes192-ctr", "aes256-ctr",
|
|
||||||
"aes128-gcm@openssh.com",
|
|
||||||
"arcfour256", "arcfour128",
|
|
||||||
}
|
|
||||||
|
|
||||||
// supportedKexAlgos specifies the supported key-exchange algorithms in
|
|
||||||
// preference order.
|
|
||||||
var supportedKexAlgos = []string{
|
|
||||||
kexAlgoCurve25519SHA256,
|
|
||||||
// P384 and P521 are not constant-time yet, but since we don't
|
|
||||||
// reuse ephemeral keys, using them for ECDH should be OK.
|
|
||||||
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
|
|
||||||
kexAlgoDH14SHA1, kexAlgoDH1SHA1,
|
|
||||||
}
|
|
||||||
|
|
||||||
// supportedHostKeyAlgos specifies the supported host-key algorithms (i.e. methods
|
|
||||||
// of authenticating servers) in preference order.
|
|
||||||
var supportedHostKeyAlgos = []string{
|
|
||||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
|
|
||||||
CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01,
|
|
||||||
|
|
||||||
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
|
|
||||||
KeyAlgoRSA, KeyAlgoDSA,
|
|
||||||
|
|
||||||
KeyAlgoED25519,
|
|
||||||
}
|
|
||||||
|
|
||||||
// supportedMACs specifies a default set of MAC algorithms in preference order.
|
|
||||||
// This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed
|
|
||||||
// because they have reached the end of their useful life.
|
|
||||||
var supportedMACs = []string{
|
|
||||||
"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1", "hmac-sha1-96",
|
|
||||||
}
|
|
||||||
|
|
||||||
var supportedCompressions = []string{compressionNone}
|
|
||||||
|
|
||||||
// hashFuncs keeps the mapping of supported algorithms to their respective
|
|
||||||
// hashes needed for signature verification.
|
|
||||||
var hashFuncs = map[string]crypto.Hash{
|
|
||||||
KeyAlgoRSA: crypto.SHA1,
|
|
||||||
KeyAlgoDSA: crypto.SHA1,
|
|
||||||
KeyAlgoECDSA256: crypto.SHA256,
|
|
||||||
KeyAlgoECDSA384: crypto.SHA384,
|
|
||||||
KeyAlgoECDSA521: crypto.SHA512,
|
|
||||||
CertAlgoRSAv01: crypto.SHA1,
|
|
||||||
CertAlgoDSAv01: crypto.SHA1,
|
|
||||||
CertAlgoECDSA256v01: crypto.SHA256,
|
|
||||||
CertAlgoECDSA384v01: crypto.SHA384,
|
|
||||||
CertAlgoECDSA521v01: crypto.SHA512,
|
|
||||||
}
|
|
||||||
|
|
||||||
// unexpectedMessageError results when the SSH message that we received didn't
|
|
||||||
// match what we wanted.
|
|
||||||
func unexpectedMessageError(expected, got uint8) error {
|
|
||||||
return fmt.Errorf("ssh: unexpected message type %d (expected %d)", got, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseError results from a malformed SSH message.
|
|
||||||
func parseError(tag uint8) error {
|
|
||||||
return fmt.Errorf("ssh: parse error in message type %d", tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func findCommon(what string, client []string, server []string) (common string, err error) {
|
|
||||||
for _, c := range client {
|
|
||||||
for _, s := range server {
|
|
||||||
if c == s {
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("ssh: no common algorithm for %s; client offered: %v, server offered: %v", what, client, server)
|
|
||||||
}
|
|
||||||
|
|
||||||
type directionAlgorithms struct {
|
|
||||||
Cipher string
|
|
||||||
MAC string
|
|
||||||
Compression string
|
|
||||||
}
|
|
||||||
|
|
||||||
// rekeyBytes returns a rekeying intervals in bytes.
|
|
||||||
func (a *directionAlgorithms) rekeyBytes() int64 {
|
|
||||||
// According to RFC4344 block ciphers should rekey after
|
|
||||||
// 2^(BLOCKSIZE/4) blocks. For all AES flavors BLOCKSIZE is
|
|
||||||
// 128.
|
|
||||||
switch a.Cipher {
|
|
||||||
case "aes128-ctr", "aes192-ctr", "aes256-ctr", gcmCipherID, aes128cbcID:
|
|
||||||
return 16 * (1 << 32)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// For others, stick with RFC4253 recommendation to rekey after 1 Gb of data.
|
|
||||||
return 1 << 30
|
|
||||||
}
|
|
||||||
|
|
||||||
type algorithms struct {
|
|
||||||
kex string
|
|
||||||
hostKey string
|
|
||||||
w directionAlgorithms
|
|
||||||
r directionAlgorithms
|
|
||||||
}
|
|
||||||
|
|
||||||
func findAgreedAlgorithms(clientKexInit, serverKexInit *kexInitMsg) (algs *algorithms, err error) {
|
|
||||||
result := &algorithms{}
|
|
||||||
|
|
||||||
result.kex, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result.hostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result.w.Cipher, err = findCommon("client to server cipher", clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result.r.Cipher, err = findCommon("server to client cipher", clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result.w.MAC, err = findCommon("client to server MAC", clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result.r.MAC, err = findCommon("server to client MAC", clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result.w.Compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result.r.Compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If rekeythreshold is too small, we can't make any progress sending
|
|
||||||
// stuff.
|
|
||||||
const minRekeyThreshold uint64 = 256
|
|
||||||
|
|
||||||
// Config contains configuration data common to both ServerConfig and
|
|
||||||
// ClientConfig.
|
|
||||||
type Config struct {
|
|
||||||
// Rand provides the source of entropy for cryptographic
|
|
||||||
// primitives. If Rand is nil, the cryptographic random reader
|
|
||||||
// in package crypto/rand will be used.
|
|
||||||
Rand io.Reader
|
|
||||||
|
|
||||||
// The maximum number of bytes sent or received after which a
|
|
||||||
// new key is negotiated. It must be at least 256. If
|
|
||||||
// unspecified, a size suitable for the chosen cipher is used.
|
|
||||||
RekeyThreshold uint64
|
|
||||||
|
|
||||||
// The allowed key exchanges algorithms. If unspecified then a
|
|
||||||
// default set of algorithms is used.
|
|
||||||
KeyExchanges []string
|
|
||||||
|
|
||||||
// The allowed cipher algorithms. If unspecified then a sensible
|
|
||||||
// default is used.
|
|
||||||
Ciphers []string
|
|
||||||
|
|
||||||
// The allowed MAC algorithms. If unspecified then a sensible default
|
|
||||||
// is used.
|
|
||||||
MACs []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDefaults sets sensible values for unset fields in config. This is
|
|
||||||
// exported for testing: Configs passed to SSH functions are copied and have
|
|
||||||
// default values set automatically.
|
|
||||||
func (c *Config) SetDefaults() {
|
|
||||||
if c.Rand == nil {
|
|
||||||
c.Rand = rand.Reader
|
|
||||||
}
|
|
||||||
if c.Ciphers == nil {
|
|
||||||
c.Ciphers = supportedCiphers
|
|
||||||
}
|
|
||||||
var ciphers []string
|
|
||||||
for _, c := range c.Ciphers {
|
|
||||||
if cipherModes[c] != nil {
|
|
||||||
// reject the cipher if we have no cipherModes definition
|
|
||||||
ciphers = append(ciphers, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.Ciphers = ciphers
|
|
||||||
|
|
||||||
if c.KeyExchanges == nil {
|
|
||||||
c.KeyExchanges = supportedKexAlgos
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.MACs == nil {
|
|
||||||
c.MACs = supportedMACs
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.RekeyThreshold == 0 {
|
|
||||||
// cipher specific default
|
|
||||||
} else if c.RekeyThreshold < minRekeyThreshold {
|
|
||||||
c.RekeyThreshold = minRekeyThreshold
|
|
||||||
} else if c.RekeyThreshold >= math.MaxInt64 {
|
|
||||||
// Avoid weirdness if somebody uses -1 as a threshold.
|
|
||||||
c.RekeyThreshold = math.MaxInt64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildDataSignedForAuth returns the data that is signed in order to prove
|
|
||||||
// possession of a private key. See RFC 4252, section 7.
|
|
||||||
func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
|
|
||||||
data := struct {
|
|
||||||
Session []byte
|
|
||||||
Type byte
|
|
||||||
User string
|
|
||||||
Service string
|
|
||||||
Method string
|
|
||||||
Sign bool
|
|
||||||
Algo []byte
|
|
||||||
PubKey []byte
|
|
||||||
}{
|
|
||||||
sessionId,
|
|
||||||
msgUserAuthRequest,
|
|
||||||
req.User,
|
|
||||||
req.Service,
|
|
||||||
req.Method,
|
|
||||||
true,
|
|
||||||
algo,
|
|
||||||
pubKey,
|
|
||||||
}
|
|
||||||
return Marshal(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendU16(buf []byte, n uint16) []byte {
|
|
||||||
return append(buf, byte(n>>8), byte(n))
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendU32(buf []byte, n uint32) []byte {
|
|
||||||
return append(buf, byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendU64(buf []byte, n uint64) []byte {
|
|
||||||
return append(buf,
|
|
||||||
byte(n>>56), byte(n>>48), byte(n>>40), byte(n>>32),
|
|
||||||
byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendInt(buf []byte, n int) []byte {
|
|
||||||
return appendU32(buf, uint32(n))
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendString(buf []byte, s string) []byte {
|
|
||||||
buf = appendU32(buf, uint32(len(s)))
|
|
||||||
buf = append(buf, s...)
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendBool(buf []byte, b bool) []byte {
|
|
||||||
if b {
|
|
||||||
return append(buf, 1)
|
|
||||||
}
|
|
||||||
return append(buf, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newCond is a helper to hide the fact that there is no usable zero
|
|
||||||
// value for sync.Cond.
|
|
||||||
func newCond() *sync.Cond { return sync.NewCond(new(sync.Mutex)) }
|
|
||||||
|
|
||||||
// window represents the buffer available to clients
|
|
||||||
// wishing to write to a channel.
|
|
||||||
type window struct {
|
|
||||||
*sync.Cond
|
|
||||||
win uint32 // RFC 4254 5.2 says the window size can grow to 2^32-1
|
|
||||||
writeWaiters int
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// add adds win to the amount of window available
|
|
||||||
// for consumers.
|
|
||||||
func (w *window) add(win uint32) bool {
|
|
||||||
// a zero sized window adjust is a noop.
|
|
||||||
if win == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
w.L.Lock()
|
|
||||||
if w.win+win < win {
|
|
||||||
w.L.Unlock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
w.win += win
|
|
||||||
// It is unusual that multiple goroutines would be attempting to reserve
|
|
||||||
// window space, but not guaranteed. Use broadcast to notify all waiters
|
|
||||||
// that additional window is available.
|
|
||||||
w.Broadcast()
|
|
||||||
w.L.Unlock()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// close sets the window to closed, so all reservations fail
|
|
||||||
// immediately.
|
|
||||||
func (w *window) close() {
|
|
||||||
w.L.Lock()
|
|
||||||
w.closed = true
|
|
||||||
w.Broadcast()
|
|
||||||
w.L.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// reserve reserves win from the available window capacity.
|
|
||||||
// If no capacity remains, reserve will block. reserve may
|
|
||||||
// return less than requested.
|
|
||||||
func (w *window) reserve(win uint32) (uint32, error) {
|
|
||||||
var err error
|
|
||||||
w.L.Lock()
|
|
||||||
w.writeWaiters++
|
|
||||||
w.Broadcast()
|
|
||||||
for w.win == 0 && !w.closed {
|
|
||||||
w.Wait()
|
|
||||||
}
|
|
||||||
w.writeWaiters--
|
|
||||||
if w.win < win {
|
|
||||||
win = w.win
|
|
||||||
}
|
|
||||||
w.win -= win
|
|
||||||
if w.closed {
|
|
||||||
err = io.EOF
|
|
||||||
}
|
|
||||||
w.L.Unlock()
|
|
||||||
return win, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitWriterBlocked waits until some goroutine is blocked for further
|
|
||||||
// writes. It is used in tests only.
|
|
||||||
func (w *window) waitWriterBlocked() {
|
|
||||||
w.Cond.L.Lock()
|
|
||||||
for w.writeWaiters == 0 {
|
|
||||||
w.Cond.Wait()
|
|
||||||
}
|
|
||||||
w.Cond.L.Unlock()
|
|
||||||
}
|
|
||||||
-143
@@ -1,143 +0,0 @@
|
|||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OpenChannelError is returned if the other side rejects an
|
|
||||||
// OpenChannel request.
|
|
||||||
type OpenChannelError struct {
|
|
||||||
Reason RejectionReason
|
|
||||||
Message string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *OpenChannelError) Error() string {
|
|
||||||
return fmt.Sprintf("ssh: rejected: %s (%s)", e.Reason, e.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnMetadata holds metadata for the connection.
|
|
||||||
type ConnMetadata interface {
|
|
||||||
// User returns the user ID for this connection.
|
|
||||||
User() string
|
|
||||||
|
|
||||||
// SessionID returns the sesson hash, also denoted by H.
|
|
||||||
SessionID() []byte
|
|
||||||
|
|
||||||
// ClientVersion returns the client's version string as hashed
|
|
||||||
// into the session ID.
|
|
||||||
ClientVersion() []byte
|
|
||||||
|
|
||||||
// ServerVersion returns the server's version string as hashed
|
|
||||||
// into the session ID.
|
|
||||||
ServerVersion() []byte
|
|
||||||
|
|
||||||
// RemoteAddr returns the remote address for this connection.
|
|
||||||
RemoteAddr() net.Addr
|
|
||||||
|
|
||||||
// LocalAddr returns the local address for this connection.
|
|
||||||
LocalAddr() net.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conn represents an SSH connection for both server and client roles.
|
|
||||||
// Conn is the basis for implementing an application layer, such
|
|
||||||
// as ClientConn, which implements the traditional shell access for
|
|
||||||
// clients.
|
|
||||||
type Conn interface {
|
|
||||||
ConnMetadata
|
|
||||||
|
|
||||||
// SendRequest sends a global request, and returns the
|
|
||||||
// reply. If wantReply is true, it returns the response status
|
|
||||||
// and payload. See also RFC4254, section 4.
|
|
||||||
SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error)
|
|
||||||
|
|
||||||
// OpenChannel tries to open an channel. If the request is
|
|
||||||
// rejected, it returns *OpenChannelError. On success it returns
|
|
||||||
// the SSH Channel and a Go channel for incoming, out-of-band
|
|
||||||
// requests. The Go channel must be serviced, or the
|
|
||||||
// connection will hang.
|
|
||||||
OpenChannel(name string, data []byte) (Channel, <-chan *Request, error)
|
|
||||||
|
|
||||||
// Close closes the underlying network connection
|
|
||||||
Close() error
|
|
||||||
|
|
||||||
// Wait blocks until the connection has shut down, and returns the
|
|
||||||
// error causing the shutdown.
|
|
||||||
Wait() error
|
|
||||||
|
|
||||||
// TODO(hanwen): consider exposing:
|
|
||||||
// RequestKeyChange
|
|
||||||
// Disconnect
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiscardRequests consumes and rejects all requests from the
|
|
||||||
// passed-in channel.
|
|
||||||
func DiscardRequests(in <-chan *Request) {
|
|
||||||
for req := range in {
|
|
||||||
if req.WantReply {
|
|
||||||
req.Reply(false, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A connection represents an incoming connection.
|
|
||||||
type connection struct {
|
|
||||||
transport *handshakeTransport
|
|
||||||
sshConn
|
|
||||||
|
|
||||||
// The connection protocol.
|
|
||||||
*mux
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connection) Close() error {
|
|
||||||
return c.sshConn.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// sshconn provides net.Conn metadata, but disallows direct reads and
|
|
||||||
// writes.
|
|
||||||
type sshConn struct {
|
|
||||||
conn net.Conn
|
|
||||||
|
|
||||||
user string
|
|
||||||
sessionID []byte
|
|
||||||
clientVersion []byte
|
|
||||||
serverVersion []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func dup(src []byte) []byte {
|
|
||||||
dst := make([]byte, len(src))
|
|
||||||
copy(dst, src)
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sshConn) User() string {
|
|
||||||
return c.user
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sshConn) RemoteAddr() net.Addr {
|
|
||||||
return c.conn.RemoteAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sshConn) Close() error {
|
|
||||||
return c.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sshConn) LocalAddr() net.Addr {
|
|
||||||
return c.conn.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sshConn) SessionID() []byte {
|
|
||||||
return dup(c.sessionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sshConn) ClientVersion() []byte {
|
|
||||||
return dup(c.clientVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sshConn) ServerVersion() []byte {
|
|
||||||
return dup(c.serverVersion)
|
|
||||||
}
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package ssh implements an SSH client and server.
|
|
||||||
|
|
||||||
SSH is a transport security protocol, an authentication protocol and a
|
|
||||||
family of application protocols. The most typical application level
|
|
||||||
protocol is a remote shell and this is specifically implemented. However,
|
|
||||||
the multiplexed nature of SSH is exposed to users that wish to support
|
|
||||||
others.
|
|
||||||
|
|
||||||
References:
|
|
||||||
[PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD
|
|
||||||
[SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
|
|
||||||
|
|
||||||
This package does not fall under the stability promise of the Go language itself,
|
|
||||||
so its API may be changed when pressing needs arise.
|
|
||||||
*/
|
|
||||||
package ssh // import "golang.org/x/crypto/ssh"
|
|
||||||
-640
@@ -1,640 +0,0 @@
|
|||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// debugHandshake, if set, prints messages sent and received. Key
|
|
||||||
// exchange messages are printed as if DH were used, so the debug
|
|
||||||
// messages are wrong when using ECDH.
|
|
||||||
const debugHandshake = false
|
|
||||||
|
|
||||||
// chanSize sets the amount of buffering SSH connections. This is
|
|
||||||
// primarily for testing: setting chanSize=0 uncovers deadlocks more
|
|
||||||
// quickly.
|
|
||||||
const chanSize = 16
|
|
||||||
|
|
||||||
// keyingTransport is a packet based transport that supports key
|
|
||||||
// changes. It need not be thread-safe. It should pass through
|
|
||||||
// msgNewKeys in both directions.
|
|
||||||
type keyingTransport interface {
|
|
||||||
packetConn
|
|
||||||
|
|
||||||
// prepareKeyChange sets up a key change. The key change for a
|
|
||||||
// direction will be effected if a msgNewKeys message is sent
|
|
||||||
// or received.
|
|
||||||
prepareKeyChange(*algorithms, *kexResult) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// handshakeTransport implements rekeying on top of a keyingTransport
|
|
||||||
// and offers a thread-safe writePacket() interface.
|
|
||||||
type handshakeTransport struct {
|
|
||||||
conn keyingTransport
|
|
||||||
config *Config
|
|
||||||
|
|
||||||
serverVersion []byte
|
|
||||||
clientVersion []byte
|
|
||||||
|
|
||||||
// hostKeys is non-empty if we are the server. In that case,
|
|
||||||
// it contains all host keys that can be used to sign the
|
|
||||||
// connection.
|
|
||||||
hostKeys []Signer
|
|
||||||
|
|
||||||
// hostKeyAlgorithms is non-empty if we are the client. In that case,
|
|
||||||
// we accept these key types from the server as host key.
|
|
||||||
hostKeyAlgorithms []string
|
|
||||||
|
|
||||||
// On read error, incoming is closed, and readError is set.
|
|
||||||
incoming chan []byte
|
|
||||||
readError error
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
writeError error
|
|
||||||
sentInitPacket []byte
|
|
||||||
sentInitMsg *kexInitMsg
|
|
||||||
pendingPackets [][]byte // Used when a key exchange is in progress.
|
|
||||||
|
|
||||||
// If the read loop wants to schedule a kex, it pings this
|
|
||||||
// channel, and the write loop will send out a kex
|
|
||||||
// message.
|
|
||||||
requestKex chan struct{}
|
|
||||||
|
|
||||||
// If the other side requests or confirms a kex, its kexInit
|
|
||||||
// packet is sent here for the write loop to find it.
|
|
||||||
startKex chan *pendingKex
|
|
||||||
|
|
||||||
// data for host key checking
|
|
||||||
hostKeyCallback HostKeyCallback
|
|
||||||
dialAddress string
|
|
||||||
remoteAddr net.Addr
|
|
||||||
|
|
||||||
// Algorithms agreed in the last key exchange.
|
|
||||||
algorithms *algorithms
|
|
||||||
|
|
||||||
readPacketsLeft uint32
|
|
||||||
readBytesLeft int64
|
|
||||||
|
|
||||||
writePacketsLeft uint32
|
|
||||||
writeBytesLeft int64
|
|
||||||
|
|
||||||
// The session ID or nil if first kex did not complete yet.
|
|
||||||
sessionID []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type pendingKex struct {
|
|
||||||
otherInit []byte
|
|
||||||
done chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion, serverVersion []byte) *handshakeTransport {
|
|
||||||
t := &handshakeTransport{
|
|
||||||
conn: conn,
|
|
||||||
serverVersion: serverVersion,
|
|
||||||
clientVersion: clientVersion,
|
|
||||||
incoming: make(chan []byte, chanSize),
|
|
||||||
requestKex: make(chan struct{}, 1),
|
|
||||||
startKex: make(chan *pendingKex, 1),
|
|
||||||
|
|
||||||
config: config,
|
|
||||||
}
|
|
||||||
t.resetReadThresholds()
|
|
||||||
t.resetWriteThresholds()
|
|
||||||
|
|
||||||
// We always start with a mandatory key exchange.
|
|
||||||
t.requestKex <- struct{}{}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byte, config *ClientConfig, dialAddr string, addr net.Addr) *handshakeTransport {
|
|
||||||
t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion)
|
|
||||||
t.dialAddress = dialAddr
|
|
||||||
t.remoteAddr = addr
|
|
||||||
t.hostKeyCallback = config.HostKeyCallback
|
|
||||||
if config.HostKeyAlgorithms != nil {
|
|
||||||
t.hostKeyAlgorithms = config.HostKeyAlgorithms
|
|
||||||
} else {
|
|
||||||
t.hostKeyAlgorithms = supportedHostKeyAlgos
|
|
||||||
}
|
|
||||||
go t.readLoop()
|
|
||||||
go t.kexLoop()
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func newServerTransport(conn keyingTransport, clientVersion, serverVersion []byte, config *ServerConfig) *handshakeTransport {
|
|
||||||
t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion)
|
|
||||||
t.hostKeys = config.hostKeys
|
|
||||||
go t.readLoop()
|
|
||||||
go t.kexLoop()
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *handshakeTransport) getSessionID() []byte {
|
|
||||||
return t.sessionID
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitSession waits for the session to be established. This should be
|
|
||||||
// the first thing to call after instantiating handshakeTransport.
|
|
||||||
func (t *handshakeTransport) waitSession() error {
|
|
||||||
p, err := t.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if p[0] != msgNewKeys {
|
|
||||||
return fmt.Errorf("ssh: first packet should be msgNewKeys")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *handshakeTransport) id() string {
|
|
||||||
if len(t.hostKeys) > 0 {
|
|
||||||
return "server"
|
|
||||||
}
|
|
||||||
return "client"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *handshakeTransport) printPacket(p []byte, write bool) {
|
|
||||||
action := "got"
|
|
||||||
if write {
|
|
||||||
action = "sent"
|
|
||||||
}
|
|
||||||
|
|
||||||
if p[0] == msgChannelData || p[0] == msgChannelExtendedData {
|
|
||||||
log.Printf("%s %s data (packet %d bytes)", t.id(), action, len(p))
|
|
||||||
} else {
|
|
||||||
msg, err := decode(p)
|
|
||||||
log.Printf("%s %s %T %v (%v)", t.id(), action, msg, msg, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *handshakeTransport) readPacket() ([]byte, error) {
|
|
||||||
p, ok := <-t.incoming
|
|
||||||
if !ok {
|
|
||||||
return nil, t.readError
|
|
||||||
}
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *handshakeTransport) readLoop() {
|
|
||||||
first := true
|
|
||||||
for {
|
|
||||||
p, err := t.readOnePacket(first)
|
|
||||||
first = false
|
|
||||||
if err != nil {
|
|
||||||
t.readError = err
|
|
||||||
close(t.incoming)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if p[0] == msgIgnore || p[0] == msgDebug {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t.incoming <- p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop writers too.
|
|
||||||
t.recordWriteError(t.readError)
|
|
||||||
|
|
||||||
// Unblock the writer should it wait for this.
|
|
||||||
close(t.startKex)
|
|
||||||
|
|
||||||
// Don't close t.requestKex; it's also written to from writePacket.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *handshakeTransport) pushPacket(p []byte) error {
|
|
||||||
if debugHandshake {
|
|
||||||
t.printPacket(p, true)
|
|
||||||
}
|
|
||||||
return t.conn.writePacket(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *handshakeTransport) getWriteError() error {
|
|
||||||
t.mu.Lock()
|
|
||||||
defer t.mu.Unlock()
|
|
||||||
return t.writeError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *handshakeTransport) recordWriteError(err error) {
|
|
||||||
t.mu.Lock()
|
|
||||||
defer t.mu.Unlock()
|
|
||||||
if t.writeError == nil && err != nil {
|
|
||||||
t.writeError = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *handshakeTransport) requestKeyExchange() {
|
|
||||||
select {
|
|
||||||
case t.requestKex <- struct{}{}:
|
|
||||||
default:
|
|
||||||
// something already requested a kex, so do nothing.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *handshakeTransport) resetWriteThresholds() {
|
|
||||||
t.writePacketsLeft = packetRekeyThreshold
|
|
||||||
if t.config.RekeyThreshold > 0 {
|
|
||||||
t.writeBytesLeft = int64(t.config.RekeyThreshold)
|
|
||||||
} else if t.algorithms != nil {
|
|
||||||
t.writeBytesLeft = t.algorithms.w.rekeyBytes()
|
|
||||||
} else {
|
|
||||||
t.writeBytesLeft = 1 << 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *handshakeTransport) kexLoop() {
|
|
||||||
|
|
||||||
write:
|
|
||||||
for t.getWriteError() == nil {
|
|
||||||
var request *pendingKex
|
|
||||||
var sent bool
|
|
||||||
|
|
||||||
for request == nil || !sent {
|
|
||||||
var ok bool
|
|
||||||
select {
|
|
||||||
case request, ok = <-t.startKex:
|
|
||||||
if !ok {
|
|
||||||
break write
|
|
||||||
}
|
|
||||||
case <-t.requestKex:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if !sent {
|
|
||||||
if err := t.sendKexInit(); err != nil {
|
|
||||||
t.recordWriteError(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
sent = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := t.getWriteError(); err != nil {
|
|
||||||
if request != nil {
|
|
||||||
request.done <- err
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're not servicing t.requestKex, but that is OK:
|
|
||||||
// we never block on sending to t.requestKex.
|
|
||||||
|
|
||||||
// We're not servicing t.startKex, but the remote end
|
|
||||||
// has just sent us a kexInitMsg, so it can't send
|
|
||||||
// another key change request, until we close the done
|
|
||||||
// channel on the pendingKex request.
|
|
||||||
|
|
||||||
err := t.enterKeyExchange(request.otherInit)
|
|
||||||
|
|
||||||
t.mu.Lock()
|
|
||||||
t.writeError = err
|
|
||||||
t.sentInitPacket = nil
|
|
||||||
t.sentInitMsg = nil
|
|
||||||
|
|
||||||
t.resetWriteThresholds()
|
|
||||||
|
|
||||||
// we have completed the key exchange. Since the
|
|
||||||
// reader is still blocked, it is safe to clear out
|
|
||||||
// the requestKex channel. This avoids the situation
|
|
||||||
// where: 1) we consumed our own request for the
|
|
||||||
// initial kex, and 2) the kex from the remote side
|
|
||||||
// caused another send on the requestKex channel,
|
|
||||||
clear:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-t.requestKex:
|
|
||||||
//
|
|
||||||
default:
|
|
||||||
break clear
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request.done <- t.writeError
|
|
||||||
|
|
||||||
// kex finished. Push packets that we received while
|
|
||||||
// the kex was in progress. Don't look at t.startKex
|
|
||||||
// and don't increment writtenSinceKex: if we trigger
|
|
||||||
// another kex while we are still busy with the last
|
|
||||||
// one, things will become very confusing.
|
|
||||||
for _, p := range t.pendingPackets {
|
|
||||||
t.writeError = t.pushPacket(p)
|
|
||||||
if t.writeError != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.pendingPackets = t.pendingPackets[:0]
|
|
||||||
t.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// drain startKex channel. We don't service t.requestKex
|
|
||||||
// because nobody does blocking sends there.
|
|
||||||
go func() {
|
|
||||||
for init := range t.startKex {
|
|
||||||
init.done <- t.writeError
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Unblock reader.
|
|
||||||
t.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// The protocol uses uint32 for packet counters, so we can't let them
|
|
||||||
// reach 1<<32. We will actually read and write more packets than
|
|
||||||
// this, though: the other side may send more packets, and after we
|
|
||||||
// hit this limit on writing we will send a few more packets for the
|
|
||||||
// key exchange itself.
|
|
||||||
const packetRekeyThreshold = (1 << 31)
|
|
||||||
|
|
||||||
func (t *handshakeTransport) resetReadThresholds() {
|
|
||||||
t.readPacketsLeft = packetRekeyThreshold
|
|
||||||
if t.config.RekeyThreshold > 0 {
|
|
||||||
t.readBytesLeft = int64(t.config.RekeyThreshold)
|
|
||||||
} else if t.algorithms != nil {
|
|
||||||
t.readBytesLeft = t.algorithms.r.rekeyBytes()
|
|
||||||
} else {
|
|
||||||
t.readBytesLeft = 1 << 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) {
|
|
||||||
p, err := t.conn.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.readPacketsLeft > 0 {
|
|
||||||
t.readPacketsLeft--
|
|
||||||
} else {
|
|
||||||
t.requestKeyExchange()
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.readBytesLeft > 0 {
|
|
||||||
t.readBytesLeft -= int64(len(p))
|
|
||||||
} else {
|
|
||||||
t.requestKeyExchange()
|
|
||||||
}
|
|
||||||
|
|
||||||
if debugHandshake {
|
|
||||||
t.printPacket(p, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if first && p[0] != msgKexInit {
|
|
||||||
return nil, fmt.Errorf("ssh: first packet should be msgKexInit")
|
|
||||||
}
|
|
||||||
|
|
||||||
if p[0] != msgKexInit {
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
firstKex := t.sessionID == nil
|
|
||||||
|
|
||||||
kex := pendingKex{
|
|
||||||
done: make(chan error, 1),
|
|
||||||
otherInit: p,
|
|
||||||
}
|
|
||||||
t.startKex <- &kex
|
|
||||||
err = <-kex.done
|
|
||||||
|
|
||||||
if debugHandshake {
|
|
||||||
log.Printf("%s exited key exchange (first %v), err %v", t.id(), firstKex, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t.resetReadThresholds()
|
|
||||||
|
|
||||||
// By default, a key exchange is hidden from higher layers by
|
|
||||||
// translating it into msgIgnore.
|
|
||||||
successPacket := []byte{msgIgnore}
|
|
||||||
if firstKex {
|
|
||||||
// sendKexInit() for the first kex waits for
|
|
||||||
// msgNewKeys so the authentication process is
|
|
||||||
// guaranteed to happen over an encrypted transport.
|
|
||||||
successPacket = []byte{msgNewKeys}
|
|
||||||
}
|
|
||||||
|
|
||||||
return successPacket, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendKexInit sends a key change message.
|
|
||||||
func (t *handshakeTransport) sendKexInit() error {
|
|
||||||
t.mu.Lock()
|
|
||||||
defer t.mu.Unlock()
|
|
||||||
if t.sentInitMsg != nil {
|
|
||||||
// kexInits may be sent either in response to the other side,
|
|
||||||
// or because our side wants to initiate a key change, so we
|
|
||||||
// may have already sent a kexInit. In that case, don't send a
|
|
||||||
// second kexInit.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := &kexInitMsg{
|
|
||||||
KexAlgos: t.config.KeyExchanges,
|
|
||||||
CiphersClientServer: t.config.Ciphers,
|
|
||||||
CiphersServerClient: t.config.Ciphers,
|
|
||||||
MACsClientServer: t.config.MACs,
|
|
||||||
MACsServerClient: t.config.MACs,
|
|
||||||
CompressionClientServer: supportedCompressions,
|
|
||||||
CompressionServerClient: supportedCompressions,
|
|
||||||
}
|
|
||||||
io.ReadFull(rand.Reader, msg.Cookie[:])
|
|
||||||
|
|
||||||
if len(t.hostKeys) > 0 {
|
|
||||||
for _, k := range t.hostKeys {
|
|
||||||
msg.ServerHostKeyAlgos = append(
|
|
||||||
msg.ServerHostKeyAlgos, k.PublicKey().Type())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
msg.ServerHostKeyAlgos = t.hostKeyAlgorithms
|
|
||||||
}
|
|
||||||
packet := Marshal(msg)
|
|
||||||
|
|
||||||
// writePacket destroys the contents, so save a copy.
|
|
||||||
packetCopy := make([]byte, len(packet))
|
|
||||||
copy(packetCopy, packet)
|
|
||||||
|
|
||||||
if err := t.pushPacket(packetCopy); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
t.sentInitMsg = msg
|
|
||||||
t.sentInitPacket = packet
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *handshakeTransport) writePacket(p []byte) error {
|
|
||||||
switch p[0] {
|
|
||||||
case msgKexInit:
|
|
||||||
return errors.New("ssh: only handshakeTransport can send kexInit")
|
|
||||||
case msgNewKeys:
|
|
||||||
return errors.New("ssh: only handshakeTransport can send newKeys")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.mu.Lock()
|
|
||||||
defer t.mu.Unlock()
|
|
||||||
if t.writeError != nil {
|
|
||||||
return t.writeError
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.sentInitMsg != nil {
|
|
||||||
// Copy the packet so the writer can reuse the buffer.
|
|
||||||
cp := make([]byte, len(p))
|
|
||||||
copy(cp, p)
|
|
||||||
t.pendingPackets = append(t.pendingPackets, cp)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.writeBytesLeft > 0 {
|
|
||||||
t.writeBytesLeft -= int64(len(p))
|
|
||||||
} else {
|
|
||||||
t.requestKeyExchange()
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.writePacketsLeft > 0 {
|
|
||||||
t.writePacketsLeft--
|
|
||||||
} else {
|
|
||||||
t.requestKeyExchange()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := t.pushPacket(p); err != nil {
|
|
||||||
t.writeError = err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *handshakeTransport) Close() error {
|
|
||||||
return t.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
|
|
||||||
if debugHandshake {
|
|
||||||
log.Printf("%s entered key exchange", t.id())
|
|
||||||
}
|
|
||||||
|
|
||||||
otherInit := &kexInitMsg{}
|
|
||||||
if err := Unmarshal(otherInitPacket, otherInit); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
magics := handshakeMagics{
|
|
||||||
clientVersion: t.clientVersion,
|
|
||||||
serverVersion: t.serverVersion,
|
|
||||||
clientKexInit: otherInitPacket,
|
|
||||||
serverKexInit: t.sentInitPacket,
|
|
||||||
}
|
|
||||||
|
|
||||||
clientInit := otherInit
|
|
||||||
serverInit := t.sentInitMsg
|
|
||||||
if len(t.hostKeys) == 0 {
|
|
||||||
clientInit, serverInit = serverInit, clientInit
|
|
||||||
|
|
||||||
magics.clientKexInit = t.sentInitPacket
|
|
||||||
magics.serverKexInit = otherInitPacket
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
t.algorithms, err = findAgreedAlgorithms(clientInit, serverInit)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't send FirstKexFollows, but we handle receiving it.
|
|
||||||
//
|
|
||||||
// RFC 4253 section 7 defines the kex and the agreement method for
|
|
||||||
// first_kex_packet_follows. It states that the guessed packet
|
|
||||||
// should be ignored if the "kex algorithm and/or the host
|
|
||||||
// key algorithm is guessed wrong (server and client have
|
|
||||||
// different preferred algorithm), or if any of the other
|
|
||||||
// algorithms cannot be agreed upon". The other algorithms have
|
|
||||||
// already been checked above so the kex algorithm and host key
|
|
||||||
// algorithm are checked here.
|
|
||||||
if otherInit.FirstKexFollows && (clientInit.KexAlgos[0] != serverInit.KexAlgos[0] || clientInit.ServerHostKeyAlgos[0] != serverInit.ServerHostKeyAlgos[0]) {
|
|
||||||
// other side sent a kex message for the wrong algorithm,
|
|
||||||
// which we have to ignore.
|
|
||||||
if _, err := t.conn.readPacket(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kex, ok := kexAlgoMap[t.algorithms.kex]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("ssh: unexpected key exchange algorithm %v", t.algorithms.kex)
|
|
||||||
}
|
|
||||||
|
|
||||||
var result *kexResult
|
|
||||||
if len(t.hostKeys) > 0 {
|
|
||||||
result, err = t.server(kex, t.algorithms, &magics)
|
|
||||||
} else {
|
|
||||||
result, err = t.client(kex, t.algorithms, &magics)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.sessionID == nil {
|
|
||||||
t.sessionID = result.H
|
|
||||||
}
|
|
||||||
result.SessionID = t.sessionID
|
|
||||||
|
|
||||||
if err := t.conn.prepareKeyChange(t.algorithms, result); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if packet, err := t.conn.readPacket(); err != nil {
|
|
||||||
return err
|
|
||||||
} else if packet[0] != msgNewKeys {
|
|
||||||
return unexpectedMessageError(msgNewKeys, packet[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *handshakeTransport) server(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) {
|
|
||||||
var hostKey Signer
|
|
||||||
for _, k := range t.hostKeys {
|
|
||||||
if algs.hostKey == k.PublicKey().Type() {
|
|
||||||
hostKey = k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := kex.Server(t.conn, t.config.Rand, magics, hostKey)
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) {
|
|
||||||
result, err := kex.Client(t.conn, t.config.Rand, magics)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hostKey, err := ParsePublicKey(result.HostKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := verifyHostKeySignature(hostKey, result); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
-540
@@ -1,540 +0,0 @@
|
|||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/subtle"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/curve25519"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
|
|
||||||
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
|
|
||||||
kexAlgoECDH256 = "ecdh-sha2-nistp256"
|
|
||||||
kexAlgoECDH384 = "ecdh-sha2-nistp384"
|
|
||||||
kexAlgoECDH521 = "ecdh-sha2-nistp521"
|
|
||||||
kexAlgoCurve25519SHA256 = "curve25519-sha256@libssh.org"
|
|
||||||
)
|
|
||||||
|
|
||||||
// kexResult captures the outcome of a key exchange.
|
|
||||||
type kexResult struct {
|
|
||||||
// Session hash. See also RFC 4253, section 8.
|
|
||||||
H []byte
|
|
||||||
|
|
||||||
// Shared secret. See also RFC 4253, section 8.
|
|
||||||
K []byte
|
|
||||||
|
|
||||||
// Host key as hashed into H.
|
|
||||||
HostKey []byte
|
|
||||||
|
|
||||||
// Signature of H.
|
|
||||||
Signature []byte
|
|
||||||
|
|
||||||
// A cryptographic hash function that matches the security
|
|
||||||
// level of the key exchange algorithm. It is used for
|
|
||||||
// calculating H, and for deriving keys from H and K.
|
|
||||||
Hash crypto.Hash
|
|
||||||
|
|
||||||
// The session ID, which is the first H computed. This is used
|
|
||||||
// to derive key material inside the transport.
|
|
||||||
SessionID []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// handshakeMagics contains data that is always included in the
|
|
||||||
// session hash.
|
|
||||||
type handshakeMagics struct {
|
|
||||||
clientVersion, serverVersion []byte
|
|
||||||
clientKexInit, serverKexInit []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *handshakeMagics) write(w io.Writer) {
|
|
||||||
writeString(w, m.clientVersion)
|
|
||||||
writeString(w, m.serverVersion)
|
|
||||||
writeString(w, m.clientKexInit)
|
|
||||||
writeString(w, m.serverKexInit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// kexAlgorithm abstracts different key exchange algorithms.
|
|
||||||
type kexAlgorithm interface {
|
|
||||||
// Server runs server-side key agreement, signing the result
|
|
||||||
// with a hostkey.
|
|
||||||
Server(p packetConn, rand io.Reader, magics *handshakeMagics, s Signer) (*kexResult, error)
|
|
||||||
|
|
||||||
// Client runs the client-side key agreement. Caller is
|
|
||||||
// responsible for verifying the host key signature.
|
|
||||||
Client(p packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement.
|
|
||||||
type dhGroup struct {
|
|
||||||
g, p, pMinus1 *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (group *dhGroup) diffieHellman(theirPublic, myPrivate *big.Int) (*big.Int, error) {
|
|
||||||
if theirPublic.Cmp(bigOne) <= 0 || theirPublic.Cmp(group.pMinus1) >= 0 {
|
|
||||||
return nil, errors.New("ssh: DH parameter out of bounds")
|
|
||||||
}
|
|
||||||
return new(big.Int).Exp(theirPublic, myPrivate, group.p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handshakeMagics) (*kexResult, error) {
|
|
||||||
hashFunc := crypto.SHA1
|
|
||||||
|
|
||||||
var x *big.Int
|
|
||||||
for {
|
|
||||||
var err error
|
|
||||||
if x, err = rand.Int(randSource, group.pMinus1); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if x.Sign() > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
X := new(big.Int).Exp(group.g, x, group.p)
|
|
||||||
kexDHInit := kexDHInitMsg{
|
|
||||||
X: X,
|
|
||||||
}
|
|
||||||
if err := c.writePacket(Marshal(&kexDHInit)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
packet, err := c.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var kexDHReply kexDHReplyMsg
|
|
||||||
if err = Unmarshal(packet, &kexDHReply); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
kInt, err := group.diffieHellman(kexDHReply.Y, x)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
h := hashFunc.New()
|
|
||||||
magics.write(h)
|
|
||||||
writeString(h, kexDHReply.HostKey)
|
|
||||||
writeInt(h, X)
|
|
||||||
writeInt(h, kexDHReply.Y)
|
|
||||||
K := make([]byte, intLength(kInt))
|
|
||||||
marshalInt(K, kInt)
|
|
||||||
h.Write(K)
|
|
||||||
|
|
||||||
return &kexResult{
|
|
||||||
H: h.Sum(nil),
|
|
||||||
K: K,
|
|
||||||
HostKey: kexDHReply.HostKey,
|
|
||||||
Signature: kexDHReply.Signature,
|
|
||||||
Hash: crypto.SHA1,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
|
|
||||||
hashFunc := crypto.SHA1
|
|
||||||
packet, err := c.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var kexDHInit kexDHInitMsg
|
|
||||||
if err = Unmarshal(packet, &kexDHInit); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var y *big.Int
|
|
||||||
for {
|
|
||||||
if y, err = rand.Int(randSource, group.pMinus1); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if y.Sign() > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Y := new(big.Int).Exp(group.g, y, group.p)
|
|
||||||
kInt, err := group.diffieHellman(kexDHInit.X, y)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hostKeyBytes := priv.PublicKey().Marshal()
|
|
||||||
|
|
||||||
h := hashFunc.New()
|
|
||||||
magics.write(h)
|
|
||||||
writeString(h, hostKeyBytes)
|
|
||||||
writeInt(h, kexDHInit.X)
|
|
||||||
writeInt(h, Y)
|
|
||||||
|
|
||||||
K := make([]byte, intLength(kInt))
|
|
||||||
marshalInt(K, kInt)
|
|
||||||
h.Write(K)
|
|
||||||
|
|
||||||
H := h.Sum(nil)
|
|
||||||
|
|
||||||
// H is already a hash, but the hostkey signing will apply its
|
|
||||||
// own key-specific hash algorithm.
|
|
||||||
sig, err := signAndMarshal(priv, randSource, H)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
kexDHReply := kexDHReplyMsg{
|
|
||||||
HostKey: hostKeyBytes,
|
|
||||||
Y: Y,
|
|
||||||
Signature: sig,
|
|
||||||
}
|
|
||||||
packet = Marshal(&kexDHReply)
|
|
||||||
|
|
||||||
err = c.writePacket(packet)
|
|
||||||
return &kexResult{
|
|
||||||
H: H,
|
|
||||||
K: K,
|
|
||||||
HostKey: hostKeyBytes,
|
|
||||||
Signature: sig,
|
|
||||||
Hash: crypto.SHA1,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ecdh performs Elliptic Curve Diffie-Hellman key exchange as
|
|
||||||
// described in RFC 5656, section 4.
|
|
||||||
type ecdh struct {
|
|
||||||
curve elliptic.Curve
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kex *ecdh) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) {
|
|
||||||
ephKey, err := ecdsa.GenerateKey(kex.curve, rand)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
kexInit := kexECDHInitMsg{
|
|
||||||
ClientPubKey: elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y),
|
|
||||||
}
|
|
||||||
|
|
||||||
serialized := Marshal(&kexInit)
|
|
||||||
if err := c.writePacket(serialized); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
packet, err := c.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var reply kexECDHReplyMsg
|
|
||||||
if err = Unmarshal(packet, &reply); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
x, y, err := unmarshalECKey(kex.curve, reply.EphemeralPubKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate shared secret
|
|
||||||
secret, _ := kex.curve.ScalarMult(x, y, ephKey.D.Bytes())
|
|
||||||
|
|
||||||
h := ecHash(kex.curve).New()
|
|
||||||
magics.write(h)
|
|
||||||
writeString(h, reply.HostKey)
|
|
||||||
writeString(h, kexInit.ClientPubKey)
|
|
||||||
writeString(h, reply.EphemeralPubKey)
|
|
||||||
K := make([]byte, intLength(secret))
|
|
||||||
marshalInt(K, secret)
|
|
||||||
h.Write(K)
|
|
||||||
|
|
||||||
return &kexResult{
|
|
||||||
H: h.Sum(nil),
|
|
||||||
K: K,
|
|
||||||
HostKey: reply.HostKey,
|
|
||||||
Signature: reply.Signature,
|
|
||||||
Hash: ecHash(kex.curve),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// unmarshalECKey parses and checks an EC key.
|
|
||||||
func unmarshalECKey(curve elliptic.Curve, pubkey []byte) (x, y *big.Int, err error) {
|
|
||||||
x, y = elliptic.Unmarshal(curve, pubkey)
|
|
||||||
if x == nil {
|
|
||||||
return nil, nil, errors.New("ssh: elliptic.Unmarshal failure")
|
|
||||||
}
|
|
||||||
if !validateECPublicKey(curve, x, y) {
|
|
||||||
return nil, nil, errors.New("ssh: public key not on curve")
|
|
||||||
}
|
|
||||||
return x, y, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateECPublicKey checks that the point is a valid public key for
|
|
||||||
// the given curve. See [SEC1], 3.2.2
|
|
||||||
func validateECPublicKey(curve elliptic.Curve, x, y *big.Int) bool {
|
|
||||||
if x.Sign() == 0 && y.Sign() == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if x.Cmp(curve.Params().P) >= 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if y.Cmp(curve.Params().P) >= 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !curve.IsOnCurve(x, y) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't check if N * PubKey == 0, since
|
|
||||||
//
|
|
||||||
// - the NIST curves have cofactor = 1, so this is implicit.
|
|
||||||
// (We don't foresee an implementation that supports non NIST
|
|
||||||
// curves)
|
|
||||||
//
|
|
||||||
// - for ephemeral keys, we don't need to worry about small
|
|
||||||
// subgroup attacks.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kex *ecdh) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
|
|
||||||
packet, err := c.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var kexECDHInit kexECDHInitMsg
|
|
||||||
if err = Unmarshal(packet, &kexECDHInit); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
clientX, clientY, err := unmarshalECKey(kex.curve, kexECDHInit.ClientPubKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We could cache this key across multiple users/multiple
|
|
||||||
// connection attempts, but the benefit is small. OpenSSH
|
|
||||||
// generates a new key for each incoming connection.
|
|
||||||
ephKey, err := ecdsa.GenerateKey(kex.curve, rand)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hostKeyBytes := priv.PublicKey().Marshal()
|
|
||||||
|
|
||||||
serializedEphKey := elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y)
|
|
||||||
|
|
||||||
// generate shared secret
|
|
||||||
secret, _ := kex.curve.ScalarMult(clientX, clientY, ephKey.D.Bytes())
|
|
||||||
|
|
||||||
h := ecHash(kex.curve).New()
|
|
||||||
magics.write(h)
|
|
||||||
writeString(h, hostKeyBytes)
|
|
||||||
writeString(h, kexECDHInit.ClientPubKey)
|
|
||||||
writeString(h, serializedEphKey)
|
|
||||||
|
|
||||||
K := make([]byte, intLength(secret))
|
|
||||||
marshalInt(K, secret)
|
|
||||||
h.Write(K)
|
|
||||||
|
|
||||||
H := h.Sum(nil)
|
|
||||||
|
|
||||||
// H is already a hash, but the hostkey signing will apply its
|
|
||||||
// own key-specific hash algorithm.
|
|
||||||
sig, err := signAndMarshal(priv, rand, H)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
reply := kexECDHReplyMsg{
|
|
||||||
EphemeralPubKey: serializedEphKey,
|
|
||||||
HostKey: hostKeyBytes,
|
|
||||||
Signature: sig,
|
|
||||||
}
|
|
||||||
|
|
||||||
serialized := Marshal(&reply)
|
|
||||||
if err := c.writePacket(serialized); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &kexResult{
|
|
||||||
H: H,
|
|
||||||
K: K,
|
|
||||||
HostKey: reply.HostKey,
|
|
||||||
Signature: sig,
|
|
||||||
Hash: ecHash(kex.curve),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var kexAlgoMap = map[string]kexAlgorithm{}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// This is the group called diffie-hellman-group1-sha1 in RFC
|
|
||||||
// 4253 and Oakley Group 2 in RFC 2409.
|
|
||||||
p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF", 16)
|
|
||||||
kexAlgoMap[kexAlgoDH1SHA1] = &dhGroup{
|
|
||||||
g: new(big.Int).SetInt64(2),
|
|
||||||
p: p,
|
|
||||||
pMinus1: new(big.Int).Sub(p, bigOne),
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the group called diffie-hellman-group14-sha1 in RFC
|
|
||||||
// 4253 and Oakley Group 14 in RFC 3526.
|
|
||||||
p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
|
|
||||||
|
|
||||||
kexAlgoMap[kexAlgoDH14SHA1] = &dhGroup{
|
|
||||||
g: new(big.Int).SetInt64(2),
|
|
||||||
p: p,
|
|
||||||
pMinus1: new(big.Int).Sub(p, bigOne),
|
|
||||||
}
|
|
||||||
|
|
||||||
kexAlgoMap[kexAlgoECDH521] = &ecdh{elliptic.P521()}
|
|
||||||
kexAlgoMap[kexAlgoECDH384] = &ecdh{elliptic.P384()}
|
|
||||||
kexAlgoMap[kexAlgoECDH256] = &ecdh{elliptic.P256()}
|
|
||||||
kexAlgoMap[kexAlgoCurve25519SHA256] = &curve25519sha256{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// curve25519sha256 implements the curve25519-sha256@libssh.org key
|
|
||||||
// agreement protocol, as described in
|
|
||||||
// https://git.libssh.org/projects/libssh.git/tree/doc/curve25519-sha256@libssh.org.txt
|
|
||||||
type curve25519sha256 struct{}
|
|
||||||
|
|
||||||
type curve25519KeyPair struct {
|
|
||||||
priv [32]byte
|
|
||||||
pub [32]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kp *curve25519KeyPair) generate(rand io.Reader) error {
|
|
||||||
if _, err := io.ReadFull(rand, kp.priv[:]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
curve25519.ScalarBaseMult(&kp.pub, &kp.priv)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// curve25519Zeros is just an array of 32 zero bytes so that we have something
|
|
||||||
// convenient to compare against in order to reject curve25519 points with the
|
|
||||||
// wrong order.
|
|
||||||
var curve25519Zeros [32]byte
|
|
||||||
|
|
||||||
func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) {
|
|
||||||
var kp curve25519KeyPair
|
|
||||||
if err := kp.generate(rand); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := c.writePacket(Marshal(&kexECDHInitMsg{kp.pub[:]})); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
packet, err := c.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var reply kexECDHReplyMsg
|
|
||||||
if err = Unmarshal(packet, &reply); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(reply.EphemeralPubKey) != 32 {
|
|
||||||
return nil, errors.New("ssh: peer's curve25519 public value has wrong length")
|
|
||||||
}
|
|
||||||
|
|
||||||
var servPub, secret [32]byte
|
|
||||||
copy(servPub[:], reply.EphemeralPubKey)
|
|
||||||
curve25519.ScalarMult(&secret, &kp.priv, &servPub)
|
|
||||||
if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 {
|
|
||||||
return nil, errors.New("ssh: peer's curve25519 public value has wrong order")
|
|
||||||
}
|
|
||||||
|
|
||||||
h := crypto.SHA256.New()
|
|
||||||
magics.write(h)
|
|
||||||
writeString(h, reply.HostKey)
|
|
||||||
writeString(h, kp.pub[:])
|
|
||||||
writeString(h, reply.EphemeralPubKey)
|
|
||||||
|
|
||||||
kInt := new(big.Int).SetBytes(secret[:])
|
|
||||||
K := make([]byte, intLength(kInt))
|
|
||||||
marshalInt(K, kInt)
|
|
||||||
h.Write(K)
|
|
||||||
|
|
||||||
return &kexResult{
|
|
||||||
H: h.Sum(nil),
|
|
||||||
K: K,
|
|
||||||
HostKey: reply.HostKey,
|
|
||||||
Signature: reply.Signature,
|
|
||||||
Hash: crypto.SHA256,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
|
|
||||||
packet, err := c.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var kexInit kexECDHInitMsg
|
|
||||||
if err = Unmarshal(packet, &kexInit); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(kexInit.ClientPubKey) != 32 {
|
|
||||||
return nil, errors.New("ssh: peer's curve25519 public value has wrong length")
|
|
||||||
}
|
|
||||||
|
|
||||||
var kp curve25519KeyPair
|
|
||||||
if err := kp.generate(rand); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var clientPub, secret [32]byte
|
|
||||||
copy(clientPub[:], kexInit.ClientPubKey)
|
|
||||||
curve25519.ScalarMult(&secret, &kp.priv, &clientPub)
|
|
||||||
if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 {
|
|
||||||
return nil, errors.New("ssh: peer's curve25519 public value has wrong order")
|
|
||||||
}
|
|
||||||
|
|
||||||
hostKeyBytes := priv.PublicKey().Marshal()
|
|
||||||
|
|
||||||
h := crypto.SHA256.New()
|
|
||||||
magics.write(h)
|
|
||||||
writeString(h, hostKeyBytes)
|
|
||||||
writeString(h, kexInit.ClientPubKey)
|
|
||||||
writeString(h, kp.pub[:])
|
|
||||||
|
|
||||||
kInt := new(big.Int).SetBytes(secret[:])
|
|
||||||
K := make([]byte, intLength(kInt))
|
|
||||||
marshalInt(K, kInt)
|
|
||||||
h.Write(K)
|
|
||||||
|
|
||||||
H := h.Sum(nil)
|
|
||||||
|
|
||||||
sig, err := signAndMarshal(priv, rand, H)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
reply := kexECDHReplyMsg{
|
|
||||||
EphemeralPubKey: kp.pub[:],
|
|
||||||
HostKey: hostKeyBytes,
|
|
||||||
Signature: sig,
|
|
||||||
}
|
|
||||||
if err := c.writePacket(Marshal(&reply)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &kexResult{
|
|
||||||
H: H,
|
|
||||||
K: K,
|
|
||||||
HostKey: hostKeyBytes,
|
|
||||||
Signature: sig,
|
|
||||||
Hash: crypto.SHA256,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
-957
@@ -1,957 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto"
|
|
||||||
"crypto/dsa"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/asn1"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ed25519"
|
|
||||||
)
|
|
||||||
|
|
||||||
// These constants represent the algorithm names for key types supported by this
|
|
||||||
// package.
|
|
||||||
const (
|
|
||||||
KeyAlgoRSA = "ssh-rsa"
|
|
||||||
KeyAlgoDSA = "ssh-dss"
|
|
||||||
KeyAlgoECDSA256 = "ecdsa-sha2-nistp256"
|
|
||||||
KeyAlgoECDSA384 = "ecdsa-sha2-nistp384"
|
|
||||||
KeyAlgoECDSA521 = "ecdsa-sha2-nistp521"
|
|
||||||
KeyAlgoED25519 = "ssh-ed25519"
|
|
||||||
)
|
|
||||||
|
|
||||||
// parsePubKey parses a public key of the given algorithm.
|
|
||||||
// Use ParsePublicKey for keys with prepended algorithm.
|
|
||||||
func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, err error) {
|
|
||||||
switch algo {
|
|
||||||
case KeyAlgoRSA:
|
|
||||||
return parseRSA(in)
|
|
||||||
case KeyAlgoDSA:
|
|
||||||
return parseDSA(in)
|
|
||||||
case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521:
|
|
||||||
return parseECDSA(in)
|
|
||||||
case KeyAlgoED25519:
|
|
||||||
return parseED25519(in)
|
|
||||||
case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01:
|
|
||||||
cert, err := parseCert(in, certToPrivAlgo(algo))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return cert, nil, nil
|
|
||||||
}
|
|
||||||
return nil, nil, fmt.Errorf("ssh: unknown key algorithm: %v", algo)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseAuthorizedKey parses a public key in OpenSSH authorized_keys format
|
|
||||||
// (see sshd(8) manual page) once the options and key type fields have been
|
|
||||||
// removed.
|
|
||||||
func parseAuthorizedKey(in []byte) (out PublicKey, comment string, err error) {
|
|
||||||
in = bytes.TrimSpace(in)
|
|
||||||
|
|
||||||
i := bytes.IndexAny(in, " \t")
|
|
||||||
if i == -1 {
|
|
||||||
i = len(in)
|
|
||||||
}
|
|
||||||
base64Key := in[:i]
|
|
||||||
|
|
||||||
key := make([]byte, base64.StdEncoding.DecodedLen(len(base64Key)))
|
|
||||||
n, err := base64.StdEncoding.Decode(key, base64Key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
key = key[:n]
|
|
||||||
out, err = ParsePublicKey(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
comment = string(bytes.TrimSpace(in[i:]))
|
|
||||||
return out, comment, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseKnownHosts parses an entry in the format of the known_hosts file.
|
|
||||||
//
|
|
||||||
// The known_hosts format is documented in the sshd(8) manual page. This
|
|
||||||
// function will parse a single entry from in. On successful return, marker
|
|
||||||
// will contain the optional marker value (i.e. "cert-authority" or "revoked")
|
|
||||||
// or else be empty, hosts will contain the hosts that this entry matches,
|
|
||||||
// pubKey will contain the public key and comment will contain any trailing
|
|
||||||
// comment at the end of the line. See the sshd(8) manual page for the various
|
|
||||||
// forms that a host string can take.
|
|
||||||
//
|
|
||||||
// The unparsed remainder of the input will be returned in rest. This function
|
|
||||||
// can be called repeatedly to parse multiple entries.
|
|
||||||
//
|
|
||||||
// If no entries were found in the input then err will be io.EOF. Otherwise a
|
|
||||||
// non-nil err value indicates a parse error.
|
|
||||||
func ParseKnownHosts(in []byte) (marker string, hosts []string, pubKey PublicKey, comment string, rest []byte, err error) {
|
|
||||||
for len(in) > 0 {
|
|
||||||
end := bytes.IndexByte(in, '\n')
|
|
||||||
if end != -1 {
|
|
||||||
rest = in[end+1:]
|
|
||||||
in = in[:end]
|
|
||||||
} else {
|
|
||||||
rest = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
end = bytes.IndexByte(in, '\r')
|
|
||||||
if end != -1 {
|
|
||||||
in = in[:end]
|
|
||||||
}
|
|
||||||
|
|
||||||
in = bytes.TrimSpace(in)
|
|
||||||
if len(in) == 0 || in[0] == '#' {
|
|
||||||
in = rest
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
i := bytes.IndexAny(in, " \t")
|
|
||||||
if i == -1 {
|
|
||||||
in = rest
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip out the beginning of the known_host key.
|
|
||||||
// This is either an optional marker or a (set of) hostname(s).
|
|
||||||
keyFields := bytes.Fields(in)
|
|
||||||
if len(keyFields) < 3 || len(keyFields) > 5 {
|
|
||||||
return "", nil, nil, "", nil, errors.New("ssh: invalid entry in known_hosts data")
|
|
||||||
}
|
|
||||||
|
|
||||||
// keyFields[0] is either "@cert-authority", "@revoked" or a comma separated
|
|
||||||
// list of hosts
|
|
||||||
marker := ""
|
|
||||||
if keyFields[0][0] == '@' {
|
|
||||||
marker = string(keyFields[0][1:])
|
|
||||||
keyFields = keyFields[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
hosts := string(keyFields[0])
|
|
||||||
// keyFields[1] contains the key type (e.g. “ssh-rsa”).
|
|
||||||
// However, that information is duplicated inside the
|
|
||||||
// base64-encoded key and so is ignored here.
|
|
||||||
|
|
||||||
key := bytes.Join(keyFields[2:], []byte(" "))
|
|
||||||
if pubKey, comment, err = parseAuthorizedKey(key); err != nil {
|
|
||||||
return "", nil, nil, "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return marker, strings.Split(hosts, ","), pubKey, comment, rest, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", nil, nil, "", nil, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseAuthorizedKeys parses a public key from an authorized_keys
|
|
||||||
// file used in OpenSSH according to the sshd(8) manual page.
|
|
||||||
func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) {
|
|
||||||
for len(in) > 0 {
|
|
||||||
end := bytes.IndexByte(in, '\n')
|
|
||||||
if end != -1 {
|
|
||||||
rest = in[end+1:]
|
|
||||||
in = in[:end]
|
|
||||||
} else {
|
|
||||||
rest = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
end = bytes.IndexByte(in, '\r')
|
|
||||||
if end != -1 {
|
|
||||||
in = in[:end]
|
|
||||||
}
|
|
||||||
|
|
||||||
in = bytes.TrimSpace(in)
|
|
||||||
if len(in) == 0 || in[0] == '#' {
|
|
||||||
in = rest
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
i := bytes.IndexAny(in, " \t")
|
|
||||||
if i == -1 {
|
|
||||||
in = rest
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if out, comment, err = parseAuthorizedKey(in[i:]); err == nil {
|
|
||||||
return out, comment, options, rest, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// No key type recognised. Maybe there's an options field at
|
|
||||||
// the beginning.
|
|
||||||
var b byte
|
|
||||||
inQuote := false
|
|
||||||
var candidateOptions []string
|
|
||||||
optionStart := 0
|
|
||||||
for i, b = range in {
|
|
||||||
isEnd := !inQuote && (b == ' ' || b == '\t')
|
|
||||||
if (b == ',' && !inQuote) || isEnd {
|
|
||||||
if i-optionStart > 0 {
|
|
||||||
candidateOptions = append(candidateOptions, string(in[optionStart:i]))
|
|
||||||
}
|
|
||||||
optionStart = i + 1
|
|
||||||
}
|
|
||||||
if isEnd {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if b == '"' && (i == 0 || (i > 0 && in[i-1] != '\\')) {
|
|
||||||
inQuote = !inQuote
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i < len(in) && (in[i] == ' ' || in[i] == '\t') {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
if i == len(in) {
|
|
||||||
// Invalid line: unmatched quote
|
|
||||||
in = rest
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
in = in[i:]
|
|
||||||
i = bytes.IndexAny(in, " \t")
|
|
||||||
if i == -1 {
|
|
||||||
in = rest
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if out, comment, err = parseAuthorizedKey(in[i:]); err == nil {
|
|
||||||
options = candidateOptions
|
|
||||||
return out, comment, options, rest, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
in = rest
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, "", nil, nil, errors.New("ssh: no key found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParsePublicKey parses an SSH public key formatted for use in
|
|
||||||
// the SSH wire protocol according to RFC 4253, section 6.6.
|
|
||||||
func ParsePublicKey(in []byte) (out PublicKey, err error) {
|
|
||||||
algo, in, ok := parseString(in)
|
|
||||||
if !ok {
|
|
||||||
return nil, errShortRead
|
|
||||||
}
|
|
||||||
var rest []byte
|
|
||||||
out, rest, err = parsePubKey(in, string(algo))
|
|
||||||
if len(rest) > 0 {
|
|
||||||
return nil, errors.New("ssh: trailing junk in public key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalAuthorizedKey serializes key for inclusion in an OpenSSH
|
|
||||||
// authorized_keys file. The return value ends with newline.
|
|
||||||
func MarshalAuthorizedKey(key PublicKey) []byte {
|
|
||||||
b := &bytes.Buffer{}
|
|
||||||
b.WriteString(key.Type())
|
|
||||||
b.WriteByte(' ')
|
|
||||||
e := base64.NewEncoder(base64.StdEncoding, b)
|
|
||||||
e.Write(key.Marshal())
|
|
||||||
e.Close()
|
|
||||||
b.WriteByte('\n')
|
|
||||||
return b.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// PublicKey is an abstraction of different types of public keys.
|
|
||||||
type PublicKey interface {
|
|
||||||
// Type returns the key's type, e.g. "ssh-rsa".
|
|
||||||
Type() string
|
|
||||||
|
|
||||||
// Marshal returns the serialized key data in SSH wire format,
|
|
||||||
// with the name prefix.
|
|
||||||
Marshal() []byte
|
|
||||||
|
|
||||||
// Verify that sig is a signature on the given data using this
|
|
||||||
// key. This function will hash the data appropriately first.
|
|
||||||
Verify(data []byte, sig *Signature) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptoPublicKey, if implemented by a PublicKey,
|
|
||||||
// returns the underlying crypto.PublicKey form of the key.
|
|
||||||
type CryptoPublicKey interface {
|
|
||||||
CryptoPublicKey() crypto.PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Signer can create signatures that verify against a public key.
|
|
||||||
type Signer interface {
|
|
||||||
// PublicKey returns an associated PublicKey instance.
|
|
||||||
PublicKey() PublicKey
|
|
||||||
|
|
||||||
// Sign returns raw signature for the given data. This method
|
|
||||||
// will apply the hash specified for the keytype to the data.
|
|
||||||
Sign(rand io.Reader, data []byte) (*Signature, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type rsaPublicKey rsa.PublicKey
|
|
||||||
|
|
||||||
func (r *rsaPublicKey) Type() string {
|
|
||||||
return "ssh-rsa"
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseRSA parses an RSA key according to RFC 4253, section 6.6.
|
|
||||||
func parseRSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|
||||||
var w struct {
|
|
||||||
E *big.Int
|
|
||||||
N *big.Int
|
|
||||||
Rest []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
if err := Unmarshal(in, &w); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.E.BitLen() > 24 {
|
|
||||||
return nil, nil, errors.New("ssh: exponent too large")
|
|
||||||
}
|
|
||||||
e := w.E.Int64()
|
|
||||||
if e < 3 || e&1 == 0 {
|
|
||||||
return nil, nil, errors.New("ssh: incorrect exponent")
|
|
||||||
}
|
|
||||||
|
|
||||||
var key rsa.PublicKey
|
|
||||||
key.E = int(e)
|
|
||||||
key.N = w.N
|
|
||||||
return (*rsaPublicKey)(&key), w.Rest, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rsaPublicKey) Marshal() []byte {
|
|
||||||
e := new(big.Int).SetInt64(int64(r.E))
|
|
||||||
// RSA publickey struct layout should match the struct used by
|
|
||||||
// parseRSACert in the x/crypto/ssh/agent package.
|
|
||||||
wirekey := struct {
|
|
||||||
Name string
|
|
||||||
E *big.Int
|
|
||||||
N *big.Int
|
|
||||||
}{
|
|
||||||
KeyAlgoRSA,
|
|
||||||
e,
|
|
||||||
r.N,
|
|
||||||
}
|
|
||||||
return Marshal(&wirekey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error {
|
|
||||||
if sig.Format != r.Type() {
|
|
||||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, r.Type())
|
|
||||||
}
|
|
||||||
h := crypto.SHA1.New()
|
|
||||||
h.Write(data)
|
|
||||||
digest := h.Sum(nil)
|
|
||||||
return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), crypto.SHA1, digest, sig.Blob)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
|
||||||
return (*rsa.PublicKey)(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
type dsaPublicKey dsa.PublicKey
|
|
||||||
|
|
||||||
func (r *dsaPublicKey) Type() string {
|
|
||||||
return "ssh-dss"
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseDSA parses an DSA key according to RFC 4253, section 6.6.
|
|
||||||
func parseDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|
||||||
var w struct {
|
|
||||||
P, Q, G, Y *big.Int
|
|
||||||
Rest []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
if err := Unmarshal(in, &w); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
key := &dsaPublicKey{
|
|
||||||
Parameters: dsa.Parameters{
|
|
||||||
P: w.P,
|
|
||||||
Q: w.Q,
|
|
||||||
G: w.G,
|
|
||||||
},
|
|
||||||
Y: w.Y,
|
|
||||||
}
|
|
||||||
return key, w.Rest, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *dsaPublicKey) Marshal() []byte {
|
|
||||||
// DSA publickey struct layout should match the struct used by
|
|
||||||
// parseDSACert in the x/crypto/ssh/agent package.
|
|
||||||
w := struct {
|
|
||||||
Name string
|
|
||||||
P, Q, G, Y *big.Int
|
|
||||||
}{
|
|
||||||
k.Type(),
|
|
||||||
k.P,
|
|
||||||
k.Q,
|
|
||||||
k.G,
|
|
||||||
k.Y,
|
|
||||||
}
|
|
||||||
|
|
||||||
return Marshal(&w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *dsaPublicKey) Verify(data []byte, sig *Signature) error {
|
|
||||||
if sig.Format != k.Type() {
|
|
||||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
|
||||||
}
|
|
||||||
h := crypto.SHA1.New()
|
|
||||||
h.Write(data)
|
|
||||||
digest := h.Sum(nil)
|
|
||||||
|
|
||||||
// Per RFC 4253, section 6.6,
|
|
||||||
// The value for 'dss_signature_blob' is encoded as a string containing
|
|
||||||
// r, followed by s (which are 160-bit integers, without lengths or
|
|
||||||
// padding, unsigned, and in network byte order).
|
|
||||||
// For DSS purposes, sig.Blob should be exactly 40 bytes in length.
|
|
||||||
if len(sig.Blob) != 40 {
|
|
||||||
return errors.New("ssh: DSA signature parse error")
|
|
||||||
}
|
|
||||||
r := new(big.Int).SetBytes(sig.Blob[:20])
|
|
||||||
s := new(big.Int).SetBytes(sig.Blob[20:])
|
|
||||||
if dsa.Verify((*dsa.PublicKey)(k), digest, r, s) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New("ssh: signature did not verify")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *dsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
|
||||||
return (*dsa.PublicKey)(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
type dsaPrivateKey struct {
|
|
||||||
*dsa.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *dsaPrivateKey) PublicKey() PublicKey {
|
|
||||||
return (*dsaPublicKey)(&k.PrivateKey.PublicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
|
||||||
h := crypto.SHA1.New()
|
|
||||||
h.Write(data)
|
|
||||||
digest := h.Sum(nil)
|
|
||||||
r, s, err := dsa.Sign(rand, k.PrivateKey, digest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sig := make([]byte, 40)
|
|
||||||
rb := r.Bytes()
|
|
||||||
sb := s.Bytes()
|
|
||||||
|
|
||||||
copy(sig[20-len(rb):20], rb)
|
|
||||||
copy(sig[40-len(sb):], sb)
|
|
||||||
|
|
||||||
return &Signature{
|
|
||||||
Format: k.PublicKey().Type(),
|
|
||||||
Blob: sig,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ecdsaPublicKey ecdsa.PublicKey
|
|
||||||
|
|
||||||
func (key *ecdsaPublicKey) Type() string {
|
|
||||||
return "ecdsa-sha2-" + key.nistID()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (key *ecdsaPublicKey) nistID() string {
|
|
||||||
switch key.Params().BitSize {
|
|
||||||
case 256:
|
|
||||||
return "nistp256"
|
|
||||||
case 384:
|
|
||||||
return "nistp384"
|
|
||||||
case 521:
|
|
||||||
return "nistp521"
|
|
||||||
}
|
|
||||||
panic("ssh: unsupported ecdsa key size")
|
|
||||||
}
|
|
||||||
|
|
||||||
type ed25519PublicKey ed25519.PublicKey
|
|
||||||
|
|
||||||
func (key ed25519PublicKey) Type() string {
|
|
||||||
return KeyAlgoED25519
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseED25519(in []byte) (out PublicKey, rest []byte, err error) {
|
|
||||||
var w struct {
|
|
||||||
KeyBytes []byte
|
|
||||||
Rest []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := Unmarshal(in, &w); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
key := ed25519.PublicKey(w.KeyBytes)
|
|
||||||
|
|
||||||
return (ed25519PublicKey)(key), w.Rest, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (key ed25519PublicKey) Marshal() []byte {
|
|
||||||
w := struct {
|
|
||||||
Name string
|
|
||||||
KeyBytes []byte
|
|
||||||
}{
|
|
||||||
KeyAlgoED25519,
|
|
||||||
[]byte(key),
|
|
||||||
}
|
|
||||||
return Marshal(&w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (key ed25519PublicKey) Verify(b []byte, sig *Signature) error {
|
|
||||||
if sig.Format != key.Type() {
|
|
||||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, key.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
edKey := (ed25519.PublicKey)(key)
|
|
||||||
if ok := ed25519.Verify(edKey, b, sig.Blob); !ok {
|
|
||||||
return errors.New("ssh: signature did not verify")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k ed25519PublicKey) CryptoPublicKey() crypto.PublicKey {
|
|
||||||
return ed25519.PublicKey(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
func supportedEllipticCurve(curve elliptic.Curve) bool {
|
|
||||||
return curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ecHash returns the hash to match the given elliptic curve, see RFC
|
|
||||||
// 5656, section 6.2.1
|
|
||||||
func ecHash(curve elliptic.Curve) crypto.Hash {
|
|
||||||
bitSize := curve.Params().BitSize
|
|
||||||
switch {
|
|
||||||
case bitSize <= 256:
|
|
||||||
return crypto.SHA256
|
|
||||||
case bitSize <= 384:
|
|
||||||
return crypto.SHA384
|
|
||||||
}
|
|
||||||
return crypto.SHA512
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseECDSA parses an ECDSA key according to RFC 5656, section 3.1.
|
|
||||||
func parseECDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|
||||||
var w struct {
|
|
||||||
Curve string
|
|
||||||
KeyBytes []byte
|
|
||||||
Rest []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := Unmarshal(in, &w); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
key := new(ecdsa.PublicKey)
|
|
||||||
|
|
||||||
switch w.Curve {
|
|
||||||
case "nistp256":
|
|
||||||
key.Curve = elliptic.P256()
|
|
||||||
case "nistp384":
|
|
||||||
key.Curve = elliptic.P384()
|
|
||||||
case "nistp521":
|
|
||||||
key.Curve = elliptic.P521()
|
|
||||||
default:
|
|
||||||
return nil, nil, errors.New("ssh: unsupported curve")
|
|
||||||
}
|
|
||||||
|
|
||||||
key.X, key.Y = elliptic.Unmarshal(key.Curve, w.KeyBytes)
|
|
||||||
if key.X == nil || key.Y == nil {
|
|
||||||
return nil, nil, errors.New("ssh: invalid curve point")
|
|
||||||
}
|
|
||||||
return (*ecdsaPublicKey)(key), w.Rest, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (key *ecdsaPublicKey) Marshal() []byte {
|
|
||||||
// See RFC 5656, section 3.1.
|
|
||||||
keyBytes := elliptic.Marshal(key.Curve, key.X, key.Y)
|
|
||||||
// ECDSA publickey struct layout should match the struct used by
|
|
||||||
// parseECDSACert in the x/crypto/ssh/agent package.
|
|
||||||
w := struct {
|
|
||||||
Name string
|
|
||||||
ID string
|
|
||||||
Key []byte
|
|
||||||
}{
|
|
||||||
key.Type(),
|
|
||||||
key.nistID(),
|
|
||||||
keyBytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
return Marshal(&w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (key *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
|
|
||||||
if sig.Format != key.Type() {
|
|
||||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, key.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
h := ecHash(key.Curve).New()
|
|
||||||
h.Write(data)
|
|
||||||
digest := h.Sum(nil)
|
|
||||||
|
|
||||||
// Per RFC 5656, section 3.1.2,
|
|
||||||
// The ecdsa_signature_blob value has the following specific encoding:
|
|
||||||
// mpint r
|
|
||||||
// mpint s
|
|
||||||
var ecSig struct {
|
|
||||||
R *big.Int
|
|
||||||
S *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := Unmarshal(sig.Blob, &ecSig); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ecdsa.Verify((*ecdsa.PublicKey)(key), digest, ecSig.R, ecSig.S) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New("ssh: signature did not verify")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *ecdsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
|
||||||
return (*ecdsa.PublicKey)(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey,
|
|
||||||
// *ecdsa.PrivateKey or any other crypto.Signer and returns a corresponding
|
|
||||||
// Signer instance. ECDSA keys must use P-256, P-384 or P-521.
|
|
||||||
func NewSignerFromKey(key interface{}) (Signer, error) {
|
|
||||||
switch key := key.(type) {
|
|
||||||
case crypto.Signer:
|
|
||||||
return NewSignerFromSigner(key)
|
|
||||||
case *dsa.PrivateKey:
|
|
||||||
return &dsaPrivateKey{key}, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("ssh: unsupported key type %T", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type wrappedSigner struct {
|
|
||||||
signer crypto.Signer
|
|
||||||
pubKey PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSignerFromSigner takes any crypto.Signer implementation and
|
|
||||||
// returns a corresponding Signer interface. This can be used, for
|
|
||||||
// example, with keys kept in hardware modules.
|
|
||||||
func NewSignerFromSigner(signer crypto.Signer) (Signer, error) {
|
|
||||||
pubKey, err := NewPublicKey(signer.Public())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &wrappedSigner{signer, pubKey}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *wrappedSigner) PublicKey() PublicKey {
|
|
||||||
return s.pubKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *wrappedSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
|
||||||
var hashFunc crypto.Hash
|
|
||||||
|
|
||||||
switch key := s.pubKey.(type) {
|
|
||||||
case *rsaPublicKey, *dsaPublicKey:
|
|
||||||
hashFunc = crypto.SHA1
|
|
||||||
case *ecdsaPublicKey:
|
|
||||||
hashFunc = ecHash(key.Curve)
|
|
||||||
case ed25519PublicKey:
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("ssh: unsupported key type %T", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
var digest []byte
|
|
||||||
if hashFunc != 0 {
|
|
||||||
h := hashFunc.New()
|
|
||||||
h.Write(data)
|
|
||||||
digest = h.Sum(nil)
|
|
||||||
} else {
|
|
||||||
digest = data
|
|
||||||
}
|
|
||||||
|
|
||||||
signature, err := s.signer.Sign(rand, digest, hashFunc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// crypto.Signer.Sign is expected to return an ASN.1-encoded signature
|
|
||||||
// for ECDSA and DSA, but that's not the encoding expected by SSH, so
|
|
||||||
// re-encode.
|
|
||||||
switch s.pubKey.(type) {
|
|
||||||
case *ecdsaPublicKey, *dsaPublicKey:
|
|
||||||
type asn1Signature struct {
|
|
||||||
R, S *big.Int
|
|
||||||
}
|
|
||||||
asn1Sig := new(asn1Signature)
|
|
||||||
_, err := asn1.Unmarshal(signature, asn1Sig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch s.pubKey.(type) {
|
|
||||||
case *ecdsaPublicKey:
|
|
||||||
signature = Marshal(asn1Sig)
|
|
||||||
|
|
||||||
case *dsaPublicKey:
|
|
||||||
signature = make([]byte, 40)
|
|
||||||
r := asn1Sig.R.Bytes()
|
|
||||||
s := asn1Sig.S.Bytes()
|
|
||||||
copy(signature[20-len(r):20], r)
|
|
||||||
copy(signature[40-len(s):40], s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Signature{
|
|
||||||
Format: s.pubKey.Type(),
|
|
||||||
Blob: signature,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPublicKey takes an *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey,
|
|
||||||
// or ed25519.PublicKey returns a corresponding PublicKey instance.
|
|
||||||
// ECDSA keys must use P-256, P-384 or P-521.
|
|
||||||
func NewPublicKey(key interface{}) (PublicKey, error) {
|
|
||||||
switch key := key.(type) {
|
|
||||||
case *rsa.PublicKey:
|
|
||||||
return (*rsaPublicKey)(key), nil
|
|
||||||
case *ecdsa.PublicKey:
|
|
||||||
if !supportedEllipticCurve(key.Curve) {
|
|
||||||
return nil, errors.New("ssh: only P-256, P-384 and P-521 EC keys are supported.")
|
|
||||||
}
|
|
||||||
return (*ecdsaPublicKey)(key), nil
|
|
||||||
case *dsa.PublicKey:
|
|
||||||
return (*dsaPublicKey)(key), nil
|
|
||||||
case ed25519.PublicKey:
|
|
||||||
return (ed25519PublicKey)(key), nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("ssh: unsupported key type %T", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParsePrivateKey returns a Signer from a PEM encoded private key. It supports
|
|
||||||
// the same keys as ParseRawPrivateKey.
|
|
||||||
func ParsePrivateKey(pemBytes []byte) (Signer, error) {
|
|
||||||
key, err := ParseRawPrivateKey(pemBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewSignerFromKey(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// encryptedBlock tells whether a private key is
|
|
||||||
// encrypted by examining its Proc-Type header
|
|
||||||
// for a mention of ENCRYPTED
|
|
||||||
// according to RFC 1421 Section 4.6.1.1.
|
|
||||||
func encryptedBlock(block *pem.Block) bool {
|
|
||||||
return strings.Contains(block.Headers["Proc-Type"], "ENCRYPTED")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseRawPrivateKey returns a private key from a PEM encoded private key. It
|
|
||||||
// supports RSA (PKCS#1), DSA (OpenSSL), and ECDSA private keys.
|
|
||||||
func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
|
|
||||||
block, _ := pem.Decode(pemBytes)
|
|
||||||
if block == nil {
|
|
||||||
return nil, errors.New("ssh: no key found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if encryptedBlock(block) {
|
|
||||||
return nil, errors.New("ssh: cannot decode encrypted private keys")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch block.Type {
|
|
||||||
case "RSA PRIVATE KEY":
|
|
||||||
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
||||||
case "EC PRIVATE KEY":
|
|
||||||
return x509.ParseECPrivateKey(block.Bytes)
|
|
||||||
case "DSA PRIVATE KEY":
|
|
||||||
return ParseDSAPrivateKey(block.Bytes)
|
|
||||||
case "OPENSSH PRIVATE KEY":
|
|
||||||
return parseOpenSSHPrivateKey(block.Bytes)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseDSAPrivateKey returns a DSA private key from its ASN.1 DER encoding, as
|
|
||||||
// specified by the OpenSSL DSA man page.
|
|
||||||
func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {
|
|
||||||
var k struct {
|
|
||||||
Version int
|
|
||||||
P *big.Int
|
|
||||||
Q *big.Int
|
|
||||||
G *big.Int
|
|
||||||
Pub *big.Int
|
|
||||||
Priv *big.Int
|
|
||||||
}
|
|
||||||
rest, err := asn1.Unmarshal(der, &k)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("ssh: failed to parse DSA key: " + err.Error())
|
|
||||||
}
|
|
||||||
if len(rest) > 0 {
|
|
||||||
return nil, errors.New("ssh: garbage after DSA key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &dsa.PrivateKey{
|
|
||||||
PublicKey: dsa.PublicKey{
|
|
||||||
Parameters: dsa.Parameters{
|
|
||||||
P: k.P,
|
|
||||||
Q: k.Q,
|
|
||||||
G: k.G,
|
|
||||||
},
|
|
||||||
Y: k.Pub,
|
|
||||||
},
|
|
||||||
X: k.Priv,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implemented based on the documentation at
|
|
||||||
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
|
|
||||||
func parseOpenSSHPrivateKey(key []byte) (crypto.PrivateKey, error) {
|
|
||||||
magic := append([]byte("openssh-key-v1"), 0)
|
|
||||||
if !bytes.Equal(magic, key[0:len(magic)]) {
|
|
||||||
return nil, errors.New("ssh: invalid openssh private key format")
|
|
||||||
}
|
|
||||||
remaining := key[len(magic):]
|
|
||||||
|
|
||||||
var w struct {
|
|
||||||
CipherName string
|
|
||||||
KdfName string
|
|
||||||
KdfOpts string
|
|
||||||
NumKeys uint32
|
|
||||||
PubKey []byte
|
|
||||||
PrivKeyBlock []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := Unmarshal(remaining, &w); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.KdfName != "none" || w.CipherName != "none" {
|
|
||||||
return nil, errors.New("ssh: cannot decode encrypted private keys")
|
|
||||||
}
|
|
||||||
|
|
||||||
pk1 := struct {
|
|
||||||
Check1 uint32
|
|
||||||
Check2 uint32
|
|
||||||
Keytype string
|
|
||||||
Rest []byte `ssh:"rest"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
if err := Unmarshal(w.PrivKeyBlock, &pk1); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if pk1.Check1 != pk1.Check2 {
|
|
||||||
return nil, errors.New("ssh: checkint mismatch")
|
|
||||||
}
|
|
||||||
|
|
||||||
// we only handle ed25519 and rsa keys currently
|
|
||||||
switch pk1.Keytype {
|
|
||||||
case KeyAlgoRSA:
|
|
||||||
// https://github.com/openssh/openssh-portable/blob/master/sshkey.c#L2760-L2773
|
|
||||||
key := struct {
|
|
||||||
N *big.Int
|
|
||||||
E *big.Int
|
|
||||||
D *big.Int
|
|
||||||
Iqmp *big.Int
|
|
||||||
P *big.Int
|
|
||||||
Q *big.Int
|
|
||||||
Comment string
|
|
||||||
Pad []byte `ssh:"rest"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
if err := Unmarshal(pk1.Rest, &key); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, b := range key.Pad {
|
|
||||||
if int(b) != i+1 {
|
|
||||||
return nil, errors.New("ssh: padding not as expected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pk := &rsa.PrivateKey{
|
|
||||||
PublicKey: rsa.PublicKey{
|
|
||||||
N: key.N,
|
|
||||||
E: int(key.E.Int64()),
|
|
||||||
},
|
|
||||||
D: key.D,
|
|
||||||
Primes: []*big.Int{key.P, key.Q},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pk.Validate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pk.Precompute()
|
|
||||||
|
|
||||||
return pk, nil
|
|
||||||
case KeyAlgoED25519:
|
|
||||||
key := struct {
|
|
||||||
Pub []byte
|
|
||||||
Priv []byte
|
|
||||||
Comment string
|
|
||||||
Pad []byte `ssh:"rest"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
if err := Unmarshal(pk1.Rest, &key); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(key.Priv) != ed25519.PrivateKeySize {
|
|
||||||
return nil, errors.New("ssh: private key unexpected length")
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, b := range key.Pad {
|
|
||||||
if int(b) != i+1 {
|
|
||||||
return nil, errors.New("ssh: padding not as expected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize))
|
|
||||||
copy(pk, key.Priv)
|
|
||||||
return &pk, nil
|
|
||||||
default:
|
|
||||||
return nil, errors.New("ssh: unhandled key type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FingerprintLegacyMD5 returns the user presentation of the key's
|
|
||||||
// fingerprint as described by RFC 4716 section 4.
|
|
||||||
func FingerprintLegacyMD5(pubKey PublicKey) string {
|
|
||||||
md5sum := md5.Sum(pubKey.Marshal())
|
|
||||||
hexarray := make([]string, len(md5sum))
|
|
||||||
for i, c := range md5sum {
|
|
||||||
hexarray[i] = hex.EncodeToString([]byte{c})
|
|
||||||
}
|
|
||||||
return strings.Join(hexarray, ":")
|
|
||||||
}
|
|
||||||
|
|
||||||
// FingerprintSHA256 returns the user presentation of the key's
|
|
||||||
// fingerprint as unpadded base64 encoded sha256 hash.
|
|
||||||
// This format was introduced from OpenSSH 6.8.
|
|
||||||
// https://www.openssh.com/txt/release-6.8
|
|
||||||
// https://tools.ietf.org/html/rfc4648#section-3.2 (unpadded base64 encoding)
|
|
||||||
func FingerprintSHA256(pubKey PublicKey) string {
|
|
||||||
sha256sum := sha256.Sum256(pubKey.Marshal())
|
|
||||||
hash := base64.RawStdEncoding.EncodeToString(sha256sum[:])
|
|
||||||
return "SHA256:" + hash
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user